From 40905cbd04c2e332cd4bc2b9e0c5b3e1da9cccfa Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期一, 30 三月 2026 08:17:32 +0800
Subject: [PATCH] feat: complete rsf-design phase 1 integration
---
rsf-design/src/views/system/role/modules/role-permission-dialog.vue | 413 ++++++++++++++++++++++++++++++++++++++++------------------
1 files changed, 282 insertions(+), 131 deletions(-)
diff --git a/rsf-design/src/views/system/role/modules/role-permission-dialog.vue b/rsf-design/src/views/system/role/modules/role-permission-dialog.vue
index 0940fab..552ba28 100644
--- a/rsf-design/src/views/system/role/modules/role-permission-dialog.vue
+++ b/rsf-design/src/views/system/role/modules/role-permission-dialog.vue
@@ -1,153 +1,304 @@
<template>
- <ElDialog
- v-model="visible"
- title="鑿滃崟鏉冮檺"
- width="520px"
- align-center
- class="el-dialog-border"
- @close="handleClose"
+ <ElDrawer
+ :model-value="visible"
+ title="瑙掕壊鏉冮檺"
+ size="860px"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ @closed="handleClosed"
>
- <ElScrollbar height="70vh">
- <ElTree
- ref="treeRef"
- :data="processedMenuList"
- show-checkbox
- node-key="name"
- :default-expand-all="isExpandAll"
- :default-checked-keys="[1, 2, 3]"
- :props="defaultProps"
- @check="handleTreeCheck"
- >
- <template #default="{ data }">
- <div style="display: flex; align-items: center">
- <span v-if="data.isAuth">
- {{ data.label }}
- </span>
- <span v-else>{{ defaultProps.label(data) }}</span>
- </div>
- </template>
- </ElTree>
- </ElScrollbar>
- <template #footer>
- <ElButton @click="outputSelectedData" style="margin-left: 8px">鑾峰彇閫変腑鏁版嵁</ElButton>
+ <div class="mb-4 text-sm text-[var(--art-text-secondary)]">
+ 褰撳墠瑙掕壊锛歿{ roleLabel }}
+ </div>
- <ElButton @click="toggleExpandAll">{{ isExpandAll ? '鍏ㄩ儴鏀惰捣' : '鍏ㄩ儴灞曞紑' }}</ElButton>
- <ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
- isSelectAll ? '鍙栨秷鍏ㄩ��' : '鍏ㄩ儴閫夋嫨'
- }}</ElButton>
- <ElButton type="primary" @click="savePermission">淇濆瓨</ElButton>
- </template>
- </ElDialog>
+ <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 { useMenuStore } from '@/store/modules/menu'
- import { formatMenuTitle } from '@/utils/router'
+ import {
+ buildRoleScopeSubmitPayload,
+ getRoleScopeConfig,
+ 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({
- modelValue: { required: false, default: false },
- roleData: { required: false, default: void 0 }
+ visible: { required: false, default: false },
+ roleData: { required: false, default: () => ({}) },
+ scopeType: { required: false, default: 'menu' }
})
- const emit = defineEmits(['update:modelValue', 'success'])
- const { menuList } = storeToRefs(useMenuStore())
- const treeRef = ref()
- const isExpandAll = ref(true)
- const isSelectAll = ref(false)
- const visible = computed({
- get: () => props.modelValue,
- set: (value) => emit('update:modelValue', value)
- })
- const processedMenuList = computed(() => {
- const processNode = (node) => {
- const processed = { ...node }
- if (node.meta?.authList?.length) {
- const authNodes = node.meta.authList.map((auth) => ({
- id: `${node.id}_${auth.authMark}`,
- name: `${node.name}_${auth.authMark}`,
- label: auth.title,
- authMark: auth.authMark,
- isAuth: true,
- checked: auth.checked || false
- }))
- processed.children = processed.children ? [...processed.children, ...authNodes] : authNodes
- }
- if (processed.children) {
- processed.children = processed.children.map(processNode)
- }
- return processed
- }
- return menuList.value.map(processNode)
- })
- const defaultProps = {
- children: 'children',
- label: (data) => formatMenuTitle(data.meta?.title) || data.label || ''
+
+ 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'
}
- watch(
- () => props.modelValue,
- (newVal) => {
- if (newVal && props.roleData) {
- console.log('璁剧疆鏉冮檺:', props.roleData)
- }
- }
+ const scopeState = reactive(
+ Object.fromEntries(
+ scopeConfigs.map((config) => [
+ config.scopeType,
+ {
+ loading: false,
+ loaded: false,
+ treeData: [],
+ checkedKeys: [],
+ halfCheckedKeys: [],
+ condition: ''
+ }
+ ])
+ )
)
- const handleClose = () => {
- visible.value = false
- treeRef.value?.setCheckedKeys([])
- }
- const savePermission = () => {
- ElMessage.success('鏉冮檺淇濆瓨鎴愬姛')
- emit('success')
- handleClose()
- }
- const toggleExpandAll = () => {
- const tree = treeRef.value
- if (!tree) return
- const nodes = tree.store.nodesMap
- Object.values(nodes).forEach((node) => {
- node.expanded = !isExpandAll.value
- })
- isExpandAll.value = !isExpandAll.value
- }
- const toggleSelectAll = () => {
- const tree = treeRef.value
- if (!tree) return
- if (!isSelectAll.value) {
- const allKeys = getAllNodeKeys(processedMenuList.value)
- tree.setCheckedKeys(allKeys)
- } else {
- tree.setCheckedKeys([])
+
+ 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)
+ })
}
- isSelectAll.value = !isSelectAll.value
}
+
+ 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 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) => {
+ 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.name) keys.push(node.name)
- if (node.children?.length) traverse(node.children)
+ if (node.id !== void 0 && node.id !== null && node.id !== '') {
+ keys.push(String(node.id))
+ }
+ if (node.children?.length) {
+ traverse(node.children)
+ }
})
}
- traverse(nodes)
+ traverse(Array.isArray(nodes) ? nodes : [])
return keys
}
- const handleTreeCheck = () => {
- const tree = treeRef.value
- if (!tree) return
- const checkedKeys = tree.getCheckedKeys()
- const allKeys = getAllNodeKeys(processedMenuList.value)
- isSelectAll.value = checkedKeys.length === allKeys.length && allKeys.length > 0
- }
- const outputSelectedData = () => {
- const tree = treeRef.value
- if (!tree) return
- const selectedData = {
- checkedKeys: tree.getCheckedKeys(),
- halfCheckedKeys: tree.getHalfCheckedKeys(),
- checkedNodes: tree.getCheckedNodes(),
- halfCheckedNodes: tree.getHalfCheckedNodes(),
- totalChecked: tree.getCheckedKeys().length,
- totalHalfChecked: tree.getHalfCheckedKeys().length
+
+ 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 })
+ }
+ }
}
- console.log('=== 閫変腑鐨勬潈闄愭暟鎹� ===', selectedData)
- ElMessage.success(`宸茶緭鍑洪�変腑鏁版嵁鍒版帶鍒跺彴锛屽叡閫変腑 ${selectedData.totalChecked} 涓妭鐐筦)
- }
+ )
+
+ watch(
+ activeScopeType,
+ async (scopeType) => {
+ if (props.visible && scopeType) {
+ await ensureScopeLoaded(scopeType)
+ }
+ }
+ )
</script>
--
Gitblit v1.9.1