1. 新增SSE看板广播机制,业务操作后主动推送刷新事件 2. 新增产线工序横道图与工位状态展示面板 3. 移除dashboard无用的three.js依赖 4. 重构WMS客户端布局与WMS前端资源哈希 5. 新增工位终端时钟图标与大屏自适应布局 6. 新增系统截图脚本与说明书生成工具 7. 修复多处代码细节与空值处理逻辑
248 lines
16 KiB
Python
248 lines
16 KiB
Python
# -*- 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 | 编制日期:%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 key(manifest 没存 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()
|