docs: add development plan and technical design

This commit is contained in:
meswork764
2026-05-27 18:36:20 +08:00
parent acca29b36b
commit 6ecd0b5bd5
9 changed files with 4464 additions and 2 deletions

1
.gitignore vendored
View File

@@ -25,6 +25,7 @@ selenium-debug.log
**/.idea/ **/.idea/
.vscode/ .vscode/
**/.vscode/ **/.vscode/
~$*.docx
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj

View File

@@ -0,0 +1,7 @@
{
"folders": [
{
"path": ".."
}
]
}

View File

@@ -0,0 +1,77 @@
# 今后开发计划与新增需求分析记录
生成日期2026-05-27
项目MES_Manage_View_V20
## 一、排期口径
- 按“最终调整”数字从小到大排序。
- 从 2026-05-28 开始排期。
- 按工作日递推,跳过周六、周日。
- 跳过端午节、中秋节、国庆节法定放假日期。
- 调休日按工作日处理。
- 暂按单开发线串行排期。
节假日口径:
- 端午节2026-06-19 至 2026-06-21。
- 中秋节2026-09-25 至 2026-09-27。
- 国庆节2026-10-01 至 2026-10-07。
- 调休上班日2026-09-20、2026-10-10。
## 二、开发计划排期
| 顺序 | 需求 | 预估 | 开始 | 结束 | 备注 |
|---:|---|---:|---|---|---|
| 1 | 及时开工报表 | 1天 | 5.28 | 5.29 | 增加正选/反选查询 |
| 2 | 机加件排产-表单 | 4天 | 5.29 | 6.04 | 日期筛选优化 + 历史工时查询SAP新增字段不算MES开发 |
| 3 | 装配已排产需求 | 12天 | 6.04 | 6.23 | 大需求,建议分阶段交付 |
| 4 | 生产日报-送检(机加/装配) | 3天 | 6.23 | 6.26 | 建议统一报表,部门做筛选维度 |
| 5 | 及时齐套跟踪结果报表 | 5天 | 6.26 | 7.03 | 依赖SAP缺件明细实时接口 |
| 6 | 生产计划跟踪 | 3天 | 7.03 | 7.08 | 工序状态、颜色、标准工时、订单状态 |
| 7 | 外协任务检验 | 3天 | 7.08 | 7.13 | 增加指派对象、供应商、SAP采购收货单号 |
| 8 | 异常管控 | 5天 | 7.13 | 7.20 | 报工页面直接报异常,多审批人可见一人处理 |
| 9 | 机加班次产量表 | 3天 | 7.20 | 7.23 | 新增报表 |
| 10 | 发货通知列表 | 1天 | 7.23 | 7.24 | SAP数据已提供可新增查询报表 |
| 11 | 质检任务早知道 | 1天 | 7.24 | 7.27 | 依赖“装配已排产需求”的装配完成时间 |
| 12 | 生产工艺 | 2天 | 7.27 | 7.29 | 无生产工艺时自动带出标准工艺 |
| 13 | 工时报表 + 备注类小改 | 2天 | 7.29 | 7.31 | 指派对象、机加领料备注、机加/装配退库备注、保存提示等合并处理 |
| 14 | 生产订单关闭 | 4天 | 7.31 | 8.06 | 批量更新SAP订单状态风险较高 |
| 15 | 及时检验报表 | 4天 | 8.06 | 8.12 | 报工超过2天未检验 |
| 16 | 工序流转卡 | 1天 | 8.12 | 8.13 | 增加打印原料编码 |
| 17 | 一楼质检大屏幕 | 4天 | 8.13 | 8.19 | 及时率、关键工序 |
| 18 | 机加现场大屏幕 | 3天 | 8.19 | 8.24 | 缺勤、关键工序 |
| 19 | 装配现场大屏幕 | 4天 | 8.24 | 8.28 | 缺勤、及时送检 |
| 20 | 编程任务清单 | 4天 | 8.28 | 9.03 | 按装配类似逻辑做编程报工 |
| 21 | 设备档案管理 | 2天 | 9.03 | 9.07 | 需先确认字段归属 |
| 22 | 设备数据采集 | 8天 | 9.07 | 9.17 | 采集接口、落库、状态汇总 |
| 23 | 设备指标/OEE | 6天 | 9.17 | 9.24 | 依赖设备采集和标准工时口径 |
| 24 | 工时查询-设备采集联动 | 2天 | 9.24 | 9.29 | 程序工时字段对接采集数据 |
| 25 | 手续确认报表 | 3天 | 9.29 | 10.09 | 装配/电气齐套列表 + 手续确认 |
| 26 | 产品合格率(工位) | 3天 | 10.09 | 10.13 | 需确认良品/总产量口径 |
| 27 | 让步接收记录 | 2天 | 10.13 | 10.15 | 新增查询页面 |
| 28 | 设计任务报工模块 | 6天 | 10.15 | 10.23 | MES自建设计任务、设计工时 |
| 29 | 工时异常报表 | 2天 | 10.23 | 10.27 | 依赖标准工时和实际单件工时 |
| 30 | 正在加工状态 | 1天 | 10.27 | 10.28 | 机加正在进行中状态 |
## 三、暂不直接排期 / 需等前置条件
| 需求 | 原因 |
|---|---|
| 及时拣配机加MES | 等SAP数据、条码规则、状态写回规则 |
| 及时拣配装配MES | 等SAP数据、条码规则、状态写回规则 |
| 及时送检报表 | 需确认发料齐套时间/SAP字段是否稳定若SAP数据已到可插入0.5天 |
| 工艺内容修改 | 已开工任务怎么处理未定 |
| 标牌打印操作 | 需求未确定 |
| 首检、巡检 | 需求内容为空,无法估时 |
| 外协采购订单收检结果 | 需求内容为空,无法估时 |
| 不合格品单据打印 | 需求内容为空,无法估时 |
## 四、排期结论
按以上单线工作日计划,已明确可做事项预计到 2026-10-28 完成。
实际执行建议先做 5.28 到 6.23 的前三项,因为它们优先级最高,且“装配已排产需求”会影响后面的质检早知道、日计划、及时送检等需求。
## 五、新增需求分析与开发记录原文

View File

@@ -1090,8 +1090,7 @@
9. 看板:生产、装配、质检、机加、综合大屏。 9. 看板:生产、装配、质检、机加、综合大屏。
10. 新增需求页面:手续确认、及时检验、装配已排产需求、齐套跟踪等。 10. 新增需求页面:手续确认、及时检验、装配已排产需求、齐套跟踪等。
## 二十一、逐页详细操作说明(按 src/views 页面) ## 二十一、逐页详细操作说明
本章按源码页面逐页编写。每个页面都从实际 `v-model`、表格列、按钮、弹窗、页签、存储过程和 SAP/WMS 调用中提取操作线索,转换成用户可执行的步骤。 本章按源码页面逐页编写。每个页面都从实际 `v-model`、表格列、按钮、弹窗、页签、存储过程和 SAP/WMS 调用中提取操作线索,转换成用户可执行的步骤。
通用阅读方法: 通用阅读方法:

File diff suppressed because it is too large Load Diff

731
analyze_mes_v20_views.js Normal file
View File

@@ -0,0 +1,731 @@
const fs = require('fs');
const path = require('path');
const root = path.resolve('MES_Manage_View_V20');
const viewsRoot = path.join(root, 'src', 'views');
const outFile = path.join(root, 'MES_Manage_View_V20_src_views_逐页面详细分析.md');
function existingSqlFiles() {
return fs.readdirSync(root)
.filter(x => x.toLowerCase().endsWith('.sql'))
.map(x => path.join(root, x));
}
function readText(file) {
const buf = fs.readFileSync(file);
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) return buf.slice(2).toString('utf16le');
if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) {
const swapped = Buffer.alloc(buf.length - 2);
for (let i = 2; i + 1 < buf.length; i += 2) {
swapped[i - 2] = buf[i + 1];
swapped[i - 1] = buf[i];
}
return swapped.toString('utf16le');
}
if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) return buf.slice(3).toString('utf8');
return buf.toString('utf8');
}
function walk(dir, exts, result = []) {
if (!fs.existsSync(dir)) return result;
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, item.name);
if (item.isDirectory()) walk(p, exts, result);
else if (exts.includes(path.extname(item.name).toLowerCase())) result.push(p);
}
return result;
}
function rel(file) {
return path.relative(root, file).replace(/\\/g, '/');
}
function lineOf(text, index) {
return text.slice(0, index).split(/\r?\n/).length;
}
function uniq(arr) {
return [...new Set((arr || []).filter(Boolean))];
}
function clean(s) {
return (s || '')
.replace(/<[^>]*>/g, '')
.replace(/\{\{.*?\}\}/g, '')
.replace(/\s+/g, ' ')
.trim();
}
function escapeReg(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function isInComment(text, index) {
const lineStart = text.lastIndexOf('\n', index) + 1;
const beforeOnLine = text.slice(lineStart, index);
if (/^\s*\/\//.test(beforeOnLine)) return true;
const blockStart = text.lastIndexOf('/*', index);
const blockEnd = text.lastIndexOf('*/', index);
return blockStart > blockEnd;
}
function moduleNameFromPath(relPath) {
const parts = relPath.split('/');
if (parts[0] !== 'src' || parts[1] !== 'views') return '其他';
if (parts.length <= 3) return parts[2] || '根页面';
return `${parts[2]} / ${parts.slice(3, -1).join(' / ') || path.basename(relPath)}`;
}
function extractLabels(text) {
const labels = [];
const patterns = [
/<el-button\b[^>]*>([\s\S]*?)<\/el-button>/g,
/\blabel="([^"]+)"/g,
/\bplaceholder="([^"]+)"/g,
/\btitle="([^"]+)"/g
];
for (const re of patterns) {
let m;
while ((m = re.exec(text))) {
const value = clean(m[1]);
if (value && !/[{};]/.test(value) && value.length <= 48) labels.push(value);
}
}
return uniq(labels).slice(0, 30);
}
function extractFrontendFields(text) {
const vModels = [];
const tableColumns = [];
const pushedParams = [];
let m;
const vmRe = /\bv-model(?:\.[\w-]+)?="([^"]+)"/g;
while ((m = vmRe.exec(text))) vModels.push(clean(m[1]));
const colRe = /<el-table-column\b([^>]*)>/g;
while ((m = colRe.exec(text))) {
const attrs = m[1];
const label = /label="([^"]+)"/.exec(attrs)?.[1] || '';
const prop = /prop="([^"]+)"/.exec(attrs)?.[1] || '';
if (label || prop) tableColumns.push({ label: clean(label), prop: clean(prop) });
}
const paramRe = /\.(?:push|unshift)\s*\(\s*\[\s*['"]([^'"]+)['"]\s*,\s*([^\]\n]+)\]/g;
while ((m = paramRe.exec(text))) pushedParams.push({ name: clean(m[1]), expr: clean(m[2]) });
return {
vModels: uniq(vModels).slice(0, 80),
tableColumns: uniq(tableColumns.map(x => `${x.label}|${x.prop}`))
.map(x => {
const [label, prop] = x.split('|');
return { label, prop };
})
.slice(0, 120),
pushedParams: uniq(pushedParams.map(x => `${x.name}|${x.expr}`))
.map(x => {
const [name, expr] = x.split('|');
return { name, expr };
})
.slice(0, 120)
};
}
function extractMethods(text) {
const methods = [];
const methodBlock = /methods\s*:\s*\{([\s\S]*?)\n\s*\}/m.exec(text);
const src = methodBlock ? methodBlock[1] : text;
const re = /^\s*([A-Za-z_$\u4e00-\u9fa5][\w$\u4e00-\u9fa5]*)\s*\([^)]*\)\s*\{/gm;
let m;
while ((m = re.exec(src))) methods.push(m[1]);
return uniq(methods).slice(0, 30);
}
function extractCreateData(text) {
const calls = [];
const re = /CreateData\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g;
let m;
while ((m = re.exec(text))) {
if (isInComment(text, m.index)) continue;
calls.push({ type: m[1], name: m[2], line: lineOf(text, m.index) });
}
return calls;
}
function extractSapCalls(text) {
const calls = [];
const re = /(getB1|postB1|patchB1|posttm)\s*\(\s*([`'"])([\s\S]*?)\2/g;
let m;
while ((m = re.exec(text))) {
if (isInComment(text, m.index)) continue;
const snippet = text.slice(m.index, m.index + 1200);
const keys = [];
const keyRe = /["']?([\u4e00-\u9fa5A-Za-z_][\u4e00-\u9fa5A-Za-z0-9_$]*)["']?\s*:/g;
let km;
while ((km = keyRe.exec(snippet)) && keys.length < 24) {
if (!['if', 'for', 'while', 'switch', 'catch', 'function'].includes(km[1])) keys.push(km[1]);
}
calls.push({ fn: m[1], url: clean(m[3]), line: lineOf(text, m.index), payloadKeys: uniq(keys) });
}
return calls;
}
function extractApiImports(text) {
const imports = [];
const re = /from\s+['"](@\/api\/[^'"]+)['"]/g;
let m;
while ((m = re.exec(text))) imports.push(m[1]);
return uniq(imports);
}
function stripSqlComments(sql) {
return sql.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/--.*$/gm, ' ');
}
function compactSqlLine(s) {
return clean(s)
.replace(/\s*,\s*/g, ', ')
.replace(/\s*=\s*/g, ' = ')
.replace(/\s+/g, ' ')
.slice(0, 240);
}
function extractSqlLines(body, regex, limit) {
return uniq(stripSqlComments(body)
.split(/\r?\n/)
.map(x => x.trim())
.filter(x => regex.test(x))
.map(compactSqlLine))
.slice(0, limit);
}
function extractSelectColumns(body, limit = 10) {
const noComments = stripSqlComments(body);
const m = /\bSELECT\b([\s\S]{0,2500}?)\bFROM\b/i.exec(noComments);
if (!m) return [];
return uniq(m[1].split(/\r?\n/)
.map(x => compactSqlLine(x.replace(/^\s*,/, '')))
.filter(x => x && !/^(TOP|DISTINCT)$/i.test(x)))
.slice(0, limit);
}
function objectRegex(name) {
return new RegExp(`(?:\\[dbo\\]\\.\\[${escapeReg(name)}\\]|dbo\\.\\[${escapeReg(name)}\\]|\\[${escapeReg(name)}\\]|\\b${escapeReg(name)}\\b)`, 'i');
}
function extractParams(body) {
const header = body.split(/\bAS\b/i)[0] || body.slice(0, 1000);
const params = [];
const re = /@([\u4e00-\u9fa5A-Za-z0-9_]+)\s+([A-Za-z0-9_\[\]\(\),]+)/g;
let m;
while ((m = re.exec(header))) params.push(`@${m[1]} ${m[2]}`);
return uniq(params).slice(0, 24);
}
function summarizeProc(name, body) {
const verbs = [];
if (/\bSELECT\b/i.test(body)) verbs.push('查询');
if (/\bINSERT\s+INTO\b/i.test(body)) verbs.push('新增');
if (/\bUPDATE\b/i.test(body)) verbs.push('更新');
if (/\bDELETE\s+FROM\b/i.test(body)) verbs.push('删除');
if (/\bOPENJSON\b|JSON_VALUE|JSON_QUERY/i.test(body)) verbs.push('解析 JSON 批量数据');
if (/ROW_NUMBER|OFFSET|@PageSize|@PageCurrent/i.test(body)) verbs.push('分页');
if (/SAP\.SBO_YL/i.test(body)) verbs.push('读取 SAP B1 数据库对象');
if (/报工|工时/.test(name + body)) verbs.push('处理报工/工时');
if (/质检|检验|不合格/.test(name + body)) verbs.push('处理质检数据');
if (/排产|派工|计划/.test(name + body)) verbs.push('处理计划/派工');
if (/外协/.test(name + body)) verbs.push('处理外协业务');
return uniq(verbs).join('、') || '执行数据库业务逻辑';
}
function extractSqlModel() {
const sql = existingSqlFiles().map(f => `\n-- FILE: ${path.basename(f)}\n${readText(f)}`).join('\n');
const objects = [];
const objectRe = /CREATE\s+(TABLE|VIEW)\s+(?:\[dbo\]\.)?\[([^\]]+)\]/gi;
let m;
while ((m = objectRe.exec(sql))) objects.push({ type: m[1].toUpperCase(), name: m[2] });
const markers = [];
const procRe = /(?:CREATE|ALTER)\s+(?:\s*PROCEDURE|\s*PROC)\s+(?:\[dbo\]\.)?\[([^\]]+)\]/gi;
while ((m = procRe.exec(sql))) markers.push({ index: m.index, name: m[1] });
const procByName = {};
for (let i = 0; i < markers.length; i++) {
const start = markers[i].index;
const nextGo = sql.slice(start).search(/\r?\nGO\r?\n/i);
const nextProc = i + 1 < markers.length ? markers[i + 1].index : sql.length;
const end = nextGo >= 0 ? Math.min(start + nextGo, nextProc) : nextProc;
const body = sql.slice(start, end);
const deps = [];
for (const obj of objects) {
if (objectRegex(obj.name).test(body)) deps.push(`${obj.type}:${obj.name}`);
}
const sapRefs = uniq(body.match(/SAP\.SBO_YL\.dbo\.[A-Za-z0-9_]+/g) || []);
for (const s of sapRefs) deps.push(`SAP:${s}`);
const writes = [];
for (const obj of objects) {
const n = escapeReg(obj.name);
const hit = new RegExp(`\\b(INSERT\\s+INTO|UPDATE|DELETE\\s+FROM|MERGE\\s+INTO)\\s+(?:\\[dbo\\]\\.\\[${n}\\]|dbo\\.\\[${n}\\]|\\[${n}\\]|${n})`, 'i').test(body);
if (hit) writes.push(obj.name);
}
const noComments = stripSqlComments(body);
procByName[markers[i].name] = {
name: markers[i].name,
body,
params: extractParams(body),
deps: uniq(deps),
writes: uniq(writes),
sapRefs,
summary: summarizeProc(markers[i].name, body),
features: {
selectColumns: extractSelectColumns(body),
filters: extractSqlLines(body, /\b(WHERE|AND|OR)\b/i, 10),
updates: extractSqlLines(body, /\b(UPDATE|SET|INSERT\s+INTO|DELETE\s+FROM|MERGE\s+INTO)\b/i, 12),
statusLines: extractSqlLines(body, /状态|排产|派工|加工|任务|质检|关闭|完成|已报工|DocEntry|SAP/i, 18),
grouping: extractSqlLines(body, /\b(GROUP\s+BY|HAVING|ORDER\s+BY|ROW_NUMBER|OFFSET|FETCH\s+NEXT)\b/i, 8),
tempTables: uniq(noComments.match(/#[\u4e00-\u9fa5A-Za-z0-9_]+/g) || []).slice(0, 10),
hasJson: /\bOPENJSON\b|JSON_VALUE|JSON_QUERY/i.test(noComments),
hasTransaction: /\bBEGIN\s+TRAN|COMMIT\s+TRAN|ROLLBACK\s+TRAN/i.test(noComments),
hasTryCatch: /\bTRY\b|\bCATCH\b/i.test(noComments),
hasPagination: /@PageCurrent|@PageSize|ROW_NUMBER|OFFSET\s+.*FETCH/i.test(noComments),
hasSap: /SAP\.SBO_YL/i.test(noComments)
}
};
}
return { objects, procByName };
}
function operationByType(type) {
return {
'1': '旧版查询',
'2': '旧版增删改',
'7': '旧版批量/JSON',
'11': '当前通用存储过程查询/执行',
'12': '当前通用存储过程写入/业务动作',
'2001': '导出/报表类过程'
}[type] || `自定义类型 ${type}`;
}
function procPurpose(name) {
const rules = [
[/查询|Get|Load|Query|Search|读取|查看/, '查询/读取数据'],
[/新增|增加|Add|Insert/, '新增业务数据'],
[/修改|编辑|Edit|Update|更新|保存|Save/, '更新/保存业务数据'],
[/删除|Delete/, '删除业务数据'],
[/Finish|确认|关闭|Close/, '确认/关闭/完成业务动作'],
[/派工|Planned|排产|计划/, '计划排产或派工'],
[/报工|工时|WorkTime|ProdReport/, '报工或工时处理'],
[/质检|检验|Check|Quality/, '质量检验处理'],
[/外协/, '外协业务处理'],
[/登录|人员|角色|菜单|权限/, '基础权限/人员数据处理'],
[/设备|工位|资源|Station|Resource/, '设备/工位/资源数据处理']
];
for (const [re, purpose] of rules) if (re.test(name)) return purpose;
return '业务存储过程调用';
}
function businessDomainFromName(name) {
const rules = [
[/登录|人员|角色|菜单|权限|部门|工位管理/, '系统基础资料/权限'],
[/设备|报警|CaChe|StationStatus|工位设备/, '设备与实时数据'],
[/排产|派工|OrderPlanned|计划/, '计划排产'],
[/ProductCenter|ProductTask|加工中心|装配中心|报工|收料|开工|退库|发料|工时/, '生产执行/工时'],
[/外协/, '外协协同'],
[/图纸|工艺|Process|Drawing|Procedure|产品结构|标准/, '工艺与图纸'],
[/质检|检验|质量|不合格|Check/, '质量检验'],
[/销售/, '销售/订单状态'],
[/班次/, '班次与人员排班'],
[/加急/, '加急任务']
];
for (const [re, domain] of rules) if (re.test(name)) return domain;
return '通用业务';
}
function pageBusinessNarrative(page) {
const p = page.relPath;
const labels = page.labels.slice(0, 10);
const procNames = uniq(page.procs.map(x => x.name)).slice(0, 6);
const common = [];
if (labels.length) common.push(`页面可见的操作/字段包括 ${labels.map(x => `${x}`).join('、')},这些是判断页面业务范围的主要线索。`);
if (procNames.length) common.push(`后端主要围绕 ${procNames.map(x => `\`${x}\``).join('、')}${page.procs.length > 6 ? ' 等过程' : ''} 取数或提交业务动作。`);
const rules = [
[/PunctualWork/, '该页面用于发现“计划应开工但现场未及时开工”的任务。用户按任务状态、工位、工序、异常原因等条件筛选,页面展示生产订单、销售订单、物料、工序、指派对象、分配数量、上序合格数、加工/收料状态和加急数量,服务于生产异常提醒和催办。'],
[/CraftMaintain/, '该页面维护产品结构树。用户可以新增、编辑、删除产品结构节点查询现有产品结构并为后续工艺、物料、BOM 或生产任务提供层级基础数据。'],
[/DeviceDataHistory/, '该页面查询设备历史采集数据。用户按工位/设备、参数名称、起止时间分页查询历史记录,用于追溯设备运行参数。'],
[/DeviceInformation/, '该页面维护设备台账和设备相关基础资料。用户可以查询设备信息,新增、修改、删除设备,维护维修记录、工时记录或点检配置。'],
[/DeviceRealData/, '该页面展示设备实时或近实时数据。用户按工位和数据类型查询当前采集值、统计值或状态,通常用于设备运行监控。'],
[/Devicedata/, '该页面面向设备采集数据管理,查询缓存变量和设备数据,支持按设备、参数、时间等维度查看实时或历史采集结果。'],
[/InstallMakePlanDo|InstallMakePlan/, '该页面处理装配件排产。用户查询待排产/已排产装配任务,选择工位和人员,修改计划开工/完成时间、优先级、说明,执行单条派工、二次派工、批量确认排产,并在确认时同步 SAP 生产订单状态或创建相关请求。'],
[/SelfMakePlandone|SelfMakePlando|SelfMakePlan/, '该页面处理自制件/机加件排产。用户查询生产订单工序任务,筛选订单、合同、物料、工序、计划时间、排产状态和指派对象,完成工位派工、人员二次派工、外协分组、计划说明维护和排产确认。'],
[/PlannOrderClose/, '该页面用于生产订单关闭。用户筛选 SAP 未关闭或 MES 待确认订单,执行 SAP 生产订单关闭,并调用 MES 过程记录或确认订单关闭状态。'],
[/PlanShell/, '该页面是计划外壳/计划跟踪类页面,按缺料、发货通知、完成状态等条件查询计划或装配相关任务,用于计划人员集中查看订单齐套、发货和关闭情况。'],
[/priorityinstall/, '该页面用于装配优先级调整。用户查询装配计划任务,修改计划开工/完成时间、派工对象或优先级,用于插单和重点订单排序。'],
[/PartDrawing/, '该页面维护零件图纸。用户按物料查询图纸、查看文件、验证名称、删除图纸并保留删除记录。'],
[/PartDrawlook/, '该页面偏只读查看零件图纸,通常给生产或质检人员按物料快速打开图纸。'],
[/ProcurementDrawlook/, '该页面提供采购场景的图纸查看。用户按采购物料查询图纸,查看或删除图纸记录,支持采购、外协、来料检验时查阅技术文件。'],
[/ProcessDrawing/, '该页面维护工艺图纸。用户按物料/工艺条件查询工艺图,验证名称,查看或删除工艺图纸。'],
[/ProcedureManagement/, '该页面维护工序/路线阶段主数据。新增或修改工序时会同步 SAP B1 的 RouteStages保证 MES 工序与 SAP 工艺阶段一致。'],
[/ProcessesDevelop/, '该页面用于工艺开发和生产工艺路线维护。用户围绕物料、工序、顺序、工艺参数、图纸等信息维护标准或生产用工艺路线。'],
[/ProcessInquiry/, '该页面用于工艺路线查询。用户按物料、产品、工序等条件查看标准工艺或生产工艺,服务于排产、生产和质检核对。'],
[/ProcessProduct/, '该页面维护产品与工艺/图纸的关系,使产品、物料、工序、图纸等基础数据形成可查询的工艺资料。'],
[/ProductionOrder/, '该页面处理生产订单资料。用户查询 ERP/MES 生产订单,并通过过程把 SAP 生产订单同步成 MES 生产计划和工序任务。'],
[/MachiningCenter/, '该页面是机加生产执行主界面。操作员按工位查看可执行任务和进行中任务,完成收料、开始加工、暂停/恢复、报工、辅助工序、首检/巡检/序检、生产发料查询、退库、图纸查看等动作,并写入加工中心操作记录和报工记录。'],
[/AssemblyCenter/, '该页面是装配生产执行主界面。装配人员按工位查询装配任务,处理开工、结束/报工、送检、缺料查询、收检记录、图纸查看和退库等动作,并会读取 SAP 生产订单信息辅助判断。'],
[/AssembleTaskManage/, '该页面管理装配任务。用户查询装配生产任务、查看工时汇总,进行派工、任务说明维护和外协/生产通知。'],
[/ProductionTaskManage/, '该页面管理生产任务。用户按工位、订单、工序查询任务,查看工时汇总,执行派工、修改计划字段、维护任务说明和通知状态。'],
[/ProductionTasksSum/, '该页面做生产任务汇总。用户按订单、工序、工位、时间等维度查询生产任务和工时汇总,并可编辑计划说明或派工信息。'],
[/ProductionPlanTrack/, '该页面跟踪生产计划执行。用户查看计划任务的开工、完工、进度、延期或异常情况,用于计划员追踪订单。'],
[/AssemblePlanTrack/, '该页面跟踪装配计划执行,展示装配任务进度、计划完成情况和异常。'],
[/AssembleTasksSum/, '该页面汇总装配任务,按订单、工位、人员或状态统计装配执行情况。'],
[/AssembleStatus/, '该页面查看装配现场状态,重点展示进行中任务和当前工位状态。'],
[/DeviceStatus/, '该页面查看设备/工位生产状态,通常用于判断设备是否在加工、待机、报警或空闲。'],
[/OutsourceMange/, '该页面管理外协任务。用户查询可外协工序,按供应商/订单/工序分组,执行外协派工并同步 SAP 采购请求或相关单据,维护外协发料、收料、质检记录、打印次数和备注。'],
[/SaleOutsourceMange/, '该页面管理销售相关外协任务。业务与外协管理类似,但面向销售订单或销售外协场景,包含派工同步 SAP、外协发料、历史记录和质检记录查询。'],
[/OutsourceSheet/, '该页面输出外协报表。用户按订单、合同、产品、计划号、供应商、状态分页查询外协报表,也可导出,用于外协进度、发料、收料、质检、开票等跟踪。'],
[/PickingTask/, '该页面处理拣配任务。用户查询生产订单相关拣配/领料任务,并可能联动 SAP 生产订单或库存出入库状态。'],
[/PersonnelBinding/, '该页面维护工位与人员绑定关系。用于限制或提示哪些人员可在指定工位报工、开工或执行任务。'],
[/ShiftManagement/, '该页面维护班次和班次人员绑定。用户新增、修改、删除班次,给班次绑定人员,为工时计算和报工归属提供基础数据。'],
[/Timesheet/, '该页面统计工时。用户按项目、作业者、设备、任务等维度查询工时数据,并可导出报表。'],
[/WorkHoursCheck/, '该页面用于工时校验。质检/管理人员查询待校验报工记录,系统计算标准/实际工时,保存单条或批量校验结果;通过校验后可把工序资源或工时/出库类单据上传到 SAP。'],
[/WorkhoursEdit/, '该页面用于编辑已产生的工时/报工记录,修正异常工时或补录说明。'],
[/Workhours/, '该页面用于工时查询,按工序、人员、设备、时间等条件查看工序工时。'],
[/Urgent/, '该页面维护加急任务。用户查询加急订单明细,并更新完成、转至等字段,用于生产/质检优先级管理。'],
[/AbnormalControl/, '该页面管理生产异常。用户查询异常记录,推进异常处理、关闭或记录处理结果。'],
[/login/, '该页面是登录入口负责账号密码登录、角色选择、Token/Cookie 写入、路由权限初始化和密码修改入口。'],
[/dashboard/, '该页面是系统首页或欢迎页,用于承载看板入口或基础统计展示。'],
[/404/, '该页面是 404 错误页,用于路由不存在时展示错误信息。'],
[/purchaseIncoming/, '该页面处理采购/来料检验。质检人员查询待检任务,查看图纸、字典和检验人员,录入合格数、不合格数、检测数据,调用过程更新来料检验结果。'],
[/processReceive/, '该页面处理序检收检。质检人员接收工序检验任务,查询加急和字典信息,读取 SAP 工序状态,新增序检收检记录。'],
[/processSpecial/, '该页面处理过程/特殊检验。用户按工序任务录入特殊检验结果和检测明细。'],
[/firstSpecial/, '该页面处理首检/特殊首件检验。用户按工序和工位选择任务,录入首检数据。'],
[/final/, '该页面处理终检任务。质检人员查询终检任务,录入终检结果,并预留 SAP 生产订单状态回写。'],
[/materialReturn/, '该页面处理原材料退库检验。用户查询退库检验任务,读取 SAP 生产订单辅助信息,录入并更新检验结果。'],
[/salesReturn/, '该页面处理销售退货检验。用户查询销售退货待检任务,录入检验结果并更新质检记录。'],
[/OtherInbound/, '该页面处理其他入库检验。用户查询其他入库待检任务,录入检验结果并更新质检记录。'],
[/QualityCenter/, '该页面是质量综合处理中心。用户集中查询质检任务、收检记录、终检/入库任务和质检明细,录入检测数据、不合格处理,并读取 SAP 生产订单信息辅助校验。'],
[/QualityDetails/, '该页面查询质检明细。用户按订单、物料、检验类型、时间等条件查询和导出检测明细。'],
[/QualityStandardQuery/, '该页面维护/查询检验标准。用户按物料、工序、检测项目维护理论值、上下限、量具和启用状态。'],
[/Unqualified/, '该页面处理不合格品。用户查询不合格记录,查看处置流程和节点,推进让步放行、返工、报废等处理。'],
[/saleorderstatus/, '该页面查询销售订单状态。用户按销售订单、合同、物料等条件查看销售订单在 SAP/MES 中的履约、生产、发货或关闭状态。'],
[/MenuManagement/, '该页面维护菜单和权限。管理员查询、增加、修改、删除菜单/角色权限配置,用于动态路由和按钮权限。'],
[/OperationLog/, '该页面查询操作日志或报工操作记录,用于审计用户操作和追踪异常。'],
[/OrgManagement/, '该页面维护组织/角色类基础数据,支持新增、修改、删除和查询。'],
[/PersonnelManagement/, '该页面维护人员账号。管理员维护人员、部门、角色、启用状态等登录和权限基础数据。'],
[/SystemRrolemaintenance/, '该页面维护系统角色。管理员查询、新增、修改、删除角色数据。'],
[/WorkstationManagement/, '该页面维护工位资源。管理员在 MES 侧新增、修改、删除工位,同时调用 SAP B1 Resources 新增或更新资源主数据。'],
[/layout|Sidebar|Navbar|TagsView|AppMain|ResizeHandler/, '该文件属于系统布局组件,负责菜单、导航、标签页、内容承载或窗口自适应,不承载具体业务流程。'],
[/login/, '该页面是登录入口负责账号密码登录、角色选择、Token/Cookie 写入、路由权限初始化和密码修改入口。'],
[/dashboard/, '该页面是系统首页或欢迎页,用于承载看板入口或基础统计展示。'],
[/404/, '该页面是 404 错误页,用于路由不存在时展示错误信息。']
];
const found = rules.find(([re]) => re.test(p));
return [found ? found[1] : '该页面为业务页面,具体功能由表单字段、按钮和存储过程调用共同决定。', ...common].join('\n\n');
}
function pageFlow(page) {
const p = page.relPath;
const flows = [
[/SelfMakePlandone|SelfMakePlando|SelfMakePlan/,
'自制/机加排产流程:\n\n```mermaid\nflowchart LR\n A[输入订单/合同/物料/工序/时间条件] --> B[调用 MES_OrderPlanned_Query 查询待排产任务]\n B --> C[选择任务行]\n C --> D{派工方式}\n D --> E[MES_OrderPlanned_SingleTaskPlanned 单任务派工]\n D --> F[计划排产_二次派工 绑定人员]\n D --> G[计划排产_外协分组]\n E --> H[MES_OrderPlanned_EditTaskPlanned/DescriptionEdit 修改计划字段]\n F --> H\n G --> H\n H --> I[MES_OrderPlanned_Finish 确认排产]\n I --> J[PATCH /ProductionOrders(...) 回写 SAP 生产订单状态]\n J --> K[页面刷新任务状态]\n```\n'],
[/InstallMakePlanDo|InstallMakePlan/,
'装配排产流程:\n\n```mermaid\nflowchart LR\n A[输入订单/合同/物料/工序/时间条件] --> B[MES_OrderPlanned_Queryzp 查询装配任务]\n B --> C[选择任务]\n C --> D[Prog_MES_LoadStations 选择工位]\n D --> E[系统管理_人员管理_所有有效人员查询 选择人员]\n E --> F[计划排产_派工 或 MES_OrderPlanned_SingleTaskPlanned]\n F --> G[计划排产_二次派工]\n G --> H[MES_OrderPlanned_Finish 确认排产]\n H --> I[PATCH /ProductionOrders(...) 或 POST SAP 请求]\n I --> J[刷新排产结果]\n```\n'],
[/MachiningCenter/,
'机加中心执行流程:\n\n```mermaid\nflowchart LR\n A[选择工位/人员/任务条件] --> B[MES_ProductCenter_GetTask 查询可执行任务]\n B --> C[选择任务]\n C --> D[收料 MES_ProductTask_MaterialRecieved]\n D --> E[开始加工 MES_ProductTask_WorkingStart]\n E --> F{生产过程}\n F --> G[暂停/恢复 MES_ProductTask_Rest]\n F --> H[首检/巡检/序检]\n F --> I[报工 MES_ProductTask_ProdReport]\n I --> J[写入操作记录/报工记录]\n J --> K[必要时读取 SAP ProductionOrders]\n K --> L[刷新任务和进行中状态]\n```\n'],
[/AssemblyCenter/,
'装配中心执行流程:\n\n```mermaid\nflowchart LR\n A[选择工位/人员] --> B[MES_ProductCenter_GetTask_zp 查询装配任务]\n B --> C[查看缺料/图纸/工序]\n C --> D[MES_人员工时记录_开始 或生产任务开始]\n D --> E[执行装配]\n E --> F[MES_ProductTask_SendInspection 送检/报工]\n F --> G[收检记录/质检数据]\n G --> H[读取 SAP ProductionOrders 辅助判断]\n H --> I[刷新装配任务]\n```\n'],
[/WorkHoursCheck/,
'工时校验流程:\n\n```mermaid\nflowchart LR\n A[选择人员/订单/工序/日期] --> B[工时校验_获取昨日之前操作记录列表]\n B --> C[选择待校验记录]\n C --> D[usp_ValidateWorkTime 或 usp_BatchValidateWorkTime 计算工时]\n D --> E{校验结果}\n E --> F[工时校验_保存校验记录]\n E --> G[工时校验_批量保存校验记录]\n F --> H[更新校验状态]\n G --> H\n H --> I[GET/PATCH /ProductionOrders 补工序资源]\n I --> J[POST /InventoryGenExits 上传 SAP]\n J --> K[标记已上传/失败留痕]\n```\n'],
[/purchaseIncoming|processReceive|processSpecial|firstSpecial|final|materialReturn|salesReturn|OtherInbound|QualityCenter/,
'质检业务流程:\n\n```mermaid\nflowchart LR\n A[选择检验类型/订单/物料/工序] --> B[查询待检任务]\n B --> C[查看图纸/字典/检验人员]\n C --> D[录入检测数量、合格数、不合格数和检测明细]\n D --> E{检验类型}\n E --> F[来料/入库/退货检验更新]\n E --> G[序检收检新增或更新]\n E --> H[终检/首检/特殊检验保存]\n F --> I[写入 YL_质量检验_质检记录及明细]\n G --> I\n H --> I\n I --> J[必要时查询/回写 SAP 生产订单或单据]\n```\n'],
[/OutsourceMange|SaleOutsourceMange|OutsourceSheet/,
'外协业务流程:\n\n```mermaid\nflowchart LR\n A[输入订单/合同/供应商/工序条件] --> B[查询外协任务或外协报表]\n B --> C[选择外协工序/供应商]\n C --> D[计划排产_派工 或外协分组]\n D --> E[POST SAP 采购/外协相关请求]\n E --> F[外协发料查询/保存]\n F --> G[外协收料/质检记录]\n G --> H[更新打印次数/备注/报表状态]\n```\n']
];
const found = flows.find(([re]) => re.test(p));
if (found) return found[1];
if (!page.procs.length) return '该页面未发现后端存储过程调用,通常不形成完整业务流程。';
const procChain = uniq(page.procs.map(x => x.name)).slice(0, 8).map(x => `\`${x}\``).join(' -> ');
return `通用流程:用户输入查询/维护条件 -> 调用 ${procChain} -> 后端读写 SQL Server -> 页面刷新表格或弹窗结果。`;
}
function fieldMappingTable(page) {
const rows = [
'| 类型 | 前端字段/显示列 | 存储过程参数或表达式 | SQL返回字段/数据库字段线索 |',
'|---|---|---|---|'
];
const mappings = [];
for (const p of page.fields.pushedParams || []) {
const relatedProc = page.procs.find(proc => proc.sqlInfo?.params?.some(x => x.includes(`@${p.name}`)));
const sqlField = relatedProc?.sqlInfo?.features?.selectColumns?.find(x => x.includes(p.name)) || '';
mappings.push(['参数', p.expr, `@${p.name}`, sqlField]);
}
for (const c of page.fields.tableColumns || []) {
if (!c.label && !c.prop) continue;
const sqlField = page.procs
.flatMap(proc => proc.sqlInfo?.features?.selectColumns || [])
.find(x => (c.prop && x.includes(c.prop)) || (c.label && x.includes(c.label))) || '';
mappings.push(['显示列', c.label || c.prop, '', sqlField || c.prop || c.label]);
}
for (const v of page.fields.vModels || []) {
const name = v.split('.').pop();
const param = page.fields.pushedParams.find(x => x.expr.includes(v) || x.expr.includes(name));
mappings.push(['输入字段', v, param ? `@${param.name}` : '', '']);
}
const uniqRows = uniq(mappings.map(x => x.join('|'))).slice(0, 40);
if (!uniqRows.length) return '未从页面模板中抽取到明确的 `v-model`、表格列或 `param.push` 字段映射。';
for (const item of uniqRows) {
const [type, front, param, sql] = item.split('|');
rows.push(`| ${type} | \`${front || '-'}\` | ${param ? `\`${param}\`` : '-'} | ${sql ? `\`${sql}\`` : '-'} |`);
}
return rows.join('\n');
}
function statusFlow(page) {
const lines = [];
for (const proc of page.procs) {
const info = proc.sqlInfo;
if (!info) continue;
const status = info.features.statusLines || [];
const useful = status.filter(x => /状态|排产|派工|加工|任务|质检|关闭|完成|已报工|DocEntry|SAP/i.test(x));
if (useful.length) lines.push(`- \`${proc.name}\`${useful.slice(0, 8).map(x => `\`${x}\``).join('')}`);
}
if (!lines.length) return '未从当前 SQL 静态解析到明确的状态字段变更。';
return lines.join('\n');
}
function sapInterfaceList(page) {
if (!page.sapCalls.length) return '页面无直接 SAP Service Layer 调用。';
const lines = ['| 行号 | 方法 | 接口 | 调用时机/影响判断 | 提交字段线索 |', '|---:|---|---|---|---|'];
for (const c of page.sapCalls) {
const timing = c.url.includes('ProductionOrders')
? '读取或更新 SAP 生产订单。失败会导致 MES 与 SAP 订单状态/工序资源不同步。'
: c.url.includes('InventoryGenExits')
? '上传库存发出/工时相关单据。失败会导致 SAP 未生成对应业务单据。'
: c.url.includes('Resources')
? '新增或更新 SAP 资源主数据。失败会导致 MES 工位与 SAP 资源不一致。'
: c.url.includes('RouteStages')
? '新增或更新 SAP 工序阶段。失败会导致 MES 工序与 SAP RouteStages 不一致。'
: '调用外部接口,失败会影响对应外部系统同步。';
lines.push(`| ${c.line} | \`${c.fn}\` | \`${c.url}\` | ${timing} | ${mdList(c.payloadKeys.slice(0, 12))} |`);
}
return lines.join('\n');
}
function dbCallChain(page) {
if (!page.procs.length) return '无数据库调用链。';
const lines = [];
for (const proc of page.procs) {
const deps = proc.sqlInfo ? depsMd(proc.sqlInfo.deps) : '过程体未解析';
const result = proc.purpose;
lines.push(`- \`${page.relPath}\` -> \`${proc.name}\` -> ${deps} -> ${result}`);
}
return lines.join('\n');
}
function mdList(items, empty = '无') {
if (!items || !items.length) return empty;
return items.map(x => `\`${x}\``).join('、');
}
function depsMd(deps) {
if (!deps || !deps.length) return '未在 SQL 中解析到明确依赖';
const tables = deps.filter(x => x.startsWith('TABLE:')).map(x => x.slice(6));
const views = deps.filter(x => x.startsWith('VIEW:')).map(x => x.slice(5));
const sap = deps.filter(x => x.startsWith('SAP:')).map(x => x.slice(4));
const parts = [];
if (tables.length) parts.push(`表:${mdList(tables)}`);
if (views.length) parts.push(`视图:${mdList(views)}`);
if (sap.length) parts.push(`SAP/链接库:${mdList(sap)}`);
return parts.join('') || '未在 SQL 中解析到明确依赖';
}
function explainProc(proc) {
const info = proc.sqlInfo;
const lines = [];
lines.push(`\`${proc.name}\` 属于 ${businessDomainFromName(proc.name)},前端以 type=${proc.type}${proc.op})调用,页面用途是 ${proc.purpose}`);
if (!info) {
lines.push('当前工作区 SQL 中没有解析到该过程体,因此只能确认前端调用关系,不能准确说明内部读写表、过滤条件和状态变更。');
return lines.join(' ');
}
const f = info.features;
lines.push(`数据库处理摘要:${info.summary}`);
lines.push(`读取/关联对象:${depsMd(info.deps)}`);
if (info.writes.length) lines.push(`写入或更新对象:${mdList(info.writes)}`);
if (info.params.length) lines.push(`入参/分页参数:${mdList(info.params)}`);
if (f.selectColumns.length) lines.push(`主要返回字段线索:${mdList(f.selectColumns)}`);
if (f.filters.length) lines.push(`关键过滤/匹配条件:${f.filters.map(x => `\`${x}\``).join('')}`);
if (f.updates.length) lines.push(`关键写入/状态处理语句线索:${f.updates.map(x => `\`${x}\``).join('')}`);
if (f.grouping.length) lines.push(`排序/分组/分页线索:${f.grouping.map(x => `\`${x}\``).join('')}`);
const flags = [];
if (f.hasPagination) flags.push('分页');
if (f.hasJson) flags.push('JSON 批量解析');
if (f.hasTransaction) flags.push('事务控制');
if (f.hasTryCatch) flags.push('异常捕获');
if (f.hasSap) flags.push('读取 SAP 数据库对象');
if (f.tempTables.length) flags.push(`临时表 ${mdList(f.tempTables)}`);
if (flags.length) lines.push(`实现特征:${flags.join('、')}`);
return lines.join(' ');
}
function procTable(procs) {
if (!procs.length) return '未发现 `CreateData(...)` 存储过程调用。';
const rows = [
'| 行号 | type | 存储过程 | 前端用途 | SQL摘要 | 依赖对象 |',
'|---:|---|---|---|---|---|'
];
for (const p of procs) {
const info = p.sqlInfo;
rows.push(`| ${p.line} | ${p.type}${p.op} | \`${p.name}\` | ${p.purpose} | ${info ? info.summary : '未找到过程体'} | ${info ? depsMd(info.deps) : '未知'} |`);
}
return rows.join('\n');
}
function sapTable(calls, procs) {
const procSap = [];
for (const p of procs) {
if (p.sqlInfo && p.sqlInfo.sapRefs.length) procSap.push(`${p.name}: ${p.sqlInfo.sapRefs.join(', ')}`);
}
if (!calls.length && !procSap.length) return '未发现页面直接调用 SAP B1 Service Layer相关存储过程也未解析到 `SAP.SBO_YL` 链接库引用。';
const lines = [];
if (calls.length) {
lines.push('页面直接 SAP/接口调用:');
for (const c of calls) lines.push(`- 第 ${c.line} 行:\`${c.fn}("${c.url}")\``);
}
if (procSap.length) {
lines.push('存储过程内读取 SAP B1 数据库对象:');
for (const s of procSap) lines.push(`- ${s}`);
}
return lines.join('\n');
}
function techFlags(page) {
const flags = ['Vue 2 Options API', 'Element UI'];
if (page.procs.length) flags.push('`CreateData` + `ExecDatabase` 调用后端过程');
else flags.push('未发现页面级后端存储过程调用');
if (page.sapCalls.length) flags.push('SAP B1 Service Layer`src/api/b1s.js`');
if (page.imports.includes('@/api/tm') || page.sapCalls.some(x => x.fn === 'posttm')) flags.push('TM/OpenAPI 接口(`src/api/tm.js`');
if (page.hasEcharts) flags.push('ECharts');
if (page.hasFullCalendar) flags.push('FullCalendar');
if (page.hasMqtt) flags.push('MQTT');
if (page.hasPdf) flags.push('PDF/图纸预览或导出');
if (page.hasExcel) flags.push('Excel/报表导出');
return flags;
}
function moduleSummary(pages) {
const groups = {};
for (const p of pages) {
const top = p.relPath.split('/')[2] || '其他';
groups[top] ||= [];
groups[top].push(p);
}
const lines = ['| 模块 | 页面数 | 主要能力 |', '|---|---:|---|'];
for (const [mod, ps] of Object.entries(groups)) {
const allProcs = uniq(ps.flatMap(p => p.procs.map(x => x.name)));
const allSap = ps.reduce((n, p) => n + p.sapCalls.length, 0);
const ability = [];
if (allProcs.length) ability.push(`过程 ${allProcs.length}`);
if (allSap) ability.push(`直接 SAP 调用 ${allSap}`);
if (ps.some(p => p.hasEcharts)) ability.push('图表');
if (ps.some(p => p.hasMqtt)) ability.push('设备实时通信');
lines.push(`| ${mod} | ${ps.length} | ${ability.join('、') || '布局/静态页面'} |`);
}
return lines.join('\n');
}
const sqlModel = extractSqlModel();
const pages = walk(viewsRoot, ['.vue', '.js']).sort().map(file => {
const text = readText(file);
const relPath = rel(file);
const procs = extractCreateData(text).map(p => ({
...p,
op: operationByType(p.type),
purpose: procPurpose(p.name),
sqlInfo: sqlModel.procByName[p.name]
}));
const page = {
file,
relPath,
module: moduleNameFromPath(relPath),
lines: text.split(/\r?\n/).length,
labels: extractLabels(text),
methods: extractMethods(text),
fields: extractFrontendFields(text),
procs,
sapCalls: extractSapCalls(text),
imports: extractApiImports(text),
hasMqtt: /mqtt|MQTT/.test(text),
hasEcharts: /echarts|ECharts|\$echarts/.test(text),
hasFullCalendar: /FullCalendar|@fullcalendar/.test(text),
hasPdf: /pdfjs|jspdf|html2canvas|pdf/i.test(text),
hasExcel: /xlsx|exportExcel|Excel|导出/.test(text)
};
page.functionDesc = pageBusinessNarrative(page);
return page;
});
const now = new Date().toISOString().slice(0, 10);
let md = '';
md += `# MES_Manage_View_V20 src/views 逐页面详细分析\n\n`;
md += `生成日期:${now}\n\n`;
md += `分析范围:\`MES_Manage_View_V20/src/views\`${pages.length} 个 .vue/.js 文件;辅助读取 \`src/api\`\`config/index.js\`\`static/config.js\` 和 SQL 脚本。\n\n`;
md += `## 重要说明\n\n`;
md += `- 页面功能不是只按文件名猜测,报告同时参考页面路径、按钮/字段文案、方法名、存储过程名和 SQL 过程体。\n`;
md += `- 存储过程处理说明来自 SQL 静态解析读写对象、参数、返回字段、过滤条件、更新语句、分组分页、JSON、事务、SAP 链接库等。\n`;
md += `- 如果某个过程没有解析到过程体,报告会明确说明不能准确判断内部表/视图和处理逻辑。\n\n`;
md += `## 技术路线总览\n\n`;
md += `- 前端Vue 2.7、Vue Router、Vuex、Element UI页面以 Options API 为主。\n`;
md += `- 数据访问:页面调用 \`this.CreateData(type, procedureName, params[, pageSize, pageCurrent])\` 组包,再调用 \`this.ExecDatabase(data)\` 访问后端。\n`;
md += `- 后端:统一接口接收 type/name/param执行 SQL Server 存储过程,返回 \`response.data\` 和分页信息。\n`;
md += `- 数据库:\`YL_MESDB\`,核心对象包括生产计划/生产任务、加工中心操作记录、质检记录、设备数据、权限菜单等表和视图。\n`;
md += `- SAP\`src/api/b1s.js\` 通过 \`window.dt_Config.baseURLB1\` 访问 B1 Service Layer部分过程也跨库读取 \`SAP.SBO_YL.dbo.*\`\n\n`;
md += `## 模块汇总\n\n${moduleSummary(pages)}\n\n`;
const allSapCalls = [];
for (const p of pages) {
for (const c of p.sapCalls) allSapCalls.push({ page: p.relPath, ...c });
}
if (allSapCalls.length) {
md += `## SAP 交互总清单\n\n`;
md += `| 页面 | 行号 | 方法 | 接口 | 提交字段线索 |\n|---|---:|---|---|---|\n`;
for (const c of allSapCalls) {
md += `| \`${c.page}\` | ${c.line} | \`${c.fn}\` | \`${c.url}\` | ${mdList(c.payloadKeys.slice(0, 10))} |\n`;
}
md += `\n`;
}
for (let i = 0; i < pages.length; i++) {
const p = pages[i];
md += `## ${i + 1}. ${p.relPath}\n\n`;
md += `**页面位置**\`${p.relPath}\` \n`;
md += `**所属模块**${p.module} \n`;
md += `**源码行数**${p.lines}\n\n`;
md += `### 页面功能\n\n${p.functionDesc}\n\n`;
md += `### 页面流程图\n\n${pageFlow(p)}\n\n`;
md += `### 用户操作与页面线索\n\n`;
md += `可见按钮/字段线索:${mdList(p.labels.slice(0, 18))}\n\n`;
md += `主要方法线索:${mdList(p.methods.slice(0, 18))}\n\n`;
md += `### 字段映射表\n\n${fieldMappingTable(p)}\n\n`;
md += `### 技术路线\n\n${techFlags(p).map(x => `- ${x}`).join('\n')}\n\n`;
md += `### 调用的存储过程总览\n\n${procTable(p.procs)}\n\n`;
md += `### 与 SAP 的交互\n\n${sapTable(p.sapCalls, p.procs)}\n\n`;
md += `### SAP 接口级说明\n\n${sapInterfaceList(p)}\n\n`;
md += `### 状态流转说明\n\n${statusFlow(p)}\n\n`;
md += `### 数据库调用链\n\n${dbCallChain(p)}\n\n`;
md += `### 存储过程处理逻辑明细\n\n`;
if (!p.procs.length) {
md += `无页面级存储过程调用。\n\n`;
} else {
for (const proc of p.procs) md += `- ${explainProc(proc)}\n`;
md += `\n`;
}
}
md += `## SQL 对象总览\n\n`;
md += `脚本中解析到表/视图 ${sqlModel.objects.length} 个、存储过程 ${Object.keys(sqlModel.procByName).length} 个。\n\n`;
md += `### 表/视图清单\n\n`;
for (const obj of sqlModel.objects) md += `- ${obj.type === 'TABLE' ? '表' : '视图'}\`${obj.name}\`\n`;
fs.writeFileSync(outFile, md, 'utf8');
console.log(outFile);

View File

@@ -0,0 +1,206 @@
param(
[Parameter(Mandatory = $true)]
[string]$PlanMarkdown,
[Parameter(Mandatory = $true)]
[string]$SourceMarkdown,
[Parameter(Mandatory = $true)]
[string]$OutputDocx
)
$ErrorActionPreference = 'Stop'
function Escape-XmlText {
param([AllowNull()][string]$Text)
if ($null -eq $Text) { return '' }
return [System.Security.SecurityElement]::Escape($Text)
}
function Add-Paragraph {
param(
[System.Collections.Generic.List[string]]$Parts,
[string]$Text,
[string]$Style = $null
)
$styleXml = ''
if ($Style) {
$styleXml = "<w:pPr><w:pStyle w:val=`"$Style`"/></w:pPr>"
}
$Parts.Add("<w:p>$styleXml<w:r><w:t xml:space=`"preserve`">$(Escape-XmlText $Text)</w:t></w:r></w:p>")
}
function Add-Table {
param(
[System.Collections.Generic.List[string]]$Parts,
[string[]]$Headers,
[object[]]$Rows
)
$tbl = New-Object System.Text.StringBuilder
[void]$tbl.Append('<w:tbl>')
[void]$tbl.Append('<w:tblPr><w:tblStyle w:val="TableGrid"/><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders></w:tblPr>')
[void]$tbl.Append('<w:tr>')
foreach ($h in $Headers) {
[void]$tbl.Append('<w:tc><w:tcPr><w:shd w:val="clear" w:color="auto" w:fill="D9EAF7"/></w:tcPr><w:p><w:r><w:rPr><w:b/></w:rPr><w:t xml:space="preserve">')
[void]$tbl.Append((Escape-XmlText $h))
[void]$tbl.Append('</w:t></w:r></w:p></w:tc>')
}
[void]$tbl.Append('</w:tr>')
foreach ($row in $Rows) {
[void]$tbl.Append('<w:tr>')
foreach ($cell in $row) {
[void]$tbl.Append('<w:tc><w:p><w:r><w:t xml:space="preserve">')
[void]$tbl.Append((Escape-XmlText ([string]$cell)))
[void]$tbl.Append('</w:t></w:r></w:p></w:tc>')
}
[void]$tbl.Append('</w:tr>')
}
[void]$tbl.Append('</w:tbl>')
$Parts.Add($tbl.ToString())
}
function Flush-TableBuffer {
param(
[System.Collections.Generic.List[string]]$Parts,
[System.Collections.Generic.List[string]]$Buffer
)
if ($Buffer.Count -lt 2) {
foreach ($line in $Buffer) {
Add-Paragraph -Parts $Parts -Text $line
}
$Buffer.Clear()
return
}
$header = $Buffer[0].Trim('|').Split('|') | ForEach-Object { $_.Trim() }
$rows = @()
for ($i = 2; $i -lt $Buffer.Count; $i++) {
$rows += ,($Buffer[$i].Trim('|').Split('|') | ForEach-Object { $_.Trim() })
}
Add-Table -Parts $Parts -Headers $header -Rows $rows
$Buffer.Clear()
}
function Add-MarkdownContent {
param(
[System.Collections.Generic.List[string]]$Parts,
[string]$MarkdownPath
)
$lines = [System.IO.File]::ReadAllLines($MarkdownPath, [System.Text.Encoding]::UTF8)
$tableBuffer = New-Object System.Collections.Generic.List[string]
$inCode = $false
foreach ($line in $lines) {
if ($line.TrimStart().StartsWith('```')) {
if ($tableBuffer.Count -gt 0) { Flush-TableBuffer -Parts $Parts -Buffer $tableBuffer }
$inCode = -not $inCode
Add-Paragraph -Parts $Parts -Text $line -Style 'Code'
continue
}
if ($inCode) {
Add-Paragraph -Parts $Parts -Text $line -Style 'Code'
continue
}
if ($line.TrimStart().StartsWith('|') -and $line.TrimEnd().EndsWith('|')) {
$tableBuffer.Add($line)
continue
}
if ($tableBuffer.Count -gt 0) { Flush-TableBuffer -Parts $Parts -Buffer $tableBuffer }
if ($line.StartsWith('### ')) {
Add-Paragraph -Parts $Parts -Text $line.Substring(4) -Style 'Heading3'
} elseif ($line.StartsWith('## ')) {
Add-Paragraph -Parts $Parts -Text $line.Substring(3) -Style 'Heading2'
} elseif ($line.StartsWith('# ')) {
Add-Paragraph -Parts $Parts -Text $line.Substring(2) -Style 'Heading1'
} elseif ($line.Trim().StartsWith('- ')) {
Add-Paragraph -Parts $Parts -Text ('· ' + $line.Trim().Substring(2))
} elseif ([string]::IsNullOrWhiteSpace($line)) {
Add-Paragraph -Parts $Parts -Text ''
} else {
Add-Paragraph -Parts $Parts -Text $line
}
}
if ($tableBuffer.Count -gt 0) { Flush-TableBuffer -Parts $Parts -Buffer $tableBuffer }
}
if (-not (Test-Path -LiteralPath $PlanMarkdown)) { throw "Plan markdown not found: $PlanMarkdown" }
if (-not (Test-Path -LiteralPath $SourceMarkdown)) { throw "Source markdown not found: $SourceMarkdown" }
$outputDir = Split-Path -Path $OutputDocx -Parent
if (-not (Test-Path -LiteralPath $outputDir)) {
New-Item -ItemType Directory -Path $outputDir | Out-Null
}
$tempRoot = Join-Path $env:TEMP ("docx_" + [Guid]::NewGuid().ToString('N'))
$wordDir = Join-Path $tempRoot 'word'
$relsDir = Join-Path $tempRoot '_rels'
$wordRelsDir = Join-Path $wordDir '_rels'
New-Item -ItemType Directory -Path $wordDir, $relsDir, $wordRelsDir | Out-Null
$parts = New-Object System.Collections.Generic.List[string]
Add-MarkdownContent -Parts $parts -MarkdownPath $PlanMarkdown
Add-MarkdownContent -Parts $parts -MarkdownPath $SourceMarkdown
$documentXml = @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
$($parts -join "`n")
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1080" w:bottom="1440" w:left="1080" w:header="720" w:footer="720" w:gutter="0"/>
</w:sectPr>
</w:body>
</w:document>
"@
$stylesXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="paragraph" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:sz w:val="21"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:before="360" w:after="120"/><w:outlineLvl w:val="0"/></w:pPr><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="30"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:before="280" w:after="100"/><w:outlineLvl w:val="1"/></w:pPr><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="26"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="heading 3"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:before="220" w:after="80"/><w:outlineLvl w:val="2"/></w:pPr><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="23"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Code"><w:name w:val="Code"/><w:basedOn w:val="Normal"/><w:qFormat/><w:rPr><w:rFonts w:ascii="Consolas" w:eastAsia="Microsoft YaHei" w:hAnsi="Consolas"/><w:sz w:val="18"/></w:rPr></w:style>
<w:style w:type="table" w:styleId="TableGrid"><w:name w:val="Table Grid"/><w:basedOn w:val="TableNormal"/><w:uiPriority w:val="59"/><w:tblPr><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders></w:tblPr></w:style>
</w:styles>
'@
$contentTypesXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
</Types>
'@
$relsXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>
'@
$docRelsXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>
'@
[System.IO.File]::WriteAllText((Join-Path $tempRoot '[Content_Types].xml'), $contentTypesXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $relsDir '.rels'), $relsXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $wordDir 'document.xml'), $documentXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $wordDir 'styles.xml'), $stylesXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $wordRelsDir 'document.xml.rels'), $docRelsXml, [System.Text.Encoding]::UTF8)
if (Test-Path -LiteralPath $OutputDocx) {
Remove-Item -LiteralPath $OutputDocx -Force
}
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory($tempRoot, $OutputDocx)
Remove-Item -LiteralPath $tempRoot -Recurse -Force
Write-Output $OutputDocx