732 lines
43 KiB
JavaScript
732 lines
43 KiB
JavaScript
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);
|