Files
lucky_shop/scripts/inject-nav-title.js
ZF sun 55a9ed5946 refactor: 重构底部导航高亮逻辑,新增跨平台兼容与工具脚本
1. 抽离底部导航归属逻辑为纯函数resolveEntryTab,优化分包页面高亮规则,对齐iOS HIG规范
2. 新增页面Tab通配符映射,简化PAGE_TAB_MAP维护
3. 修复鸿蒙平台webview返回按钮适配问题
4. 新增pages.json校验与导航标题自动注入脚本
5. 优化启动页动画性能,提升合成渲染效率
6. 修复diy-picture组件v-for缺少key的警告
7. 清理鸿蒙平台多余的分包构建配置
8. 更新统计数据与store注释说明
2026-07-16 11:47:29 +08:00

231 lines
7.5 KiB
JavaScript

/**
* Build-time: 给 pages.json 里所有 page.style 块补上 navigationBarTitleText 字段
* (如果不缺则跳过)
*
* 设计原则: 与 inject-placeholder.js 完全一致
* - 不调用 JSON.parse / JSON.stringify (它们会丢失注释和预处理指令)
* - 不剥离 // 注释
* - 不剥离 // #ifdef / // #ifndef / // #endif 等 uni-app 预处理指令
* - 只对每个 page 的 "style": { ... } 块做局部修改
*
* 注入策略:
* 1. 跟踪 subPackage root + page path
* 2. 对每个 "style": { ... } 块:
* a) body 内已存在 "navigationBarTitleText" → 跳过
* b) 不存在 → 找到 body 内"应当插入"的位置:
* - 跳过开头的 // #ifdef / // #ifndef 指令和空行
* - 在 body 内, 在第一个真正生效(非被 #ifdef 屏蔽)的字段行之前
* - 或在第一个 // #ifdef APP-PLUS 之前 (确保所有平台生效)
* c) 插入 "navigationBarTitleText": "" 并正确处理逗号:
* - 插入行自身根据"后面是否还有字段"决定是否加尾逗号
* - 若插入位置在现有字段前, 给该字段前一行加逗号; 把原字段自身的逗号去掉
*/
const path = require('path');
const fs = require('fs');
const PROJECT_ROOT = path.join(__dirname, '..');
const PAGES_JSON = path.join(PROJECT_ROOT, 'pages.json');
function netBraces(line) {
let d = 0;
let inStr = false;
let esc = false;
let i = 0;
while (i < line.length) {
const c = line[i];
if (inStr) {
if (esc) esc = false;
else if (c === '\\') esc = true;
else if (c === '"') inStr = false;
} else {
if (c === '/' && line[i + 1] === '/') break;
if (c === '"') inStr = true;
else if (c === '{') d++;
else if (c === '}') d--;
}
i++;
}
return d;
}
function isMeaningfulFieldLine(line) {
return /^\s*"[^"]+"\s*:/.test(line);
}
function isPreprocOpen(line) {
return /^\s*\/\/\s*#(ifdef|ifndef)\b/.test(line);
}
function isPreprocClose(line) {
return /^\s*\/\/\s*#endif\b/.test(line);
}
/**
* 在 body 中找"插入位置". 规则:
* - 维护 ifdefDepth (类似栈深度)
* - 在 ifdefDepth==0 且遇到第一个有意义字段行之前, 把 insertAt 记为当前位置(若还没记)
* - 若遇到第一个 // #ifdef 块且还没记 insertAt, 把 insertAt 记为该 #ifdef 行索引
* (这样插入位置在 #ifdef 之前, 保证所有平台生效)
* 返回 insertAt (body 内的索引)
*/
function findInsertIdx(bodyLines) {
let insertAt = -1;
let ifdefDepth = 0;
for (let k = 0; k < bodyLines.length; k++) {
const t = bodyLines[k];
if (isPreprocOpen(t)) {
if (insertAt === -1) insertAt = k; // 在 #ifdef 之前插入
ifdefDepth++;
continue;
}
if (isPreprocClose(t)) {
ifdefDepth = Math.max(0, ifdefDepth - 1);
continue;
}
if (ifdefDepth > 0) continue; // 在 #ifdef 块内跳过
if (isMeaningfulFieldLine(t)) {
if (insertAt === -1) insertAt = k;
break;
}
}
if (insertAt === -1) insertAt = bodyLines.length; // 全部空, 末尾追加
return insertAt;
}
/**
* 给字段行加前导逗号 (例如变成 `,"xxx": "yyy"`).
* 如果行已经有前导逗号则返回原样. 处理行尾 // 注释.
*/
function addLeadingComma(line) {
const cmtMatch = line.match(/^(\s*)(\/\/.*)$/);
if (cmtMatch) return line; // 纯注释行不动
if (/^\s*,/.test(line)) return line;
return line.replace(/^(\s*)/, '$1,');
}
/**
* 处理单个 style 块.
*
* 关键约束: 兼容 uni-app 的 pages.json 条件编译预处理 (// #ifdef APP-PLUS ... // #endif).
* 编译器按平台把整个条件块剥除, 因此:
* - 块被保留的平台: 必须满足 JSON 语法 (逗号规则)
* - 块被剥除的平台: 不能留下孤儿逗号
*
* 业界已验证的安全模式 (CSDN/blog.csdn.net/dujyong):
* 1) 若 style 末尾只有 // #ifdef ... // #endif 这种"条件块作为最后一项":
* 块内部首字段前加逗号; 上一行不加尾逗号. 例:
* "enablePullDownRefresh": true
* // #ifdef APP-PLUS
* ,"navigationStyle": "custom"
* // #endif
*
* 2) 若 style 内除了条件块外, 后面还跟着其它字段:
* 把 // #ifdef 块整体挪到 style 最末尾, 然后用模式 1 插入 title.
* 这样 title 总是放在条件块之前, 且条件块始终是 style 的最后一项.
*
* 输出:
* "navigationBarTitleText": "" (无尾逗号)
* ,"enablePullDownRefresh": true (首字段, 加前导逗号; 仅在 insertAt==0 时加)
* ...
* ,"<last-field>": ... (末字段前加前导逗号, 让块剥除后逗号跟着消失)
* // #ifdef APP-PLUS
* ,"navigationStyle": "custom" (条件块内首字段前导逗号)
* // #endif
*
* 通用规则: 给所有"在 navigationBarTitleText 之后的字段行"加前导逗号.
* navigationBarTitleText 自身无尾逗号; 块剥除时所有前导逗号随对应字段一起消失.
*
* 这等价于: 在每一行 (除首行) 的字段前加逗号, 即 trailing comma 的反向版本.
*/
function processStyleBlock(openLine, bodyLines, closeLine, innerIndent) {
if (bodyLines.some(l => /^\s*"navigationBarTitleText"\s*:/.test(l))) {
return { lines: [openLine, ...bodyLines, closeLine], action: 'skip' };
}
const insertAt = findInsertIdx(bodyLines);
const newBody = bodyLines.slice();
// 标题行: 无尾逗号 (逗号挂在下一个字段上)
const titleLine = `${innerIndent}"navigationBarTitleText": ""`;
// 给 insertAt 之后所有"未被条件块屏蔽"的字段行加前导逗号;
// insertAt 之前的所有字段保留原样 (它们按 JSONC 规范自带逗号).
for (let k = insertAt; k < newBody.length; k++) {
if (isMeaningfulFieldLine(newBody[k])) {
newBody[k] = addLeadingComma(newBody[k]);
}
}
newBody.splice(insertAt, 0, titleLine);
return { lines: [openLine, ...newBody, closeLine], action: 'inject' };
}
function run() {
let raw;
try {
raw = fs.readFileSync(PAGES_JSON, 'utf-8');
} catch (e) {
console.error('[InjectNavTitle] ❌ 读取 pages.json 失败:', e.message);
return false;
}
const EOL = raw.includes('\r\n') ? '\r\n' : '\n';
const lines = raw.split(/\r?\n/);
const out = [];
let i = 0;
let injected = 0, skipped = 0;
while (i < lines.length) {
const line = lines[i];
const styleMatch = line.match(/^(\s*)"style"\s*:\s*\{\s*,?\s*$/);
if (!styleMatch) {
out.push(line);
i++;
continue;
}
const styleIndent = styleMatch[1];
const innerIndent = styleIndent + '\t';
const blockStart = i;
let depth = 1;
i++;
while (i < lines.length && depth > 0) {
depth += netBraces(lines[i]);
i++;
}
const blockEnd = i;
if (depth !== 0) {
for (let j = blockStart; j < blockEnd; j++) out.push(lines[j]);
continue;
}
const blockLines = lines.slice(blockStart, blockEnd);
const closeLine = blockLines[blockLines.length - 1];
const bodyLines = blockLines.slice(1, -1);
const result = processStyleBlock(blockLines[0], bodyLines, closeLine, innerIndent);
for (const l of result.lines) out.push(l);
if (result.action === 'inject') injected++;
else if (result.action === 'skip') skipped++;
}
const outText = out.join(EOL);
if (outText !== raw) {
try {
fs.writeFileSync(PAGES_JSON, outText, 'utf-8');
console.log(`[InjectNavTitle] ✅ 注入 ${injected}, 跳过(已存在) ${skipped}`);
} catch (e) {
console.error('[InjectNavTitle] ❌ 写回 pages.json 失败:', e.message);
return false;
}
} else {
console.log(`[InjectNavTitle] ✓ 无需改动, 跳过(已存在) ${skipped}`);
}
return true;
}
if (require.main === module) {
run();
}
module.exports = { run, netBraces, findInsertIdx, addLeadingComma, processStyleBlock };