1. 抽离底部导航归属逻辑为纯函数resolveEntryTab,优化分包页面高亮规则,对齐iOS HIG规范 2. 新增页面Tab通配符映射,简化PAGE_TAB_MAP维护 3. 修复鸿蒙平台webview返回按钮适配问题 4. 新增pages.json校验与导航标题自动注入脚本 5. 优化启动页动画性能,提升合成渲染效率 6. 修复diy-picture组件v-for缺少key的警告 7. 清理鸿蒙平台多余的分包构建配置 8. 更新统计数据与store注释说明
231 lines
7.5 KiB
JavaScript
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 }; |