Files
danyang-match/ui-tests/admin-reference-contracts.mjs
T
WorkBuddy f96dcb8aa6 fix(admin): 管理端入口收敛到 /admin/ 子路径,staging 端口 13521,修复生产 /admin/ 假成功
承接 rc.15 的管理端端口收敛(3000 → 13520),本轮把「入口」也收敛到一处:

- staging admin 发布端口 3001 → 13521(保持「生产 + 1」错开),同步
  tests/uat/run-uat.sh 的默认 UAT_ADMIN_URL。
- 管理端产物改为 /admin/ 子路径部署:vite 构建 base=/admin/(dev 仍为 /,开发与
  排查习惯不变)、router history base 跟随 import.meta.env.BASE_URL、三处容器化
  入口(生产 nginx / 本地整套栈 nginx / admin-web 容器内 nginx)统一为「不剥离
  前缀」转发,形态一致。
- 修复生产 nginx /admin/ 的假成功:原为 alias /usr/share/nginx/html/,而那正是
  nginx:alpine 纯净镜像自带的默认页目录(管理端产物不在该容器内),访问返回
  200 + nginx 欢迎页 —— 浏览器不报错、状态码正常,只是打开的不是管理端,比 404
  更难发现。改为 upstream admin_web + proxy_pass,并以真实两跳容器实测:
  /admin/ → 200 且为管理端标题与 /admin/assets/*、/admin/dashboard → 200(SPA
  兜底)、/admin/brand/logo-mark.png → 200、根路径 → 显式 404(用 location = /
  钉住,不依赖「本配置恰好没有 server 级 root」这一偶然事实)。
- 品牌资源引用统一走新增的 src/brand.ts(public/ 资源不受 vite base 影响,原 5 处
  写死根路径,子路径部署下会裂图);favicon 由仓库内并不存在的 /favicon.svg 改为
  存在的品牌标。
- 补全 tests/uat/run-uat.sh 被截断的尾部:原文件停在 `if [ -`,bash -n 不通过、
  脚本根本无法运行;顺带把此前「定义了却无人消费」的 ADMIN_URL 接到管理端可达性
  实测上,并补结果汇总与非零退出码。
- ui-tests:ui-document-baseline-contracts 新增两类一致性断言(staging 发布端口 ⇄
  run-uat.sh 默认入口、子路径部署的五处约束);admin-reference-contracts 的资产
  断言适配 brand 模块(断言「组件经 brand 模块引用」+「brand 模块指向正式资产」)。
  6 条新增断言全部经负向注入验证(注入对应缺陷 → 守卫变红且信息指向正确对象)。

验证:UI 门控 14/14 绿;vue-tsc EXIT=0;vite build EXIT=0 且产物资源均为
/admin/ 前缀;三份 nginx 配置 nginx -t 通过。

注:docs/STATUS.md 含此前的在途文档改动(非本次工作,且其中引用了已被删除的
plans 文件),故未纳入本提交;本轮的口径更新已写入该文件待一并处理。
2026-09-18 20:51:12 +08:00

74 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import { existsSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
const root = resolve(import.meta.dirname, '..')
const read = (path) => readFileSync(resolve(root, path), 'utf8')
const theme = read('admin-web/src/theme/index.css')
const layout = read('admin-web/src/layouts/MainLayout.vue')
const dashboard = read('admin-web/src/views/DashboardView.vue')
// 品牌资产的实际文件名集中在 src/brand.ts:public/ 下的资源不受 vite base 影响,
// 引用时必须显式拼 BASE_URL(否则在 /admin/ 子路径部署下 404)。因此下面断言
// 「组件经 brand 模块引用」+「brand 模块指向正式资产」两处,而不是在组件里找文件名字面量。
const brand = read('admin-web/src/brand.ts')
for (const token of [
'--admin-coral',
'--admin-shell-bg',
'--admin-sidebar-bg',
'--admin-card-radius',
'--admin-shell-radius',
'--admin-shadow',
]) {
assert.ok(theme.includes(token), `缺少管理端参考图令牌 ${token}`)
}
assert.ok(
existsSync(resolve(root, 'admin-web/public/brand/admin-danyang-backdrop.jpg')),
'缺少丹阳公益主题背景资产',
)
for (const className of ['admin-scene', 'admin-shell', 'admin-sidebar', 'admin-topbar', 'admin-content']) {
assert.ok(layout.includes(className), `缺少框架结构 ${className}`)
}
assert.match(brand, /admin-danyang-backdrop\.jpg/, '品牌模块未指向正式背景资产')
assert.match(layout, /BRAND_BACKDROP/, '管理端外层未使用正式背景资产')
assert.match(layout, /permissions/, '侧栏未保留权限控制')
for (const item of [
'工作台', '用户管理', '资料审核', '匹配管理', '公益红娘', '公益咨询', '活动管理',
'内容管理', '善缘服务站', '举报风控', '数据统计', '组织人员', '系统设置',
]) {
assert.ok(layout.includes(item), `侧栏缺少 ${item}`)
}
for (const className of [
'dashboard-metrics',
'metric-card',
'todo-panel',
'activity-panel',
'service-overview-panel',
'effectiveness-panel',
'dashboard-loading',
'dashboard-empty',
'dashboard-error',
'dashboard-forbidden',
]) {
assert.ok(dashboard.includes(className), `工作台缺少状态或区块 ${className}`)
}
for (const title of ['今日待办事项', '近期公益活动', '服务数据概览', '项目服务成效']) {
assert.ok(dashboard.includes(title), `工作台缺少 ${title}`)
}
assert.match(dashboard, /metrics/, '工作台缺少指标数据绑定')
assert.match(dashboard, /Asia\/Shanghai/, '工作台日期未固定为中国时区')
assert.match(dashboard, /loadDashboard/, '工作台未接入真实数据适配层')
assert.match(dashboard, /retryLoad/, '工作台缺少失败重试')
assert.match(brand, /activity-default\.jpg/, '品牌模块未指向活动封面降级资产')
assert.match(dashboard, /BRAND_ACTIVITY_FALLBACK/, '活动封面缺少本地降级资产')
assert.match(dashboard, /item\.max\s*>\s*0/, '成效进度未处理 max 为 0 的情况')
assert.match(dashboard, /v-for="metric in dashboard\.metrics"/, '六指标必须由 DashboardModel 统一渲染')
console.log('Admin reference contracts passed')