index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. <template>
  2. <div class="table-box">
  3. <ProTable ref="proTable" :columns="columns" row-key="roleId" :request-api="listRoleApi">
  4. <!-- 表格 header 按钮 -->
  5. <template #tableHeader="scope">
  6. <el-button type="primary" v-auth="['system:role:add']" :icon="CirclePlus" @click="openDialog(1, '角色信息新增')"> 新增 </el-button>
  7. <el-button type="primary" v-auth="['system:role:import']" :icon="Upload" plain @click="batchAdd"> 导入 </el-button>
  8. <el-button type="primary" v-auth="['system:role:export']" :icon="Download" plain @click="downloadFile"> 导出 </el-button>
  9. <el-button
  10. type="danger"
  11. v-auth="['system:user:remove']"
  12. :icon="Delete"
  13. plain
  14. :disabled="!scope.isSelected"
  15. @click="batchDelete(scope.selectedListIds)"
  16. >
  17. 批量删除
  18. </el-button>
  19. </template>
  20. <!-- 表格操作 -->
  21. <template #operation="scope">
  22. <el-button
  23. type="primary"
  24. v-if="scope.row.roleId !== 1"
  25. link
  26. :icon="EditPen"
  27. v-auth="['system:role:edit']"
  28. @click="openDialog(2, '角色编辑', scope.row)"
  29. >
  30. 编辑
  31. </el-button>
  32. <el-button type="primary" v-if="scope.row.roleId !== 1" link :icon="Delete" v-auth="['system:role:remove']" @click="deleteRole(scope.row)">
  33. 删除
  34. </el-button>
  35. <el-button
  36. type="primary"
  37. v-if="scope.row.roleId !== 1"
  38. link
  39. :icon="CircleCheck"
  40. v-auth="['system:role:edit']"
  41. @click="openDataDialog(3, '数据权限', scope.row)"
  42. >
  43. 数据权限
  44. </el-button>
  45. <el-button type="primary" v-if="scope.row.roleId !== 1" link :icon="User" v-auth="['system:role:edit']" @click="handleAuthUser(scope.row)">
  46. 分配用户
  47. </el-button>
  48. </template>
  49. </ProTable>
  50. <FormDialog ref="formDialogRef">
  51. <template #default="{ parameter }">
  52. <ProFrom ref="proFormRef" :items-options="itemsOptions" :disabled="!parameter.isEdit" :form-options="_options" :model="parameter.model">
  53. <template #menuCheckStrictly="{ formModel }">
  54. <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
  55. <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
  56. <el-checkbox v-model="formModel.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')"> 父子联动 </el-checkbox>
  57. <el-tree
  58. class="tree-border"
  59. :data="menuOptions"
  60. show-checkbox
  61. ref="menuRef"
  62. node-key="id"
  63. :check-strictly="!formModel.menuCheckStrictly"
  64. empty-text="加载中,请稍后"
  65. :props="{ label: 'label', children: 'children' }"
  66. />
  67. </template>
  68. </ProFrom>
  69. </template>
  70. <template #footer="{ parameter }">
  71. <el-button type="primary" :loading="butLoading" @click="handleSubmit(proFormRef, parameter, formDialogRef, '1')">确认</el-button>
  72. <el-button @click="handleCancel(formDialogRef)">取消</el-button>
  73. </template>
  74. </FormDialog>
  75. <FormDialog ref="formDataRef">
  76. <template #default="{ parameter }">
  77. <ProFrom ref="proFormDateRef" :items-options="itemsOptions" :form-options="_options" :model="parameter.model">
  78. <template #deptCheckStrictly="{ formModel }">
  79. <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
  80. <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
  81. <el-checkbox v-model="formModel.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')"> 父子联动 </el-checkbox>
  82. <el-tree
  83. class="tree-border"
  84. :data="deptOptions"
  85. show-checkbox
  86. ref="deptRef"
  87. default-expand-all
  88. node-key="id"
  89. :check-strictly="!formModel.deptCheckStrictly"
  90. empty-text="加载中,请稍后"
  91. :props="{ label: 'label', children: 'children' }"
  92. />
  93. </template>
  94. </ProFrom>
  95. </template>
  96. <template #footer="{ parameter }">
  97. <el-button type="primary" :loading="butLoading" @click="handleSubmit(proFormDateRef, parameter, formDataRef)">确认</el-button>
  98. <el-button @click="handleCancel(formDataRef)">取消</el-button>
  99. </template>
  100. </FormDialog>
  101. <ImportExcel ref="dialogRef" />
  102. </div>
  103. </template>
  104. <script setup lang="tsx" name="RoleManage">
  105. import { ref, reactive, nextTick } from 'vue'
  106. import { useHandleData } from '@/hooks/useHandleData'
  107. import { useDownload } from '@/hooks/useDownload'
  108. import { ElMessageBox, ElMessage } from 'element-plus'
  109. import ProTable from '@/components/ProTable/index.vue'
  110. import ImportExcel from '@/components/ImportExcel/index.vue'
  111. import FormDialog from '@/components/CustomDialog/index.vue'
  112. import ProFrom from '@/components/ProForm/index.vue'
  113. import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
  114. import { Delete, EditPen, Download, Upload, CircleCheck, User, CirclePlus } from '@element-plus/icons-vue'
  115. import {
  116. listRoleApi,
  117. delRoleApi,
  118. addRoleApi,
  119. updateRoleApi,
  120. importTemplateApi,
  121. importDataApi,
  122. exportApi,
  123. changeStatusApi,
  124. deptTreeSelectApi,
  125. dataScopeApi,
  126. roleMenuTreeSelectApi
  127. } from '@/api/modules/system/role'
  128. import { treeSelectApi } from '@/api/modules/system/menu'
  129. import { getDictsApi } from '@/api/modules/system/dictData'
  130. import { useRouter } from 'vue-router'
  131. const router = useRouter()
  132. // 菜单列表
  133. const menuOptions = ref<any>()
  134. const menuExpand = ref(false)
  135. const menuNodeAll = ref(false)
  136. const deptExpand = ref(true)
  137. const deptNodeAll = ref(false)
  138. const menuRef = ref<any>(null)
  139. const proFormRef = ref<any>(null)
  140. const proFormDateRef = ref<any>(null)
  141. const deptOptions = ref<any[]>([])
  142. const deptRef = ref<any>(null)
  143. const butLoading = ref(false)
  144. // 数据范围选项
  145. const dataScopeOptions = ref([
  146. { value: '1', label: '全部数据权限' },
  147. { value: '2', label: '自定数据权限' },
  148. { value: '3', label: '本部门数据权限' },
  149. { value: '4', label: '本部门及以下数据权限' },
  150. { value: '5', label: '仅本人数据权限' }
  151. ])
  152. // 获取菜单
  153. const getMenuTreeSelect = () => {
  154. treeSelectApi().then((response: any) => {
  155. if (response.code === 200) {
  156. menuOptions.value = response.data
  157. }
  158. })
  159. }
  160. getMenuTreeSelect()
  161. // 根据角色ID查询菜单树结构
  162. const getRoleMenuTreeSelect = (roleId: any) => {
  163. return roleMenuTreeSelectApi(roleId).then((res: any) => {
  164. menuOptions.value = res.menus
  165. return res
  166. })
  167. }
  168. // 根据角色ID查询部门树结构
  169. const getDeptTree = (roleId: string) => {
  170. return deptTreeSelectApi(roleId).then((res: any) => {
  171. if (res.code === 200) {
  172. deptOptions.value = res.depts
  173. return res
  174. }
  175. })
  176. }
  177. // ProTable 实例
  178. const proTable = ref<ProTableInstance>()
  179. // 删除角色信息信息
  180. const deleteRole = async (params: any) => {
  181. await useHandleData(delRoleApi, params.roleId, `删除【${params.roleName}】角色信息`)
  182. proTable.value?.getTableList()
  183. }
  184. // 批量删除角色信息信息
  185. const batchDelete = async (ids: string[]) => {
  186. await useHandleData(delRoleApi, ids, '删除所选角色信息信息')
  187. proTable.value?.clearSelection()
  188. proTable.value?.getTableList()
  189. }
  190. // 导出角色信息列表
  191. const downloadFile = async () => {
  192. ElMessageBox.confirm('确认导出角色信息数据?', '温馨提示', { type: 'warning' }).then(() =>
  193. useDownload(exportApi, '角色信息列表', proTable.value?.searchParam)
  194. )
  195. }
  196. // 批量添加角色信息
  197. const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
  198. const batchAdd = () => {
  199. const params = {
  200. title: '角色信息',
  201. tempApi: importTemplateApi,
  202. importApi: importDataApi,
  203. getTableList: proTable.value?.getTableList
  204. }
  205. dialogRef.value?.acceptParams(params)
  206. }
  207. const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
  208. // 打开弹框的功能
  209. const openDialog = async (type: number, title: string, row?: any) => {
  210. reset()
  211. // 重置表单
  212. setItemsOptions()
  213. const params = {
  214. title,
  215. width: 720,
  216. isEdit: true,
  217. itemsOptions: itemsOptions,
  218. model: type == 1 ? {} : row,
  219. api: type == 1 ? addRoleApi : updateRoleApi,
  220. getTableList: proTable.value?.getTableList
  221. }
  222. formDialogRef.value?.openDialog(params)
  223. if (row?.roleId) {
  224. getRoleMenuTreeSelect(row?.roleId).then(res => {
  225. let checkedKeys = res.checkedKeys
  226. checkedKeys.forEach((v: any) => {
  227. nextTick(() => {
  228. menuRef.value.setChecked(v, true, false)
  229. })
  230. })
  231. })
  232. }
  233. }
  234. const formDataRef = ref<InstanceType<typeof FormDialog> | null>(null)
  235. // 打开弹框的功能
  236. const openDataDialog = async (type: number, title: string, row?: any) => {
  237. reset()
  238. // 重置表单
  239. setPerDataItemsOptions()
  240. const params = {
  241. title,
  242. width: 720,
  243. isEdit: true,
  244. itemsOptions: itemsOptions,
  245. model: type == 1 ? {} : row,
  246. api: dataScopeApi,
  247. getTableList: proTable.value?.getTableList
  248. }
  249. formDataRef.value?.openDialog(params)
  250. if (row?.roleId) {
  251. nextTick(() => {
  252. getDeptTree(row?.roleId).then(res => {
  253. nextTick(() => {
  254. if (deptRef.value) {
  255. deptRef.value.setCheckedKeys(res.checkedKeys)
  256. }
  257. })
  258. })
  259. })
  260. }
  261. }
  262. // 树权限(展开/折叠
  263. const handleCheckedTreeExpand = (value: any, type: string) => {
  264. if (type === 'menu') {
  265. const nodes = menuRef.value.store._getAllNodes()
  266. nodes.forEach(item => {
  267. item.expanded = value
  268. })
  269. } else if (type === 'dept') {
  270. const nodes = deptRef.value.store._getAllNodes()
  271. nodes.forEach(item => {
  272. item.expanded = value
  273. })
  274. }
  275. }
  276. // 树权限(全选/全不选)
  277. const handleCheckedTreeNodeAll = (value: any, type: string) => {
  278. if (type === 'menu') {
  279. menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
  280. } else if (type === 'dept') {
  281. deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
  282. }
  283. }
  284. // 树权限(父子联动)
  285. const handleCheckedTreeConnect = (value: any, type: string) => {
  286. if (type === 'menu') {
  287. proFormRef.value.formModel.menuCheckStrictly = value ? true : false
  288. } else if (type === 'dept') {
  289. proFormDateRef.value.formModel.deptCheckStrictly = value ? true : false
  290. }
  291. }
  292. // 分配用户
  293. function handleAuthUser(row: any) {
  294. router.push('/system/role-auth/user/' + row.roleId)
  295. }
  296. // 切换绝俗状态
  297. const changeStatus = async (row: any) => {
  298. await useHandleData(changeStatusApi, { roleId: row.roleId, status: row.status == '1' ? 0 : 1 }, `切换【${row.roleName}】角色状态`)
  299. proTable.value?.getTableList()
  300. }
  301. // 所有部门节点数据
  302. const getDeptAllCheckedKeys = () => {
  303. // 目前被选中的部门节点
  304. let checkedKeys = deptRef.value.getCheckedKeys()
  305. // 半选中的部门节点
  306. let halfCheckedKeys = deptRef.value.getHalfCheckedKeys()
  307. checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
  308. return checkedKeys
  309. }
  310. // 所有菜单节点数据
  311. const getMenuAllCheckedKeys = () => {
  312. // 目前被选中的菜单节点
  313. let checkedKeys = menuRef.value.getCheckedKeys()
  314. // 半选中的菜单节点
  315. let halfCheckedKeys = menuRef.value.getHalfCheckedKeys()
  316. checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
  317. return checkedKeys
  318. }
  319. // 提交
  320. const handleSubmit = (proFormRef: any, parameter: any, closeRefVal: any, type?: string) => {
  321. const formEl = proFormRef.proFormRef
  322. butLoading.value = true
  323. if (!formEl) return
  324. formEl.validate(valid => {
  325. if (valid) {
  326. if (type === '1') {
  327. parameter.model.menuIds = getMenuAllCheckedKeys()
  328. } else {
  329. parameter.model.deptIds = getDeptAllCheckedKeys()
  330. }
  331. parameter.api!(parameter.model).then(res => {
  332. if (res.code == 200) {
  333. proFormRef?.resetForm(formEl)
  334. ElMessage.success('操作成功')
  335. handleCancel(closeRefVal)
  336. proTable.value?.getTableList()
  337. } else {
  338. console.log('message', res.message)
  339. }
  340. })
  341. butLoading.value = false
  342. }
  343. butLoading.value = false
  344. })
  345. }
  346. // 取消按钮,重置表单,关闭弹框
  347. const handleCancel = (closeRefVal: any) => {
  348. closeRefVal.handleCancel()
  349. }
  350. const reset = () => {
  351. if (menuRef.value) {
  352. menuRef.value.setCheckedKeys([])
  353. }
  354. menuExpand.value = false
  355. menuNodeAll.value = false
  356. deptExpand.value = true
  357. deptNodeAll.value = false
  358. }
  359. // 表格配置项
  360. const columns = reactive<ColumnProps<any>[]>([
  361. { type: 'selection', fixed: 'left', width: 70 },
  362. { prop: 'roleId', label: '角色编号' },
  363. {
  364. prop: 'roleName',
  365. label: '角色名称'
  366. },
  367. {
  368. prop: 'roleKey',
  369. label: '角色权限字符串'
  370. },
  371. {
  372. prop: 'roleSort',
  373. label: '显示顺序'
  374. },
  375. {
  376. prop: 'status',
  377. label: '角色状态',
  378. enum: () => getDictsApi('sys_normal_disable'),
  379. search: { el: 'tree-select' },
  380. fieldNames: { label: 'dictLabel', value: 'dictValue' },
  381. render: scope => {
  382. return (
  383. <el-switch
  384. model-value={scope.row.status}
  385. active-text={scope.row.status === '1' ? '禁用' : '启用'}
  386. active-value={'0'}
  387. inactive-value={'1'}
  388. onClick={() => changeStatus(scope.row)}
  389. />
  390. )
  391. }
  392. },
  393. {
  394. prop: 'createTime',
  395. label: '创建时间',
  396. search: {
  397. el: 'date-picker',
  398. props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
  399. }
  400. },
  401. { prop: 'operation', label: '操作', width: 350, fixed: 'right' }
  402. ])
  403. let _options = ref<ProForm.FormOptions>({
  404. labelWidth: 120,
  405. hasFooter: false,
  406. disabled: false
  407. })
  408. // 表单配置项
  409. let itemsOptions: ProForm.ItemsOptions[] = []
  410. const setItemsOptions = () => {
  411. itemsOptions = [
  412. {
  413. label: '角色名称',
  414. prop: 'roleName',
  415. rules: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
  416. compOptions: {
  417. placeholder: '请输入角色名称'
  418. }
  419. },
  420. {
  421. label: '角色权限',
  422. prop: 'roleKey',
  423. rules: [{ required: true, message: '角色权限字符串不能为空', trigger: 'blur' }],
  424. tooltip: '@PreAuthorize("@ss.hasPermi(`admin`)")',
  425. compOptions: {
  426. placeholder: '请输入角色权限字符串'
  427. }
  428. },
  429. {
  430. label: '显示顺序',
  431. prop: 'roleSort',
  432. rules: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
  433. compOptions: {
  434. placeholder: '请输入显示顺序'
  435. }
  436. },
  437. {
  438. label: '角色状态',
  439. prop: 'status',
  440. rules: [{ required: true, message: '角色状态不能为空', trigger: 'blur' }],
  441. compOptions: {
  442. placeholder: '请输入角色状态'
  443. }
  444. },
  445. {
  446. label: '菜单权限',
  447. prop: 'menuCheckStrictly',
  448. hideLabelSuffix: true,
  449. compOptions: {
  450. elTagName: 'slot',
  451. placeholder: '请输入菜单树选择项是否关联显示'
  452. }
  453. },
  454. {
  455. label: '备注',
  456. prop: 'remark',
  457. compOptions: {
  458. type: 'textarea',
  459. placeholder: '请输入内容'
  460. }
  461. }
  462. ]
  463. }
  464. const setPerDataItemsOptions = () => {
  465. itemsOptions = [
  466. {
  467. label: '角色名称',
  468. prop: 'roleName',
  469. compOptions: {
  470. disabled: true
  471. }
  472. },
  473. {
  474. label: '权限字符',
  475. prop: 'roleKey',
  476. compOptions: {
  477. disabled: true
  478. }
  479. },
  480. {
  481. label: '权限范围',
  482. prop: 'dataScope',
  483. compOptions: {
  484. elTagName: 'select',
  485. enum: dataScopeOptions.value,
  486. onChange: (val: string) => {
  487. if (val !== '2') {
  488. deptRef.value.setCheckedKeys([])
  489. }
  490. }
  491. }
  492. },
  493. {
  494. label: '数据权限',
  495. prop: 'deptCheckStrictly',
  496. hideLabelSuffix: true,
  497. compOptions: {
  498. elTagName: 'slot'
  499. }
  500. }
  501. ]
  502. }
  503. </script>
  504. <style scoped lang="scss">
  505. @import './index.scss';
  506. </style>