Files
platform-prototype/scripts/generate-overview.js
T
zhangyanandClaude Opus 4.6 723b5cef02 feat: 净菜包装秤术语统一+包装作业交互升级 + 组配秤按单份拆分
净菜包装秤(原贴标秤):
- 全模块术语统一: 贴标→包装, 贴标秤→包装秤, 去丝/段/片后缀
- 包装作业页: 清单按packSize展开为单份, 移除弹窗改为直接读秤, 新增搜索框
- 包装任务/历史页: 表头对齐, 按单份拆分展示
- 新增label-preview.html标签预览页

净菜组配秤:
- 任务/历史页: 行展示按单份拆分, 食材名称简化(去丝/段/片/块/液后缀)
- 组配作业页: 移除确认弹窗, 底部按钮区改为flex布局

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 10:11:51 +08:00

509 lines
15 KiB
JavaScript

/**
* 全平台原型完成情况汇总页生成脚本
*
* 用法:node scripts/generate-overview.js [输出目录]
* 默认输出到:C:\Users\lenovo\Desktop\数字味道\蚁熊SAAS项目\产品\已完成\全平台
*
* 由 git post-merge hook 在 git pull 后自动触发
*/
const fs = require('fs');
const path = require('path');
// ===== 配置 =====
const PROJECT_ROOT = path.resolve(__dirname, '..');
const INDEX_HTML = path.join(PROJECT_ROOT, 'index.html');
const OUTPUT_DIR = process.argv[2] || 'C:\\Users\\lenovo\\Desktop\\数字味道\\蚁熊SAAS项目\\产品\\已完成\\全平台';
// ===== 从 index.html 提取 SITE_MAP 数据 =====
function extractSiteMap(html) {
// 找到 const SITE_MAP = [ 开始的位置
const startMarker = 'const SITE_MAP = [';
const startIdx = html.indexOf(startMarker);
if (startIdx === -1) throw new Error('未找到 SITE_MAP 定义');
// 从 SITE_MAP 开始,数括号匹配找到结束的 ];
let depth = 0;
let inString = false;
let stringChar = '';
let i = startIdx + startMarker.length - 1; // -1 因为 [ 已经包含在 marker 中
for (; i < html.length; i++) {
const ch = html[i];
if (inString) {
if (ch === '\\') { i++; continue; }
if (ch === stringChar) { inString = false; }
continue;
}
if (ch === '"' || ch === "'" || ch === '`') {
inString = true;
stringChar = ch;
continue;
}
if (ch === '[' || ch === '{') { depth++; }
else if (ch === ']' || ch === '}') {
depth--;
if (depth === 0) {
// 找到了闭合的 ]
const jsCode = html.substring(startIdx + startMarker.indexOf('['), i + 1);
// 处理 JS 中的尾逗号(JSON 不允许)
const cleanedCode = jsCode
.replace(/,(\s*[}\]])/g, '$1') // 移除尾逗号
.replace(/\/\*[\s\S]*?\*\//g, '') // 移除块注释
.replace(/\/\/.*$/gm, ''); // 移除行注释
try {
return eval('(' + cleanedCode + ')');
} catch (e) {
throw new Error('SITE_MAP 解析失败: ' + e.message);
}
}
}
}
throw new Error('SITE_MAP 未正确闭合');
}
// ===== 提取 ICONS SVG 定义 =====
function extractIcons(html) {
const match = html.match(/const ICONS = (\{[\s\S]*?\n\};)/);
if (!match) return {};
try {
const cleaned = match[1].replace(/,(\s*\})/g, '$1');
return eval('(' + cleaned + ')');
} catch (e) {
return {};
}
}
// ===== 计算模块状态 =====
function getModuleStatus(mod) {
const hasPages = mod.pages && mod.pages.length > 0;
if (!hasPages) return { status: 'pending', label: '待设计', cls: 'pending' };
if (mod.done) return { status: 'done', label: '已完成', cls: 'done' };
return { status: 'wip', label: '设计中', cls: 'wip' };
}
// ===== 统计各端数据 =====
function buildTabStats(tab) {
if (tab.roles) {
// 健康应急 APP 特殊结构:roles + shared
const roleModules = [];
tab.roles.forEach(role => {
role.modules.forEach(mod => {
const st = getModuleStatus(mod);
roleModules.push({
name: role.role + ' - ' + mod.name,
dir: mod.dir,
pages: mod.pages || [],
pageCount: (mod.pages || []).length,
status: st.status,
statusLabel: st.label,
statusCls: st.cls,
prd: mod.prd || null
});
});
});
if (tab.shared) {
tab.shared.forEach(mod => {
const st = getModuleStatus(mod);
roleModules.push({
name: '共享 - ' + mod.name,
dir: mod.dir,
pages: mod.pages || [],
pageCount: (mod.pages || []).length,
status: st.status,
statusLabel: st.label,
statusCls: st.cls,
prd: null
});
});
}
return {
id: tab.id,
label: tab.label,
modules: roleModules,
totalPages: roleModules.reduce((s, m) => s + m.pageCount, 0),
doneModules: roleModules.filter(m => m.status === 'done').length,
wipModules: roleModules.filter(m => m.status === 'wip').length,
pendingModules: roleModules.filter(m => m.status === 'pending').length,
totalModules: roleModules.length
};
}
// 标准结构:modules[]
const modules = (tab.modules || []).map(mod => {
const st = getModuleStatus(mod);
return {
name: mod.name,
dir: mod.dir,
pages: mod.pages || [],
pageCount: (mod.pages || []).length,
status: st.status,
statusLabel: st.label,
statusCls: st.cls,
prd: mod.prd || null,
entry: mod.entry || 'index.html'
};
});
return {
id: tab.id,
label: tab.label,
modules,
totalPages: modules.reduce((s, m) => s + m.pageCount, 0),
doneModules: modules.filter(m => m.status === 'done').length,
wipModules: modules.filter(m => m.status === 'wip').length,
pendingModules: modules.filter(m => m.status === 'pending').length,
totalModules: modules.length
};
}
// ===== 生成 HTML =====
function generateHTML(tabStats, generatedAt) {
const grandTotalPages = tabStats.reduce((s, t) => s + t.totalPages, 0);
const grandTotalModules = tabStats.reduce((s, t) => s + t.totalModules, 0);
const grandDoneModules = tabStats.reduce((s, t) => s + t.doneModules, 0);
const platformColors = {
'web-admin': '#69b1ff',
'employee-app': '#95de64',
'emergency-app': '#ffc069',
'other-terminals': '#b37feb'
};
const platformIcons = {
'web-admin': '&#x1F4BB;',
'employee-app': '&#x1F4F1;',
'emergency-app': '&#x1F3A5;',
'other-terminals': '&#x1F310;'
};
const platformLabels = {
'web-admin': 'Web 管理端',
'employee-app': '员工端 APP',
'emergency-app': '健康应急 APP',
'other-terminals': '其他终端'
};
function renderModuleCard(mod) {
const statusConfig = {
done: { cls: 'card--done', badge: '已完成', badgeCls: 'badge--done' },
wip: { cls: 'card--wip', badge: '设计中', badgeCls: 'badge--wip' },
pending: { cls: 'card--pending', badge: '待设计', badgeCls: 'badge--pending' }
};
const sc = statusConfig[mod.status];
const pageLabel = mod.pageCount > 0 ? `${mod.pageCount} 页` : '暂无页面';
const prdLink = mod.prd ? `<a class="card__prd" href="../platform-prototype/${mod.prd}" target="_blank">PRD</a>` : '';
return `
<div class="card ${sc.cls}">
<div class="card__header">
<span class="card__name">${mod.name}</span>
<span class="badge ${sc.badgeCls}">${sc.badge}</span>
</div>
<div class="card__meta">
<span class="card__pages">${pageLabel}</span>
${prdLink}
</div>
</div>`;
}
function renderTabSection(stats) {
const color = platformColors[stats.id] || '#ccc';
const icon = platformIcons[stats.id] || '';
const donePct = stats.totalModules > 0
? Math.round((stats.doneModules / stats.totalModules) * 100)
: 0;
return `
<div class="platform">
<div class="platform__header" style="border-left-color: ${color}">
<div class="platform__title">
<span class="platform__icon">${icon}</span>
<h2>${stats.label}</h2>
</div>
<div class="platform__summary">
<span class="stat"><strong>${stats.totalModules}</strong> 模块</span>
<span class="stat"><strong>${stats.totalPages}</strong> 页面</span>
<span class="stat stat--done"><strong>${stats.doneModules}</strong> 已完成</span>
<span class="stat stat--wip"><strong>${stats.wipModules}</strong> 设计中</span>
<span class="stat stat--pending"><strong>${stats.pendingModules}</strong> 待设计</span>
</div>
<div class="progress-bar">
<div class="progress-bar__fill" style="width:${donePct}%; background:${color}"></div>
</div>
</div>
<div class="module-grid">
${stats.modules.map(renderModuleCard).join('')}
</div>
</div>`;
}
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>全平台原型完成情况汇总</title>
<style>
:root {
--primary: #1677FF;
--text-primary: #1a1a2e;
--text-secondary: #5a607f;
--border: #e8ecf1;
--bg: #f0f2f8;
--card-bg: #fff;
--radius: 10px;
--shadow: 0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.06);
--shadow-hover: 0 4px 16px rgba(22,119,255,0.10), 0 2px 6px rgba(0,0,0,0.06);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
background: var(--bg);
color: var(--text-primary);
min-height: 100vh;
}
/* Header */
.header {
background: linear-gradient(135deg, #0b3d91 0%, #1677FF 40%, #4facfe 100%);
color: #fff;
padding: 32px 48px 24px;
text-align: center;
}
.header__title { font-size: 24px; font-weight: 800; }
.header__sub { font-size: 13px; color: rgba(255,255,255,0.65); margin-top: 6px; }
/* 总览卡片 */
.overview {
max-width: 1400px;
margin: -16px auto 0;
padding: 0 48px;
position: relative;
z-index: 2;
}
.overview__cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.overview__card {
background: var(--card-bg);
border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
padding: 20px 24px;
text-align: center;
}
.overview__card__num {
font-size: 36px;
font-weight: 800;
color: var(--primary);
line-height: 1.2;
}
.overview__card__num--green { color: #52c41a; }
.overview__card__num--orange { color: #fa8c16; }
.overview__card__num--purple { color: #722ed1; }
.overview__card__label {
font-size: 13px;
color: var(--text-secondary);
margin-top: 4px;
}
/* 平台分区 */
.content {
max-width: 1400px;
margin: 0 auto;
padding: 28px 48px 48px;
}
.platform {
margin-bottom: 32px;
}
.platform__header {
border-left: 4px solid;
padding-left: 16px;
margin-bottom: 16px;
}
.platform__title {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.platform__icon { font-size: 22px; }
.platform__title h2 { font-size: 18px; font-weight: 700; }
.platform__summary {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.stat { font-size: 13px; color: var(--text-secondary); }
.stat strong { color: var(--text-primary); }
.stat--done strong { color: #52c41a; }
.stat--wip strong { color: #fa8c16; }
.stat--pending strong { color: #999; }
.progress-bar {
height: 6px;
background: #e8ecf1;
border-radius: 3px;
overflow: hidden;
}
.progress-bar__fill {
height: 100%;
border-radius: 3px;
transition: width 0.6s ease;
}
/* 模块网格 */
.module-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
}
/* 模块卡片 */
.card {
background: var(--card-bg);
border-radius: 8px;
box-shadow: var(--shadow);
padding: 14px 16px;
border: 1.5px solid var(--border);
transition: all .2s;
}
.card:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }
.card--done { border-color: #b7eb8f; }
.card--wip { border-color: #ffe58f; }
.card--pending { border-style: dashed; border-color: #d0d5dd; opacity: 0.55; }
.card__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.card__name { font-size: 14px; font-weight: 600; }
.card__meta {
display: flex;
align-items: center;
gap: 12px;
}
.card__pages { font-size: 12px; color: var(--text-secondary); }
.card__prd {
font-size: 11px;
color: var(--primary);
text-decoration: none;
padding: 2px 8px;
background: #e6f7ff;
border-radius: 4px;
}
.card__prd:hover { text-decoration: underline; }
.badge {
font-size: 11px;
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
flex-shrink: 0;
}
.badge--done { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.badge--wip { background: #fffbe6; color: #fa8c16; border: 1px solid #ffe58f; }
.badge--pending { background: #fafafa; color: #999; border: 1px solid #d0d5dd; }
/* Footer */
.footer {
text-align: center;
padding: 20px 48px 32px;
font-size: 12px;
color: #bbb;
}
/* 模块入口链接 */
.card__link {
font-size: 11px;
color: var(--primary);
text-decoration: none;
padding: 2px 8px;
background: #f0f5ff;
border-radius: 4px;
}
.card__link:hover { text-decoration: underline; }
@media print {
body { background: #fff; }
.header { background: #1677FF !important; -webkit-print-color-adjust: exact; }
}
</style>
</head>
<body>
<div class="header">
<h1 class="header__title">健康CQ升级 — 全平台原型完成情况</h1>
<p class="header__sub">自动生成于 ${generatedAt} | 数据来源:git pull 后最新 SITE_MAP</p>
</div>
<div class="overview">
<div class="overview__cards">
<div class="overview__card">
<div class="overview__card__num">${grandTotalPages}</div>
<div class="overview__card__label">全平台页面总数</div>
</div>
<div class="overview__card">
<div class="overview__card__num overview__card__num--green">${grandDoneModules}</div>
<div class="overview__card__label">已完成模块</div>
</div>
<div class="overview__card">
<div class="overview__card__num overview__card__num--orange">${grandTotalModules - grandDoneModules}</div>
<div class="overview__card__label">未完成模块</div>
</div>
<div class="overview__card">
<div class="overview__card__num overview__card__num--purple">${grandTotalModules > 0 ? Math.round((grandDoneModules / grandTotalModules) * 100) : 0}%</div>
<div class="overview__card__label">模块完成率</div>
</div>
</div>
</div>
<div class="content">
${tabStats.map(renderTabSection).join('')}
</div>
<div class="footer">
本文件由 git post-merge hook 自动生成 | 最近一次 git 提交后触发 | 请勿手动编辑
</div>
</body>
</html>`;
}
// ===== 主流程 =====
function main() {
console.log('[generate-overview] 读取 index.html...');
const html = fs.readFileSync(INDEX_HTML, 'utf-8');
console.log('[generate-overview] 解析 SITE_MAP...');
const siteMap = extractSiteMap(html);
console.log('[generate-overview] 统计各端数据...');
const tabStats = siteMap.map(buildTabStats);
const now = new Date();
const generatedAt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
console.log('[generate-overview] 生成 HTML...');
const outputHTML = generateHTML(tabStats, generatedAt);
// 确保输出目录存在
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
const outputFile = path.join(OUTPUT_DIR, `全平台原型进度_${dateStr}.html`);
fs.writeFileSync(outputFile, outputHTML, 'utf-8');
console.log(`[generate-overview] 已生成: ${outputFile}`);
// 输出统计摘要
const grandTotal = tabStats.reduce((s, t) => s + t.totalPages, 0);
console.log(`[generate-overview] 全平台总计: ${grandTotal} 个页面, ${tabStats.reduce((s, t) => s + t.totalModules, 0)} 个模块`);
}
main();