refactor(system/role): 优化角色权限代码

This commit is contained in:
2025-07-09 22:37:00 +08:00
parent 4cf763e817
commit 8090861bc6

View File

@@ -8,7 +8,7 @@
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }" :scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="false" :pagination="false"
:disabled-tools="['fullscreen', 'size', 'setting']" :disabled-tools="['fullscreen', 'size', 'setting']"
:row-selection="{ type: 'checkbox', showCheckedAll, selectRowKeys: selectedKeys }" :row-selection="disabled ? false : { type: 'checkbox', showCheckedAll: showCheckedAll && !disabled, selectRowKeys: selectedKeys }"
@select="select" @select="select"
@select-all="selectAll" @select-all="selectAll"
@refresh="refresh" @refresh="refresh"
@@ -44,7 +44,7 @@
</template> </template>
<template #permissions="{ record }"> <template #permissions="{ record }">
<div v-if="record.permissions && record.permissions.length > 0"> <div v-if="record.permissions && record.permissions.length > 0">
<a-checkbox-group v-model="record.checkedPermissions" :disabled="disabled" @change="selectPermission(record)"> <a-checkbox-group v-model="record.checkedPermissions" :disabled="disabled || record.disabled" @change="selectPermission(record)">
<a-checkbox v-for="permission in record.permissions" :key="permission.id" :value="permission.id"> <a-checkbox v-for="permission in record.permissions" :key="permission.id" :value="permission.id">
{{ permission.title }} {{ permission.title }}
</a-checkbox> </a-checkbox>
@@ -55,7 +55,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, ref } from 'vue' import { nextTick, ref, watch } from 'vue'
import { Message, type TableInstance } from '@arco-design/web-vue' import { Message, type TableInstance } from '@arco-design/web-vue'
import { type MenuResp, listMenu } from '@/apis/system/menu' import { type MenuResp, listMenu } from '@/apis/system/menu'
import { isMobile } from '@/utils' import { isMobile } from '@/utils'
@@ -72,6 +72,21 @@ interface Props {
roleId: string roleId: string
} }
interface PermissionItem {
id: string | number
title: string
parentId: string | number
permission?: string
isChecked?: boolean
}
interface ExtendedMenuResp extends MenuResp {
permissions?: PermissionItem[]
checkedPermissions?: (string | number)[]
isChecked?: boolean
disabled?: boolean
}
const tableRef = ref<InstanceType<typeof GiTable>>() const tableRef = ref<InstanceType<typeof GiTable>>()
// 是否父子联动 // 是否父子联动
const isCascade = ref(true) const isCascade = ref(true)
@@ -90,16 +105,17 @@ const onExpanded = () => {
* *
* @param menus 菜单数据 * @param menus 菜单数据
*/ */
const transformMenu = (menus: MenuResp[]) => { const transformMenu = (menus: MenuResp[]): ExtendedMenuResp[] => {
return menus.map((item) => { return menus.map((item): ExtendedMenuResp => {
// 如果当前项有子项,递归处理子项 // 如果当前项有子项,递归处理子项
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
// 过滤出 type 为 3 的按钮权限 // 过滤出 type 为 3 的按钮权限
const permissions = item.children.filter((child) => child.type === 3 || child.permission).map((child) => ({ const permissions = item.children.filter((child) => child.type === 3 || child.permission).map((child): PermissionItem => ({
id: child.id, id: child.id,
title: child.title, title: child.title,
parentId: child.parentId, parentId: child.parentId,
permission: child.permission, permission: child.permission,
isChecked: false,
})) }))
// 过滤出 type 不为 3 的子项 // 过滤出 type 不为 3 的子项
@@ -107,8 +123,8 @@ const transformMenu = (menus: MenuResp[]) => {
// 如果有权限,将其添加到当前项的 permissions 属性中 // 如果有权限,将其添加到当前项的 permissions 属性中
if (permissions.length > 0) { if (permissions.length > 0) {
item.permissions = permissions (item as ExtendedMenuResp).permissions = permissions
item.checkedPermissions = permissions.filter((permission) => permission.isChecked) ;(item as ExtendedMenuResp).checkedPermissions = permissions.filter((permission) => permission.isChecked).map((permission) => permission.id)
} }
// 递归处理剩余的子项 // 递归处理剩余的子项
@@ -125,24 +141,47 @@ const transformMenu = (menus: MenuResp[]) => {
} }
// 更新表格数据的选中状态 // 更新表格数据的选中状态
const updateTableDataCheckedStatus = (data: MenuResp[], selectedKeys: (string | number)[]) => { const updateTableDataCheckedStatus = (data: ExtendedMenuResp[], selectedKeys: (string | number)[]) => {
data.forEach((item) => { data.forEach((item) => {
item.disabled = disabled.value item.disabled = disabled.value
// 设置菜单项的选中状态 // 设置菜单项的选中状态
item.isChecked = selectedKeys.includes(item.id) item.isChecked = selectedKeys.includes(item.id)
// 设置权限的选中状态 // 设置权限的选中状态
if (item.permissions) { if (item.permissions) {
item.permissions.forEach((permission) => {
permission.isChecked = selectedKeys.includes(permission.id)
})
item.checkedPermissions = item.permissions item.checkedPermissions = item.permissions
.filter((permission) => selectedKeys.includes(permission.id)) .filter((permission) => selectedKeys.includes(permission.id))
.map((permission) => permission.id) .map((permission) => permission.id)
} }
// 递归处理子菜单 // 递归处理子菜单
if (item.children) { if (item.children) {
updateTableDataCheckedStatus(item.children, selectedKeys) updateTableDataCheckedStatus(item.children as ExtendedMenuResp[], selectedKeys)
} }
}) })
} }
// 查找指定菜单 - 使用 Map 缓存优化查找性能
const menuMap = ref<Map<string | number, ExtendedMenuResp>>(new Map())
// 构建菜单映射缓存
const buildMenuMap = (data: ExtendedMenuResp[]) => {
const map = new Map<string | number, ExtendedMenuResp>()
const traverse = (items: ExtendedMenuResp[]) => {
items.forEach((item) => {
map.set(item.id, item)
if (item.children?.length) {
traverse(item.children as ExtendedMenuResp[])
}
})
}
traverse(data)
menuMap.value = map
}
const selectedKeys = ref<Set<string | number>>(new Set()) const selectedKeys = ref<Set<string | number>>(new Set())
const { const {
@@ -151,110 +190,145 @@ const {
search, search,
} = useTable(() => listMenu(), { } = useTable(() => listMenu(), {
immediate: true, immediate: true,
formatResult(data) { formatResult(data: MenuResp[]) {
return transformMenu(data) return transformMenu(data)
}, },
onSuccess: () => { onSuccess: () => {
nextTick(() => { nextTick(() => {
tableRef.value?.tableRef?.expandAll(true) tableRef.value?.tableRef?.expandAll(true)
}) })
// 构建菜单映射缓存
buildMenuMap(tableData.value as ExtendedMenuResp[])
// 初始加载时应用已选中的权限 // 初始加载时应用已选中的权限
if (selectedKeys.value.size > 0) { if (selectedKeys.value.size > 0) {
updateTableDataCheckedStatus(tableData.value, Array.from(selectedKeys.value)) updateTableDataCheckedStatus(tableData.value as ExtendedMenuResp[], Array.from(selectedKeys.value))
} }
}, },
}) })
const columns: TableInstance['columns'] = [ const columns: TableInstance['columns'] = [
{ title: '菜单', dataIndex: 'title', slotName: 'title', width: 170, fixed: !isMobile() ? 'left' : undefined }, { title: '菜单', dataIndex: 'title', slotName: 'title', width: 170, ellipsis: true, tooltip: true, fixed: !isMobile() ? 'left' : undefined },
{ title: '权限', dataIndex: 'permissions', slotName: 'permissions' }, { title: '权限', dataIndex: 'permissions', slotName: 'permissions' },
] ]
// 级联选中子项 // 级联选中子项
const cascadeSelectChild = (record: MenuResp, isCascade: boolean) => { const cascadeSelectChild = (record: ExtendedMenuResp, isCascade: boolean) => {
if (isCascade && record.children && record.children.length > 0) { if (!isCascade) return
// 批量处理子菜单
if (record.children && record.children.length > 0) {
record.children.forEach((child) => { record.children.forEach((child) => {
child.isChecked = record.isChecked const extendedChild = child as ExtendedMenuResp
tableRef.value?.tableRef?.select(child.id, child.isChecked) extendedChild.isChecked = record.isChecked
child.isChecked tableRef.value?.tableRef?.select(child.id, extendedChild.isChecked)
? selectedKeys.value.add(child.id)
: selectedKeys.value.delete(child.id) // 使用批量操作优化性能
if ((child.children && child.children.length > 0) || (child.permissions && child.permissions.length > 0)) { if (extendedChild.isChecked) {
cascadeSelectChild(child, isCascade) selectedKeys.value.add(child.id)
} else {
selectedKeys.value.delete(child.id)
}
// 递归处理子项
if (child.children?.length || extendedChild.permissions?.length) {
cascadeSelectChild(extendedChild, isCascade)
} }
}) })
} }
// 递归选中权限
if (isCascade && record.permissions && record.permissions.length > 0) { // 批量处理权限
if (record.permissions && record.permissions.length > 0) {
const checkedPermissions: (string | number)[] = []
record.permissions.forEach((permission) => { record.permissions.forEach((permission) => {
permission.isChecked = record.isChecked permission.isChecked = record.isChecked
permission.isChecked if (permission.isChecked) {
? selectedKeys.value.add(permission.id) selectedKeys.value.add(permission.id)
: selectedKeys.value.delete(permission.id) checkedPermissions.push(permission.id)
} else {
selectedKeys.value.delete(permission.id)
}
}) })
record.checkedPermissions = record.permissions.filter((permission) => permission.isChecked).map((permission) => permission.id) record.checkedPermissions = checkedPermissions
} }
} }
// 查找指定菜单 // 查找指定菜单
const findItem = (id: string, data: MenuResp[]) => { const findItem = (id: string | number): ExtendedMenuResp | null => {
for (const item of data) { return menuMap.value.get(id) || null
if (item.id === id) return item
if (item.children?.length) {
const found = findItem(id, item.children)
if (found) return found
}
}
return null
} }
// 级联选中父项目 // 级联选中父项目
const cascadeSelectParent = (record: MenuResp, isCascade: boolean) => { const cascadeSelectParent = (record: ExtendedMenuResp, isCascade: boolean) => {
if (isCascade && record.parentId && record.parentId !== '0') { if (!isCascade || !record.parentId || record.parentId === '0') return
const parent = findItem(record.parentId, tableData.value)
if (parent) { const parent = findItem(record.parentId)
// 如果父项目的某个子项被选中了,它就依然保持选中状态 if (!parent) return
parent.isChecked = parent.children?.some((child) => child.isChecked)
tableRef.value?.tableRef?.select(parent.id, parent.isChecked) // 检查父项目的所有子项是否有被选中的
if (!parent.isChecked && !record.isChecked) { const hasCheckedChildren = parent.children?.some((child) => (child as ExtendedMenuResp).isChecked)
selectedKeys.value.delete(parent.id) parent.isChecked = hasCheckedChildren || false
} else {
selectedKeys.value.add(parent.id) // 更新表格选中状态
} tableRef.value?.tableRef?.select(parent.id, parent.isChecked)
if (parent.parentId && parent.parentId !== 0) {
cascadeSelectParent(parent, isCascade) // 更新选中键集合
} if (parent.isChecked) {
} selectedKeys.value.add(parent.id)
} else {
selectedKeys.value.delete(parent.id)
}
// 递归处理父级的父级
if (parent.parentId && parent.parentId !== '0') {
cascadeSelectParent(parent, isCascade)
} }
} }
// 选中 // 选中
const select: TableInstance['onSelect'] = (rowKeys, checked, record) => { const select: TableInstance['onSelect'] = (rowKeys, checked, record) => {
const extendedRecord = record as ExtendedMenuResp
// 如果处于禁用状态,直接返回,不执行任何逻辑
if (disabled.value || extendedRecord.disabled) {
return
}
const isChecked = rowKeys.includes(checked) const isChecked = rowKeys.includes(checked)
isChecked isChecked
? selectedKeys.value.add(record.id) ? selectedKeys.value.add(extendedRecord.id)
: selectedKeys.value.delete(record.id) : selectedKeys.value.delete(extendedRecord.id)
record.isChecked = isChecked extendedRecord.isChecked = isChecked
// 级联选中子项 // 级联选中子项
cascadeSelectChild(record, isCascade.value) cascadeSelectChild(extendedRecord, isCascade.value)
// 级联选中父项 // 级联选中父项
cascadeSelectParent(record, isCascade.value) cascadeSelectParent(extendedRecord, isCascade.value)
} }
// 全选 // 全选
const selectAll: TableInstance['onSelectAll'] = (checked) => { const selectAll: TableInstance['onSelectAll'] = (checked) => {
// 如果处于禁用状态,直接返回,不执行任何逻辑
if (disabled.value) {
return
}
tableData.value.forEach((item) => { tableData.value.forEach((item) => {
item.isChecked = checked const extendedItem = item as ExtendedMenuResp
extendedItem.isChecked = checked
checked checked
? selectedKeys.value.add(item.id) ? selectedKeys.value.add(item.id)
: selectedKeys.value.delete(item.id) : selectedKeys.value.delete(item.id)
cascadeSelectChild(item, true) cascadeSelectChild(extendedItem, true)
}) })
} }
// 选中权限 // 选中权限
const selectPermission = (record) => { const selectPermission = (record: ExtendedMenuResp) => {
const checkPermissions = record.checkedPermissions // 如果处于禁用状态,直接返回,不执行任何逻辑
if (disabled.value || record.disabled) {
return
}
const checkPermissions = record.checkedPermissions || []
// 取消选中 // 取消选中
if (checkPermissions.length === 0) { if (checkPermissions.length === 0) {
if (isCascade.value) { if (isCascade.value) {
@@ -263,7 +337,7 @@ const selectPermission = (record) => {
tableRef.value?.tableRef?.select(record.id, record.isChecked) tableRef.value?.tableRef?.select(record.id, record.isChecked)
cascadeSelectParent(record, isCascade.value) cascadeSelectParent(record, isCascade.value)
} }
record.permissions.forEach((permission) => { record.permissions?.forEach((permission) => {
permission.isChecked = false permission.isChecked = false
selectedKeys.value.delete(permission.id) selectedKeys.value.delete(permission.id)
}) })
@@ -277,7 +351,7 @@ const selectPermission = (record) => {
tableRef.value?.tableRef?.select(record.id, record.isChecked) tableRef.value?.tableRef?.select(record.id, record.isChecked)
cascadeSelectParent(record, isCascade.value) cascadeSelectParent(record, isCascade.value)
} }
record.permissions.forEach((permission) => { record.permissions?.forEach((permission) => {
permission.isChecked = checkPermissions.includes(permission.id) permission.isChecked = checkPermissions.includes(permission.id)
permission.isChecked permission.isChecked
? selectedKeys.value.add(permission.id) ? selectedKeys.value.add(permission.id)
@@ -309,8 +383,10 @@ const fetchRole = async (id: string) => {
isCascade.value = data.menuCheckStrictly isCascade.value = data.menuCheckStrictly
// 更新选中键集合 // 更新选中键集合
selectedKeys.value = new Set(data.menuIds) selectedKeys.value = new Set(data.menuIds)
// 重新构建菜单映射缓存
buildMenuMap(tableData.value as ExtendedMenuResp[])
// 更新表格数据的选中状态 // 更新表格数据的选中状态
updateTableDataCheckedStatus(tableData.value, data.menuIds) updateTableDataCheckedStatus(tableData.value as ExtendedMenuResp[], data.menuIds)
// 手动设置表格行的选中状态,确保组件响应 // 手动设置表格行的选中状态,确保组件响应
await nextTick(() => { await nextTick(() => {
tableRef.value?.tableRef?.selectAll(false) tableRef.value?.tableRef?.selectAll(false)
@@ -325,7 +401,9 @@ const fetchRole = async (id: string) => {
// 刷新 // 刷新
const refresh = () => { const refresh = () => {
search() search()
fetchRole(props.roleId) if (props.roleId) {
fetchRole(props.roleId)
}
} }
// 监听 roleId 的变化 // 监听 roleId 的变化