版本冻结 v0.2.0,固化 2026-09-24 本轮全部变更。 admin-web: - 修复旧物善用管理页缺失样式类 .workspace-view / .view-header / .table-wrap, 此前页面无内边距导致内容被侧边栏遮挡、窄屏表格无横向滚动 - 修复服务统计页缺失样式类 .dashboard-grid / .dashboard-card / .stat-row, 此前 8 个指标卡退化为裸文本堆叠 - 补齐未定义的 --shadow-card 变量(财务编辑面板阴影此前失效) - 侧边栏 13 项导航由手写重复改为 NAV_ITEMS 数据驱动,并按业务顺序重排 - AdminIcon 补齐 coins / phone / volunteer / flag / calendar / recycle 语义图标 - 侧边栏行高与间距压缩使 13 项一屏放下,滚动条隐藏 - 小于等于 760px 断点导航由 display:none 改为横向滚动,移动端恢复可切换 api: - prisma.config.ts 显式加载仓库根 .env,修复 prisma generate / migrate 取不到 DATABASE_URL - dev / bootstrap:admin / test:integration 增加 tsx --env-file-if-exists=../../.env miniprogram: - custom-tab-bar 选中态与申请表单分步流程抽为可测纯函数并补充单测 - 页面完整性门禁脚本增强 工程: - 版本统一升位 0.1.0 到 0.2.0(根与 4 个 workspace 包) - .gitignore 增加 .pnpm-store/、.workbuddy/、微信开发者工具私人配置 与仓库根目录误生成的工程配置,避免本地产物与机器相关信息入库 验证:pnpm lint 通过、pnpm test:unit 19 个用例通过、miniprogram typecheck 通过、 admin-web typecheck 与 build 通过、pnpm install --frozen-lockfile 未改写 lockfile、 无头浏览器端到端登录复验通过、待提交内容密钥扫描无命中。 说明:本轮定位并排除了一起 Prisma 生成目录残缺事故——API 开发进程与并发的 prisma generate 争抢同一输出目录,Windows 文件锁导致只生成 34/56 个模型文件, 使 TS 全量类型解析失败并产生数千条 lint 报错;停止竞争进程后干净重新生成即恢复。 该生成目录已被 .gitignore 忽略,不进入版本库。
58 lines
1.9 KiB
JavaScript
58 lines
1.9 KiB
JavaScript
import { existsSync, readFileSync } from 'node:fs';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
|
|
const projectRoot = resolve(import.meta.dirname, '..');
|
|
const miniRoot = join(projectRoot, 'miniprogram');
|
|
const app = JSON.parse(readFileSync(join(miniRoot, 'app.json'), 'utf8'));
|
|
const allowedTags = new Set([
|
|
'block',
|
|
'button',
|
|
'checkbox',
|
|
'checkbox-group',
|
|
'image',
|
|
'input',
|
|
'label',
|
|
'picker',
|
|
'rich-text',
|
|
'scroll-view',
|
|
'switch',
|
|
'text',
|
|
'textarea',
|
|
'view',
|
|
]);
|
|
const failures = [];
|
|
|
|
for (const page of app.pages) {
|
|
const base = join(miniRoot, page);
|
|
for (const extension of ['.ts', '.json', '.wxml', '.wxss']) {
|
|
if (!existsSync(`${base}${extension}`)) failures.push(`${page}${extension} 缺失`);
|
|
}
|
|
const wxml = readFileSync(`${base}.wxml`, 'utf8');
|
|
if (!wxml.trim()) failures.push(`${page}.wxml 为空`);
|
|
for (const match of wxml.matchAll(/<\/?([A-Za-z][A-Za-z0-9-]*)/g)) {
|
|
if (!allowedTags.has(match[1])) failures.push(`${page}.wxml 使用不支持标签 <${match[1]}>`);
|
|
}
|
|
for (const match of wxml.matchAll(/<([A-Za-z][A-Za-z0-9-]*)\b([^>]*)>/g)) {
|
|
const attributes = match[2];
|
|
if (/\bwx:(?:elif|else)\b/.test(attributes) && /\bwx:for\b/.test(attributes)) {
|
|
failures.push(
|
|
`${page}.wxml 的 <${match[1]}> 不能同时使用 wx:elif/wx:else 与 wx:for,请用条件 block 包裹循环`,
|
|
);
|
|
}
|
|
}
|
|
const source = readFileSync(`${base}.ts`, 'utf8');
|
|
for (const match of source.matchAll(
|
|
/wx\.(?:navigateTo|redirectTo)\(\{\s*url:\s*[`'"]([^`'"]+)/g,
|
|
)) {
|
|
const target = match[1].split('?')[0].replace(/^\//, '');
|
|
if (target.includes('${')) continue;
|
|
if (!app.pages.includes(target)) failures.push(`${page}.ts 路由未注册:${target}`);
|
|
}
|
|
}
|
|
|
|
if (failures.length) {
|
|
console.error(failures.join('\n'));
|
|
process.exit(1);
|
|
}
|
|
console.log(`小程序页面完整性检查通过:${app.pages.length} 个页面`);
|