index.vue 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <template>
  2. <ContentWrap>
  3. <!-- 列表 -->
  4. <vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
  5. <!-- 操作:新增 -->
  6. <template #toolbar_buttons>
  7. <XButton
  8. type="primary"
  9. preIcon="ep:zoom-in"
  10. :title="t('action.add')"
  11. v-hasPermi="['system:role:create']"
  12. @click="handleCreate()"
  13. />
  14. </template>
  15. <template #actionbtns_default="{ row }">
  16. <XTextButton
  17. preIcon="ep:edit"
  18. :title="t('action.edit')"
  19. v-hasPermi="['system:role:update']"
  20. @click="handleUpdate(row.id)"
  21. />
  22. <XTextButton
  23. preIcon="ep:view"
  24. :title="t('action.detail')"
  25. v-hasPermi="['system:role:update']"
  26. @click="handleDetail(row.id)"
  27. />
  28. <XTextButton
  29. preIcon="ep:basketball"
  30. title="菜单权限"
  31. v-hasPermi="['system:permission:assign-role-menu']"
  32. @click="handleScope('menu', row)"
  33. />
  34. <XTextButton
  35. preIcon="ep:coin"
  36. title="数据权限"
  37. v-hasPermi="['system:permission:assign-role-data-scope']"
  38. @click="handleScope('data', row)"
  39. />
  40. <XTextButton
  41. preIcon="ep:delete"
  42. :title="t('action.del')"
  43. v-hasPermi="['system:role:delete']"
  44. @click="handleDelete(row.id)"
  45. />
  46. </template>
  47. </vxe-grid>
  48. </ContentWrap>
  49. <XModal v-model="dialogVisible" :title="dialogTitle">
  50. <!-- 对话框(添加 / 修改) -->
  51. <Form
  52. v-if="['create', 'update'].includes(actionType)"
  53. :schema="allSchemas.formSchema"
  54. :rules="rules"
  55. ref="formRef"
  56. />
  57. <!-- 对话框(详情) -->
  58. <Descriptions
  59. v-if="actionType === 'detail'"
  60. :schema="allSchemas.detailSchema"
  61. :data="detailRef"
  62. />
  63. <!-- 操作按钮 -->
  64. <template #footer>
  65. <XButton
  66. v-if="['create', 'update'].includes(actionType)"
  67. type="primary"
  68. :title="t('action.save')"
  69. :loading="actionLoading"
  70. @click="submitForm()"
  71. />
  72. <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
  73. </template>
  74. </XModal>
  75. <XModal v-model="dialogScopeVisible" :title="dialogScopeTitle">
  76. <el-form :model="dataScopeForm">
  77. <el-form-item label="角色名称">
  78. <el-input v-model="dataScopeForm.name" :disabled="true" />
  79. </el-form-item>
  80. <el-form-item label="角色标识">
  81. <el-input v-model="dataScopeForm.code" :disabled="true" />
  82. </el-form-item>
  83. <!-- 分配角色的数据权限对话框 -->
  84. <el-form-item label="权限范围" v-if="actionScopeType === 'data'">
  85. <el-select v-model="dataScopeForm.dataScope">
  86. <el-option
  87. v-for="item in dataScopeDictDatas"
  88. :key="parseInt(item.value)"
  89. :label="item.label"
  90. :value="parseInt(item.value)"
  91. />
  92. </el-select>
  93. </el-form-item>
  94. <!-- 分配角色的菜单权限对话框 -->
  95. <el-form-item
  96. label="权限范围"
  97. v-if="
  98. actionScopeType === 'menu' || dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM
  99. "
  100. >
  101. <el-card class="box-card">
  102. <template #header>
  103. 父子联动(选中父节点,自动选择子节点):
  104. <el-switch v-model="checkStrictly" inline-prompt active-text="是" inactive-text="否" />
  105. 全选/全不选:
  106. <el-switch
  107. v-model="treeNodeAll"
  108. inline-prompt
  109. active-text="是"
  110. inactive-text="否"
  111. @change="handleCheckedTreeNodeAll()"
  112. />
  113. </template>
  114. <el-tree
  115. ref="treeRef"
  116. node-key="id"
  117. show-checkbox
  118. :default-checked-keys="defaultCheckedKeys"
  119. :check-strictly="!checkStrictly"
  120. :props="defaultProps"
  121. :data="treeOptions"
  122. empty-text="加载中,请稍后"
  123. />
  124. </el-card>
  125. </el-form-item>
  126. </el-form>
  127. <!-- 操作按钮 -->
  128. <template #footer>
  129. <XButton
  130. type="primary"
  131. :title="t('action.save')"
  132. :loading="actionLoading"
  133. @click="submitScope()"
  134. />
  135. <XButton
  136. :loading="actionLoading"
  137. :title="t('dialog.close')"
  138. @click="dialogScopeVisible = false"
  139. />
  140. </template>
  141. </XModal>
  142. </template>
  143. <script setup lang="ts">
  144. import { onMounted, reactive, ref, unref } from 'vue'
  145. import {
  146. ElForm,
  147. ElFormItem,
  148. ElInput,
  149. ElSelect,
  150. ElOption,
  151. ElTree,
  152. ElCard,
  153. ElSwitch
  154. } from 'element-plus'
  155. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  156. import { useI18n } from '@/hooks/web/useI18n'
  157. import { useMessage } from '@/hooks/web/useMessage'
  158. import { FormExpose } from '@/components/Form'
  159. import { rules, allSchemas } from './role.data'
  160. import { handleTree } from '@/utils/tree'
  161. import { SystemDataScopeEnum } from '@/utils/constants'
  162. import { useVxeGrid } from '@/hooks/web/useVxeGrid'
  163. import { VxeGridInstance } from 'vxe-table'
  164. import * as RoleApi from '@/api/system/role'
  165. import { listSimpleMenusApi } from '@/api/system/menu'
  166. import { listSimpleDeptApi } from '@/api/system/dept'
  167. import * as PermissionApi from '@/api/system/permission'
  168. import type {
  169. PermissionAssignRoleDataScopeReqVO,
  170. PermissionAssignRoleMenuReqVO
  171. } from '@/api/system/permission/types'
  172. const { t } = useI18n() // 国际化
  173. const message = useMessage() // 消息弹窗
  174. // 列表相关的变量
  175. const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
  176. const { gridOptions, reloadList, delList } = useVxeGrid<RoleApi.RoleVO>({
  177. allSchemas: allSchemas,
  178. getListApi: RoleApi.getRolePageApi,
  179. delListApi: RoleApi.deleteRoleApi
  180. })
  181. // ========== CRUD 相关 ==========
  182. const actionLoading = ref(false) // 遮罩层
  183. const actionType = ref('') // 操作按钮的类型
  184. const dialogVisible = ref(false) // 是否显示弹出层
  185. const dialogTitle = ref('edit') // 弹出层标题
  186. const formRef = ref<FormExpose>() // 表单 Ref
  187. const detailRef = ref() // 详情 Ref
  188. // 设置标题
  189. const setDialogTile = (type: string) => {
  190. dialogTitle.value = t('action.' + type)
  191. actionType.value = type
  192. dialogVisible.value = true
  193. }
  194. // 新增操作
  195. const handleCreate = () => {
  196. setDialogTile('create')
  197. }
  198. // 修改操作
  199. const handleUpdate = async (rowId: number) => {
  200. setDialogTile('update')
  201. // 设置数据
  202. const res = await RoleApi.getRoleApi(rowId)
  203. unref(formRef)?.setValues(res)
  204. }
  205. // 删除操作
  206. const handleDelete = async (rowId: number) => {
  207. await delList(xGrid, rowId)
  208. }
  209. // 提交按钮
  210. const submitForm = async () => {
  211. const elForm = unref(formRef)?.getElFormRef()
  212. if (!elForm) return
  213. elForm.validate(async (valid) => {
  214. if (valid) {
  215. actionLoading.value = true
  216. // 提交请求
  217. try {
  218. const data = unref(formRef)?.formModel as RoleApi.RoleVO
  219. if (actionType.value === 'create') {
  220. await RoleApi.createRoleApi(data)
  221. message.success(t('common.createSuccess'))
  222. } else {
  223. await RoleApi.updateRoleApi(data)
  224. message.success(t('common.updateSuccess'))
  225. }
  226. dialogVisible.value = false
  227. } finally {
  228. actionLoading.value = false
  229. // 刷新列表
  230. reloadList(xGrid)
  231. }
  232. }
  233. })
  234. }
  235. // 详情操作
  236. const handleDetail = async (rowId: number) => {
  237. setDialogTile('detail')
  238. // 设置数据
  239. const res = await RoleApi.getRoleApi(rowId)
  240. detailRef.value = res
  241. }
  242. // ========== 数据权限 ==========
  243. const dataScopeForm = reactive({
  244. id: 0,
  245. name: '',
  246. code: '',
  247. dataScope: 0,
  248. checkList: []
  249. })
  250. const defaultProps = {
  251. children: 'children',
  252. label: 'name',
  253. value: 'id'
  254. }
  255. const treeOptions = ref<any[]>([]) // 菜单树形结构
  256. const treeRef = ref<InstanceType<typeof ElTree>>()
  257. const dialogScopeVisible = ref(false)
  258. const dialogScopeTitle = ref('数据权限')
  259. const actionScopeType = ref('')
  260. const dataScopeDictDatas = ref()
  261. const defaultCheckedKeys = ref()
  262. // 选项
  263. const checkStrictly = ref(true)
  264. const treeNodeAll = ref(false)
  265. // 全选/全不选
  266. const handleCheckedTreeNodeAll = () => {
  267. treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : [])
  268. }
  269. // 权限操作
  270. const handleScope = async (type: string, row: RoleApi.RoleVO) => {
  271. dataScopeForm.id = row.id
  272. dataScopeForm.name = row.name
  273. dataScopeForm.code = row.code
  274. if (type === 'menu') {
  275. const menuRes = await listSimpleMenusApi()
  276. treeOptions.value = handleTree(menuRes)
  277. const role = await PermissionApi.listRoleMenusApi(row.id)
  278. if (role) {
  279. // treeRef.value!.setCheckedKeys(role as unknown as Array<number>)
  280. defaultCheckedKeys.value = role
  281. }
  282. } else if (type === 'data') {
  283. const deptRes = await listSimpleDeptApi()
  284. treeOptions.value = handleTree(deptRes)
  285. const role = await RoleApi.getRoleApi(row.id)
  286. dataScopeForm.dataScope = role.dataScope
  287. if (role.dataScopeDeptIds) {
  288. // treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false)
  289. defaultCheckedKeys.value = role.dataScopeDeptIds
  290. }
  291. }
  292. actionScopeType.value = type
  293. dialogScopeVisible.value = true
  294. }
  295. // 保存权限
  296. const submitScope = async () => {
  297. const keys = treeRef.value!.getCheckedKeys(false) as unknown as Array<number>
  298. if ('data' === actionScopeType.value) {
  299. const data = ref<PermissionAssignRoleDataScopeReqVO>({
  300. roleId: dataScopeForm.id,
  301. dataScope: dataScopeForm.dataScope,
  302. dataScopeDeptIds: dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM ? [] : keys
  303. })
  304. await PermissionApi.assignRoleDataScopeApi(data.value)
  305. } else if ('menu' === actionScopeType.value) {
  306. const data = ref<PermissionAssignRoleMenuReqVO>({
  307. roleId: dataScopeForm.id,
  308. menuIds: keys
  309. })
  310. await PermissionApi.assignRoleMenuApi(data.value)
  311. }
  312. message.success(t('common.updateSuccess'))
  313. dialogScopeVisible.value = false
  314. }
  315. const init = () => {
  316. dataScopeDictDatas.value = getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)
  317. }
  318. // ========== 初始化 ==========
  319. onMounted(() => {
  320. init()
  321. })
  322. </script>