移动端坑位
适用端:移动端 | 类型:排错
本页是移动端专属的按症状查索引,24 条(编号 M1–M24)。每条只写「现象 → 根因 → 怎么办」,机制细节链到本页所在章的正文页。
三条约定与坑位索引一致:
- 每条都有实测证据——源码锚点、脚本量测结果、产物里的字面量,或本仓库自己记录的构建输出。没有证据的不进清单,只在「未证实」里出现。
- 标 🔴 的是会咬人的:安全暴露、发布事故、或排查方向会被带偏到别处。
- 正文页各自的「坑与注意」段是贴着上下文的提醒;本页是按症状找的索引,同一件事两边都出现时不重复展开。
跨端通用的坑(数据库与多租户、菜单与权限、后端构建部署、代码生成器、测试协作)在坑位索引,那里用编号 A1–F6,与本页的 M 编号是两套互不复用的空间。那页的 G 节反过来指向本页标 🔴 的那几条,好让「上线前过一遍」能在一页里做完——条目正文只在本页有一份,那边只是指路。
索引
最后一列是「机制在哪页」——本页只写现象与怎么办。
| # | 一句话 | 严重度 | 机制在哪页 |
|---|---|---|---|
| M1 | flex-1 单独用在 z-paging 上会高度塌陷 | 高 | 页面开发范式 |
| M2 | 忘了 :fixed="false",列表会盖住自定义 navbar | 高 | 页面开发范式 |
| M3 🔴 | @query 漏 catch → 永久转圈 + 未捕获 rejection | 高 | 页面开发范式 · 请求封装与登录态 |
| M4 | yd-* / wd-* / z-paging 不用 import,useToast 必须 import | 中 | 页面开发范式 · 工程结构 |
| M5 🔴 | 跳转失败一律提示「页面不存在」,会把排查带偏 | 中 | 服务端驱动导航 |
| M6 | tabbar 页不能 navigateTo,switchTab 又不接受 query | 高 | 服务端驱动导航 |
| M7 | 4 个 tabbar 项共用同一个 route,按 route 去重会误判 | 中 | 服务端驱动导航 · 在线表单在移动端 |
| M8 | 改 pages.config.ts / tabbar/config.ts 必须重启 dev server | 高 | 工程结构 |
| M9 🔴 | env/.env 里的 VITE_ 值会被内联进生产包。已修 | 致命 | 多端构建与发布 |
| M10 | sync-manifest 曾把 app 产物路径写死成 dist/dev/app。已修 | 中 | 多端构建与发布 |
| M11 | 约 52% 的 H5 产物是用户点不到的分包 | 中 | 移动端总览 · 多端构建与发布 |
| M12 | VITE_SHOW_SOURCEMAP 与 VITE_FALLBACK_LOCALE 都是空写 | 低 | 多端构建与发布 |
| M13 | versionName / versionCode 硬编码,App 端永远 1.0.0 | 中 | 多端构建与发布 |
| M14 | build:h5 会弹浏览器,build:mp 会拉开发者工具 | 中 | 多端构建与发布 |
| M15 | 小程序上传的默认版本号取自 yudao-version | 中 | 多端构建与发布 |
| M16 | 56 个 dev/build 脚本里只有 3 个挂了 init-baseFiles 前置钩子 | 中 | 环境准备与启动 · 多端构建与发布 |
| M17 | staticUrl() 会把 VITE_STATIC_BASEURL 拼在 /static/ 前。已修 | 高 | 多端构建与发布 |
| M18 🔴 | 三个身份标识仍是上游的,上传会传到上游小程序 | 致命 | 多端构建与发布 |
| M19 🔴 | 请求加密开关是 true,但没有任何调用方传 isEncrypt | 中 | 请求封装与登录态 · 多端构建与发布 |
| M20 | eruda 只在 h5 + development 注入 | 低 | 环境准备与启动 |
| M21 | src/types/* 大部分不入库,新克隆 type-check 会报找不到模块 | 中 | 工程结构 · 移动端总览 |
| M22 | src/components/yudao-ui/ 目录名刻意不改 | 低 | 工程结构 |
| M23 | Android 权限清单整段继承上游 | 中 | 多端构建与发布 |
| M24 | unibest 上游合并要按月推进,不能整段 merge | 中 | 移动端总览 |
锚点是编号,不是标题
与坑位索引同一套做法:标题后用 {#m1} 显式声明锚点,全站引用写 #m1。改标题随意,别改 {#mx};编号一旦发出就不复用、不重排。
一、布局与组件
M1. z-paging 高度塌陷:flex-1 必须配 min-h-0
现象:列表页只显示第一屏,往下滚不动;或者列表区域高度为 0,整页空白但 navbar 和搜索框都在。
根因:flexbox 子项的 min-height 默认是 auto,不会小于内容高度。flex-1 单独用在 z-paging 上时,容器高度由内容反推,z-paging 内部的 scroll-view 拿不到确定高度,滚动就失效了。
怎么办:父容器 h-screen flex flex-col overflow-hidden(全局类 .yd-page-container-paging,src/style/index.scss:42-43),z-paging 上写 class="min-h-0 flex-1"。这是全仓的既有约定,不是个别页面的写法:min-h-0 出现在 643 个 .vue 文件里。范本见 src/pages/bpm/components/todo-list.vue:2,10。
M2. 忘了 :fixed="false",z-paging 会盖住自定义 navbar
现象:自己写的 <wd-navbar> 或搜索栏被列表盖住,或者页面出现两层滚动条。
根因:z-paging 默认用 position: fixed 全屏铺满,它不知道页面上还有别的元素。
怎么办:加 :fixed="false",让它改为吃父容器高度(配合 M1)。全仓 367 个 .vue 用了 <z-paging>,:fixed="false" 出现 372 次——基本是「用了 z-paging 就一定会写」。
M3. @query 漏 catch → 永久转圈 + 未捕获 rejection
🔴 现象:页面一直转圈,控制台一条 Uncaught (in promise),用户看到的是「页面打不开」。
根因:移动端把「业务码非 0」从 resolve 改成了 reject(src/http/http.ts:186,注释「reject 替代原本的 resolve」)。所以后端返回错误时,await 一定抛。z-paging 的 @query 回调如果不 catch,既不会 complete()(于是一直转圈),也不会 complete(false)(于是没有错误态),rejection 还没人接。
怎么办:try 里成功走 pagingRef.value.complete(list),catch 里走 pagingRef.value.complete(false)。范本在 src/pages/message/index.vue:108-122。这一条是待修的 17 个异常页的主要形态之一——批量巡检时按「有没有 catch」筛,比按页面名筛准。
:auto="false" 是另一件事
src/pages-infra/form-design/run/index.vue 的 z-paging 写了 :auto="false"(全仓 13 处),目的是等渲染配置到位再发第一次查询,否则会在没有排序和查询条件的情况下先打一次 /page,第一页和后面几页的规则不一致。这与 M3 无关,别混着改。
M4. yd-* / wd-* / z-paging 不用 import,但 useToast 必须 import
现象:useToast is not defined;或者反过来,给 wd-button 补了一行 import 结果报重复声明。
根因:三条不同的解析链,覆盖范围不一样:
| 来源 | 谁解析 | 要不要 import |
|---|---|---|
wd-*(wot-design-uni)、z-paging | pages.config.ts 的 easycom | 不要 |
yd-*(自研) | easycom 规则 ^yd-(.*) → @/components/yudao-ui/yd-$1/yd-$1.vue | 不要 |
src/components/** 下的本地组件 | UniComponents(vite.config.ts:93-99) | 不要 |
src/hooks/** 下的函数 | AutoImport({ dirs: ['src/hooks'] })(vite.config.ts:155-160) | 不要 |
Vue / uni-app API(ref、computed、uni.*) | AutoImport({ imports: ['vue', 'uni-app'] }) | 不要 |
useToast | 来自 @wot-ui/ui/components/wd-toast,不在 src/hooks 里,也不在 src/types/auto-import.d.ts 里 | 要 |
怎么办:import { useToast } from '@wot-ui/ui/components/wd-toast'(范本 src/pages/message/index.vue:80)。判断标准很简单:只有 src/hooks/ 下的东西被自动导入,第三方包里的 hook 一律手动 import。
二、路由与页面栈
M5. 跳转失败一律提示「页面不存在」,会把排查带偏
🔴 现象:点工作台某个菜单弹「页面不存在」,于是去查 system_nav_node 的 route 拼写、查 src/pages.json 有没有这条路由——都查不出问题,因为路由是对的。
根因:src/pages/index/index.ts:196-198(switchTab)与 :204-206(navigateTo)的 fail 回调都是同一句 toast.show('页面不存在'),把所有失败原因都归成这一种。而 uni.navigateTo 的失败原因不止「路由不存在」,最典型的另一种是页面栈已满:uni-app 的 navigateTo 层级上限是 10 层,超过就 fail。
怎么办:先确认路由真的存在——用服务端驱动导航里那段脚本把 src/pages.json 的 764 条路由 dump 出来 grep。路由在的话,就是栈满了:往回退几层再进,或者把这个入口改成 redirectTo(替换当前页,不压栈)。
想改提示语的话
把 fail 改成打印 err.errMsg 更有用(uni 会给出 navigateTo:fail page "xxx" is not found 或 navigateTo:fail webview count exceed limit 这类具体原因),但要同步改两处——switchTab 和 navigateTo 各一份。
M6. tabbar 页不能 navigateTo,switchTab 又不接受 query
现象:跳 tabbar 页白跳(页面不动),或者跳过去了但参数丢了。
根因:uni-app 的两条硬约束——navigateTo 不能跳 tabbar 页;switchTab 的 url 不接受 query 参数。
怎么办:先判类型再选 API,参数走 globalData 中转。既有实现在 src/pages/index/index.ts:188-208:isTabBarPage(path)(src/tabbar/config.ts:151)为真时,若 query 非空先 setTabParams(query)(src/utils/url.ts)再 uni.switchTab({ url: path });否则 uni.navigateTo({ url: menu.url })(注意这一支传的是带 query 的完整 url)。同样的分支在 navigateBackPlus(src/utils/index.ts:220-241)与 src/utils/index.ts:199-208 也各有一份。
M7. 4 个 tabbar 项共用同一个 route,按 route 去重会误判
现象:巡检脚本 / 埋点 / 权限校验按 route 统计页面,发现「审批」下 4 个入口只算 1 个页面;或者按 route 建的映射表出现键冲突。
根因:本地导航树里 4 个 tabbar 项(我的流程 / 待办 / 已办 / 抄送)的 route 全是 /pages/bpm/index,靠 query 区分。在线表单也是同一套路:所有表单入口的 route 都是 /pages-infra/form-design/run/index,只有 query 里的 code 不同(后端 FormDesignServiceImpl#buildNavNodeConfig 里 APP_NAV_ROUTE 是常量)。
怎么办:任何「一个入口一个键」的场景,键要用 route + query 而不是 route。实测清单见服务端驱动导航与在线表单在移动端。
M8. 改 pages.config.ts / tabbar/config.ts 必须重启 dev server
现象:加了一个页面 / 改了 tabbar,浏览器里没变化,或者报「page not found」。
根因:这两个文件是生成 src/pages.json 的输入,而 pages.json 是产物(且被 gitignore,.gitignore:43)。HMR 覆盖不到它。vite.config.ts 只给 vite.config.ts 自己配了 ViteRestart(:161-164),其余配置文件改了都不会自动重启。
怎么办:Ctrl-C 后重跑 pnpm dev。改 env/*、manifest.config.ts、src/tabbar/config.ts 同理。
三、构建与产物
M9. env/.env 里的 VITE_ 值会被内联进生产包
🔴 现象:生产环境的登录页自动填好了用户名和密码。
根因:env/.env 是所有 mode 的公共基底,production 也加载它;而 Vite 会把 import.meta.env.VITE_* 静态内联进产物,运行期没有任何开关能摘掉。原先 VITE_APP_DEFAULT_LOGIN_{TENANT_ID,USERNAME,PASSWORD} 就写在这里,消费点是 src/pages-core/auth/login.vue:123-124(无条件填进 formData)与 components/tenant-picker.vue:50。
实测:2026-08-30 那次 H5 构建的产物里能直接搜到明文——
dist/build/h5/assets/pages-core-auth-login.Dx0p7Pwd.js
… t=O({username:"admin",password:"admin123",captchaVerification:""}) …怎么办:已修(提交 48bf755a)。三项默认值移到 .env.development 与 .env.test(都是开发态 mode),.env 原位留了注释说明为什么不能挪回去。用 Vite 自己的 loadEnv 逐 mode 验过:development / test 仍能取到,production 三者均为 undefined。
推广开来看:任何 VITE_ 开头的值都要当作「会出现在公开产物里」对待。自查办法见多端构建与发布——grep -rl "要查的值" dist/build/h5/assets/。
M10. sync-manifest 曾把 app 产物路径写死成 dist/dev/app
现象:dist/ 下多了一个空的 dev/app/ 目录——连 H5 构建也会造出来。仓库里那个 2026-08-30 造的就是。
根因:vite-plugins/sync-manifest-plugins.ts 的 writeBundle 把目标写死成 ./dist/dev/app/manifest.json,而且 mkdirSync 在「有没有 plugins 可同步」的判断之前无条件执行。第二层后果更严重:uni build -p app 的产物在 dist/build/app,插件却往 dist/dev/app 写,同步结果永远进不了生产产物。
怎么办:已修(提交 5f653c4b)。非 app 平台直接返回;build / dev 按 NODE_ENV 推导(与同目录 open-dev-tools.js:26,99 早就在用的口径一致);没有 app-plus.distribute.plugins 时在建目录之前就返回。改完用 esbuild 转出插件、逐组合调 writeBundle.handler 验过 7 例。
当前是否有实际影响:没有——manifest.config.ts 从未生成过 app-plus.distribute.plugins 字段(实测为 undefined),所以写入分支从来没执行过。要引入本地原生插件时才会用到,届时按多端构建与发布的 App 一节第 4 步配齐。
M11. 约 52% 的 H5 产物是用户点不到的分包
现象:H5 产物 assets/ 13.4 MB、1242 个 js 文件,首屏加载偏重。
根因:vite.config.ts:104-121 的 UniPages({ subPackages }) 列了全部 16 个分包目录,其中 11 个(crm/erp/im/iot/mall/member/mes/mp/pay/wms/statistics)在后端没有对应模块——ainb 已把这些模块整体删除,导航树 system_nav_node 也不给它们建节点。它们编译进包,但用户永远点不到。
实测(基于 2026-08-30 的产物,见多端构建与发布):11 个不可达分包的 pages-* 分块合计 6,408 KB;另有只被 pages-statistics 引用的 yd-chart(echarts,560 KB)与只被 pages-im 引用的 livekit 分块(512 KB,已计入前者)。echarts 与 livekit-client 的 import 点全部落在这两个分包内,已逐个 grep 确认。合计约 7.0 MB / 13.4 MB ≈ 52%。
怎么办:这属于产品决策而非构建配置。目录留着是为了将来后端补上模块时能直接接回来。真要瘦身:从 subPackages 数组里去掉不要的目录,同步改 pages.config.ts 与 src/tabbar/config.ts 里的引用,路由数会从 764 降到约 190。先确认「不打算再要这些模块」。
M12. VITE_SHOW_SOURCEMAP 与 VITE_FALLBACK_LOCALE 都是空写
现象:改了这两个变量,产物没有任何变化。
根因:
VITE_SHOW_SOURCEMAP:三个 env 文件都设了(.env.development:6、.env.test:6、.env.production:6),但全仓唯一读它的地方是vite.config.ts:248那行被注释掉的代码。build.sourcemap恒定false(:246)。VITE_FALLBACK_LOCALE:manifest.config.ts:20解构了它、:31用它填locale,但任何 env 文件里都没定义。实测生成出来的src/manifest.json里locale字段整个不存在(undefined被JSON.stringify丢掉)。
怎么办:要 sourcemap 就改 vite.config.ts:246(注意会泄露未混淆源码,只建议对 test mode 开)。要固定语言就在 env/.env 里补 VITE_FALLBACK_LOCALE = 'zh-Hans'。或者干脆把这两处死代码删掉,别留下「看起来能配」的假象。
M13. versionName / versionCode 硬编码,App 端永远 1.0.0
现象:App「关于」页显示的版本永远是 1.0.0;Android 装新版本提示「不能覆盖安装」或「版本已存在」。
根因:manifest.config.ts:28-29 写死 'versionName': '1.0.0'、'versionCode': '100',既不读 package.json:4 的 version,也没有任何脚本递增它。versionCode 不递增,Android 会拒绝覆盖安装。
怎么办:发 App 前手工把这两个值改掉,或者让它们从 package.json 读(manifest.config.ts 里已经在用 loadEnv,再 readJson('./package.json') 即可)。H5 与小程序不受影响——H5 没有版本号概念,小程序上传版本由 --version= 参数决定(见 M15)。
M14. build:h5 会弹浏览器,build:mp 会拉开发者工具
现象:CI 上跑 pnpm build:h5,构建结束后多出一个浏览器进程;跑 pnpm build:mp,构建卡住或报找不到开发者工具。
根因:两个插件都会自动「弹东西」——
| 行为 | 触发条件 | 开关 |
|---|---|---|
visualizer 生成体积报告并 open: true | UNI_PLATFORM === 'h5' && mode === 'production'(vite.config.ts:173-180) | 没有环境变量开关,只能改代码或让 mode 不等于 production |
| 自动打开微信开发者工具 | SKIP_OPEN_DEVTOOLS !== 'true' 且 UNI_PLATFORM 含 mp 且首次构建(vite.config.ts:194-197 → scripts/open-dev-tools.js:97-113) | SKIP_OPEN_DEVTOOLS=true |
怎么办:CI 上至少设 SKIP_OPEN_DEVTOOLS=true(scripts/upload-weixin.js:142-150 就是这么做的)。开发者工具 CLI 路径通过 WECHAT_DEVTOOLS_CLI_PATH 配(env/.env:9,11 有 macOS 与 Windows 两个注释掉的示例),未配置时退回 open-dev-tools.js:43(macOS)/ :53(Windows)的平台默认路径;找不到只打印提示后返回,不会让构建失败。
M15. 小程序上传的默认版本号取自 yudao-version
现象:微信后台看到的版本号是 2026.07.0-snapshot。
根因:scripts/upload-weixin.js:62-69 的 readPackageVersion() 取 pkg['yudao-version'] || pkg.version || '1.0.0',而 package.json:6 的 "yudao-version": "2026.07.0-snapshot" 优先于 package.json:4 的 "version": "2026.07-snapshot"。这个键名是上游遗留,本项目刻意保留(它记录的是上游合并基线,见 docs/merge/),所以不能简单删掉。
怎么办:正式发布显式传 --version=1.0.0,或设 WECHAT_UPLOAD_VERSION。回退顺序见多端构建与发布。
M16. 56 个 dev/build 脚本里只有 3 个挂了 init-baseFiles 前置钩子
现象:手工删掉 src/pages.json 后直接 pnpm build:h5,报找不到页面 / 产物是空的。
根因:src/pages.json 与 src/manifest.json 都不入库(.gitignore:42-43),要先由 scripts/create-base-files.js 造桩、再由 UniPages / UniManifest 覆盖成真内容。但 npm/pnpm 的 pre<name> 钩子只对脚本名精确匹配生效:只有 predev、predev:app、predev:mp 三条,覆盖 pnpm dev / dev:app / dev:mp。dev:h5、dev:test、dev:prod、dev:mp-weixin、以及全部 25 条 build:* 都没有。
怎么办:目前不出问题,是因为 prepare(package.json:94)在 pnpm install 时已经跑过一次 init-baseFiles——兜底在安装期而不是构建期。踩空了就手工 pnpm init-baseFiles。
桩文件里的路由是不存在的
create-base-files.js 生成的 pages.json 桩只有两条路由,其中 pages/me/me 在本仓库不存在(真实的「我的」页在 pages-core 分包)。桩随后会被插件覆盖,所以当前无害;但任何在插件之前读 pages.json 的工具都会拿到一条 404 路由。
四、静态资源与外部服务
M17. staticUrl() 会把 VITE_STATIC_BASEURL 拼在 /static/ 前
现象:图片全部裂开,请求打到了一个陌生域名。
根因:src/utils/download.ts:335-343 的 staticUrl(path) 对非 http(s) / blob: / data: 的入参,一律返回 ${VITE_STATIC_BASEURL}${normalizedPath}。而这个变量原先指向上游演示域名 http://test.yudao.iocoder.cn,于是 /static/qrcode.png 被拼成了上游域名下的路径。
怎么办:已修(提交 e1e4be57)。env/.env:43 与 .env.production:17 都改成空串 '',让 /static/ 走应用自身。要指向 CDN 就填完整域名且不要带 /static 后缀——后缀由入参自己带。
注意:/static/ 是 uni-app 的约定目录,只有放在 src/static/ 下的文件才会被原样复制进产物。放在别处的图片即使路径写对了也不会被打包。
M18. 三个身份标识仍是上游的
🔴 现象:pnpm upload:mp-weixin 会把包上传到上游的小程序账号(或者因私钥不匹配而失败);HBuilderX 云打包报 appid 不属于当前账号;地图组件不显示。
根因:env/.env 里三项还是上游演示值——
| 变量 | 当前值 | 谁在读 |
|---|---|---|
VITE_WX_APPID(:5) | wx63c280fe3248a3e7 | manifest.config.ts:127 → mp-weixin.appid;scripts/upload-weixin.js:156-160(缺失时 throw) |
VITE_UNI_APPID(:4) | __UNI__D1E5001 | manifest.config.ts:26 → appid,HBuilderX 云打包按它认账号 |
VITE_BAIDU_MAP_KEY(:53) | Y2aJXiswwPxy6mwFs1z9c7U5gwX9WfUN | 地图组件;明文 AK 已进仓库也会进产物 |
怎么办:换成自己的值。VITE_WX_APPID 不能简单删掉(上传脚本会 throw),必须替换。DCloud appid 要在 HBuilderX 里重新获取。百度地图 AK 要在百度开放平台申请,并且配置域名白名单,否则即使换对了也可能被拒。
M19. 请求加密开关是 true,但没有任何调用方传 isEncrypt
🔴 现象:以为接口是加密的,抓包发现全是明文 JSON——而 env/.env:75 明明白白写着 VITE_APP_API_ENCRYPT_ENABLE = true。
根因:VITE_APP_API_ENCRYPT_ENABLE 只是能力开关(src/utils/encrypt.ts:10,在 :157 与 :193 各挡一次)。真正的闸门是 src/http/interceptor.ts:83 的 if (options.isEncrypt)——逐请求开启。而全仓库 isEncrypt 只出现在三个地方:类型声明 src/http/types.ts:13、闸门 interceptor.ts:83、以及响应侧另一个变量 isEncryptResponse(src/http/http.ts:85-86)。没有任何调用方传过 isEncrypt: true。
怎么办:现状是「请求加密从不触发;响应解密只在服务端下发加密头时才走」。要真启用,得先解决一个前提问题:env/.env:78-79 的两把 AES 密钥是明文写在仓库里的,会被内联进每一端的产物(同 M9 的机制)。启用前必须换成运行期下发的方案,否则加密等于没加。
五、调试与类型
M20. eruda 只在 h5 + development 注入
现象:pnpm dev:test / dev:prod 打开页面,右下角没有调试面板按钮,以为是坏了。
根因:vite.config.ts:189-191 的 vitePluginEruda({ open: UNI_PLATFORM === 'h5' && mode === 'development' })。dev:test 的 mode 是 test、dev:prod 是 production,都不满足。
怎么办:要在 test/prod mode 下调试,改 vite.config.ts:190 的条件。或者直接用浏览器 devtools(H5 端本来就够),eruda 主要是给真机 H5 用的。
M21. src/types/* 大部分不入库,新克隆 type-check 会报找不到模块
现象:全新克隆跑 pnpm type-check,报 Cannot find module '@/pages.json'。
根因:.gitignore:34 是 src/types/*,只有 4 个例外被显式放行(:36,38,40,41)。实测:
| 文件 | 是否入库 | 谁生成 |
|---|---|---|
src/types/auto-import.d.ts | ✓ TRACKED | AutoImport(vite.config.ts:157) |
src/types/local-components.d.ts | ✓ TRACKED | UniComponents(vite.config.ts:98) |
src/types/uni-pages.d.ts | ✗ not tracked | UniPages(vite.config.ts:122) |
src/types/env.d.ts | ✗ not tracked | — |
src/pages.json / src/manifest.json | ✗ not tracked(.gitignore:42-43) | UniPages / UniManifest |
而 src/utils/index.ts:3 直接 import { pages, subPackages } from '@/pages.json',HOME_PAGE(:185)也依赖它。
怎么办:先 pnpm install(prepare 会跑 init-baseFiles 造桩),再跑一次 pnpm dev 让插件生成真文件,然后才 pnpm type-check。这与 Web 仓的 F2(auto-*.d.ts 只由 dev server 生成)是同一类问题,但移动端更严重:Web 仓的 auto-*.d.ts 只是类型缺失,移动端是 pages.json 这个运行时也要用的模块根本不存在。
新增组件后要提交 local-components.d.ts
src/types/local-components.d.ts 是入库的,所以在 src/components/ 下新增组件后,要跑一次 dev 让它重新生成,并把这个文件一起提交,否则别人 type-check 会报找不到组件。反过来,uni-pages.d.ts 不入库,别去提交它。
六、工程协作
M22. src/components/yudao-ui/ 目录名刻意不改
现象:看着像上游残留,想「顺手改成 enlev-ui」。
根因:这个目录被两处双重引用——easycom 规则 ^yd-(.*) → @/components/yudao-ui/yd-$1/yd-$1.vue(pages.config.ts),以及自动生成的组件类型声明。改名牵连 56 个文件的 import,且没有任何收益。
怎么办:不改。目录名不代表归属变更;归属口径写在 LICENSE、licenses/ 与 THIRD-PARTY-NOTICES.md 里。同理,仓库内的 docs/ 是 unibest 原版站点(含上游打赏/赞助页),属遗留但不要顺手删(不可逆,且 docs/merge/ 的合并记录在同一目录下)。
M23. Android 权限清单整段继承上游
现象:App 上架被应用商店拒审,理由是「申请了与功能无关的权限」。
根因:manifest.config.ts:66-82 的 15 条权限整段来自上游,含 READ_LOGS、WRITE_SETTINGS、READ_PHONE_STATE、GET_ACCOUNTS、MOUNT_UNMOUNT_FILESYSTEMS 这类宽权限,而本项目的 App 端功能面并没有用到它们。
怎么办:当前 App 端未打包发布,所以还没有实际后果。真要上架前必须逐条核对,只保留有对应功能的权限。同一段里还有 minSdkVersion: 21 / targetSdkVersion: 30(:63-64)——targetSdk 30 低于主流商店当前要求,也要一并抬。
M24. unibest 上游合并要按月推进,不能整段 merge
现象:想跟上 unibest 上游,git merge unibest/main 后冲突面铺满整个工程。
根因:本仓库是 unibest 4.1.0 骨架 + 芋道移动端业务页的混合体,且已经整体改名。上游的工程配置(vite.config.ts、pages.config.ts、src/tabbar/、src/router/interceptor.ts)与本项目定制高度重叠,整段 merge 会把定制冲掉。
怎么办:照仓库自己的 docs/merge/README.md 走——按月份推进,每个月内部再按「完全合并 / 部分合并 / 忽略」分类处理,不整段 merge 也不整段 cherry-pick。月度记录在 docs/merge/2025-12.md 到 2026-05.md(6 个月,共 1037 行)。已明确的口径:先做低风险小改(微信小程序 baseUrl、devtools CLI path、tabBar fallback、import-sort、native plugin copy 日志);tabbar 同步修复单独做专项,它不是低风险项;CLI、demo、模板文档、changesets、版本号类改动默认忽略。
未证实
- 10 层页面栈上限(M5)本机未实测触发:uni-app 官方文档写明
navigateTo的层级上限是 10,fail回调会拿到webview count exceed limit之类的errMsg。本仓库源码里没有任何针对该上限的处理或注释,也没有复现过——M5 的已证实部分是「所有 fail 都提示同一句『页面不存在』」(源码锚点确凿),栈满是其中一种会被误报的原因。 - M11 的体积数字基于 2026-08-30 的过期产物:仓库里的
dist/比 HEAD 早两个月,<title>还是改名前的旧品牌。比例(约 52%)应当稳定,绝对值会变。 - M23 的商店拒审是行业常识推断,本项目没有真的提交过 App 审核。
docs/merge/记录的上游合并结论未复核:那 6 个月度文档是上一轮工作的产出,本轮只引用、没有重新对上游做一次 diff。
相关页面
- 移动端总览:这个仓库是什么、哪些分包不可达(M11 的前提);
- 移动端环境准备与启动:装依赖、三条 dev 路径(M16、M20、M21);
- 工程结构与约定式路由:
pages.json的生成机制、easycom 规则(M4、M8、M21、M22); - 请求封装与登录态:业务码为什么 reject、加解密开关怎么被消费(M3、M19);
- 移动端页面开发范式:能直接抄的列表页骨架(M1–M4);
- 服务端驱动导航:system_nav_node 全链路:
route/query/is_tabbar的语义(M5–M7); - 在线表单在移动端:一个运行时页承载所有表单(M7);
- 多端构建与发布:mode 与产物、上传链路(M9–M18);
- 坑位索引:跨端通用的 29 条(编号
A1–F6)。
