Files
t/NEXT-STEPS.md
T
root 3117146281 feat: 打通 HBuilderX CLI 构建 + 修复文章闭环静态盲区
构建:
- 新增 tools/build.js:junction HBuilderX 工具链,CLI 构建 h5/mp-weixin
- vue 指向补丁版 @dcloudio/uni-h5-vue(官方 npm vue 不导出 isInSSRComponentSetup)
- 设 HX_APP_ROOT 避免退化成 H5 空壳产物;产物完整性校验

校验工具:
- 新增 check-cloud-methods.js:acorn 解析云对象方法,比对 94 处调用点
- 新增 check-android-contract.js:Kotlin 侧云对象契约校验
- audit-project.js 修 downloadFile 误报(注释未剥离);tools/ 排除出扫描
- package.json 声明此前隐式依赖的 acorn

功能:
- 补 uni-cms-articles.getPublishedArticles(安卓端依赖但此前不存在)
- 修 u-parse <audio> 引用已移除组件导致 H5 构建失败
2026-09-12 01:02:45 +08:00

413 lines
22 KiB
Markdown
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.
# 下一步计划 · 文章闭环收尾 + 工具链打通
> 交接对象:下一个接手的模型 / 工程师。
> 本文档分两部分:**A. 本轮实际做完的事**(含验证证据),**B. 还没做的清单**(含具体做法)。
> 前置计划:`.zcode/plans/plan-sess_cd5437b2-dd23-478b-bfec-f4cce1ee4b10.md`(架构决策与 7 步实施步骤,已全部落地)。
---
## A. 本轮完成清单(本次会话实测,非推断)
### A1. 文章编辑→存储→渲染闭环(上一轮遗留,本轮核对确认已落地)
| # | 内容 | 落点 | 核对方式 |
|---|---|---|---|
| 1 | 数据适配层 `normalizeCmsList` / `cmsLstToDelta` / `createVoteId` / `getArticleText` / `getArticleImages` / `hasRenderableContent` | `uniCloud-alipay/cloudfunctions/uni-cms-articles/content-adapter.js`(170 行,含空块过滤、媒体字符串归一、投票最少 2 项) | 已读全文 + `check-article-flow.js` 通过 |
| 2 | 云对象 add/update 契约:mobile 缺 content 时服务端生成兼容 Delta;update 用 `valueOrExisting` 合并缺省字段;发布强制 `hasRenderableContent`;vote_id 自动生成 | `uni-cms-articles/index.obj.js` | 已读全文 |
| 3 | schema:`content` 从 `required` 移除(现为 `['user_id','title']`);read 权限收敛为 `doc.article_status == 1 \|\| (auth.uid != null && ('admin' in auth.role \|\| doc.user_id == auth.uid))` | `uni-cms-articles.schema.json` | 程序化解析确认 |
| 4 | 审核覆盖:`content \|\| cmsLst` 双通道;mobile 不依赖 content 返回正文;`content.ops` 缺失时降级不抛异常 | `uni-cms-articles.schema.ext.js` | 已读 `afterRead` 全文 |
| 5 | 移动编辑器:首块文字可加载(`editText.vue:97` 无 `if (index && ...)` 假值陷阱,`:137` 用 `Number.isInteger`);`{...oldItem}` 保留媒体字段;小标题写回;封面统一字符串数组 | `pages2/editCms/*.vue` | 逐处 grep 确认 |
| 6 | 详情渲染:mobile 分支输出 text/title;`render-article-detail/*` 兼容 `data.value` 与 `data.attributes.src`;列表递归 children;移除 Vue2 `this.$set`;`wx.` 已用 `#ifdef MP-WEIXIN` 包裹;`100wv/60hv` 全部清零 | `uni_modules/uni-cms-article/**` | grep 确认 0 命中 |
| 7 | 媒体删除鉴权:admin 放行;普通作者必须带 `article_id` 且文件确实被该文章引用 | `ext-storage-co/index.obj.js:65-82` | 已读实现 |
### A2. 本轮新修的问题
**① `ext-storage-co.downloadFile()` 审计告警 = 误报,已从根上修掉**
真因:`tools/audit-project.js` 的 `WRITE_METHOD_RE` 不剥离 `//` 注释,
把文件里**被注释掉的** `async downloadFile()` 当成活方法,报「未校验登录态」。
修法(已落地):新增 `blankLineComments()`,用**等长空格**替换行注释——
保持字节偏移不变,所以 `lineAt()` 行号和 `src.slice()` 切片仍然正确。
`sectionCloudAuth` 内所有正则改跑 `scanned`,`lineAt` 仍传原始 `src`。
> ⚠️ 不要改成直接 `src.replace(...)` 后再切片——偏移会错位,行号全乱。
**② 新增 `tools/check-cloud-methods.js`——云对象方法存在性校验**
补的是一层**原本完全没人管的静态盲区**:`audit-project.js` 只校验
`importObject` 引用的「云函数文件」是否存在,**不校验方法名**。
方法名写错在静态审计里 0 报错,只在运行时炸 `xxx is not a function`。
- 用 `acorn` 解析 `module.exports` 对象字面量取顶层方法名
- 支持两种导出形态:对象字面量 / `module.exports = require('./functions')`(递归解析 require 链,深度上限 3)
- 同时发现随插件分发的云对象(`uni-id-co` / `uni-pay-co` / `uni-captcha-co` / `uni-media-library-co`)
- 4 组内联样本自测:对象字面量 / 默认对象参数 / 注释里的假方法 / 字符串里的花括号
- **实测结果:18 个云对象、94 处调用点,0 错误**
被它拦下的真实问题(均已确认无碍):
- `article_info` 缺 `getInfo` / `isSelectVote` / `getCmsArticleLog` → 实际都有,是我第一版正则提取器漏了
- `uni-media-library-co` 缺 6 个方法 → 该云对象是 `module.exports = require('./functions')` 转出,加了 require 链解析后正常
**③ 新增 `tools/check-android-contract.js`——安卓端 × 云对象契约校验**
`android/` 是独立 Kotlin 工程,通过 URL 化 HTTP 网关调云对象,同样有方法名漂移风险。
- 从 Kotlin 源码抽 `callChecked/callRaw("云对象","方法")` 调用对(70 个文件、44 处调用点、去重 43 个)
- **做了反例测试**:故意把 `getPublishedArticles` 改成 `getPublishedArticlesXX` → 脚本正确报错;还原后恢复 0 错误
**④ `package.json` 声明了此前隐式依赖的 `acorn`**
`check-cloud-methods.js` 用了 `acorn`,但它是**未声明的传递依赖**——
一次 `npm install` 就会把它剪掉,脚本随即 `Cannot find module 'acorn'`。
现已写入 `devDependencies`(`^8.11.3`)并补了 npm scripts。
**⑤ 补上安卓端依赖的 `getPublishedArticles`(真实功能缺口)**
`android/README.md` 要求部署该方法,`ArticleRepository.kt:34` 优先调用它,
**但云函数里根本不存在**——安卓首页/搜索永远只能回退到「仅本人文章」,游客拿到空列表。
已实现(`uni-cms-articles/index.obj.js`):
- 公开、无需登录;`article_status = 1` 过滤
- 支持 `offset` / `limit`(上限 100)、`category_id`、`keyword`
- **keyword 做了正则转义**(`escapeRegExp`),避免用户输入被当正则执行
- **投影只取列表字段,刻意不取 `content`**:因为 `schema.ext` 的 `afterRead`
在读取 `content` 字段时会顺带给 `view_count` 加 1,列表页不该把「出现在首页」
算成一次阅读
- 排序 `publish_date desc`,附带 `total`
**⑥ 修掉 H5 构建的硬阻塞:`u-parse` 引用已被移除的 `<audio>` 组件**
`uni_modules/uview-ui/components/u-parse/node/node.vue` 用了 `<audio>`,
但新版 uni-h5 已不再导出 `Audio` 组件,H5 端编译直接失败:
```
"Audio" is not exported by "@dcloudio/uni-h5/dist/uni-h5.es.js"
```
改为普通 `view` 承载(保留 id/class/style 以维持排版),并写明了原因。
这是 vendor 文件,但**不修就构建不出来**,属于必要最小改动。
**注**:`components/cu-editor/cu-editor.vue:182` 也有 `<audio>`,但该组件全项目无引用,
且它不在构建路径上(H5 构建已通过),所以没动——**留着记录,别顺手改**。
### A3. 打通 HBuilderX CLI 构建(本轮最大产出)
这是原来完全没解决的事:**只能靠 HBuilderX GUI 点按钮,无法命令行/CI 构建**。
现在 `node tools/build.js -p h5` 直接可跑。
#### 踩过的 5 个坑(按发现顺序)
| # | 现象 | 真因 | 解法 |
|---|---|---|---|
| 1 | `ENOENT: ...\t\src\manifest.json` | uni CLI 默认按标准 CLI 工程布局找 `src/`,HBuilderX 工程根目录才是 `UNI_INPUT_DIR` | 设 `UNI_INPUT_DIR` / `UNI_OUTPUT_DIR` |
| 2 | 找不到 `@dcloudio/*` | 项目 `node_modules` 是 **Vue2 时代的残缺安装**(只有 4 个 @dcloudio 包),Vue3 工具链只随 HBuilderX 分发 | 把 HBuilderX 工具链目录 **junction** 进项目 `node_modules` |
| 3 | `Preprocessor dependency "sass" not found` | sass 在另一个插件目录 `plugins/compile-dart-sass/` | 单独 junction |
| 4 | `"isInSSRComponentSetup" is not exported by vue` | **关键**:官方 npm `vue` 3.x 不导出这个符号,但 `@dcloudio/uni-app` 从 `'vue'` 导入它。uni-app 用的是**打过补丁的运行时** `@dcloudio/uni-h5-vue`(`dist/vue.runtime.esm.js`,确实导出该符号)。uni 的 resolveId 插件本应把 `vue` 别名过去,但没拦住 | 把 `node_modules/vue` **直接 junction 到 `@dcloudio/uni-h5-vue`** |
| 5 | 构建"成功"但产物是 H5 空壳(无 `app.json`、无业务分包) | 缺 `HX_APP_ROOT` 时 `uni-cli-shared` 的 `isInHBuilderX()` 返回 false,退化成非 HBuilderX 模块解析路径 | 设 `HX_APP_ROOT` + `UNI_HBUILDERX_PLUGINS` |
附带:系统 node v24 跑 vite 会报 `Invalid or incompatible cached data (cachedDataRejected)`,
改用 **HBuilderX 自带的 node**(`plugins/node/node.exe`,v22.22.2)即稳定。
#### 交付物:`tools/build.js`
- 幂等:每次跑自动建联接,已存在的跳过(重跑 `新建 0 个`)
- `--doctor` 环境体检(含验证补丁版 vue 是否真的导出 `isInSSRComponentSetup`)
- `--link-only` 只建联接
- `--report` 打印产物体积明细
- **产物校验**:构建退出码为 0 不代表产物可用——脚本会检查
mp 必须有 `app.json`/`app.js`/`app.wxss`,H5 必须有 `index.html`,且 JS 文件数必须 > 0
npm scripts:`npm run build:h5` / `npm run build:mp-weixin` / `npm run verify` / `npm run verify:quick`
#### 实测证据
```
$ node tools/build.js -p h5 --report
✓ 构建完成(90 个 JS 文件)
产物目录:unpackage/dist/build/web
assets/ 2335.9 KB
static/ 312.1 KB
合计 2791.1 KB
$ node tools/build.js -p mp-weixin
✓ 构建完成(335 个 JS 文件)
→ app.json / app.js / app.wxss 齐全,pages2 等业务分包完整
```
H5 产物里 `editCms` / `editText` / `smallTitle` / `vote` / `editSetting` / `preview`
各自的 chunk 都在,7 个 chunk 含 `cmsLst`,说明文章链路真的编译进去了:
```
pages2-editCms-editCms.Cc0kHD0h.js
pages2-editCms-editText.CLBQkN2p.js
pages2-editCms-smallTitle.D2I257SK.js
pages2-editCms-vote.EUryHJH3.js
...
```
用本地静态服务器验证过 `HTTP 200`(`index.html` 1095 B、主 bundle 749754 B、
`editCms` chunk 12164 B)。
### A4. 验证门禁现状
```
$ node tools/verify.js
【检查器自测】
✓ 静态审计自测
✓ Vue 校验自测
✓ 文章内容适配自测
✓ 云对象方法校验自测
【项目检查】
✓ 静态审计 0 错误 / 11 警告 ← 原 12,误报已修
✓ Vue SFC 校验 0 错误 / 49 警告
✓ 云对象方法校验 0 错误 / 1 警告 ← 新增
✓ 安卓端契约校验 0 错误 / 0 警告 ← 新增
【数据与资源】13 项全 ✓(schema 63 文件有效、db_init 9 表)
═══════════════════════════════════════════
✓ 全部通过
```
---
## B. 未完成清单
### P0-1 · 提交工作区(最高优先)
**为什么**:19 个改动文件 + 5 个新源文件全裸奔在工作区,一次误操作就全丢。
```bash
cd C:/Users/12914/Desktop/t
git add -A
git status --short
git diff --cached --stat
git commit -m "feat: 打通 HBuilderX CLI 构建 + 修复文章闭环静态盲区
构建:
- 新增 tools/build.js:junction HBuilderX 工具链,CLI 构建 h5/mp-weixin
- vue 指向补丁版 @dcloudio/uni-h5-vue(官方 npm vue 不导出 isInSSRComponentSetup)
- 设 HX_APP_ROOT 避免退化成 H5 空壳产物;产物完整性校验
校验工具:
- 新增 check-cloud-methods.js:acorn 解析云对象方法,比对 94 处调用点
- 新增 check-android-contract.js:Kotlin 侧云对象契约校验
- audit-project.js 修 downloadFile 误报(注释未剥离);tools/ 排除出扫描
- package.json 声明此前隐式依赖的 acorn
功能:
- 补 uni-cms-articles.getPublishedArticles(安卓端依赖但此前不存在)
- 修 u-parse <audio> 引用已移除组件导致 H5 构建失败"
git log --oneline -1
```
**注意**:
- `.zcode/` 已加入 `.gitignore`(本次已做),不要提交。
- CRLF 警告是仓库既有配置,不影响提交,别改 `core.autocrlf`。
- `unpackage/` 与 `package-lock.json` 本来就在 `.gitignore`,构建产物不会入库。
- `private.wxb4c2c6c51ecba864.key` 已在 `.gitignore`,确认没被 `git add -A` 带进去。
### P0-2 · 补厚内容适配契约测试(最大的测试空心区)
`tools/check-article-flow.js` 只有 44 行、4 组断言,原计划第 8 步列的 7 条要求中
**a/b/c/d/e/f/g 六条测不到——因为逻辑还埋在 .vue 组件里,Node 侧无法引用**。
| 缺失断言 | 逻辑现状位置 |
|---|---|
| a. 编辑首个文字块(index=0)不被清空 | `editText.vue:97-108` |
| b. 视频改说明后 `src/poster/duration` 仍在 | `editCms.vue` 的 video 文字编辑分支 |
| c. 小标题能写入 `cmsLst`(`type:'title'`) | `smallTitle.vue:120-140` |
| d. 封面始终是字符串数组 `['','','']` | `editSetting.vue` |
| e. mobile 发布 payload 同时含 `cmsLst` + `content` | `preview.vue:404-420` |
| f. 5 种块在预览/详情分流后都有渲染目标 | 预览与详情模板分支 |
| g. `hasRenderableContent` 对空/纯空块的判定 | 已在 adapter,但只测了正例 |
**关键:先抽纯函数,再写断言。** 在 `content-adapter.js` 追加
`mergeCmsItem` / `normalizeThumbnail` / `buildPublishPayload`,
把组件里的内联逻辑改为调用它们(行为不变),然后补齐 a–g 共 ≥10 组断言。
**抽取时别弄丢已修好的地方**:
- `editText.vue:97` 是 `if (cms && cms.cmsLst && cms.cmsLst[index])`(没有 `if (index && ...)` 的假值陷阱)
- `editText.vue:137` 用 `Number.isInteger(Number(index))`
- `editText.vue:102` 已在用 `{ ...oldItem, text, html, delta }` 合并
- `smallTitle.vue:133` 用 `editingIndex.value >= 0`
### P0-3 · 端到端 QA(唯一能真证明「闭环」的一步)
构建能过 ≠ 功能正常。静态脚本只能证明数据层;保存/预览/发布/详情是跨端运行时行为。
**本轮已排除的**:浏览器自动化(`mcp__node_repl__js` 未在本会话提供),
所以只能做到「H5 产物可服务 + chunk 齐全」,**真正的页面渲染与交互没人验证过**。
环境:HBuilderX(或用 `node tools/build.js -p h5` 出产物后起静态服务器)+
关联 `uniCloud-alipay` 服务空间,上传 `cloudfunctions` 与 `database`。
| # | 操作 | 期望 |
|---|---|---|
| 1 | 新建 mobile 文章:标题 + 首块文字 + 小标题 + 图片 + 视频(填说明) + 投票 | 存草稿成功 |
| 2 | 退出编辑页重新进入 | **首块文字仍在**(回归 a);小标题在;视频说明在;封面是字符串 |
| 3 | **打开已有文字的文章,不点任何东西直接保存** | 原文不变(见 P1-2) |
| 4 | 点预览 | 5 种块各渲染一次,**不重复渲染**(历史上 u-parse + 单独渲染会重复) |
| 5 | 发布 | 请求 payload 同时含 `cmsLst` 与 `content`;`vote_id` 非空 |
| 6 | 打开详情页 | 全部块可见;视频可播;投票可提交并计数 |
| 7 | 匿名/未登录,用草稿 `_id` 直连读 | **被拒**(read 权限表达式生效) |
| 8 | 换封面:先让上传失败(断网) | 旧封面仍在,文章引用未被改坏 |
| 9 | 普通作者删**自己**文章的图 | 成功 |
| 10 | 普通作者拿 A 文章 `article_id` 删 B 文章的文件 | **失败**「文件不是该文章引用资源」 |
| 11 | 老 PC 文章(`edit_type != 'mobile'`,content-only)打开详情 | 走官方 renderer,**不被 mobile 分支污染、不被兜底清空** |
| 12 | H5/小程序启动控制台 | 无 `wx is not defined`、无 `$set` 报错、无非法单位 |
| 13 | **安卓端首页(未部署 getPublishedArticles 时 vs 部署后)** | 部署前回退「仅本人」;部署后游客可见已发布文章 |
**注意第 11 条**:`afterRead` 对 PC 文章有兜底
`if (!article.content || !Array.isArray(article.content.ops)) { article.content = { ops: [] } }`,
要验证老文章**不会因此静默变空白正文**。若发现被清空,改成「保留原值 + 记日志」。
### P1-1 · 内容审核链路未真跑(本地脚本覆盖不到)
`schema.ext.js` 的 `beforeAdd` / `beforeUpdate` / `afterRead` 只在 uniCloud 运行时执行。
做法:控制台/真机发一条含敏感词 + 一张违规图的 payload,确认
文本命中抛错、图片命中抛错、**mobile 文章只填 `cmsLst` 不填 `content` 也要被审到**。
记录请求 id 与返回。
### P1-2 · 编辑器空内容覆盖保护(残留边界风险)
`pages2/editCms/editText.vue:100-108`(已核对):`fail` 分支已经不覆盖原内容,
但 `success` 分支在「editor ready 但内容未 hydrate」时会用空串把原文字抹掉。
```js
const nextText = text || ''
const hadContent = Boolean(oldItem.text || oldItem.html)
const gotContent = Boolean(nextText || html || (delta && delta.ops && delta.ops.length))
if (hadContent && !gotContent) {
console.warn('编辑器内容为空,跳过覆盖以防丢稿')
await back()
return
}
```
同一守卫在 `smallTitle.vue` 的 `getContents` 成功回调里也要加。
**验收**:P0-3 用例 3。
### P1-3 · 安卓端真实可跑性未验证
`tools/check-android-contract.js` 只证明**方法名对得上**,不证明能跑。
现状盘点(本轮查明):
- 工具链齐:JDK 17(`android-tools/jdk17/jdk-17.0.20+8`)、Gradle 8.7(`android-tools/gradle-8.7`)、Android SDK(`android-tools/sdk`,build-tools 34.0.0)
- AVD 存在:`dsh_api34` / `test_avd`
- `gradle projects` 已跑通(`Root project 'junge-liaoliang'` → `Project ':app'`)
- **但 `android/` 没有 `gradlew` wrapper**,只能用外部 gradle;`adb devices` 当前无设备
做法:
```bash
# 1) 起模拟器
cd C:/Users/12914/Desktop/t/android
export JAVA_HOME="C:/Users/12914/Desktop/android-tools/jdk17/jdk-17.0.20+8"
"/c/Users/12914/Desktop/android-tools/sdk/emulator/emulator.exe" -avd dsh_api34 &
"/c/Users/12914/Desktop/android-tools/sdk/platform-tools/adb.exe" devices
# 2) 构建 debug
"/c/Users/12914/Desktop/android-tools/gradle-8.7/bin/gradle.bat" :app:assembleDebug
# 3) 装并跑,对着 uniCloud 空间验证 getPublishedArticles 真能返回数据
```
**先确认 `UNICLOUD_BASE_URL`**:`android/app/build.gradle.kts:21` 当前是
`https://env-00jxt5y29hrd.dev-hz.cloudbasefunction.cn`——需确认这是控制台里
真实开通 URL 化的域名,否则所有接口都会失败。
**另一个要留意的集成点**:`uni-cms-articles.schema.ext.js` 的 `afterRead` 开头有
```js
if (!config.clientAppIds || !config.clientAppIds.length) throw new Error(...)
if (!config.clientAppIds.includes(clientInfo.appId)) return // ← 静默跳过
```
当前 `uni-cms-config/uni-cms/config.json` 里 `clientAppIds = ["__UNI__EF95AB3"]`,
与 `manifest.json` 的 appid 一致,所以小程序/H5 正常。
但从安卓经 URL 化网关调用时 `clientInfo.appId` 很可能是空值 →
`includes(undefined)` 为 false → **`afterRead` 直接 return,整段内容后处理被跳过**
(解锁内容、`content_images`、`view_count` 自增都不会执行)。
对 `getPublishedArticles` 无影响(它本来就不取 `content`)。
但要确认 `article_info.getDetail` 从安卓调用时返回的 `content` 是否仍是**未处理的原始 Delta**——
如果是,安卓端要么自己处理,要么得把安卓的 appId 加进 `clientAppIds`。
这是 P1-3 真跑时要专门看一眼的点。
### P2-1 · 编码类警告(独立问题,别混进文章链路提交)
| 文件 | 问题 | 处理 |
|---|---|---|
| `hx_page.html` | UTF-8 BOM | 已在 `.gitignore`(构建产物)→ 给 audit 加 ignore 白名单 |
| `uni_modules/uni-pagination/components/uni-pagination/i18n/es.json` | 非法 UTF-8(GBK/二次编码损坏) | 独立修:从 `git show HEAD:<path>` 取原文重存 UTF-8 |
### P2-2 · `check-vue.js` 49 条警告 = 全部不在范围内
分布:`uni_modules` 36 / `components` 12 / `pages3` 1。
全是第三方或 Vue2 遗留写法(`beforeDestroy`、`filters`、`this.$set`、`<script lang="uts">`)。
**结论:不改**。顺手改会引入回归,且会让文章闭环的提交 diff 失焦。登记为「既有基线」。
同理 `components/thorui/**` 的 `this.$set`(12 处)也不要动。
---
## C. 建议执行顺序
```
P0-1 提交工作区 ← 先做,锁住本轮成果
↓
P0-2 抽纯函数 + 补断言 ← 纯本地,可离线
↓
P1-2 编辑器空覆盖守卫 ← 小改动,配合 P0-2 一起测
↓
P0-3 端到端 QA ← 需要 HBuilderX + 服务空间,真正验收
↓
P1-3 安卓端真跑
↓
P1-1 审核链路真跑
↓
P2 编码/警告清理(可选)
```
---
## D. 验收标准
1. ✅ 新建 mobile 文章:标题+文字+小标题+图片+视频+投票,能存草稿、重开、预览、发布
2. ✅ 发布后详情所有块可见,文字/小标题不消失,视频可播,投票可提交
3. ✅ 编辑旧文章的首个文字块、视频说明、封面、模板、音乐后,其他字段不丢
4. ✅ 上传失败旧媒体保留;普通作者只能处理自己文章的媒体
5. ✅ 匿名用户只能看已发布文章,不能按已知 ID 读草稿
6. ✅ H5/App/微信小程序不因 `wx`、Vue2 `$set`、非法单位、空图片地址崩溃
7. ✅ 现有 PC/标准 `content` 文章继续走官方 renderer,不被 mobile 逻辑破坏
8. ✅ `node tools/build.js -p h5` 与 `-p mp-weixin` 均产出完整产物
9. ✅ `node tools/verify.js` 全绿(4 自测 + 4 检查 + 13 数据项)
10. ⬜ `check-article-flow.js` 断言组数 ≥10,覆盖 a–g(**待做**)
11. ⬜ 工作区已提交(**待做**)
12. ⬜ 安卓端在模拟器上真实跑通首页文章流(**待做**)
---
## E. 环境备忘(本轮实测)
| 项 | 值 |
|---|---|
| Node | v24.19.0(跑 `tools/*.js` 可以;**跑 vite 构建不行**,要用 HBuilderX 的 node) |
| HBuilderX 构建 node | `C:/Program Files/HBuilderX/HBuilderX/plugins/node/node.exe` (v22.22.2) |
| HBuilderX | `C:/Program Files/HBuilderX/HBuilderX`(编译器 5.15,Vue3) |
| JDK | `C:/Users/12914/Desktop/android-tools/jdk17/jdk-17.0.20+8` |
| Gradle | `C:/Users/12914/Desktop/android-tools/gradle-8.7/bin/gradle.bat` |
| Android SDK | `C:/Users/12914/Desktop/android-tools/sdk`(build-tools 34.0.0,AVD `dsh_api34`) |
| 当前分支 | `test`(主分支 `main`) |
| `.zcode/` | 工具链运行产物,已 ignore |
| 端口 | 5173 被**别的项目**(english-drill)占用;adb 用 5037 |
**不要做的事**:
- 不要在项目根跑 `npm install`——会按 Vue2 时代的 `package.json` 剪掉联接,
需要重跑 `node tools/build.js --link-only` 恢复(本项目是 HBuilderX 工程,不是标准 CLI 工程)
- 不要改 `vite.config.js`(HBuilderX 需要它指向 HBX 工具链)
- 不要动 `unpackage/`(构建产物,已 ignore)