feat: add web application

This commit is contained in:
richjohnrambo
2026-10-10 20:55:59 +08:00
parent 3eb5d78875
commit 50fcfd2d3d
571 changed files with 73992 additions and 135 deletions
+800
View File
@@ -0,0 +1,800 @@
from __future__ import annotations
from datetime import date
from pathlib import Path
from PIL import Image, ImageDraw
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
from build_reference_prd import (
INK, MUTED, add_page_number, add_picture, add_table, bullet, font,
heading, numbered, p, page_break, rounded, set_run_font, text,
)
ROOT = Path(__file__).resolve().parents[1]
OUT_DIR = ROOT / "docs" / "admin_reference_prd"
ASSET_DIR = OUT_DIR / "assets"
OUT_DOCX = OUT_DIR / "后台管理系统需求文档.docx"
NAV_BG = "#191B20"
TOP_BG = "#FFFFFF"
PAGE_BG = "#F3F5F8"
TEAL = "#4F958E"
BLUE = "#5A7BEF"
RED = "#D95E62"
ORANGE = "#E39B3C"
PURPLE = "#8C54CC"
GREEN = "#45A06B"
def ui_text(draw, xy, value, size=20, fill="#202631", bold=False, anchor=None):
text(draw, xy, value, size=size, fill=fill, bold=bold, anchor=anchor)
def desktop_base(title: str, active: str):
im = Image.new("RGB", (1600, 900), PAGE_BG)
d = ImageDraw.Draw(im)
d.rectangle((0, 0, 250, 900), fill=NAV_BG)
d.rectangle((250, 0, 1600, 66), fill=TOP_BG)
ui_text(d, (125, 34), "DD后台", 23, "white", anchor="mm")
ui_text(d, (278, 34), "☰", 24, "#333A46", anchor="mm")
ui_text(d, (330, 34), "↻", 24, "#333A46", anchor="mm")
ui_text(d, (1530, 34), "admin ▾", 18, "#333A46", anchor="mm")
groups = [
("主页", ["首页", "网站设置", "文本管理", "Banner设置", "手工商城"]),
("会员管理", ["会员列表", "会员等级", "任务大厅"]),
("交易管理", ["订单列表", "充值管理", "提现管理", "客服管理"]),
("商城管理", ["商品列表"]),
("任务管理", ["任务列表", "会员任务审核"]),
("管理员", ["后台管理员", "角色管理", "权限管理", "数据字典"]),
("设置", ["基本资料", "修改密码"]),
]
y = 85
for group, children in groups:
ui_text(d, (24, y), group, 17, "#D9DEE7")
y += 34
if active in children or active == group:
for child in children:
if child == active:
d.rectangle((0, y - 6, 250, y + 27), fill=TEAL)
ui_text(d, (50, y + 10), child, 16, "white", anchor="lm")
else:
ui_text(d, (50, y + 10), child, 15, "#C9CFD8", anchor="lm")
y += 34
y += 10
d.rectangle((250, 66, 1600, 112), fill="#FFFFFF")
ui_text(d, (282, 89), "⌂", 19, "#596273", anchor="mm")
ui_text(d, (322, 89), title, 17, "#596273", anchor="lm")
return im, d
def metric_card(d, box, label, value, sub1, sub2, color):
rounded(d, box, 8, color)
x1, y1, x2, y2 = box
ui_text(d, (x1 + 28, y1 + 36), label, 20, "white")
ui_text(d, (x1 + 28, y1 + 84), value, 32, "white", bold=True)
ui_text(d, (x1 + 28, y1 + 123), sub1, 16, "white")
ui_text(d, (x1 + 28, y1 + 151), sub2, 16, "white")
def dashboard_proto():
im, d = desktop_base("首页", "首页")
ui_text(d, (275, 142), "商城统计", 18, "#333A46")
cards = [
("用户总量", "5,038", "今日注册 13", "昨日注册 21", "#D95B83"),
("充值用户总量", "2,609", "今日新增 7", "昨日新增 15", "#E86866"),
("商品总量", "705", "今日新增 0", "昨日新增 0", "#5BA4CF"),
("订单总量", "75,548", "今日新增 243", "昨日新增 264", "#9851CF"),
("订单总金额", "87,576,426.20", "今日新增 155,616.00", "昨日新增 554,758.90", "#5755B7"),
("用户充值", "40,145,160.82", "今日新增 59,828.00", "昨日新增 230,469.00", "#E6AF1E"),
("用户提现", "3,500,782.79", "今日新增 2,316.04", "昨日新增 4,648.47", "#D96E19"),
("抢单佣金", "2,315,790.44", "今日新增 6,073.24", "昨日新增 13,803.84", "#833052"),
]
for i, c in enumerate(cards):
col, row = i % 2, i // 2
x = 275 + col * 645
y = 165 + row * 170
metric_card(d, (x, y, x + 615, y + 150), *c)
return im
def list_proto(title: str, active: str, filters, headers, rows, action_label, sensitive=False):
im, d = desktop_base(title, active)
x0, y0 = 280, 140
for i, label in enumerate(filters):
x = x0 + (i % 4) * 300
y = y0 + (i // 4) * 58
ui_text(d, (x, y), label, 15, "#374151")
rounded(d, (x + 72, y - 10, x + 270, y + 25), 4, "white", "#D5DAE2")
ui_text(d, (x + 86, y + 7), "请输入 / 请选择", 13, "#9AA2AE", anchor="lm")
top = y0 + ((len(filters) - 1) // 4 + 1) * 58
rounded(d, (280, top, 360, top + 38), 4, BLUE)
ui_text(d, (320, top + 20), "搜索", 15, "white", anchor="mm")
rounded(d, (370, top, 465, top + 38), 4, RED)
ui_text(d, (418, top + 20), "批量删除", 15, "white", anchor="mm")
rounded(d, (475, top, 555, top + 38), 4, TEAL)
ui_text(d, (515, top + 20), "添加", 15, "white", anchor="mm")
table_y = top + 58
widths = [120] + [max(100, int(1050 / max(1, len(headers) - 1))) for _ in headers[1:]]
scale = 1270 / sum(widths)
widths = [int(w * scale) for w in widths]
x = 275
for i, h in enumerate(headers):
d.rectangle((x, table_y, x + widths[i], table_y + 46), fill="#405D8E")
ui_text(d, (x + widths[i] / 2, table_y + 24), h, 13, "white", anchor="mm")
x += widths[i]
for ridx, row in enumerate(rows):
y = table_y + 46 + ridx * 54
x = 275
for i, val in enumerate(row):
fill = "white" if ridx % 2 == 0 else "#F7F9FC"
d.rectangle((x, y, x + widths[i], y + 54), fill=fill, outline="#DDE2EA")
display = val
if sensitive and i in (1, 2, 3):
display = val
ui_text(d, (x + 8, y + 27), display[:18], 12, "#2C3440", anchor="lm")
x += widths[i]
rounded(d, (1180, table_y + 240, 1515, table_y + 288), 6, "#FFF7E6", "#E5B45A")
ui_text(d, (1195, table_y + 264), action_label, 13, "#915F14", anchor="lm")
ui_text(d, (280, 860), "共 5,038 条 30 条/页 1 2 3 …", 13, "#667085")
return im
def modal_proto():
im = list_proto(
"会员列表", "会员列表", ["用户名", "手机号", "注册时间", "状态", "自动派单"],
["ID", "账号", "绑卡姓名", "用户名", "余额", "状态", "操作"],
[["8165", "155****7676", "王**", "会员 A", "299.00", "正常", "编辑 / 银行卡 / 地址 / 团队"],
["8164", "180****8046", "—", "会员 B", "0.00", "正常", "账变 / 奖励金 / 禁用"]],
"敏感信息默认脱敏;查看需授权并留痕", True,
)
overlay = Image.new("RGBA", im.size, (0, 0, 0, 85))
im = Image.alpha_composite(im.convert("RGBA"), overlay).convert("RGB")
d = ImageDraw.Draw(im)
rounded(d, (560, 155, 1285, 785), 10, "white", "#D2D7E0")
d.rectangle((560, 155, 1285, 208), fill="#F6F7F9")
ui_text(d, (590, 182), "添加会员", 21, "#202631", bold=True, anchor="lm")
ui_text(d, (1250, 182), "×", 23, "#667085", anchor="mm")
fields = ["标记", "用户名称", "手机号码", "账户余额", "冻结金额", "会员等级", "交易状态", "登录密码", "交易密码", "上级ID", "信用分"]
for i, lab in enumerate(fields):
col, row = i % 2, i // 2
x = 595 + col * 330
y = 238 + row * 74
ui_text(d, (x, y), lab, 15, "#3C4450")
rounded(d, (x, y + 20, x + 290, y + 56), 4, "white", "#CBD2DC")
rounded(d, (975, 725, 1065, 765), 5, BLUE)
rounded(d, (1078, 725, 1168, 765), 5, "#E8EBF0")
ui_text(d, (1020, 746), "确定", 15, "white", anchor="mm")
ui_text(d, (1123, 746), "取消", 15, "#3F4651", anchor="mm")
return im
def finance_proto():
im, d = desktop_base("提现管理", "提现管理")
x0 = 280
ui_text(d, (x0, 150), "订单号", 15)
rounded(d, (350, 132, 560, 170), 4, "white", "#D4DAE3")
ui_text(d, (600, 150), "用户名", 15)
rounded(d, (670, 132, 880, 170), 4, "white", "#D4DAE3")
rounded(d, (900, 132, 980, 170), 4, BLUE)
ui_text(d, (940, 151), "搜索", 15, "white", anchor="mm")
headers = ["提现用户", "余额", "提现金额", "手续费", "实际到账", "银行", "银行卡号", "发起时间", "处理时间", "操作"]
xs = [280, 420, 520, 630, 740, 855, 970, 1120, 1300, 1450, 1570]
for i, h in enumerate(headers):
d.rectangle((xs[i], 210, xs[i + 1], 258), fill="#405D8E")
ui_text(d, ((xs[i] + xs[i + 1]) / 2, 234), h, 12, "white", anchor="mm")
rows = [
["会员 A", "300.00", "48.38", "0.00", "48.38", "农业银行", "6228 **** **** 7161", "10-07 17:23", "—", "通过 / 驳回"],
["会员 B", "2,940.00", "500.00", "2.00", "498.00", "邮储银行", "6217 **** **** 3408", "10-07 16:21", "16:34", "审核通过"],
["会员 C", "718.35", "100.00", "1.00", "99.00", "建设银行", "6236 **** **** 5670", "10-07 15:25", "15:48", "审核驳回"],
]
for r, row in enumerate(rows):
y = 258 + r * 62
for i, val in enumerate(row):
d.rectangle((xs[i], y, xs[i + 1], y + 62), fill="white" if r % 2 == 0 else "#F7F9FC", outline="#DDE2EA")
ui_text(d, (xs[i] + 7, y + 32), val, 11, "#2C3440", anchor="lm")
rounded(d, (305, 520, 785, 740), 10, "#FFF4F0", "#E98E73")
ui_text(d, (335, 558), "P0 资金审核要求", 22, RED, bold=True)
for i, line in enumerate(["完整银行卡号仅在授权后临时查看", "通过/驳回必须填写原因", "大额或异常订单双人复核", "幂等、余额校验、审计日志不可缺失"]):
ui_text(d, (340, 610 + i * 34), "• " + line, 16, "#523129")
rounded(d, (845, 520, 1515, 740), 10, "#EFF6FF", "#7AA2E8")
ui_text(d, (875, 558), "审核抽屉", 22, BLUE, bold=True)
for i, lab in enumerate(["订单摘要与风险命中", "收款信息(脱敏)", "凭证预览", "处理意见与复核人"]):
ui_text(d, (880, 610 + i * 34), "□ " + lab, 16, "#2D466B")
return im
def rbac_proto():
im, d = desktop_base("角色管理", "角色管理")
ui_text(d, (280, 150), "角色与权限矩阵", 22, "#202631", bold=True)
headers = ["角色", "会员", "订单", "充值", "提现", "内容", "配置", "账号/权限", "导出"]
xs = [280, 470, 590, 710, 830, 950, 1070, 1190, 1400, 1535]
for i, h in enumerate(headers):
d.rectangle((xs[i], 190, xs[i + 1], 240), fill="#405D8E")
ui_text(d, ((xs[i] + xs[i + 1]) / 2, 216), h, 13, "white", anchor="mm")
rows = [
("超级管理员", ["读写", "读写", "复核", "复核", "发布", "发布", "管理", "授权"]),
("运营", ["读", "读", "无", "无", "编辑", "草稿", "无", "无"]),
("客服", ["脱敏读", "读", "只读", "只读", "无", "无", "无", "无"]),
("财务", ["脱敏读", "读", "审核", "审核", "无", "无", "无", "受控"]),
("风控", ["受控查看", "冻结", "复核", "复核", "无", "只读", "无", "受控"]),
("审计员", ["只读", "只读", "只读", "只读", "只读", "只读", "只读", "受控"]),
]
colors = {"无": "#E9EDF3", "只读": "#E8F1FF", "读": "#E8F1FF", "脱敏读": "#E8F1FF", "读写": "#E9F7ED", "编辑": "#E9F7ED", "发布": "#FFF1D8", "审核": "#FFF1D8", "复核": "#FFE4E4", "管理": "#FFE4E4", "授权": "#FFE4E4", "冻结": "#FFE4E4", "受控": "#F2E9FF", "受控查看": "#F2E9FF", "草稿": "#E9F7ED"}
for r, (role, vals) in enumerate(rows):
y = 240 + r * 62
d.rectangle((xs[0], y, xs[1], y + 62), fill="white", outline="#DDE2EA")
ui_text(d, (xs[0] + 14, y + 32), role, 14, "#2C3440", anchor="lm")
for i, val in enumerate(vals, 1):
d.rectangle((xs[i], y, xs[i + 1], y + 62), fill=colors.get(val, "white"), outline="#DDE2EA")
ui_text(d, ((xs[i] + xs[i + 1]) / 2, y + 32), val, 12, "#364152", anchor="mm")
rounded(d, (280, 665, 1535, 792), 10, "#FFF8E8", "#E1B454")
ui_text(d, (310, 705), "关键原则", 20, "#7B5314", bold=True)
ui_text(d, (310, 746), "最小权限 · 职责分离 · 敏感字段临时授权 · 权限变更双人复核 · 全量审计", 18, "#7B5314")
return im
def flow_proto():
im = Image.new("RGB", (1600, 900), "white")
d = ImageDraw.Draw(im)
ui_text(d, (800, 60), "C 端与后台业务闭环", 32, "#202631", bold=True, anchor="mm")
lanes = [("会员", 150, "#EAF1FF"), ("系统", 390, "#EAF8F0"), ("运营/财务", 630, "#FFF4E0")]
for name, y, color in lanes:
rounded(d, (70, y, 1530, y + 160), 16, color)
rounded(d, (90, y + 45, 220, y + 115), 14, "white", "#BCC6D4")
ui_text(d, (155, y + 80), name, 21, "#303846", bold=True, anchor="mm")
boxes = [
("注册/登录", 270, 175, BLUE), ("绑定资料", 500, 175, BLUE), ("领取任务", 730, 175, BLUE), ("提交结果", 960, 175, BLUE), ("查看台账", 1190, 175, BLUE),
("身份与风控", 270, 415, GREEN), ("创建订单", 500, 415, GREEN), ("冻结/解冻", 730, 415, GREEN), ("结算与幂等", 960, 415, GREEN), ("通知与申诉", 1190, 415, GREEN),
("会员配置", 270, 655, ORANGE), ("内容/商品", 500, 655, ORANGE), ("任务审核", 730, 655, ORANGE), ("充值/提现复核", 960, 655, ORANGE), ("审计/对账", 1190, 655, ORANGE),
]
for label, x, y, color in boxes:
rounded(d, (x, y, x + 175, y + 70), 12, color)
ui_text(d, (x + 88, y + 36), label, 16, "white", bold=True, anchor="mm")
for y in (210, 450, 690):
for x in (450, 680, 910, 1140):
ui_text(d, (x, y), "→", 27, "#687386", anchor="mm")
for x in (357, 587, 817, 1047, 1277):
ui_text(d, (x, 355), "↕", 27, "#687386", anchor="mm")
ui_text(d, (x, 595), "↕", 27, "#687386", anchor="mm")
return im
def save_assets():
ASSET_DIR.mkdir(parents=True, exist_ok=True)
assets = {
"prototype_01_dashboard.png": dashboard_proto(),
"prototype_02_member_list.png": list_proto(
"会员列表", "会员列表", ["用户名", "手机号", "注册时间", "状态", "自动派单"],
["ID", "账号", "绑卡姓名", "用户名", "余额", "状态", "操作"],
[["8165", "155****7676", "王**", "会员 A", "299.00", "正常", "编辑 / 银行卡 / 地址"],
["8164", "180****8046", "—", "会员 B", "0.00", "正常", "团队 / 账变 / 奖励金"]],
"删除、禁用、设为假人均需二次确认与理由", True,
),
"prototype_03_member_modal.png": modal_proto(),
"prototype_04_withdrawal.png": finance_proto(),
"prototype_05_rbac.png": rbac_proto(),
"prototype_06_flow.png": flow_proto(),
}
paths = []
for name, image in assets.items():
path = ASSET_DIR / name
image.save(path, quality=94)
paths.append(path)
return paths
def configure_doc(doc: Document):
sec = doc.sections[0]
sec.page_width = Inches(8.5)
sec.page_height = Inches(11)
sec.top_margin = Inches(0.64)
sec.bottom_margin = Inches(0.62)
sec.left_margin = Inches(0.68)
sec.right_margin = Inches(0.68)
normal = doc.styles["Normal"]
normal.font.name = "Heiti SC"
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Heiti SC")
normal.font.size = Pt(10.2)
normal.font.color.rgb = RGBColor.from_string(INK)
for name, size in (("Title", 27), ("Heading 1", 17.5), ("Heading 2", 13.5), ("Heading 3", 11.3)):
st = doc.styles[name]
st.font.name = "Heiti SC"
st._element.rPr.rFonts.set(qn("w:eastAsia"), "Heiti SC")
st.font.size = Pt(size)
st.font.color.rgb = RGBColor(0, 0, 0)
st.font.bold = True
header = sec.header.paragraphs[0]
header.alignment = WD_ALIGN_PARAGRAPH.RIGHT
set_run_font(header.add_run("后台管理系统需求文档"), size=8.5, color=MUTED)
add_page_number(sec.footer.paragraphs[0])
def add_cover(doc: Document):
title = doc.add_paragraph(style="Title")
title.alignment = WD_ALIGN_PARAGRAPH.LEFT
title.paragraph_format.space_before = Pt(72)
title.paragraph_format.space_after = Pt(20)
title_pr = title._p.get_or_add_pPr()
border = OxmlElement("w:pBdr")
for edge in ("top", "left", "bottom", "right", "between"):
node = OxmlElement("w:" + edge)
node.set(qn("w:val"), "nil")
border.append(node)
title_pr.append(border)
set_run_font(title.add_run("后台管理系统需求文档"), size=27, bold=True, color="000000")
sub = doc.add_paragraph()
set_run_font(sub.add_run("参考管理端功能拆解、C 端映射与安全重构方案"), size=15.5, bold=True, color="000000")
sub.paragraph_format.space_after = Pt(32)
p(doc, "目标:基于参考管理系统的实际页面、按钮、链接与弹窗,形成可直接用于产品设计、前后端开发、测试和运维的后台需求基线;保留业务闭环,不复制品牌资产和真实用户数据。")
p(doc, "主要读者:产品负责人、交互设计师、前端开发、后端开发、测试、运营、客服、财务、风控、安全与审计。")
p(doc, f"观察日期:{date(2026, 10, 7).isoformat()} 参考环境:Web 管理后台 文档版本:V1.0")
p(doc, "结论:后台覆盖门户配置、会员、交易、商品、任务、RBAC 与个人设置七大域。复刻时应优先修复敏感数据裸露、资金操作单人审批、配置无校验、接口错误和审计不足。")
page_break(doc)
def add_scope(doc):
heading(doc, "1 文档范围与走查方法", 1)
p(doc, "本需求对后台 21 个可见页面入口进行了逐项走查,并打开新增、编辑、查看、分配、审核等代表性弹窗。为了保护现网数据,没有执行最终确定、上传、删除、禁用、设为假人、审核通过或驳回、资金调整、奖励金发放、权限保存和改密。")
heading(doc, "1.1 已覆盖入口", 2)
add_table(doc, ["一级模块", "页面", "主要控件"], [
("主页", "首页、网站设置、文本管理、Banner 设置、手工商城", "指标卡、保存、上传、编辑、删除、发布"),
("会员管理", "会员列表、会员等级、任务大厅", "搜索、新增、编辑、团队、账变、奖励金、等级配置"),
("交易管理", "订单、充值、提现、客服", "筛选、自动刷新、匹配、凭证、审核、客服嵌入"),
("商城管理", "商品列表", "搜索、新增、编辑、推荐、删除、富文本"),
("任务管理", "任务列表、会员任务审核", "任务新增、编辑、通过、驳回"),
("管理员", "后台管理员、角色、权限、数据字典", "账号、角色、权限树、配置项"),
("设置", "基本资料、修改密码", "资料修改、密码修改"),
], [0.95, 2.7, 2.95])
heading(doc, "1.2 操作边界", 2)
for item in [
"只验证页面、按钮和弹窗是否可达,未提交会改变会员、资金、权限或配置的数据。",
"删除、禁用、设为假人、审核、账变、奖励金、改密等高风险入口仅记录需求,不执行最终动作。",
"原型图基于实测界面脱敏重绘,手机号、姓名、IP、银行卡、订单号均为掩码或虚构值。",
"接口名称以推荐实现命名;现网真实接口需在开发前通过代码或服务端文档二次核验。",
]:
bullet(doc, item)
heading(doc, "1.3 证据等级", 2)
add_table(doc, ["等级", "定义", "使用方式"], [
("A 实测", "页面、按钮、表格或弹窗在走查中直接可见", "可作为复刻基线"),
("B 推导", "由相邻页面、C 端闭环或字段关系推导", "开发前需确认"),
("C 建议", "为安全、合规、可用性提出的重构要求", "作为新系统强制规范"),
], [0.8, 3.3, 2.5])
def add_architecture(doc):
heading(doc, "2 产品定位与后台角色", 1)
p(doc, "该后台用于运营一个移动端会员任务与账户平台。C 端会员进行注册、余额储入、领取订单、任务提交、佣金结算、提现和客服咨询;后台完成内容配置、会员治理、商品与任务配置、交易审核、财务对账和权限审计。")
add_table(doc, ["角色", "职责", "允许范围", "禁止范围"], [
("超级管理员", "系统与权限治理", "账号、角色、权限、系统参数", "不得绕过双人复核直接放款"),
("运营", "内容、Banner、商品、任务、等级", "草稿、预览、发布申请", "不得查看完整银行卡或改余额"),
("客服", "会员与订单查询、工单", "脱敏资料、只读订单、备注", "不得资金审核或导出全量用户"),
("财务", "充值、提现、对账", "凭证、审核、支付状态", "不得修改会员身份与权限"),
("风控", "异常行为与冻结复核", "设备、IP 风险、冻结申请", "不得单人完成冻结与解冻"),
("审计", "只读复核与导出", "日志、审批链、差异报表", "不得修改业务数据"),
], [0.9, 1.65, 2.1, 2.25])
heading(doc, "2.1 信息架构", 2)
p(doc, "采用“一级业务域 + 二级页面 + 多标签工作区”的桌面后台结构。一级菜单固定在左侧,二级页面以标签页打开;标签支持关闭、刷新和回到首页。任何页面刷新后,应保持当前路由但不保留未提交敏感表单。")
heading(doc, "2.2 全局交互规范", 2)
for item in [
"列表页统一:查询区、主操作区、表格、固定操作列、分页;查询条件可重置并写入 URL。",
"弹窗统一:标题、关闭、表单、确定、取消;确定前做前后端双重校验,提交后禁止重复点击。",
"危险操作使用红色按钮并要求对象、影响、理由和二次确认;资金与权限操作额外要求复核人。",
"敏感值默认脱敏;查看完整值需单独权限、再次确认、限定时长,并记录操作者、目标、时间和用途。",
"加载超过 2 秒展示骨架与取消,接口失败展示错误码、重试和上报入口,不长期停留空白页。",
"操作成功后提供结果提示与审计编号;失败不得只显示“error”。",
]:
bullet(doc, item)
def add_c_mapping(doc, flow_img):
heading(doc, "3 C 端与后台功能映射", 1)
p(doc, "后台不是独立工具,其配置和操作必须能解释 C 端每个入口、状态和金额。以下矩阵为联调的单一事实源。")
add_table(doc, ["C 端能力", "后台来源", "后台操作", "一致性要求"], [
("首页横幅与快捷入口", "Banner、网站设置", "排序、有效期、跳转、开关", "发布后按版本生效"),
("平台简介/规则/公告", "文本管理", "富文本编辑、预览、发布", "CSP 与 HTML 白名单"),
("手工商城", "手工商城", "商品增删改、图片、价格、时间", "C 端仅显示已上架"),
("会员等级", "会员等级", "门槛、赠送、次数、佣金、提现次数", "计算与展示同源"),
("领取中心", "网站设置、商品、订单", "营业时间、最低余额、匹配规则", "服务端状态机判定"),
("订单记录", "订单列表", "查询、冻结、匹配、异常处理", "业务单号和台账可追溯"),
("储入", "充值管理", "凭证、审核、入账、驳回", "幂等入账与自动对账"),
("取出", "提现管理、银行卡信息", "收款资料、审核、通道、结果", "金额、手续费、到账同源"),
("任务大厅与任务", "任务大厅、任务列表、会员任务审核", "配置、分发、审核", "等级、佣金、状态一致"),
("个人资料", "会员列表", "资料、团队、状态、风控", "敏感字段默认脱敏"),
("通知与弹幕", "文本、会员弹幕", "发布、定向、撤回、查看记录", "真实性标识和审计"),
("客服", "客服管理", "客服入口、工作时间、会话映射", "失败有备用通道"),
], [1.2, 1.25, 2.3, 1.8], 7.8)
add_picture(doc, flow_img, "图 1 C 端与后台业务闭环(脱敏重绘原型)", width=6.7)
def add_dashboard(doc, img):
page_break(doc)
heading(doc, "4 首页数据看板", 1)
p(doc, "首页展示用户、商品、订单、充值、提现、佣金、利息宝转入、下级返佣与账户余额等汇总卡,并展示微信、支付宝和银行卡渠道统计。新系统应把口径、时间区间、刷新时间和数据延迟显式化。")
add_table(doc, ["指标", "定义", "筛选维度", "权限"], [
("用户总量/新增", "去重有效会员数", "今日、昨日、自定义日期", "运营只读"),
("充值用户", "至少一笔成功充值的会员数", "时间、渠道、等级", "财务/运营"),
("订单量与金额", "按订单状态和完成时间统计", "状态、商品、等级", "运营/财务"),
("充值/提现", "成功与待处理金额分开", "渠道、状态、时间", "财务"),
("佣金/返佣", "任务佣金与层级返佣分开", "类型、等级、时间", "财务/审计"),
("账户余额", "钱包可用与冻结余额总和", "币种、账户状态", "财务/审计"),
], [1.2, 2.55, 1.75, 1.0])
bullet(doc, "卡片可点击进入对应明细页,并继承时间筛选;无权限用户只能看到隐藏金额或不可点击状态。")
bullet(doc, "页面顶部展示“数据截至时间”和刷新按钮;自动刷新不得造成重复请求风暴。")
add_picture(doc, img, "图 2 后台首页看板(按实测界面脱敏重绘)", width=6.7)
def add_content_modules(doc):
heading(doc, "5 门户、内容与 Banner 管理", 1)
heading(doc, "5.1 网站设置", 2)
add_table(doc, ["配置组", "字段", "要求"], [
("品牌", "Logo、站点名称、隐私政策 URL", "Logo 可预览;URL 仅允许 HTTPS 白名单"),
("交易", "交易余额、等待时间、每日次数、奖励次数", "数值范围校验;修改按版本发布"),
("佣金", "0–5 级普通佣金", "Decimal;总和上限;法务开关"),
("时间", "提现、充值、交易、签到时间", "支持多区间;禁止结束早于开始"),
("门槛", "最低充值、自动派单时间与金额", "最小值≤最大值;币种与单位清晰"),
("开关", "免费领取、手工商城、首页任务、签到、商品模式、验证码", "每项写明 C 端影响和降级文案"),
], [0.85, 2.65, 3.1])
p(doc, "实测问题:交易等待时间输入框提示语与字段无关;签到时段出现“18:00–17:00”反向区间。新系统保存前必须做语义校验,并支持草稿、预览、发布、回滚。")
heading(doc, "5.2 文本管理", 2)
p(doc, "内容类型包括平台公告、平台简介、订单规则、代理合作、FAQ、指南、利息宝规则、首页滚动、订单提示、活动、首页弹窗和公司资质。编辑器需支持标题、正文、图片、链接、预览、定时发布、版本和回滚。")
bullet(doc, "富文本使用标签/属性白名单;禁止 script、事件属性、javascript: URL 和未知 iframe。")
bullet(doc, "现网编辑弹窗标题错误显示“编辑商品”,新系统标题必须与内容类型一致。")
heading(doc, "5.3 Banner 设置", 2)
p(doc, "支持多张首页 Banner 的上传、删除、排序、上下线、有效期、跳转类型与替代文本。上传前预览并校验尺寸、比例、MIME、文件大小和恶意内容。删除需要二次确认,已发布素材优先下线而非物理删除。")
heading(doc, "5.4 手工商城", 2)
p(doc, "列表字段为图片、名称、价格、时间与操作;新增/编辑字段为产品名称、价格、图片与展示时间。现网新增弹窗曾长时间停留 loading,需增加 8 秒超时、重试和错误上报。")
def add_member(doc, list_img, modal_img):
page_break(doc)
heading(doc, "6 会员管理", 1)
heading(doc, "6.1 会员列表", 2)
p(doc, "查询条件:用户名、手机号、注册时间、状态、自动派单。列表字段:ID、账号、绑卡姓名、用户名、账户余额、冻结金额、上级用户、邀请码、注册时间、最后登录 IP、IP 属地、自动抢单、状态、当日交易次数。")
add_table(doc, ["操作", "页面或弹窗", "关键校验", "审计要求"], [
("添加/编辑", "会员表单", "手机号唯一;余额不可由普通编辑修改;密码强度", "记录前后值,密码永不回显"),
("银行卡信息", "开户人、银行、电话、卡号、数字钱包、云闪付、收款码", "必要性与格式;图片扫描", "完整值查看留痕"),
("地址信息", "姓名、电话、地区、详细地址", "最小化收集;字段脱敏", "查看和修改留痕"),
("查看团队", "当前用户、层级、人数、余额、佣金、充值、提现", "仅展示授权层级", "导出单独授权"),
("账变", "余额调整或流水", "金额、类型、理由、业务单号", "双人复核;不可直接改余额"),
("弹幕", "会员定向或模拟内容", "标注来源、有效期、撤回", "禁止伪造真实用户行为"),
("奖励金", "奖励金额与原因", "非负、额度、幂等", "财务复核与凭证"),
("设为假人", "账号标记", "不得混入真实统计或 C 端荣誉榜", "仅测试环境默认启用"),
("禁用", "账号状态", "理由、范围、时长、申诉", "双人复核;通知用户"),
("删除", "软删除/注销", "检查资金与订单;不可物理删台账", "高风险二次确认"),
], [0.85, 2.0, 2.3, 1.8], 7.6)
p(doc, "P0:现网页面默认显示完整手机号、姓名与登录 IP。新系统必须默认脱敏,按角色和目的临时授权查看;列表、导出、日志、埋点和截图统一执行同一脱敏规则。")
add_picture(doc, list_img, "图 3 会员列表脱敏原型", width=6.7)
add_picture(doc, modal_img, "图 4 添加会员弹窗原型", width=6.7, page_break_before=True)
def add_member_level_taskhall(doc):
page_break(doc)
heading(doc, "7 会员等级与任务大厅", 1)
heading(doc, "7.1 会员等级", 2)
p(doc, "列表字段为等级名称、升级价格、赠送金额、接单次数、佣金比例和提现次数;支持编辑与受控删除。等级配置直接影响 C 端充值、领取和提现,必须使用版本化发布,不得修改后立即影响进行中的订单。")
add_table(doc, ["规则", "校验", "生效策略"], [
("升级价格/赠送金额", "≥0;货币精度;赠送比例上限", "新会员或下个结算周期"),
("接单次数", "整数;按日重置时区明确", "发布后次日生效"),
("佣金比例", "0–1 Decimal;展示百分比", "锁定到任务订单快照"),
("提现次数", "整数;与提现时间窗一致", "发布后次日生效"),
("删除等级", "无有效会员与在途订单方可删除", "优先停用并保留历史"),
], [1.45, 2.65, 2.5])
heading(doc, "7.2 任务大厅", 2)
p(doc, "页面字段为名称、图标、比例、简介、创建时间;新增弹窗还包含会员等级。实测列表接口直接显示“数据接口请求异常:error”,但新增弹窗可打开。新系统需要标准错误码、可重试状态、traceId 与空数据区分。")
bullet(doc, "任务大厅条目必须绑定可用会员等级、比例单位、图标和 C 端跳转。")
bullet(doc, "停用条目不影响已领取任务;删除仅允许无历史引用的数据,建议统一软删除。")
def add_transactions(doc, finance_img):
page_break(doc)
heading(doc, "8 交易管理", 1)
heading(doc, "8.1 订单列表", 2)
p(doc, "查询条件:订单号、手机号、用户名、日期;支持开启/关闭自动刷新。列表含绑卡姓名、用户名、自动派单、余额、手机号、当日交易次数、商品、数量、金额、佣金、下单时间、解冻时间、消息、订单状态、冻结状态和操作。")
add_table(doc, ["状态", "后台操作", "约束"], [
("匹配中", "查看进度、取消匹配", "同一会员仅一笔匹配;需幂等"),
("待处理", "查看详情、发送提醒", "不可手动完成;服务端倒计时"),
("冻结", "查看风险、发起解冻复核", "冻结理由、时长、申诉路径"),
("已完成", "查看台账", "金额、佣金、解冻三方一致"),
("异常/失败", "重新匹配、人工处理申请", "人工改状态必须双人审批"),
], [1.0, 2.2, 3.35])
heading(doc, "8.2 充值管理", 2)
p(doc, "顶部展示全部、用户已支付、用户未支付、手动审核、手动驳回等汇总金额。列表字段:订单号、真实姓名、手机号、余额、交易金额、打款凭证、添加时间、支付方式、类型、处理时间、备注和操作。")
bullet(doc, "审核通过前校验凭证、订单金额、收款主体、重复订单和账户状态;入账使用幂等键。")
bullet(doc, "驳回必须选择标准原因并可补充说明;通知 C 端且允许补充材料。")
heading(doc, "8.3 提现管理", 2)
p(doc, "查询条件:订单号、用户名、日期。列表含用户余额、提现金额、手续费、实际到账、银行名称、开户名称、银行卡号、云闪付、数字钱包、支付宝/微信收款码、发起与处理时间、操作。")
p(doc, "P0:现网直接显示完整银行卡号与多个钱包账号。新系统列表只显示掩码,完整值不进入普通 DOM;付款处理时通过一次性授权查看,超时自动隐藏。")
add_picture(doc, finance_img, "图 5 提现审核脱敏原型", width=6.7)
heading(doc, "8.4 客服管理", 2)
p(doc, "实测页面在加载后保持空白。新系统至少提供客服账号、名称、工作时间、在线状态、跳转 URL、排序、启停和会话映射;第三方 iframe 失败时显示备用联系方式和错误编号。")
def add_products_tasks(doc):
page_break(doc)
heading(doc, "9 商品与任务管理", 1)
heading(doc, "9.1 商品列表", 2)
p(doc, "查询条件:商品名称、支付类型。列表:ID、图片、名称、商品价格、店铺名称、创建时间、是否推荐、操作。新增/编辑字段:商品名称、分类、店铺名称、商品单价、展示图片、商品详情富文本。")
bullet(doc, "推荐/不推荐为明确状态切换,提交前展示影响范围;列表筛选和 C 端排序一致。")
bullet(doc, "价格采用 Decimal;图片与富文本执行内容安全校验;商品被订单引用后只允许停用。")
heading(doc, "9.2 任务列表", 2)
p(doc, "按会员类型筛选;字段为任务编号、封面、标题、描述、等级、佣金、创建时间。新增弹窗含标题、描述、封面、等级、佣金和详情。实测新增任务弹窗标题误用“添加大厅任务”,需修正。")
heading(doc, "9.3 会员任务审核", 2)
p(doc, "按手机号、用户名查询;列表含记录 ID、用户名、手机、任务标题、等级、佣金、成功率、状态、备注、创建时间和操作。待审核记录支持通过与驳回。")
add_table(doc, ["动作", "前置条件", "提交内容", "结果"], [
("通过", "任务存在、会员有效、未重复审核", "审核意见、证据摘要、幂等键", "发放佣金或进入结算队列"),
("驳回", "记录为待审核", "标准原因、补充说明、是否允许重提", "通知会员并保留证据"),
("撤回审核", "仅异常且未结算", "变更单与复核人", "冲正而非修改历史记录"),
], [0.9, 2.35, 2.2, 2.0])
def add_rbac(doc, rbac_img):
page_break(doc)
heading(doc, "10 管理员、角色与权限", 1)
heading(doc, "10.1 后台管理员", 2)
p(doc, "查询条件为登录名、手机、邮箱、角色;列表含姓名、登录名、手机号、邮箱、角色、更新时间和创建时间。新增字段为姓名、用户名、密码、手机、邮箱和多选角色。")
bullet(doc, "密码强度至少 12 位,禁止默认密码,首次登录强制改密;密码永不回显。")
bullet(doc, "禁用优先于删除;删除账号时保留历史审计主体,不得影响日志可追溯性。")
heading(doc, "10.2 角色管理", 2)
p(doc, "角色列表支持新增、编辑、删除和分配权限。分配弹窗为可展开/折叠的权限树。保存权限属于高风险操作,必须展示新增与移除差异,并由另一名管理员复核。")
heading(doc, "10.3 权限管理", 2)
p(doc, "权限树字段为 ID、名称、权限标识、父级 ID 和操作;支持添加顶级权限、添加子级、编辑、删除、展开/折叠。权限标识唯一且不可随意改名;被角色引用时禁止删除。")
add_picture(doc, rbac_img, "图 6 建议 RBAC 权限矩阵", width=6.7)
def add_dictionary_settings(doc):
page_break(doc)
heading(doc, "11 数据字典与个人设置", 1)
heading(doc, "11.1 数据字典", 2)
p(doc, "列表字段为 ID、名称、值、创建时间;支持新增、编辑、删除。现网同时存在英文 key、中文名称和缺失名称的条目,并与网站设置重复维护。建议将字典拆为“系统常量”和“业务配置”,以结构化 Schema 管理。")
add_table(doc, ["字段", "要求"], [
("key", "全局唯一;小写或大写规范统一;发布后不可改名"),
("value", "按 Schema 校验类型、范围、枚举、时间段和单位"),
("description", "必填,说明业务含义、C 端影响和默认值"),
("scope", "环境、租户、渠道、会员等级等作用域"),
("version", "草稿、已发布、已回滚;记录生效时间"),
("secret", "敏感配置不得明文显示,使用密钥管理系统"),
], [1.3, 5.2])
heading(doc, "11.2 基本资料", 2)
p(doc, "字段为用户名(不可修改)、名称、手机、邮箱,提交按钮为“确认修改”。修改手机或邮箱需验证当前身份和新联系方式;成功后记录审计并通知原联系方式。")
heading(doc, "11.3 修改密码", 2)
p(doc, "字段为当前密码、新密码、确认新密码,现网提示 6–16 个字符。新系统建议至少 12 位并支持密码管理器;最终提交必须由用户本人完成,成功后撤销其他会话并发送安全通知。")
def add_state_models(doc):
page_break(doc)
heading(doc, "12 状态机与数据模型", 1)
heading(doc, "12.1 核心状态机", 2)
add_table(doc, ["实体", "状态", "允许转换"], [
("会员", "正常、受限、禁用、注销", "正常→受限/禁用;复核后恢复;注销不可逆"),
("订单", "匹配中、待处理、处理中、完成、超时、冻结、取消、失败", "仅服务端状态机转换"),
("充值", "待支付、待审核、成功、驳回、过期、取消", "成功后只允许冲正"),
("提现", "待审核、复核中、处理中、成功、失败、驳回、取消", "付款前可取消;成功后只允许冲正"),
("任务审核", "待审核、通过、驳回、已结算、已冲正", "结算后不可回到待审核"),
("内容", "草稿、待审、已发布、已下线、已归档", "发布需审批;可回滚版本"),
("配置", "草稿、待发布、已发布、已回滚", "发布后生成不可变版本"),
], [1.0, 2.45, 3.1])
heading(doc, "12.2 核心实体", 2)
add_table(doc, ["实体", "关键字段", "约束"], [
("AdminUser", "id, loginName, roles, status, mfa, lastLogin", "管理员身份与会话"),
("Role/Permission", "roleId, permissionKey, scope", "最小权限与数据范围"),
("Member", "id, mobileEnc, nameEnc, levelId, status", "敏感字段加密、索引分离"),
("Wallet", "available, frozen, currency, version", "只允许台账事务修改"),
("LedgerEntry", "bizNo, type, amount, before, after, reversalOf", "不可变、幂等、可冲正"),
("Order", "orderNo, memberId, productSnapshot, amount, commission, status", "保存规则快照"),
("Deposit/Withdrawal", "bizNo, amount, fee, channel, status, reviewer", "审批链与支付回执"),
("Content/Banner", "type, body, version, publishAt, operator", "预览与版本化发布"),
("ConfigVersion", "key, value, schema, effectiveAt", "语义校验与回滚"),
("AuditLog", "actor, action, target, before, after, reason, traceId", "防篡改与长期留存"),
], [1.15, 3.5, 2.0], 7.8)
def add_api(doc):
page_break(doc)
heading(doc, "13 推荐接口清单", 1)
p(doc, "接口为新系统建议设计,统一前缀 /admin-api/v1。响应包含 data、code、message、traceId;所有写接口必须携带 Idempotency-Key,资金与权限写操作同时携带审批单号。")
rows = [
("GET", "/dashboard/summary", "首页指标", "timeRange, dimensions"),
("GET/PUT", "/site-settings", "读取/保存站点配置草稿", "version, settings"),
("POST", "/site-settings/publish", "发布配置", "draftVersion, reason"),
("GET/POST", "/contents", "内容列表/新建", "type, status, body"),
("POST", "/contents/{id}/publish", "内容发布", "publishAt, approver"),
("GET/POST", "/banners", "Banner 查询/新建", "imageAssetId, sort, target"),
("GET/POST", "/members", "会员查询/新建", "filters, profile"),
("GET/PATCH", "/members/{id}", "会员详情/编辑", "editableProfile, version"),
("POST", "/members/{id}/adjustment-requests", "账变申请", "type, amount, reason, evidence"),
("POST", "/members/{id}/status-requests", "禁用/解禁申请", "targetStatus, reason, duration"),
("GET/POST", "/member-levels", "等级查询/草稿", "rules, effectiveAt"),
("GET/POST", "/products", "商品查询/新建", "category, price, assets, detail"),
("GET/POST", "/tasks", "任务查询/新建", "levelIds, commission, detail"),
("GET", "/orders", "订单列表", "orderNo, member, status, timeRange"),
("POST", "/orders/{id}/exception-requests", "异常处理申请", "action, reason"),
("GET", "/deposits", "充值列表", "status, channel, timeRange"),
("POST", "/deposits/{id}/review", "充值审核", "decision, reason, version"),
("GET", "/withdrawals", "提现列表", "status, member, timeRange"),
("POST", "/withdrawals/{id}/review", "提现审核", "decision, reason, version"),
("GET", "/withdrawals/{id}/sensitive-view", "一次性查看收款信息", "purpose, challengeToken"),
("GET/POST", "/task-submissions", "任务审核列表/决定", "decision, reason"),
("GET/POST", "/admin-users", "管理员查询/新建", "profile, roleIds"),
("GET/POST", "/roles", "角色查询/新建", "name, scope"),
("POST", "/roles/{id}/permission-change-requests", "权限变更申请", "add, remove, reason"),
("GET", "/audit-logs", "审计查询", "actor, action, target, timeRange"),
]
add_table(doc, ["方法", "路径", "用途", "关键参数"], rows, [0.55, 2.3, 1.75, 2.0], 7.4)
def add_security(doc):
page_break(doc)
heading(doc, "14 安全、隐私与审计", 1)
add_table(doc, ["风险", "现网表现", "新系统要求", "优先级"], [
("个人信息裸露", "完整手机号、姓名、IP", "默认脱敏;临时授权;全链路审计", "P0"),
("金融信息裸露", "完整银行卡与钱包账号", "应用层加密;非 DOM 展示;一次性查看", "P0"),
("资金单人操作", "通过/驳回、账变、奖励入口直接可达", "职责分离、双人复核、额度和幂等", "P0"),
("伪造行为", "设为假人、弹幕入口", "测试数据隔离;禁止混入真实榜单与统计", "P0"),
("富文本 XSS", "商品与内容支持富文本", "白名单、CSP、上传扫描、预览隔离", "P0"),
("配置误发布", "时间反向、字段提示错误", "Schema 校验、预览、审批、回滚", "P0"),
("接口不可用", "任务大厅 error、客服空白", "错误码、重试、熔断、监控和降级", "P1"),
("权限漂移", "多角色与权限树直接保存", "差异预览、审批、定期复核", "P0"),
], [1.0, 1.65, 3.0, 0.65])
heading(doc, "14.1 审计日志", 2)
for item in [
"记录登录、查看敏感信息、导出、查询大范围数据、资金审核、账变、奖励、冻结、配置发布、权限变更和改密。",
"字段至少包含操作者、角色、MFA、目标、动作、前值、后值、理由、审批人、IP、设备、traceId、时间和结果。",
"日志写入独立存储并防篡改;业务管理员无删除权限;按监管与公司政策设定留存期。",
"导出文件水印、加密、限时下载;下载与转发均可追踪。",
]:
bullet(doc, item)
heading(doc, "14.2 登录与会话", 2)
for item in [
"管理员必须启用 MFA;高风险动作触发再次认证。",
"闲置 15–30 分钟自动锁定;会话撤销、设备列表和异地登录提醒。",
"连续失败触发限流与告警;不得依赖前端验证码作为唯一防护。",
"服务端密码使用 Argon2id 或 bcrypt;任何日志、监控和前端状态都不得保存密码。",
]:
bullet(doc, item)
def add_nonfunctional(doc):
heading(doc, "15 非功能需求", 1)
add_table(doc, ["类别", "目标", "验收指标"], [
("性能", "常用列表与详情快速可用", "列表 P95<1s;写操作 P95<1.5s;首屏<2.5s"),
("可用性", "关键交易全年可用", "资金审核 99.95%;其余 99.9%"),
("容量", "支持 10 万会员与百万级订单", "游标分页、索引与冷热分层"),
("并发", "自动刷新与多运营并发安全", "乐观锁、版本号、冲突提示"),
("可观测", "定位每次请求与资金变更", "traceId、指标、日志、告警、审计关联"),
("兼容", "主流桌面浏览器", "Chrome/Edge 最近两个稳定版;1366×768 可用"),
("可访问", "键盘与读屏可操作", "焦点、标签、颜色对比、44px 点击区"),
("灾备", "配置与交易可恢复", "RPO≤5 分钟,RTO≤30 分钟;季度演练"),
], [1.0, 3.0, 2.65])
heading(doc, "15.1 列表与弹窗通用验收", 2)
for item in [
"查询条件、分页、排序、刷新后可恢复;空数据、加载中、失败、无权限均有独立状态。",
"横向宽表固定关键列和操作列;敏感字段不因横向滚动或 tooltip 意外暴露。",
"弹窗关闭与取消不产生写操作;表单脏数据关闭前提示;成功后只刷新受影响区域。",
"重复点击、网络重试和浏览器回退不会重复创建、重复审核或重复入账。",
"所有时间显示时区,金额显示币种和精度,百分比与小数含义统一。",
]:
bullet(doc, item)
def add_acceptance(doc):
page_break(doc)
heading(doc, "16 端到端验收场景", 1)
scenarios = [
("A01 内容发布", "运营新建公告→预览→提交审核→审批→定时发布→C 端显示相同版本→回滚"),
("A02 会员查询", "客服按掩码手机号查询→查看脱敏资料→申请查看完整值→审计留痕→超时隐藏"),
("A03 任务闭环", "配置任务→C 端领取→生成订单→提交→后台审核→佣金入账→台账核对"),
("A04 充值审核", "C 端提交→后台待审核→重复回调→单次入账→通知→对账一致"),
("A05 提现复核", "C 端申请→风控命中→财务初审→复核→通道付款→状态与到账金额一致"),
("A06 禁用会员", "客服发起→填写理由和时长→风控复核→会员受限→通知与申诉→解禁"),
("A07 账变", "运营申请奖励→财务复核→台账入账→重复提交被幂等拦截→审计可追溯"),
("A08 权限变更", "管理员申请新增提现审核权限→展示差异→复核→即时生效→历史会话重新鉴权"),
("A09 配置错误", "输入反向时间/非法比例→前后端阻止→错误定位到字段→不生成草稿版本"),
("A10 服务降级", "任务大厅 API 失败/客服 iframe 失败→清晰错误→重试/备用入口→告警产生"),
]
add_table(doc, ["编号", "场景与验收路径"], scenarios, [0.75, 5.9])
heading(doc, "16.1 发布门槛", 2)
for item in [
"P0 风险全部关闭;资金、权限、隐私与上传通过专项安全测试。",
"C 端和后台使用同一配置版本、状态枚举、金额口径和错误码。",
"关键写操作完成幂等、并发、回滚、重试、超时与断网测试。",
"审计日志可以从一次用户投诉还原完整操作者、审批链和资金流水。",
"备份恢复、支付对账、权限复核和事故响应完成演练。",
]:
bullet(doc, item)
def add_roadmap(doc):
page_break(doc)
heading(doc, "17 研发里程碑", 1)
add_table(doc, ["阶段", "范围", "退出条件"], [
("M0 设计基线", "信息架构、组件、状态机、RBAC、字段字典", "产品/设计/技术/安全评审通过"),
("M1 基础后台", "登录、管理员、角色、权限、审计、内容、Banner", "MFA、审计、发布流可用"),
("M2 会员与配置", "会员、等级、团队、配置中心", "脱敏、临时授权、版本发布完成"),
("M3 商品与任务", "商品、任务、任务大厅、审核", "C 端联调与佣金快照通过"),
("M4 交易与资金", "订单、充值、提现、对账、复核", "支付沙箱、幂等、双人复核通过"),
("M5 运营与稳定性", "看板、客服、监控、灾备、性能", "SLO、告警、恢复演练通过"),
], [1.1, 3.4, 2.25])
heading(doc, "17.1 建议开发顺序", 2)
for item in [
"先建设统一认证、RBAC、审计、脱敏和配置发布,再开发具体业务页。",
"会员、商品、任务可先使用模拟资金;充值和提现必须最后接真实通道。",
"资金域上线前实行沙箱、小流量、额度限制和人工对账。",
"上线后第一周每日权限复核、账务核对和错误率复盘。",
]:
numbered(doc, item)
def add_prototypes(doc, imgs):
page_break(doc)
heading(doc, "18 原型图说明", 1)
p(doc, "文档中的界面图按实测后台的布局、菜单、表格、弹窗和业务字段脱敏重绘,可直接用于讨论信息层级和交互。它们不是视觉稿:品牌、颜色、图标和真实数据应在 UI 设计阶段重新建立。")
for idx, (img, caption) in enumerate([
(imgs[0], "首页看板"), (imgs[1], "会员列表"), (imgs[2], "会员弹窗"),
(imgs[3], "提现审核"), (imgs[4], "角色权限"), (imgs[5], "业务闭环"),
], 1):
if idx > 1:
page_break(doc)
heading(doc, f"18.{idx} {caption}(脱敏重绘)", 2)
pic = doc.add_paragraph()
pic.alignment = WD_ALIGN_PARAGRAPH.CENTER
pic.add_run().add_picture(str(img), width=Inches(5.8))
def add_open_questions(doc):
page_break(doc)
heading(doc, "19 待确认事项", 1)
for item in [
"目标系统是否处理真实资金,适用司法辖区、支付牌照与实名要求是什么。",
"“设为假人、弹幕、奖励金、层级返佣”是否保留;若保留,业务合法性和真实性如何证明。",
"充值、提现、账变和会员禁用的初审/复核角色与金额阈值。",
"C 端现有接口能否修改,还是需要后台兼容旧字段和状态枚举。",
"客服使用自建工单还是第三方服务,允许向第三方传输哪些数据。",
"运营内容是否需要多语言、定时发布、区域差异与灰度发布。",
"历史用户、订单、银行卡和台账的迁移范围、清洗规则和保留年限。",
"生产环境数据导出、敏感值查看和紧急权限的审批人。",
]:
bullet(doc, item)
p(doc, "在上述事项确认前,可以完成认证、RBAC、审计、内容、会员脱敏查询、商品任务配置和沙箱交易;不得接入真实收款、放款或自动资金调整。")
def build_doc():
OUT_DIR.mkdir(parents=True, exist_ok=True)
imgs = save_assets()
doc = Document()
configure_doc(doc)
add_cover(doc)
add_scope(doc)
add_architecture(doc)
add_c_mapping(doc, imgs[5])
add_dashboard(doc, imgs[0])
add_content_modules(doc)
add_member(doc, imgs[1], imgs[2])
add_member_level_taskhall(doc)
add_transactions(doc, imgs[3])
add_products_tasks(doc)
add_rbac(doc, imgs[4])
add_dictionary_settings(doc)
add_state_models(doc)
add_api(doc)
add_security(doc)
add_nonfunctional(doc)
add_acceptance(doc)
add_roadmap(doc)
add_prototypes(doc, imgs)
add_open_questions(doc)
doc.core_properties.title = "后台管理系统需求文档"
doc.core_properties.subject = "参考管理系统拆解、C 端映射与安全重构方案"
doc.core_properties.author = "Product Requirement Analysis"
doc.core_properties.keywords = "后台, 需求文档, 会员, 交易, RBAC, 审计, 原型"
doc.save(OUT_DOCX)
print(OUT_DOCX)
if __name__ == "__main__":
build_doc()