Pārlūkot izejas kodu

feat: 角色添加菜单

wanggaokun 3 nedēļas atpakaļ
vecāks
revīzija
a434f24854

+ 19 - 2
src/api/interface/system/menu.ts

@@ -59,9 +59,26 @@ export interface MenuBO {
  * 接收后端返回树信息
  */
 export type MenuTreeVO = {
-  id?: string
+  id: string
   menuId: string
   parentId: string
+  path: string
+  name: string
+  orderNum: number
+  menuType: string
+  component?: string | (() => Promise<unknown>)
+  redirect?: string
+  meta: MetaProps
+  children?: MenuTreeVO[]
+}
+
+interface MetaProps {
+  icon: string
   title: string
-  children: MenuTreeVO[]
+  isLink?: boolean
+  isHidden: boolean
+  isFull: boolean
+  isAffix: boolean
+  isKeepAlive: boolean
+  useDataScope: string
 }

+ 18 - 0
src/api/interface/system/role.ts

@@ -1,3 +1,4 @@
+import { MenuVO } from '@/api/interface/system/menu'
 /**
  * 查询参数
  */
@@ -34,3 +35,20 @@ export interface RoleBO {
   remark?: string
   status?: string
 }
+
+/**
+ * 传入已有角色
+ */
+export interface RoleMenuBO {
+  roleId: string
+  menuIds: string[]
+}
+
+/**
+ * 接收已有角色
+ */
+export interface RoleMenuVO {
+  allMenus: MenuVO[]
+  treeMenus: Menu.MenuOptions[]
+  hasMenus: string[]
+}

+ 1 - 0
src/api/interface/system/user.ts

@@ -70,6 +70,7 @@ export interface UserRoleBO {
   userId: string
   roleIds: string[]
 }
+
 /**
  * 接收已有角色
  */

+ 1 - 1
src/api/module/system/menu.ts

@@ -1,4 +1,4 @@
-import { MenuBO, MenuTreeVO, MenuVO } from '@/api/interface/system/menu'
+import { MenuBO, MenuTreeVO } from '@/api/interface/system/menu'
 import http from '@/axios'
 class MenuApi {
   static getMenuList = (): Promise<ResultData<any>> => {

+ 17 - 1
src/api/module/system/role.ts

@@ -1,4 +1,4 @@
-import { RoleVO, RoleQuery, RoleBO } from '@/api/interface/system/role'
+import { RoleVO, RoleQuery, RoleBO, RoleMenuVO, RoleMenuBO } from '@/api/interface/system/role'
 import http from '@/axios'
 
 class RoleApi {
@@ -41,5 +41,21 @@ class RoleApi {
   static delete = (data: string[]): Promise<ResultData<any>> => {
     return http.delete({ url: '/system/role/delete', data })
   }
+
+  /**
+   * @name 查询已有菜单
+   * @returns returns
+   */
+  static getMenu = (roleId: string): Promise<ResultData<any>> => {
+    return http.get<RoleMenuVO>({ url: `/system/role/menu/${roleId}` })
+  }
+
+  /**
+   * @name 用户添加角色
+   * @returns returns
+   */
+  static addMenu = (data: RoleMenuBO): Promise<ResultData<any>> => {
+    return http.put({ url: `/system/role/menu/add`, data })
+  }
 }
 export default RoleApi

+ 3 - 3
src/api/module/system/user.ts

@@ -66,8 +66,8 @@ class UserApi {
    * @name 查询已有角色
    * @returns returns
    */
-  static getRole = (params: string): Promise<ResultData<any>> => {
-    return http.get<UserRoleVO>({ url: `/system/user/role/${params}` })
+  static getRole = (userId: string): Promise<ResultData<any>> => {
+    return http.get<UserRoleVO>({ url: `/system/user/role/${userId}` })
   }
 
   /**
@@ -75,7 +75,7 @@ class UserApi {
    * @returns returns
    */
   static addRole = (data: UserRoleBO): Promise<ResultData<any>> => {
-    return http.put<UserRoleVO>({ url: `/system/user/role/add`, data })
+    return http.put({ url: `/system/user/role/add`, data })
   }
 }
 export default UserApi

+ 2 - 0
src/types/auto-components.d.ts

@@ -30,6 +30,7 @@ declare module 'vue' {
     EcoUser: typeof import('./../components/EcoUser/index.vue')['default']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
@@ -82,6 +83,7 @@ declare module 'vue' {
     OrgDrawer: typeof import('./../views/system/org/components/OrgDrawer.vue')['default']
     OrgForm: typeof import('./../views/system/org/components/orgForm.vue')['default']
     Pagination: typeof import('./../components/ProTable/Pagination.vue')['default']
+    PermissionsDrawer: typeof import('./../views/system/role/components/PermissionsDrawer.vue')['default']
     PositionDrawer: typeof import('./../views/system/position/components/PositionDrawer.vue')['default']
     ProTable: typeof import('./../components/ProTable/index.vue')['default']
     RoleDrawer: typeof import('./../views/system/role/components/RoleDrawer.vue')['default']

+ 4 - 4
src/types/global.d.ts

@@ -78,14 +78,14 @@ declare type keyOnPrefix<T> = {
 declare namespace Menu {
   interface MenuOptions {
     id: string
-    pid: string
+    menuId: string
+    parentId: string
     path: string
     name: string
-    sort: number
-    menuTypeCd: string
+    orderNum: number
+    menuType: string
     component?: string | (() => Promise<unknown>)
     redirect?: string
-    permissions?: string
     meta: MetaProps
     children?: MenuOptions[]
   }

+ 149 - 0
src/views/system/role/components/MenuDrawer.vue

@@ -0,0 +1,149 @@
+<template>
+  <ElDrawer ref="elDrawerRef" v-model="drawerVisible" :title="drawerProps.title" v-bind="$attrs" destroy-on-close>
+    <template #header="scope">
+      <slot name="header" v-bind="scope">
+        <div style="display: flex">
+          <slot name="title">
+            <span style="flex: 1">{{ drawerProps.title }}</span>
+          </slot>
+        </div>
+      </slot>
+    </template>
+    <el-form label-width="100px" label-suffix=" :" @submit.enter.prevent="handleConfirm">
+      <el-form-item>
+        <el-checkbox v-model="isExpand" @change="changeExpand"> 展开/折叠 </el-checkbox>
+        <el-checkbox v-model="isNodeAll" @change="changeNodeAll"> 全选/全不选 </el-checkbox>
+        <el-checkbox v-model="isCheckStrictly"> 父子联动 </el-checkbox>
+      </el-form-item>
+      <el-form-item label="权限">
+        <el-tree
+          :data="treeMenus"
+          show-checkbox
+          ref="treeRef"
+          node-key="menuId"
+          :default-expand-all="isExpand"
+          :check-strictly="!isCheckStrictly"
+          :props="treeProps"
+          empty-text="加载中,请稍候">
+        </el-tree>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <slot name="footer">
+        <ElButton @click="drawerVisible = false">取 消</ElButton>
+        <ElButton type="primary" @click="handleConfirm()">确 定</ElButton>
+      </slot>
+    </template>
+  </ElDrawer>
+</template>
+
+<script setup lang="ts" name="MenuDrawer">
+import { MenuVO } from '@/api/interface/system/menu'
+import { RoleVO } from '@/api/interface/system/role'
+import RoleApi from '@/api/module/system/role'
+import { ResultEnum } from '@/enums/HttpEnum'
+import { CheckboxValueType } from 'element-plus'
+
+interface EcoDrawerProps {
+  api?: (params: any) => Promise<any> // 调用接口
+  title: string // 顶部标题
+  row: Partial<RoleVO>
+  getTableList?: () => void
+}
+const treeProps = {
+  children: 'children',
+  label: 'title',
+  value: 'menuId'
+}
+
+/**
+ * 全部展开/折叠
+ * @param value
+ */
+const changeExpand = (value: CheckboxValueType) => {
+  for (const i in treeRef.value!.store.nodesMap) {
+    treeRef.value!.store.nodesMap[i].expanded = value
+  }
+}
+
+/**
+ * 全选
+ * @param value
+ */
+const changeNodeAll = (value: CheckboxValueType) => {
+  if (value) {
+    treeRef.value!.setCheckedNodes(treeMenus.value as any)
+  } else {
+    treeRef.value!.setCheckedNodes([])
+  }
+}
+
+const isExpand = ref(true)
+const isNodeAll = ref(false)
+const isCheckStrictly = ref(true)
+
+const treeRef = ref()
+const allMenus = ref<MenuVO[]>([])
+const treeMenus = ref<Menu.MenuOptions[]>([])
+const hasMenus = ref<string[]>([])
+
+const drawerVisible = ref(false)
+const drawerProps = ref<EcoDrawerProps>({
+  title: '',
+  row: {}
+})
+
+// emit
+const emit = defineEmits(['submit'])
+
+// 接收父组件传过来的参数
+const acceptParams = (params: EcoDrawerProps) => {
+  drawerProps.value = params
+  drawerVisible.value = true
+  getMenu(params.row.roleId || '')
+}
+
+/**
+ * 获取权限信息
+ * @param roleId
+ */
+const getMenu = (roleId: string) => {
+  if (!roleId) return
+  RoleApi.getMenu(roleId).then(res => {
+    hasMenus.value = res.data.hasMenus
+    allMenus.value = res.data.allMenus
+    treeMenus.value = res.data.treeMenus
+    nextTick(() => {
+      console.log(hasMenus.value)
+      hasMenus.value.forEach(item => {
+        const node = treeRef.value!.getNode(item)
+        if (node?.isLeaf) {
+          treeRef.value!.setChecked(node, true, false)
+        }
+      })
+    })
+  })
+}
+
+const handleConfirm = async () => {
+  try {
+    const checkedKeys = treeRef.value!.getCheckedKeys()
+    const halfCheckedKeys = treeRef.value!.getHalfCheckedKeys()
+    const { code } = await drawerProps.value.api!({ roleId: drawerProps.value.row.roleId, menuIds: [...checkedKeys, ...halfCheckedKeys] })
+    if (code == ResultEnum.SUCCESS) {
+      ElMessage.success({ message: `${drawerProps.value.title}成功!` })
+      drawerProps.value.getTableList!()
+      emit('submit')
+      drawerVisible.value = false
+    }
+  } catch (error) {
+    console.log(error)
+  }
+}
+
+defineExpose({
+  acceptParams
+})
+</script>
+
+<style scoped lang="scss"></style>

+ 106 - 0
src/views/system/role/components/PermissionsDrawer.vue

@@ -0,0 +1,106 @@
+<template>
+  <ElDrawer ref="elDrawerRef" v-model="drawerVisible" :title="drawerProps.title" v-bind="$attrs" destroy-on-close>
+    <template #header="scope">
+      <slot name="header" v-bind="scope">
+        <div style="display: flex">
+          <slot name="title">
+            <span style="flex: 1">{{ drawerProps.title }}</span>
+          </slot>
+        </div>
+      </slot>
+    </template>
+
+    <el-form ref="ruleFormRef" label-width="100px" label-suffix=" :" :rules="rules" :model="drawerProps.row" @submit.enter.prevent="handleConfirm">
+      <el-form-item label="参数名称" prop="name">
+        <el-input v-model="drawerProps.row.name" placeholder="填写参数名称" clearable />
+      </el-form-item>
+      <el-form-item label="权限标识" prop="code">
+        <el-input v-model="drawerProps.row.code" :disabled="drawerProps.row.roleId === '1'" placeholder="填写权限标识" clearable />
+      </el-form-item>
+      <el-form-item label="排序" prop="orderNum">
+        <el-input-number v-model="drawerProps.row.orderNum" :precision="0" :min="1" :max="999999" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="drawerProps.row.status">
+          <el-radio-button :value="item.dictValue" v-for="(item, index) in commonStatus" :key="index" :label="item.dictLabel" />
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="drawerProps.row.remark" placeholder="请填写备注" clearable />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <slot name="footer">
+        <ElButton @click="drawerVisible = false">取 消</ElButton>
+        <ElButton type="primary" @click="handleConfirm()">确 定</ElButton>
+      </slot>
+    </template>
+  </ElDrawer>
+</template>
+
+<script setup lang="ts" name="PermissionsDrawer">
+import { RoleBO } from '@/api/interface/system/role'
+import { ResultEnum } from '@/enums/HttpEnum'
+import { useDictOptions } from '@/hooks'
+
+const commonStatus = useDictOptions('COMMON_STATUS')
+
+import { FormInstance } from 'element-plus'
+const rules = reactive({
+  name: [{ required: true, message: '请填写角色名称' }],
+  code: [{ required: true, message: '请填权限标识' }],
+  orderNum: [{ required: true, message: '排序不能为空' }],
+  status: [{ required: true, message: '状态不能为空' }]
+})
+
+interface EcoDrawerProps {
+  api?: (params: any) => Promise<any> // 调用接口
+  title: string // 顶部标题
+  isView: boolean
+  row: Partial<RoleBO>
+  getTableList?: () => void
+}
+
+const drawerVisible = ref(false)
+const drawerProps = ref<EcoDrawerProps>({
+  isView: false,
+  title: '',
+  row: {}
+})
+
+// emit
+const emit = defineEmits(['submit'])
+
+// 接收父组件传过来的参数
+const acceptParams = (params: EcoDrawerProps) => {
+  drawerProps.value = params
+  drawerVisible.value = true
+  drawerProps.value.row.status = drawerProps.value.row.status || '1'
+}
+
+// 提交数据(新增/编辑)
+const ruleFormRef = ref<FormInstance>()
+const handleConfirm = () => {
+  ruleFormRef.value!.validate(async valid => {
+    if (!valid) return
+    try {
+      const { code } = await drawerProps.value.api!(drawerProps.value.row)
+      if (code == ResultEnum.SUCCESS) {
+        ElMessage.success({ message: `${drawerProps.value.title}成功!` })
+        drawerProps.value.getTableList!()
+        emit('submit')
+        drawerVisible.value = false
+      }
+    } catch (error) {
+      console.log(error)
+    }
+  })
+}
+
+defineExpose({
+  acceptParams
+})
+</script>
+
+<style scoped lang="scss"></style>

+ 42 - 3
src/views/system/role/index.vue

@@ -8,19 +8,36 @@
       </template>
 
       <template #operation="{ row }">
-        <el-button type="primary" link icon="EditPen" @click="openDrawer('编辑', row)"> 编辑 </el-button>
-        <el-button v-if="row.isLock !== '1'" type="primary" link icon="Delete" @click="deleteRow(row)"> 删除 </el-button>
+        <div class="operation-group">
+          <el-button type="primary" link icon="EditPen" @click="openDrawer('编辑', row)"> 编辑 </el-button>
+          <el-button v-if="row.isLock !== '1'" type="primary" link icon="Delete" @click="deleteRow(row)"> 删除 </el-button>
+          <div>
+            <el-dropdown trigger="click">
+              <el-button type="primary" link icon="DArrowRight"> 授权 </el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item icon="Lock" @click="openAuthMenuDrawer('授权菜单', row)">授权菜单</el-dropdown-item>
+                  <el-dropdown-item icon="Lock" @click="openAuthPermissionsDrawer('授权权限', row)"> 授权权限 </el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </div>
+        </div>
       </template>
     </ProTable>
     <RoleDrawer ref="drawerRef" size="40%" />
+    <MenuDrawer ref="drawerMenuRef" size="40%" />
+    <PermissionsDrawer ref="drawerPermissionsRef" size="40%" />
   </div>
 </template>
 <script lang="tsx" setup name="RoleManage">
 import RoleApi from '@/api/module/system/role'
 import RoleDrawer from './components/RoleDrawer.vue'
+import MenuDrawer from './components/MenuDrawer.vue'
 import { ColumnProps, ProTableInstance, SearchProps } from '@/components/ProTable/interface'
 import { useDictOptions, useHandleData } from '@/hooks'
 import { RoleBO, RoleQuery, RoleVO } from '@/api/interface/system/role'
+import PermissionsDrawer from './components/PermissionsDrawer.vue'
 
 const proTableRef = ref<ProTableInstance>()
 
@@ -46,7 +63,7 @@ const columns: ColumnProps<RoleVO>[] = [
 ]
 
 // 表格配置项
-const searchColumns: SearchProps[] = [{ prop: 'name', label: '参数名称', el: 'input' }]
+const searchColumns: SearchProps[] = [{ prop: 'name', label: '角色名称', el: 'input' }]
 
 // 获取table列表
 const getTableList = (params: RoleQuery) => RoleApi.page(params)
@@ -76,4 +93,26 @@ const openDrawer = (title: string, row: Partial<RoleBO> = {}) => {
   }
   drawerRef.value?.acceptParams(params)
 }
+const drawerMenuRef = ref<InstanceType<typeof MenuDrawer> | null>(null)
+const openAuthMenuDrawer = (title: string, row: Partial<RoleVO> = {}) => {
+  const params = {
+    title,
+    row: { ...row },
+    api: RoleApi.addMenu,
+    getTableList: proTableRef.value?.getTableList
+  }
+  drawerMenuRef.value?.acceptParams(params)
+}
+
+const drawerPermissionsRef = ref<InstanceType<typeof PermissionsDrawer> | null>(null)
+const openAuthPermissionsDrawer = (title: string, row: Partial<RoleBO> = {}) => {}
 </script>
+<style lang="scss" scoped>
+.operation-group {
+  display: flex;
+  justify-content: center;
+}
+.operation-group > * {
+  margin-left: 8px;
+}
+</style>

+ 1 - 1
src/views/system/user/components/AddRoleDialog.vue

@@ -23,7 +23,7 @@
 
 <script setup lang="ts" name="AddRoleDialog">
 import { RoleVO } from '@/api/interface/system/role'
-import { UserBO, UserRoleBO } from '@/api/interface/system/user'
+import { UserBO } from '@/api/interface/system/user'
 import UserApi from '@/api/module/system/user'
 import { ResultEnum } from '@/enums/HttpEnum'