Ver código fonte

REVIEW 角色管理(设置菜单权限)

YunaiV 2 anos atrás
pai
commit
ecde723fa2

+ 1 - 1
src/api/system/permission/index.ts

@@ -17,7 +17,7 @@ export interface PermissionAssignRoleDataScopeReqVO {
 }
 
 // 查询角色拥有的菜单权限
-export const listRoleMenusApi = async (roleId: number) => {
+export const getRoleMenuList = async (roleId: number) => {
   return await request.get({ url: '/system/permission/list-role-resources?roleId=' + roleId })
 }
 

+ 0 - 152
src/views/system/role/MenuPermissionForm.vue

@@ -1,152 +0,0 @@
-<template>
-  <Dialog :title="dialogScopeTitle" v-model="dialogScopeVisible" width="800">
-    <el-form
-      ref="menuPermissionFormRef"
-      :model="dataScopeForm"
-      :inline="true"
-      label-width="80px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="角色名称">
-        <el-tag>{{ dataScopeForm.name }}</el-tag>
-      </el-form-item>
-      <el-form-item label="角色标识">
-        <el-tag>{{ dataScopeForm.code }}</el-tag>
-      </el-form-item>
-      <!-- 分配角色的菜单权限对话框 -->
-      <el-row>
-        <el-col :span="24">
-          <el-form-item label="权限范围" style="display: flex">
-            <el-card class="card" shadow="never">
-              <template #header>
-                父子联动(选中父节点,自动选择子节点):
-                <el-switch
-                  v-model="checkStrictly"
-                  inline-prompt
-                  active-text="是"
-                  inactive-text="否"
-                />
-                全选/全不选:
-                <el-switch
-                  v-model="treeNodeAll"
-                  inline-prompt
-                  active-text="是"
-                  inactive-text="否"
-                  @change="handleCheckedTreeNodeAll()"
-                />
-              </template>
-              <el-tree
-                ref="treeRef"
-                node-key="id"
-                show-checkbox
-                :check-strictly="!checkStrictly"
-                :props="defaultProps"
-                :data="treeOptions"
-                empty-text="加载中,请稍后"
-              />
-            </el-card>
-          </el-form-item> </el-col
-      ></el-row>
-    </el-form>
-    <!-- 操作按钮 -->
-    <template #footer>
-      <div class="dialog-footer">
-        <el-button
-          :title="t('action.save')"
-          :loading="actionLoading"
-          @click="submitScope()"
-          type="primary"
-          :disabled="formLoading"
-        >
-          保存
-        </el-button>
-        <el-button
-          :loading="actionLoading"
-          :title="t('dialog.close')"
-          @click="dialogScopeVisible = false"
-          >取 消</el-button
-        >
-      </div>
-    </template>
-  </Dialog>
-</template>
-
-<script setup lang="ts">
-import * as RoleApi from '@/api/system/role'
-import type { ElTree } from 'element-plus'
-import type { FormExpose } from '@/components/Form'
-import { handleTree, defaultProps } from '@/utils/tree'
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import * as MenuApi from '@/api/system/menu'
-import * as PermissionApi from '@/api/system/permission'
-// ========== CRUD 相关 ==========
-const actionLoading = ref(false) // 遮罩层
-const menuPermissionFormRef = ref<FormExpose>() // 表单 Ref
-const { t } = useI18n() // 国际化
-const dialogScopeTitle = ref('菜单权限')
-const dataScopeDictDatas = ref()
-const message = useMessage() // 消息弹窗
-const actionScopeType = ref('')
-// 选项
-const checkStrictly = ref(true)
-const treeNodeAll = ref(false)
-const dialogScopeVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-const treeOptions = ref<any[]>([]) // 菜单树形结构
-const treeRef = ref<InstanceType<typeof ElTree>>()
-// ========== 数据权限 ==========
-const dataScopeForm = reactive({
-  id: 0,
-  name: '',
-  code: '',
-  dataScope: 0,
-  checkList: []
-})
-
-/** 打开弹窗 */
-const openModal = async (type: string, row: RoleApi.RoleVO) => {
-  dataScopeForm.id = row.id
-  dataScopeForm.name = row.name
-  dataScopeForm.code = row.code
-  actionScopeType.value = type
-  dialogScopeVisible.value = true
-  const menuRes = await MenuApi.getSimpleMenusList()
-  treeOptions.value = handleTree(menuRes)
-  const role = await PermissionApi.listRoleMenusApi(row.id)
-  if (role) {
-    role?.forEach((item: any) => {
-      unref(treeRef)?.setChecked(item, true, false)
-    })
-  }
-}
-
-// 保存权限
-const submitScope = async () => {
-  const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({
-    roleId: dataScopeForm.id,
-    menuIds: [
-      ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>),
-      ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>)
-    ]
-  })
-  await PermissionApi.assignRoleMenuApi(data.value)
-
-  message.success(t('common.updateSuccess'))
-  dialogScopeVisible.value = false
-}
-
-// 全选/全不选
-const handleCheckedTreeNodeAll = () => {
-  treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : [])
-}
-
-const init = () => {
-  dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)
-}
-
-defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
-// ========== 初始化 ==========
-onMounted(() => {
-  init()
-})
-</script>

+ 161 - 0
src/views/system/role/RoleAssignMenuForm.vue

@@ -0,0 +1,161 @@
+<template>
+  <Dialog title="菜单权限" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :inline="true"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="角色名称">
+        <el-tag>{{ formData.name }}</el-tag>
+      </el-form-item>
+      <el-form-item label="角色标识">
+        <el-tag>{{ formData.code }}</el-tag>
+      </el-form-item>
+      <el-form-item label="菜单权限">
+        <el-card class="cardHeight">
+          <template #header>
+            全选/全不选:
+            <el-switch
+              v-model="treeNodeAll"
+              inline-prompt
+              active-text="是"
+              inactive-text="否"
+              @change="handleCheckedTreeNodeAll"
+            />
+            全部展开/折叠:
+            <el-switch
+              v-model="menuExpand"
+              inline-prompt
+              active-text="展开"
+              inactive-text="折叠"
+              @change="handleCheckedTreeExpand"
+            />
+          </template>
+          <el-tree
+            ref="treeRef"
+            node-key="id"
+            show-checkbox
+            :props="defaultProps"
+            :data="menuOptions"
+            empty-text="加载中,请稍候"
+          />
+        </el-card>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="modelVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { handleTree, defaultProps } from '@/utils/tree'
+import * as RoleApi from '@/api/system/role'
+import type { ElTree } from 'element-plus'
+import * as MenuApi from '@/api/system/menu'
+import * as PermissionApi from '@/api/system/permission'
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = reactive({
+  id: 0,
+  name: '',
+  code: '',
+  menuIds: []
+})
+const formRef = ref() // 表单 Ref
+const menuOptions = ref<any[]>([]) // 菜单树形结构
+const menuExpand = ref(false) // 展开/折叠
+const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件 Ref
+const treeNodeAll = ref(false) // 全选/全不选
+
+/** 打开弹窗 */
+const open = async (row: RoleApi.RoleVO) => {
+  modelVisible.value = true
+  resetForm()
+  // 加载 Menu 列表。注意,必须放在前面,不然下面 setChecked 没数据节点
+  menuOptions.value = handleTree(await MenuApi.getSimpleMenusList())
+  // 设置数据
+  formData.id = row.id
+  formData.name = row.name
+  formData.code = row.code
+  formLoading.value = true
+  try {
+    formData.value.menuIds = await PermissionApi.getRoleMenuList(row.id)
+    // 设置选中
+    formData.value.menuIds.forEach((menuId: number) => {
+      treeRef.value.setChecked(menuId, true, false)
+    })
+  } finally {
+    formLoading.value = false
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = {
+      roleId: formData.id,
+      menuIds: [
+        ...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点
+        ...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点
+      ]
+    }
+    await PermissionApi.assignRoleMenuApi(data)
+    message.success(t('common.updateSuccess'))
+    modelVisible.value = false
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  // 重置选项
+  treeNodeAll.value = false
+  menuExpand.value = false
+  // 重置表单
+  formData.value = {
+    id: 0,
+    name: '',
+    code: '',
+    menuIds: []
+  }
+  treeRef.value?.setCheckedNodes([])
+  formRef.value?.resetFields()
+}
+
+/** 全选/全不选 */
+const handleCheckedTreeNodeAll = () => {
+  treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])
+}
+
+/** 展开/折叠全部 */
+const handleCheckedTreeExpand = () => {
+  const nodes = treeRef.value?.store.nodesMap
+  for (let node in nodes) {
+    if (nodes[node].expanded === menuExpand.value) {
+      continue
+    }
+    nodes[node].expanded = menuExpand.value
+  }
+}
+</script>
+<style lang="scss" scoped>
+.cardHeight {
+  width: 100%;
+  max-height: 400px;
+  overflow-y: scroll;
+}
+</style>

+ 13 - 9
src/views/system/role/index.vue

@@ -108,7 +108,7 @@
             preIcon="ep:basketball"
             title="菜单权限"
             v-hasPermi="['system:permission:assign-role-menu']"
-            @click="handleScope('menu', scope.row)"
+            @click="openAssignMenuForm(scope.row)"
           >
             菜单权限
           </el-button>
@@ -145,18 +145,18 @@
   <!-- 表单弹窗:添加/修改 -->
   <RoleForm ref="formRef" @success="getList" />
   <!-- 表单弹窗:菜单权限 -->
-  <MenuPermissionForm ref="menuPermissionFormRef" @success="getList" />
+  <RoleAssignMenuForm ref="assignMenuFormRef" @success="getList" />
   <!-- 表单弹窗:数据权限 -->
   <DataPermissionForm ref="dataPermissionFormRef" @success="getList" />
 </template>
 <script setup lang="tsx">
-import * as RoleApi from '@/api/system/role'
-import RoleForm from './RoleForm.vue'
-import MenuPermissionForm from './MenuPermissionForm.vue'
-import DataPermissionForm from './DataPermissionForm.vue'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
+import * as RoleApi from '@/api/system/role'
+import RoleForm from './RoleForm.vue'
+import RoleAssignMenuForm from './RoleAssignMenuForm.vue'
+import DataPermissionForm from './DataPermissionForm.vue'
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
@@ -204,19 +204,23 @@ const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
 
-/** 菜单权限操作 */
-const menuPermissionFormRef = ref()
 /** 数据权限操作 */
 const dataPermissionFormRef = ref()
 
 const handleScope = async (type: string, row: RoleApi.RoleVO) => {
   if (type === 'menu') {
-    menuPermissionFormRef.value.openModal(type, row)
+    assignMenuFormRef.value.openModal(type, row)
   } else if (type === 'data') {
     dataPermissionFormRef.value.openModal(type, row)
   }
 }
 
+/** 菜单权限操作 */
+const assignMenuFormRef = ref()
+const openAssignMenuForm = async (row: RoleApi.RoleVO) => {
+  assignMenuFormRef.value.open(row)
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {

+ 1 - 2
src/views/system/tenantPackage/TenantPackageForm.vue

@@ -64,11 +64,10 @@
 <script setup lang="ts" name="TenantPackageForm">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { CommonStatusEnum } from '@/utils/constants'
-import { defaultProps } from '@/utils/tree'
+import { defaultProps, handleTree } from '@/utils/tree'
 import * as TenantPackageApi from '@/api/system/tenantPackage'
 import * as MenuApi from '@/api/system/menu'
 import { ElTree } from 'element-plus'
-import { handleTree } from '@/utils/tree'
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗