| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 |
- /**
- * Generate S2 制造协同模块蓝图设计方案.docx
- * 严格按照 S1 蓝图模板格式:每个功能章节含 8 个子章节
- * [目标/宗旨, 业务流程图, 业务流程说明, 业务流程规则, 变化和改进点, 权限管理需求, 系统接口集成, 报表需求]
- */
- const fs = require('fs');
- const sharp = require('sharp');
- const {
- Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
- HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
- convertInchesToTwip, PageBreak, ImageRun, TabStopType, TabStopPosition,
- InternalHyperlink, TableOfContents
- } = require('docx');
- // ═══════════════════════════════════════════════════
- // 通用样式与辅助函数
- // ═══════════════════════════════════════════════════
- const FONT = '微软雅黑';
- const thinBorder = {
- top: { style: BorderStyle.SINGLE, size: 1 },
- bottom: { style: BorderStyle.SINGLE, size: 1 },
- left: { style: BorderStyle.SINGLE, size: 1 },
- right: { style: BorderStyle.SINGLE, size: 1 },
- };
- const headerShading = { fill: '4472C4', type: ShadingType.CLEAR };
- function p(text, opts = {}) {
- return new Paragraph({
- children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
- spacing: { after: 120, line: 360 },
- });
- }
- function bp(text) { return p(text, { bold: true }); }
- function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
- function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
- function h1(text) {
- return new Paragraph({
- children: [new TextRun({ text, bold: true, size: 32, font: FONT })],
- heading: HeadingLevel.HEADING_1,
- spacing: { before: 400, after: 200 },
- });
- }
- function h2(text) {
- return new Paragraph({
- children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
- heading: HeadingLevel.HEADING_2,
- spacing: { before: 300, after: 150 },
- });
- }
- function h3(text) {
- return new Paragraph({
- children: [new TextRun({ text, bold: true, size: 24, font: FONT })],
- heading: HeadingLevel.HEADING_3,
- spacing: { before: 200, after: 100 },
- });
- }
- function bullet(text, indent = 0) {
- return new Paragraph({
- children: [new TextRun({ text: ` ${text}`, size: 21, font: FONT })],
- spacing: { after: 60, line: 340 },
- indent: { left: 300 + indent * 400 },
- });
- }
- // Table helpers
- function hdrCell(text, w = 1800) {
- return new TableCell({
- children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 20, font: FONT, color: 'FFFFFF' })], alignment: AlignmentType.CENTER })],
- width: { size: w, type: WidthType.DXA }, borders: thinBorder, shading: headerShading,
- });
- }
- function tc(text, w = 1800, opts = {}) {
- return new TableCell({
- children: [new Paragraph({ children: [new TextRun({ text: String(text ?? ''), size: 20, font: FONT })], alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT })],
- width: { size: w, type: WidthType.DXA }, borders: thinBorder,
- });
- }
- function table(headers, rows, widths) {
- return new Table({
- rows: [
- new TableRow({ children: headers.map((h, i) => hdrCell(h, widths ? widths[i] : 1800)), tableHeader: true }),
- ...rows.map(r => new TableRow({ children: r.map((c, i) => tc(c, widths ? widths[i] : 1800)) })),
- ],
- width: { size: 100, type: WidthType.PERCENTAGE },
- });
- }
- // 活动流程表(6列)
- function activityTable(rows) {
- const w = [600, 1400, 1100, 3800, 1400, 1700];
- return table(['编号', '活动名称', '执行角色', '活动描述', '输入', '输出'], rows, w);
- }
- // 业务规则表(3列)
- function ruleTable(rows) {
- return table(['序列号', '业务情形', '描述/方案'], rows, [900, 2800, 6300]);
- }
- // 接口表(4列)
- function interfaceTable(rows) {
- return table(['序号', '接口名称', '接口说明', '频次及触发方式'], rows, [600, 2400, 4200, 2800]);
- }
- // 报表表(4列)
- function reportTable(rows) {
- return table(['序号', '名称', '描述', '方案'], rows, [600, 2600, 3600, 3200]);
- }
- // 权限表(3列)
- function permTable(rows) {
- return table(['序号', '岗位名称', '对应系统权限'], rows, [600, 2000, 7400]);
- }
- // ── 目录条目(模板格式) ──
- const TOC2_TAB = 2880;
- const TOC3_TAB = 3600;
- const TOC_FONT_SIZE = 20;
- function tocChapter(num, title) {
- return new Paragraph({
- children: [
- new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: title, bold: true, size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
- ],
- tabStops: [
- { type: TabStopType.LEFT, position: TOC2_TAB },
- { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
- ],
- spacing: { before: 80, after: 40 },
- });
- }
- function tocSub(num, title) {
- return new Paragraph({
- children: [
- new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: title, size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
- new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
- ],
- tabStops: [
- { type: TabStopType.LEFT, position: TOC3_TAB },
- { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
- ],
- spacing: { before: 30, after: 30 },
- });
- }
- function buildTOC() {
- const ch = [];
- ch.push(new TableOfContents('目录', {
- headingStyleRange: '1-2',
- hyperlink: true,
- }));
- ch.push(new Paragraph({
- children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
- spacing: { before: 40, after: 200 },
- }));
- // 文档控制
- ch.push(tocChapter('', '文档控制'));
- ch.push(tocSub('', '更改记录'));
- ch.push(tocSub('', '审核'));
- ch.push(tocSub('', '发布'));
- ch.push(tocChapter('', '目录'));
- // 第1章
- ch.push(tocChapter('1', '总体业务方案'));
- ch.push(tocSub('1.1', '目标和宗旨'));
- ch.push(tocSub('1.2', '总体业务流程图'));
- ch.push(tocSub('1.3', '方案设计'));
- // 第2~4章(8子章节)
- const chSubs = [
- '目标/宗旨', '业务流程图', '业务流程说明', '业务流程规则',
- '变化和改进点', '权限管理需求', '系统接口集成', '报表需求',
- ];
- const chTitles = ['生产排程', '作业计划', '制造协同看板与KPI'];
- for (let i = 0; i < chTitles.length; i++) {
- const n = i + 2;
- ch.push(tocChapter(String(n), chTitles[i]));
- for (let j = 0; j < chSubs.length; j++) {
- ch.push(tocSub(`${n}.${j + 1}`, chSubs[j]));
- }
- }
- // 第5章 数据中台
- ch.push(tocChapter('5', '数据中台架构'));
- ['目标/宗旨', '分层设计', 'S2核心数据流', '对象映射总表', 'MDP作业调度'].forEach((title, i) => {
- ch.push(tocSub(`5.${i + 1}`, title));
- });
- // 第6章 权限管理汇总
- ch.push(tocChapter('6', '权限管理汇总'));
- return ch;
- }
- // ═══════════════════════════════════════════════════
- // 流程图生成
- // ═══════════════════════════════════════════════════
- const FLOW_W = 680, BOX_W = 220, BOX_H = 42, GAP_Y = 28;
- const ROLE_X = 30, BOX_X = 130, MARGIN_Y = 40;
- const flowImageCache = {};
- function flowImageParagraph(pngBuffer) {
- return new Paragraph({
- children: [new ImageRun({ data: pngBuffer, transformation: { width: 540, height: 360 }, type: 'png' })],
- alignment: AlignmentType.CENTER,
- spacing: { before: 120, after: 120 },
- });
- }
- async function generateFlowPNG(steps) {
- const N = steps.length;
- const svgH = MARGIN_Y * 2 + N * (BOX_H + GAP_Y) - GAP_Y;
- const centerX = BOX_X + BOX_W / 2;
- let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${FLOW_W}" height="${svgH}">`;
- svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
- steps.forEach((s, i) => {
- const y = MARGIN_Y + i * (BOX_H + GAP_Y);
- svg += `<text x="${ROLE_X}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="12" fill="#666" text-anchor="start">${s.role || ''}</text>`;
- const isStart = i === 0, isEnd = i === N - 1;
- const fill = isStart ? '#1565C0' : (isEnd ? '#2E7D32' : '#E3F2FD');
- const stroke = isStart ? '#0D47A1' : (isEnd ? '#1B5E20' : '#1565C0');
- const textColor = (isStart || isEnd) ? '#FFFFFF' : '#333333';
- svg += `<rect x="${BOX_X}" y="${y}" width="${BOX_W}" height="${BOX_H}" rx="6" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>`;
- svg += `<text x="${centerX}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="13" fill="${textColor}" text-anchor="middle">${s.label}</text>`;
- if (i < N - 1) {
- const ay = y + BOX_H + 4, by2 = ay + GAP_Y - 8;
- svg += `<line x1="${centerX}" y1="${ay}" x2="${centerX}" y2="${by2}" stroke="#888" stroke-width="1.5"/>`;
- svg += `<polygon points="${centerX - 5},${by2 - 10} ${centerX + 5},${by2 - 10} ${centerX},${by2}" fill="#888"/>`;
- }
- });
- svg += '</svg>';
- return sharp(Buffer.from(svg)).png().toBuffer();
- }
- // 第1章专用:S2总体复合流程图
- async function generateOverviewFlowPNG() {
- const W = 780, H = 580;
- let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">`;
- svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
- // 标题
- svg += `<text x="${W/2}" y="28" font-family="Microsoft YaHei" font-size="15" font-weight="bold" fill="#1F4E79" text-anchor="middle">S2 制造协同 — 总体业务流程</text>`;
- // 区域背景
- const zones = [
- { y: 42, h: 195, color: '#E8F0FE', label: '主线流程(排程到执行)', lx: 10, ly: 60 },
- { y: 245, h: 125, color: '#FFF3E0', label: '基础数据管理(支撑排程)', lx: 10, ly: 263 },
- { y: 378, h: 90, color: '#E8F5E9', label: '数据中台链路', lx: 10, ly: 396 },
- ];
- zones.forEach(z => {
- svg += `<rect x="8" y="${z.y}" width="${W-16}" height="${z.h}" rx="6" fill="${z.color}" stroke="#ccc" stroke-width="0.5" stroke-dasharray="4,3"/>`;
- svg += `<text x="${z.lx}" y="${z.ly}" font-family="Microsoft YaHei" font-size="12" font-weight="bold" fill="#555">${z.label}</text>`;
- });
- // 主线流程节点
- const mainNodes = [
- { x: 30, y: 85, w: 130, h: 38, label: 'S1工单下达\n(工单传入)', color: '#6C757D' },
- { x: 190, y: 85, w: 130, h: 38, label: '生产排程\n(工序+优先级)', color: '#1565C0' },
- { x: 350, y: 85, w: 130, h: 38, label: '物料需求同步\n(MRP确认)', color: '#1565C0' },
- { x: 510, y: 85, w: 130, h: 38, label: '可执行日计划\n(下达产线)', color: '#1565C0' },
- { x: 660, y: 85, w: 100, h: 38, label: 'S6 MES\n生产执行', color: '#6C757D' },
- { x: 510, y: 150, w: 130, h: 38, label: '进度看板\n(完工/领料)', color: '#2E7D32' },
- ];
- mainNodes.forEach(n => {
- const fill = n.color === '#6C757D' ? '#E0E0E0' : n.color;
- const stroke = n.color === '#6C757D' ? '#999' : n.color;
- const tc = n.color === '#6C757D' ? '#555' : '#fff';
- svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
- const lines = n.label.split('\n');
- const cy = n.y + n.h / 2;
- lines.forEach((l, li) => {
- const off = (lines.length - 1) * -7 + li * 14;
- svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
- });
- });
- // 箭头
- const arrows = [
- { x1: 160, y1: 104, x2: 190, y2: 104 }, { x1: 320, y1: 104, x2: 350, y2: 104 },
- { x1: 480, y1: 104, x2: 510, y2: 104 }, { x1: 640, y1: 104, x2: 660, y2: 104 },
- { x1: 575, y1: 123, x2: 575, y2: 150 },
- ];
- arrows.forEach(a => {
- svg += `<line x1="${a.x1}" y1="${a.y1}" x2="${a.x2}" y2="${a.y2}" stroke="#666" stroke-width="1.2"/>`;
- const ang = Math.atan2(a.y2 - a.y1, a.x2 - a.x1);
- const lx = a.x2 - 6 * Math.cos(ang), ly = a.y2 - 6 * Math.sin(ang);
- svg += `<polygon points="${lx + 4 * Math.sin(ang)},${ly - 4 * Math.cos(ang)} ${lx - 4 * Math.sin(ang)},${ly + 4 * Math.cos(ang)} ${lx + 6 * Math.cos(ang)},${ly + 6 * Math.sin(ang)}" fill="#666"/>`;
- });
- svg += `<text x="425" y="110" font-family="Microsoft YaHei" font-size="9" fill="#999" text-anchor="middle">(灰色=S1/S6跨模块衔接)</text>`;
- // 支撑流程节点
- const suppNodes = [
- { x: 40, y: 280, w: 145, h: 36, label: '产线工作日历\n(班次定义)', color: '#E65100' },
- { x: 215, y: 280, w: 145, h: 36, label: '休息时间管理\n(产线停休)', color: '#E65100' },
- { x: 390, y: 280, w: 145, h: 36, label: '节假日管理\n(休假/调班)', color: '#E65100' },
- { x: 565, y: 280, w: 145, h: 36, label: '加班管理\n(延长工时)', color: '#E65100' },
- ];
- suppNodes.forEach(n => {
- svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
- const lines = n.label.split('\n');
- const cy = n.y + n.h / 2;
- lines.forEach((l, li) => {
- const off = (lines.length - 1) * -7 + li * 14;
- svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="#fff" text-anchor="middle">${l}</text>`;
- });
- });
- // 数据流节点
- const dataNodes = [
- { x: 40, y: 418, w: 130, h: 36, label: 'WorkOrdMaster', color: '#2E7D32' },
- { x: 200, y: 418, w: 120, h: 36, label: 'mdp_stg_*', color: '#6A1B9A' },
- { x: 350, y: 418, w: 120, h: 36, label: 'mdp_std_*', color: '#6A1B9A' },
- { x: 500, y: 418, w: 120, h: 36, label: 'dwd_* (宽表)', color: '#0D47A1' },
- { x: 650, y: 418, w: 100, h: 36, label: 'KPI 指标层', color: '#B71C1C' },
- ];
- dataNodes.forEach(n => {
- svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
- svg += `<text x="${n.x + n.w/2}" y="${n.y + n.h/2 + 4}" font-family="Microsoft YaHei" font-size="10" fill="#fff" text-anchor="middle">${n.label}</text>`;
- });
- for (let i = 0; i < dataNodes.length - 1; i++) {
- const a = dataNodes[i], b = dataNodes[i + 1];
- const ax = a.x + a.w, ay = a.y + a.h / 2, bx = b.x;
- svg += `<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${ay}" stroke="#666" stroke-width="1.2"/>`;
- svg += `<polygon points="${bx - 6},${ay - 4} ${bx - 6},${ay + 4} ${bx},${ay}" fill="#666"/>`;
- }
- svg += `<text x="265" y="240" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↑ 为排程和日计划提供可用产能约束 ↑</text>`;
- svg += '</svg>';
- return sharp(Buffer.from(svg)).png().toBuffer();
- }
- // ═══════════════════════════════════════════════════
- // 章节构建函数
- // ═══════════════════════════════════════════════════
- function buildChapter(num, title, cfg) {
- const ch = [];
- const pre = `${num}.`;
- // §X.1 目标/宗旨
- ch.push(h2(`${pre}1 目标/宗旨`));
- (cfg.targets || []).forEach(t => ch.push(bullet(t)));
- // §X.2 业务流程图
- ch.push(h2(`${pre}2 业务流程图`));
- const img = cfg.flowImage || (cfg.flowSteps ? flowImageCache[num] : null);
- if (img) {
- ch.push(flowImageParagraph(img));
- } else {
- ch.push(p('(见系统操作流程图 / 附件 Visio 流程图)'));
- }
- if (cfg.flowNote) ch.push(p(cfg.flowNote));
- // §X.3 业务流程说明
- ch.push(h2(`${pre}3 业务流程说明`));
- if (cfg.activities && cfg.activities.length) {
- ch.push(activityTable(cfg.activities));
- } else {
- ch.push(p('参照系统页面操作流程,主要操作步骤包含:'));
- (cfg.activityDesc || []).forEach(a => ch.push(bullet(a)));
- }
- // §X.4 业务流程规则
- ch.push(h2(`${pre}4 业务流程规则`));
- if (cfg.rules && cfg.rules.length) {
- ch.push(ruleTable(cfg.rules));
- } else {
- ch.push(p('无特殊业务规则'));
- }
- // §X.5 变化和改进点
- ch.push(h2(`${pre}5 变化和改进点`));
- (cfg.improvements || []).forEach(i => ch.push(bullet(i)));
- // §X.6 权限管理需求
- ch.push(h2(`${pre}6 权限管理需求`));
- if (cfg.permissions && cfg.permissions.length) {
- ch.push(permTable(cfg.permissions));
- } else {
- ch.push(p('参照第 6 章 权限管理汇总'));
- }
- // §X.7 系统接口集成
- ch.push(h2(`${pre}7 系统接口集成`));
- if (cfg.interfaces && cfg.interfaces.length) {
- ch.push(interfaceTable(cfg.interfaces));
- } else {
- ch.push(p('无外部系统接口;页面数据通过 Admin.NET 内置 API 获取'));
- }
- // §X.8 报表需求
- ch.push(h2(`${pre}8 报表需求`));
- if (cfg.reports && cfg.reports.length) {
- ch.push(reportTable(cfg.reports));
- } else {
- ch.push(p('无独立报表需求,相关数据通过看板和列表页面查看'));
- }
- return ch;
- }
- // ═══════════════════════════════════════════════════
- // 章节数据配置
- // ═══════════════════════════════════════════════════
- // ── Chapter 2: 生产排程 ──
- const _ch2cfg = {
- targets: [
- '实现生产工单的在线排程管理,将S1下达的工单按工序展开并确定执行优先级。',
- '通过"生产排程"按钮统一触发工单排程计算,结合产线日历和资源产能约束生成可执行的工序任务。',
- '支持工单优先级调整(加急/特急),快速响应生产异常和插单需求。',
- '提供排产异常记录管理和工单关闭功能,形成完整的排程闭环。',
- '工单工序排程中支持物料需求同步(MRP联动)、工艺路线同步和工序明细查看。',
- ],
- flowNote: '流程路径:工单工序排程列表 → 筛选查询工单 → 生产排程(工具栏按钮) → 同步物料需求 → 调整优先级 → 排产数据维护异常记录 → 工单关闭。',
- flowSteps: [
- { label: '工单工序排程列表查询', role: '计划员/调度' },
- { label: '生产排程(工具栏触发)', role: '计划员/调度' },
- { label: '同步物料需求(MRP)', role: '计划员/调度' },
- { label: '优先级调整(加急/特急)', role: '计划员/调度' },
- { label: '工单关闭 / 异常处理', role: '计划员/调度' },
- ],
- activities: [
- ['10', '工单排程列表查询', '计划员/调度', '通过工单编号、生产批号、物料编码、开工日期、状态等条件筛选排程工单', '筛选条件', '排程工单列表'],
- ['20', '生产排程', '计划员/调度', '点击工具栏"生产排程"按钮,系统根据排程算法(考虑产线日历、工序、产能)生成排产结果', '工单列表+产线日历', '排产结果'],
- ['30', '同步物料需求', '计划员/调度', '点击"同步物料需求"按钮,同步生成物料需求计划(MRP),确保排产物料齐套', '工单列表', 'MRP需求'],
- ['40', '工单优先级别管理', '计划员/调度', '通过操作列"加急"或"特急"按钮调整工单优先级;弹出优先级调整表单,可选择原因说明', '生产异常/插单需求', '优先级更新'],
- ['50', '同步工艺路线', '计划员/调度', '点击"同步工艺路线"按钮,从产品设计中同步最新工艺路线到排程工单', '产品设计工艺', '工单工序明细'],
- ['60', '工单执行追踪', '计划员/调度', '点击"工单执行追踪"查看工单全流程执行进度', '工单编号', '执行进度'],
- ['70', '排产异常记录', '计划员/调度', '在排产异常记录页面新增/编辑/删除排产数据维护异常记录,记录工单编号、物料编码、操作类型和执行时间', '排产数据维护', '异常记录'],
- ['80', '工单关闭', '计划员/调度', '勾选多行工单后点击"工单关闭"按钮(批量操作),将工单状态置为已关闭', '选中的工单行', '已关闭工单'],
- ],
- rules: [
- ['1', '排程数据来源', '工单排程页面数据来自WorkOrdMaster表,通过/api/WorkOrder/scheduling/list读取'],
- ['2', '工单状态流转', '待下达(S1)→已排产→生产中→已完成→已关闭;状态通过行内下拉框直接修改'],
- ['3', '优先级枚举', '优先级枚举值:1=普通,2=加急,3=特急;通过PriorityDialogForm选择调整'],
- ['4', '工单关闭', '支持批量勾选后一次性关闭,前端按钮在未选行时置灰;关闭操作不可逆(后端校验工单状态、数量约束)'],
- ['5', '排程算法', '生产排程按钮触发后端排程服务,综合考虑产线工作日历、工序顺序、资源产能约束'],
- ['6', '排产异常管理', '异常记录(ScheduleExceptionMaster表)支持独立CRUD,与排程工单通过工单编号关联;记录操作类型和执行时间'],
- ['7', '工单编码唯一性', '工单编号全局唯一,由上游S1系统生成后传入'],
- ['8', '防重复排程', '已排程且生产中的工单不可重复排程;排程前检查工单状态'],
- ],
- improvements: [
- '实现在线生产排程管理,替代线下Excel排程方式。',
- '工单优先级在线调整(加急/特急),支持快速响应插单和异常。',
- '排产异常记录可追溯,形成完整的排产数据维护日志。',
- '工单关闭归口统一,与S1下达形成闭环联动。',
- '工艺路线同步机制确保排程与设计数据一致。',
- ],
- permissions: [
- ['1', '计划员/调度', '查看工单排程列表;执行生产排程;同步物料需求;调整优先级;工单关闭;管理排产异常记录'],
- ['2', '车间主管', '查看工单排程列表和排产异常记录'],
- ],
- interfaces: [
- ['1', '工单排程列表API', 'GET /api/WorkOrder/scheduling/list 读取WorkOrdMaster表', '实时(页面加载)'],
- ['2', '生产排程API', 'POST /api/WorkOrder/scheduling/production-schedule 触发后端排程计算', '手动触发(工具栏)'],
- ['3', '同步物料需求API', 'POST /api/WorkOrder/scheduling/sync-material 触发MRP需求计算', '手动触发(工具栏)'],
- ['4', '工单关闭API', 'POST /api/WorkOrder/scheduling/batch-close 批量关闭工单', '手动触发(工具栏)'],
- ['5', '排产异常CRUD API', 'GET/POST/PUT/DELETE /api/ScheduleException/* 排产异常记录管理', '实时(CRUD)'],
- ['6', '同步工艺路线API', 'POST /api/WorkOrder/scheduling/sync-routing 从产品设计同步工艺到工单', '手动触发(行操作)'],
- ['7', '工单状态同步MES', '排程结果和工单状态变更同步至MES系统', '实时'],
- ],
- reports: [
- ['1', '工单排程达成率', '统计生产排程与工单下达的时间差和排程覆盖率', '按周/按月'],
- ['2', '排产异常统计', '按异常类型、物料、产线统计排产异常次数和分布', '按日/周/月'],
- ],
- };
- const ch2 = () => buildChapter('2', '生产排程', _ch2cfg);
- // ── Chapter 3: 作业计划 ──
- const _ch3cfg = {
- targets: [
- '在工单排程完成后,生成面向车间产线的可执行日计划,将排产结果精确到每天、每道工序、每台设备。',
- '管理产线工作日历(班次定义),为排程和日计划提供可用工时基础数据。',
- '管理产线休息时间,定义每日固定停休时段,排程时自动避开。',
- '管理产线节假日(休假/调班),确保排程不占用法定休息日。',
- '管理产线加班时段(ResourceOccupancyTime),为临时加产提供延长工时支持。',
- '通过日计划下达操作,将确认的日计划下发至S6 MES执行层。',
- ],
- flowNote: '流程路径:可执行日计划列表 → 筛选(计划日期≥今天默认) → 勾选多行 → 点击下达 → 日计划状态更新。同时维护:产线工作日历 → 休息时间 → 节假日 → 加班(基础数据CRUD)。',
- flowSteps: [
- { label: '可执行日计划筛选', role: '计划员/调度' },
- { label: '勾选待下达日计划行', role: '计划员/调度' },
- { label: '批量下达(工具栏)', role: '计划员/调度' },
- { label: '产线日历+休息+节假日维护', role: '基础数据管理员' },
- { label: '加班时段录入', role: '计划员/调度' },
- ],
- activities: [
- ['10', '日计划列表查询', '计划员/调度', '通过生产指令、物料编码、生产批次、工单状态、日计划下达状态、工作中心、设备类型、工序、计划日期等条件筛选', '筛选条件', '日计划列表'],
- ['20', '日计划下达', '计划员/调度', '勾选多行可执行日计划后点击"下达"按钮(批量操作),将日计划下发至MES执行层', '选中的日计划行', '已下达日计划'],
- ['30', '产线工作日历管理', '基础数据管理员', '新增/编辑/删除产线工作日历记录,定义每条产线每周每天的班次开始时间和时长(班次1+班次2)', '产线信息', '工作日历记录'],
- ['40', '产线休息时间管理', '基础数据管理员', '新增/编辑/删除产线休息时间记录,定义每个产线的固定休息开始时间点和休息时长(分钟)', '产线信息', '休息时间记录'],
- ['50', '产线节假日管理', '基础数据管理员', '新增/编辑/删除节假日记录,类型可选"休假"或"调班";排程时自动避开休假日期', '节假日定义', '节假日记录'],
- ['60', '产线加班管理', '计划员/调度', '新增/编辑/删除产线加班时段记录,定义加班起止时间、有效工作时长和休息时长', '加班需求', '加班记录'],
- ],
- rules: [
- ['1', '日计划默认筛选', '默认计划日期≥今天、工单未关闭;可通过"计划日期≥"条件调至更早日期查看历史数据'],
- ['2', '日计划下达为批量操作', '支持勾选多行后一次性下达;未选行时下达按钮置灰'],
- ['3', '工作中心下拉筛选', '支持从数据源远程搜索和筛选工作中心'],
- ['4', '产线工作日历字段', '星期(WeekDayName)、生产线(ProdLine)、描述、班次1开始/时长(小时)、班次2开始/时长(小时)'],
- ['5', '产线休息时间字段', '生产线(关联LineMaster)、产线描述、休息开始时间点、休息时长(分钟)、备注'],
- ['6', '节假日类型', '类型包含"休假"和"调班"两种,排程时自动过滤休假日期'],
- ['7', '加班记录字段', '产线(Resource)、开始/结束时间、有效工作时长(分钟)、休息时长(分钟)、类型、描述'],
- ['8', '基础数据关联', '产线工作日历和休息时间均关联LineMaster产线主数据;下拉选择时显示「Line|Describe」格式'],
- ],
- improvements: [
- '可执行日计划实现在线可视化,精确到每道工序和设备的日产能分配。',
- '产线工作日历在线管理,替代线下纸质排班表。',
- '休息时间、节假日、加班统一纳入系统,排程自动考虑产能约束。',
- '日计划下达与MES联动,信息在线流转替代人工传递。',
- ],
- permissions: [
- ['1', '计划员/调度', '查看可执行日计划列表;批量下达;查看产线日历、休息、节假日、加班信息'],
- ['2', '基础数据管理员', '新增/编辑/删除产线工作日历、休息时间、节假日、加班记录'],
- ['3', '车间主管', '查看可执行日计划列表和产线基础数据'],
- ],
- interfaces: [
- ['1', '可执行日计划列表API', 'GET /api/ScheduleDailyPlan/list 读取日计划数据', '实时(页面加载)'],
- ['2', '日计划下达API', 'POST /api/ScheduleDailyPlan/release 批量下达日计划', '手动触发(工具栏)'],
- ['3', '产线工作日历API', 'GET/POST/PUT/DELETE /api/ShopCalendarWorkCtr/*', '实时(CRUD)'],
- ['4', '产线休息时间API', 'GET/POST/PUT/DELETE /api/QualityLineRestDetail/*', '实时(CRUD)'],
- ['5', '产线节假日API', 'GET/POST/PUT/DELETE /api/HolidayMaster/*', '实时(CRUD)'],
- ['6', '产线加班API', 'GET/POST/PUT/DELETE /api/ResourceOccupancyTime/*', '实时(CRUD)'],
- ['7', '产线下拉查询API', 'GET /api/LineMaster/list 获取产线列表(供工作日历等下拉选项使用)', '实时(页面加载/下拉触发)'],
- ],
- reports: [
- ['1', '日计划执行率', '统计每日计划下达后的实际执行完成比率', '按日/周/月'],
- ['2', '产线利用率统计', '按产线统计总有效工时与计划工时的对比', '按周/按月'],
- ],
- };
- const ch3 = () => buildChapter('3', '作业计划', _ch3cfg);
- // ── Chapter 4: 制造协同看板与KPI ──
- const _ch4cfg = {
- targets: [
- '为管理层和业务人员提供S2模块的核心KPI可视化看板。',
- '通过工单执行进度看板展示每张工单的全流程关键进度,包括工单数量、入库数量、未入库数量、领料状态和进度百分比。',
- '通过指标看板(DynamicModuleDashboard, module-code="S2")展示L1层级核心指标。',
- '将S2模块的排程满足率、排程周期、在制库存周转天数和排程人效纳入KPI考核体系。',
- ],
- flowNote: '流程路径:工单执行进度看板 → 筛选查询 → 查看工单进度条和领料状态 → 定位于制造协同指标看板 → L1核心指标卡片 → 趋势分析。',
- flowSteps: [
- { label: '进入工单执行进度看板', role: '管理层/计划员' },
- { label: '筛选查询工单', role: '计划员' },
- { label: '查看进度条和领料状态', role: '计划员' },
- { label: '进入KPI指标看板', role: '管理层' },
- { label: '分析L1核心指标', role: '管理层' },
- ],
- activities: [
- ['10', '查看工单进度看板', '管理层/计划员', '进入工单执行进度看板页面,按工单编号、物料编码、计划开工日期筛选', '筛选条件', '工单进度列表'],
- ['20', '分析工单进度', '计划员', '查看进度百分条(完成数量/工单数量);关注领料状态和未入库数量', '工单进度数据', '进度分析'],
- ['30', '查看看板KPI', '管理层', '进入S2制造协同指标看板,查看L1核心KPI卡片', '—', 'L1指标展示'],
- ['40', '指标下钻分析', '管理层', '从L1指标下钻到L2分解指标,分析趋势变化', 'L1指标', '趋势图表'],
- ],
- rules: [
- ['1', '进度看板数据来源', '工单进度数据来自WorkOrdMaster,关联工艺默认产线和MES领料状态'],
- ['2', '进度计算规则', '进度百分比 = 入库数量(QtyCompleted) / 工单数量(QtyOrded) × 100%'],
- ['3', '单据类型', '单据类型枚举:s=销售工单、p=计划工单;通过typed列区分'],
- ['4', '单据状态', '状态枚举:w=投产、r=下达、c=关闭;看板中为只读展示'],
- ['5', 'L1核心指标(S2)', '订单排程满足率(S2-L1-A)、订单排程周期(S2-L1-B)、在制库存周转天数(S2-L1-C)、订单排程人效(S2-L1-D)'],
- ['6', '看板组件架构', '使用DynamicModuleDashboard动态看板组件,module-code="S2"'],
- ['7', '指标存储', '指标定义写入ado_smart_ops_kpi_master;计算结果写入ado_s9_kpi_value_* 按天粒度存储'],
- ['8', '领料状态', '领料状态(materialSituation)关联MES/WMS领料数据,反映工单物料准备的实时状况'],
- ],
- improvements: [
- '工单进度看板实现在线可视化,替代人工跟单和纸质报表方式。',
- '领料状态和进度条直观展示,管理层一目了然。',
- 'KPI自动化计算替代人工统计,排程满足率和周期指标实时更新。',
- '看板与数据中台对接,确保数据口径一致。',
- ],
- permissions: [
- ['1', '管理层', '查看S2看板全部指标和工单进度看板'],
- ['2', '计划员/调度', '查看工单进度看板;按条件筛选分析'],
- ],
- interfaces: [
- ['1', '工单进度看板API', 'GET /api/WorkOrderProgressDashboard/list', '实时(页面加载)'],
- ['2', 'S2看板聚合API', 'GET /api/AidopKanban/module-detail?moduleCode=S2', '实时(页面加载)'],
- ['3', 'KPI数据源', 'ado_s9_kpi_value_l1~l4_day 按天粒度存储', 'MDP定时刷新'],
- ],
- reports: [],
- };
- const ch4 = () => buildChapter('4', '制造协同看板与KPI', _ch4cfg);
- // ── Chapter 5: 数据中台架构 ──
- function buildCh5() {
- const ch = [];
- ch.push(h1('5 数据中台架构'));
- ch.push(h2('5.1 目标/宗旨'));
- ch.push(p('S2制造协同模块采用Ai-DOP统一数据中台分层架构,将工单排程数据、产线基础数据、MES执行数据经过贴源(STG)、标准化(STD)、宽表构建(DWD)、指标计算(KPI)四层处理,最终服务于前端排程列表、日计划、进度看板和KPI考核。'));
- ch.push(h2('5.2 分层设计'));
- ch.push(table(
- ['分层', '表前缀', '职责', '更新机制'],
- [
- ['贴源层(STG)', 'mdp_stg_*', '保留源系统原始数据,含raw_data/source_table/sync_batch_id', 'MDP同步覆盖写入'],
- ['标准层(STD)', 'mdp_std_*', '统一字段口径,清洗脏数据,形成标准业务对象', 'MDP转换(取最新批次)'],
- ['宽表层(DWD)', 'dwd_*', '面向页面和KPI的主题事实表,关联多源数据', 'MDP转换(取最新批次)'],
- ['指标层(KPI)', 'ado_s9_kpi_value_*', '按天存储L1-L4指标值', 'MDP KPI计算(取最新批次)'],
- ],
- [1500, 2000, 4200, 2300]
- ));
- ch.push(h2('5.3 S2核心数据流'));
- ch.push(bp('工单排程/日计划链路'));
- ch.push(p('WorkOrdMaster / ScheduleDailyPlan / ScheduleExceptionMaster'));
- ch.push(p(' → mdp_stg_production(贴源层)→ mdp_std_production(标准层)→ dwd_production_schedule(宽表)→ ado_s9_kpi_value_*(指标层)'));
- ch.push(bp('产线基础数据链路'));
- ch.push(p('ShopCalendarWorkCtr / QualityLineRestDetail / HolidayMaster / ResourceOccupancyTime'));
- ch.push(p(' → mdp_stg_production(贴源层)→ mdp_std_production(标准层)→ dwd_line_calendar(宽表)→ 排程产能约束计算'));
- ch.push(bp('MES执行反馈链路'));
- ch.push(p('MES领料状态 / 完工入库数据 → mdp_stg_production → dwd_production_schedule(宽表)→ 工单进度看板'));
- ch.push(h2('5.4 对象映射总表'));
- ch.push(table(
- ['业务对象', '运行表', '贴源层', '标准层/DWD', '消费端'],
- [
- ['工单排程', 'WorkOrdMaster', 'mdp_stg_production', 'mdp_std_production / dwd_production_schedule', '排程列表、日计划、KPI'],
- ['可执行日计划', 'ScheduleDailyPlan', 'mdp_stg_production', 'mdp_std_production / dwd_production_schedule', '日计划列表'],
- ['排产异常', 'ScheduleExceptionMaster', 'mdp_stg_production', 'mdp_std_production', '异常记录列表'],
- ['产线工作日历', 'ShopCalendarWorkCtr', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '日历管理、排程约束'],
- ['产线休息时间', 'QualityLineRestDetail', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '休息时间管理'],
- ['产线节假日', 'HolidayMaster', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '节假日管理'],
- ['产线加班', 'ResourceOccupancyTime', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '加班管理'],
- ['工单进度', 'WorkOrdMaster+领料状态', 'mdp_stg_production', 'dwd_production_schedule', '进度看板'],
- ],
- [1100, 2000, 1800, 2600, 2500]
- ));
- ch.push(h2('5.5 MDP作业调度'));
- ch.push(table(
- ['配置项', '内容'],
- [
- ['作业编码', 'S2_MDP_SYNC_TRANSFORM'],
- ['批次格式', 'S2_MDP_FULL_yyyyMMddHHmmss'],
- ['手动刷新入口', 'POST /api/Production/linkageplan/refresh'],
- ['监控入口', 'mdp_transform_run_log(作业日志)、mdp_sync_log(同步日志)、前端MDP监控页'],
- ['执行方式', '手动触发 + 未来扩展定时调度'],
- ],
- [2200, 7800]
- ));
- ch.push(h2('5.6 变化和改进点'));
- ch.push(bullet('建立统一数据中台分层架构,S2排程和日计划数据从运行时表标准化到中台。'));
- ch.push(bullet('产线基础数据(日历、休息、节假日、加班)纳入中台,确保排程约束数据唯一可信。'));
- ch.push(bullet('MES执行反馈数据接入中台,驱动工单进度看板实时更新。'));
- ch.push(bullet('MDP作业可观测,支持日志查询和异常追溯。'));
- return ch;
- }
- // ── Chapter 6: 权限管理汇总 ──
- function buildCh6() {
- const ch = [];
- ch.push(h1('6 权限管理汇总'));
- ch.push(p('以下汇总S2制造协同模块各功能所涉及的角色和权限:'));
- ch.push(table(
- ['序号', '角色', '权限范围', '涉及模块'],
- [
- ['1', '计划员/调度', '查看工单排程列表;执行生产排程;同步物料需求;调整优先级(加急/特急);工单关闭;查看/下达可执行日计划;管理排产异常记录;查看工单进度看板', '生产排程、作业计划、制造协同看板'],
- ['2', '基础数据管理员', '新增/编辑/删除产线工作日历、休息时间、节假日、加班时段', '作业计划(基础数据)'],
- ['3', '车间主管', '查看工单排程列表、可执行日计划列表、产线基础数据和进度看板', '生产排程、作业计划、制造协同看板'],
- ['4', '管理层', '查看S2看板全部KPI指标和工单进度看板', '制造协同看板'],
- ['5', '系统管理员', '管理用户角色权限;监控MDP作业状态;数据修复操作', '系统配置'],
- ],
- [600, 1800, 5000, 2600]
- ));
- return ch;
- }
- // ═══════════════════════════════════════════════════
- // 文档构建主流程
- // ═══════════════════════════════════════════════════
- async function buildDocument() {
- // 预生成所有流程图 PNG
- console.log('Generating flow chart images...');
- flowImageCache['overview'] = await generateOverviewFlowPNG();
- const allChapterConfigs = { '2': _ch2cfg, '3': _ch3cfg, '4': _ch4cfg };
- for (const [num, cfg] of Object.entries(allChapterConfigs)) {
- if (cfg.flowSteps) {
- flowImageCache[num] = await generateFlowPNG(cfg.flowSteps);
- }
- }
- console.log('Flow chart images generated.');
- const sections = [];
- // ── 封面 ──
- sections.push(empty(), empty(), empty());
- sections.push(new Paragraph({
- children: [new TextRun({ text: 'S2 制造协同模块', bold: true, size: 52, font: FONT, color: '1F4E79' })],
- alignment: AlignmentType.CENTER, spacing: { after: 100 },
- }));
- sections.push(new Paragraph({
- children: [new TextRun({ text: '蓝图设计方案', bold: true, size: 52, font: FONT, color: '1F4E79' })],
- alignment: AlignmentType.CENTER, spacing: { after: 600 },
- }));
- sections.push(new Paragraph({
- children: [new TextRun({ text: 'Ai-DOP 智慧运营决策平台', size: 28, font: FONT, color: '4472C4' })],
- alignment: AlignmentType.CENTER, spacing: { after: 120 },
- }));
- sections.push(new Paragraph({
- children: [new TextRun({ text: '版本:V1.0 日期:2026年6月8日 作者:彭熙玉', size: 22, font: FONT, color: '808080' })],
- alignment: AlignmentType.CENTER, spacing: { after: 2400 },
- }));
- sections.push(new Paragraph({
- children: [new TextRun({ text: '【机密文档 · 仅限项目内部使用】', size: 21, font: FONT, color: 'C00000', italics: true })],
- alignment: AlignmentType.CENTER,
- }));
- // ── 文档控制 ──
- sections.push(pageBreak(), h1('文档控制'));
- sections.push(h2('更改记录'));
- sections.push(table(['日期', '姓名', '版本', '变更说明'],
- [['2026/06/08', '彭熙玉', 'V1.0', '初版(基于DOP整体方案设计和S2模块实际实现)']],
- [1800, 1200, 1000, 6000]));
- sections.push(empty());
- sections.push(h2('审核'));
- sections.push(table(['姓名', '职位', '签字/日期'], [['', '', ''], ['', '', '']], [3000, 3000, 4000]));
- sections.push(empty());
- sections.push(h2('发布'));
- sections.push(table(['编号', '名称', '地点'], [['', '', '']], [3000, 4000, 3000]));
- // ── 目录 ──
- sections.push(pageBreak(), h1('目录'));
- sections.push(...buildTOC());
- sections.push(pageBreak());
- // ── 第1章: 总体业务方案 ──
- sections.push(h1('1 总体业务方案'));
- sections.push(h2('1.1 目标和宗旨'));
- sections.push(bullet('S2制造协同模块是Ai-DOP平台"从排程到执行"的核心衔接层,承接S1下达的工单,完成生产排程、作业计划编制和制造进度监控。'));
- sections.push(bullet('通过生产排程将工单按工序展开、确定优先级并计算产能约束,生成可执行的工序任务。'));
- sections.push(bullet('通过作业计划将排程结果精细化到每日每道工序,并管理产线工作日历、休息时间、节假日和加班等基础产能数据。'));
- sections.push(bullet('通过制造协同看板实时监控工单执行进度,驱动KPI考核和持续改善。'));
- sections.push(h2('1.2 总体业务流程图'));
- sections.push(p('S2制造协同模块承接S1下达的工单,完成生产排程和作业计划编制,并将结果下发至S6 MES执行层。下图展示了主线流程(排程到执行)、基础数据管理(支撑排程)和底层数据中台链路的整体关系:'));
- sections.push(flowImageParagraph(flowImageCache['overview']));
- sections.push(h2('1.3 方案设计'));
- sections.push(p('S2模块作为Ai-DOP S0-S9模块化架构中的"制造协同"功能域,覆盖以下核心功能模块:'));
- sections.push(bullet('生产排程:工单工序排程(优先级管理、物料同步、工艺同步、工单关闭)+ 排产异常记录管理'));
- sections.push(bullet('作业计划:可执行日计划(批量下达)+ 产线工作日历/休息时间/节假日/加班(基础数据管理)'));
- sections.push(bullet('制造协同看板:工单执行进度看板(进度条+领料状态)+ KPI指标看板(L1/L2层级)'));
- sections.push(p('S2模块与S1(工单下达)、S6(生产执行/MES)紧密衔接:从S1接收已下达工单,排程后生成日计划下发给S6,并接收MES领料和入库反馈更新进度看板。基础数据(产线日历等)为排程算法提供可用产能约束,确保排程结果的可行性和准确性。'));
- // ── 第2~4章: 各功能模块 ──
- sections.push(pageBreak()); sections.push(h1('2 生产排程')); sections.push(...ch2());
- sections.push(pageBreak()); sections.push(h1('3 作业计划')); sections.push(...ch3());
- sections.push(pageBreak()); sections.push(h1('4 制造协同看板与KPI')); sections.push(...ch4());
- // ── 第5~6章 ──
- sections.push(pageBreak()); sections.push(...buildCh5());
- sections.push(pageBreak()); sections.push(...buildCh6());
- return sections;
- }
- // ═══════════════════════════════════════════════════
- // 生成文档
- // ═══════════════════════════════════════════════════
- async function main() {
- const doc = new Document({
- styles: { default: { document: { run: { font: FONT, size: 21 } } } },
- sections: [{
- properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.0), right: convertInchesToTwip(1.0) } } },
- children: await buildDocument(),
- }],
- });
- const buffer = await Packer.toBuffer(doc);
- const outPath = 'd:\\DEMONET\\doc\\S2制造协同模块蓝图设计方案.docx';
- const fallbackPath = 'd:\\DEMONET\\doc\\S2制造协同模块蓝图设计方案_V2.docx';
- const tempPath = 'd:\\DEMONET\\doc\\S2_blueprint_temp.docx';
- fs.writeFileSync(tempPath, buffer);
- try { fs.unlinkSync(outPath); } catch(e) { /* 被占用则跳过 */ }
- try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
- catch(e) {
- try { fs.unlinkSync(fallbackPath); } catch(_) {}
- try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
- catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
- }
- console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
- }
- main().catch(err => { console.error('Error:', err); process.exit(1); });
|