index.vue 16 KB

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