From f4f726804555d326d6d7431256275dcca3836448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 12:24:05 +0800 Subject: [PATCH] =?UTF-8?q?task-12(=E5=A3=B3=E5=B1=82/=E8=B7=AF=E7=94=B1):?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0=E8=B7=AF=E7=94=B1=E7=BA=A7=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E9=A1=B5=E9=9D=A2(404=20=E5=85=9C=E5=BA=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 src/pages/error/NotFoundPage.vue(el-result + 返回首页),router 在业务路由 之后注册 :pathMatch(.*)* 兜底(无 menuKey,不参与权限门禁),保留壳层 chrome; main.css 增加 .not-found 布局。 --- .../src/pages/error/NotFoundPage.vue | 15 ++++ admin-frontend-vue/src/router/index.ts | 12 ++-- admin-frontend-vue/src/styles/main.css | 1 + admin-frontend-vue/tests/task-12.test.ts | 71 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 admin-frontend-vue/src/pages/error/NotFoundPage.vue create mode 100644 admin-frontend-vue/tests/task-12.test.ts diff --git a/admin-frontend-vue/src/pages/error/NotFoundPage.vue b/admin-frontend-vue/src/pages/error/NotFoundPage.vue new file mode 100644 index 00000000..4ff85fd8 --- /dev/null +++ b/admin-frontend-vue/src/pages/error/NotFoundPage.vue @@ -0,0 +1,15 @@ + + + diff --git a/admin-frontend-vue/src/router/index.ts b/admin-frontend-vue/src/router/index.ts index aee595b2..717b110e 100644 --- a/admin-frontend-vue/src/router/index.ts +++ b/admin-frontend-vue/src/router/index.ts @@ -1,6 +1,7 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' import type { AdminMenuNode } from '@/types/admin' import AdminLayout from '@/layout/AdminLayout.vue' +import NotFoundPage from '@/pages/error/NotFoundPage.vue' import { useAdminSessionStore } from '@/stores/admin-session' import { APP_BASE_PATH } from '@/config/app' import { adminRouteRecords } from './routes' @@ -12,10 +13,13 @@ const router = createRouter({ { path: '/', component: AdminLayout, - children: adminRouteRecords.map((record) => ({ - ...record, - component: defineLazyPage(record.component as () => Promise<{ default: unknown }>), - })) as RouteRecordRaw[], + children: [ + ...adminRouteRecords.map((record) => ({ + ...record, + component: defineLazyPage(record.component as () => Promise<{ default: unknown }>), + })), + { path: ':pathMatch(.*)*', component: NotFoundPage, meta: { title: '页面不存在' } }, + ] as RouteRecordRaw[], }, ], }) diff --git a/admin-frontend-vue/src/styles/main.css b/admin-frontend-vue/src/styles/main.css index 8d737580..61090caa 100644 --- a/admin-frontend-vue/src/styles/main.css +++ b/admin-frontend-vue/src/styles/main.css @@ -50,6 +50,7 @@ button, input, textarea, select { font: inherit; } .page-loading, .page-error { padding: 48px 20px; text-align: center; } .page-loading { color: var(--admin-muted); } .page-error { color: #b91c1c; } +.not-found { padding: 56px 20px; } @media (max-width: 820px) { .admin-sidebar { flex-basis: 64px; } diff --git a/admin-frontend-vue/tests/task-12.test.ts b/admin-frontend-vue/tests/task-12.test.ts new file mode 100644 index 00000000..e72daa8e --- /dev/null +++ b/admin-frontend-vue/tests/task-12.test.ts @@ -0,0 +1,71 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { existsSync } from 'node:fs' +import { join } from 'node:path' +import { readSource, occurrences } from './helpers.ts' +import { adminPages } from '../src/router/routes.ts' + +const ROUTER = 'src/router/index.ts' +const NOT_FOUND = 'src/pages/error/NotFoundPage.vue' + +test('test_task_012_route_error_page_normal_primary_path', () => { + // 正常主路径:未知路径注册到路由级错误页兜底。 + const router = readSource(ROUTER) + assert.match(router, /path:\s*':pathMatch\(\.\*\)\*'/) + assert.match(router, /NotFoundPage/) + assert.match(router, /页面不存在/) +}) + +test('test_task_012_route_error_page_normal_variant_input', () => { + // 正常变体:错误页在 AdminLayout children 内,保留壳层 chrome。 + const router = readSource(ROUTER) + const catchAllIndex = router.indexOf(':pathMatch(.*)*') + const registryIndex = router.indexOf('adminRouteRecords.map') + assert.ok(catchAllIndex > registryIndex, 'catch-all 应在业务路由之后注册') + assert.match(router, /path: '\/',/) +}) + +test('test_task_012_route_error_page_normal_repeated_operation_is_idempotent', () => { + // 正常重复:错误页注册不重复追加。 + const first = readSource(ROUTER) + assert.equal(occurrences(first, ':pathMatch(.*)*'), 1) +}) + +test('test_task_012_route_error_page_boundary_empty_input', () => { + // 边界空值:错误页文件存在,且不进入业务路由注册表。 + assert.equal(existsSync(join(process.cwd(), NOT_FOUND)), true) + assert.equal(adminPages.length, 3, '错误页不应计入业务路由') +}) + +test('test_task_012_route_error_page_boundary_single_item', () => { + // 边界单元素:错误页是单一 Vue 页面组件。 + const source = readSource(NOT_FOUND) + assert.equal(occurrences(source, '