<template>
|
<ElDrawer
|
:model-value="visible"
|
title="角色权限"
|
size="860px"
|
destroy-on-close
|
@update:model-value="handleVisibleChange"
|
@closed="handleClosed"
|
>
|
<div class="mb-4 text-sm text-[var(--art-text-secondary)]">
|
当前角色:{{ roleLabel }}
|
</div>
|
|
<ElTabs v-model="activeScopeType" class="role-scope-tabs">
|
<ElTabPane
|
v-for="config in scopeConfigs"
|
:key="config.scopeType"
|
:label="config.title"
|
:name="config.scopeType"
|
>
|
<div v-if="scopeState[config.scopeType].loading" class="py-6">
|
<ElSkeleton :rows="10" animated />
|
</div>
|
<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>
|
</ElSpace>
|
<ElButton type="primary" @click="handleSave(config.scopeType)">保存当前权限</ElButton>
|
</div>
|
|
<div class="flex items-center gap-3">
|
<ElInput
|
v-model.trim="scopeState[config.scopeType].condition"
|
clearable
|
placeholder="搜索权限树"
|
@clear="handleSearch(config.scopeType)"
|
@keyup.enter="handleSearch(config.scopeType)"
|
/>
|
<ElButton @click="handleSearch(config.scopeType)">搜索</ElButton>
|
</div>
|
|
<ElScrollbar height="56vh">
|
<ElTree
|
:ref="(el) => setTreeRef(config.scopeType, el)"
|
:data="scopeState[config.scopeType].treeData"
|
node-key="id"
|
show-checkbox
|
:default-expand-all="true"
|
:default-checked-keys="scopeState[config.scopeType].checkedKeys"
|
:props="treeProps"
|
@check="handleTreeCheck(config.scopeType)"
|
>
|
<template #default="{ data }">
|
<div class="flex items-center gap-2">
|
<span>{{ resolveScopeNodeLabel(data) }}</span>
|
<ElTag v-if="data.isAuthButton" type="info" effect="plain" size="small">
|
按钮
|
</ElTag>
|
</div>
|
</template>
|
</ElTree>
|
</ElScrollbar>
|
</div>
|
</ElTabPane>
|
</ElTabs>
|
</ElDrawer>
|
</template>
|
|
<script setup>
|
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 { ElMessage } from 'element-plus'
|
|
const props = defineProps({
|
visible: { required: false, default: false },
|
roleData: { required: false, default: () => ({}) },
|
scopeType: { required: false, default: 'menu' }
|
})
|
|
const emit = defineEmits(['update:visible', 'success'])
|
|
const scopeConfigs = ['menu', 'pda', 'matnr', 'warehouse'].map((scopeType) => getRoleScopeConfig(scopeType))
|
const activeScopeType = ref(props.scopeType || 'menu')
|
const treeRefs = reactive({})
|
const treeProps = {
|
label: 'label',
|
children: 'children'
|
}
|
const scopeState = reactive(
|
Object.fromEntries(
|
scopeConfigs.map((config) => [
|
config.scopeType,
|
{
|
loading: false,
|
loaded: false,
|
treeData: [],
|
checkedKeys: [],
|
halfCheckedKeys: [],
|
condition: ''
|
}
|
])
|
)
|
)
|
|
const visible = computed({
|
get: () => props.visible,
|
set: (value) => emit('update:visible', value)
|
})
|
|
const roleLabel = computed(() => props.roleData?.name || props.roleData?.code || '未选择角色')
|
|
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 [checkedIds, treeData] = reloadSelection ? await Promise.all(requests) : [state.checkedKeys, await requests[0]]
|
state.treeData = normalizeRoleScopeTreeData(config.scopeType, treeData)
|
state.checkedKeys = normalizeScopeKeys(checkedIds)
|
state.halfCheckedKeys = []
|
state.loaded = true
|
} catch (error) {
|
ElMessage.error(error?.message || `加载${config.title}失败`)
|
} finally {
|
state.loading = false
|
nextTick(() => {
|
treeRefs[scopeType]?.setCheckedKeys(scopeState[scopeType].checkedKeys)
|
})
|
}
|
}
|
|
const ensureScopeLoaded = async (scopeType, options = {}) => {
|
if (!props.roleData?.id || !scopeType) {
|
return
|
}
|
|
const { force = false, reloadSelection = true } = options
|
if (!force && scopeState[scopeType].loaded) {
|
return
|
}
|
|
await loadScopeData(scopeType, { reloadSelection })
|
}
|
|
const setTreeRef = (scopeType, el) => {
|
if (el) {
|
treeRefs[scopeType] = el
|
}
|
}
|
|
const handleTreeCheck = (scopeType) => {
|
const tree = treeRefs[scopeType]
|
if (!tree) return
|
scopeState[scopeType].checkedKeys = normalizeScopeKeys(tree.getCheckedKeys())
|
scopeState[scopeType].halfCheckedKeys = normalizeScopeKeys(tree.getHalfCheckedKeys())
|
}
|
|
const handleSelectAll = (scopeType) => {
|
const tree = treeRefs[scopeType]
|
if (!tree) return
|
const allKeys = getAllNodeKeys(scopeState[scopeType].treeData)
|
tree.setCheckedKeys(allKeys)
|
handleTreeCheck(scopeType)
|
}
|
|
const handleClear = (scopeType) => {
|
const tree = treeRefs[scopeType]
|
if (!tree) return
|
tree.setCheckedKeys([])
|
handleTreeCheck(scopeType)
|
}
|
|
const handleSave = async (scopeType) => {
|
if (!props.roleData?.id) return
|
try {
|
await fetchUpdateRoleScope(
|
scopeType,
|
buildRoleScopeSubmitPayload(
|
props.roleData.id,
|
scopeState[scopeType].checkedKeys,
|
scopeState[scopeType].halfCheckedKeys
|
)
|
)
|
ElMessage.success('权限保存成功')
|
emit('success')
|
visible.value = false
|
} catch (error) {
|
ElMessage.error(error?.message || '权限保存失败')
|
}
|
}
|
|
const handleVisibleChange = (value) => {
|
visible.value = value
|
}
|
|
const handleSearch = async (scopeType) => {
|
await loadScopeData(scopeType, { reloadSelection: false })
|
}
|
|
const resolveScopeNodeLabel = (data) => {
|
const rawLabel = typeof data?.label === 'string' ? data.label.trim() : ''
|
if (!rawLabel) {
|
return ''
|
}
|
return resolveBackendMenuTitle(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 = ''
|
})
|
}
|
|
const getAllNodeKeys = (nodes) => {
|
const keys = []
|
const traverse = (nodeList) => {
|
nodeList.forEach((node) => {
|
if (node.id !== void 0 && node.id !== null && node.id !== '') {
|
keys.push(String(node.id))
|
}
|
if (node.children?.length) {
|
traverse(node.children)
|
}
|
})
|
}
|
traverse(Array.isArray(nodes) ? nodes : [])
|
return keys
|
}
|
|
watch(
|
() => props.visible,
|
async (isVisible) => {
|
if (isVisible) {
|
activeScopeType.value = props.scopeType || 'menu'
|
await ensureScopeLoaded(activeScopeType.value, { force: true })
|
}
|
},
|
{ immediate: true }
|
)
|
|
watch(
|
() => props.scopeType,
|
async (scopeType) => {
|
if (scopeType) {
|
activeScopeType.value = scopeType
|
if (props.visible) {
|
await ensureScopeLoaded(scopeType, { force: true })
|
}
|
}
|
}
|
)
|
|
watch(
|
activeScopeType,
|
async (scopeType) => {
|
if (props.visible && scopeType) {
|
await ensureScopeLoaded(scopeType)
|
}
|
}
|
)
|
</script>
|