Files
ERP/diagrams/gen_tutorial.py
T
QiufengandClaude Opus 4.8 5e51dc3f56 SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:19:15 +08:00

200 lines
17 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 -*-
import html
# section: dict(kind, ...)
# kind 'tutorial': {chapter, title, intro, img, ar(图宽高比), markers:[(n,x,y)], callouts:[(x,y,dir,text)], steps:[...], note}
SECTIONS = [
{'kind':'cover'},
{'kind':'toc'},
{'kind':'intro'},
{'kind':'tutorial','chapter':'第一步 · 登录','title':'1 登录系统','img':'shots/01_login.png',
'intro':'打开系统网址(内测:https://resonant-elated-launder.ngrok-free.dev),用内测账号登录。',
'markers':[(1,37,46),(2,37,53),(3,50,60),(4,50,68)],
'callouts':[(62,46,'left','输入用户名(admin')],
'steps':['① 在「用户名」框输入账号,如 admin','② 在「密码」框输入密码 123456','③ 点蓝色「登录」按钮进入系统','④ 内测账号:admin / zhangwei / lina,初始密码都是 123456'],
'note':'登录后默认进入个人空间,顶部第一个模块就是「凯迪组织视图」。'},
{'kind':'tutorial','chapter':'第二步 · 总览','title':'2 凯迪组织视图:一眼看全','img':'shots/02_orgview.png',
'intro':'这是整套系统的门户。它按甲方需求表的组织架构(10 个中心 / 29 个部室 / 260 个功能块)排列,并实时标出每块做到了什么程度。',
'markers':[(1,6,9),(2,9,30),(3,46,29),(4,18,55),(5,12,62)],
'callouts':[(20,26,'down','100% = 甲方需求表全部功能块都已落地')],
'steps':['① 顶部导航第一个模块「凯迪组织视图」','② 左上大数字=表格契合度(已做到 100%)','③ 绿色进度条=已建可用 260 / 共 260 项','④ 下面 10 个中心卡片,每个显示该中心完成度','⑤ 点中心里的任意「部室」蓝色按钮 → 进入部室工作台'],
'note':'所有中心都已 100%,整条进度条全绿。'},
{'kind':'tutorial','chapter':'第三步 · 找功能','title':'3 部室工作台:找到你部门的活','img':'shots/03_dept.png',
'intro':'点任意部室进来,就是这个部门要用的全部功能——每个功能块都能点开进入真实页面。',
'markers':[(1,12,16),(2,12,27),(3,33,27),(4,24,48),(5,44,52),(6,12,84)],
'callouts':[(48,48,'left','绿色=可用,点「打开」进真实页面')],
'steps':['① 面包屑:组织视图 / 中心 / 部室','② 该部室用到的系统标签(OA / 财务T+ / ERP','③ 覆盖度','④ 功能板块卡片:绿=可用、蓝=部分、灰=规划中','⑤ 点「打开」直接进入该功能的真实页面','⑥ 底部「与其他部门协作」:和哪些部门自动交换数据'],
'note':'这一层是「部门视图」——按你的部门组织,找功能不用记菜单。'},
{'kind':'tutorial','chapter':'第四步 · 最简单的操作','title':'4 录一条业务台账(增删改查)','img':'shots/04_ledger_ops.png',
'intro':'最基础的操作:任何业务台账(这里是污水厂「工艺运行台账」)都能新建、编辑、删除,数据存进真实后端、刷新仍在。',
'markers':[(1,88,18),(2,12,30),(3,30,56),(4,72,56)],
'callouts':[(88,12,'down','点这里新建一条记录')],
'steps':['① 点右上「+ 新建」打开录入表单','② 填写字段(日期、进出水量、COD、状态…)后保存','③ 列表里每行可点进去「编辑 / 删除」','④ 状态列自动语义着色(达标绿 / 超标红)','⑤ 顶部搜索框可按关键字快速过滤'],
'note':'数据写入后端 biz_record 真实数据表(不是前端假数据),可查询、可统计。'},
{'kind':'tutorial','chapter':'第五步 · 进阶','title':'5 合同台账 + 一键派生分包','img':'shots/05_contract.png',
'intro':'合同中心管理全部合同;总包合同可一键「派生分包」,系统自动倒换甲乙方、并校验分包累计不超总包(成本控制)。',
'markers':[(1,88,18),(2,15,42),(3,15,70)],
'callouts':[(18,66,'up','HT-B2-F1/F2 就是自动派生出来的分包合同')],
'steps':['① 合同列表(含总包、分包,分包编号带 -F1/-F2)','② 点任一合同行进入详情抽屉','③ 详情里:履约进度、关联发票/付款/用印、变更记录','④ 总包合同详情有「派生分包」按钮 → 填分包商和金额','⑤ 系统自动:我方变发包甲方、分包商为乙方;分包超总包会被拦截'],
'note':'合同的生效、用印、回写已开票/已付款额都由流程自动联动(见第八章)。'},
{'kind':'tutorial','chapter':'第六步 · 业务流程(起点)','title':'6 商机 → 转投标','img':'shots/06_crm.png',
'intro':'业务流程链的起点。在 CRM 看到机会,点商机详情里的「转投标」,系统自动建一张投标台账——不用去投标模块重录。',
'markers':[(1,88,18),(2,30,45)],
'callouts':[(33,40,'left','点「详情」进抽屉,里面有「转投标」按钮')],
'steps':['① 商机管理列出所有销售机会','② 点某商机的「详情」打开抽屉','③ 抽屉右上「转投标」→ 自动生成投标台账(投标准备)','④ 商机阶段自动推进到「方案报价」'],
'note':'链路:情报线索 → 商机 →(转投标)→ 投标 → 中标 → …'},
{'kind':'tutorial','chapter':'第七步 · 业务流程(中标自动化)','title':'7 中标 → 自动建项目 + 总包合同','img':'shots/07_bidding.png',
'intro':'把投标状态改成「中标」,系统自动建项目台账 + 总包合同草稿——金额、甲方自动带入,不用手工再录。',
'markers':[(1,30,40),(2,72,40)],
'callouts':[(72,34,'down','状态改「中标」→ 自动建项目+合同')],
'steps':['① 投标台账管理所有投标项目','② 把某投标状态改为「中标」','③ 系统自动:建项目台账(phase=中标) + 总包合同草稿','④ 自动联动留痕里能看到这两个动作'],
'note':'这条就是「招投标 → 项目 → 合同」的自动接力。'},
{'kind':'tutorial','chapter':'第八步 · 流程闭环(核心)','title':'8 项目360:整条流程链一屏看全','img':'shots/08_project360.png','tall':True,
'intro':'这是整套系统的灵魂页。选一个项目,把它从商机线索到归档的整条生命周期画成一条链,绿色「自动」徽标标出哪几步是系统替你接力做的。',
'markers':[(1,88,16),(2,10,30),(3,46,44),(4,15,72)],
'callouts':[(46,36,'down','业务流程链:7 段闭环,绿色徽标=系统自动触发')],
'steps':['① 右上选项目','② 资金汇总:合同总额 / 已付款 / 已开票','③ 业务流程链:商机线索→投标→合同→用印→开票收款→付款→验收归档','④ 带「自动」徽标的步骤=系统自动接力(合同/开票/付款)','⑤ 下面卡片:该项目关联的合同/发票/付款/用印/档案全在一屏'],
'note':'这页就是回答「这套系统到底替凯迪把活干通了没有」——一屏说清。'},
{'kind':'tutorial','chapter':'第九步 · 看系统替你干了什么','title':'9 自动联动留痕','img':'shots/09_automation.png',
'intro':'所有自动接力都在这里留痕。一个业务事项办结后,系统自动写了哪些下游单据/状态,全程可追溯。',
'markers':[(1,15,40),(2,70,40)],
'callouts':[(18,34,'down','来源事项 → 系统自动产生的联动动作')],
'steps':['① 数据中心 → 自动联动留痕','② 左列「来源事项」:触发的业务办结','③ 右列「联动动作」:系统自动写的下游(生成付款/用印/发票/归档…)','④ 一个事项可产生多个联动动作(如验收→项目验收+项目归档)'],
'note':'这是「流程驱动」的证据——不是人工搬数据,是系统自动接力。'},
{'kind':'logic'},
{'kind':'rules'},
{'kind':'back'},
]
CSS = """
@page { size: A4 landscape; margin: 11mm 12mm; }
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif; color:#1d2433; font-size:13px; line-height:1.6; }
.page { page-break-after:always; position:relative; }
.page:last-child { page-break-after:auto; }
h1 { font-size:26px; font-weight:700; }
.cover { height:182mm; display:flex; flex-direction:column; justify-content:center; align-items:center; text-align:center; }
.cover .badge { display:inline-block; background:#2764e7; color:#fff; font-size:13px; padding:5px 16px; border-radius:20px; margin-bottom:24px; }
.cover h1 { font-size:46px; color:#1947aa; margin-bottom:14px; }
.cover .csub { font-size:18px; color:#667085; margin-bottom:40px; }
.cover .stats { display:flex; gap:40px; margin-top:10px; }
.cover .stat { text-align:center; }
.cover .stat b { display:block; font-size:30px; color:#2764e7; }
.cover .stat span { font-size:13px; color:#8a94a6; }
.cover .foot { position:absolute; bottom:0; font-size:12px; color:#8a94a6; }
.chap { display:inline-block; background:#eef3ff; color:#2764e7; font-weight:700; font-size:13px; padding:4px 12px; border-radius:6px; margin-bottom:8px; }
.ttl { font-size:22px; font-weight:700; color:#1d2433; margin-bottom:6px; }
.lead { font-size:13px; color:#667085; margin-bottom:12px; max-width:250mm; }
.body { display:flex; gap:18px; align-items:flex-start; }
.shotwrap { flex:0 0 63%; position:relative; border:1px solid #d9dee8; border-radius:10px; overflow:hidden; line-height:0; }
.shotwrap.tall { flex:0 0 58%; }
.shotwrap img { width:100%; display:block; }
.mk { position:absolute; width:24px; height:24px; border-radius:50%; background:#e23b3b; color:#fff; font-size:13px; font-weight:700; display:flex; align-items:center; justify-content:center; transform:translate(-50%,-50%); box-shadow:0 1px 4px rgba(0,0,0,.35); border:2px solid #fff; }
.callout { position:absolute; background:#1d2433; color:#fff; font-size:11px; padding:5px 9px; border-radius:6px; max-width:190px; line-height:1.4; transform:translate(-50%,-50%); z-index:5; }
.callout.cl-left { transform:translate(-100%,-50%); }
.callout.cl-down::after { content:''; position:absolute; left:50%; top:100%; border:6px solid transparent; border-top-color:#1d2433; transform:translateX(-50%); }
.callout.cl-up::after { content:''; position:absolute; left:50%; bottom:100%; border:6px solid transparent; border-bottom-color:#1d2433; transform:translateX(-50%); }
.callout.cl-left::after { content:''; position:absolute; left:100%; top:50%; border:6px solid transparent; border-left-color:#1d2433; transform:translateY(-50%); }
.steps { flex:1; }
.steps h3 { font-size:14px; color:#2764e7; margin-bottom:8px; }
.steps ol { list-style:none; }
.steps li { font-size:12.5px; padding:5px 0 5px 0; border-bottom:1px dashed #e8ebf1; color:#1d2433; }
.note { margin-top:12px; background:#e9f6ef; border-left:3px solid #16845b; padding:9px 14px; font-size:12px; color:#16845b; border-radius:0 6px 6px 0; }
.toc-list { columns:2; column-gap:40px; margin-top:18px; }
.toc-list div { padding:7px 0; font-size:14px; border-bottom:1px solid #eef1f5; }
.toc-list b { color:#2764e7; margin-right:10px; }
.intro-grid { display:flex; gap:24px; margin-top:14px; }
.intro-txt { flex:1; }
.intro-txt p { margin-bottom:10px; font-size:13px; }
.intro-txt .k { font-weight:700; color:#1947aa; }
.intro-img { flex:0 0 52%; }
.intro-img img { width:100%; border:1px solid #d9dee8; border-radius:10px; }
.sysrow { display:flex; gap:12px; margin:14px 0; }
.syscard { flex:1; border:1px solid #d9dee8; border-radius:10px; padding:12px 14px; }
.syscard b { color:#2764e7; }
.fullimg { width:100%; border:1px solid #d9dee8; border-radius:10px; margin-top:10px; }
.logiccap { font-size:13px; color:#667085; margin:6px 0 4px; }
"""
def esc(s): return html.escape(str(s))
def render_tutorial(s):
mks = "".join(f'<span class="mk" style="left:{x}%;top:{y}%">{n}</span>' for n,x,y in s.get('markers',[]))
cls = "".join(f'<span class="callout cl-{d}" style="left:{x}%;top:{y}%">{esc(t)}</span>' for x,y,d,t in s.get('callouts',[]))
steps = "".join(f"<li>{esc(t)}</li>" for t in s['steps'])
tall = ' tall' if s.get('tall') else ''
note = f'<div class="note">{esc(s["note"])}</div>' if s.get('note') else ''
return f"""<div class="page">
<span class="chap">{esc(s['chapter'])}</span>
<div class="ttl">{esc(s['title'])}</div>
<div class="lead">{esc(s['intro'])}</div>
<div class="body">
<div class="shotwrap{tall}"><img src="{s['img']}">{mks}{cls}</div>
<div class="steps"><h3>操作步骤</h3><ol>{steps}</ol>{note}</div>
</div></div>"""
def render(s):
k=s['kind']
if k=='cover':
return """<div class="page"><div class="cover">
<span class="badge">企业内训 · 系统介绍 + 操作教程</span>
<h1>凯迪 ERP 系统使用教程</h1>
<div class="csub">ERP + OA 一体化平台 · 从登录到全流程闭环,手把手教会怎么用</div>
<div class="stats">
<div class="stat"><b>33</b><span>功能模块</span></div>
<div class="stat"><b>170</b><span>业务页面</span></div>
<div class="stat"><b>10</b><span>自动联动链</span></div>
<div class="stat"><b>100%</b><span>需求表契合度</span></div>
</div>
<div class="foot">凯迪科技 · 2026</div>
</div></div>"""
if k=='toc':
items=[('一','系统是什么(介绍 + 功能全景)'),('二','登录系统'),('三','凯迪组织视图:一眼看全'),
('四','部室工作台:找到你的活'),('五','录一条业务台账(增删改查)'),('六','合同台账 + 一键派生分包'),
('七','商机 → 转投标'),('八','中标 → 自动建项目+合同'),('九','项目360:整条流程链闭环'),
('十','自动联动留痕'),('十一','业务逻辑闭环示意图'),('十二','14 条自动联动规则')]
rows="".join(f'<div><b>{a}</b>{esc(b)}</div>' for a,b in items)
return f'<div class="page"><h1>目录</h1><div class="toc-list">{rows}</div></div>'
if k=='intro':
return """<div class="page"><span class="chap">第一章</span><div class="ttl">系统是什么</div>
<div class="lead">凯迪 ERP 是一套「ERP + OA 一体化」平台,把市场、研发、工程、生产运营、合同资金、管控、行政全部业务搬到一个系统里,并用流程链自动串起来。</div>
<div class="sysrow">
<div class="syscard"><b>OA(协同层)</b><br>发起 / 审批 / 待办 / 通知 / 公文,全员每天用</div>
<div class="syscard"><b>ERP(业务主干)</b><br>项目 / 合同 / 资金 / 招投标 / 研发 / 运营… 统一数据库 + 流程驱动</div>
<div class="syscard"><b>财务 T+(核算)</b><br>做账 / 凭证 / 报表,与 ERP 打通</div>
</div>
<div class="intro-grid">
<div class="intro-txt">
<p><span class="k">规模</span>33 个功能模块、170 个页面,后端 90 个数据实体 / 98 个接口控制器,全部真实持久化。</p>
<p><span class="k">怎么组织</span>:上层是「部门视图」(按 10 中心 / 29 部室排列,找功能不用记菜单),底层是统一中台模块 + 流程链。</p>
<p><span class="k">最大特点</span>:10+ 条业务自动联动链——中标自动建项目合同、合同批了自动出用印单、收款自动出发票、付款自动出凭证、验收自动归档,全程留痕。</p>
<p><span class="k">契合度</span>:甲方需求表 10 中心 / 29 部室 / 260 功能块,<b style="color:#16845b">100% 全部落地</b>。</p>
</div>
<div class="intro-img"><img src="凯迪ERP-系统功能全景.png"></div>
</div></div>"""
if k=='logic':
return """<div class="page"><span class="chap">第十一章</span><div class="ttl">业务逻辑闭环 · 示意图</div>
<div class="logiccap">从市场线索一直到归档,整条业务怎么在系统里自动接力跑通:</div>
<img class="fullimg" src="凯迪ERP-业务逻辑链总图.png">
</div>"""
if k=='rules':
return """<div class="page"><span class="chap">第十二章</span><div class="ttl">14 条自动联动规则</div>
<div class="logiccap">某个业务动作办结后,系统自动写下游单据/状态(数据中心「自动联动留痕」全程可查):</div>
<img class="fullimg" src="凯迪ERP-自动联动链规则图.png">
</div>"""
if k=='back':
return """<div class="page"><div class="cover">
<h1 style="font-size:34px">把"组织架构图"做成了"会自己跑的系统"</h1>
<div class="csub" style="max-width:200mm;margin-top:16px">部门只是导航视图,底层按业务对象 + 流程链组织;<br>甲方需求表 100% 落地,10 条流程自动接力,全程留痕可查。</div>
<div class="foot">凯迪 ERP 系统使用教程 · 凯迪科技 · 2026</div>
</div></div>"""
if k=='tutorial':
return render_tutorial(s)
return ""
htmlout = f"<!doctype html><html><head><meta charset='utf-8'><style>{CSS}</style></head><body>"
htmlout += "".join(render(s) for s in SECTIONS)
htmlout += "</body></html>"
open('/Users/qiu/Desktop/ERP/diagrams/tutorial.html','w').write(htmlout)
print("tutorial.html written,", len(SECTIONS), "sections")