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