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