468 lines
46 KiB
JavaScript
468 lines
46 KiB
JavaScript
const fs = require('fs')
|
||
const path = require('path')
|
||
const JSZip = require('jszip')
|
||
|
||
const root = path.resolve(__dirname, '..')
|
||
const docDir = path.join(root, 'doc')
|
||
const menuRows = parseCsv(fs.readFileSync(path.join(root, 'work', 'menu_inventory_role1.csv'), 'utf8'))
|
||
const pageRows = parseCsv(fs.readFileSync(path.join(root, 'work', 'page_ui_inventory_role1_20260725.csv'), 'utf8'))
|
||
const frontendRows = parseCsv(fs.readFileSync(path.join(root, 'work', 'frontend_procedure_call_inventory_20260725.csv'), 'utf8'))
|
||
const dbRows = parseCsv(fs.readFileSync(path.join(root, 'work', 'database_procedure_inventory_20260725.csv'), 'utf8'))
|
||
const dependencyRows = parseCsv(fs.readFileSync(path.join(root, 'work', 'database_expression_dependencies_20260725.csv'), 'utf8'))
|
||
const missingRows = parseCsv(fs.readFileSync(path.join(root, 'work', 'frontend_calls_missing_db_procedure_20260725.csv'), 'utf8'))
|
||
const scanSummary = JSON.parse(fs.readFileSync(path.join(root, 'work', 'frontend_function_scan_summary_20260725.json'), 'utf8'))
|
||
|
||
function parseCsv(text) {
|
||
const source = text.replace(/^\uFEFF/, '').trim()
|
||
const records = []
|
||
let record = []
|
||
let value = ''
|
||
let quoted = false
|
||
|
||
for (let i = 0; i < source.length; i++) {
|
||
const c = source[i]
|
||
if (c === '"') {
|
||
if (quoted && source[i + 1] === '"') {
|
||
value += '"'
|
||
i++
|
||
} else {
|
||
quoted = !quoted
|
||
}
|
||
} else if (c === ',' && !quoted) {
|
||
record.push(value)
|
||
value = ''
|
||
} else if ((c === '\r' || c === '\n') && !quoted) {
|
||
if (c === '\r' && source[i + 1] === '\n') i++
|
||
record.push(value)
|
||
records.push(record)
|
||
record = []
|
||
value = ''
|
||
} else {
|
||
value += c
|
||
}
|
||
}
|
||
|
||
record.push(value)
|
||
records.push(record)
|
||
const headers = records.shift()
|
||
return records
|
||
.filter(values => values.some(item => item !== ''))
|
||
.map(values => Object.fromEntries(headers.map((header, i) => [header, values[i] || ''])))
|
||
}
|
||
|
||
function unique(values) {
|
||
return [...new Set(values.map(value => String(value || '').replace(/ /g, ' ').replace(/\s+/g, ' ').trim()).filter(Boolean))]
|
||
}
|
||
|
||
function list(value, limit = 10) {
|
||
const values = unique(String(value || '').split('|')).filter(item => !/^(if|for|returnModule|fn|-->)/.test(item))
|
||
return values.slice(0, limit).join('、') + (values.length > limit ? ' 等' : '')
|
||
}
|
||
|
||
function csv(value) {
|
||
return `"${String(value == null ? '' : value).replace(/"/g, '""')}"`
|
||
}
|
||
|
||
const rootRows = menuRows.filter(row => row.pid === '0')
|
||
const pagesByModule = new Map()
|
||
for (const row of pageRows) {
|
||
if (!pagesByModule.has(row.module)) pagesByModule.set(row.module, [])
|
||
pagesByModule.get(row.module).push(row)
|
||
}
|
||
const moduleTitles = Object.fromEntries(rootRows.map(row => [row.componet.split('/')[1], row.title]))
|
||
const moduleIntros = {
|
||
SystemMaintenance: '维护人员、角色、菜单、工位和操作记录,是权限与基础配置入口。',
|
||
PlanManagement: '管理机加件、装配件、工位任务、排产状态、优先级、关闭和齐套跟踪。',
|
||
ProductionManagement: '覆盖生产计划跟踪、加工中心、装配、工时、外协、拣配、设备状态和发货通知。',
|
||
QualityManagement: '覆盖终检、序检、采购/退库/其他入库检、质检报工、质检明细和不合格品处置。',
|
||
ProcessManagement: '维护工序、标准工艺、生产工艺、工艺任务、零件图纸和工艺图纸。',
|
||
DeviceManagement: '维护设备基础信息、工位绑定、设备采集数据、历史数据和实时数据。',
|
||
SaleManagement: '查看销售订单状态、排产、发货和入库相关信息。',
|
||
AnomalousManagement: '集中查看未及时开工、外协开工、加急任务和装配及时送检异常。',
|
||
ResearchManagement: '覆盖设计任务、设计报工、设计工时校验、工时报表和研发财务工时。'
|
||
}
|
||
|
||
function moduleWorkflow(module) {
|
||
const workflows = {
|
||
SystemMaintenance: ['先维护人员和角色,再配置菜单权限;修改权限后退出并重新登录验证。', '工位管理用于维护 MES 工位名称和绑定信息,删除或修改前确认没有未完成任务。', '操作记录用于追溯关键人工操作,出现争议时先按时间、人员和模块筛选。'],
|
||
PlanManagement: ['先按订单、物料、日期和状态查询,再选择任务执行排产、派工、优先级或计划时间调整。', '批量操作前核对选择行、计划数量和指派对象;排产完成后到已排产/工位任务汇总复核。', '系统类装配任务和阀类装配任务中的预算工时为手工输入,进度由任务/质检工时除以对应预算自动计算。'],
|
||
ProductionManagement: ['加工中心按待处理任务、工序和工位操作,通常按收料、开工、暂停/恢复、报工、送检或异常流程推进。', '工时修正、结算工时和外协流程属于受控写操作,必须确认业务单据和审批状态。', '生产计划跟踪、工时报表、装配状态和发货通知优先用于查询和核对。'],
|
||
QualityManagement: ['先选择质检任务和质检类型,再录入实测值、判定、合格/不合格数量并保存;不合格品进入处置流程。', '终检、序检、收检、采购入库检、退库检和其他入库检应按实际业务节点使用,不要跨节点补录。', '质检明细、及时检验报表和产品合格率用于追溯和统计,导出前先确认筛选条件。'],
|
||
ProcessManagement: ['先维护工序和标准工艺,再生成/编辑生产工艺,最后向生产订单下发工艺任务。', '图纸维护和工艺维护用于查看、上传或关联图纸;修改工序顺序、工时和启用状态前确认影响范围。', '工艺查询优先用于只读核对,工艺编辑需保留变更原因。'],
|
||
DeviceManagement: ['先建立设备基础信息并绑定工位,再使用设备数据、历史数据和实时数据检查采集状态。', '设备信息中的证书有效期、主要设备、特种设备和标准开机工时应保持准确。', '实时数据异常时先判断采集链路,再判断页面和数据库,不要直接重复提交设备写操作。'],
|
||
SaleManagement: ['使用订单号、合同号、产品编码、计划日期、排产/发货/入库状态筛选销售订单。', '订单状态用于跨计划、生产、库存和发货的核对,不作为单独修改生产状态的入口。'],
|
||
AnomalousManagement: ['先按异常类型、订单、产品和时间筛选,确认责任工序和当前状态。', '加急任务和及时送检异常处理前先确认计划、工序、人员和优先级,避免重复催办。'],
|
||
ResearchManagement: ['设计任务用于接收和分派研发任务,设计报工用于提交研发工时,设计工时校验和报表用于复核。', '研发财务工时属于统计口径页面,修改前需确认研发任务和财务归集规则。']
|
||
}
|
||
return workflows[module] || ['按页面筛选条件查询数据,选择记录后执行页面提供的操作。', '涉及保存、删除、状态切换的操作必须先确认权限和回滚方式。']
|
||
}
|
||
|
||
const markdown = []
|
||
markdown.push('# 大连元利流体技术有限公司 MES 后台网页版用户操作手册')
|
||
markdown.push('')
|
||
markdown.push('**文档版本:V1.0 | 适用环境:MES 测试环境 | 日期:2026-07-25**')
|
||
markdown.push('')
|
||
markdown.push('> 本手册基于测试环境角色 1 的 84 条菜单、75 个可进入页面、前端代码静态扫描和测试数据库对象盘点生成。不同角色的菜单、按钮和数据范围以实际权限为准。')
|
||
markdown.push('')
|
||
markdown.push('## 1. 使用前说明')
|
||
markdown.push('')
|
||
markdown.push('- 测试前端地址:`http://124.220.15.242:10012/`。')
|
||
markdown.push('- MES 接口由前端自动调用,用户不需要手工拼接接口地址。')
|
||
markdown.push('- 测试环境 B1/TM 只允许登录与只读 GET;语义 POST/PATCH 会在前端拦截,不要用写操作验证连通性。')
|
||
markdown.push('- 页面按钮和数据范围由角色权限控制;看不到某个菜单或按钮时先联系系统管理员,不要直接修改浏览器参数。')
|
||
markdown.push('- 涉及新增、编辑、删除、排产、派工、报工、质检、入库和状态切换的操作,必须先确认业务单据、操作人和回滚方式。')
|
||
markdown.push('')
|
||
markdown.push('## 2. 登录与通用操作')
|
||
markdown.push('')
|
||
markdown.push('### 2.1 登录')
|
||
markdown.push('')
|
||
markdown.push('1. 打开测试前端地址。')
|
||
markdown.push('2. 输入账号和密码,点击“登录”。')
|
||
markdown.push('3. 登录成功后进入首页;系统按角色加载菜单和按钮权限。')
|
||
markdown.push('4. “记住账号”只保存账号,不保存密码;离开共享电脑时不要勾选。')
|
||
markdown.push('')
|
||
markdown.push('### 2.2 列表查询')
|
||
markdown.push('')
|
||
markdown.push('1. 先填写必要筛选条件,日期条件使用页面提供的日期选择器。')
|
||
markdown.push('2. 点击“查询”,等待表格刷新。')
|
||
markdown.push('3. 使用分页、每页条数、排序和导出功能核对结果。')
|
||
markdown.push('4. 查询结果为空时先清空筛选条件,再判断是否确实没有数据。')
|
||
markdown.push('')
|
||
markdown.push('### 2.3 新增、编辑、删除与状态操作')
|
||
markdown.push('')
|
||
markdown.push('1. 新增前确认必填字段、编码唯一性和关联对象。')
|
||
markdown.push('2. 编辑前先选中正确记录,核对订单、合同、工位或人员。')
|
||
markdown.push('3. 删除、关闭、完成、启用、禁用、排产、派工和报工属于状态性操作,执行前必须二次确认。')
|
||
markdown.push('4. 保存后重新查询并核对操作时间、编辑人和状态。')
|
||
markdown.push('')
|
||
markdown.push('### 2.4 页面常见问题')
|
||
markdown.push('')
|
||
markdown.push('| 现象 | 处理顺序 |')
|
||
markdown.push('| --- | --- |')
|
||
markdown.push('| 登录成功但没有菜单 | 退出重新登录;确认角色是否启用、菜单是否分配、接口是否返回菜单数据。 |')
|
||
markdown.push('| 查询很慢 | 缩小日期和订单范围;记录页面、条件和耗时,交给运维分析存储过程和执行计划。 |')
|
||
markdown.push('| 按钮不存在 | 检查角色 AddData/DeleteData/EditData/SearchData 权限,不要直接改前端隐藏逻辑。 |')
|
||
markdown.push('| B1/TM 操作失败 | 测试环境先确认是否为被保护的 POST/PATCH;写操作不会为了测试而放开。 |')
|
||
markdown.push('| 保存后页面没有变化 | 重新查询并查看接口返回;确认过程是否存在、参数名和业务状态是否满足。 |')
|
||
markdown.push('')
|
||
markdown.push('## 3. 菜单与页面总览')
|
||
markdown.push('')
|
||
markdown.push(`当前角色菜单共 **${menuRows.length}** 条,其中模块 **${rootRows.length}** 个、可进入页面 **${pageRows.length}** 个。`)
|
||
markdown.push('')
|
||
for (const rootRow of rootRows) {
|
||
const module = rootRow.componet.split('/')[1]
|
||
const pages = pagesByModule.get(module) || []
|
||
markdown.push(`### ${rootRow.title}`)
|
||
markdown.push('')
|
||
markdown.push(`用途:${moduleIntros[module] || '用于 MES 业务管理和查询。'}`)
|
||
markdown.push('')
|
||
markdown.push('| 页面 | 路径 | 主要操作 | 关键字段/内容 |')
|
||
markdown.push('| --- | --- | --- | --- |')
|
||
for (const page of pages) {
|
||
markdown.push(`| ${page.title} | \`${page.path}\` | ${list(page.buttons, 8) || '查询/查看'} | ${list(page.columns, 10) || '按页面实际数据展示'} |`)
|
||
}
|
||
markdown.push('')
|
||
}
|
||
markdown.push('## 4. 各模块操作流程')
|
||
markdown.push('')
|
||
for (const rootRow of rootRows) {
|
||
const module = rootRow.componet.split('/')[1]
|
||
markdown.push(`### 4.${rootRows.indexOf(rootRow) + 1} ${rootRow.title}`)
|
||
markdown.push('')
|
||
markdown.push(`模块定位:${moduleIntros[module] || '用于 MES 业务管理和查询。'}`)
|
||
for (const item of moduleWorkflow(module)) markdown.push(`- ${item}`)
|
||
markdown.push('')
|
||
const pages = pagesByModule.get(module) || []
|
||
for (const page of pages) {
|
||
markdown.push(`#### ${page.title}`)
|
||
markdown.push('')
|
||
markdown.push(`页面地址:\`${page.path}\``)
|
||
markdown.push('')
|
||
markdown.push(`- 页面源码:\`${page.source}\``)
|
||
markdown.push(`- 页面操作:${list(page.buttons, 14) || '查询、查看'}`)
|
||
markdown.push(`- 主要字段:${list(page.columns, 18) || '按接口返回字段展示'}`)
|
||
if (page.procedures) markdown.push(`- 相关过程:${list(page.procedures.split('|'), 18)}`)
|
||
if (page.b1GetPostPatch && page.b1GetPostPatch !== '0/0/0') markdown.push(`- B1 调用:GET/POST/PATCH = ${page.b1GetPostPatch};测试环境 POST/PATCH 受保护。`)
|
||
if (page.tmGetPostPatch && page.tmGetPostPatch !== '0/0/0') markdown.push(`- TM 调用:GET/POST/PATCH = ${page.tmGetPostPatch};测试环境写操作受保护。`)
|
||
markdown.push('操作建议:先查询并确认记录,再执行页面按钮;保存或状态操作后重新查询核对结果。')
|
||
markdown.push('')
|
||
}
|
||
}
|
||
markdown.push('## 5. 重点业务流程')
|
||
markdown.push('')
|
||
markdown.push('### 5.1 排产与装配任务')
|
||
markdown.push('')
|
||
markdown.push('1. 在“机加件排产”或“装配件排产”按订单、物料、日期和状态查询。')
|
||
markdown.push('2. 选择任务后执行排产、派工、优先级或计划时间调整;批量操作前核对选择行。')
|
||
markdown.push('3. 到“已排产”“工位任务汇总”或“及时齐套跟踪结果”复核状态。')
|
||
markdown.push('4. 在“系统类装配任务”“阀类装配任务”维护预算装配工时和合同预算测试工时,进度字段由系统计算。')
|
||
markdown.push('')
|
||
markdown.push('### 5.2 工艺维护与工艺下发')
|
||
markdown.push('')
|
||
markdown.push('1. 在“工序管理”维护工序基础信息。')
|
||
markdown.push('2. 在“制定标准工艺”维护准备工时、标准工时、工装量具、机床和车间。')
|
||
markdown.push('3. 在“生产工艺”查询订单并生成/编辑工艺,确认工序顺序和工时。')
|
||
markdown.push('4. 需要下发生产任务时,确认订单、工艺版本和目标工序后执行下发。')
|
||
markdown.push('')
|
||
markdown.push('### 5.3 加工中心与装配中心')
|
||
markdown.push('')
|
||
markdown.push('1. 按工位、人员、班次和任务状态查询待处理任务。')
|
||
markdown.push('2. 按业务顺序执行收料、开工、暂停/恢复、报工、送检和异常处理。')
|
||
markdown.push('3. 质检数据录入前核对检测项目、理论值、上下限、实测值和判定。')
|
||
markdown.push('4. 操作完成后检查任务状态、工时、数量和最后编辑人。')
|
||
markdown.push('')
|
||
markdown.push('### 5.4 质量检验与不合格品')
|
||
markdown.push('')
|
||
markdown.push('1. 根据业务节点进入终检、序检、收检、采购入库检、销售退货检、原材料退库检或其他入库检。')
|
||
markdown.push('2. 选择任务和检测项目,录入检验数量、合格数、不合格数和实测值。')
|
||
markdown.push('3. 不合格结果进入“不合格品处置”,填写处置结论并保留审批/操作记录。')
|
||
markdown.push('4. 使用“质检明细”“及时检验报表”“产品合格率”进行追溯和统计。')
|
||
markdown.push('')
|
||
markdown.push('### 5.5 人员、角色、菜单和工位')
|
||
markdown.push('')
|
||
markdown.push('1. 先新增或维护人员,再配置角色。')
|
||
markdown.push('2. 在角色管理为角色分配模块和操作权限。')
|
||
markdown.push('3. 在菜单管理维护模块路径和层级;变更菜单前先确认路由组件存在。')
|
||
markdown.push('4. 在工位管理维护工位号、名称和关联信息。')
|
||
markdown.push('5. 权限修改后使用目标账号重新登录验证,不以管理员账号代替验收。')
|
||
markdown.push('')
|
||
markdown.push('## 6. 权限与数据安全')
|
||
markdown.push('')
|
||
markdown.push('- 菜单权限来自数据库角色菜单过程;页面按钮受 AddData、DeleteData、EditData、SearchData 等权限控制。')
|
||
markdown.push('- 测试前端配置 `externalReadOnly=true` 时,B1/TM 语义 POST/PATCH 在网络请求前被拦截。')
|
||
markdown.push('- 不要在浏览器开发者工具中手工重放生产接口或外部系统写请求。')
|
||
markdown.push('- 不要把账号、密码、Token、业务数据或导出文件提交到代码仓库。')
|
||
markdown.push('- 发现数据异常时保留页面、筛选条件、时间、账号、响应状态和操作记录,不要先删除数据。')
|
||
markdown.push('')
|
||
markdown.push('## 7. 功能定位与新增需求说明')
|
||
markdown.push('')
|
||
markdown.push('### 7.1 页面问题定位')
|
||
markdown.push('')
|
||
markdown.push('1. 先从菜单路径定位页面源码:`src/views/<模块>/<页面>/index.vue`,特殊页面以菜单组件路径为准。')
|
||
markdown.push('2. 在页面中查找 `CreateData`,确认过程名、调用类型和参数名。')
|
||
markdown.push('3. 通用接口入口位于 `src/utils/request.js` 和 `src/utils/curd.js`;动态菜单由 `src/router/getTreeData.js` 和 `src/router/menuComponentModule.js` 生成。')
|
||
markdown.push('4. B1/TM 封装分别位于 `src/api/b1s.js`、`src/api/tm.js`;测试环境不要绕过只读保护。')
|
||
markdown.push('5. 到数据库过程清单中确认过程存在、参数数量、写入特征和外部依赖,再决定改前端、改过程还是补充对象。')
|
||
markdown.push('')
|
||
markdown.push('### 7.2 新增需求的推荐顺序')
|
||
markdown.push('')
|
||
markdown.push('1. 明确页面角色、数据范围、查询条件、字段口径和读写边界。')
|
||
markdown.push('2. 先确认现有过程是否可复用,避免新增同义过程。')
|
||
markdown.push('3. 测试库新增/修改过程时编写前置校验、事务、验证 SQL 和回滚脚本。')
|
||
markdown.push('4. 前端实现表格、表单、权限按钮、加载/空结果/错误状态和分页。')
|
||
markdown.push('5. 先做只读回归,再用隔离数据做写流程回归,最后更新菜单、手册和变更记录。')
|
||
markdown.push('')
|
||
markdown.push('## 8. 全量扫描结果与已知风险')
|
||
markdown.push('')
|
||
markdown.push(`- 前端源码:${scanSummary.sourceFiles} 个源码文件,其中 ${scanSummary.vueFiles} 个 Vue、${scanSummary.jsFiles} 个 JS。`)
|
||
markdown.push(`- CreateData:${scanSummary.totalCreateDataCalls} 次调用,${scanSummary.literalCreateDataCalls} 次字面量过程调用,${scanSummary.uniqueLiteralProcedures} 个唯一字面量过程名,另有 ${scanSummary.dynamicCreateDataCalls} 次动态调用需人工复核。`)
|
||
markdown.push(`- 外部集成调用:B1 GET/POST/PATCH = ${scanSummary.b1.get}/${scanSummary.b1.post}/${scanSummary.b1.patch};TM GET/POST/PATCH = ${scanSummary.tm.get}/${scanSummary.tm.post}/${scanSummary.tm.patch}。`)
|
||
markdown.push(`- 测试数据库:${dbRows.length} 个存储过程、63 张表、27 个视图;过程定义中包含明显写关键字的约 ${dbRows.filter(row => row.HasWriteKeyword === '1').length} 个。`)
|
||
markdown.push(`- 数据库表达式依赖:${dependencyRows.length} 条,其中跨服务器依赖 ${dependencyRows.filter(row => String(row.ReferencedServer || '').trim()).length} 条,跨库依赖 ${dependencyRows.filter(row => String(row.ReferencedDatabase || '').trim()).length} 条。`)
|
||
markdown.push(`- 前端调用但测试库找不到同名过程:${missingRows.length} 个,属于重点风险,不代表全部页面不可用,需按部署版本、动态过程或历史页面逐项确认。`)
|
||
if (missingRows.length) {
|
||
markdown.push('')
|
||
markdown.push('缺失过程清单:')
|
||
for (const row of missingRows) markdown.push(`- \`${row.Procedure}\`:${row.SourceFiles}`)
|
||
}
|
||
markdown.push('- 数据库存在但静态前端未调用的过程可能来自事件、定时任务、接口内部调用、历史功能或未授权菜单,不能仅凭“未调用”判定为废弃。')
|
||
markdown.push('- 当前测试环境的核心外部依赖、证书、后端高权限账号和写流程验收仍按接管报告中的限制执行。')
|
||
markdown.push('')
|
||
markdown.push('## 9. 交付清单')
|
||
markdown.push('')
|
||
markdown.push('- 本用户手册:`doc/MES后台网页版用户操作手册_V1.0_20260725.docx`')
|
||
markdown.push('- Markdown 版:`doc/MES后台网页版用户操作手册_V1.0_20260725.md`')
|
||
markdown.push('- 菜单清单:`work/menu_inventory_role1.csv`')
|
||
markdown.push('- 页面 UI 清单:`work/page_ui_inventory_role1_20260725.csv`')
|
||
markdown.push('- 前端过程调用清单:`work/frontend_procedure_call_inventory_20260725.csv`')
|
||
markdown.push('- 数据库过程清单:`work/database_procedure_inventory_20260725.csv`')
|
||
markdown.push('- 前端/数据库差异清单:`work/frontend_calls_missing_db_procedure_20260725.csv`')
|
||
markdown.push('- 全量过程扫描脚本:`work/scan-frontend-function-inventory.js`')
|
||
markdown.push('- 页面扫描脚本:`work/scan-page-ui-inventory.js`')
|
||
markdown.push('')
|
||
markdown.push('> 手册首版基于源码、菜单和存储过程静态盘点;涉及真实业务写入的页面仍需在隔离数据上完成业务负责人验收后,才能标记为生产可用。')
|
||
|
||
const markdownPath = path.join(docDir, 'MES后台网页版用户操作手册_V1.0_20260725.md')
|
||
fs.writeFileSync(markdownPath, markdown.join('\n') + '\n', 'utf8')
|
||
|
||
const mapLines = []
|
||
mapLines.push('# MES 后台网页版功能与存储过程全量盘点')
|
||
mapLines.push('')
|
||
mapLines.push('盘点日期:2026-07-25 | 环境:测试数据库 YL_MESDB | 角色:1')
|
||
mapLines.push('')
|
||
mapLines.push('## 1. 盘点范围')
|
||
mapLines.push('')
|
||
mapLines.push(`- 前端源码:${scanSummary.sourceFiles} 个文件,其中 ${scanSummary.vueFiles} 个 Vue、${scanSummary.jsFiles} 个 JS。`)
|
||
mapLines.push(`- 菜单:${menuRows.length} 条,其中模块 ${rootRows.length} 个、可进入页面 ${pageRows.length} 个;75 个页面全部映射到源码文件。`)
|
||
mapLines.push(`- 前端过程调用:${scanSummary.totalCreateDataCalls} 次 CreateData,${scanSummary.uniqueLiteralProcedures} 个唯一字面量过程名,${scanSummary.dynamicCreateDataCalls} 次动态调用。`)
|
||
mapLines.push(`- 数据库对象:${dbRows.length} 个存储过程、63 张表、27 个视图。`)
|
||
mapLines.push(`- 数据库表达式依赖:${dependencyRows.length} 条,其中跨服务器依赖 ${dependencyRows.filter(row => String(row.ReferencedServer || '').trim()).length} 条,跨库依赖 ${dependencyRows.filter(row => String(row.ReferencedDatabase || '').trim()).length} 条。`)
|
||
mapLines.push('')
|
||
mapLines.push('## 2. 页面到过程映射')
|
||
mapLines.push('')
|
||
mapLines.push('| 模块 | 页面 | 源码 | 主要操作 | 过程调用 | B1 GET/POST/PATCH | TM GET/POST/PATCH |')
|
||
mapLines.push('| --- | --- | --- | --- | --- | --- | --- |')
|
||
for (const page of pageRows) mapLines.push(`| ${page.module} | ${page.title} | ${page.source} | ${list(page.buttons, 8)} | ${list(page.procedures.split('|'), 16)} | ${page.b1GetPostPatch} | ${page.tmGetPostPatch} |`)
|
||
mapLines.push('')
|
||
mapLines.push('## 3. 前端调用与数据库差异')
|
||
mapLines.push('')
|
||
mapLines.push(`前端字面量调用但测试库找不到同名过程共 **${missingRows.length}** 个。该结果可能由测试库版本差异、动态接口、历史页面或过程名称变更造成,必须逐项确认:`)
|
||
mapLines.push('')
|
||
for (const row of missingRows) mapLines.push(`- ${row.Procedure}:调用次数 ${row.CallCount},来源 ${row.SourceFiles}`)
|
||
mapLines.push('')
|
||
mapLines.push(`数据库中有 ${dbRows.filter(row => !frontendRows.some(front => String(front.Procedure || '').toLocaleLowerCase() === String(row.name || '').toLocaleLowerCase())).length} 个过程未被静态前端直接调用。它们可能由事件、定时任务、接口内部调用、其他客户端、历史菜单或后台作业使用,不能仅凭静态未引用判定为废弃。`)
|
||
mapLines.push('')
|
||
mapLines.push('## 4. 过程风险分类')
|
||
mapLines.push('')
|
||
mapLines.push(`- 过程总数:${dbRows.length}。`)
|
||
mapLines.push(`- 定义中出现 INSERT/UPDATE/DELETE/MERGE 等明显写关键字:${dbRows.filter(row => row.HasWriteKeyword === '1').length} 个。`)
|
||
mapLines.push(`- 明确包含 OPENQUERY、[SAP] 或现场 SAP 交货表引用:${dbRows.filter(row => row.HasExternalDependency === '1').length} 个。`)
|
||
mapLines.push('- 写过程必须按业务数据、权限、事务、回滚和审计要求单独验收;外部依赖过程必须在现场连接可用时再做联调。')
|
||
mapLines.push('')
|
||
mapLines.push('## 5. 新需求定位路径')
|
||
mapLines.push('')
|
||
mapLines.push('1. 从菜单路径定位 `src/views/<模块>/<页面>/index.vue`。')
|
||
mapLines.push('2. 在页面中搜索 `CreateData`,记录过程名、type、参数名和调用前后的页面状态。')
|
||
mapLines.push('3. 在 `work/database_procedure_inventory_20260725.csv` 查过程参数数量、写入特征和外部依赖。')
|
||
mapLines.push('4. 如果过程不存在,先确认测试库与发布物版本,再决定补过程、改过程名映射还是修复页面。')
|
||
mapLines.push('5. 变更必须配套前置校验、事务、验证 SQL、回滚脚本、页面回归和手册更新。')
|
||
mapLines.push('')
|
||
mapLines.push('## 6. 原始清单')
|
||
mapLines.push('')
|
||
mapLines.push('- `work/menu_inventory_role1.csv`:角色菜单清单。')
|
||
mapLines.push('- `work/page_ui_inventory_role1_20260725.csv`:页面按钮、字段、方法和过程清单。')
|
||
mapLines.push('- `work/frontend_procedure_call_inventory_20260725.csv`:前端过程调用清单。')
|
||
mapLines.push('- `work/database_procedure_inventory_20260725.csv`:测试库全部过程清单。')
|
||
mapLines.push('- `work/database_procedure_parameters_20260725.csv`:全部过程参数清单。')
|
||
mapLines.push('- `work/database_table_columns_20260725.csv`、`work/database_view_columns_20260725.csv`:表和视图字段清单。')
|
||
mapLines.push('- `work/database_expression_dependencies_20260725.csv`:过程、视图、约束等表达式依赖清单。')
|
||
mapLines.push('- `work/frontend_calls_missing_db_procedure_20260725.csv`:前端调用缺失过程清单。')
|
||
mapLines.push('- `work/database_procedures_unused_by_static_frontend_20260725.csv`:数据库未被静态前端调用清单。')
|
||
const mapPath = path.join(docDir, 'MES后台网页版功能与存储过程全量盘点_20260725.md')
|
||
fs.writeFileSync(mapPath, mapLines.join('\n') + '\n', 'utf8')
|
||
|
||
// A compact DOCX rendering of the same manual. Tables are generated from the same inventory data.
|
||
const colors = { navy: '17365D', blue: '1F4E78', lightBlue: 'D9EAF7', gray: '666666', lightGray: 'F2F2F2', green: '548235', paleGreen: 'E2F0D9', amber: 'BF8F00', paleAmber: 'FFF2CC', red: 'C00000', paleRed: 'FCE4D6', border: 'B4C6E7', white: 'FFFFFF' }
|
||
const esc = value => String(value == null ? '' : value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
|
||
function r(text, opts = {}) {
|
||
const p = [`<w:rFonts w:ascii="${esc(opts.font || '宋体')}" w:hAnsi="${esc(opts.font || '宋体')}" w:eastAsia="${esc(opts.eastAsia || opts.font || '宋体')}"/>`]
|
||
if (opts.bold) p.push('<w:b/>')
|
||
if (opts.italic) p.push('<w:i/>')
|
||
if (opts.color) p.push(`<w:color w:val="${opts.color}"/>`)
|
||
p.push(`<w:sz w:val="${opts.size || 21}"/><w:szCs w:val="${opts.size || 21}"/>`)
|
||
return `<w:r><w:rPr>${p.join('')}</w:rPr><w:t xml:space="preserve">${esc(text)}</w:t></w:r>`
|
||
}
|
||
function p(content, opts = {}) {
|
||
const runs = Array.isArray(content) ? content : [r(content, opts.run || {})]
|
||
const pp = []
|
||
if (opts.style) pp.push(`<w:pStyle w:val="${opts.style}"/>`)
|
||
if (opts.align) pp.push(`<w:jc w:val="${opts.align}"/>`)
|
||
if (opts.keepNext) pp.push('<w:keepNext/>')
|
||
if (opts.pageBreakBefore) pp.push('<w:pageBreakBefore/>')
|
||
pp.push(`<w:spacing w:after="${opts.after == null ? 100 : opts.after}" w:line="${opts.line || 320}" w:lineRule="auto"/>`)
|
||
return `<w:p><w:pPr>${pp.join('')}</w:pPr>${runs.join('')}</w:p>`
|
||
}
|
||
function h(text, level, pageBreak = false) { return p(text, { style: `Heading${level}`, keepNext: true, pageBreakBefore: pageBreak }) }
|
||
function bulletXml(text) { return p([r('• ', { font: '微软雅黑', eastAsia: '微软雅黑', bold: true, color: colors.blue }), r(text)], { after: 70 }) }
|
||
function numberXml(num, text) { return p([r(`${num}. `, { font: '微软雅黑', eastAsia: '微软雅黑', bold: true, color: colors.blue }), r(text)], { after: 80 }) }
|
||
function td(text, width, opts = {}) {
|
||
const content = Array.isArray(text) ? text : [p(String(text), { run: { size: opts.size || 18, bold: opts.bold, color: opts.color, font: opts.font || '宋体', eastAsia: opts.font || '宋体' }, after: 0, line: 260 })]
|
||
return `<w:tc><w:tcPr><w:tcW w:w="${width}" w:type="dxa"/><w:tcMar><w:top w:w="70" w:type="dxa"/><w:left w:w="80" w:type="dxa"/><w:bottom w:w="70" w:type="dxa"/><w:right w:w="80" w:type="dxa"/></w:tcMar><w:vAlign w:val="center"/>${opts.fill ? `<w:shd w:fill="${opts.fill}"/>` : ''}</w:tcPr>${content.join('')}</w:tc>`
|
||
}
|
||
function tbl(rows, widths, header = false) {
|
||
const grid = widths.map(width => `<w:gridCol w:w="${width}"/>`).join('')
|
||
const body = rows.map((row, index) => `<w:tr><w:trPr>${header && index === 0 ? '<w:tblHeader/>' : ''}<w:cantSplit/></w:trPr>${row.map((text, i) => td(text, widths[i], { fill: header && index === 0 ? colors.blue : (index % 2 === 0 ? colors.lightBlue : null), bold: header && index === 0, color: header && index === 0 ? colors.white : null })).join('')}</w:tr>`).join('')
|
||
return `<w:tbl><w:tblPr><w:tblW w:w="9000" w:type="dxa"/><w:tblLayout w:type="fixed"/><w:tblBorders><w:top w:val="single" w:sz="5" w:color="${colors.border}"/><w:left w:val="single" w:sz="5" w:color="${colors.border}"/><w:bottom w:val="single" w:sz="5" w:color="${colors.border}"/><w:right w:val="single" w:sz="5" w:color="${colors.border}"/><w:insideH w:val="single" w:sz="3" w:color="D9E2F3"/><w:insideV w:val="single" w:sz="3" w:color="D9E2F3"/></w:tblBorders></w:tblPr><w:tblGrid>${grid}</w:tblGrid>${body}</w:tbl>`
|
||
}
|
||
function info(title, text, fill = colors.paleGreen, color = colors.green) {
|
||
const titleParagraph = p([r(title, { font: '微软雅黑', eastAsia: '微软雅黑', bold: true, color, size: 22 })], { after: 70 })
|
||
const textParagraph = p([r(text, { size: 20 })], { after: 0 })
|
||
return `<w:tbl><w:tblPr><w:tblW w:w="9000" w:type="dxa"/><w:tblBorders><w:top w:val="single" w:sz="5" w:color="${color}"/><w:left w:val="single" w:sz="5" w:color="${color}"/><w:bottom w:val="single" w:sz="5" w:color="${color}"/><w:right w:val="single" w:sz="5" w:color="${color}"/></w:tblBorders></w:tblPr><w:tblGrid><w:gridCol w:w="9000"/></w:tblGrid><w:tr><w:trPr><w:cantSplit/></w:trPr><w:tc><w:tcPr><w:tcW w:w="9000" w:type="dxa"/><w:shd w:fill="${fill}"/><w:tcMar><w:top w:w="90" w:type="dxa"/><w:left w:w="110" w:type="dxa"/><w:bottom w:w="90" w:type="dxa"/><w:right w:w="110" w:type="dxa"/></w:tcMar></w:tcPr>${titleParagraph}${textParagraph}</w:tc></w:tr></w:tbl>`
|
||
}
|
||
|
||
const docBody = []
|
||
docBody.push(p('大连元利流体技术有限公司', { align: 'center', after: 100, run: { font: '微软雅黑', eastAsia: '微软雅黑', size: 28, color: colors.gray } }))
|
||
docBody.push(p('MES 后台网页版用户操作手册', { align: 'center', after: 150, run: { font: '微软雅黑', eastAsia: '微软雅黑', size: 52, bold: true, color: colors.navy } }))
|
||
docBody.push(p('测试环境使用与功能定位指南', { align: 'center', after: 800, run: { font: '微软雅黑', eastAsia: '微软雅黑', size: 26, color: colors.blue } }))
|
||
docBody.push(info('手册范围', '本手册覆盖测试角色菜单中的 84 条菜单、75 个页面,提供用户操作、权限、安全边界和问题定位方法。', colors.paleGreen, colors.green))
|
||
docBody.push(p('版本:V1.0 日期:2026-07-25 适用:MES 测试环境', { align: 'center', after: 300, run: { size: 20, color: colors.gray } }))
|
||
docBody.push(p('本手册不包含账号、密码、Token 或生产业务明细。', { align: 'center', after: 500, run: { size: 18, color: colors.gray } }))
|
||
docBody.push('<w:p><w:r><w:br w:type="page"/></w:r></w:p>')
|
||
docBody.push(h('1. 使用前说明', 1))
|
||
for (const item of ['测试前端地址:http://124.220.15.242:10012/。', 'MES 接口由前端自动调用,用户不需要手工拼接接口地址。', '测试环境 B1/TM 只允许登录与只读 GET;语义 POST/PATCH 会在前端拦截。', '页面按钮和数据范围由角色权限控制。', '涉及新增、编辑、删除、排产、派工、报工、质检、入库和状态切换的操作,必须先确认业务单据和回滚方式。']) docBody.push(bulletXml(item))
|
||
docBody.push(h('2. 登录与通用操作', 1, true))
|
||
for (const item of ['打开测试前端地址,输入账号和密码登录。', '登录成功后系统按角色加载菜单和按钮权限。', '记住账号不会保存密码;共享电脑不要勾选。', '列表页面先填写筛选条件再查询,使用分页和导出核对结果。', '保存或状态操作后重新查询,检查状态、数量、操作人和操作时间。']) docBody.push(numberXml(docBody.length, item))
|
||
docBody.push(h('3. 菜单页面总览', 1, true))
|
||
docBody.push(p(`当前角色菜单 ${menuRows.length} 条,模块 ${rootRows.length} 个,可进入页面 ${pageRows.length} 个。不同角色可能少于此数量。`, { after: 140 }))
|
||
for (const rootRow of rootRows) {
|
||
const module = rootRow.componet.split('/')[1]
|
||
const pages = pagesByModule.get(module) || []
|
||
docBody.push(h(rootRow.title, 2))
|
||
docBody.push(p(moduleIntros[module] || '用于 MES 业务管理和查询。', { after: 100 }))
|
||
const pageTable = [['页面', '路径', '主要操作', '关键字段/内容']]
|
||
for (const page of pages) pageTable.push([page.title, page.path, list(page.buttons, 7) || '查询/查看', list(page.columns, 9) || '按页面返回字段'])
|
||
docBody.push(tbl(pageTable, [1500, 2600, 2600, 2300], true))
|
||
}
|
||
docBody.push(h('4. 各模块操作流程', 1, true))
|
||
for (const rootRow of rootRows) {
|
||
const module = rootRow.componet.split('/')[1]
|
||
docBody.push(h(rootRow.title, 2))
|
||
for (const item of moduleWorkflow(module)) docBody.push(bulletXml(item))
|
||
const pages = pagesByModule.get(module) || []
|
||
for (const page of pages) {
|
||
docBody.push(h(page.title, 3))
|
||
docBody.push(p(`页面:${page.path}`, { after: 50 }))
|
||
docBody.push(p(`操作:${list(page.buttons, 12) || '查询/查看'};主要字段:${list(page.columns, 14) || '按接口返回字段展示'}。`, { after: 50 }))
|
||
if (page.procedures) docBody.push(p(`相关存储过程:${list(page.procedures.split('|'), 14)}`, { after: 50 }))
|
||
docBody.push(p('建议:先查询并确认记录,再执行页面按钮;保存或状态操作后重新查询核对结果。', { after: 100 }))
|
||
}
|
||
}
|
||
docBody.push(h('5. 重点业务流程', 1, true))
|
||
for (const [title, items] of [
|
||
['排产与装配任务', ['按订单、物料、日期和状态查询,再执行排产、派工或优先级操作。', '系统类/阀类装配任务的预算工时手工输入,进度由任务或质检工时除以预算自动计算。', '批量操作后到已排产、工位任务汇总和齐套跟踪结果复核。']],
|
||
['工艺维护与下发', ['维护工序和标准工艺,再生成/编辑生产工艺。', '确认工序顺序、准备工时、标准工时、工装量具、机床和车间。', '下发任务前确认订单、工艺版本和目标工序。']],
|
||
['加工中心与装配中心', ['按工位、人员、班次和任务状态查询。', '按业务顺序执行收料、开工、暂停/恢复、报工、送检和异常处理。', '操作完成后核对任务状态、工时、数量和最后编辑人。']],
|
||
['质量检验与不合格品', ['根据业务节点进入终检、序检、收检、采购入库检、退库检或其他入库检。', '录入检测项目、实测值、判定、合格数和不合格数。', '不合格结果进入不合格品处置并保留操作记录。']],
|
||
['人员、角色、菜单和工位', ['先维护人员和角色,再配置菜单权限。', '权限修改后使用目标账号重新登录验证。', '菜单变更前确认路由组件存在。']]
|
||
]) { docBody.push(h(title, 2)); items.forEach((item, i) => docBody.push(numberXml(i + 1, item))) }
|
||
docBody.push(h('6. 权限、安全与问题定位', 1, true))
|
||
for (const item of ['按钮权限来自角色的 AddData、DeleteData、EditData、SearchData 等字段。', '测试环境 B1/TM 语义 POST/PATCH 在网络请求前拦截,不通过放开写权限排查连通性。', '页面问题先定位 src/views/<模块>/<页面>/index.vue,再查 CreateData 过程名和参数。', '通用接口入口为 src/utils/request.js、src/utils/curd.js;动态菜单入口为 src/router/getTreeData.js。', '数据库问题先检查过程是否存在、参数数量、写入特征和外部依赖,再决定修改前端还是数据库过程。', '不要将密码、Token、生产业务数据或导出文件提交到代码仓库。']) docBody.push(bulletXml(item))
|
||
docBody.push(h('7. 全量扫描结果与已知风险', 1, true))
|
||
docBody.push(tbl([
|
||
['项目', '结果'],
|
||
['前端源码', `${scanSummary.sourceFiles} 个源码文件:${scanSummary.vueFiles} 个 Vue、${scanSummary.jsFiles} 个 JS`],
|
||
['存储过程调用', `${scanSummary.totalCreateDataCalls} 次 CreateData,${scanSummary.uniqueLiteralProcedures} 个唯一字面量过程,${scanSummary.dynamicCreateDataCalls} 次动态调用待人工复核`],
|
||
['数据库对象', `${dbRows.length} 个过程、63 张表、27 个视图`],
|
||
['前端/数据库差异', `${missingRows.length} 个前端调用在测试库找不到同名过程;另有数据库过程可能来自事件、定时任务或历史功能`],
|
||
['外部集成', `B1 GET/POST/PATCH=${scanSummary.b1.get}/${scanSummary.b1.post}/${scanSummary.b1.patch};TM GET/POST/PATCH=${scanSummary.tm.get}/${scanSummary.tm.post}/${scanSummary.tm.patch}`]
|
||
], [2600, 6400], true))
|
||
if (missingRows.length) {
|
||
docBody.push(h('前端调用但测试库缺失的过程', 2))
|
||
docBody.push(p('以下过程必须按部署版本、动态调用、历史页面和测试库差异逐项确认,不能直接判定为页面故障:', { after: 80 }))
|
||
for (const row of missingRows) docBody.push(bulletXml(`${row.Procedure}(${row.SourceFiles})`))
|
||
}
|
||
docBody.push(h('8. 交付清单', 1, true))
|
||
for (const item of ['本用户手册:doc/MES后台网页版用户操作手册_V1.0_20260725.docx', 'Markdown 版:doc/MES后台网页版用户操作手册_V1.0_20260725.md', '菜单清单:work/menu_inventory_role1.csv', '页面 UI 清单:work/page_ui_inventory_role1_20260725.csv', '前端过程调用清单:work/frontend_procedure_call_inventory_20260725.csv', '数据库过程清单:work/database_procedure_inventory_20260725.csv', '前端/数据库差异清单:work/frontend_calls_missing_db_procedure_20260725.csv']) docBody.push(bulletXml(item))
|
||
docBody.push(info('手册状态', '这是基于源码、菜单和数据库静态盘点生成的 V1.0。涉及真实业务写入的页面,仍需在隔离数据上由业务负责人完成验收后,才能标记为生产可用。', colors.paleAmber, colors.amber))
|
||
|
||
const section = '<w:sectPr><w:headerReference w:type="default" r:id="rId1"/><w:footerReference w:type="default" r:id="rId2"/><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1360" w:right="1360" w:bottom="1360" w:left="1360" w:header="650" w:footer="650" w:gutter="0"/><w:cols w:space="720"/></w:sectPr>'
|
||
const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:body>${docBody.join('')}${section}</w:body></w:document>`
|
||
const stylesXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:eastAsia="宋体"/><w:sz w:val="20"/><w:szCs w:val="20"/><w:lang w:val="zh-CN" w:eastAsia="zh-CN"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr><w:spacing w:after="100" w:line="320" w:lineRule="auto"/></w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="正文"/><w:rPr><w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:eastAsia="宋体"/><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style><w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="标题 1"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="300" w:after="150"/><w:outlineLvl w:val="0"/></w:pPr><w:rPr><w:rFonts w:ascii="微软雅黑" w:hAnsi="微软雅黑" w:eastAsia="微软雅黑"/><w:b/><w:color w:val="${colors.navy}"/><w:sz w:val="30"/><w:szCs w:val="30"/></w:rPr></w:style><w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="标题 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="220" w:after="100"/><w:outlineLvl w:val="1"/></w:pPr><w:rPr><w:rFonts w:ascii="微软雅黑" w:hAnsi="微软雅黑" w:eastAsia="微软雅黑"/><w:b/><w:color w:val="${colors.blue}"/><w:sz w:val="25"/><w:szCs w:val="25"/></w:rPr></w:style><w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="标题 3"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="160" w:after="80"/><w:outlineLvl w:val="2"/></w:pPr><w:rPr><w:rFonts w:ascii="微软雅黑" w:hAnsi="微软雅黑" w:eastAsia="微软雅黑"/><w:b/><w:color w:val="${colors.gray}"/><w:sz w:val="22"/><w:szCs w:val="22"/></w:rPr></w:style></w:styles>`
|
||
const headerXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:p><w:pPr><w:jc w:val="right"/></w:pPr>${r('大连元利流体技术有限公司 | MES 后台网页版用户操作手册', { font: '微软雅黑', eastAsia: '微软雅黑', size: 16, color: colors.gray })}</w:p></w:hdr>`
|
||
const footerXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:p><w:pPr><w:jc w:val="center"/></w:pPr>${r('内部资料 | 第 ', { size: 16, color: colors.gray })}<w:fldSimple w:instr="PAGE"><w:r><w:rPr><w:sz w:val="16"/><w:color w:val="${colors.gray}"/></w:rPr><w:t>1</w:t></w:r></w:fldSimple>${r(' 页', { size: 16, color: colors.gray })}</w:p></w:ftr>`
|
||
const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/><Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/><Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>`
|
||
const packageRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/></Relationships>`
|
||
const documentRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>`
|
||
const core = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>MES 后台网页版用户操作手册</dc:title><dc:subject>MES 测试环境用户操作与功能定位</dc:subject><dc:creator>MES 接管维护</dc:creator><cp:lastModifiedBy>MES 接管维护</cp:lastModifiedBy><dcterms:created xsi:type="dcterms:W3CDTF">2026-07-25T08:00:00Z</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">2026-07-25T08:00:00Z</dcterms:modified></cp:coreProperties>`
|
||
const app = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>Microsoft Office Word</Application><AppVersion>16.0000</AppVersion><Company>大连元利流体技术有限公司</Company></Properties>`
|
||
const zip = new JSZip()
|
||
zip.file('[Content_Types].xml', contentTypes)
|
||
zip.folder('_rels').file('.rels', packageRels)
|
||
zip.folder('docProps').file('core.xml', core).file('app.xml', app)
|
||
const word = zip.folder('word')
|
||
word.file('document.xml', documentXml).file('styles.xml', stylesXml).file('header1.xml', headerXml).file('footer1.xml', footerXml)
|
||
word.folder('_rels').file('document.xml.rels', documentRels)
|
||
const docxPath = path.join(docDir, 'MES后台网页版用户操作手册_V1.0_20260725.docx')
|
||
fs.writeFileSync(docxPath, zip.generate({ type: 'nodebuffer', compression: 'DEFLATE' }))
|
||
console.log(JSON.stringify({ markdownPath, docxPath, mapPath, menuRows: menuRows.length, pages: pageRows.length, dbProcedures: dbRows.length, missingProcedures: missingRows.length, docxBytes: fs.statSync(docxPath).size }))
|