const fs = require('fs'); const path = require('path'); const repoRoot = process.cwd(); const projectRoot = path.join(repoRoot, 'MES_Manage_View_V20'); const viewsRoot = path.join(projectRoot, 'src', 'views'); const manualPath = path.join(projectRoot, '元利项目详细用户使用说明书.md'); function readText(file) { const buf = fs.readFileSync(file); if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) { return buf.slice(3).toString('utf8'); } if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) { return buf.slice(2).toString('utf16le'); } return buf.toString('utf8'); } function writeText(file, text) { fs.writeFileSync(file, text, 'utf8'); } function walk(dir, out = []) { if (!fs.existsSync(dir)) return out; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) walk(full, out); else if (/\.(vue|js)$/i.test(entry.name)) out.push(full); } return out; } function rel(file) { return path.relative(projectRoot, file).replace(/\\/g, '/'); } function clean(value) { return (value || '') .replace(//g, ' ') .replace(/<[^>]+>/g, ' ') .replace(/\{\{[\s\S]*?\}\}/g, ' ') .replace(/ /g, ' ') .replace(/\s+/g, ' ') .trim(); } function uniq(values) { const seen = new Set(); const out = []; for (const value of values) { const v = String(value || '').trim(); if (!v || seen.has(v)) continue; seen.add(v); out.push(v); } return out; } function stripNoise(values) { return uniq(values) .filter(x => x.length <= 42) .filter(x => !/[{};]/.test(x)) .filter(x => !/^(scope|row|item|index|true|false|null|undefined)$/i.test(x)) .filter(x => !/^\d+$/.test(x)); } function extractAll(re, text, map) { const out = []; let match; while ((match = re.exec(text))) out.push(map(match)); return out; } function attr(attrs, name) { const m = new RegExp(`${name}="([^"]*)"`).exec(attrs); return m ? clean(m[1]) : ''; } function extractTemplate(text) { const start = text.search(/]*>/i); if (start < 0) return text; const open = /]*>/i.exec(text.slice(start)); if (!open) return text; const contentStart = start + open.index + open[0].length; const scriptStart = text.search(/= 0 ? scriptStart : text.length; const close = text.lastIndexOf('', searchEnd); if (close > contentStart) return text.slice(contentStart, close); return text.slice(contentStart, searchEnd); } function extractScript(text) { const m = /]*>([\s\S]*?)<\/script>/i.exec(text); return m ? m[1] : text; } function extractPage(file) { const text = readText(file); const template = extractTemplate(text); const script = extractScript(text); const relative = rel(file); const parts = relative.split('/'); const module = parts[2] || '根页面'; const name = path.basename(file) === 'index.vue' ? parts[parts.length - 2] : path.basename(file); const labels = extractAll(/\blabel="([^"]+)"/g, template, m => clean(m[1])); const placeholders = extractAll(/\bplaceholder="([^"]+)"/g, template, m => clean(m[1])); const titles = extractAll(/\btitle="([^"]+)"/g, template, m => clean(m[1])); const formLabels = extractAll(/]*)>/g, template, m => attr(m[1], 'label')); const tabLabels = extractAll(/]*)>/g, template, m => attr(m[1], 'label')); const options = extractAll(/]*)>/g, template, m => attr(m[1], 'label')); const buttons = extractAll(/]*)>([\s\S]*?)<\/el-button>/g, template, m => { const textValue = clean(m[2]) || attr(m[1], 'title') || attr(m[1], 'aria-label'); const click = attr(m[1], '@click') || attr(m[1], 'v-on:click'); return textValue ? { text: textValue, click } : null; }) .filter(Boolean) .filter(b => b.text.length <= 42) .filter(b => !/[{};]/.test(b.text)); const tableColumns = extractAll(/]*)>/g, template, m => { const label = attr(m[1], 'label'); const prop = attr(m[1], 'prop'); const type = attr(m[1], 'type'); return label || prop || type ? { label, prop, type } : null; }).filter(Boolean); const vModels = extractAll(/\bv-model(?:\.[\w-]+)?="([^"]+)"/g, template, m => clean(m[1])); const params = extractAll(/\.(?:push|unshift)\s*\(\s*\[\s*['"]([^'"]+)['"]\s*,\s*([^\]\n]+)\]/g, script, m => ({ name: clean(m[1]), expr: clean(m[2]) })); const procs = extractAll(/CreateData\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g, script, m => ({ type: m[1], name: clean(m[2]) })); const sapCalls = extractAll(/\b(getB1|postB1|patchB1|posttm)\s*\(\s*([`'"])([\s\S]*?)\2/g, script, m => ({ method: m[1], url: clean(m[3]).slice(0, 90) })); const dialogs = extractAll(//g, template, m => { const title = attr(m[1], 'title') || clean((/:title="([^"]+)"/.exec(m[1]) || [])[1]); return title; }); const methods = extractAll(/^\s*([A-Za-z_$\u4e00-\u9fa5][\w$\u4e00-\u9fa5]*)\s*\([^)]*\)\s*\{/gm, script, m => m[1]); const hasCalendar = /FullCalendar|@fullcalendar|eventDrop|eventResize/.test(text); const hasPagination = /el-pagination|pageSize|pageCurrent/.test(text); const hasUpload = /el-upload|上传|upload/i.test(text); const hasTree = /el-tree|treeData|Tree/.test(text); return { file, relative, module, name, text, labels: stripNoise(labels), placeholders: stripNoise(placeholders), titles: stripNoise(titles), formLabels: stripNoise(formLabels), tabLabels: stripNoise(tabLabels), options: stripNoise(options), buttons, tableColumns, vModels: stripNoise(vModels), params, procs, sapCalls, dialogs: stripNoise(dialogs), methods: stripNoise(methods), hasCalendar, hasPagination, hasUpload, hasTree }; } function roleFor(page) { if (/SystemMaintenance|layout|login|dashboard|404/.test(page.relative)) return '系统管理员/相关用户'; if (/QualityManagement/.test(page.relative)) return '质检员/质量主管'; if (/PlanManagement/.test(page.relative)) return '计划员/生产主管'; if (/ProductionManagement/.test(page.relative)) return '操作工/班组长/生产主管'; if (/ProcessManagement|CraftManagement/.test(page.relative)) return '工艺员/技术人员'; if (/DeviceManagement/.test(page.relative)) return '设备管理员/生产主管'; if (/SaleManagement/.test(page.relative)) return '销售/计划/生产跟单人员'; if (/AnomalousManagement/.test(page.relative)) return '班组长/生产主管/计划员'; return '相关业务人员'; } function moduleName(page) { const map = { AnomalousManagement: '异常管理', CraftManagement: '产品结构维护', DeviceManagement: '设备管理', PlanManagement: '计划排产', ProcessManagement: '工艺与图纸管理', ProductionManagement: '生产执行管理', QualityManagement: '质量管理', SaleManagement: '销售与交付查询', SystemMaintenance: '系统维护', dashboard: '首页与看板', layout: '布局与导航', login: '登录', }; if (page.relative === 'src/views/404.vue') return '错误页'; return map[page.module] || page.module; } function purpose(page) { const p = page.relative; const rules = [ [/404\.vue$/, '该页面用于路由不存在或无效地址访问时展示 404 提示,帮助用户返回首页或重新选择菜单。'], [/login/, '该页面是系统登录入口,用户输入工号、密码并选择角色后进入 MES;同时提供修改密码功能。'], [/dashboard/, '该页面是系统首页/看板入口,用户登录后查看系统概览或进入各业务模块。'], [/PunctualWork/, '该页面用于查看未及时开工、待执行、可执行、已开工和已完成任务,帮助班组长或计划员催办生产开工。'], [/CraftMaintain/, '该页面用于维护产品结构树,支持新增、编辑、删除产品结构节点,为工艺、BOM 和生产任务提供层级基础资料。'], [/DeviceDataHistory/, '该页面用于查询设备历史采集数据,按工位、设备、参数和时间追溯设备运行记录。'], [/DeviceInformation/, '该页面用于维护设备台账、设备属性、维修/工时/点检相关信息。'], [/DeviceRealData|Devicedata/, '该页面用于查看设备实时数据或采集缓存数据,判断设备当前运行、报警和参数状态。'], [/SelfMakePlandone[\\/]/, '该页面用于机加/自制件已排产任务查看和调整,支持计划时间、指派对象、指派数量、最晚开始、二次派工和备注维护。'], [/SelfMakePlando[\\/]/, '该页面用于机加/自制件待排产任务处理,支持表单排产、日程拖拽调整、部分派工、批量排产、二次派工、原料状态和图纸查看。'], [/SelfMakePlan[\\/]/, '该页面用于机加/自制件排产前准备,查询生产订单工序任务并维护计划、工位、数量和说明后确认排产。'], [/InstallMakePlanDo|InstallMakePlan/, '该页面用于装配件排产,维护装配工位、人员、计划开始/完成、优先级、说明,并在排产完成时同步 SAP 或生成相关请求。'], [/PlanShell/, '该页面用于计划外壳和计划跟踪,集中查看订单计划、齐套、缺料、发货或关闭相关信息。'], [/PlannOrderClose/, '该页面用于关闭生产订单,用户查询待关闭订单后执行 MES/SAP 关闭动作。'], [/priorityinstall/, '该页面用于装配计划优先级和日程调整,支持表单和日程方式查看重点任务。'], [/ProductionOrder/, '该页面用于查询和同步生产订单,把 SAP/ERP 生产订单转成 MES 生产计划与工序任务。'], [/PartDrawing/, '该页面用于维护零件图纸,支持按物料查询、查看、上传/验证和删除图纸。'], [/PartDrawlook|ProcurementDrawlook/, '该页面用于按物料查看零件图纸,供生产、采购、外协或质检快速查阅技术文件。'], [/ProcessDrawing/, '该页面用于维护工艺图纸,支持查询、查看、上传/验证和删除工艺图纸。'], [/ProcedureManagement/, '该页面用于维护工序/路线阶段主数据,并与 SAP B1 RouteStages 保持一致。'], [/ProcessInquiry/, '该页面用于查询标准工艺或生产工艺,供排产、生产、质检核对工序路线。'], [/ProcessProduct|ProcessesDevelop/, '该页面用于维护产品工艺资料、工序路线、参数和图纸关系。'], [/MachiningCenter/, '该页面是机加生产执行主界面,操作工按工位执行收料、开工、暂停、报工、检验、退库、发料和图纸查看。'], [/AssemblyCenter/, '该页面是装配生产执行主界面,装配人员按工位执行开工、报工、送检、退库、缺料查询和图纸查看。'], [/ProductionTaskManage|AssembleTaskManage/, '该页面用于生产/装配任务管理,查询任务、查看工时、维护派工和任务说明。'], [/ProductionTasksSum|AssembleTasksSum/, '该页面用于生产/装配任务汇总,按订单、人员、工位、状态和时间统计任务执行情况。'], [/ProductionPlanTrack|AssemblePlanTrack/, '该页面用于跟踪生产/装配计划执行进度,查看计划、完成、延期和异常情况。'], [/AssembleStatus|DeviceStatus/, '该页面用于查看现场状态,关注工位、设备、任务是否开工、进行中或完成。'], [/OutsourceMange|SaleOutsourceMange/, '该页面用于外协任务管理,处理外协派工、供应商、发料、收料、质检记录和 SAP 采购请求。'], [/OutsourceSheet/, '该页面用于外协报表查询和导出,跟踪外协订单、供应商、数量、质检和结算状态。'], [/PickingTask/, '该页面用于生产领料/拣配任务查询与处理,核对订单物料和库存任务。'], [/PersonnelBinding/, '该页面用于维护人员与工位绑定关系,控制或提示人员可操作的工位。'], [/ShiftManagement/, '该页面用于维护班次、班次人员和排班基础数据。'], [/Timesheet|Workhours$/, '该页面用于查询工时记录,按人员、设备、任务、订单和日期统计工时。'], [/WorkhoursEdit/, '该页面用于修正或补录工时/报工记录,处理异常工时。'], [/WorkHoursCheck/, '该页面用于工时校验和 SAP 提交,用户核对报工记录后批量或单条校验并上传相关单据。'], [/Urgent/, '该页面用于维护加急任务,跟踪加急订单、加急数量和完成情况。'], [/AbnormalControl/, '该页面用于生产异常管理,查询异常、处理异常、转办、查看进度和关闭异常。'], [/QualityCenter/, '该页面是质量综合处理中心,集中处理待检、收检、检测明细、不合格和 SAP/WMS 联动。'], [/QualityDetails/, '该页面用于查询质检明细,按订单、物料、检验类型和时间追溯检测记录。'], [/QualityStandardQuery/, '该页面用于查询或维护质量检验标准、检测项目、上下限和启用状态。'], [/Unqualified/, '该页面用于不合格品处理,记录处置方式、责任、原因和处理结果。'], [/CheckTask[\\/].*purchaseIncoming/, '该页面用于采购/来料检验,质检人员录入来料检验数量、合格数、不合格数和检测明细。'], [/CheckTask[\\/].*processReceive/, '该页面用于工序收检/序检收检,质检人员接收工序检验任务并录入检验结果。'], [/CheckTask[\\/].*firstSpecial/, '该页面用于首检或特殊首件检验,录入首件检测结果。'], [/CheckTask[\\/].*final/, '该页面用于终检任务处理,录入最终检验结果。'], [/CheckTask[\\/].*materialReturn/, '该页面用于材料退库检验,核对退库物料并录入检验结论。'], [/CheckTask[\\/].*salesReturn/, '该页面用于销售退货检验,录入退货质检结果。'], [/CheckTask[\\/].*OtherInbound/, '该页面用于其他入库检验,录入其他入库质检结果。'], [/CheckTask[\\/].*processSpecial/, '该页面用于过程特殊检验,录入特殊检验项目和结果。'], [/saleorderstatus/, '该页面用于查询销售订单状态,跟踪销售订单在生产、交付和关闭过程中的状态。'], [/MenuManagement/, '该页面用于维护系统菜单、按钮权限和路由配置。'], [/OperationLog/, '该页面用于查询操作日志,追溯用户操作、报工或业务变更记录。'], [/OrgManagement/, '该页面用于维护组织、部门或组织树基础资料。'], [/PersonnelManagement/, '该页面用于维护人员账号、部门、角色和启用状态。'], [/SystemRrolemaintenance/, '该页面用于维护系统角色和角色权限。'], [/WorkstationManagement/, '该页面用于维护工位/资源主数据,并同步 SAP B1 Resources。'], [/layout|Sidebar|Navbar|TagsView|AppMain|ResizeHandler/, '该文件属于系统布局和导航组件,用户通过它切换菜单、标签页、折叠菜单或退出登录,不维护具体业务单据。'], ]; const found = rules.find(([re]) => re.test(p)); if (found) return found[1]; return '该页面为业务页面,用户按查询条件筛选数据,在列表或弹窗中查看明细,并按页面按钮完成维护、提交或状态更新。'; } function actionVerb(button) { const text = button.text; if (/查询|搜索|全部|待执行|可执行|已开工|已完成|未完成/.test(text)) return '查询/筛选'; if (/新增|添加|新建|创建/.test(text)) return '新增'; if (/编辑|修改|保存|确定|确 定/.test(text)) return '编辑/保存'; if (/删除|移除/.test(text)) return '删除'; if (/导出|打印/.test(text)) return '导出/打印'; if (/查看|详情|图纸|明细|进度/.test(text)) return '查看'; if (/排产|派工|开工|报工|送检|收料|发料|退库|关闭|提交|处理|转办|完结|校验|上传/.test(text)) return '业务提交'; if (/取消|关闭|返回/.test(text)) return '取消/关闭'; return '操作'; } function fieldName(value) { return value .replace(/^.*\./, '') .replace(/\[[^\]]+\]/g, '') .replace(/Form$/, '') .replace(/Time$/, '时间') .replace(/No$/, '编号'); } function queryItems(page) { const fromParams = page.params .filter(p => !/任务列表|操作人|最后编辑人|editstr|personnel|TaskAID/.test(p.name)) .map(p => p.name); const fromModels = page.vModels .filter(v => !/^scope\.row|^item\.|^row\.|multipleSelection|loading|dialog|Visible|activeTab/.test(v)) .map(fieldName); const fromPlaceholders = page.placeholders .map(x => x.replace(/^请输入/, '').replace(/^请选择/, '').replace(/起始|结束/g, '').trim()) .filter(Boolean); return stripNoise([...fromParams, ...fromModels, ...fromPlaceholders]).slice(0, 22); } function tableItems(page) { return stripNoise(page.tableColumns.map(c => c.label || c.prop || c.type)).slice(0, 34); } function buttonItems(page) { return stripNoise(page.buttons.map(b => b.text)).slice(0, 30); } function procItems(page) { return uniq(page.procs.map(p => p.name)).slice(0, 20); } function sapItems(page) { return uniq(page.sapCalls.map(s => `${s.method} ${s.url}`)).slice(0, 12); } function lineList(values, fallback) { if (!values.length) return `- ${fallback}`; return values.map(v => `- \`${v}\``).join('\n'); } function detailedSteps(page) { const steps = []; const queries = queryItems(page); const tabs = page.tabLabels; const columns = tableItems(page); const buttons = page.buttons.slice(0, 24); const dialogs = page.dialogs; const forms = page.formLabels; const procs = procItems(page); const pathText = page.relative; steps.push(`从左侧菜单进入该页,确认页面标题、当前账号和业务角色正确。`); if (tabs.length) { steps.push(`先查看页签:${tabs.map(x => `\`${x}\``).join('、')};需要表格处理时进入表单/列表页签,需要看时间排布时进入日程/看板页签。`); } if (queries.length) { steps.push(`在查询区按需要填写筛选条件:${queries.slice(0, 12).map(x => `\`${x}\``).join('、')}。`); if (queries.length > 12) steps.push(`如果仍查不到数据,继续补充条件:${queries.slice(12, 22).map(x => `\`${x}\``).join('、')},或清空过窄条件后重新查询。`); } else { steps.push(`该页没有识别到固定查询区,按页面提示直接查看信息或使用菜单/按钮进入下一步。`); } const queryButtons = buttons.filter(b => actionVerb(b) === '查询/筛选'); if (queryButtons.length) { steps.push(`点击 ${stripNoise(queryButtons.map(b => b.text)).slice(0, 8).map(x => `\`${x}\``).join('、')} 刷新列表;状态按钮会切换任务范围。`); } else if (buttons.length) { steps.push(`点击页面上的查询或刷新类按钮获取最新数据;如果页面自动加载,则先核对默认列表是否符合当前业务范围。`); } if (page.hasPagination) { steps.push(`列表较多时使用分页区切换页码或调整每页条数,避免只检查第一页。`); } if (columns.length) { steps.push(`查询后先核对关键列:${columns.slice(0, 12).map(x => `\`${x}\``).join('、')}。`); if (columns.length > 12) steps.push(`继续横向滚动或展开表格,核对:${columns.slice(12, 26).map(x => `\`${x}\``).join('、')}。`); } if (page.hasTree) { steps.push(`如果页面左侧或上方有树形结构,先选中组织、物料、菜单或产品节点,再查看右侧列表;新增子节点前确认父节点选中正确。`); } const nonQueryButtons = buttons.filter(b => !['查询/筛选', '取消/关闭'].includes(actionVerb(b))); for (const button of nonQueryButtons.slice(0, 10)) { const verb = actionVerb(button); if (verb === '新增') steps.push(`需要新增时点击 \`${button.text}\`,在弹窗中逐项填写必填字段,确认编码、名称、状态等基础信息后保存。`); else if (verb === '编辑/保存') steps.push(`需要维护已有记录时先选中目标行,再点击或触发表格内 \`${button.text}\`,修改字段后保存并等待成功提示。`); else if (verb === '删除') steps.push(`需要删除时先确认该记录没有正在使用的业务数据,再点击 \`${button.text}\`,按确认提示执行。`); else if (verb === '导出/打印') steps.push(`需要留存或流转时点击 \`${button.text}\`,导出/打印前确认当前查询条件和选中记录正确。`); else if (verb === '查看') steps.push(`需要查看明细时点击 \`${button.text}\`,在弹窗或新区域核对图纸、明细、进度、记录或关联单据。`); else if (verb === '业务提交') steps.push(`需要推进业务时先选中目标行并核对数量、时间、人员、工位、物料和备注,再点击 \`${button.text}\` 提交。`); else steps.push(`按业务需要点击 \`${button.text}\`,操作前先确认选中行和输入内容。`); } if (dialogs.length) { steps.push(`出现弹窗时按弹窗标题处理:${dialogs.slice(0, 8).map(x => `\`${x}\``).join('、')};弹窗关闭前确认必填项已保存。`); } if (forms.length) { steps.push(`弹窗或表单内重点填写:${forms.slice(0, 14).map(x => `\`${x}\``).join('、')},提交前逐项核对。`); } if (page.hasUpload) { steps.push(`涉及上传时先选择正确文件,再点击上传/保存;上传后重新查询,确认文件名、版本、上传时间或预览内容已显示。`); } if (page.hasCalendar) { steps.push(`进入日程页后,单击任务查看详情;拖拽任务可调整计划开始时间,拉伸任务可调整计划完成时间,系统提示确认后才会保存。`); steps.push(`日程保存失败或取消时任务会恢复原日期;保存成功后回到表单页重新查询,核对计划开始和计划完成是否同步。`); } if (procs.length) { steps.push(`保存或提交成功后重新点击查询,确认页面数据已通过 ${procs.slice(0, 5).map(x => `\`${x}\``).join('、')} 等后台过程更新。`); } if (page.sapCalls.length) { steps.push(`涉及 SAP/WMS 接口时,必须查看页面成功或失败提示;失败时先记录错误信息,不要连续重复提交同一单据。`); } if (/SelfMakePlando[\\/]/.test(pathText)) { steps.push(`在 \`指派数量\` 中输入小于计划数的数量时,表示本次只派出部分数量,剩余数量需要继续跟踪和后续派工。`); steps.push(`点击 \`批量排产\` 前应选择同一生产订单的多道工序,系统会按订单行号排序并继承上一道工序的部分计划信息。`); steps.push(`点击 \`排产完成\` 后,系统校验指派对象、指派数量、计划开始、计划完成,并同步生产订单最早开始和最晚完成。`); } if (/SelfMakePlandone[\\/]/.test(pathText)) { steps.push(`已排产列表中仍可调整指派对象、指派数量、计划开始、计划完成、外协分组、最晚开始和二次派工;调整后要重新查询确认。`); } if (/MachiningCenter|AssemblyCenter/.test(pathText)) { steps.push(`现场执行类操作必须按实际顺序处理:先确认任务和工位,再收料/开工,完成加工或装配后再报工/送检。`); } if (/QualityManagement/.test(pathText)) { steps.push(`录入检验结果时先核对检验类型、订单、物料、批次/工序,再填写合格数、不合格数和检测明细,避免检验记录挂错任务。`); } if (/WorkHoursCheck/.test(pathText)) { steps.push(`工时校验前先比对 MES 报工、设备工时和 SAP 工序资源;只对核对无误的记录执行提交。`); } if (/WorkstationManagement/.test(pathText)) { steps.push(`工位新增或修改会影响 SAP 资源、派工、报工和看板,保存后要用排产或生产执行页面验证工位是否可选。`); } if (/MenuManagement|SystemRrolemaintenance|PersonnelManagement/.test(pathText)) { steps.push(`权限类修改保存后,需要让相关用户重新登录或刷新权限,再验证菜单、按钮和数据范围是否生效。`); } return steps.map((s, i) => `${i + 1}. ${s}`).join('\n'); } function notes(page) { const values = []; if (/PlanManagement/.test(page.relative)) values.push('排产、派工和计划时间会影响生产看板、及时开工统计和 SAP 生产订单日期,提交前必须核对。'); if (/ProductionManagement/.test(page.relative)) values.push('生产执行页面的数据会直接影响报工、工时、质检和库存任务,现场操作必须与实际加工进度一致。'); if (/QualityManagement/.test(page.relative)) values.push('质检结果保存后会影响合格入库、不合格处理和后续工序,提交前必须核对数量。'); if (/ProcessManagement|CraftManagement/.test(page.relative)) values.push('工艺、图纸和结构资料会被排产、生产、质检引用,版本错误会直接影响现场执行。'); if (/DeviceManagement/.test(page.relative)) values.push('设备和采集数据用于看板、OEE 和报工分析,发现异常时应先确认设备编码和采集点位。'); if (/SystemMaintenance/.test(page.relative)) values.push('系统维护类数据会影响登录、权限、菜单和业务可选项,修改后需要验证。'); if (page.sapCalls.length) values.push('该页存在 SAP/WMS 调用,失败时应记录返回提示并确认是否已经生成外部单据。'); if (page.hasCalendar) values.push('日程拖拽属于直接改计划时间的操作,拖拽前应确认任务未被现场执行锁定。'); if (!values.length) values.push('操作后建议重新查询页面,确认列表、状态和明细已经刷新。'); return values.map(v => `- ${v}`).join('\n'); } function section(page, index) { const query = queryItems(page); const columns = tableItems(page); const buttons = buttonItems(page); const procs = procItems(page); const sap = sapItems(page); return `### 21.${index} ${page.name} 页面路径:\`${page.relative}\` 所属模块:${moduleName(page)} 建议使用人员:${roleFor(page)} 页面用途:${purpose(page)} **具体操作步骤** ${detailedSteps(page)} **查询条件/输入项** ${lineList(query, '未识别到固定输入项,按现场页面提示操作。')} **结果表重点列** ${lineList(columns, '未识别到固定表格列,以页面实际显示为准。')} **页面按钮/状态/页签** ${lineList([...buttons, ...page.tabLabels].slice(0, 36), '未识别到明确按钮,以页面实际显示为准。')} **弹窗/表单重点项** ${lineList([...page.dialogs, ...page.formLabels].slice(0, 30), '未识别到固定弹窗或表单项。')} **后台处理/外部接口** ${lineList([...procs.map(x => `存储过程:${x}`), ...sap.map(x => `接口:${x}`)].slice(0, 30), '未发现页面级后台提交动作。')} **操作注意事项** ${notes(page)} `; } function moduleOrder(page) { const order = [ '404.vue', 'AnomalousManagement', 'CraftManagement', 'DeviceManagement', 'PlanManagement', 'ProcessManagement', 'ProductionManagement', 'QualityManagement', 'SaleManagement', 'SystemMaintenance', 'dashboard', 'layout', 'login' ]; const key = page.relative === 'src/views/404.vue' ? '404.vue' : page.module; const idx = order.indexOf(key); return idx >= 0 ? idx : 99; } function moduleHeading(page) { const key = page.relative === 'src/views/404.vue' ? '404.vue' : page.module; const headings = { '404.vue': '错误页', AnomalousManagement: '异常管理', CraftManagement: '产品结构维护', DeviceManagement: '设备管理', PlanManagement: '计划排产', ProcessManagement: '工艺与图纸管理', ProductionManagement: '生产执行管理', QualityManagement: '质量管理', SaleManagement: '销售与交付查询', SystemMaintenance: '系统维护', dashboard: '首页与看板', layout: '布局与导航', login: '登录' }; return headings[key] || key; } function buildChapter(pages) { const sorted = pages.sort((a, b) => { const m = moduleOrder(a) - moduleOrder(b); if (m !== 0) return m; return a.relative.localeCompare(b.relative, 'zh-Hans-CN'); }); const lines = []; lines.push('## 二十一、逐页详细操作说明(按 src/views 页面)'); lines.push(''); lines.push('本章按源码页面逐页编写。每个页面都从实际 `v-model`、表格列、按钮、弹窗、页签、存储过程和 SAP/WMS 调用中提取操作线索,转换成用户可执行的步骤。'); lines.push(''); lines.push('通用阅读方法:'); lines.push('- `查询条件/输入项` 是用户进入页面后可以填写、选择或用于筛选的字段。'); lines.push('- `结果表重点列` 是查询后需要核对的表格列,表格可横向滚动时应继续向右查看。'); lines.push('- `页面按钮/状态/页签` 是页面上能触发查询、查看、编辑、提交、导出、状态切换或页面切换的入口。'); lines.push('- `后台处理/外部接口` 用于说明保存后可能调用的存储过程、SAP 或 WMS 接口,业务人员主要关注成功/失败提示。'); lines.push('- 涉及排产、报工、质检、外协、SAP/WMS 的操作,提交前必须核对订单、物料、数量、人员、工位和日期。'); lines.push(''); let current = ''; let moduleIndex = 0; sorted.forEach((page, i) => { const heading = moduleHeading(page); if (heading !== current) { moduleIndex += 1; current = heading; lines.push(`## 21-${moduleIndex} ${heading}`); lines.push(''); } lines.push(section(page, i + 1).trimEnd()); lines.push(''); }); return lines.join('\n').trimEnd() + '\n'; } function main() { const pages = walk(viewsRoot) .filter(file => !/node_modules/.test(file)) .map(extractPage); const chapter = buildChapter(pages); const manual = readText(manualPath); const marker = '## 二十一、逐页详细操作说明(按 src/views 页面)'; const idx = manual.indexOf(marker); if (idx < 0) throw new Error(`找不到章节标记:${marker}`); const nextChapter = manual.slice(idx + marker.length).search(/\n## 二十二、/); const head = manual.slice(0, idx).trimEnd() + '\n\n'; const tail = nextChapter >= 0 ? manual.slice(idx + marker.length + nextChapter) : ''; writeText(manualPath, head + chapter + (tail ? '\n' + tail.trimStart() : '')); console.log(`rewrote chapter 21 for ${pages.length} pages`); } main();