index.vue 10 KB

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