docs(design): 删除磁盘监控设计文档并更新前端页面结构规范
- 删除 frontend/src/views/systemMonitor/2026-04-22-disk-monitor-design.md 设计文档 - 删除 frontend/src/views/tools/addLedger/API_DEBUG.md 调试文档 - 在 AGENTS.md 中新增前端页面结构归档章节,规范复杂工具页结构 - 明确 index.vue、components/、utils/ 职责边界和拆分原则 - 规定页面级类型和 contract 脚本管理方式 - 统一复杂页面拆分优先顺序和注意事项
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..')
|
||||
|
||||
const read = path => readFileSync(resolve(root, path), 'utf8')
|
||||
|
||||
const staticRouterSource = read('src/routers/modules/staticRouter.ts')
|
||||
const mainSource = read('src/layouts/components/Main/index.vue')
|
||||
const tabsSource = read('src/stores/modules/tabs.ts')
|
||||
|
||||
const routeContracts = [
|
||||
['tools', 'src/views/tools/index.vue'],
|
||||
['toolWaveform', 'src/views/tools/waveform/index.vue'],
|
||||
['toolMmsMapping', 'src/views/tools/mmsMapping/index.vue'],
|
||||
['toolAddData', 'src/views/tools/addData/index.vue'],
|
||||
['toolAddLedger', 'src/views/tools/addLedger/index.vue'],
|
||||
['eventList', 'src/views/event/eventList/index.vue'],
|
||||
['systemMonitor', 'src/views/systemMonitor/index.vue'],
|
||||
['diskMonitor', 'src/views/systemMonitor/diskMonitor/index.vue']
|
||||
]
|
||||
|
||||
const extractRouteBlock = routeName => {
|
||||
const routeNameIndex = staticRouterSource.indexOf(`name: '${routeName}'`)
|
||||
if (routeNameIndex === -1) {
|
||||
throw new Error(`Route ${routeName} was not found in staticRouter.ts`)
|
||||
}
|
||||
|
||||
const nextRouteIndex = staticRouterSource.indexOf('\n {', routeNameIndex + 1)
|
||||
return staticRouterSource.slice(routeNameIndex, nextRouteIndex === -1 ? undefined : nextRouteIndex)
|
||||
}
|
||||
|
||||
const extractComponentName = viewPath => {
|
||||
const viewSource = read(viewPath)
|
||||
const componentName = viewSource.match(/defineOptions\(\s*\{\s*name:\s*'([^']+)'/s)?.[1]
|
||||
|
||||
if (!componentName) {
|
||||
throw new Error(`${viewPath} must define an explicit component name for keep-alive`)
|
||||
}
|
||||
|
||||
return componentName
|
||||
}
|
||||
|
||||
const errors = []
|
||||
|
||||
for (const [routeName, viewPath] of routeContracts) {
|
||||
const routeBlock = extractRouteBlock(routeName)
|
||||
const componentName = extractComponentName(viewPath)
|
||||
const cacheName = routeBlock.match(/cacheName:\s*'([^']+)'/)?.[1]
|
||||
|
||||
if (cacheName !== componentName) {
|
||||
errors.push(`${routeName} meta.cacheName should be ${componentName}, got ${cacheName || '<missing>'}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!mainSource.includes('v-if="isRouterShow"')) {
|
||||
errors.push('Main router component must use isRouterShow so tab refresh can rebuild the current page')
|
||||
}
|
||||
|
||||
if (!mainSource.includes('keepAliveName')) {
|
||||
errors.push('Main keep-alive include list must come from keepAliveName')
|
||||
}
|
||||
|
||||
if (!tabsSource.includes('cacheName')) {
|
||||
errors.push('Tabs store must keep actual component cacheName values in keepAliveName')
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.error(errors.join('\n'))
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
<router-view v-slot="{ Component, route }" style="height: 100%">
|
||||
<!-- {{ keepAliveName}} -->
|
||||
<!-- <transition name="slide-right" mode="out-in"> -->
|
||||
<keep-alive :include="tabsMenuList">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
<keep-alive :include="keepAliveName">
|
||||
<component v-if="isRouterShow" :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<!-- </transition> -->
|
||||
</router-view>
|
||||
@@ -26,13 +26,14 @@ import Maximize from './components/Maximize.vue'
|
||||
import Tabs from '@/layouts/components/Tabs/index.vue'
|
||||
import Footer from '@/layouts/components/Footer/index.vue'
|
||||
import { useAuthStore } from '@/stores/modules/auth'
|
||||
import { useTabsStore } from '@/stores/modules/tabs'
|
||||
|
||||
const tabStore = useTabsStore()
|
||||
defineOptions({
|
||||
name: 'LayoutMain'
|
||||
})
|
||||
|
||||
const globalStore = useGlobalStore()
|
||||
const tabsMenuList = computed(() => tabStore.tabsMenuList.map(item => item.name))
|
||||
const authStore = useAuthStore()
|
||||
const { maximize, isCollapse, layout, tabs, footer } = storeToRefs(globalStore)
|
||||
const { maximize, isCollapse, layout, tabs } = storeToRefs(globalStore)
|
||||
const keepAliveStore = useKeepAliveStore()
|
||||
const { keepAliveName } = storeToRefs(keepAliveStore)
|
||||
//是否显示导航栏
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { resolveBusinessMenuPath } from '@/stores/modules/auth'
|
||||
|
||||
defineProps<{ menuList: Menu.MenuOptions[] }>()
|
||||
const router = useRouter()
|
||||
@@ -29,7 +30,7 @@ const handleClickMenu = async (subItem: Menu.MenuOptions) => {
|
||||
window.open(subItem.meta.isLink, '_blank')
|
||||
return
|
||||
}
|
||||
await router.push(subItem.path)
|
||||
await router.push(resolveBusinessMenuPath(subItem))
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@@ -63,14 +63,22 @@ const currentTabRoute = computed(() => {
|
||||
return router.getRoutes().find(item => item.path === currentTabPath.value)
|
||||
})
|
||||
|
||||
const currentCacheName = computed(() => {
|
||||
return (
|
||||
(route.meta.cacheName as string | undefined) ||
|
||||
(currentTabRoute.value?.meta.cacheName as string | undefined) ||
|
||||
(route.name as string)
|
||||
)
|
||||
})
|
||||
|
||||
// refresh current page
|
||||
const refreshCurrentPage: Function = inject('refresh') as Function
|
||||
const refresh = () => {
|
||||
setTimeout(() => {
|
||||
keepAliveStore.removeKeepAliveName(route.name as string)
|
||||
keepAliveStore.removeKeepAliveName(currentCacheName.value)
|
||||
refreshCurrentPage(false)
|
||||
nextTick(() => {
|
||||
keepAliveStore.addKeepAliveName(route.name as string)
|
||||
keepAliveStore.addKeepAliveName(currentCacheName.value)
|
||||
refreshCurrentPage(true)
|
||||
})
|
||||
}, 0)
|
||||
|
||||
@@ -57,7 +57,7 @@ onMounted(() => {
|
||||
|
||||
const ensureParentTab = () => {
|
||||
const parentPath = resolveCurrentTabPath()
|
||||
if (!parentPath || tabStore.tabsMenuList.some(item => item.path === parentPath)) return
|
||||
if (!parentPath) return
|
||||
|
||||
const parentRoute = router.getRoutes().find(item => item.path === parentPath && item.name)
|
||||
if (!parentRoute) return
|
||||
@@ -69,7 +69,8 @@ const ensureParentTab = () => {
|
||||
path: parentRoute.path,
|
||||
name: parentRoute.name as string,
|
||||
close: !parentRoute.meta.isAffix,
|
||||
isKeepAlive: parentRoute.meta.isKeepAlive as boolean
|
||||
isKeepAlive: (route.meta.isKeepAlive ?? parentRoute.meta.isKeepAlive) as boolean,
|
||||
cacheName: (route.meta.cacheName || parentRoute.meta.cacheName) as string | undefined
|
||||
})
|
||||
}
|
||||
|
||||
@@ -87,6 +88,7 @@ watch(
|
||||
title: route.meta.title as string,
|
||||
path: route.fullPath,
|
||||
name: route.name as string,
|
||||
cacheName: route.meta.cacheName as string | undefined,
|
||||
close: !route.meta.isAffix,
|
||||
isKeepAlive: route.meta.isKeepAlive as boolean
|
||||
}
|
||||
@@ -113,6 +115,7 @@ const initTabs = () => {
|
||||
title: item.meta.title as string,
|
||||
path: item.path,
|
||||
name: item.name as string,
|
||||
cacheName: item.meta.cacheName as string | undefined,
|
||||
close: !item.meta.isAffix,
|
||||
isKeepAlive: item.meta.isKeepAlive as boolean,
|
||||
unshift: true
|
||||
|
||||
Reference in New Issue
Block a user