@@ -0,0 +1,113 @@
# T5 执行说明 · 前端·壳与管理端框架
> 任务书 T5(v1.1)|执行手册 §1 模板组合「前端 = fork notice/front」+ §2.1(一个 SPA 承载全部形态)+ §2.2(模块行表与 path 空行口径)|方案 v1.4 §3.3(页面即组件、`self != top` 剥壳)+ §7.3(《云海子应用前端接入规范》v1.11)+ §9 V22
> 主体改动 = **front 仓**( `form-center/form-center-front`);service 仓两处 T4 验收遗留小改;YschTheme 仓 consumers.json 登记。
> 本轮不自创设计:值形状、控件、接口口径全部沿用契约,前端只做壳与骨架。
---
## 1. 做了什么
### 1.1 notice/front 基线整体迁入并剥领域(不是改造 T1 裸壳)
T1 留的 `front` 只有 `App.vue + main.ts + 仅 vue 依赖` 。本轮把 notice/front 的**壳基础设施**落进来并剥净领域,形成 47 个源文件(另 25 个 svg 资产)的工程:
| 迁入件 | 来源 | 说明 |
| --- | --- | --- |
| `index.html` | Notice 现行版 | env-config 同步引导 + **A 类 loader(§2.1 原文照抄,未改一字) ** ; title 走 `createHtmlPlugin` 注入「表单中心」 |
| `vite.config.ts` | Notice 现行版 | vue / svg-icons / vite-plugin-mock / auto-import / html / UnoCSS 六插件 + `base:"."` ( P11 已就位)+ **port 8089( P6) ** + `/api` →8086、`/static` →中央通道(`YH_THEME_PROXY_TARGET` 可覆盖)代理 + 产物恒 `dist` 根(zip 根含 index.html,§7 坑) |
| `src/utils/alova/{alova-core,alova-iview}.ts` | Notice | 信封解包(`code==0` →data、`302` →挂起 Promise 跳 CAS)、`yschapirequest/yschreferer` 头、800ms 延迟 Spin |
| `src/components/layout.vue` | **Notice refined 版(13afb22) ** | 菜单 icon 兜底 `icon-list` + 面包屑首页图标行内基线流 `-0.125em` (规范 §4 两行)+ Logo `yh-logo-url` 平台键优先/本仓兜底 + `yh-appearance-updated` 监听 + 无配色切换器(§3 红线) |
| `src/utils/hook/useElementHeight.ts` 、`src/components/TablePageAlovaNew.vue` | Notice/flow-center 同构 | 通道撑满高度模型(§4.3 三硬约束)+ `.ysch-spin` loading 插槽形态(§4 表格加载行) |
| `src/utils/tableActions.ts` | **UserBase 同构源 ** | `actionColumn/normalizeActions/splitActions/actionCell` ,显式 `mode` (§5.1) + `disabledWhen` 置灰保位(§5.2 红线) |
| `src/utils/{index,menuRoute,mitt}.ts` 、`src/utils/directive/*` 、`src/assets/styles/*` 、`src/assets/icons/*` | Notice | `Utils` 类(旧 `operatingHandle` 保留为薄封装,§5.4)、main.scss 含 `.op-cell` /`.table-spin-load` /固定层事件/省略号 Tooltip 四段 |
| `src/components/{UserAvatar.vue,utils/avatar.ts}` | **YschTheme `npm run sync` 下发 ** | 与通道源 MD5 逐字节一致(`9ac72f8b` /`1fa7163c` ),sync --check 报「全部对齐」 |
**剥除清单 ** (任务书「剥 notice 全部领域 views 与 mobiapi」):`views/notice/*` 8 页、`api/module/{notice,approval,cc}.ts` 、`assets/mock/noticeapi.ts` 、`assets/config/menu.ts` (本地菜单兜底=假象,与后端「不保留本地 MENUS 兜底」同口径,一并剥)、`utils/alova/alova-vant.ts` ( **mobiapi 腿**,指向 `/mobiapi` 的移动端实例)、`NoticeUpload/ThemeSwitcher/GradeClassSel/BschSubjectSelect/UserCheckModal/MyTree*` 等领域或切换器组件、`utils/{axios,diagramImg,bschSsoRedirect,flowLayout…}` 与 index.html 的 **ztree+jquery 库引用 ** (树件随 T8 需要时按平台件再引)。重量依赖 `@wangeditor` 、`pdfjs-dist` 、`@open-file-viewer` 、`vuedraggable` 、`axios` 、`lodash` 、`@alova/wormhole` 未装(各自属 T6/T7 的编辑/预览/拖拽需求,届时显式加)。
### 1.2 左导航(GetUserMod)与 path 空跳过
- **`src/utils/menuNav.ts` (新增,本轮唯一的导航判定实现)**:纯函数 + 解析器注入(`buildNavTree(modList, resolve)` ),三条规则写在文件头——① **跳过判据取平台原始 `path` 是否为空 ** (不取解析结果:靠名字/modId 猜出路由再显示就是把权限锚点画成死菜单);② 有可渲染子节点的分组行保留为**分组头**(不可点),子节点全被跳则**整组不渲染**;③ 原始 path 非空但解析不出已注册路由的行**照常显示**、点击时 `Message.warning` 点名配置错误(不静默少一条菜单)。
- 高亮/面包屑按 `flattenNav` 对照(分组头不入表),`firstNavPage` 决定登录后的默认落地,`isSingleNav` 承 Notice 的「单模块独占」语义。
- `App.vue` :拿 `GetUserInfo + GetUserMod` → `buildNavTree` → 交 `layout.vue` 渲染;**失败只降成"导航为空",不造兜底树**(后端注释同口径)。DEV 下 `console.table` 同时打原始树与过滤后导航,冒烟可直接对比。
### 1.3 `api/module/base.ts` 形态与 appCode 参
`BASE="/api/base"` 的 `getUserInfo()` / `getUserMod(appCode?)` ( **参数名按 P2 定稿 appCode**)。文件头写明:path 空行只由后端 `DataScopeService` 扫描消费、接口不过滤,前端只在导航层跳过。**P3 口径**:H4 未上线时该请求返回空树,页面即空态,不回落本应用树(mock 侧同一条判定已实现并在冒烟 D 段断言)。
一处必要偏离:Notice 的 `new Method<T>("GET", …)` 在 alova 3.5.5 类型下**不合法**( `Method` 的第一泛型位是 `AlovaGenerics` 而非响应类型,strict vue-tsc 报 TS2344),本端改 `alovaInstance.Get<T>()` ——同一个 Method 对象、同一套拦截器与信封解包,行为等价(P49)。
### 1.4 路由骨架:一个 SPA 四形态(手册 §2.1)
`createWebHashHistory` ( Notice/flow-center 同族),路由表即 `/admin/{forms,data,stats,dicts}` 、`/f/:code` 、`/app/:appCode[/:view]` 、`/my` , `/` = 入口页、`/:pathMatch(.*)*` → `/` (外部深链不白屏)。产物部署在 `/form-center/front/` ,故手册写的 `/form-center/f/{code}` = ctx + `#/f/{code}` 。
- **`meta.modId` 全部不写**(规范 §4「modId 全量对齐后端 GetUserMod,禁臆造」):本应用模块行除 `fc:scope:all` 外 customId 均未配(手册 §2.2 表为「—」),后端 `resolveModId` 因此回退 `app_module` 主键 id,该值跨环境不稳定,写进路由必然失配;高亮与面包屑按 path 匹配。
- 占位页 7 个(`views/admin/*` 4 + `Fill` + `AppContext` + `My` + `Entry` ),统一走 `components/PageStub.vue` 的 `page-wrapper.yh-fill > main-container` 骨架 + 常驻 `Alert type=info show-icon` (§4.2 无 closable/banner)。**占位页刻意不挂 `.table-region` **——没有 Table 却铺一个"应当被量高"的容器,会留下假范式(§4.3 三硬约束的成立前提是有 Table 喂回高度)。零领域逻辑、零接口调用(`Fill` /`AppContext` 只把 `code` /`appCode` 与形态显示出来)。
### 1.5 `self != top` 剥壳(方案 §3.3)
`App.vue` 顶层判 `window.self !== window.top` → 嵌入态裸渲染 `<router-view>` :不画 layout、**不发两个鉴权请求**(嵌入上下文取不到本应用会话,失败会弹错误并画出带壳页),并内联旁路 `_base.scss` 的 `body{min-width:1200px}` ( 740px Drawer 里不旁路就整体右偏+右裁,flow-center 同款手法)。显式 `?mode=` 闭集留给 T7,本轮只做 `self!=top` 这一条兜底。
### 1.6 主题通道登记(YschTheme 仓)
`consumers.json` 新增 `form-center` 条目(`path:"../form-center/front"` 、`target:"pc"` 、`files` 只登记 avatar 两件——按 §3 红线不建配色切换器,故不登记 `theme.ts` /`ThemeSwitcher.vue` ; ctx=/form-center/ 写进 note);同步更新 `tests/scripts.spec.ts` 的「消费端清单显式可控」断言(清单加一条 + form-center files 显式断言)。`npm run sync` → form-center 两件报「一致」;`npx vitest run` → **8 文件 84 例全绿 ** ,其中 `iview-version.spec` 即「消费端 lock 实装版本 === 通道 devDependencies 精确版本」的版本锁测试,form-center 通过=**1.3.24 锁红不再失守**。
### 1.7 service 仓两处顺带(T4 验收遗留,不喧宾夺主)
- **勘误③ 真落**: `docs/T3-report.md` §2 的 V32 行「**两个不同教师**」后补注「(实为两个 roleId=7 操作者——教师(1) 按 P21 无代填权)」。已核源码:`AnswerSubmitIT.onceLimitIsPerSubjectWhenSubjectPresent` 用的是 `user(10001, SCH, 7)` 与 `user(10002, SCH, 7)` ,注释属实。T4 报告 §1.6 声称已改、实测未改,本轮落到文本。
- **P47 收紧**: `FormModuleKeys` 的 customId 扫描改为**只收 path 空节点**(新方法 `anchorCustomIds` , `collect` 单处分支;旧 `customIds` 无其他调用方,一并收敛不留死代码),`DataScopeService.hasScopeAllAnchor` 切过去。补测 `DataScopeServiceTest.customIdOnNavRowDoesNotUpgradeScope` :带 path 的导航行误配 `fc:scope:all` → 仍 `SELF_SUBMITTED` ;同一用户同一值改挂 path 空行 → 立刻 `ALL` (证明收窄生效、不是扫描整体失效)。配套给 `InMemoryModuleTreeSource` 加 `grantPathWithCustomId` 桩方法。**契约未改**(§8 的「锚点行形态」注记 T4 已补)。
---
## 2. 自验证据(逐条)
取证脚本:**`form-center/local-debug/t5-shell-smoke.sh` **( 13 断言,ASCII 锚点,JSON 交 node 解析不在 shell grep 中文)。终态运行结果 * * `13 pass / 0 fail / 1 skip` **。
| 项 | 判据 | 证据 |
| --- | --- | --- |
| `npm run build` | 必须过 | ✅ `✓ built in 5.54s` (脚本 A 段 `PASS build` ) |
| `npm run type-check` | 必须过(`vue-tsc --noEmit` , strict + `noUnusedLocals/Parameters` ) | ✅ 0 错误(脚本 A 段 `PASS type-check` )。Notice 基线在本机 `vue-tsc` 是坏的(`store/index.ts` 无 `IModule` 、`Basics.d.ts` 写 `page: !number` ),本轮按 T1~T4「本地门禁 + CI 只证构建链」的口径把 type-check 做成硬门禁,故对迁入件做了最小整形(P49/P50) |
| **V22 ** build 通过 | 方案 §9 原文 | ✅ 上一行 |
| **V22 ** consumers.json 已登记 | 规范 §2.3 | ✅ 脚本 B 段 `PASS consumers.json 已登记 form-center( ctx=/form-center/) ` ; `npm run sync` 报 `一致 form-center src/utils/avatar.ts / UserAvatar.vue` 、`[sync] 全部对齐` ; `npx vitest run` 84/84 绿 |
| **V22 ** view-ui-plus 锁 1.3.24 | 规范 §1/§2.3 | ✅ `package.json` 声明 `"1.3.24"` (无 `^` ) + `package-lock.json` 实装 `1.3.24` (脚本 B 段)+ 通道 `iview-version.spec` 通过 + `npm ls view-ui-plus` → `1.3.24` |
| **V22 ** 本地无 layout.css/icons.css | 规范 §2.2 回收步 | ✅ 脚本 B 段 `PASS no-local-layout-css-icons-css` ( `find src -name layout.css -o -name icons.css` 为空);`main.scss` 无相关 import; `dist/index.html` 保留 `yh-theme-init.js` loader 与 `api/env-config` 引导、`%VITE_THEME_HOST%` 未被烧值 |
| **V22 ** 控件目录含 `app.vue` 槽位 | — | ⏭ 任务书已注明:**app.vue 槽位一条留 T6 验**(控件目录随设计器产生,本轮无控件) |
| 登录出壳 + 左导航(菜单树=手册 §2.2 表) | 任务书自验 | **降级取证 ** ( P48,原因见 §3):`npm run dev:mock` + in-app browser a11y 快照——顶栏 `Logo|分隔线|表单中心|Mock 管理员|退出` 、左导航**恰 5 项**(表单设计/数据管理/统计/字典管理/我的填写)、**「数据全量」(path 空) 未出现**、面包屑 `首页 > 表单中心 > 表单设计` 、URL 自动落 `#/admin/forms` (首个可跳转项)。截图不可用(in-app browser 无可见 viewport),以结构化快照为据 |
| path 空跳过 / 分组收敛 / 无授权仅「我的填写」 | 手册 §2.2 验证口径 | ✅ 脚本 C 段:`esbuild` 单独转译 `utils/menuNav.ts` 后在 node 里断言——6 行树(含 1 行 path 空)→ 导航 5 项且锚点不在;`[我的填写, 锚点]` → 1 项且 `isSingleNav=true` ;子项全锚点的分组整组不渲染、分组头不入高亮表 |
| 外指 appCode 空树不回落 | P2/P3 | ✅ 脚本 D 段:mock 侧与后端同一条判定(`appCode=some-other-app` → `data=[]` );前端 `getUserMod(appCode)` 是唯一入口、无任何「失败即取本应用树」的代码路径 |
| `self != top` 剥壳 | 方案 §3.3 | ✅ 同源 iframe 探针(dev:mock 页内动态挂 740px iframe 指 `#/f/DEMO_PROBE` ):顶层 `.header-container/.left-menu/.path-nav` 各 1, iframe 内**各 0**、`self!==top=true` 、`body.style.minWidth="0px"` 、正文显示「形态 = 页面块(self != top,已剥壳)」 |
| P6 本地端口 | 手册 §5 | ✅ front dev=8089( `vite.config.ts` )、service=8086( `/api` 代理 target) |
| P11 壳的平台约定补齐 | 手册 §5 | ✅ loader/alova/router/auto-import/svg-icons/env-config 引导/`base:"./"` 全落,T1 留在 `vite.config.ts` 的 TODO 注释已删 |
| **service 两处小改 ** | 本地 `mvn test` 门禁 | ✅ `mvn -B -o clean package` → **BUILD SUCCESS, Tests run: 178, Failures: 0, Errors: 0 ** ( T4 基线 177 + 本轮 P47 新增 1 条) |
---
## 3. 问题单(本轮登记,编号接续 P47;供执行手册 §5 裁决)
| # | 日期 | 批次 | 问题 | 本轮取值(最保守解读) | 是否回文档修订 |
| --- | --- | --- | --- | --- | --- |
| P48 | 10-14 | T5 | 任务书要求的「登录态冒烟(CAS/UserBase 本地起 → 真会话出壳 + 菜单树=§2.2 表)」不可执行 | **按 P1 先例降级取证 ** ,两条独立事实:①本机 CAS(8443)/UserBase(8060) 未运行(`curl` 均无响应),Redis 亦未起;②**更本质的缺口**——本地 `bsch_userbase` 里 `application_info` 中 `app_code='form-center'` 的行数=**0**、`app_module` 无任何 form-center 模块行(查得 `select count(*) … where a.app_code='form-center'` → 0, `app_module` 按 app_id 分组无对应 id),即 H2( UserBase 建中心应用行 + §2.2 模块行 + 组授权)未做。即便把三件服务起齐,`GetUserMod` 也只能返回空树,拿不到 §2.2 那棵树。补证=脚本 C 段纯函数断言 + D 段 mock 树 + 浏览器结构化快照(§2 表)。**H2 完成后 T8/T9 需真会话复跑一次** | 否(缺口在外部数据,非设计) |
| P49 | 10-14 | T5 | Notice 的 `api/module` 形态 `new Method<T>("GET", …)` 与 alova 3.5.5 的类型不兼容(`Method` 第一泛型位是 `AlovaGenerics` ),strict `vue-tsc` 恒报 TS2344; flow-center/open-gateway 同病(它们的 `npm run type-check` 从没跑绿过,规范 §8 也写「vue-tsc 损坏勿依赖」) | 本端改 `alovaInstance.Get<T>(url, {params})` :同一个 Method 对象、同一套 `beforeRequest/responded` 与信封解包,行为等价、类型正确。**T6~T8 的 api/module 文件沿此写法**(其余形态:`BASE` 常量 + class + `export const xxxApi` 不变)。不改 Notice 已验收工程 | 否(工程写法,非契约) |
| P50 | 10-14 | T5 | 四端同构件 `utils/tableActions.ts` 落第五端(本端 type-check 是门禁) | 照 open-gateway 先例:顶部补 `// @ts-nocheck` + 4 行说明,**正文与同构源逐字节一致(MD5 `973393684783c740399b8eb9e97369bc` ,与 UserBase/flow-center 全等)**, strict 下两处告警均无害(`h` 被形参遮蔽、`it.on()` 已在 normalize 闭包绑定)。规范 §8「与四端 MD5 对齐」的判据按"正文对齐 + 端顶补丁"执行,同 open-gateway 一轮的既有先例 | 否 |
| P51 | 10-14 | T5 | 新端的操作列形态(§5.1 要求「工程级恒定」,Notice=icon / UserBase=text)本轮没有列表页可定 | 暂按同族 center 应用取 **text 恒定 ** ( flow-center/svc-hall/open-gateway 皆 text),`mode` 显式声明、缺省 text;**T8 出第一个真实列表页时若用户要 icon 形态,改的是各页 `mode` 参数而非工具件** | 否 |
| P52 | 10-14 | T5 | 剥领域后的「回补时机」未写:index.html 的 ztree+jquery、`@wangeditor` /`pdfjs-dist` /`@open-file-viewer` /`vuedraggable` /`axios` /`lodash` 均未随 fork 带入 | 一律**按需再引**(设计器 T6、附件预览与拖拽 T6/T7、组树选择器 T8),不提前铺空依赖:本轮 `npm ci` 518 包、构建 5.5s,若照搬 notice 全量依赖会显著拖慢 CI 且产物里多份未用图形资产(§4 表格加载红线:应用侧零图形资产)。回补时在对应批次报告里点名 | 否 |
| P53 | 10-14 | T5 | 入口页 `/` 的落地语义方案未写:Notice 把 `/` 做成真实业务页(已收到),flow-center 用 `redirect` | `/` = 真实入口页(无模块时的可诊断空态),登录后由壳 `replace` 到首个可跳转项;兜底 `/:pathMatch(.*)*` → `/` 而非某个具体管理页(**无授权用户不该被送到他进不去的页面**)。若用户希望 `/` 恒落「我的填写」,改 `routes.ts` 一行 | 否 |
另记两条**非问题**事实,供接手的人少踩:①`local-debug/` 不在任何仓库工作树内(`form-center` 根无 .git,仓库根是 `service/` 与 `front/` ),本轮新增的 `t5-shell-smoke.sh` **不入库 ** (沿 T1~T4 同一事实);②YschTheme 仓**无 git 远端**( `git remote -v` 空),consumers.json/tests 的登记改动只落本机工作区,未 commit、未 push,通道发布仍走该仓 `release.mjs` /`release.py` 人工动作。
---
## 4. 推送与 CI
| 仓 | 提交 | 推送 | CI run | 结论 |
| --- | --- | --- | --- | --- |
| form-center-front | `fdd9aaa` (81 条路径:73 新增 + 7 修改 + 1 删除(T1 的 `src/env.d.ts` 并入 `src/vite-env.d.ts` ),逐条 `git add` ) | 见下 | — | — |
| form-center-service | 勘误③ + P47 + 本文 | 见下 | — | — |
(提交号、run 号与终态在本轮推送后回填,沿用 T1~T4 的「feat → docs 回填」两次提交形态。)
- 暂存方式:按显式路径逐条 `git add` ,提交前 `git status` 核对无残留未跟踪项、无 `add -A` 、无敏感文件;`node_modules/` 、`dist/` 、`*.log` 已由 front 仓 `.gitignore` 排除;`src/auto-imports.d.ts` 按 Notice 先例**入库**( CI `npm ci` 后首次 `vue-tsc` 需要它)。
- 前端 CI 只证构建链(`npm ci` + `npm run build` ,同后端「CI 不跑单测」先例),type-check 与冒烟是**本地门禁**——本轮两者均绿(§2)。
- YschTheme:无远端,登记改动留本机工作区(见 §3 末)。
—— 全文完 ——