aps-agent/build-manual-v2.js

322 lines
21 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// 图文版操作手册(大白话 + 真实截图)生成脚本
// ============================================================
import fs from "node:fs";
import path from "node:path";
import {
AlignmentType, BorderStyle, Document, Footer, Header, HeadingLevel,
ImageRun, Packer, PageBreak, PageNumber, Paragraph, ShadingType,
Table, TableCell, TableRow, TextRun, WidthType,
} from "docx";
const outputPath = process.argv[2];
if (!outputPath) throw new Error("Usage: node build-manual-v2.js /abs/out.docx");
const ROOT = path.dirname(outputPath);
const SHOTS = path.join(ROOT, "_shots");
const T = String.raw;
const palette = { dark: "1F2A36", primary: "2F4858", light: "6B7A86", fill: "F2F5F7" };
const fontBody = { ascii: "Times New Roman", hAnsi: "Times New Roman", cs: "Times New Roman", eastAsia: "SimSun" };
const fontHead = { ascii: "Times New Roman", hAnsi: "Times New Roman", cs: "Times New Roman", eastAsia: "SimHei" };
const run = (text, options = {}) => new TextRun({ text, font: fontBody, size: 24, ...options });
const para = (children, options = {}) => new Paragraph({
spacing: { after: 140, line: 340 }, ...options,
children: Array.isArray(children) ? children : [children],
});
const body = (text) => para(run(text), { indent: { firstLine: 480 }, alignment: AlignmentType.JUSTIFIED });
const h1 = (text) => para(new TextRun({ text, font: fontHead, bold: true, size: 32, color: palette.dark }), {
heading: HeadingLevel.HEADING_1, spacing: { before: 360, after: 200 },
});
const h2 = (text) => para(new TextRun({ text, font: fontHead, bold: true, size: 26, color: palette.primary }), {
heading: HeadingLevel.HEADING_2, spacing: { before: 240, after: 140 },
});
const tip = (text) => para(
[new TextRun({ text: T`💡 `, size: 24 }), run(text, { color: palette.primary })],
{ indent: { left: 240 }, shading: { type: ShadingType.CLEAR, fill: palette.fill } },
);
let figNo = 0;
const fig = (file, captionText) => {
figNo += 1;
const img = new ImageRun({
type: "png",
data: fs.readFileSync(path.join(SHOTS, file)),
transformation: { width: 640, height: 400 },
});
return [
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 120, after: 60 }, children: [img] }),
para(new TextRun({ text: T`图 ${figNo} ${captionText}`, font: fontHead, size: 20, color: palette.light }), {
alignment: AlignmentType.CENTER, spacing: { after: 200 },
}),
];
};
const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
const table3 = (headers, rows, widths) => {
const total = widths.reduce((a, b) => a + b, 0);
const mk = (text, i, opts = {}) => new TableCell({
children: [new Paragraph({
spacing: { after: 30, line: 300 },
children: [new TextRun({ text, font: opts.bold ? fontHead : fontBody, size: 21, bold: !!opts.bold })],
})],
width: { size: widths[i], type: WidthType.DXA },
margins: { top: 70, bottom: 70, left: 100, right: 100 },
borders: opts.borders,
});
return new Table({
width: { size: total, type: WidthType.DXA },
columnWidths: widths,
rows: [
new TableRow({
tableHeader: true,
children: headers.map((h, i) => mk(h, i, {
bold: true,
borders: {
top: { style: BorderStyle.SINGLE, size: 12, color: palette.dark },
bottom: { style: BorderStyle.SINGLE, size: 6, color: palette.dark },
left: noBorder, right: noBorder,
},
})),
}),
...rows.map((row, ri) => new TableRow({
children: row.map((c, i) => mk(String(c), i, {
borders: {
top: noBorder,
bottom: ri === rows.length - 1
? { style: BorderStyle.SINGLE, size: 12, color: palette.dark } : noBorder,
left: noBorder, right: noBorder,
},
})),
})),
],
});
};
const children = [];
const add = (...items) => children.push(...items);
const PAGEBREAK = () => add(new Paragraph({ children: [new PageBreak()] }));
// ---------------- 封面 ----------------
add(
para(new TextRun({ text: T`工业智核 APS 排产智能体`, font: fontHead, bold: true, size: 48, color: palette.dark }), {
alignment: AlignmentType.CENTER, spacing: { before: 3200, after: 240 },
}),
para(new TextRun({ text: T`图 文 使 用 手 册`, font: fontHead, bold: true, size: 44, color: palette.dark }), {
alignment: AlignmentType.CENTER, spacing: { after: 900 },
}),
para(run(T`—— 全截图实操版 · 看完就会用 ——`, { color: palette.light }), {
alignment: AlignmentType.CENTER, spacing: { after: 2400 },
}),
para(run(T`版本:V1.0(按 2026-09-01 系统实际界面截图)`), { alignment: AlignmentType.CENTER, spacing: { after: 160 } }),
para(run(T`适合谁看:计划员、车间调度、实施和售前同事`), { alignment: AlignmentType.CENTER }),
new Paragraph({ children: [new PageBreak()] }),
);
// ---------------- 写在前面 ----------------
add(
h1(T`写在前面`),
body(T`这本手册不讲原理、不堆术语,就干一件事:照着截图,一步一步告诉你这个系统每个功能是干嘛的、在哪点、怎么用。手册里每一张图都是从真实系统上现截的,你照着点,看到的就是图上那样。`),
body(T`这个系统一句话就能说清:它是一个会听人话的排产助手。你在左边跟它聊天(比如“跑一版柔性排产”),它在右边把甘特图、产能、订单这些画面给你摆出来。排产行业里管这叫 APS(高级计划排产),你就把它当成一个特别懂生产、还特别守规矩的计划员搭档就行。`),
body(T`说它守规矩,是因为系统里有个铁律:凡是动真格的操作(发布版本、下达采购、改主数据、下发车间),它都会先弹一张“确认卡”让你点“批准”才执行,执行前还自动拍个快照存档。所以放心大胆地点,错了随时能回滚。`),
);
// ---------------- 一、登录 ----------------
PAGEBREAK();
add(
h1(T`一、登录:选对企业,输账号密码`),
body(T`打开系统后,先点左下角的“登录”。在弹窗里填三样东西:企业名称、用户名、密码,然后点登录按钮。账号由你们企业的管理员开通,忘了密码也找管理员,系统本身没有自助注册。`),
...fig("01-登录.png", T`登录弹窗:企业名称 + 用户名 + 密码`),
tip(T`如果你用的是桌面版(安装包装的那个),不用账号,输入授权码就能进。`),
);
// ---------------- 二、主界面 ----------------
add(
h1(T`二、先认门:主界面分三块`),
body(T`登录进来看到的就是主界面。记住三块区域,后面所有操作都绕不开它们:`),
...fig("02-主界面.png", T`主界面:左边办事入口,中间聊天,右边工具条`),
table3(
[T`区域`, T`叫什么`, T`干嘛用的`],
[
[T`左边竖栏`, T`导航栏`, T`新建任务、看订单、管主数据、进柔性工作台;下面是你的项目和任务列表`],
[T`中间大块`, T`对话区`, T`跟系统聊天的地方。底下输入框打字,回车发送;系统回的话里会带各种卡片,能直接点`],
[T`右边细条`, T`工具导轨`, T`一列小图标:会话结果、订单、主数据、柔性工作台、多智能体、图纸识别……点哪个,哪个就在右边展开`],
],
[1700, 1700, 5800],
),
body(T`刚进系统时中间会有一张欢迎页,给你三个常用建议(跑一版柔性排产、试排一版交期优先、多策略方案对比),点一下就等于把这句话打进对话区,新手从这儿开始最省事。`),
);
// ---------------- 三、对话区 ----------------
PAGEBREAK();
add(
h1(T`三、动嘴就能干活:对话区`),
h2(T`3.1 让它排一版产`),
body(T`在底部输入框打一句“跑一版柔性排产”,回车。系统想一会儿,就会回你一张结果卡:排了多少条虚拟产线、多少张工单、最晚哪天完工、有几张单能准时、有没有冲突。有什么料缺、什么工艺没确认,它也会老老实实列出来,绝不装没事。`),
...fig("03-对话柔性排产.png", T`说一句“跑一版柔性排产”,结果直接打在对话里`),
tip(T`常用的说法还有:“试排一版交期优先”“按产能均衡排一下”“换型最小化试排”。注意“试排”只是草稿,不会动真数据。`),
h2(T`3.2 让几个策略打擂台`),
body(T`拿不准哪种排法好?说一句“对比几种策略”。系统会在后台沙盒里把交期优先、产能均衡、换型最小化、战役合并、综合优化几种打法各跑一遍,给你一溜方案卡,每张卡上写清延迟、冲突、利用率、换型次数。看中哪张点“采用此方案”,它就变成正式草稿。`),
...fig("19-对话策略对比.png", T`“对比几种策略”:五种打法并排比,选好就采用`),
h2(T`3.3 有规矩不懂,直接问`),
body(T`系统里有个知识库,装着你们的工艺规定、SOP 这些文档。直接问“换线有什么规定”,它会把相关规定翻出来给你,并且标明出处;没查到就老实说没查到,绝不瞎编。`),
...fig("20-对话知识问答.png", T`知识问答:答案带来源,查不到就老实说没有`),
);
// ---------------- 四、柔性工作台 ----------------
PAGEBREAK();
add(
h1(T`四、不爱打字?点按钮也行:柔性工作台`),
body(T`点右边工具导轨的“柔性工作台”,右边就展开一个操作台,排产能点的按钮都在这儿。这是为“多品种小批量、设备能挪来挪去”的车间准备的:系统不问“走哪条产线”,只问“哪道工序”,从能干这道工序的设备池里临时挑设备,拼一条一次性的“虚拟产线”,用完就散。`),
...fig("12-柔性工作台.png", T`柔性工作台:排产、询价、异常、冲突、What-if 一页搞定`),
body(T`台面上你能干这些事:`),
table3(
[T`按钮区`, T`点了会怎样`],
[
[T`瓶颈锚排产 / 正排 / 倒排`, T`三种排法任选:按交期顺推、从交期倒推、或者卡住最慢的瓶颈工序往两头排`],
[T`三模式对比`, T`三种排法一起跑,摆在一起比,哪种好一眼看见`],
[T`滚动时域`, T`选排产看多远:实时 1 小时、短窗 2 小时、中窗 2 天、长窗 7 天`],
[T`分级重排`, T`出异常时选动作幅度:L2 短窗、L3 日窗、L4 全局重排(都要你确认才动)`],
[T`交期询价`, T`销售问“这批啥时候能交”,把产品和数量填进去,给出乐观 / 预计两个交期和缺口提示`],
[T`SAP 集成 / MES 执行`, T`跟 ERP 拉单回写、把定稿的排产下发车间(下发要二次确认)`],
],
[2600, 6600],
),
tip(T`截图下方还能看到“当前限制性瓶颈:喷涂,日产能≈1728件”——系统永远告诉你全厂最慢的那道工序是谁,产能就按它算,不虚报。`),
);
// ---------------- 五、看结果 ----------------
PAGEBREAK();
add(
h1(T`五、排完了去哪看:结果视图`),
body(T`排产有结果后,点右边导轨第一个图标“会话结果”,右边就打开一排页签:甘特图、负荷热力、利用率、交期承诺、分桶计划、KPI、对比表、世界对比、产能池。挨个看:`),
h2(T`5.1 产能池:全厂家底一页看清`),
body(T`每个工序能力池一张卡:日产能多少、几台设备、负载百分之几,未来 4 小时 / 1 天 / 1 周的预测都在。最上面黄条永远标着“限制性瓶颈”是谁。哪台设备停了、调走了,这里马上重算。`),
...fig("09-产能池.png", T`产能池仪表盘:瓶颈一目了然,设备停了立刻重算`),
h2(T`5.2 利用率:哪台设备闲、哪台累`),
body(T`设备热力图按天涂色:绿的是闲(<50%),黄橙渐忙,红的是超载(≥100%)。哪台设备天天红、哪台天天绿,排产合不合理一眼就知道。`),
...fig("23-利用率.png", T`资源利用率:设备 7 天热力图,红黄绿一眼分明`),
h2(T`5.3 KPI 仪表盘:老板要的数都在这`),
body(T`平均利用率、准时率、总延迟、在制工单、未解冲突、MES 完工率,六张卡汇总固定轨和柔性轨两本账。`),
...fig("08-KPI仪表盘.png", T`运营 KPI:利用率、准时率、延迟、冲突一屏汇总`),
h2(T`5.4 分桶计划:看得更远一点`),
body(T`这是给“排产之前”做打算的:近 7 天按日、中期按周、远期按月,把产能和需求放进桶里比。还能点“可行性”看订单能不能按期、点“库存投影”看哪天会断料、点“削峰”让系统建议把超产的桶往空档挪。`),
...fig("07-分桶计划.png", T`分桶计划:粗能力、可行性、库存投影、削峰都在这`),
h2(T`5.5 对比表与世界对比`),
body(T`“对比表”把几种策略的指标排成矩阵,最优的格子高亮;“世界对比”把两个版本的世界状态并排摆开,订单、资源占用、KPI 差了多少逐项列——回滚前后、方案前后差在哪,不用猜。`),
...fig("22-世界对比.png", T`两个世界对比:版本之间差了什么,逐项摆开`),
tip(T`甘特图页签在试排出正式版本后,会把每张工单画成时间条,工单还能直接用手拖着调(15 分钟吸附,拖完出确认卡,批准才生效)。`),
);
// ---------------- 六、订单 ----------------
PAGEBREAK();
add(
h1(T`六、订单管理:从接单到下发一条链`),
body(T`点右边导轨“订单管理”。最上面是订单总数、待审、加急、预测四个数;下面一排是“闭环 APS 阶段”流水线,把一张单子的命分成六站:商务订单 → BOM/供需净算 → 制造需求准入 → 排产草案 → P2 发布 → P3 下发 MES。每站都有独立门禁,过不去就卡在那站,系统会告诉你缺什么。`),
...fig("13-订单管理.png", T`订单管理:六站流水线,卡在哪站、缺什么,写得明明白白`),
body(T`这里你还能:新增订单、批量批准待审订单、一键清理、导入 Excel/CSV 订单、看“排产分析报告”。订单相关的口令也好使:“订单池”“提交审核”“批量批准”“插单快评”。`),
);
// ---------------- 七、主数据 ----------------
add(
h1(T`七、主数据:排产的米和锅`),
body(T`点右边导轨“主数据”。排产准不准,七分靠主数据。这里分几个页签:资源(工厂→车间→产线→设备的家谱树)、工艺模型(物料 / BOM / 工序 / 工艺路线 / 产线绑定 / 换型矩阵)、柔性资源、日历与维保、工时维护。`),
...fig("14-主数据.png", T`主数据:工艺模型页签,物料、BOM、工序、路线都在这`),
body(T`录数据有个铁顺序,千万别乱:先工厂产线 → 再物料 → 再 BOM → 再工序 → 再工艺路线 → 再绑定产线,最后才录订单。缺一环,排产要么失败,要么排出来你也找不到依据。改任何主数据都会弹确认卡,而且只影响之后的新版本,不会偷偷改历史。`),
tip(T`懒得一条条录?点“导入 Excel/CSV”,先预览校验再确认入库;订单、物料、BOM、路线、设备、模具都能导。`),
);
// ---------------- 八、排产结构 ----------------
add(
h1(T`八、排产结构:一张单凭什么这么排`),
body(T`点右边导轨“排产结构”,能看到当前排产版本的结构化分解:哪张订单拆了哪几道工序、占了哪台设备哪个时间段。排产结果不是一张孤零零的图,每道工序都能回指到订单、BOM 和工艺——“凭什么这么排”永远有据可查。`),
...fig("15-排产结构.png", T`排产结构:订单到工序到设备的完整拆解`),
);
// ---------------- 九、多智能体 ----------------
PAGEBREAK();
add(
h1(T`九、多智能体编排:让一群 AI 替你跑流程`),
body(T`点右边导轨“多智能体编排”。这功能相当于组建一支 AI 小分队:你创建一个 Goal(比如“锐扬排产全流程”,自带解析数据 → MRP 分解 → 下达 → 排产 → 出报告五件事),点“分发执行”,系统就把活派给有对应本事的智能体,没有合适的就现场造一个。`),
...fig("16-多智能体编排.png", T`多智能体编排:挂 Goal、自动分发、看门狗盯梢`),
body(T`底下有个“看门狗”盯着:哪个任务超时(默认 45 秒)就收回来重排,反复失败就升级叫人来看;全干完自动结案。消息总线里能实时看到智能体之间接力传话。干砸了的任务会老实标 FAILED,不会假装成功。`),
);
// ---------------- 十、图纸识别 ----------------
add(
h1(T`十、图纸识别:DXF 图纸直接变主数据`),
body(T`点右边导轨“图纸识别”,把 CAD 的 DXF 图纸传上去(或填个路径、扫个文件夹)。系统自动读标题栏:图号、名称、比例、版本,每个字段都标了可信度;图纸还能在页面上缩放、平移、按图层开关地预览。`),
...fig("17-图纸识别.png", T`图纸识别:读标题栏、标置信度、SVG 预览`),
body(T`认出来的物料信息会变成“候选”,你核对一遍、点确认,才正式写进主数据。图纸上没有的信息(材质、工时、BOM 子件数量),系统一律标“缺失”,绝不替你瞎猜。`),
);
// ---------------- 十一、命令面板 ----------------
add(
h1(T`十一、懒人神器:Ctrl+K 命令面板`),
body(T`键盘按 Ctrl+K,屏幕中央弹出命令面板。想找什么功能直接打字过滤,比如输“柔性”,“柔性工作台”“柔性甘特”就都冒出来了,回车直达。所有面板和视图都能这么跳。`),
...fig("18-命令面板.png", T`Ctrl+K:输俩字,功能直达`),
);
// ---------------- 十二、安全规矩 ----------------
PAGEBREAK();
add(
h1(T`十二、几条安全规矩(知道就不慌)`),
table3(
[T`规矩`, T`通俗解释`],
[
[T`确认卡`, T`凡是动真格的(发布、下达、改主数据、下发 MES),先弹卡让你批准,不批不执行`],
[T`自动存档`, T`每次执行确认卡之前,系统自动拍快照。你也可以随时说“存个档”`],
[T`随时回滚`, T`点时间线上的存档就能回到过去,回滚前还会再帮你存一次现在的状态`],
[T`试排不动真数据`, T`“试排”“对比策略”都在草稿或沙盒里玩,主干数据分毫不动`],
[T`审计留痕`, T`谁、什么时候、批了什么,全部记账且改不了(哈希链),出了事能倒查`],
[T`不装成功`, T`排不动、缺料、没设备,系统直接红色阻断并写清原因,不会给你假绿灯`],
],
[2200, 7000],
),
);
// ---------------- 附录 ----------------
add(
h1(T`附录:张口就来的口令大全`),
table3(
[T`想干啥`, T`就说`],
[
[T`排产`, T`试排一版交期优先 / 按产能均衡排一下 / 换型最小化试排 / 跑一版柔性排产 / 三模式对比`],
[T`比方案`, T`对比几种策略 → 看中后点“采用此方案” → “发布这个版本”`],
[T`订单分解`, T`执行MRP分解 / 分解订单 SO-001 / 生成采购建议 / 下达MRP建议单`],
[T`插单`, T`插单快评 → 看影响 → 采用插单`],
[T`看计划层`, T`时间分桶 / 可行性分析 / 能否按期 / 库存投影 / 削峰 / 产供决策`],
[T`管订单`, T`订单池 / 提交审核 / 批量批准 / 查看预测 / 预测转正`],
[T`问规矩`, T`换线有什么规定 / @知识:标题`],
[T`出报表`, T`生成日报 / 版本对比 / 打开仪表盘`],
[T`存档后悔`, T`存个档 / 回滚`],
[T`数据`, T`分析一下数据文件 / 导入完整生产路线 / 清空数据重新初始化`],
],
[2000, 7200],
),
body(T`最后一句:系统答不上来的话会老实说“听不懂”或“没查到”,绝不糊弄你。遇到红色阻断别硬来,按它给的修复入口把缺的料、缺的工艺补上,再重新排就是了。`),
);
const doc = new Document({
features: { updateFields: false },
sections: [{
properties: {
page: { size: { width: 11906, height: 16838 }, margin: { top: 1440, bottom: 1440, left: 1440, right: 1440 } },
},
headers: {
default: new Header({ children: [para(run(T`工业智核 APS 排产智能体 · 图文使用手册`, { size: 18, color: palette.light }), { alignment: AlignmentType.CENTER, spacing: { after: 0 } })] }),
},
footers: {
default: new Footer({ children: [para(new TextRun({ children: [PageNumber.CURRENT], size: 18 }), { alignment: AlignmentType.CENTER, spacing: { after: 0 } })] }),
},
children,
}],
});
fs.writeFileSync(outputPath, await Packer.toBuffer(doc));