فهرست منبع

perf: 优化权限管理

kailong321200875 1 سال پیش
والد
کامیت
efc1c25db8

+ 0 - 2
mock/department/index.ts

@@ -124,8 +124,6 @@ export default [
             email: '@EMAIL',
             // 创建时间
             createTime: '@datetime',
-            // 角色
-            role: '@first',
             // 用户id
             id: toAnyString()
           })

+ 104 - 22
mock/menu/index.ts

@@ -24,6 +24,8 @@ export default [
               name: 'Dashboard',
               status: Mock.Random.integer(0, 1),
               id: 1,
+              type: 0,
+              parentId: undefined,
               title: '首页',
               meta: {
                 title: '首页',
@@ -37,10 +39,23 @@ export default [
                   name: 'Analysis',
                   status: Mock.Random.integer(0, 1),
                   id: 2,
+                  type: 1,
+                  parentId: 1,
                   title: '分析页',
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'add'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    }
+                  ],
                   meta: {
                     title: '分析页',
-                    noCache: true
+                    noCache: true,
+                    permission: ['add', 'edit']
                   }
                 },
                 {
@@ -49,7 +64,23 @@ export default [
                   name: 'Workplace',
                   status: Mock.Random.integer(0, 1),
                   id: 3,
+                  type: 1,
+                  parentId: 1,
                   title: '工作台',
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'add'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    },
+                    {
+                      label: '删除',
+                      value: 'delete'
+                    }
+                  ],
                   meta: {
                     title: '工作台',
                     noCache: true
@@ -60,7 +91,6 @@ export default [
             {
               path: '/external-link',
               component: '#',
-              title: '文档',
               meta: {
                 title: '文档',
                 icon: 'clarity:document-solid'
@@ -68,12 +98,17 @@ export default [
               name: 'ExternalLink',
               status: Mock.Random.integer(0, 1),
               id: 4,
+              type: 0,
+              parentId: undefined,
+              title: '文档',
               children: [
                 {
                   path: 'https://element-plus-admin-doc.cn/',
                   name: 'DocumentLink',
                   status: Mock.Random.integer(0, 1),
                   id: 5,
+                  type: 1,
+                  parentId: 4,
                   title: '文档',
                   meta: {
                     title: '文档'
@@ -88,6 +123,8 @@ export default [
               name: 'Level',
               status: Mock.Random.integer(0, 1),
               id: 6,
+              type: 0,
+              parentId: undefined,
               title: '菜单',
               meta: {
                 title: '菜单',
@@ -100,8 +137,10 @@ export default [
                   component: '##',
                   status: Mock.Random.integer(0, 1),
                   id: 7,
-                  redirect: '/level/menu1/menu1-1/menu1-1-1',
+                  type: 0,
+                  parentId: 6,
                   title: '菜单1',
+                  redirect: '/level/menu1/menu1-1/menu1-1-1',
                   meta: {
                     title: '菜单1'
                   },
@@ -112,8 +151,10 @@ export default [
                       component: '##',
                       status: Mock.Random.integer(0, 1),
                       id: 8,
-                      redirect: '/level/menu1/menu1-1/menu1-1-1',
+                      type: 0,
+                      parentId: 7,
                       title: '菜单1-1',
+                      redirect: '/level/menu1/menu1-1/menu1-1-1',
                       meta: {
                         title: '菜单1-1',
                         alwaysShow: true
@@ -125,7 +166,8 @@ export default [
                           component: 'views/Level/Menu111',
                           status: Mock.Random.integer(0, 1),
                           id: 9,
-                          permission: ['edit', 'add', 'delete'],
+                          type: 1,
+                          parentId: 8,
                           title: '菜单1-1-1',
                           meta: {
                             title: '菜单1-1-1'
@@ -139,7 +181,8 @@ export default [
                       component: 'views/Level/Menu12',
                       status: Mock.Random.integer(0, 1),
                       id: 10,
-                      permission: ['edit', 'add', 'delete'],
+                      type: 1,
+                      parentId: 7,
                       title: '菜单1-2',
                       meta: {
                         title: '菜单1-2'
@@ -153,7 +196,8 @@ export default [
                   component: 'views/Level/Menu2',
                   status: Mock.Random.integer(0, 1),
                   id: 11,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 6,
                   title: '菜单2',
                   meta: {
                     title: '菜单2'
@@ -168,6 +212,8 @@ export default [
               name: 'Example',
               status: Mock.Random.integer(0, 1),
               id: 12,
+              type: 0,
+              parentId: undefined,
               title: '综合示例',
               meta: {
                 title: '综合示例',
@@ -181,11 +227,29 @@ export default [
                   name: 'ExampleDialog',
                   status: Mock.Random.integer(0, 1),
                   id: 13,
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-弹窗',
-                  permission: ['edit', 'add', 'delete'],
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'add'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    },
+                    {
+                      label: '删除',
+                      value: 'delete'
+                    },
+                    {
+                      label: '查看',
+                      value: 'view'
+                    }
+                  ],
                   meta: {
-                    title: '综合示例-弹窗',
-                    permission: ['edit', 'add']
+                    title: '综合示例-弹窗'
                   }
                 },
                 {
@@ -194,11 +258,29 @@ export default [
                   name: 'ExamplePage',
                   status: Mock.Random.integer(0, 1),
                   id: 14,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-页面',
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'edit'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    },
+                    {
+                      label: '删除',
+                      value: 'delete'
+                    },
+                    {
+                      label: '查看',
+                      value: 'view'
+                    }
+                  ],
                   meta: {
-                    title: '综合示例-页面',
-                    permission: ['edit', 'add']
+                    title: '综合示例-页面'
                   }
                 },
                 {
@@ -207,7 +289,8 @@ export default [
                   name: 'ExampleAdd',
                   status: Mock.Random.integer(0, 1),
                   id: 15,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-新增',
                   meta: {
                     title: '综合示例-新增',
@@ -215,8 +298,7 @@ export default [
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,
-                    activeMenu: '/example/example-page',
-                    permission: ['delete', 'add']
+                    activeMenu: '/example/example-page'
                   }
                 },
                 {
@@ -225,7 +307,8 @@ export default [
                   name: 'ExampleEdit',
                   status: Mock.Random.integer(0, 1),
                   id: 16,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-编辑',
                   meta: {
                     title: '综合示例-编辑',
@@ -233,8 +316,7 @@ export default [
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,
-                    activeMenu: '/example/example-page',
-                    permission: ['delete', 'add']
+                    activeMenu: '/example/example-page'
                   }
                 },
                 {
@@ -243,7 +325,8 @@ export default [
                   name: 'ExampleDetail',
                   status: Mock.Random.integer(0, 1),
                   id: 17,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-详情',
                   meta: {
                     title: '综合示例-详情',
@@ -251,8 +334,7 @@ export default [
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,
-                    activeMenu: '/example/example-page',
-                    permission: ['delete', 'edit']
+                    activeMenu: '/example/example-page'
                   }
                 }
               ]

+ 1 - 1
src/locales/zh-CN.ts

@@ -521,7 +521,7 @@ export default {
   menu: {
     menuName: '菜单名称',
     icon: '图标',
-    permission: '权限标识',
+    permission: '按钮权限',
     component: '组件',
     path: '路径',
     status: '状态',

+ 7 - 1
src/views/Authorization/Menu/Menu.vue

@@ -35,7 +35,13 @@ const tableColumns = reactive<TableColumn[]>([
   },
   {
     field: 'meta.title',
-    label: t('menu.menuName')
+    label: t('menu.menuName'),
+    slots: {
+      default: (data: any) => {
+        const title = data.row.meta.title
+        return <>{title}</>
+      }
+    }
   },
   {
     field: 'meta.icon',

+ 67 - 0
src/views/Authorization/Menu/components/AddButtonPermission.vue

@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import { FormSchema, Form } from '@/components/Form'
+import { ElDrawer, ElButton } from 'element-plus'
+import { reactive } from 'vue'
+import { useForm } from '@/hooks/web/useForm'
+import { useValidator } from '@/hooks/web/useValidator'
+
+const modelValue = defineModel<boolean>()
+
+const { required } = useValidator()
+
+const formSchema = reactive<FormSchema[]>([
+  {
+    field: 'label',
+    label: 'label',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'value',
+    label: 'value',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  }
+])
+
+const { formRegister, formMethods } = useForm()
+const { getFormData, getElFormExpose } = formMethods
+
+const emit = defineEmits(['confirm'])
+
+const rules = reactive({
+  label: [required()],
+  value: [required()]
+})
+
+const confirm = async () => {
+  const elFormExpose = await getElFormExpose()
+  if (!elFormExpose) return
+  const valid = await elFormExpose?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    emit('confirm', formData)
+    modelValue.value = false
+  }
+}
+</script>
+
+<template>
+  <ElDrawer v-model="modelValue" title="新增按钮权限">
+    <template #default>
+      <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+    </template>
+    <template #footer>
+      <div>
+        <ElButton @click="() => (modelValue = false)">取消</ElButton>
+        <ElButton type="primary" @click="confirm">确认</ElButton>
+      </div>
+    </template>
+  </ElDrawer>
+</template>

+ 18 - 0
src/views/Authorization/Menu/components/Detail.vue

@@ -75,6 +75,24 @@ const detailSchema = ref<DescriptionsSchema[]>([
     field: 'meta.activeMenu',
     label: '高亮菜单'
   },
+  {
+    field: 'permissionList',
+    label: '按钮权限',
+    span: 24,
+    slots: {
+      default: (data: any) => (
+        <>
+          {data?.permissionList?.map((v) => {
+            return (
+              <ElTag class="mr-1" key={v.value}>
+                {v.label}
+              </ElTag>
+            )
+          })}
+        </>
+      )
+    }
+  },
   {
     field: 'menuState',
     label: '菜单状态',

+ 74 - 15
src/views/Authorization/Menu/components/Write.vue

@@ -1,11 +1,12 @@
 <script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
+import { PropType, reactive, watch, ref, unref } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
 import { useI18n } from '@/hooks/web/useI18n'
 import { getMenuListApi } from '@/api/menu'
 import { ElTag, ElButton } from 'element-plus'
+import AddButtonPermission from './AddButtonPermission.vue'
 
 const { t } = useI18n()
 
@@ -18,6 +19,16 @@ const props = defineProps({
   }
 })
 
+const handleClose = async (tag: any) => {
+  const formData = await getFormData()
+  // 删除对应的权限
+  setValues({
+    permissionList: formData?.permissionList?.filter((v: any) => v.value !== tag.value)
+  })
+}
+
+const showDrawer = ref(false)
+
 const formSchema = reactive<FormSchema[]>([
   {
     field: 'type',
@@ -50,7 +61,7 @@ const formSchema = reactive<FormSchema[]>([
               }
             ])
             setValues({
-              component: ''
+              component: unref(cacheComponent)
             })
           } else {
             setSchema([
@@ -104,7 +115,7 @@ const formSchema = reactive<FormSchema[]>([
             })
           } else if (formData.type === 1) {
             setValues({
-              component: ''
+              component: unref(cacheComponent) ?? ''
             })
           }
         }
@@ -127,7 +138,12 @@ const formSchema = reactive<FormSchema[]>([
     value: '#',
     componentProps: {
       disabled: true,
-      placeholder: '#为顶级目录,##为子目录'
+      placeholder: '#为顶级目录,##为子目录',
+      on: {
+        change: (val: string) => {
+          cacheComponent.value = val
+        }
+      }
     }
   },
   {
@@ -176,18 +192,16 @@ const formSchema = reactive<FormSchema[]>([
     },
     formItemProps: {
       slots: {
-        default: () => (
+        default: (data: any) => (
           <>
-            <ElTag class="mx-1" closable disableTransitions={false}>
-              新增
-            </ElTag>
-            <ElTag class="mx-1" closable disableTransitions={false}>
-              编辑
-            </ElTag>
-            <ElTag class="mx-1" closable disableTransitions={false}>
-              删除
-            </ElTag>
-            <ElButton type="primary" size="small" onClick={() => console.log('添加权限')}>
+            {data?.permissionList?.map((v) => {
+              return (
+                <ElTag class="mr-1" key={v.value} closable onClose={() => handleClose(v)}>
+                  {v.label}
+                </ElTag>
+              )
+            })}
+            <ElButton type="primary" size="small" onClick={() => (showDrawer.value = true)}>
               添加权限
             </ElButton>
           </>
@@ -252,10 +266,47 @@ const submit = async () => {
   }
 }
 
+const cacheComponent = ref('')
+
 watch(
   () => props.currentRow,
   (currentRow) => {
     if (!currentRow) return
+    cacheComponent.value = currentRow.type === 1 ? currentRow.component : ''
+    if (currentRow.parentId === 0) {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: true
+        }
+      ])
+    } else {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: false
+        }
+      ])
+    }
+    if (currentRow.type === 1) {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: false
+        }
+      ])
+    } else {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: true
+        }
+      ])
+    }
     setValues(currentRow)
   },
   {
@@ -267,8 +318,16 @@ watch(
 defineExpose({
   submit
 })
+
+const confirm = async (data: any) => {
+  const formData = await getFormData()
+  setValues({
+    permissionList: [...(formData?.permissionList || []), data]
+  })
+}
 </script>
 
 <template>
   <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+  <AddButtonPermission v-model="showDrawer" @confirm="confirm" />
 </template>

+ 5 - 0
src/views/Authorization/Role/Role.vue

@@ -9,6 +9,7 @@ import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
 import Write from './components/Write.vue'
+import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
 
 const { t } = useI18n()
@@ -71,6 +72,9 @@ const tableColumns = reactive<TableColumn[]>([
             <ElButton type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </ElButton>
+            <ElButton type="success" onClick={() => action(row, 'detail')}>
+              {t('exampleDemo.detail')}
+            </ElButton>
             <ElButton type="danger">{t('exampleDemo.del')}</ElButton>
           </>
         )
@@ -151,6 +155,7 @@ const save = async () => {
 
   <Dialog v-model="dialogVisible" :title="dialogTitle">
     <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
+    <Detail v-else :current-row="currentRow" />
 
     <template #footer>
       <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">

+ 106 - 0
src/views/Authorization/Role/components/Detail.vue

@@ -0,0 +1,106 @@
+<script setup lang="tsx">
+import { PropType, ref, unref, nextTick } from 'vue'
+import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
+import { ElTag, ElTree } from 'element-plus'
+import { findIndex } from '@/utils'
+import { getMenuListApi } from '@/api/menu'
+
+defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => undefined
+  }
+})
+
+const filterPermissionName = (value: string) => {
+  const index = findIndex(unref(currentTreeData)?.permissionList || [], (item) => {
+    return item.value === value
+  })
+  return (unref(currentTreeData)?.permissionList || [])[index].label ?? ''
+}
+
+const renderTag = (enable?: boolean) => {
+  return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
+}
+
+const treeRef = ref<typeof ElTree>()
+
+const currentTreeData = ref()
+const nodeClick = (treeData: any) => {
+  currentTreeData.value = treeData
+}
+
+const treeData = ref<any[]>([])
+const getMenuList = async () => {
+  const res = await getMenuListApi()
+  if (res) {
+    treeData.value = res.data.list
+    await nextTick()
+  }
+}
+getMenuList()
+
+const detailSchema = ref<DescriptionsSchema[]>([
+  {
+    field: 'roleName',
+    label: '角色名称'
+  },
+  {
+    field: 'status',
+    label: '状态',
+    slots: {
+      default: (data: any) => {
+        return renderTag(data.status)
+      }
+    }
+  },
+  {
+    field: 'remark',
+    label: '备注',
+    span: 24
+  },
+  {
+    field: 'permissionList',
+    label: '菜单分配',
+    span: 24,
+    slots: {
+      default: () => {
+        return (
+          <>
+            <div class="flex w-full">
+              <div class="flex-1">
+                <ElTree
+                  ref={treeRef}
+                  node-key="id"
+                  props={{ children: 'children', label: 'title' }}
+                  highlight-current
+                  expand-on-click-node={false}
+                  data={treeData.value}
+                  onNode-click={nodeClick}
+                >
+                  {{
+                    default: (data) => {
+                      return <span>{data?.data?.title}</span>
+                    }
+                  }}
+                </ElTree>
+              </div>
+              <div class="flex-1">
+                {unref(currentTreeData)
+                  ? unref(currentTreeData)?.meta?.permission?.map((v: string) => {
+                      return <ElTag class="ml-2 mt-2">{filterPermissionName(v)}</ElTag>
+                    })
+                  : null}
+              </div>
+            </div>
+          </>
+        )
+      }
+    }
+  }
+])
+</script>
+
+<template>
+  <Descriptions :schema="detailSchema" :data="currentRow || {}" />
+</template>

+ 3 - 3
src/views/Authorization/Role/components/Write.vue

@@ -76,10 +76,10 @@ const formSchema = ref<FormSchema[]>([
                   </ElTree>
                 </div>
                 <div class="flex-1">
-                  {unref(currentTreeData) && unref(currentTreeData)?.permission ? (
+                  {unref(currentTreeData) && unref(currentTreeData)?.permissionList ? (
                     <ElCheckboxGroup v-model={unref(currentTreeData).meta.permission}>
-                      {unref(currentTreeData)?.permission.map((v: string) => {
-                        return <ElCheckbox label={v} />
+                      {unref(currentTreeData)?.permissionList.map((v: any) => {
+                        return <ElCheckbox label={v.value}>{v.label}</ElCheckbox>
                       })}
                     </ElCheckboxGroup>
                   ) : null}

+ 17 - 0
src/views/Authorization/User/User.vue

@@ -11,6 +11,7 @@ import { Search } from '@/components/Search'
 import Write from './components/Write.vue'
 import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
+import { getRoleListApi } from '@/api/role'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
 
 const { t } = useI18n()
@@ -113,6 +114,22 @@ const crudSchemas = reactive<CrudSchema[]>([
     label: t('userDemo.role'),
     search: {
       hidden: true
+    },
+    form: {
+      component: 'Select',
+      value: [],
+      componentProps: {
+        multiple: true,
+        collapseTags: true,
+        maxCollapseTags: 1
+      },
+      optionApi: async () => {
+        const res = await getRoleListApi()
+        return res.data?.list?.map((v) => ({
+          label: v.roleName,
+          value: v.id
+        }))
+      }
     }
   },
   {