perf: 优化权限管理

This commit is contained in:
kailong321200875 2023-11-12 11:37:28 +08:00
parent 92d436b8bb
commit efc1c25db8
11 changed files with 402 additions and 44 deletions

View File

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

View File

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

View File

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

View File

@ -35,7 +35,13 @@ const tableColumns = reactive<TableColumn[]>([
}, },
{ {
field: 'meta.title', 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', field: 'meta.icon',

View File

@ -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>

View File

@ -75,6 +75,24 @@ const detailSchema = ref<DescriptionsSchema[]>([
field: 'meta.activeMenu', field: 'meta.activeMenu',
label: '高亮菜单' 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', field: 'menuState',
label: '菜单状态', label: '菜单状态',

View File

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

View File

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

View File

@ -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>

View File

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

View File

@ -11,6 +11,7 @@ import { Search } from '@/components/Search'
import Write from './components/Write.vue' import Write from './components/Write.vue'
import Detail from './components/Detail.vue' import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog' import { Dialog } from '@/components/Dialog'
import { getRoleListApi } from '@/api/role'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() const { t } = useI18n()
@ -113,6 +114,22 @@ const crudSchemas = reactive<CrudSchema[]>([
label: t('userDemo.role'), label: t('userDemo.role'),
search: { search: {
hidden: true 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
}))
}
} }
}, },
{ {