Files
yl/analyze_mes_v20_views.js
2026-05-27 18:36:20 +08:00

732 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);