index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <template>
  2. <div class="main-box">
  3. <TreeFilter title="部门列表" :is-all="false" :data="treeFilterData" :default-value="initParam.deptId" @change="changeTreeFilter" />
  4. <div class="table-box">
  5. <ProTable
  6. ref="proTable"
  7. row-key="userId"
  8. :indent="20"
  9. :columns="columns"
  10. :request-api="listUserApi"
  11. :request-auto="false"
  12. :init-param="initParam"
  13. :search-col="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 3 }"
  14. >
  15. <!-- 表格 header 按钮 -->
  16. <template #tableHeader="scope">
  17. <el-button type="primary" v-auth="['system:user:add']" icon="CirclePlus" @click="openDialog(1, '用户新增')"> 新增 </el-button>
  18. <el-button type="primary" v-auth="['system:user:import']" icon="Upload" plain @click="batchAdd">导入</el-button>
  19. <el-button type="primary" v-auth="['system:user:export']" icon="Download" plain @click="downloadFile">导出</el-button>
  20. <el-button
  21. type="danger"
  22. v-auth="['system:user:remove']"
  23. icon="Delete"
  24. plain
  25. :disabled="!scope.isSelected"
  26. @click="batchDelete(scope.selectedListIds)"
  27. >
  28. 批量删除
  29. </el-button>
  30. </template>
  31. <!-- 表格操作 -->
  32. <template #operation="scope">
  33. <el-button type="primary" link v-if="scope.row.userId !== 1" icon="EditPen" @click="openDialog(2, '用户编辑', scope.row)">编辑</el-button>
  34. <el-button type="primary" link v-if="scope.row.userId !== 1" icon="Delete" @click="deleteAccount(scope.row)">删除</el-button>
  35. <el-button type="primary" link v-if="scope.row.userId !== 1" icon="View" @click="handleResetPwd(scope.row)">重置密码</el-button>
  36. </template>
  37. </ProTable>
  38. <FormDialog ref="formDialogRef" />
  39. <ImportExcel ref="dialogRef" />
  40. </div>
  41. </div>
  42. </template>
  43. <script setup lang="tsx" name="UserManage">
  44. import { onMounted, reactive, ref } from 'vue'
  45. import { User } from '@/api/interface'
  46. import { useDownload } from '@/hooks/useDownload'
  47. import { useHandleData } from '@/hooks/useHandleData'
  48. import { ElMessageBox, ElMessage } from 'element-plus'
  49. import ProTable from '@/components/ProTable/index.vue'
  50. import TreeFilter from '@/components/TreeFilter/index.vue'
  51. import ImportExcel from '@/components/ImportExcel/index.vue'
  52. import FormDialog from '@/components/FormDialog/index.vue'
  53. import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
  54. import {
  55. listUserApi,
  56. delUserApi,
  57. addUserApi,
  58. updateUserApi,
  59. changeUserStatus,
  60. importTemplateApi,
  61. importDataApi,
  62. exportApi,
  63. getUserApi,
  64. deptTreeSelectApi,
  65. resetUserPwdApi
  66. } from '@/api/modules/system/user'
  67. import { getDictsApi } from '@/api/modules/system/dictData'
  68. onMounted(() => {
  69. getTreeFilter()
  70. })
  71. // ProTable 实例
  72. const proTable = ref<ProTableInstance>()
  73. // 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
  74. const initParam = reactive({ deptId: '' })
  75. // 获取 treeFilter 数据
  76. // 当 proTable 的 requestAuto 属性为 false,不会自动请求表格数据,等待 treeFilter 数据回来之后,更改 initParam.departmentId 的值,才会触发请求 proTable 数据
  77. const treeFilterData = ref<any>([])
  78. const getTreeFilter = async () => {
  79. const { data } = await deptTreeSelectApi()
  80. treeFilterData.value = data
  81. initParam.deptId = treeFilterData.value[0].id
  82. }
  83. // 树形筛选切换
  84. const changeTreeFilter = (val: string) => {
  85. // ElMessage.success('请注意查看请求参数变化 🤔')
  86. proTable.value!.pageable.pageNum = 1
  87. initParam.deptId = val
  88. }
  89. // 切换用户状态
  90. const changeStatus = async (row: User.ResUserList) => {
  91. await useHandleData(changeUserStatus, { userId: row.userId, status: row.status == '1' ? 0 : 1 }, `切换【${row.userName}】用户状态`)
  92. proTable.value?.getTableList()
  93. }
  94. // 删除用户信息
  95. const deleteAccount = async (params: User.ResUserList) => {
  96. await useHandleData(delUserApi, params.userId, `删除【${params.userName}】用户`)
  97. proTable.value?.getTableList()
  98. }
  99. // 批量删除用户信息
  100. const batchDelete = async (ids: string[]) => {
  101. await useHandleData(delUserApi, ids, '删除所选用户信息')
  102. proTable.value?.clearSelection()
  103. proTable.value?.getTableList()
  104. }
  105. function handleResetPwd(row: any) {
  106. ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', {
  107. confirmButtonText: '确定',
  108. cancelButtonText: '取消',
  109. closeOnClickModal: false,
  110. inputPattern: /^.{5,20}$/,
  111. inputErrorMessage: '用户密码长度必须介于 5 和 20 之间'
  112. })
  113. .then(({ value }: any) => {
  114. resetUserPwdApi({ userId: row.userId, password: value }).then(res => {
  115. if (res.code == 200) ElMessage.success('修改成功,新密码是:' + value)
  116. else ElMessage.error(res.msg)
  117. })
  118. })
  119. .catch((e: any) => {
  120. console.log(e)
  121. })
  122. }
  123. // 导出用户列表
  124. const downloadFile = async () => {
  125. ElMessageBox.confirm('确认导出用户数据?', '温馨提示', { type: 'warning' }).then(() =>
  126. useDownload(exportApi, '用户列表', proTable.value?.searchParam)
  127. )
  128. }
  129. // 批量添加用户
  130. const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
  131. const batchAdd = () => {
  132. const params = {
  133. title: '用户',
  134. tempApi: importTemplateApi,
  135. importApi: importDataApi,
  136. getTableList: proTable.value?.getTableList
  137. }
  138. dialogRef.value?.acceptParams(params)
  139. }
  140. const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
  141. const postOptions = ref<any[]>([])
  142. const roleOptions = ref<any[]>([])
  143. // 打开弹框的功能
  144. const openDialog = async (type: number, title: string, row?: any) => {
  145. let res = await getUserApi(row?.userId || null)
  146. postOptions.value = res.data.posts
  147. roleOptions.value = res.data.roles
  148. // 表单项配置
  149. setItemsOptions()
  150. if (type == 1) {
  151. itemsOptions.splice(
  152. 4,
  153. 0,
  154. {
  155. label: '用户名称',
  156. prop: 'userName',
  157. span: 12,
  158. rules: [
  159. { required: true, message: '用户名称不能为空' },
  160. { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
  161. ],
  162. compOptions: {
  163. placeholder: '请输入用户名称'
  164. }
  165. },
  166. {
  167. label: '用户密码',
  168. prop: 'password',
  169. span: 12,
  170. rules: [
  171. { required: true, message: '密码不能为空' },
  172. { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
  173. ],
  174. compOptions: {
  175. showPassword: true,
  176. type: 'password',
  177. placeholder: '请输入密码'
  178. }
  179. }
  180. )
  181. }
  182. if (row?.userId) {
  183. res.data.user.postIds = res.data.postIds
  184. res.data.user.roleIds = res.data.roleIds
  185. }
  186. const params = {
  187. title,
  188. width: 680,
  189. isEdit: type !== 3,
  190. itemsOptions: itemsOptions,
  191. model: type == 1 ? {} : res.data.user,
  192. api: type == 1 ? addUserApi : updateUserApi,
  193. getTableList: proTable.value?.getTableList
  194. }
  195. formDialogRef.value?.openDialog(params)
  196. }
  197. // 表格配置项
  198. const columns = reactive<ColumnProps<User.ResUserList>[]>([
  199. { type: 'selection', fixed: 'left', width: 70 },
  200. { prop: 'userId', label: '用户编号' },
  201. { prop: 'userName', label: '用户名称', search: { el: 'input' }, width: 120 },
  202. { prop: 'nickName', label: '用户昵称', search: { el: 'input' }, width: 120 },
  203. { prop: 'dept.deptName', label: '部门', width: 120 },
  204. { prop: 'phonenumber', label: '手机号码', width: 120 },
  205. {
  206. prop: 'status',
  207. label: '用户状态',
  208. width: 120,
  209. enum: () => getDictsApi('sys_normal_disable'),
  210. search: { el: 'tree-select' },
  211. fieldNames: { label: 'dictLabel', value: 'dictValue' },
  212. render: scope => {
  213. return (
  214. <el-switch
  215. model-value={scope.row.status}
  216. active-text={scope.row.status === '1' ? '禁用' : '启用'}
  217. active-value={'0'}
  218. inactive-value={'1'}
  219. onClick={() => changeStatus(scope.row)}
  220. />
  221. )
  222. }
  223. },
  224. {
  225. prop: 'sex',
  226. label: '性别',
  227. width: 120,
  228. enum: () => getDictsApi('sys_user_gender'),
  229. fieldNames: {
  230. label: 'dictLabel',
  231. value: 'dictValue'
  232. }
  233. },
  234. { prop: 'createTime', label: '创建时间', width: 180 },
  235. { prop: 'operation', label: '操作', width: 240, fixed: 'right' }
  236. ])
  237. // 表单配置项
  238. let itemsOptions: ProForm.ItemsOptions[] = []
  239. const setItemsOptions = () => {
  240. itemsOptions = [
  241. {
  242. label: '用户昵称',
  243. span: 12,
  244. prop: 'nickName',
  245. rules: [{ required: true, message: '用户昵称不能为空' }],
  246. compOptions: {
  247. placeholder: '请输入用户昵称'
  248. }
  249. },
  250. {
  251. label: '归属部门',
  252. span: 12,
  253. prop: 'deptId',
  254. compOptions: {
  255. elTagName: 'tree-select',
  256. enum: () => deptTreeSelectApi(),
  257. valueKey: 'id',
  258. labelKey: 'label',
  259. checkStrictly: true,
  260. placeholder: '请选择归属部门'
  261. }
  262. },
  263. {
  264. label: '手机号码',
  265. span: 12,
  266. prop: 'phonenumber',
  267. rules: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
  268. compOptions: {
  269. placeholder: '请输入手机号码'
  270. }
  271. },
  272. {
  273. label: '邮箱',
  274. span: 12,
  275. prop: 'email',
  276. rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
  277. compOptions: {
  278. placeholder: '请输入邮箱'
  279. }
  280. },
  281. {
  282. label: '性别',
  283. span: 12,
  284. prop: 'sex',
  285. compOptions: {
  286. elTagName: 'radio-group',
  287. enum: () => getDictsApi('sys_user_gender'),
  288. labelKey: 'dictLabel',
  289. valueKey: 'dictValue',
  290. placeholder: '请输入邮箱'
  291. }
  292. },
  293. {
  294. label: '状态',
  295. span: 12,
  296. prop: 'status',
  297. compOptions: {
  298. elTagName: 'radio-group',
  299. value: '0',
  300. enum: () => getDictsApi('sys_normal_disable'),
  301. labelKey: 'dictLabel',
  302. valueKey: 'dictValue'
  303. }
  304. },
  305. {
  306. label: '岗位',
  307. span: 12,
  308. prop: 'postIds',
  309. compOptions: {
  310. elTagName: 'select',
  311. valueKey: 'postId',
  312. labelKey: 'postName',
  313. multiple: true,
  314. enum: postOptions.value,
  315. placeholder: '请选择岗位'
  316. }
  317. },
  318. {
  319. label: '角色',
  320. span: 12,
  321. prop: 'roleIds',
  322. compOptions: {
  323. elTagName: 'select',
  324. enum: roleOptions.value,
  325. valueKey: 'roleId',
  326. labelKey: 'roleName',
  327. multiple: true
  328. }
  329. },
  330. {
  331. label: '备注',
  332. prop: 'remark',
  333. compOptions: {
  334. type: 'textarea',
  335. placeholder: '请输入内容'
  336. }
  337. }
  338. ]
  339. }
  340. </script>