|
|
@@ -0,0 +1,361 @@
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+"""Generate wireframe PNGs and insert into S0-S2 功能设计说明.docx."""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import re
|
|
|
+from pathlib import Path
|
|
|
+from typing import Callable
|
|
|
+
|
|
|
+from docx import Document
|
|
|
+from docx.shared import Inches, Pt
|
|
|
+from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
|
+from docx.oxml import OxmlElement
|
|
|
+from docx.text.paragraph import Paragraph
|
|
|
+from PIL import Image, ImageDraw, ImageFont
|
|
|
+
|
|
|
+BASE = Path(r"D:\Projects\Ai-DOP\项目\项目管理\产互联项目管理\交付文档")
|
|
|
+MARKER = "【原型页面】"
|
|
|
+
|
|
|
+W, H = 1200, 680
|
|
|
+
|
|
|
+
|
|
|
+def load_fonts():
|
|
|
+ try:
|
|
|
+ s = ImageFont.truetype("msyh.ttc", 12)
|
|
|
+ m = ImageFont.truetype("msyh.ttc", 14)
|
|
|
+ b = ImageFont.truetype("msyh.ttc", 18)
|
|
|
+ xs = ImageFont.truetype("msyh.ttc", 11)
|
|
|
+ except OSError:
|
|
|
+ s = m = b = xs = ImageFont.load_default()
|
|
|
+ return xs, s, m, b
|
|
|
+
|
|
|
+
|
|
|
+def rr(draw, xy, radius=6, **kw):
|
|
|
+ draw.rounded_rectangle(xy, radius=radius, **kw)
|
|
|
+
|
|
|
+
|
|
|
+def text(draw, xy, value, font, fill="#444444"):
|
|
|
+ draw.text(xy, value, fill=fill, font=font)
|
|
|
+
|
|
|
+
|
|
|
+def shell(draw, xs, s, b, module, menu, title, subtitle):
|
|
|
+ draw.rectangle([0, 0, W, 44], fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (190, 14), f"Ai-DOP / {module}", s, "#444444")
|
|
|
+ draw.rectangle([0, 44, 170, H], fill="#1f2937")
|
|
|
+ text(draw, (16, 72), module, s, "#ffffff")
|
|
|
+ text(draw, (32, 98), menu, xs, "#cbd5e1")
|
|
|
+ draw.rectangle([170, 44, W, H], fill="#f3f4f6")
|
|
|
+ rr(draw, [190, 58, W - 20, 118], radius=8, fill="#111827")
|
|
|
+ text(draw, (210, 72), title, b, "#ffffff")
|
|
|
+ text(draw, (210, 98), subtitle, xs, "#cbd5e1")
|
|
|
+
|
|
|
+
|
|
|
+def filters(draw, xs, s, y, labels):
|
|
|
+ x = 210
|
|
|
+ for label in labels:
|
|
|
+ text(draw, (x, y), label, xs, "#444444")
|
|
|
+ rr(draw, [x, y + 16, x + 110, y + 42], radius=4, outline="#d9d9d9", fill="#ffffff")
|
|
|
+ text(draw, (x + 8, y + 24), "全部", xs, "#888888")
|
|
|
+ x += 130
|
|
|
+
|
|
|
+
|
|
|
+def toolbar(draw, s, y, buttons):
|
|
|
+ x = 210
|
|
|
+ for label, color in buttons:
|
|
|
+ w = max(64, len(label) * 14 + 24)
|
|
|
+ rr(draw, [x, y, x + w, y + 28], radius=4, fill=color, outline=color if color != "#ffffff" else "#d9d9d9")
|
|
|
+ fill = "#ffffff" if color != "#ffffff" else "#444444"
|
|
|
+ text(draw, (x + 10, y + 6), label, s, fill)
|
|
|
+ x += w + 10
|
|
|
+
|
|
|
+
|
|
|
+def table(draw, xs, s, y, headers, row=None, height=300):
|
|
|
+ rr(draw, [190, y, W - 20, y + height], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ draw.rectangle([190, y, W - 20, y + 34], fill="#fafafa", outline="#d9d9d9")
|
|
|
+ cols = len(headers)
|
|
|
+ col_w = (W - 230) // cols
|
|
|
+ for i, h in enumerate(headers):
|
|
|
+ text(draw, (200 + i * col_w, y + 10), h, xs, "#333333")
|
|
|
+ if row:
|
|
|
+ draw.line([190, y + 74, W - 20, y + 74], fill="#f0f0f0", width=1)
|
|
|
+ for i, cell in enumerate(row):
|
|
|
+ text(draw, (200 + i * col_w, y + 52), cell, xs, "#666666")
|
|
|
+
|
|
|
+
|
|
|
+def save_tpl(path: Path, draw_fn: Callable):
|
|
|
+ img = Image.new("RGB", (W, H), "#fafafa")
|
|
|
+ draw = ImageDraw.Draw(img)
|
|
|
+ draw_fn(draw)
|
|
|
+ path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
+ img.save(path, format="PNG")
|
|
|
+
|
|
|
+
|
|
|
+def tpl_crud(draw, module, menu, title, subtitle, filters_labels, add_label, headers, row):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, module, menu, title, subtitle)
|
|
|
+ filters(draw, xs, s, 132, filters_labels)
|
|
|
+ toolbar(draw, s, 188, [("查询", "#1677ff"), ("重置", "#ffffff"), (add_label, "#52c41a")])
|
|
|
+ table(draw, xs, s, 232, headers, row)
|
|
|
+
|
|
|
+
|
|
|
+def tpl_form(draw, module, menu, title, subtitle, fields):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, module, menu, title, subtitle)
|
|
|
+ rr(draw, [190, 132, W - 20, H - 30], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ y = 150
|
|
|
+ for label in fields:
|
|
|
+ text(draw, (210, y), label, xs, "#444444")
|
|
|
+ rr(draw, [210, y + 16, 520, y + 42], radius=4, outline="#d9d9d9", fill="#ffffff")
|
|
|
+ y += 56
|
|
|
+ toolbar(draw, s, H - 58, [("保存", "#1677ff"), ("重置", "#ffffff")])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_kpi_graph(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "智慧运营看板", "运营指标建模", "运营指标建模", "FUNC-S0-055/056 指标图谱与关系编辑")
|
|
|
+ rr(draw, [190, 132, W - 340, H - 30], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (210, 148), "L1/L2 指标关系图谱", s, "#333333")
|
|
|
+ for i in range(5):
|
|
|
+ x = 240 + i * 130
|
|
|
+ y = 220 + (i % 2) * 80
|
|
|
+ rr(draw, [x, y, x + 90, y + 34], radius=6, fill="#e6f4ff", outline="#91caff")
|
|
|
+ text(draw, (x + 10, y + 10), f"L1-{i+1}", xs, "#1677ff")
|
|
|
+ rr(draw, [W - 310, 132, W - 20, H - 30], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (W - 290, 148), "右侧编辑抽屉", s, "#333333")
|
|
|
+ text(draw, (W - 290, 178), "显示名称", xs, "#444444")
|
|
|
+ rr(draw, [W - 290, 194, W - 40, 220], radius=4, outline="#d9d9d9")
|
|
|
+ text(draw, (W - 290, 236), "计算说明 / 聚合方式", xs, "#444444")
|
|
|
+ rr(draw, [W - 290, 252, W - 40, 310], radius=4, outline="#d9d9d9")
|
|
|
+
|
|
|
+
|
|
|
+def tpl_kpi_module(draw, title):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "智慧运营看板", "详情看板配置", title, "勾选 L1/L2 指标并保存布局")
|
|
|
+ rr(draw, [190, 132, 520, H - 30], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (210, 148), "模块指标勾选区", s, "#333333")
|
|
|
+ for i, name in enumerate(["订单评审周期", "订单满足率", "交付及时率", "异常闭环率"]):
|
|
|
+ text(draw, (220, 182 + i * 34), f"☑ {name}", xs, "#444444")
|
|
|
+ rr(draw, [540, 132, W - 20, H - 30], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (560, 148), "预览区 / 样式选择", s, "#333333")
|
|
|
+ toolbar(draw, s, H - 58, [("保存布局", "#1677ff")])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_kpi_master(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "智慧运营看板", "指标目录", "指标目录管理", "FUNC-S0-058 KpiMaster")
|
|
|
+ filters(draw, xs, s, 132, ["模块", "层级", "关键字"])
|
|
|
+ toolbar(draw, s, 188, [("查询", "#1677ff"), ("新增指标", "#52c41a")])
|
|
|
+ table(draw, xs, s, 232, ["指标编码", "显示名称", "模块", "层级", "父级", "启用", "操作"], ["S1_L1_001", "订单评审周期", "S1", "L1", "-", "是", "编辑"])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_scheduling(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S2 制造协同", "生产排程", "工单工序排程", "FUNC-S2-001 生产排程")
|
|
|
+ filters(draw, xs, s, 132, ["工单号", "物料", "开工日期", "状态"])
|
|
|
+ toolbar(draw, s, 188, [("工单关闭", "#ff4d4f"), ("生产排程", "#1677ff"), ("同步物料需求", "#1677ff"), ("同步工艺路线", "#ffffff")])
|
|
|
+ table(draw, xs, s, 232, ["工单号", "物料", "数量", "优先级", "齐套", "状态", "操作"], ["WO001", "MAT-A", "100", "普通", "齐套", "初始", "加急/追踪"])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_daily_plan(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S2 制造协同", "作业计划", "可执行日计划", "FUNC-S2-003 可执行日计划")
|
|
|
+ filters(draw, xs, s, 132, ["生产指令", "计划日期", "工作中心", "下达状态"])
|
|
|
+ toolbar(draw, s, 188, [("查询", "#1677ff"), ("下达", "#52c41a")])
|
|
|
+ table(draw, xs, s, 232, ["状态", "生产指令", "工序", "设备", "计划数量", "计划日期", "操作"], ["待下达", "WO001", "10", "设备A", "100", "2026-06-22", "勾选"])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_progress(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S2 制造协同", "制造协同看板", "工单执行进度看板", "FUNC-S2-008 工单执行进度看板")
|
|
|
+ filters(draw, xs, s, 132, ["工单号", "物料", "计划开工"])
|
|
|
+ table(draw, xs, s, 188, ["工单号", "物料", "工单数量", "入库数量", "领料状态", "工单进度"], ["WO001", "MAT-A", "100", "60", "已领料", "60%"], height=360)
|
|
|
+
|
|
|
+
|
|
|
+def tpl_module_kanban(draw, module, title):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "智慧运营看板", module, title, "L1 指标卡 + L2 趋势图")
|
|
|
+ y = 132
|
|
|
+ x = 190
|
|
|
+ for i, name in enumerate(["指标A", "指标B", "指标C", "指标D"]):
|
|
|
+ rr(draw, [x, y, x + 230, y + 92], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (x + 12, y + 12), name, s, "#333333")
|
|
|
+ text(draw, (x + 12, y + 40), "当前值 / 目标值", xs, "#666666")
|
|
|
+ text(draw, (x + 12, y + 62), "状态:绿/黄/红", xs, "#888888")
|
|
|
+ x += 245
|
|
|
+ rr(draw, [190, 240, W - 20, H - 30], radius=8, fill="#ffffff", outline="#d9d9d9")
|
|
|
+ text(draw, (210, 258), "L2 指标趋势图", s, "#333333")
|
|
|
+
|
|
|
+
|
|
|
+def tpl_order_review(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S1 产销协同", "订单评审管理", "订单评审管理", "FUNC-S1-003 订单评审管理")
|
|
|
+ filters(draw, xs, s, 132, ["订单号", "客户", "日期范围"])
|
|
|
+ toolbar(draw, s, 188, [("订单评审", "#1677ff"), ("交期确认", "#fa8c16"), ("3级计划重排", "#ffffff"), ("添加", "#52c41a")])
|
|
|
+ table(draw, xs, s, 232, ["订单号", "客户", "状态", "审批进度", "建议交期", "操作"], ["SO001", "客户A", "评审", "审批中", "2026-07-01", "编辑/变更"])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_delivery(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S1 产销协同", "订单交付管理", "订单交付管理", "FUNC-S1-004 订单交付管理")
|
|
|
+ filters(draw, xs, s, 132, ["订单号", "客户", "交付状态"])
|
|
|
+ table(draw, xs, s, 188, ["订单号", "客户", "交付状态", "目标交期", "实际进度", "操作"], ["SO001", "客户A", "生产中", "2026-07-01", "65%", "销售订单/交付跟踪"], height=360)
|
|
|
+
|
|
|
+
|
|
|
+def tpl_dispatch(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S1 产销协同", "工单下达", "工单下达", "FUNC-S1-006 工单下达")
|
|
|
+ filters(draw, xs, s, 132, ["工单号", "物料", "开工日期", "状态"])
|
|
|
+ toolbar(draw, s, 188, [("齐套检查", "#1677ff"), ("生成物料需求", "#1677ff")])
|
|
|
+ table(draw, xs, s, 232, ["工单号", "物料", "数量", "齐套状态", "状态", "操作"], ["WO001", "MAT-A", "100", "齐套", "待下达", "工单下达"])
|
|
|
+
|
|
|
+
|
|
|
+def tpl_product_design(draw):
|
|
|
+ xs, s, m, b = load_fonts()
|
|
|
+ shell(draw, xs, s, b, "S1 产销协同", "产品设计管理", "产品设计管理", "FUNC-S1-001 产品设计管理")
|
|
|
+ filters(draw, xs, s, 132, ["单据编号", "合同号", "产品类型"])
|
|
|
+ toolbar(draw, s, 188, [("新建产品设计", "#1677ff")])
|
|
|
+ table(draw, xs, s, 232, ["单据编号", "合同号", "产品类型", "设计负责人", "图号", "操作"], ["PD202606220001", "CR001", "常规", "张三", "DW-001", "查看/编辑"])
|
|
|
+
|
|
|
+
|
|
|
+def build_templates(out_dir: Path) -> dict[str, Path]:
|
|
|
+ paths: dict[str, Path] = {}
|
|
|
+
|
|
|
+ def put(key: str, filename: str, fn):
|
|
|
+ p = out_dir / filename
|
|
|
+ save_tpl(p, fn)
|
|
|
+ paths[key] = p
|
|
|
+
|
|
|
+ put("s0_crud", "tpl-s0-crud.png", lambda d: tpl_crud(d, "S0 运营建模", "主数据维护", "主数据列表", "S0 标准 CRUD 页面", ["关键字", "公司", "工厂", "启用"], "新增", ["编码", "名称", "状态", "更新时间", "操作"], ["001", "示例名称", "启用", "2026-06-22", "编辑"]))
|
|
|
+ put("s0_form", "tpl-s0-form.png", lambda d: tpl_form(d, "S0 运营建模", "制造建模", "工单控制参数", "FUNC-S0-018 工单控制参数管理", ["默认批次规则", "下达前置时间", "其他控制参数"]))
|
|
|
+ put("s0_quality", "tpl-s0-quality.png", lambda d: tpl_crud(d, "S0 运营建模", "质量建模", "检验项目管理", "FUNC-S0-042~054 质量域主数据", ["关键字", "公司", "工厂"], "新增", ["编码", "名称", "状态", "操作"], ["IQC-001", "外观检验", "启用", "编辑"]))
|
|
|
+ put("s0_kpi_graph", "tpl-s0-kpi-graph.png", tpl_kpi_graph)
|
|
|
+ put("s0_kpi_module", "tpl-s0-kpi-module.png", lambda d: tpl_kpi_module(d, "模块 L1/L2 指标配置"))
|
|
|
+ put("s0_kpi_master", "tpl-s0-kpi-master.png", tpl_kpi_master)
|
|
|
+ put("s1_design", "tpl-s1-design.png", tpl_product_design)
|
|
|
+ put("s1_crud", "tpl-s1-crud.png", lambda d: tpl_crud(d, "S1 产销协同", "合同评审管理", "合同评审管理", "FUNC-S1-002 合同评审管理", ["合同号", "客户", "日期"], "新建合同评审", ["合同号", "客户", "评审状态", "创建时间", "操作"], ["CR001", "客户A", "审批中", "2026-06-22", "编辑"]))
|
|
|
+ put("s1_order", "tpl-s1-order.png", tpl_order_review)
|
|
|
+ put("s1_delivery", "tpl-s1-delivery.png", tpl_delivery)
|
|
|
+ put("s1_ship", "tpl-s1-ship.png", lambda d: tpl_crud(d, "S1 产销协同", "订单发货管理", "订单发货管理", "FUNC-S1-005 订单发货管理", ["发货单号", "销售单号", "客户"], "新建发货单", ["发货单号", "销售单号", "客户", "发货日期", "操作"], ["SH001", "SO001", "客户A", "2026-06-22", "编辑"]))
|
|
|
+ put("s1_dispatch", "tpl-s1-dispatch.png", tpl_dispatch)
|
|
|
+ put("s1_linkage", "tpl-s1-linkage.png", lambda d: tpl_module_kanban(d, "计划联动看板", "计划联动看板"))
|
|
|
+ put("s1_verify", "tpl-s1-verify.png", lambda d: tpl_crud(d, "S1 产销协同", "需求明细核验", "需求明细核验", "FUNC-S1-008 需求明细核验", ["订单号", "物料", "状态"], "核验", ["订单号", "物料", "需求数量", "核验结果", "操作"], ["SO001", "MAT-A", "100", "通过", "查看"]))
|
|
|
+ put("s1_kanban", "tpl-s1-kanban.png", lambda d: tpl_module_kanban(d, "S1 产销协同看板", "S1 产销协同看板"))
|
|
|
+ put("s2_scheduling", "tpl-s2-scheduling.png", tpl_scheduling)
|
|
|
+ put("s2_exception", "tpl-s2-exception.png", lambda d: tpl_crud(d, "S2 制造协同", "排产异常记录", "排产异常记录", "FUNC-S2-002 排产异常记录", ["工单号", "执行时间", "物料"], "新增", ["工单号", "操作类型", "执行时间", "物料", "操作"], ["WO001", "手工调整", "2026-06-22", "MAT-A", "编辑"]))
|
|
|
+ put("s2_daily", "tpl-s2-daily.png", tpl_daily_plan)
|
|
|
+ put("s2_capacity", "tpl-s2-capacity.png", lambda d: tpl_crud(d, "S2 制造协同", "作业计划", "产线工作日历管理", "FUNC-S2-004~007 产线产能基础数据", ["产线", "类型"], "新增", ["产线", "日期/时段", "参数", "备注", "操作"], ["产线A", "周一", "8h", "-", "编辑"]))
|
|
|
+ put("s2_progress", "tpl-s2-progress.png", tpl_progress)
|
|
|
+ put("s2_kanban", "tpl-s2-kanban.png", lambda d: tpl_module_kanban(d, "S2 制造协同看板", "S2 制造协同看板"))
|
|
|
+ return paths
|
|
|
+
|
|
|
+
|
|
|
+FUNC_TEMPLATE = {}
|
|
|
+
|
|
|
+for n in range(1, 18):
|
|
|
+ FUNC_TEMPLATE[f"FUNC-S0-{n:03d}"] = "s0_crud"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-018"] = "s0_form"
|
|
|
+for n in range(19, 20):
|
|
|
+ FUNC_TEMPLATE[f"FUNC-S0-{n:03d}"] = "s0_crud"
|
|
|
+for n in range(20, 42):
|
|
|
+ FUNC_TEMPLATE[f"FUNC-S0-{n:03d}"] = "s0_crud"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-042~054"] = "s0_quality"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-055"] = "s0_kpi_graph"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-056"] = "s0_kpi_graph"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-057"] = "s0_kpi_module"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-058"] = "s0_kpi_master"
|
|
|
+FUNC_TEMPLATE["FUNC-S0-059"] = "s0_kpi_module"
|
|
|
+
|
|
|
+FUNC_TEMPLATE["FUNC-S1-001"] = "s1_design"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-002"] = "s1_crud"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-003"] = "s1_order"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-004"] = "s1_delivery"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-005"] = "s1_ship"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-006"] = "s1_dispatch"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-007"] = "s1_linkage"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-008"] = "s1_verify"
|
|
|
+FUNC_TEMPLATE["FUNC-S1-009"] = "s1_kanban"
|
|
|
+
|
|
|
+FUNC_TEMPLATE["FUNC-S2-001"] = "s2_scheduling"
|
|
|
+FUNC_TEMPLATE["FUNC-S2-002"] = "s2_exception"
|
|
|
+FUNC_TEMPLATE["FUNC-S2-003"] = "s2_daily"
|
|
|
+for n in range(4, 8):
|
|
|
+ FUNC_TEMPLATE[f"FUNC-S2-{n:03d}"] = "s2_capacity"
|
|
|
+FUNC_TEMPLATE["FUNC-S2-008"] = "s2_progress"
|
|
|
+FUNC_TEMPLATE["FUNC-S2-009"] = "s2_kanban"
|
|
|
+
|
|
|
+HEADING_RE = re.compile(r"^(FUNC-S(\d+)-(\d+(?:~\d+)?))(?:\s+(.*))?$")
|
|
|
+
|
|
|
+
|
|
|
+def is_func_heading(text: str) -> bool:
|
|
|
+ text = text.strip()
|
|
|
+ if re.fullmatch(r"FUNC-S\d+-\d{3}", text):
|
|
|
+ return False
|
|
|
+ return bool(HEADING_RE.match(text))
|
|
|
+
|
|
|
+
|
|
|
+def func_code(text: str) -> str | None:
|
|
|
+ m = HEADING_RE.match(text.strip())
|
|
|
+ return m.group(1) if m else None
|
|
|
+
|
|
|
+
|
|
|
+def insert_after(paragraph: Paragraph, text: str = "") -> Paragraph:
|
|
|
+ new_p = OxmlElement("w:p")
|
|
|
+ paragraph._p.addnext(new_p)
|
|
|
+ new_para = Paragraph(new_p, paragraph._parent)
|
|
|
+ if text:
|
|
|
+ new_para.add_run(text)
|
|
|
+ return new_para
|
|
|
+
|
|
|
+
|
|
|
+def section_has_marker(doc: Document, start: int, end: int) -> bool:
|
|
|
+ for i in range(start, end):
|
|
|
+ if MARKER in doc.paragraphs[i].text:
|
|
|
+ return True
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+def process_doc(mod: str, templates: dict[str, Path]):
|
|
|
+ path = BASE / mod / f"{mod}-功能设计说明.docx"
|
|
|
+ doc = Document(path)
|
|
|
+ headings = [(i, func_code(p.text)) for i, p in enumerate(doc.paragraphs) if is_func_heading(p.text)]
|
|
|
+ inserts: list[tuple[int, str, Path]] = []
|
|
|
+
|
|
|
+ for idx, (start, code) in enumerate(headings):
|
|
|
+ if not code:
|
|
|
+ continue
|
|
|
+ end = headings[idx + 1][0] if idx + 1 < len(headings) else len(doc.paragraphs)
|
|
|
+ if section_has_marker(doc, start, end):
|
|
|
+ continue
|
|
|
+ tpl_key = FUNC_TEMPLATE.get(code)
|
|
|
+ if not tpl_key:
|
|
|
+ continue
|
|
|
+ png = templates[tpl_key]
|
|
|
+ insert_at = end - 1
|
|
|
+ inserts.append((insert_at, code, png))
|
|
|
+
|
|
|
+ for insert_at, code, png in reversed(inserts):
|
|
|
+ anchor = doc.paragraphs[insert_at]
|
|
|
+ cap = insert_after(anchor, MARKER)
|
|
|
+ cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ pic_p = insert_after(cap)
|
|
|
+ pic_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ run = pic_p.add_run()
|
|
|
+ run.add_picture(str(png), width=Inches(6.2))
|
|
|
+ note = insert_after(pic_p, f"图:{code}")
|
|
|
+ note.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ for r in note.runs:
|
|
|
+ r.font.size = Pt(9)
|
|
|
+
|
|
|
+ doc.save(path)
|
|
|
+ print(f"{mod}: inserted {len(inserts)} prototype images -> {path}")
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ for mod in ["S0", "S1", "S2"]:
|
|
|
+ out_dir = BASE / mod / "prototype"
|
|
|
+ templates = build_templates(out_dir)
|
|
|
+ process_doc(mod, templates)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|