Files
bj_power/build_pdf.py
T
SunYF 92f0439d89 feat: 新增产线看板功能并优化多端系统体验
1. 新增SSE看板广播机制,业务操作后主动推送刷新事件
2. 新增产线工序横道图与工位状态展示面板
3. 移除dashboard无用的three.js依赖
4. 重构WMS客户端布局与WMS前端资源哈希
5. 新增工位终端时钟图标与大屏自适应布局
6. 新增系统截图脚本与说明书生成工具
7. 修复多处代码细节与空值处理逻辑
2026-09-04 17:31:18 +08:00

248 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
# -*- coding: utf-8 -*-
"""读取 shots/manifest.json,生成甲方交付用系统页面截图说明书 PDF。"""
import os, json, base64, sys, datetime
from playwright.sync_api import sync_playwright
SHOTS = "D:/hardman/bj_power/shots"
OUT = "D:/hardman/bj_power/系统页面截图说明书.pdf"
# ---------------- 每页说明文案 ----------------
# key -> [功能说明, 关键操作, 主要字段] (均为列表,逐条展示)
DESC = {
# ===== WMS 库房客户端 =====
"dashboard": ["登录后首页,集中展示仓库运行概览与快捷入口。",
["点击快捷卡片可跳转到对应业务模块", "顶部显示当前用户与待办提醒"],
["待入库/待出库数量", "库存总量", "近期出入库动态", "快捷入口"]],
"inbound": ["统一入库管理,按「结构件入库 / 精密件SN入库 / 入库记录」三 Tab 组织。",
["选择对应 Tab 录入(前两个默认展开)", "入库记录 Tab 带查询条件、分页与全量导出"],
["入库单号", "物料编码/名称", "批次(结构件) 或 序列号(精密件)", "数量", "库位区域", "操作人", "入库时间"]],
"outbound": ["统一出库管理,含「备料出库 / 通用出库 / 出库记录」。",
["备料出库受工单备料台账约束(累计≤需求)", "通用出库支持装箱号与合同号", "记录 Tab 分页+导出"],
["出库单号", "出库类型(备料/通用)", "物料/数量", "目标工位(DOCKxx)", "装箱号", "合同号", "状态"]],
"inventory": ["库存查询三视图:①物料汇总(默认) ②库存明细 ③区域汇总。",
["默认首屏为物料汇总(忽略区域/质量聚合)", "点物料行下钻批次/SN 抽屉查看明细"],
["物料编码/名称", "总数/锁定/可用", "合格/未检/不合格", "分布区域", "最近入库单"]],
"inspection": ["质量检验,两 Tab:检验录入(默认) / 检验记录。",
["在录入 Tab 填写检验项并提交", "提交成功后记录 Tab 自动刷新"],
["检验单号", "关联工单/物料", "检验项目", "结果(合格/不合格)", "检验人", "时间"]],
"stocktake": ["库存盘点,顶部步骤条:发起 → 扫码实盘 → 确认差异写回。",
["选择区域发起盘点", "录入各物料实盘数量(支持差异)", "确认后将差异写回库存"],
["盘点单号", "盘点区域", "系统量", "实盘量", "差异量", "状态(进行中/已完成)"]],
"semi": ["半成品管理,登记与查询车间半成品。",
["录入半成品入库", "按条件查询并分页展示"],
["半成品编码/名称", "数量", "状态", "入库时间", "关联工单"]],
"ledger": ["工单备料台账,按工单维度展示物料备料与领用进度。",
["状态由已领量派生:待领料/部分领料/领料完结", "支持工单号/物料/状态筛选"],
["工单号", "物料编码", "需求总量", "已领量", "领料状态"]],
"agv": ["AGV 配送任务监控,展示搬运任务与执行状态。",
["按状态筛选任务", "查看任务详情与时间线"],
["任务号", "起点/终点工位", "物料", "状态(待执行/执行中/完成)", "创建时间"]],
"zone": ["区域维护(基础资料),管理库区/货位区域。",
["新增/编辑区域", "编码搜索大小写不敏感"],
["区域编码", "区域名称", "描述", "创建时间"]],
"material": ["物料档案(基础资料),维护物料主数据,支持 Excel 导入/导出。",
["新增/编辑物料", "导入 Excel 模板(全成功或全失败)", "按筛选导出 xlsx"],
["物料编码", "名称/简称", "规格", "单位", "类型(结构件/精密件)", "说明"]],
"users": ["账号管理,系统用户增删改查(admin 账号锁定不可改)。",
["新增/编辑账号并分配角色", "禁用/启用账号", "非 admin 可正常维护"],
["用户名", "姓名", "角色", "状态(启用/禁用)", "创建时间"]],
"roles": ["角色管理,按树形勾选配置菜单+按钮级权限(check-strictly)。",
["编辑角色权限(树勾选父子独立)", "admin 角色锁定为全权限"],
["角色名称", "权限码", "关联账号数", "是否内置"]],
"eventlog": ["操作日志,全量记录用户操作行为用于审计。",
["按时间/操作人/模块筛选", "分页浏览"],
["操作人", "模块", "动作", "IP", "操作时间"]],
"changepwd": ["修改密码,当前登录用户自助改密。",
["输入原密码与新密码", "二次确认后提交"],
["原密码", "新密码", "确认新密码"]],
"display": ["库存动态大屏(免登录),实时轮播展示库房运行态势。",
["大屏独立展示,无需登录", "数据实时/轮询刷新"],
["库存总量", "出入库趋势", "预警信息", "区域分布"]],
# ===== MES 产线控制 =====
"workorder": ["生产工单管理,覆盖 CREATED/RELEASED/IN_PROGRESS/PAUSED/DONE 全生命周期。",
["创建工单", "下发/暂停/完工操作", "按状态筛选"],
["工单号", "产品类型", "数量", "状态", "计划/实际起止"]],
"dailyplan": ["日排产,按日期将工单拆解为每日生产计划。",
["按排产日期生成计划", "查看每日排产明细"],
["工单号", "排产日期", "排产数量", "状态"]],
"bom": ["物料清单(BOM),维护产品多级物料构成。",
["新增/编辑 BOM 物料与用量", "查看产品物料树"],
["产品", "子物料", "单位用量", "损耗率"]],
"materialrequest": ["备料单,由日排产自动生成的下料/领料清单。",
["按日生成备料单", "查看备料明细与状态"],
["备料单号", "工单号", "物料", "需求数", "状态"]],
"plcsend": ["工位组合下发,将工艺指令下发至 PLC/工位。",
["下发组合指令", "查看下发日志与结果"],
["指令号", "目标工位", "下发内容", "下发时间", "结果"]],
"torque": ["拧紧查询,记录并查询拧紧力矩数据。",
["按工单/工位/时间查询", "查看合格判定"],
["工件", "工位", "力矩值", "合格阈值", "结果", "时间"]],
"processflow": ["工艺流程,定义产品加工的工序流。",
["新增流程并配置工序步骤", "启用/停用流程"],
["流程名称", "工序步骤", "状态"]],
"station": ["关联工位,维护工位与工艺流程/产品的绑定关系。",
["绑定工位到流程", "查看工位配置"],
["工位号", "工位名称", "关联流程"]],
"performance": ["绩效报表,统计人员/产线产出与质量。",
["按时间/人员筛选", "查看完工与合格率"],
["人员", "完工数", "合格率", "工时"]],
"inspect": ["巡检终端,录入质量巡检项与结果。",
["录入巡检点结果", "提交巡检记录"],
["巡检点", "结果", "巡检人", "时间"]],
"scan": ["手动报工,支持扫描/录入方式上报工序完工。",
["扫描或录入报工", "填写数量与工序"],
["工单", "工序", "数量", "报工人", "时间"]],
"trace": ["工件追溯,按工件/序列号查询全生命周期。",
["输入工件号查询", "查看工序时间线"],
["工件号", "工单", "各工序", "拧紧/检验", "时间线"]],
"producttype": ["产品类型,维护产品主数据类型。",
["新增/编辑产品类型", "启停状态管理"],
["类型编码", "名称", "类别", "状态"]],
"account": ["账号管理(MES),维护产线系统账号与工位权限。",
["新增/编辑账号", "分配角色与工位权限"],
["用户名", "姓名", "角色", "工位权限", "状态"]],
"role": ["角色管理(MES),配置角色与权限。",
["编辑角色权限", "查看角色关联"],
["角色名", "权限", "关联账号"]],
"eventlog_mes": ["操作日志(MES),记录产线系统操作审计。",
["按条件筛选", "分页浏览"],
["操作人", "动作", "时间"]],
"changepwd_mes": ["修改密码(MES),当前用户自助改密。",
["输入原/新密码提交"],
["原密码", "新密码", "确认"]],
# ===== 大屏 =====
"dashboard_dash": ["生产·仓储·产线三维总览大屏,一屏聚合三块看板。",
["生产看板:工单进度与产量", "仓储看板:库存与出入库", "产线三维模型实时展示"],
["工单进度", "产量统计", "库存态势", "产线 3D 模型"]],
# ===== 工位终端 =====
"ws_login": ["工位终端登录页,账号 + 工位终端密码登录,工位号由配置文件固定。",
["输入账号与工位终端密码", "点击登录进入主界面"],
["账号", "密码", "固定工位号(由配置下发)"]],
"ws_main": ["工位终端主界面,触屏操作当前工位任务。",
["工序完成上报", "暂存退库", "查看我的工作量", "放大查看图纸"],
["工位号", "当前任务", "操作按钮", "工作量统计"]],
}
SYSTEMS = [
("wms", "一、库房客户端系统(WMS Client · 端口 8891", "wms"),
("mes", "二、MES 产线控制系统(端口 8888)", "mes"),
("dash", "三、数据可视化大屏(端口 5173)", "dash"),
("ws", "四、工位终端系统(端口 8892", "ws"),
]
def b64(path):
with open(path, "rb") as f:
return "data:image/png;base64," + base64.b64encode(f.read()).decode()
def build_html(manifest):
today = datetime.date.today().strftime("%Y-%m-%d")
parts = []
parts.append("""<style>
@page { size: A4 landscape; margin: 12mm 10mm; }
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei","PingFang SC","SimSun",sans-serif; color:#1f2937; margin:0; }
.cover { height: 265mm; display:flex; flex-direction:column; justify-content:center; align-items:center;
background:linear-gradient(135deg,#1c3faa,#0a1330); color:#fff; }
.cover h1 { font-size:40px; margin:0 0 10px; letter-spacing:2px; }
.cover .sub { font-size:18px; opacity:.9; }
.cover .meta { margin-top:40px; font-size:14px; opacity:.8; text-align:center; line-height:2; }
.toc { padding:10mm 6mm; }
.toc h2 { font-size:24px; border-bottom:3px solid #1c3faa; padding-bottom:8px; }
.toc ol { font-size:15px; line-height:2.2; }
.toc .cnt { float:right; color:#666; }
.chapter { padding:4mm 2mm; }
.chapter h2 { font-size:22px; color:#1c3faa; border-left:6px solid #1c3faa; padding-left:10px; margin-bottom:6mm; }
.page-block { break-before: page; }
.page-block h3 { font-size:17px; margin:0 0 4mm; color:#111; }
.page-block .img-wrap { width:100%; border:1px solid #e5e7eb; border-radius:6px; overflow:hidden; background:#f8fafc; }
.page-block img { width:100%; max-height:138mm; object-fit:contain; display:block; }
.desc { margin-top:4mm; font-size:12.5px; line-height:1.75; }
.desc .sec { font-weight:700; color:#1c3faa; margin-right:6px; }
.desc ul { margin:2px 0 6px; padding-left:20px; }
.footer-note { font-size:11px; color:#999; margin-top:3mm; }
.sys-tag { display:inline-block; background:#1c3faa; color:#fff; font-size:11px; padding:1px 8px; border-radius:10px; margin-left:8px; vertical-align:middle; }
</style>""")
# 封面
parts.append("""<div class="cover">
<h1>北京电力 WMS 系统 · 页面截图说明书</h1>
<div class="sub">库房客户端 · MES 产线控制 · 数据大屏 · 工位终端</div>
<div class="meta">
交付对象:甲方(项目验收 / 操作参考)<br/>
系统版本:V1.0&nbsp;&nbsp;|&nbsp;&nbsp;编制日期:%s<br/>
配套后端 API 服务:WMS 后端(端口 8890,无独立界面,本册聚焦 4 个前端系统)
</div>
</div>""" % today)
# 目录
toc = ['<div class="toc"><h2>目录</h2><ol>']
total_pages = 0
for key, title, _ in SYSTEMS:
n = len(manifest.get(key, []))
total_pages += n
toc.append('<li>%s<span class="cnt">%d 页</span></li>' % (title, n))
toc.append('<li>全册共 %d 个功能页面截图</li>' % total_pages)
toc.append('</ol>')
toc.append('<p class="footer-note">说明:本说明书每个功能页面配一张系统截图,并附「功能说明 / 关键操作 / 主要字段」三段说明,便于甲方快速了解各模块能力与操作方式。截图数据均为系统演示数据。</p>')
toc.append('</div>')
parts.append("".join(toc))
# 各系统章节
for key, title, prefix in SYSTEMS:
items = manifest.get(key, [])
parts.append('<div class="chapter"><h2>%s</h2>' % title)
parts.append('<p class="footer-note">本章共 %d 个页面。</p>' % len(items))
parts.append('</div>')
for idx, it in enumerate(items, 1):
key = os.path.splitext(os.path.basename(it["file"]))[0].split("_", 1)[-1]
dkey = it.get("desc_key") or key
d = DESC.get(dkey, DESC.get(key, [["", [], []]]))
func = d[0] if d and d[0] else "(详见截图)"
ops = d[1] if len(d) > 1 else []
fields = d[2] if len(d) > 2 else []
ops_html = "".join("<li>%s</li>" % o for o in ops)
fields_html = "、".join(fields)
img = b64(it["file"])
parts.append(
'<div class="page-block">'
'<h3>%d.%s<span class="sys-tag">%s</span></h3>' % (idx, it["title"], title.split("")[0])
+ '<div class="img-wrap"><img src="%s"/></div>' % img
+ '<div class="desc">'
'<div><span class="sec">功能说明:</span>%s</div>' % func
+ ('<div><span class="sec">关键操作:</span><ul>%s</ul></div>' % ops_html if ops else '')
+ ('<div><span class="sec">主要字段:</span>%s</div>' % fields_html if fields else '')
+ '</div></div>'
)
return "".join(parts)
def main():
with open(os.path.join(SHOTS, "manifest.json"), "r", encoding="utf-8") as f:
manifest = json.load(f)
# 给 dash / ws / MES 的 key 映射成特殊 desc keymanifest 没存 key,从文件名取)
def file_key(path):
return os.path.splitext(os.path.basename(path))[0].split("_", 1)[-1]
for it in manifest.get("dash", []):
it["desc_key"] = "dashboard_dash"
for it in manifest.get("ws", []):
k = file_key(it["file"])
it["desc_key"] = "ws_login" if k == "login" else "ws_main"
for it in manifest.get("mes", []):
k = file_key(it["file"])
if k == "eventlog": it["desc_key"] = "eventlog_mes"
if k == "changepwd": it["desc_key"] = "changepwd_mes"
html = build_html(manifest)
with sync_playwright() as p:
browser = p.chromium.launch(channel="msedge", args=["--no-sandbox"])
page = browser.new_page()
page.set_content(html, wait_until="networkidle")
page.pdf(path=OUT, format="A4", landscape=True, print_background=True,
margin={"top":"10mm","bottom":"10mm","left":"8mm","right":"8mm"})
browser.close()
size = os.path.getsize(OUT)
print("PDF 生成完成: %s (%.2f MB)" % (OUT, size/1024/1024))
if __name__ == "__main__":
main()