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 = [ /]*>([\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 = /]*)>/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);