1
2 天以前 3a4a8c78098f41d3a7ce41f272cdefc35b572681
rsf-design/src/views/system/role/modules/role-permission-dialog.vue
@@ -1,14 +1,14 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="角色权限"
    :title="t('pages.system.role.permission.title')"
    size="860px"
    destroy-on-close
    @update:model-value="handleVisibleChange"
    @closed="handleClosed"
  >
    <div class="mb-4 text-sm text-[var(--art-text-secondary)]">
      当前角色:{{ roleLabel }}
      {{ t('pages.system.role.permission.currentRole') }}{{ roleLabel }}
    </div>
    <ElTabs v-model="activeScopeType" class="role-scope-tabs">
@@ -24,21 +24,21 @@
        <div v-else class="space-y-3">
          <div class="flex items-center justify-between gap-3">
            <ElSpace wrap>
              <ElButton @click="handleSelectAll(config.scopeType)">全选</ElButton>
              <ElButton @click="handleClear(config.scopeType)">清空</ElButton>
              <ElButton @click="handleSelectAll(config.scopeType)">{{ t('pages.system.role.permission.selectAll') }}</ElButton>
              <ElButton @click="handleClear(config.scopeType)">{{ t('pages.system.role.permission.clear') }}</ElButton>
            </ElSpace>
            <ElButton type="primary" @click="handleSave(config.scopeType)">保存当前权限</ElButton>
            <ElButton type="primary" @click="handleSave(config.scopeType)">{{ t('pages.system.role.permission.saveCurrent') }}</ElButton>
          </div>
          <div class="flex items-center gap-3">
            <ElInput
              v-model.trim="scopeState[config.scopeType].condition"
              clearable
              placeholder="搜索权限树"
              :placeholder="t('pages.system.role.permission.searchPlaceholder')"
              @clear="handleSearch(config.scopeType)"
              @keyup.enter="handleSearch(config.scopeType)"
            />
            <ElButton @click="handleSearch(config.scopeType)">搜索</ElButton>
            <ElButton @click="handleSearch(config.scopeType)">{{ t('common.actions.search') }}</ElButton>
          </div>
          <ElScrollbar height="56vh">
@@ -56,7 +56,7 @@
                <div class="flex items-center gap-2">
                  <span>{{ resolveScopeNodeLabel(data) }}</span>
                  <ElTag v-if="data.isAuthButton" type="info" effect="plain" size="small">
                    按钮
                    {{ t('pages.system.role.permission.authButton') }}
                  </ElTag>
                </div>
              </template>
@@ -72,11 +72,16 @@
  import {
    buildRoleScopeSubmitPayload,
    getRoleScopeConfig,
    normalizeScopeKeys,
    normalizeScopeKey,
    normalizeRoleScopeTreeData
  } from '../rolePage.helpers'
  import { fetchGetRoleScopeList, fetchGetRoleScopeTree, fetchUpdateRoleScope } from '@/api/system-manage'
  import { resolveBackendMenuTitle } from '@/utils/backend-menu-title'
  import { formatMenuTitle } from '@/utils/router'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import { ElMessage } from 'element-plus'
  import { useI18n } from 'vue-i18n'
  const props = defineProps({
    visible: { required: false, default: false },
@@ -85,6 +90,7 @@
  })
  const emit = defineEmits(['update:visible', 'success'])
  const { t } = useI18n()
  const scopeConfigs = ['menu', 'pda', 'matnr', 'warehouse'].map((scopeType) => getRoleScopeConfig(scopeType))
  const activeScopeType = ref(props.scopeType || 'menu')
@@ -93,46 +99,65 @@
    label: 'label',
    children: 'children'
  }
  const scopeState = reactive(
    Object.fromEntries(
      scopeConfigs.map((config) => [
        config.scopeType,
        {
          loading: false,
          loaded: false,
          treeData: [],
          checkedKeys: [],
          halfCheckedKeys: [],
          condition: ''
        }
      ])
    )
  )
  const scopeState = reactive(Object.fromEntries(scopeConfigs.map((config) => [config.scopeType, createScopeTabState()])))
  const visible = computed({
    get: () => props.visible,
    set: (value) => emit('update:visible', value)
  })
  const roleLabel = computed(() => props.roleData?.name || props.roleData?.code || '未选择角色')
  const roleLabel = computed(() => props.roleData?.name || props.roleData?.code || t('pages.system.role.permission.unselected'))
  function createScopeTabState() {
    return {
      loading: false,
      loaded: false,
      treeData: [],
      checkedKeys: [],
      halfCheckedKeys: [],
      condition: ''
    }
  }
  function resetScopeTabState(state) {
    Object.assign(state, createScopeTabState())
  }
  function hasNodeKey(value) {
    return value !== void 0 && value !== null && value !== ''
  }
  const loadScopeData = async (scopeType, { reloadSelection = true } = {}) => {
    const config = getRoleScopeConfig(scopeType)
    const state = scopeState[scopeType]
    state.loading = true
    try {
      const requests = [fetchGetRoleScopeTree(config.scopeType, { condition: state.condition || '' })]
      if (reloadSelection) {
        requests.unshift(fetchGetRoleScopeList(config.scopeType, props.roleData.id))
      }
      const selectionRequest = reloadSelection
        ? fetchGetRoleScopeList(config.scopeType, props.roleData.id)
        : Promise.resolve(state.checkedKeys)
      const treeRequest = fetchGetRoleScopeTree(config.scopeType, { condition: state.condition || '' })
      const [checkedIds, treeData] = reloadSelection ? await Promise.all(requests) : [state.checkedKeys, await requests[0]]
      const guardedResult = await guardRequestWithMessage(
        Promise.all([selectionRequest, treeRequest]),
        null,
        {
          timeoutMessage: t('pages.system.role.permission.scopeLoadTimeout', { title: config.title })
        }
      )
      if (!guardedResult) {
        state.treeData = []
        state.checkedKeys = []
        state.halfCheckedKeys = []
        state.loaded = true
        return
      }
      const [checkedIds, treeData] = guardedResult
      state.treeData = normalizeRoleScopeTreeData(config.scopeType, treeData)
      state.checkedKeys = normalizeScopeKeys(checkedIds)
      state.halfCheckedKeys = []
      state.loaded = true
    } catch (error) {
      ElMessage.error(error?.message || `加载${config.title}失败`)
      ElMessage.error(error?.message || t('pages.system.role.permission.scopeLoadFailed', { title: config.title }))
    } finally {
      state.loading = false
      nextTick(() => {
@@ -152,31 +177,6 @@
    }
    await loadScopeData(scopeType, { reloadSelection })
  }
  const normalizeScopeKeys = (keys = []) => {
    if (!Array.isArray(keys)) {
      return []
    }
    return Array.from(
      new Set(
        keys
          .map((key) => normalizeScopeKey(key))
          .filter((key) => key !== '')
      )
    )
  }
  const normalizeScopeKey = (value) => {
    if (value === '' || value === null || value === void 0) {
      return ''
    }
    const numeric = Number(value)
    if (Number.isNaN(numeric)) {
      return String(value)
    }
    return String(numeric)
  }
  const setTreeRef = (scopeType, el) => {
@@ -218,11 +218,11 @@
          scopeState[scopeType].halfCheckedKeys
        )
      )
      ElMessage.success('权限保存成功')
      ElMessage.success(t('pages.system.role.permission.saveSuccess'))
      emit('success')
      visible.value = false
    } catch (error) {
      ElMessage.error(error?.message || '权限保存失败')
      ElMessage.error(error?.message || t('pages.system.role.permission.saveFailed'))
    }
  }
@@ -239,18 +239,17 @@
    if (!rawLabel) {
      return ''
    }
    return resolveBackendMenuTitle(rawLabel)
    if (activeScopeType.value === 'menu') {
      const resolvedTitle = resolveBackendMenuTitle(rawLabel, data?.component || '')
      return resolvedTitle ? formatMenuTitle(resolvedTitle) : ''
    }
    return rawLabel
  }
  const handleClosed = () => {
    activeScopeType.value = props.scopeType || 'menu'
    Object.keys(scopeState).forEach((key) => {
      scopeState[key].loading = false
      scopeState[key].loaded = false
      scopeState[key].treeData = []
      scopeState[key].checkedKeys = []
      scopeState[key].halfCheckedKeys = []
      scopeState[key].condition = ''
      resetScopeTabState(scopeState[key])
    })
  }
@@ -258,7 +257,7 @@
    const keys = []
    const traverse = (nodeList) => {
      nodeList.forEach((node) => {
        if (node.id !== void 0 && node.id !== null && node.id !== '') {
        if (hasNodeKey(node.id)) {
          keys.push(String(node.id))
        }
        if (node.children?.length) {