index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. <template>
  2. <div class="pageWrap bgc">
  3. <div class="aside-box-system">
  4. <el-aside width="200px" style="height: 100%">
  5. <el-container>
  6. <el-header>
  7. <el-input v-model="groupFilterText" placeholder="输入关键字进行过滤" clearable style="margin-top: 10px" />
  8. </el-header>
  9. <el-main class="nopadding">
  10. <el-tree
  11. ref="treeRef"
  12. class="menu-tree"
  13. :data="treeData"
  14. node-key="id"
  15. :current-node-key="''"
  16. :highlight-current="true"
  17. :expand-on-click-node="false"
  18. :props="defaultProps"
  19. default-expand-all
  20. :filter-node-method="filterNode"
  21. @node-click="roleClick"
  22. />
  23. </el-main>
  24. </el-container>
  25. </el-aside>
  26. </div>
  27. <!-- <el-divider direction="vertical" style="height: 100%" />-->
  28. <div class="content-warp flex-column-page-wrap">
  29. <!-- 公用搜索组件 -->
  30. <LeSearchForm ref="searchForm" v-model:searchData="searchData" :forms="forms" :loading="tableOpts.options.loading"></LeSearchForm>
  31. <!-- LeTable 组件使用 -->
  32. <LeTable
  33. ref="tableRef"
  34. v-model:searchParams="tableOpts.searchParams"
  35. v-bind="tableOpts"
  36. v-model:curRow="tableOpts.curRow"
  37. v-model:checked-options="checkedColumns"
  38. :columns="activeColumns"
  39. >
  40. <template #toolLeft>
  41. <el-button type="primary" @click="addHandler">
  42. <el-icon class="btn-icon">
  43. <Plus />
  44. </el-icon>
  45. </el-button>
  46. <el-button type="danger" :disabled="curSelectionRows.length === 0" @click="batch_del">
  47. <el-icon class="btn-icon">
  48. <Delete />
  49. </el-icon>
  50. </el-button>
  51. <el-button plain :disabled="curSelectionRows.length === 0" @click="chooseThing('role')"> 分配角色</el-button>
  52. <el-button plain :disabled="curSelectionRows.length === 0" @click="chooseThing('department')"> 分配部门 </el-button>
  53. <el-button plain :disabled="curSelectionRows.length === 0" @click="resetPwdVisibile = true"> 密码重置 </el-button>
  54. </template>
  55. <template #filterAvatarSlot="scope">
  56. <el-avatar :src="scope.row.avatar" size="small"></el-avatar>
  57. </template>
  58. <template #statusSlot="scope">
  59. <status-indicator v-if="scope.row.status === 1" pulse type="success"></status-indicator>
  60. <status-indicator v-else pulse type="danger"></status-indicator>
  61. </template>
  62. <template #actionSlot="scope">
  63. <el-tooltip content="编辑" placement="bottom" effect="light">
  64. <el-icon class="ibt0" @click="table_edit(scope.row)">
  65. <Edit />
  66. </el-icon>
  67. </el-tooltip>
  68. <el-divider direction="vertical"></el-divider>
  69. <el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row)">
  70. <template #reference>
  71. <el-icon class="ibt0">
  72. <Delete />
  73. </el-icon>
  74. </template>
  75. </el-popconfirm>
  76. </template>
  77. </LeTable>
  78. </div>
  79. <LeFormConfigDialog
  80. v-if="visible"
  81. ref="dialogUserRef"
  82. v-model="visible"
  83. :title="`${isCreate ? '新增' : '编辑'}用户`"
  84. width="600px"
  85. :form-data="activeData"
  86. :form-options="formOptions"
  87. @submit="submitHandler"
  88. />
  89. <assign-role-dialog
  90. v-if="assignRoleVisibile"
  91. v-model="assignRoleVisibile"
  92. :user-ids="curSelectionRows.map(item => item.id)"
  93. :type-model="typeModel"
  94. @success-fn="updateParams()"
  95. @closed="assignRoleVisibile = false"
  96. ></assign-role-dialog>
  97. <reset-pwd
  98. v-if="resetPwdVisibile"
  99. v-model="resetPwdVisibile"
  100. :user-ids="curSelectionRows.map(item => item.id)"
  101. @closed="resetPwdVisibile = false"
  102. >
  103. </reset-pwd>
  104. </div>
  105. </template>
  106. <script lang="tsx" setup>
  107. import role from '@/api/system/role'
  108. import user from '@/api/system/user'
  109. import department from '@/api/system/department'
  110. import { computed, nextTick, ref, watch } from 'vue'
  111. import { ElMessage, ElMessageBox, ElTree } from 'element-plus'
  112. import { useTablePage } from '@/hooks/useTablePage'
  113. import { Plus, Delete } from '@element-plus/icons-vue'
  114. import StatusIndicator from '@/components/StatusIndicator'
  115. import AssignRoleDialog from './assign-role.vue'
  116. import ResetPwd from './reset-pwd.vue'
  117. const visible = ref(false) // 弹窗显示隐藏
  118. const isCreate = ref(true)
  119. const activeData = ref({})
  120. const formsDialog = [
  121. {
  122. prop: 'username',
  123. label: '登录账号',
  124. itemType: 'input',
  125. placeholder: '用于登录系统',
  126. rules: [{ required: true, message: '请输入登录账号', trigger: 'blur' }]
  127. },
  128. {
  129. prop: 'password',
  130. label: '登录密码',
  131. itemType: 'input',
  132. rules: [{ required: true, message: '请输入登录密码', trigger: 'blur' }]
  133. },
  134. {
  135. prop: 'password2',
  136. label: '确认密码',
  137. itemType: 'input',
  138. rules: [{ required: true, message: '请再次输入密码', trigger: 'blur' }]
  139. },
  140. {
  141. prop: 'nickName',
  142. label: '昵称',
  143. itemType: 'input'
  144. },
  145. {
  146. prop: 'realName',
  147. label: '姓名',
  148. itemType: 'input'
  149. },
  150. {
  151. prop: 'sex',
  152. label: '性别',
  153. itemType: 'radio',
  154. options: [
  155. { value: '男', label: '男' },
  156. { value: '女', label: '女' }
  157. ]
  158. },
  159. {
  160. prop: 'status',
  161. label: '状态',
  162. itemType: 'switch',
  163. activeText: '正常',
  164. inactiveText: '禁用'
  165. },
  166. {
  167. prop: 'roleIds',
  168. label: '所属角色',
  169. itemType: 'select',
  170. multiple: true,
  171. collapseTags: true,
  172. maxCollapseTags: 1,
  173. filterable: true,
  174. options: []
  175. },
  176. {
  177. prop: 'departmentIds',
  178. label: '所属部门',
  179. itemType: 'select',
  180. multiple: true,
  181. collapseTags: true,
  182. maxCollapseTags: 1,
  183. filterable: true,
  184. options: []
  185. }
  186. ]
  187. // 新增的表单 和 编辑的表单
  188. const formOptions = computed(() => {
  189. // 去掉 formsDialog 下标为 1 2 的数据
  190. const { 1: password, 2: password2, ...rest } = formsDialog
  191. let editFormDialog = []
  192. for (let i in rest) {
  193. editFormDialog.push(rest[i])
  194. }
  195. return {
  196. forms: isCreate.value ? formsDialog : editFormDialog,
  197. labelWidth: 120,
  198. span: 30,
  199. formConfig: {
  200. showCancelBtn: true,
  201. submitLoading: false
  202. }
  203. }
  204. })
  205. const showGroupLoading = ref(false)
  206. const assignRoleVisibile = ref(false)
  207. const resetPwdVisibile = ref(false)
  208. const groupFilterText = ref('')
  209. const treeRef = ref<InstanceType<typeof ElTree>>()
  210. const defaultProps = {
  211. children: 'children',
  212. label: 'label'
  213. }
  214. const filterNode = (value: string, data: Tree) => {
  215. if (!value) return true
  216. return data.label.includes(value)
  217. }
  218. const treeData = ref([])
  219. const typeModel = ref('')
  220. // 获取左侧菜单数据
  221. const getGroup = async () => {
  222. showGroupLoading.value = true
  223. let data = await role.roleListTreeApi()
  224. showGroupLoading.value = false
  225. data.unshift({ id: '', label: '所有' })
  226. treeData.value = data // console.log('获取左侧菜单数据')
  227. }
  228. // 获取全部的角色列表
  229. const getRolesList = async () => {
  230. try {
  231. const data = await role.roleListAllApi()
  232. formOptions.value.forms[7].options = data.map(item => {
  233. return { value: item.id, label: item.name }
  234. })
  235. } catch (e) {
  236. console.log(e)
  237. }
  238. }
  239. // 获取全部的部门列表
  240. const getDepartmentsList = async () => {
  241. try {
  242. const data = await department.departmentListAllApi()
  243. formOptions.value.forms[8].options = data.map(item => {
  244. return { value: item.id, label: item.name }
  245. })
  246. } catch (e) {
  247. console.log(e)
  248. }
  249. }
  250. // 左侧菜单点击
  251. const roleClick = data => {
  252. console.log(data.id, 'data.id')
  253. // 修改search参数 watch 变更 自动刷新 列表
  254. // searchData.value = { ...searchData.value, data: { roleId: data.id ? data.id : null } }
  255. searchData.value = { ...searchData.value, roleId: data.id || null }
  256. }
  257. // 表格搜索条件
  258. const forms = ref([
  259. {
  260. prop: 'name',
  261. label: '账号/姓名:',
  262. itemType: 'input',
  263. placeholder: '请输入登录账号 / 姓名'
  264. },
  265. {
  266. prop: 'status',
  267. label: '状态:',
  268. itemType: 'select',
  269. placeholder: '请选择状态',
  270. options: [
  271. {
  272. label: '禁用',
  273. value: 0
  274. },
  275. {
  276. label: '正常',
  277. value: 1
  278. }
  279. ]
  280. }
  281. ])
  282. // table列表数据请求
  283. const queryList = async () => {
  284. const { options, searchParams } = tableOpts
  285. options.loading = true
  286. console.log('搜索参数: ', JSON.stringify(tableOpts.searchParams))
  287. try {
  288. const { records: list, total } = await user.userPageApi(searchParams)
  289. tableOpts.total = total
  290. tableOpts.list = list
  291. } catch {
  292. console.log('获取列表数据失败')
  293. tableOpts.total = 0
  294. tableOpts.list = []
  295. options.loading = false // 更改加载中的 loading值
  296. } finally {
  297. options.loading = false
  298. }
  299. }
  300. // table 参数
  301. const columns = [
  302. {
  303. prop: 'filterAvatar',
  304. label: '头像',
  305. minWidth: 80,
  306. slots: {
  307. default: 'filterAvatarSlot'
  308. }
  309. },
  310. {
  311. prop: 'username',
  312. label: '登录账号',
  313. minWidth: 100
  314. },
  315. {
  316. prop: 'status',
  317. label: '状态',
  318. minWidth: 60,
  319. slots: {
  320. default: 'statusSlot'
  321. }
  322. },
  323. {
  324. prop: 'nickName',
  325. label: '昵称',
  326. minWidth: 100
  327. },
  328. {
  329. prop: 'realName',
  330. label: '姓名',
  331. minWidth: 100
  332. },
  333. {
  334. prop: 'sex',
  335. label: '性别',
  336. minWidth: 80
  337. },
  338. {
  339. prop: 'updateBy',
  340. label: '修改人',
  341. minWidth: 100
  342. },
  343. {
  344. prop: 'updateTime',
  345. label: '修改时间',
  346. minWidth: 150
  347. },
  348. {
  349. prop: 'createBy',
  350. label: '创建人',
  351. minWidth: 100
  352. },
  353. {
  354. prop: 'createTime',
  355. label: '创建时间',
  356. minWidth: 150
  357. },
  358. {
  359. prop: 'action',
  360. label: '操作',
  361. width: 100,
  362. fixed: 'right',
  363. slots: {
  364. default: 'actionSlot'
  365. }
  366. }
  367. ]
  368. const { searchData, tableOpts, checkedColumns, activeColumns, curSelectionRows, updateParams } = useTablePage(
  369. {
  370. options: {
  371. showIndex: false
  372. },
  373. // 需要展示的列
  374. columns,
  375. // 控制列配置
  376. columnsConfig: {
  377. columns
  378. }
  379. },
  380. {
  381. queryList,
  382. fetchImmediate: false
  383. }
  384. )
  385. // 删除
  386. const deleteItem = async ids => {
  387. // try {
  388. await user.userDeleteApi(ids)
  389. ElMessage.success(`删除成功~`)
  390. updateParams()
  391. // } catch (e) {
  392. // console.log('删除失败')
  393. // ElMessage.error(`删除失败~`)
  394. // }
  395. }
  396. // 单个删除
  397. const table_del = row => {
  398. deleteItem([row.id])
  399. }
  400. //批量删除
  401. const batch_del = () => {
  402. const ids = curSelectionRows.value.map(item => item.id) // 多选数据
  403. ElMessageBox.confirm('是否删除选中数据?', '提示', {
  404. confirmButtonText: '确认',
  405. cancelButtonText: '取消',
  406. type: 'error',
  407. buttonSize: 'default'
  408. }).then(() => {
  409. deleteItem(ids)
  410. })
  411. }
  412. const table_edit = async row => {
  413. try {
  414. const data = await user.userRoleIdsApi({ id: row.id })
  415. isCreate.value = false
  416. activeData.value = { ...row, status: row.status ? true : false, roleIds: data?.roleIds, departmentIds: data?.departmentIds }
  417. visible.value = true
  418. } catch (e) {
  419. ElMessage.error(`获取用户所属角色失败~`)
  420. }
  421. }
  422. // 弹窗事件
  423. const submitHandler = async params => {
  424. formOptions.value.formConfig.submitLoading = true
  425. try {
  426. params.status = params.status ? 1 : 0
  427. params.id = activeData.value.id ? activeData.value.id : null
  428. await user.userAddOrEditSaveApi(params)
  429. ElMessage.success(`${isCreate.value ? '新增' : '修改'}成功~`)
  430. visible.value = false
  431. updateParams()
  432. formOptions.value.formConfig.submitLoading = false
  433. } catch (e) {
  434. console.log(e)
  435. formOptions.value.formConfig.submitLoading = false
  436. }
  437. }
  438. const addHandler = () => {
  439. isCreate.value = true
  440. activeData.value = {}
  441. visible.value = true
  442. }
  443. const chooseThing = item => {
  444. assignRoleVisibile.value = true
  445. typeModel.value = item
  446. }
  447. nextTick(() => {
  448. getGroup()
  449. getRolesList()
  450. getDepartmentsList()
  451. queryList()
  452. })
  453. watch(groupFilterText, val => {
  454. treeRef.value!.filter(val)
  455. })
  456. </script>
  457. <style scoped lang="scss">
  458. .nopadding {
  459. padding: 0px;
  460. }
  461. </style>