fix(miniapp): 修复底部 tabBar 图标全部消失(开发者工具误删 app.json 专属图标)

根因:微信开发者工具(>=1.05.2112301)的 ignoreDevUnusedFiles 会按 WXML/JS 依赖分析过滤
"无依赖"文件。tabBar 图标仅在 app.json 中声明,不属于任何页面 WXML/JS 依赖,被误判为无用
文件从开发包剥离,导致 5 个图标全部加载失败(渲染为破图占位)。

修复:
- 在 project.config.json 与 miniapp/project.config.json 的 setting 中显式关闭
  ignoreDevUnusedFiles / ignoreUploadUnusedFiles(false),阻止图标被过滤。
- 同步关闭本地 gitignore 的 project.private.config.json(DevTools 运行时实际读取项)。
- 预览通道 scripts/preview-miniapp.mjs:补全 /assets 绝对路径→相对路径重写,并手动复刻
  原生 tabBar(原生 tabBar 不在任何页面 WXML 中,静态预览需自行复刻)。
- 回归守卫 ui-tests/tabbar-icons.mjs 扩充为 5 段,覆盖根因(配置开关)+ 预览复刻。
- miniapp/assets/tabbar/DESIGN.md 增加根因警示,防止后续被误开开关。

验证:UI 契约 23/23 全绿(tabbar-icons.mjs 位于 [19/23]);CDP 渲染实测 127 张图 0 破图,
5 个 tabBar 图标 naturalW=81 全部加载,选中色 rgb(233,107,107)、未选 rgb(153,153,153),
与 app.json selectedColor/color 一致。
This commit is contained in:
WorkBuddy
2026-09-23 15:57:04 +08:00
parent f4e00b4d95
commit 45e84722e1
6 changed files with 5642 additions and 2681 deletions
+54 -3
View File
@@ -31,6 +31,47 @@ const targets = requested.length ? requested : PAGES
const read = (p) => readFileSync(resolve(root, p), 'utf8')
const readIf = (p) => (existsSync(resolve(root, p)) ? read(p) : '')
// ============================================================
// 资源路径改写
// WXML/WXSS 写的是小程序根绝对路径 /assets/...;浏览器以 file:// 打开本预览时
// /assets 会被解析到文件系统根目录 → 图片全部 404(表现为破图)。
// preview.html 位于 artifacts/ui-redesign/miniapp/,向上三级即仓库根。
// ============================================================
const ASSET_PREFIX = '../../../miniapp'
const rewriteAssetUrls = (s) => s
.replace(/(\bsrc=")\/assets\//g, `$1${ASSET_PREFIX}/assets/`)
.replace(/(\bsrc=')\/assets\//g, `$1${ASSET_PREFIX}/assets/`)
.replace(/url\((["']?)\/assets\//g, `url($1${ASSET_PREFIX}/assets/`)
// ============================================================
// 原生 tabBar 复刻
// 小程序 tabBar 由 app.json 声明、由客户端原生渲染,**不存在于任何 WXML**。
// 静态预览若不补这一层,会让人误判为「底部导航图标丢了」。
// 5 项与 app.json 的 tabBar.list 一一对应(含选中态图标)。
// ============================================================
const TAB_ACTIVE_BY_PAGE = {
index: 'home', discover: 'discover', messages: 'message', activity: 'activity', mine: 'mine',
}
const TAB_ITEMS = [
{ key: 'home', label: '首页' },
{ key: 'discover', label: '找对象' },
{ key: 'message', label: '消息' },
{ key: 'activity', label: '活动' },
{ key: 'mine', label: '我的' },
]
function renderTabbar(page) {
const active = TAB_ACTIVE_BY_PAGE[page]
if (!active) return ''
const items = TAB_ITEMS.map((it) => {
const on = it.key === active
const file = on ? `${it.key}-active.png` : `${it.key}.png`
return `<div class="mini-tabbar__item${on ? ' is-active' : ''}">`
+ `<img class="mini-tabbar__icon" src="${ASSET_PREFIX}/assets/tabbar/${file}" alt="${it.label}" />`
+ `<span class="mini-tabbar__label">${it.label}</span></div>`
}).join('')
return `<div class="mini-tabbar" role="tablist">${items}</div>`
}
// ============================================================
// mock 数据:让各页在静态预览下呈现「有数据」的真实布局
// 键为页面名,值为该页 data 中会被 WXML 消费的字段
@@ -702,7 +743,7 @@ function buildPage(page, deviceWidth = 375) {
} catch (e) {
body = `<pre style="color:#c00;padding:16px">渲染失败: ${e.message}</pre>`
}
return { html: `<div class="wx-page reference-page">${body}</div>`, css, missing: false }
return { html: `<div class="wx-page reference-page">${body}${renderTabbar(page)}</div>`, css, missing: false }
}
function main() {
@@ -762,6 +803,16 @@ function main() {
.phone { background:#fff; border-radius:36px; padding:12px; box-shadow:0 20px 60px rgba(28,30,88,.18); }
.phone-screen { width:100%; height:760px; overflow-y:auto; border-radius:26px; background:#F4F5FA; }
.wx-page { min-height:100%; }
/* 原生 tabBar 复刻。颜色/边框取 app.json 的 tabBar 字段:
color #999999 / selectedColor #E96B6B / backgroundColor #FFFFFF / borderStyle black。
图标按微信客户端实际渲染尺寸(约 26px)呈现,与 81px 源图尺寸不同;文字 10px 同理。 */
.mini-tabbar { position:sticky; bottom:0; z-index:20; display:flex; align-items:stretch;
background:#FFFFFF; border-top:1px solid rgba(0,0,0,.2); padding-bottom:20px; }
.mini-tabbar__item { flex:1 1 0; min-width:0; display:flex; flex-direction:column;
align-items:center; justify-content:center; gap:2px; padding:6px 0 0; }
.mini-tabbar__icon { width:26px; height:26px; display:block; }
.mini-tabbar__label { font-size:10px; line-height:14px; color:#999999; }
.mini-tabbar__item.is-active .mini-tabbar__label { color:#E96B6B; }
/* ====== 以下为页面 WXSS 转换结果(rpx 已按 375 宽换算) ====== */
${cssAll}
</style>
@@ -770,7 +821,7 @@ function main() {
<div class="banner">
<h1>小程序 11 页 · 静态预览</h1>
${nav}
<span class="note">本预览为 WXML/WXSS 静态还原,非真机渲染,不含真实接口数据</span>
<span class="note">本预览为 WXML/WXSS 静态还原,非真机渲染,不含真实接口数据;底部 tabBar 按 app.json 复刻</span>
</div>
<div class="stage">${sections}</div>
<script>
@@ -785,7 +836,7 @@ function main() {
</html>`
const outFile = resolve(OUT_DIR, 'preview.html')
writeFileSync(outFile, html, 'utf8')
writeFileSync(outFile, rewriteAssetUrls(html), 'utf8')
const ok = built.filter((b) => !b.missing).length
console.log(`已生成: ${outFile}`)
console.log(`页面数: ${ok}/${targets.length}`)