董国庆
2026-02-25 196186cdafc3b559053cda6260d709886450945a
路由权限修改
7个文件已修改
795 ■■■■■ 已修改文件
service.config.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directives/permission.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/usePermission.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/auth.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/router/helper.ts 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/typings/api/login.d.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/accountPermission/permissionGroup/form.vue 642 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service.config.ts
@@ -1,8 +1,8 @@
/** 不同请求服务的环境配置 */
export const serviceConfig: Record<ServiceEnvType, Record<string, string>> = {
  dev: {
    // url: 'http://localhost:3000',
    url: 'http://221.182.45.100:8000',
    url: 'http://192.168.110.80:8000',
    // url: 'http://221.182.45.100:8000',
  },
  production: {
    url: 'https://mock.apifox.cn/m1/4071143-0-default',
src/directives/permission.ts
@@ -4,7 +4,7 @@
export function install(app: App) {
  const { hasPermission } = usePermission()
  function updatapermission(el: HTMLElement, permission: Entity.RoleType | Entity.RoleType[]) {
  function updatapermission(el: HTMLElement, permission: Entity.RoleType | Entity.RoleType[] | string | string[]) {
    if (!permission)
      throw new Error('v-permissson Directive with no explicit role attached')
@@ -12,7 +12,7 @@
      el.parentElement?.removeChild(el)
  }
  const permissionDirective: Directive<HTMLElement, Entity.RoleType | Entity.RoleType[]> = {
  const permissionDirective: Directive<HTMLElement, Entity.RoleType | Entity.RoleType[] | string | string[]> = {
    mounted(el, binding) {
      updatapermission(el, binding.value)
    },
src/hooks/usePermission.ts
@@ -1,12 +1,12 @@
import { useAuthStore } from '@/store'
import { isArray, isString } from 'radash'
import { isArray, isString, isNumber } from 'radash'
/** 权限判断 */
export function usePermission() {
  const authStore = useAuthStore()
  function hasPermission(
    permission?: Entity.RoleType | Entity.RoleType[],
    permission?: Entity.RoleType | Entity.RoleType[] | string | string[] | number | number[],
  ) {
    if (!permission)
      return true
@@ -15,23 +15,27 @@
      return false
    const { role } = authStore.userInfo
    // 如果用户没有角色信息,默认允许访问(临时方案)
    if (!role || role.length === 0)
      return true
    const { permissions } = authStore
    // 角色为super可直接通过
    let has = role.includes('super')
    if (!has) {
      if (isArray(permission))
        // 角色为数组, 判断是否有交集
        has = permission.some(i => role.includes(i))
    if (role && role.includes('super'))
      return true
      if (isString(permission))
        // 角色为字符串, 判断是否包含
        has = role.includes(permission)
    if (isArray(permission)) {
      // 角色为数组, 判断是否有交集 或 权限是否有交集
      return permission.some(i =>
        (role && role.includes(i as Entity.RoleType)) ||
        (permissions && permissions.some(p => String(p) === String(i)))
      )
    }
    return has
    if (isString(permission) || isNumber(permission)) {
      // 角色为字符串/数字, 判断是否包含 或 权限是否包含
      return (role && role.includes(permission as Entity.RoleType)) ||
             (permissions && permissions.some(p => String(p) === String(permission)))
    }
    return false
  }
  return {
src/store/auth.ts
@@ -7,12 +7,14 @@
interface AuthStatus {
  userInfo: Entity.User | null
  token: string
  permissions: string[]
}
export const useAuthStore = defineStore('auth-store', {
  state: (): AuthStatus => {
    return {
      userInfo: local.get('userInfo'),
      token: local.get('accessToken') || '',
      permissions: local.get('permissions') || [],
    }
  },
  getters: {
@@ -49,6 +51,7 @@
      local.remove('accessToken')
      local.remove('refreshToken')
      local.remove('userInfo')
      local.remove('permissions')
    },
    async loginEncrypted(userName: string, password: string) {
@@ -70,8 +73,10 @@
      // 将token和userInfo保存下来
      local.set('userInfo', data.info.sysUser)
      local.set('accessToken', data.token.access_token)
      local.set('permissions', data.info.permissions)
      this.token = data.token.access_token
      this.userInfo = data.info.sysUser
      this.permissions = data.info.permissions
      // 添加路由和菜单
      const routeStore = useRouteStore()
src/store/router/helper.ts
@@ -29,7 +29,15 @@
  console.log('After standardization:', resultRouter)
  // Route permission filtering
  resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles))
  resultRouter = resultRouter.filter(i => {
    // 优先检查路由 ID 是否在权限列表中
    if (i.id) {
      return hasPermission(i.id)
    }
    // 否则检查 meta.roles
    return hasPermission(i.meta.roles)
  })
  console.log('After permission filtering:', resultRouter)
  // Generate routes, no need to import files for those with redirect
@@ -98,63 +106,64 @@
        if (orderChilds.length > 0)
          target = min(orderChilds, i => i.meta.order!) as AppRoute.Route
        if (target)
          route.redirect = target.path
        route.redirect = target ? target.path : ''
      }
      setRedirect(route.children)
    }
  })
}
/* 生成侧边菜单的数据 */
export function createMenus(userRoutes: AppRoute.RowRoute[]) {
  const resultMenus = standardizedRoutes(userRoutes)
/**
 * 递归生成菜单
 */
export function createMenus(routes: AppRoute.RowRoute[]) {
  const { hasPermission } = usePermission()
  // filter menus that do not need to be displayed
  const visibleMenus = resultMenus.filter(route => !route.meta.hide)
  // Structure the meta field
  let resultRouter = standardizedRoutes(routes)
  // generate side menu
  return arrayToTree(transformAuthRoutesToMenus(visibleMenus))
  // Route permission filtering
  resultRouter = resultRouter.filter(i => {
    // 优先检查路由 ID 是否在权限列表中
    if (i.id) {
      return hasPermission(i.id)
    }
    // 否则检查 meta.roles
    return hasPermission(i.meta.roles)
  })
  // Generate route tree
  resultRouter = arrayToTree(resultRouter) as AppRoute.Route[]
  // Sort by order
  resultRouter.sort((a, b) => (a.meta.order || 0) - (b.meta.order || 0))
  return transformMenu(resultRouter)
}
// render the returned routing table as a sidebar
function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) {
  const { hasPermission } = usePermission()
  return userRoutes
    // Filter out side menus without permission
    .filter(i => hasPermission(i.meta.roles))
    //  Sort the menu according to the order size
    .sort((a, b) => {
      if (a.meta && a.meta.order && b.meta && b.meta.order)
        return a.meta.order - b.meta.order
      else if (a.meta && a.meta.order)
        return -1
      else if (b.meta && b.meta.order)
        return 1
      else return 0
    })
    // Convert to side menu data structure
    .map((item) => {
      const target: MenuOption = {
        id: item.id,
        pid: item.pid,
        label:
          (!item.meta.menuType || item.meta.menuType === 'page')
            ? () =>
                h(
                  RouterLink,
                  {
                    to: {
                      path: item.path,
                    },
                  },
                  { default: () => $t(`route.${String(item.name)}`, item.meta.title) },
                )
            : () => $t(`route.${String(item.name)}`, item.meta.title),
        key: item.path,
        icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
function transformMenu(routes: AppRoute.Route[]): MenuOption[] {
  return routes
    .filter(route => !route.meta.hide)
    .map(route => {
      const menu: MenuOption = {
        key: route.name,
        label: route.meta.title ? $t(route.meta.title) : route.name,
        icon: route.meta.icon ? renderIcon(route.meta.icon) : undefined,
      }
      return target
      if (route.children && route.children.length > 0) {
        menu.children = transformMenu(route.children)
      } else if (route.meta.menuType === 'page') {
        // 如果是页面类型的菜单,点击跳转
        menu.label = () => h(
          RouterLink,
          {
            to: {
              name: route.name,
            },
          },
          { default: () => (route.meta.title ? $t(route.meta.title) : route.name) },
        )
      }
      return menu
    })
}
src/typings/api/login.d.ts
@@ -14,6 +14,7 @@
      }
      info: {
        sysUser: Entity.User
        permissions: string[]
      }
    }
  }
src/views/system/accountPermission/permissionGroup/form.vue
@@ -12,6 +12,7 @@
  NSpace,
  NTabPane,
  NTabs,
  NTree,
  NDataTable,
} from 'naive-ui'
import {
@@ -21,6 +22,15 @@
  updatePermissionGroup,
  getMenuList
} from '@/service'
// 用于生成唯一的key
let uniqueIdCounter = 0
function generateUniqueKey() {
  return `node_${uniqueIdCounter++}`
}
// 节点映射表,用于通过 key 查找节点信息
const nodeMap = reactive(new Map<string, any>())
const route = useRoute()
const router = useRouter()
@@ -116,7 +126,7 @@
const activeTab = ref<'management' | 'operation'>('management')
const groupType = computed(() => (activeTab.value === 'management' ? 1 : 2))
// 处理标签页切换
    // 处理标签页切换
function handleTabChange(value: 'management' | 'operation') {
  if (mode.value === 'view') {
    activeTab.value = value
@@ -124,7 +134,7 @@
  }
  const currentTab = activeTab.value
  const hasSelected = Object.values(selectedPermissions.value[currentTab] || {}).some(val => val)
  const hasSelected = selectedPermissions[currentTab].checkedKeys.length > 0 || selectedPermissions[currentTab].indeterminateKeys.length > 0
  if (hasSelected) {
    window.$dialog?.warning({
@@ -133,7 +143,8 @@
      positiveText: '确认切换',
      negativeText: '取消',
      onPositiveClick: () => {
        selectedPermissions.value[currentTab] = {}
        selectedPermissions[currentTab].checkedKeys = []
        selectedPermissions[currentTab].indeterminateKeys = []
        activeTab.value = value
      },
    })
@@ -152,8 +163,24 @@
// 虚拟权限数据 - 管理端
const managementPermissions = ref<any[]>([])
// 转换树数据,为每个节点生成唯一 key
function transformTreeData(nodes: any[]): any[] {
  return nodes.map(node => {
    const key = generateUniqueKey()
    const newNode = {
      ...node,
      key, // 使用 key 属性作为唯一标识
      children: node.children ? transformTreeData(node.children) : undefined,
    }
    // 存储节点映射
    nodeMap.set(key, newNode)
    return newNode
  })
}
// 虚拟权限数据 - 运维APP
const operationPermissions = ref([
const operationPermissions = ref(transformTreeData([
  {
    id: '10',
    menuName: '车辆管理',
@@ -183,18 +210,100 @@
      },
    ],
  },
])
]))
// 当前权限数据
const currentPermissions = computed(() => {
  return activeTab.value === 'management' ? managementPermissions.value : operationPermissions.value
})
// 选中的权限
const selectedPermissions = ref<Record<string, any>>({
  management: {},
  operation: {},
// 详情模式下的权限数据(禁用所有节点,但允许展开查看)
const viewPermissions = computed(() => {
  const disableNodes = (nodes: any[]): any[] => {
    return nodes.map(node => ({
      ...node,
      disabled: true,
      children: node.children ? disableNodes(node.children) : undefined,
    }))
  }
  return disableNodes(currentPermissions.value)
})
// 选中的权限
const selectedPermissions = reactive({
  management: {
    checkedKeys: [] as Array<string | number>,
    indeterminateKeys: [] as Array<string | number>,
  },
  operation: {
    checkedKeys: [] as Array<string | number>,
    indeterminateKeys: [] as Array<string | number>,
  },
})
// 过滤出叶子节点的ID,用于回显
function filterLeafKeys(menuIds: (string | number)[], treeData: any[]) {
  const leafKeys: string[] = []
  const idSet = new Set(menuIds.map(String))
  function traverse(nodes: any[]) {
    nodes.forEach((node) => {
      if (!node.children || node.children.length === 0) {
        if (idSet.has(String(node.id))) {
          // 如果节点ID匹配,使用生成的唯一 key
          leafKeys.push(node.key)
        }
      }
      else {
        traverse(node.children)
      }
    })
  }
  traverse(treeData)
  return leafKeys
}
// 计算树节点的选中状态(全选/半选)
function getTreeStatus(leafKeys: string[], treeData: any[]) {
  const checkedKeys: string[] = []
  const indeterminateKeys: string[] = []
  const leafSet = new Set(leafKeys)
  function processNode(node: any): number {
    if (!node.children || node.children.length === 0) {
      const isChecked = leafSet.has(node.key)
      if (isChecked) {
        checkedKeys.push(node.key)
        return 1
      }
      return 0
    }
    let hasChecked = false
    let hasUnchecked = false
    let hasIndeterminate = false
    for (const child of node.children) {
      const status = processNode(child)
      if (status === 1) hasChecked = true
      else if (status === 0) hasUnchecked = true
      else hasIndeterminate = true
    }
    if (hasIndeterminate || (hasChecked && hasUnchecked)) {
      indeterminateKeys.push(node.key)
      return 2
    }
    else if (hasChecked) {
      checkedKeys.push(node.key)
      return 1
    }
    return 0
  }
  treeData.forEach(node => processNode(node))
  return { checkedKeys, indeterminateKeys }
}
// 表单验证规则
const rules: FormRules = {
@@ -225,12 +334,17 @@
        // 回显选中的权限
        if (data.menuIds && Array.isArray(data.menuIds)) {
          const currentTab = activeTab.value
          if (!selectedPermissions.value[currentTab]) {
            selectedPermissions.value[currentTab] = {}
          const leafKeys = filterLeafKeys(data.menuIds, currentPermissions.value)
          if (mode.value === 'view') {
            // View 模式下,手动计算全选和半选状态
            const { checkedKeys, indeterminateKeys } = getTreeStatus(leafKeys, currentPermissions.value)
            selectedPermissions[currentTab].checkedKeys = checkedKeys
            selectedPermissions[currentTab].indeterminateKeys = indeterminateKeys
          } else {
            // Edit/Add 模式下,仅设置叶子节点,依赖 NTree 的 cascade 自动推导
            selectedPermissions[currentTab].checkedKeys = leafKeys
          }
          data.menuIds.forEach((menuId: string | number) => {
            selectedPermissions.value[currentTab][menuId.toString()] = true
          })
        }
      }
    }
@@ -255,13 +369,22 @@
  loading.value = true
  try {
    const currentTab = activeTab.value
    const menuIds = Object.keys(selectedPermissions.value[currentTab] || {})
      .filter(key => selectedPermissions.value[currentTab][key])
    const { checkedKeys, indeterminateKeys } = selectedPermissions[currentTab]
    // 从 keys 中还原出原始 ID
    const allKeys = new Set([...checkedKeys, ...indeterminateKeys])
    const menuIds = Array.from(allKeys).map(key => {
      const node = nodeMap.get(key as string)
      return node ? node.id : null
    }).filter(id => id !== null)
    // 去重,防止同一个 ID 对应多个 key 的情况导致重复提交
    const uniqueMenuIds = Array.from(new Set(menuIds))
    const params: any = {
      groupName: formModel.value.groupName,
      groupType: groupType.value,
      menuIds,
      menuIds: uniqueMenuIds,
      remarks: formModel.value.remarks,
    }
@@ -289,206 +412,17 @@
  }
}
// 检查模块是否全选
function isModuleChecked(moduleKey: string) {
  const currentTab = activeTab.value
  const moduleData = currentPermissions.value.find(m => m.id === moduleKey)
  if (!moduleData || !moduleData.children || moduleData.children.length === 0)
    return false
  // 检查模块本身是否被选中
  const moduleIsSelected = selectedPermissions.value[currentTab]?.[moduleData.id]
  // 检查模块下所有页面(二级菜单)和权限(三级菜单)是否都选中
  const allPagesAndPermsSelected = moduleData.children.every(page => {
    // 检查页面本身是否被选中
    const pageIsSelected = selectedPermissions.value[currentTab]?.[page.id]
    // 如果页面下有子权限,还需要检查子权限是否全部被选中
    if (page.children && page.children.length > 0) {
      const allChildrenSelected = page.children.every(perm =>
        selectedPermissions.value[currentTab]?.[perm.id],
      )
      // 页面本身和所有子权限都需要被选中
      return pageIsSelected && allChildrenSelected
    }
    // 如果页面下没有子权限,只检查页面本身是否被选中
    return pageIsSelected
  })
  // 模块本身、所有页面和权限都需要被选中
  return moduleIsSelected && allPagesAndPermsSelected
}
// 检查模块是否部分选中
function isModuleIndeterminate(moduleKey: string) {
  const currentTab = activeTab.value
  const moduleData = currentPermissions.value.find(m => m.id === moduleKey)
  if (!moduleData || !moduleData.children || moduleData.children.length === 0)
    return false
  // 计算模块下所有数据的选中状态(包括模块本身、页面和权限)
  let selectedCount = 0
  let totalCount = 1 // 模块本身算一个
  // 检查模块本身是否被选中
  if (selectedPermissions.value[currentTab]?.[moduleData.id]) {
    selectedCount++
  }
  for (const page of moduleData.children) {
    totalCount++ // 页面本身算一个
    // 检查页面本身是否被选中
    if (selectedPermissions.value[currentTab]?.[page.id]) {
      selectedCount++
    }
    // 检查页面下的子权限
    if (page.children && page.children.length > 0) {
      totalCount += page.children.length
      selectedCount += page.children.filter(perm =>
        selectedPermissions.value[currentTab]?.[perm.id],
      ).length
    }
  }
  return selectedCount > 0 && selectedCount < totalCount
}
// 切换模块
function toggleModule(moduleKey: string, checked: boolean) {
  const currentTab = activeTab.value
  const moduleData = currentPermissions.value.find(m => m.id === moduleKey)
  if (!moduleData || !moduleData.children || moduleData.children.length === 0)
    return
  if (!selectedPermissions.value[currentTab]) {
    selectedPermissions.value[currentTab] = {}
  }
  // 设置模块本身的状态
  selectedPermissions.value[currentTab][moduleData.id] = checked
  // 切换模块下所有页面(二级菜单)和权限(三级菜单)的状态
  moduleData.children.forEach((page) => {
    // 设置页面本身的状态
    selectedPermissions.value[currentTab][page.id] = checked
    // 如果页面下有子权限,也要设置它们的状态
    if (page.children && page.children.length > 0) {
      page.children.forEach((perm) => {
        selectedPermissions.value[currentTab][perm.id] = checked
      })
    }
  })
}
// 检查页面是否全选
function isPageChecked(moduleKey: string, pageKey: string) {
  const currentTab = activeTab.value
  const moduleData = currentPermissions.value.find(m => m.id === moduleKey)
  const pageData = moduleData?.children.find(p => p.id === pageKey)
  if (!pageData)
    return false
  // 检查页面本身是否被选中,如果有权限ID的话
  const pageIsSelected = selectedPermissions.value[currentTab]?.[pageData.id]
  // 如果页面下有子权限,还需要检查子权限是否全部被选中
  if (pageData.children && pageData.children.length > 0) {
    const allChildrenSelected = pageData.children.every(perm =>
      selectedPermissions.value[currentTab]?.[perm.id],
    )
    // 如果页面本身被选中,且所有子权限也被选中,则返回true
    // 或者只有子权限全选而页面本身未选中(这种情况取决于业务逻辑)
    return pageIsSelected || allChildrenSelected
  }
  // 如果页面下没有子权限,只检查页面本身是否被选中
  return pageIsSelected || false
}
// 检查页面是否部分选中
function isPageIndeterminate(moduleKey: string, pageKey: string) {
  const currentTab = activeTab.value
  const moduleData = currentPermissions.value.find(m => m.id === moduleKey)
  const pageData = moduleData?.children.find(p => p.id === pageKey)
  if (!pageData)
    return false
  // 检查页面本身是否被选中
  const pageIsSelected = selectedPermissions.value[currentTab]?.[pageData.id]
  // 检查页面下的子权限
  if (pageData.children && pageData.children.length > 0) {
    const checkedCount = pageData.children.filter(perm =>
      selectedPermissions.value[currentTab]?.[perm.id],
    ).length
    // 如果页面本身被选中,但不是所有子权限都被选中,则为部分选中
    if (pageIsSelected && checkedCount < pageData.children.length) {
      return true
    }
    // 如果页面本身未被选中,但有部分子权限被选中,则为部分选中
    if (!pageIsSelected && checkedCount > 0 && checkedCount < pageData.children.length) {
      return true
    }
  }
  return false
}
// 切换页面
function togglePage(moduleKey: string, pageKey: string, checked: boolean) {
  const currentTab = activeTab.value
  const moduleData = currentPermissions.value.find(m => m.id === moduleKey)
  const pageData = moduleData?.children.find(p => p.id === pageKey)
  if (!pageData)
    return
  if (!selectedPermissions.value[currentTab]) {
    selectedPermissions.value[currentTab] = {}
  }
  // 设置页面本身的状态
  selectedPermissions.value[currentTab][pageData.id] = checked
  // 如果页面下有子权限,也要设置它们的状态
  if (pageData.children && pageData.children.length > 0) {
    pageData.children.forEach((perm) => {
      selectedPermissions.value[currentTab][perm.id] = checked
    })
  }
}
// 切换单个权限
function togglePermission(moduleKey: string, pageKey: string, permKey: string, checked: boolean) {
  const currentTab = activeTab.value
  if (!selectedPermissions.value[currentTab]) {
    selectedPermissions.value[currentTab] = {}
  }
  selectedPermissions.value[currentTab][permKey] = checked
}
// 检查权限是否选中
function isPermissionChecked(moduleKey: string, pageKey: string, permKey: string) {
  const currentTab = activeTab.value
  return selectedPermissions.value[currentTab]?.[permKey] || false
}
const getMenuTree = async () => {
  getMenuList().then((res) => {
  return getMenuList().then((res) => {
    if (res.code === 200) {
      managementPermissions.value = res.data
      managementPermissions.value = transformTreeData(res.data || [])
    }
  })
}
onMounted(() => {
  getPermissionGroupDetail()
  getMenuTree()
onMounted(async () => {
  await getMenuTree()
  await getPermissionGroupDetail()
})
</script>
@@ -522,47 +456,19 @@
            <!-- 详情模式下的 Tab: 权限信息 / 关联用户 -->
            <NTabs v-if="mode === 'view'" v-model:value="mainTab" type="line" animated>
              <NTabPane name="permission" tab="权限信息">
                <div class="permission-table">
                  <div class="table-header">
                    <div class="col-module">
                      板块
                    </div>
                    <div class="col-page">
                      页面
                    </div>
                    <div class="col-permissions">
                      权限
                    </div>
                  </div>
                  <div class="table-body">
                    <div v-for="module in currentPermissions" :key="module.id" class="module-row">
                      <div class="col-module">
                        <NCheckbox :checked="isModuleChecked(module.id)"
                          :indeterminate="isModuleIndeterminate(module.id)" :disabled="true">
                          {{ module.menuName }}
                        </NCheckbox>
                      </div>
                      <div class="col-page-permissions">
                        <div v-for="page in module.children" :key="page.id" class="page-row">
                          <div class="col-page">
                            <NCheckbox :checked="isPageChecked(module.id, page.id)"
                              :indeterminate="isPageIndeterminate(module.id, page.id)" :disabled="true">
                              {{ page.menuName }}
                            </NCheckbox>
                          </div>
                          <div class="col-permissions">
                            <NSpace :size="[16, 8]" :wrap="true">
                              <NCheckbox v-for="perm in page.children" :key="perm.id"
                                :checked="isPermissionChecked(module.id, page.id, perm.id)" :disabled="true">
                                {{ perm.menuName }}
                              </NCheckbox>
                            </NSpace>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                <div class="tree-container">
                  <NTree
                    block-line
                    :cascade="false"
                    checkable
                    :data="viewPermissions"
                    key-field="key"
                    label-field="menuName"
                    children-field="children"
                    :default-expand-all="false"
                    :checked-keys="selectedPermissions[activeTab].checkedKeys"
                    :indeterminate-keys="selectedPermissions[activeTab].indeterminateKeys"
                  />
                </div>
              </NTabPane>
              <NTabPane name="accounts" tab="关联用户">
@@ -588,114 +494,41 @@
            <!-- 新增/编辑模式下的 Tab: 管理端 / 运维APP -->
            <NTabs v-else :value="activeTab" type="line" animated @update:value="handleTabChange">
              <NTabPane name="management" tab="管理端">
                <div class="permission-table">
                  <div class="table-header">
                    <div class="col-module">
                      板块
                    </div>
                    <div class="col-page">
                      页面
                    </div>
                    <div class="col-permissions">
                      权限
                    </div>
                  </div>
                  <div class="table-body">
                    <div v-for="module in managementPermissions" :key="module.id" class="module-row">
                      <div class="col-module">
                        <NCheckbox :checked="isModuleChecked(module.id)"
                          :indeterminate="isModuleIndeterminate(module.id)"
                          @update:checked="(checked) => toggleModule(module.id, checked)">
                          {{ module.menuName }}
                        </NCheckbox>
                      </div>
                      <div class="col-page-permissions">
                        <div v-for="page in module.children" :key="page.id" class="page-row">
                          <div class="col-page">
                            <NCheckbox :checked="isPageChecked(module.id, page.id)"
                              :indeterminate="isPageIndeterminate(module.id, page.id)"
                              @update:checked="(checked) => togglePage(module.id, page.id, checked)">
                              {{ page.menuName }}
                            </NCheckbox>
                          </div>
                          <div class="col-permissions">
                            <NSpace :size="[16, 8]" :wrap="true">
                              <NCheckbox v-for="perm in page.children" :key="perm.id"
                                :checked="isPermissionChecked(module.id, page.id, perm.id)"
                                @update:checked="(checked) => togglePermission(module.id, page.id, perm.id, checked)">
                                {{ perm.menuName }}
                              </NCheckbox>
                            </NSpace>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                <div class="tree-container">
                  <NTree
                    block-line
                    cascade
                    checkable
                    :data="currentPermissions"
                    key-field="key"
                    label-field="menuName"
                    children-field="children"
                    :default-expand-all="false"
                    :checked-keys="selectedPermissions[activeTab].checkedKeys"
                    :indeterminate-keys="selectedPermissions[activeTab].indeterminateKeys"
                    @update:checked-keys="(keys) => selectedPermissions[activeTab].checkedKeys = keys"
                    @update:indeterminate-keys="(keys) => selectedPermissions[activeTab].indeterminateKeys = keys"
                  />
                </div>
              </NTabPane>
              <!-- <NTabPane name="operation" tab="运维APP">
                <div class="permission-table">
                  <div class="table-header">
                    <div class="col-module">
                      板块
                    </div>
                    <div class="col-page">
                      页面
                    </div>
                    <div class="col-permissions">
                      权限
                    </div>
                  </div>
                  <div class="table-body">
                    <div
                      v-for="module in operationPermissions"
                      :key="module.id"
                      class="module-row"
                    >
                      <div class="col-module">
                        <NCheckbox
                          :checked="isModuleChecked(module.id)"
                          :indeterminate="isModuleIndeterminate(module.id)"
                          @update:checked="(checked) => toggleModule(module.id, checked)"
                        >
                          {{ module.menuName }}
                        </NCheckbox>
                      </div>
                      <div class="col-page-permissions">
                        <div
                          v-for="page in module.children"
                          :key="page.id"
                          class="page-row"
                        >
                          <div class="col-page">
                            <NCheckbox
                              :checked="isPageChecked(module.id, page.id)"
                              :indeterminate="isPageIndeterminate(module.id, page.id)"
                              @update:checked="(checked) => togglePage(module.id, page.id, checked)"
                            >
                              {{ page.menuName }}
                            </NCheckbox>
                          </div>
                          <div class="col-permissions">
                            <NSpace :size="[16, 8]" :wrap="true">
                              <NCheckbox
                                v-for="perm in page.children"
                                :key="perm.id"
                                :checked="isPermissionChecked(module.id, page.id, perm.id)"
                                @update:checked="(checked) => togglePermission(module.id, page.id, perm.id, checked)"
                              >
                                {{ perm.menuName }}
                              </NCheckbox>
                            </NSpace>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
              <NTabPane name="operation" tab="运维APP">
                <div class="tree-container">
                  <NTree
                    block-line
                    cascade
                    checkable
                    :data="currentPermissions"
                    key-field="key"
                    label-field="menuName"
                    children-field="children"
                    :default-expand-all="false"
                    :checked-keys="selectedPermissions[activeTab].checkedKeys"
                    :indeterminate-keys="selectedPermissions[activeTab].indeterminateKeys"
                    @update:checked-keys="(keys) => selectedPermissions[activeTab].checkedKeys = keys"
                    @update:indeterminate-keys="(keys) => selectedPermissions[activeTab].indeterminateKeys = keys"
                  />
                </div>
              </NTabPane> -->
              </NTabPane>
            </NTabs>
          </div>
        </NFormItem>
@@ -732,94 +565,11 @@
  width: 100%;
}
.permission-table {
  width: 100%;
  border: 1px solid #e8e8e8;
  border-radius: 2px;
  overflow: hidden;
  background-color: #fff;
}
.table-header {
  display: flex;
  background-color: #fafafa;
  border-bottom: 1px solid #e8e8e8;
  font-weight: 500;
  padding: 12px 16px;
  color: #333;
  text-align: center;
}
.table-header .col-module {
  width: 150px;
  flex-shrink: 0;
  text-align: center;
}
.table-header .col-page {
  width: 180px;
  flex-shrink: 0;
  text-align: center;
}
.table-header .col-permissions {
  flex: 1;
  text-align: center;
}
.table-body {
  background-color: #fff;
}
.module-row {
  display: flex;
  border-bottom: 1px solid #e8e8e8;
}
.module-row:last-child {
  border-bottom: none;
}
.module-row .col-module {
  width: 150px;
  flex-shrink: 0;
  padding: 16px;
  border-right: 1px solid #e8e8e8;
  display: flex;
  align-items: flex-start;
  background-color: #fafafa;
}
.module-row .col-page-permissions {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.page-row {
  display: flex;
  border-bottom: 1px solid #e8e8e8;
  min-height: 48px;
}
.page-row:last-child {
  border-bottom: none;
}
.page-row .col-page {
  width: 180px;
  flex-shrink: 0;
  padding: 12px 16px;
  border-right: 1px solid #e8e8e8;
  display: flex;
  align-items: center;
  background-color: #fafafa;
}
.page-row .col-permissions {
  flex: 1;
  padding: 12px 16px;
  display: flex;
  align-items: center;
.tree-container {
  height: 400px;
  overflow-y: auto;
  border: 1px solid #eee;
  padding: 12px;
  border-radius: 4px;
}
</style>