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/menu/index.vue | 335 ++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 233 insertions(+), 102 deletions(-)
diff --git a/rsf-design/src/views/system/menu/index.vue b/rsf-design/src/views/system/menu/index.vue
index 3a79c27..2bb409c 100644
--- a/rsf-design/src/views/system/menu/index.vue
+++ b/rsf-design/src/views/system/menu/index.vue
@@ -1,7 +1,6 @@
<!-- 鑿滃崟绠$悊椤甸潰 -->
<template>
<div class="menu-page art-full-height">
- <!-- 鎼滅储鏍� -->
<ArtSearchBar
v-model="formFilters"
:items="formItems"
@@ -11,7 +10,6 @@
/>
<ElCard class="art-table-card">
- <!-- 琛ㄦ牸澶撮儴 -->
<ArtTableHeader
:showZebra="false"
:loading="loading"
@@ -19,7 +17,7 @@
@refresh="handleRefresh"
>
<template #left>
- <ElButton v-auth="'add'" @click="handleAddMenu" v-ripple> 娣诲姞鑿滃崟 </ElButton>
+ <ElButton v-auth="'add'" @click="handleAddMenu" v-ripple>娣诲姞鑿滃崟</ElButton>
<ElButton @click="toggleExpand" v-ripple>
{{ isExpanded ? '鏀惰捣' : '灞曞紑' }}
</ElButton>
@@ -28,7 +26,7 @@
<ArtTable
ref="tableRef"
- rowKey="path"
+ rowKey="id"
:loading="loading"
:columns="columns"
:data="filteredTableData"
@@ -37,12 +35,12 @@
:default-expand-all="false"
/>
- <!-- 鑿滃崟寮圭獥 -->
<MenuDialog
v-model:visible="dialogVisible"
:type="dialogType"
:editData="editData"
:lockType="lockMenuType"
+ :menuTreeOptions="menuTreeOptions"
@submit="handleSubmit"
/>
</ElCard>
@@ -56,22 +54,34 @@
import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
import { useTableColumns } from '@/hooks/core/useTableColumns'
- import { fetchGetMenuList } from '@/api/system-manage'
- import { ElTag, ElMessageBox } from 'element-plus'
+ import {
+ fetchDeleteMenu,
+ fetchGetMenuList,
+ fetchSaveMenu,
+ fetchUpdateMenu
+ } from '@/api/system-manage'
+ import { ElTag, ElMessage, ElMessageBox } from 'element-plus'
+
defineOptions({ name: 'Menus' })
+
const loading = ref(false)
const isExpanded = ref(false)
const tableRef = ref()
const dialogVisible = ref(false)
const dialogType = ref('menu')
const editData = ref(null)
- const lockMenuType = ref(false)
+ const lockMenuType = ref(true)
+ const tableData = ref([])
+ const menuTreeOptions = ref([])
+
const initialSearchState = {
name: '',
route: ''
}
+
const formFilters = reactive({ ...initialSearchState })
const appliedFilters = reactive({ ...initialSearchState })
+
const formItems = computed(() => [
{
label: '鑿滃崟鍚嶇О',
@@ -86,44 +96,88 @@
props: { clearable: true }
}
])
- onMounted(() => {
- getMenuList()
- })
- const getMenuList = async () => {
+
+ const normalizeNumber = (value, fallback = 0) => {
+ if (value === '' || value === null || value === undefined) {
+ return fallback
+ }
+ const normalized = Number(value)
+ return Number.isNaN(normalized) ? fallback : normalized
+ }
+
+ const normalizeMenuTreeOptions = (nodes = []) => {
+ if (!Array.isArray(nodes)) {
+ return []
+ }
+
+ return nodes
+ .map((node) => ({
+ label: formatMenuTitle(node.meta?.title || node.name || ''),
+ value: normalizeNumber(node.id, 0),
+ children: normalizeMenuTreeOptions(node.children)
+ }))
+ }
+
+ const loadMenuResources = async () => {
loading.value = true
try {
- const list = await fetchGetMenuList()
- tableData.value = list
+ const list = await fetchGetMenuList({})
+ tableData.value = Array.isArray(list) ? list : []
+ menuTreeOptions.value = [
+ {
+ label: '椤剁骇鑿滃崟',
+ value: 0,
+ children: normalizeMenuTreeOptions(tableData.value)
+ }
+ ]
} catch (error) {
- throw error instanceof Error ? error : new Error('鑾峰彇鑿滃崟澶辫触')
+ ElMessage.error(error?.message || '鑾峰彇鑿滃崟澶辫触')
} finally {
loading.value = false
}
}
+
+ onMounted(() => {
+ loadMenuResources()
+ })
+
+ const hasNestedMenus = (row) => Array.isArray(row.children) && row.children.some((child) => !child.meta?.isAuthButton)
+
const getMenuTypeTag = (row) => {
- if (row.meta?.isAuthButton) return 'danger'
- if (row.children?.length) return 'info'
- if (row.meta?.link && row.meta?.isIframe) return 'success'
- if (row.path) return 'primary'
- if (row.meta?.link) return 'warning'
- return 'info'
+ if (row.meta?.isAuthButton || Number(row.type) === 1) return 'danger'
+ if (hasNestedMenus(row)) return 'info'
+ return 'primary'
}
+
const getMenuTypeText = (row) => {
- if (row.meta?.isAuthButton) return '鎸夐挳'
- if (row.children?.length) return '鐩綍'
- if (row.meta?.link && row.meta?.isIframe) return '鍐呭祵'
- if (row.path) return '鑿滃崟'
- if (row.meta?.link) return '澶栭摼'
- return '鏈煡'
+ if (row.meta?.isAuthButton || Number(row.type) === 1) return '鎸夐挳'
+ if (hasNestedMenus(row)) return '鐩綍'
+ return '鑿滃崟'
}
+
+ const getStatusMeta = (status) => {
+ return normalizeNumber(status, 1) === 1
+ ? { text: '鍚敤', type: 'success' }
+ : { text: '绂佺敤', type: 'danger' }
+ }
+
const getMenuDisplayTitle = (row) => {
const titleKey = row.meta?.title || row.name || ''
const normalizedTitleKey =
titleKey && !String(titleKey).includes('.') ? `menu.${titleKey}` : titleKey
+
return formatMenuTitle(normalizedTitleKey)
}
+
const getMenuDisplayIcon = (row) => row.meta?.icon || row.icon || ''
+
const { columnChecks, columns } = useTableColumns(() => [
+ {
+ prop: 'meta.title',
+ label: '鑿滃崟鍚嶇О',
+ minWidth: 180,
+ formatter: (row) => getMenuDisplayTitle(row)
+ },
{
prop: 'meta.icon',
label: '鍥炬爣棰勮',
@@ -134,52 +188,64 @@
if (!icon) return h('span', { class: 'text-g-400' }, '-')
- return h('div', { class: 'flex items-center justify-center' }, [
- h(ArtSvgIcon, { icon, class: 'text-base text-g-700' })
- ])
+ return h(
+ 'div',
+ {
+ class:
+ 'mx-auto flex h-8 w-8 items-center justify-center rounded-md border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)]'
+ },
+ [h(ArtSvgIcon, { icon, class: 'text-base text-g-700' })]
+ )
}
- },
- {
- prop: 'meta.title',
- label: '鑿滃崟鍚嶇О',
- minWidth: 120,
- formatter: (row) => getMenuDisplayTitle(row)
},
{
prop: 'type',
label: '鑿滃崟绫诲瀷',
- formatter: (row) => {
- return h(ElTag, { type: getMenuTypeTag(row) }, () => getMenuTypeText(row))
- }
+ width: 110,
+ formatter: (row) =>
+ h(ElTag, { type: getMenuTypeTag(row), effect: 'light' }, () => getMenuTypeText(row))
},
{
- prop: 'path',
+ prop: 'route',
label: '璺敱',
+ minWidth: 180,
formatter: (row) => {
if (row.meta?.isAuthButton) return ''
- return row.meta?.link || row.path || ''
+ return row.route || ''
}
},
{
- prop: 'meta.authList',
+ prop: 'authority',
label: '鏉冮檺鏍囪瘑',
+ minWidth: 180,
formatter: (row) => {
if (row.meta?.isAuthButton) {
- return row.meta?.authMark || ''
+ return row.authority || row.meta?.authMark || ''
}
- if (!row.meta?.authList?.length) return ''
+ if (!row.meta?.authList?.length) return row.authority || ''
return `${row.meta.authList.length} 涓潈闄愭爣璇哷
}
},
{
- prop: 'date',
- label: '缂栬緫鏃堕棿',
- formatter: () => '2022-3-12 12:00:00'
+ prop: 'sort',
+ label: '鎺掑簭',
+ width: 90
},
{
prop: 'status',
label: '鐘舵��',
- formatter: () => h(ElTag, { type: 'success' }, () => '鍚敤')
+ width: 100,
+ formatter: (row) => {
+ const statusMeta = getStatusMeta(row.status)
+ return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+ }
+ },
+ {
+ prop: 'memo',
+ label: '澶囨敞',
+ minWidth: 180,
+ showOverflowTooltip: true,
+ formatter: (row) => row.memo || '-'
},
{
prop: 'operation',
@@ -187,7 +253,7 @@
width: 180,
align: 'right',
formatter: (row) => {
- const buttonStyle = { style: 'text-align: right' }
+ const buttonStyle = { class: 'flex justify-end' }
if (row.meta?.isAuthButton) {
return h('div', buttonStyle, [
h(ArtButtonTable, {
@@ -196,14 +262,14 @@
}),
h(ArtButtonTable, {
type: 'delete',
- onClick: () => handleDeleteAuth()
+ onClick: () => handleDeleteAuth(row)
})
])
}
return h('div', buttonStyle, [
h(ArtButtonTable, {
type: 'add',
- onClick: () => handleAddAuth(),
+ onClick: () => handleAddAuth(row),
title: '鏂板鏉冮檺'
}),
h(ArtButtonTable, {
@@ -212,25 +278,13 @@
}),
h(ArtButtonTable, {
type: 'delete',
- onClick: () => handleDeleteMenu()
+ onClick: () => handleDeleteMenu(row)
})
])
}
- }
+ },
])
- const tableData = ref([])
- const handleReset = () => {
- Object.assign(formFilters, { ...initialSearchState })
- Object.assign(appliedFilters, { ...initialSearchState })
- getMenuList()
- }
- const handleSearch = () => {
- Object.assign(appliedFilters, { ...formFilters })
- getMenuList()
- }
- const handleRefresh = () => {
- getMenuList()
- }
+
const deepClone = (obj) => {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
@@ -243,6 +297,7 @@
}
return cloned
}
+
const convertAuthListToChildren = (items) => {
return items.map((item) => {
const clonedItem = deepClone(item)
@@ -251,13 +306,17 @@
}
if (item.meta?.authList?.length) {
const authChildren = item.meta.authList.map((auth) => ({
- path: `${item.path}_auth_${auth.authMark}`,
- name: `${String(item.name)}_auth_${auth.authMark}`,
+ ...deepClone(auth),
+ route: auth.route || '',
+ component: auth.component || '',
meta: {
title: auth.title,
authMark: auth.authMark,
isAuthButton: true,
- parentPath: item.path
+ parentPath: item.path,
+ icon: auth.icon,
+ sort: auth.sort,
+ isEnable: normalizeNumber(auth.status, 1) === 1
}
}))
clonedItem.children = clonedItem.children?.length
@@ -267,15 +326,17 @@
return clonedItem
})
}
+
const searchMenu = (items) => {
- const results = []
+ const results = []
for (const item of items) {
const searchName = appliedFilters.name?.toLowerCase().trim() || ''
const searchRoute = appliedFilters.route?.toLowerCase().trim() || ''
const menuTitle = getMenuDisplayTitle(item).toLowerCase()
- const menuPath = (item.path || '').toLowerCase()
+ const menuRoute = String(item.route || item.path || item.authority || '').toLowerCase()
const nameMatch = !searchName || menuTitle.includes(searchName)
- const routeMatch = !searchRoute || menuPath.includes(searchRoute)
+ const routeMatch = !searchRoute || menuRoute.includes(searchRoute)
+
if (item.children?.length) {
const matchedChildren = searchMenu(item.children)
if (matchedChildren.length > 0) {
@@ -285,77 +346,147 @@
continue
}
}
+
if (nameMatch && routeMatch) {
results.push(deepClone(item))
}
}
return results
}
+
const filteredTableData = computed(() => {
const searchedData = searchMenu(tableData.value)
return convertAuthListToChildren(searchedData)
})
+
+ const closeDialog = () => {
+ dialogVisible.value = false
+ editData.value = null
+ }
+
const handleAddMenu = () => {
dialogType.value = 'menu'
editData.value = null
lockMenuType.value = true
dialogVisible.value = true
}
- const handleAddAuth = () => {
- dialogType.value = 'menu'
- editData.value = null
- lockMenuType.value = false
+
+ const handleAddAuth = (row) => {
+ dialogType.value = 'button'
+ editData.value = {
+ parentId: row.id,
+ type: 1,
+ status: 1,
+ sort: 0
+ }
+ lockMenuType.value = true
dialogVisible.value = true
}
+
const handleEditMenu = (row) => {
dialogType.value = 'menu'
editData.value = row
lockMenuType.value = true
dialogVisible.value = true
}
+
const handleEditAuth = (row) => {
dialogType.value = 'button'
- editData.value = {
- title: row.meta?.title,
- authMark: row.meta?.authMark
- }
- lockMenuType.value = false
+ editData.value = row
+ lockMenuType.value = true
dialogVisible.value = true
}
- const handleSubmit = (formData) => {
- console.log('鎻愪氦鏁版嵁:', formData)
- getMenuList()
+
+ const buildMenuSubmitPayload = (formData) => {
+ return {
+ ...(formData.id ? { id: normalizeNumber(formData.id, 0) } : {}),
+ parentId: normalizeNumber(formData.parentId, 0),
+ name: String(formData.name || '').trim(),
+ route: String(formData.route || '').trim(),
+ component: String(formData.component || '').trim(),
+ authority: String(formData.authority || '').trim(),
+ icon: String(formData.icon || '').trim(),
+ sort: normalizeNumber(formData.sort, 0),
+ status: normalizeNumber(formData.status, 1),
+ memo: String(formData.memo || '').trim(),
+ type: formData.menuType === 'button' ? 1 : 0
+ }
}
- const handleDeleteMenu = async () => {
+
+ const handleSubmit = async (formData) => {
+ const payload = buildMenuSubmitPayload(formData)
+ if (payload.id && payload.id === payload.parentId) {
+ ElMessage.error('涓婄骇鑿滃崟涓嶈兘閫夋嫨褰撳墠鑿滃崟')
+ return
+ }
+
try {
- await ElMessageBox.confirm('纭畾瑕佸垹闄よ鑿滃崟鍚楋紵鍒犻櫎鍚庢棤娉曟仮澶�', '鎻愮ず', {
+ if (payload.id) {
+ await fetchUpdateMenu(payload)
+ ElMessage.success('淇敼鎴愬姛')
+ } else {
+ await fetchSaveMenu(payload)
+ ElMessage.success('鏂板鎴愬姛')
+ }
+ closeDialog()
+ await loadMenuResources()
+ } catch (error) {
+ ElMessage.error(error?.message || '鎻愪氦澶辫触')
+ }
+ }
+
+ const handleDeleteMenu = async (row) => {
+ try {
+ await ElMessageBox.confirm(
+ `纭畾瑕佸垹闄よ彍鍗曘��${formatMenuTitle(row.meta?.title || row.name || '')}銆嶅悧锛熷垹闄ゅ悗鏃犳硶鎭㈠`,
+ '鍒犻櫎纭',
+ {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ )
+ await fetchDeleteMenu(row.id)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ await loadMenuResources()
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+ }
+ }
+ }
+
+ const handleDeleteAuth = async (row) => {
+ try {
+ await ElMessageBox.confirm(`纭畾瑕佸垹闄ゆ潈闄愩��${row.name || row.authority || row.id}銆嶅悧锛熷垹闄ゅ悗鏃犳硶鎭㈠`, '鍒犻櫎纭', {
confirmButtonText: '纭畾',
cancelButtonText: '鍙栨秷',
type: 'warning'
})
+ await fetchDeleteMenu(row.id)
ElMessage.success('鍒犻櫎鎴愬姛')
- getMenuList()
+ await loadMenuResources()
} catch (error) {
if (error !== 'cancel') {
- ElMessage.error('鍒犻櫎澶辫触')
+ ElMessage.error(error?.message || '鍒犻櫎澶辫触')
}
}
}
- const handleDeleteAuth = async () => {
- try {
- await ElMessageBox.confirm('纭畾瑕佸垹闄よ鏉冮檺鍚楋紵鍒犻櫎鍚庢棤娉曟仮澶�', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- })
- ElMessage.success('鍒犻櫎鎴愬姛')
- getMenuList()
- } catch (error) {
- if (error !== 'cancel') {
- ElMessage.error('鍒犻櫎澶辫触')
- }
- }
+
+ const handleReset = () => {
+ Object.assign(formFilters, { ...initialSearchState })
+ Object.assign(appliedFilters, { ...initialSearchState })
+ loadMenuResources()
}
+
+ const handleSearch = () => {
+ Object.assign(appliedFilters, { ...formFilters })
+ }
+
+ const handleRefresh = () => {
+ loadMenuResources()
+ }
+
const toggleExpand = () => {
isExpanded.value = !isExpanded.value
nextTick(() => {
--
Gitblit v1.9.1