38 KiB
技术方案与详细设计
说明:本文件保留了项目早期方案与演进背景。若其中出现旧
/entry、两级“关系分类 -> 具体关系”、默认标签池或旧主题入口描述,请以当前代码基线为准:搜索字段统一为户主、成员、拼音、标签、备注;系统初始化后不预置默认标签;关系分类支持按需新增;快速录入与分享页分别服务婚礼当天录礼金和链接内简化编辑。
1. 技术栈
1.1 后端
- Python 3
- 建议框架:Flask
- 模板引擎:Jinja2
- ORM:SQLAlchemy
选择原因:
- 适合中小型 CRUD 系统
- 服务端渲染配合 jQuery 实现简单、直接
- 易于快速开发账号、表单、列表、导出等功能
1.2 前端
- jQuery
- Tailwind CSS(CLI 编译模式)
- 少量自定义 CSS
当前实现基线:
- 页面继续采用 Flask + Jinja2 SSR 输出完整 HTML
- 页面交互增强以原生 JavaScript 为主,保留与 jQuery 技术栈兼容的轻量增强模式
- 管理首页与长辈页通过 SSR + partial HTML + fetch 实现动态刷新,仍保留普通 GET / POST fallback
- 中文 / 拼音 / 首字母搜索在服务端 Python 中完成,依赖
pypinyin - CSV 导入导出在服务端实现,依赖 Python 标准库
csv - 端到端测试采用 Python + Playwright + pytest-playwright
主题系统实现
- 支持 light / dark 双主题切换,切换入口位于右上角用户菜单
- 使用
<html class="dark">驱动暗色主题样式切换 - 使用
localStorage('hw-theme')持久化用户偏好,并结合prefers-color-scheme作为默认值 - 在页面最前面执行同步主题初始化脚本,避免首屏 FOUC
- 同步设置
document.documentElement.style.colorScheme,让浏览器原生控件跟随主题
样式构建方案
- 当前采用 Tailwind CSS v4 CLI 进行样式编译,不再依赖浏览器运行时 CDN 解析
- 源文件:
app/static/src/styles.css - 输出文件:
app/static/css/main.css - 模板入口:
app/templates/base.html通过 Flaskurl_for('static', filename='css/main.css')引用 styles.css中集中维护:@theme品牌色与字体变量@layer base(如.sr-only)@layer components(如.btn、.card、.status-badge、侧边栏壳层)
- 通过
@source "../../templates"扫描 Jinja 模板中的 Tailwind 类名,保证编译产物覆盖 SSR 页面所需样式 - 本地开发时通过
npm run build:css/npm run watch:css生成编译产物
1.3 数据库
- 正式环境:MySQL
- 本地测试:SQLite
实现原则:
- 尽量使用 ORM
- 尽量避免写死数据库特性差异较大的 SQL
- 金额使用 Decimal 语义处理,避免浮点误差
1.4 页面模式
- 以前后端一体为主
- 页面由 Flask + Jinja2 服务端渲染
- 已登录后台页面采用 sidebar-first 布局,未登录登录页保持轻量居中布局
- jQuery 用于:
- 搜索
- 筛选
- 局部刷新
- 弹窗编辑
- 导入导出交互
2. 目录结构建议
建议后续项目结构:
hw/
├── README.md
├── docs/
│ └── technical-design.md
├── app/
│ ├── __init__.py
│ ├── config.py
│ ├── models/
│ ├── services/
│ ├── routes/
│ ├── templates/
│ └── static/
├── migrations/
├── tests/
└── run.py
3. 核心模块设计
3.1 认证与账号模块
目标
提供简单账号密码登录,满足以下要求:
- 基本身份识别
- 长辈专用账号独立追踪
- 审计日志可关联到具体操作人
- 管理员可自行创建新账号
账号角色建议
admin:完整权限editor:可查看、录入、编辑业务数据entry_only:仅可访问快速录入页,并且只能修改当天高频字段
登录机制建议
- 使用账号名 + 密码登录
- 密码必须加密存储
- 使用服务器 session 维持登录态
- 提供退出登录
- 提供账号启停能力
- 首个管理员账号在系统初始化阶段手动创建
- 后续账号由管理员在后台直接创建
密码与会话建议
- 密码哈希建议优先使用
werkzeug.security或等价安全方案 - 不保存明文密码
- 可提供管理员重置密码能力
- Session 登录态适合当前服务端渲染项目,复杂度低于 JWT
- 登录成功、失败、退出登录都建议记审计日志
页面与权限建议
| 页面 | admin | editor | entry_only |
|---|---|---|---|
| 管理首页 | ✅ | ✅ | ❌ |
| 快速录入页 | ✅ | ✅ | ✅ |
| 分享搜索 / 分享编辑页 | 公开链接 | 公开链接 | 公开链接 |
| CSV 导入导出 | ✅ | ✅ | ❌ |
| 账号管理 | ✅ | ❌ | ❌ |
| 审计日志 | ✅ | 可选 | ❌ |
快速录入账号设计
建议为长辈创建单独账号,例如:
father-side-amother-side-buncle-entry
登录后进入固定的快速录入页,不让其接触复杂管理页。
好处:
- 使用路径简单
- 可以知道是谁完成了搜索后的修改
- 审计日志天然具备来源追踪
快速录入页访问流建议
建议访问流如下:
- 管理员在后台创建长辈专用账号
- 为该账号分配
entry_only角色 - 将专用登录入口提供给对应长辈
- 长辈使用账号密码登录
- 登录成功后系统自动跳转到
/quick-entry - 录入人员先搜索系统中已存在的户
- 选择目标户后进入单独编辑页;若搜不到,可直接新增一户并记礼金
- 所有修改行为自动写入
updated_by - 审计日志记录本次登录与保存修改行为
说明:
- “专属入口”建议理解为“专用登录入口 + 专属账号身份”;公开分享页另走分享链接模式
- 本次婚宴涉及的户应由管理员预先录入系统,长辈账号不负责纯新增
- 这样既简单,也便于后续审计和权限控制
3.2 数据库建模约定
3.2.1 MVP 表范围
当前阶段保持 6 张核心表,避免过早拆成过多小表:
accountshouseholdshousehold_membersgift_recordsaudit_logsoption_items
说明:
households是核心主表,承接户级主信息、邀请状态、到场状态、回礼状态与聚合金额gift_records负责礼金 / 礼品明细,不再额外引入独立的“回礼记录表”作为第 7 张表- 喜糖、伴手礼、儿童红包等回礼发放状态在 MVP 中直接落到
households,便于长辈页和管理页快速编辑 - 若后续需要追踪每次回礼发放明细,再新增独立明细表,不在当前 MVP 范围内
3.2.2 通用字段与类型约定
- 主键统一使用
BIGINT(SQLite 本地测试可映射为INTEGER自增主键) - 时间字段统一使用
DATETIME - 金额字段统一使用
DECIMAL(12,2) - 布尔语义字段在 MySQL 中使用
TINYINT(1),在 ORM 层映射为布尔值 - JSON 结构字段在 MySQL 中优先使用
JSON,SQLite 本地测试阶段可用TEXT存 JSON 字符串 - 文本备注字段默认使用
TEXT - 软删除统一使用
deleted_at DATETIME NULL - 并发控制统一使用
version INT NOT NULL DEFAULT 1
3.2.3 软删除与外键约定
当前阶段的软删除策略:
households:软删除,避免删除后丢失礼金、人情往来、审计上下文gift_records:软删除,避免误删礼金明细后不可追溯accounts:默认不物理删除,建议通过status=disabled停用;MVP 可不加deleted_athousehold_members:MVP 中可直接物理删除,但删除动作要记审计日志;若后续需要支持恢复误删成员,可再补deleted_atoption_items:默认不物理删除,通过is_enabled停启、is_system保护系统项audit_logs:默认只追加、不删除
外键约定:
household_members.household_id -> households.idgift_records.household_id -> households.idhouseholds.created_by / updated_by -> accounts.idhousehold_members.created_by / updated_by -> accounts.idgift_records.created_by / updated_by -> accounts.idoption_items.created_by / updated_by -> accounts.idaudit_logs.actor_user_id -> accounts.id(可空,保留用户名快照)households.relation_category_option_id -> option_items.idhouseholds.gift_method_option_id -> option_items.idhouseholds.gift_scene_option_id -> option_items.idgift_records.method_option_id -> option_items.idgift_records.scene_option_id -> option_items.idoption_items.parent_id -> option_items.id
推荐外键行为:
- 对
households的子表(如household_members、gift_records)使用ON DELETE RESTRICT,避免误删主表时破坏历史数据 - 对
created_by/updated_by/actor_user_id建议使用ON DELETE SET NULL,配合用户名快照保留审计信息 - 对
option_items关联建议使用ON DELETE RESTRICT,防止配置项被删除后造成业务数据悬空
3.2.4 枚举与动态选项约定
为兼顾“够用就行”和“后续可配置”,当前采用两类策略:
-
固定枚举,代码常量维护
accounts.role:admin/editor/entry_onlyaccounts.status:active/disabledhouseholds.side:groom_side/bride_side/both_sidehouseholds.invite_status:not_invited/invited/confirmed/declinedhouseholds.attendance_status:pending/attending/absent/partialhouseholds.favor_status:not_given/givenhouseholds.candy_status:not_given/givenhouseholds.child_red_packet_status:not_given/partial/givengift_records.record_type:cash_gift/physical_gift/pre_wedding_cash/wedding_day_cash/post_wedding_cash
-
可配置选项,使用
option_items维护- 关系分类
- 标签
- 礼金方式
- 礼金记录场景
- 后续如有需要,也可扩展更多下拉选项组
说明:
relation_category、标签等不写死,满足后续新增 / 启停 / 排序需求side仍保留为固定枚举,因为范围稳定且影响筛选频率高;展示层应统一映射为中文友好文案,避免直接暴露原始枚举值- 标签在 MVP 中使用
tag_option_ids_json保存选中的option_items.id数组,避免为了标签单独增加第 7 张关联表 - 这意味着标签引用完整性主要由应用层保证;若未来 SQL 级标签统计需求增强,再补充
household_tag_links一类的关联表
3.3 账号与审计模块
3.3.1 accounts 表
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
BIGINT | 是 | 自增 | 主键 |
username |
VARCHAR(64) | 是 | - | 登录账号,唯一 |
password_hash |
VARCHAR(255) | 是 | - | 密码哈希,不存明文 |
display_name |
VARCHAR(64) | 否 | NULL | 显示名称,如“爸爸录入账号” |
role |
VARCHAR(32) | 是 | editor |
角色:admin / editor / entry_only |
status |
VARCHAR(16) | 是 | active |
状态:active / disabled |
last_login_at |
DATETIME | 否 | NULL | 最后登录时间 |
created_by |
BIGINT | 否 | NULL | 创建人账号 ID |
updated_by |
BIGINT | 否 | NULL | 最后更新人账号 ID |
created_at |
DATETIME | 是 | 当前时间 | 创建时间 |
updated_at |
DATETIME | 是 | 当前时间 | 更新时间 |
约束与索引:
UNIQUE KEY uk_accounts_username (username)KEY idx_accounts_role_status (role, status)KEY idx_accounts_last_login_at (last_login_at)
实现说明:
- 新账号默认
status=active - 不建议物理删除账号,优先停用
- 长辈专用账号通过
role=entry_only实现能力边界
3.3.2 audit_logs 表
设计目标
系统中的关键变更必须可追溯。
记录范围
至少记录:
- 登录
- 登出
- 新增一户
- 编辑一户
- 删除 / 软删除一户
- 新增 / 删除成员
- 长辈页保存修改
- 导入 CSV
- 创建账号
- 重置密码
- 启停账号
字段定义
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
BIGINT | 是 | 自增 | 主键 |
actor_user_id |
BIGINT | 否 | NULL | 操作人账号 ID,允许为空 |
actor_username |
VARCHAR(64) | 是 | - | 操作时账号名快照 |
actor_display_name |
VARCHAR(64) | 否 | NULL | 操作时显示名快照 |
action_type |
VARCHAR(32) | 是 | - | 动作类型,如 login、update_household |
target_type |
VARCHAR(32) | 是 | - | 目标类型,如 household、account |
target_id |
BIGINT | 否 | NULL | 目标对象 ID |
target_display_name |
VARCHAR(128) | 否 | NULL | 目标对象名称快照 |
before_data_json |
JSON / TEXT | 否 | NULL | 变更前摘要 |
after_data_json |
JSON / TEXT | 否 | NULL | 变更后摘要 |
request_path |
VARCHAR(255) | 否 | NULL | 请求路径 |
request_method |
VARCHAR(16) | 否 | NULL | 请求方法 |
ip_address |
VARCHAR(45) | 否 | NULL | IPv4 / IPv6 |
user_agent |
VARCHAR(512) | 否 | NULL | 浏览器标识 |
created_at |
DATETIME | 是 | 当前时间 | 记录时间 |
推荐索引:
KEY idx_audit_logs_created_at (created_at)KEY idx_audit_logs_actor_created_at (actor_user_id, created_at)KEY idx_audit_logs_action_created_at (action_type, created_at)KEY idx_audit_logs_target (target_type, target_id, created_at)
实现说明:
- 审计日志默认只追加,不做软删除
- 即使账号后续改名或停用,仍可通过
actor_username/actor_display_name快照追溯历史 - 不建议记录普通搜索行为,避免日志噪音过大
审计展示建议
日志页需支持:
- 时间倒序
- 按人筛选
- 按动作筛选
- 按对象筛选
- 查看变更前后对比摘要
3.4 户、成员与礼金模块
3.4.1 households 表
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
BIGINT | 是 | 自增 | 主键 |
household_code |
VARCHAR(32) | 是 | - | 户唯一编码,便于导入导出和人工核对 |
head_name |
VARCHAR(64) | 是 | - | 户主姓名 |
phone |
VARCHAR(32) | 否 | NULL | 联系电话 |
side |
VARCHAR(32) | 是 | groom_side |
所属侧 |
relation_category_option_id |
BIGINT | 否 | NULL | 关系分类选项 |
tag_option_ids_json |
JSON / TEXT | 否 | NULL | 选中的标签 ID 数组 |
invite_status |
VARCHAR(32) | 是 | not_invited |
邀请状态 |
attendance_status |
VARCHAR(32) | 是 | pending |
到场状态 |
expected_attendee_count |
INT | 是 | 0 |
预计到场人数 |
actual_attendee_count |
INT | 是 | 0 |
实际到场人数 |
child_count |
INT | 是 | 0 |
儿童人数 |
red_packet_child_count |
INT | 是 | 0 |
需红包儿童人数 |
total_gift_amount |
DECIMAL(12,2) | 是 | 0.00 |
汇总礼金金额 |
gift_method_option_id |
BIGINT | 否 | NULL | 当前主礼金方式 |
gift_scene_option_id |
BIGINT | 否 | NULL | 当前主礼金记录场景 |
favor_status |
VARCHAR(32) | 是 | not_given |
伴手礼状态 |
candy_status |
VARCHAR(32) | 是 | not_given |
喜糖状态 |
child_red_packet_status |
VARCHAR(32) | 是 | not_given |
儿童红包状态 |
note |
TEXT | 否 | NULL | 备注 |
created_by |
BIGINT | 否 | NULL | 创建人账号 ID |
updated_by |
BIGINT | 否 | NULL | 最后更新人账号 ID |
created_at |
DATETIME | 是 | 当前时间 | 创建时间 |
updated_at |
DATETIME | 是 | 当前时间 | 更新时间 |
deleted_at |
DATETIME | 否 | NULL | 软删除时间 |
version |
INT | 是 | 1 |
乐观锁版本号 |
约束与索引:
UNIQUE KEY uk_households_household_code (household_code)KEY idx_households_head_name (head_name)KEY idx_households_phone (phone)KEY idx_households_side_relation_category (side, relation_category_option_id)KEY idx_households_attendance_status (attendance_status)KEY idx_households_updated_at (updated_at)KEY idx_households_deleted_at (deleted_at)
推荐应用层校验:
actual_attendee_count >= 0expected_attendee_count >= 0child_count >= 0red_packet_child_count >= 0red_packet_child_count <= child_countactual_attendee_count不应明显大于成员数与预计人数总和,若超出则给出提示phone允许为空,但若填写应做基础格式校验
实现说明:
entry_only角色默认不能修改户的核心身份字段,如户主姓名、所属侧、关系分类、标签entry_only角色仅允许修改到场、礼金、备注等当天高频字段gift_method_option_id与gift_scene_option_id用于户级“当前主状态”,详细礼金流水仍写入gift_recordstotal_gift_amount属于聚合缓存字段,新增 / 修改 / 软删除 / 恢复gift_records后都应重新汇总该户的有效礼金金额,避免长期与明细脱节version用于乐观锁,保存时校验前端版本号与数据库版本号一致,再执行version = version + 1
3.4.2 household_members 表
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
BIGINT | 是 | 自增 | 主键 |
household_id |
BIGINT | 是 | - | 所属户 ID |
name |
VARCHAR(64) | 是 | - | 成员姓名 |
relation_to_head |
VARCHAR(64) | 否 | NULL | 与户主关系 |
gender |
VARCHAR(16) | 否 | NULL | 可选,MVP 可仅做展示辅助 |
age_group |
VARCHAR(32) | 否 | NULL | 可选,如儿童 / 成人 / 长辈 |
is_child |
TINYINT(1) | 是 | 0 |
是否儿童 |
needs_red_packet |
TINYINT(1) | 是 | 0 |
是否需要儿童红包 |
expected_to_attend |
TINYINT(1) | 是 | 1 |
是否预计到场 |
actually_attended |
TINYINT(1) | 是 | 0 |
是否实际到场 |
sort_order |
INT | 是 | 0 |
展示顺序 |
note |
TEXT | 否 | NULL | 备注 |
created_by |
BIGINT | 否 | NULL | 创建人账号 ID |
updated_by |
BIGINT | 否 | NULL | 最后更新人账号 ID |
created_at |
DATETIME | 是 | 当前时间 | 创建时间 |
updated_at |
DATETIME | 是 | 当前时间 | 更新时间 |
约束与索引:
KEY idx_household_members_household_id (household_id)KEY idx_household_members_household_sort (household_id, sort_order)KEY idx_household_members_household_name (household_id, name)
实现说明:
- 成员表当前不做软删除,误删时依靠审计日志回溯
gender、age_group来自需求文档中的可选字段,MVP 可以先保留字段,不做复杂校验- 成员数据主要服务于人数核对、儿童红包判断和后续桌席安排
3.4.3 gift_records 表
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
BIGINT | 是 | 自增 | 主键 |
household_id |
BIGINT | 是 | - | 所属户 ID |
record_type |
VARCHAR(32) | 是 | cash_gift |
记录类型 |
amount |
DECIMAL(12,2) | 否 | NULL | 现金礼金金额 |
gift_name |
VARCHAR(128) | 否 | NULL | 礼品名称 |
estimated_value |
DECIMAL(12,2) | 否 | NULL | 礼品估值 |
method_option_id |
BIGINT | 否 | NULL | 礼金方式 |
scene_option_id |
BIGINT | 否 | NULL | 记录场景 |
record_time |
DATETIME | 是 | 当前时间 | 记录时间 |
created_by |
BIGINT | 否 | NULL | 创建人账号 ID |
updated_by |
BIGINT | 否 | NULL | 最后更新人账号 ID |
deleted_at |
DATETIME | 否 | NULL | 软删除时间 |
note |
TEXT | 否 | NULL | 备注 |
created_at |
DATETIME | 是 | 当前时间 | 创建时间 |
updated_at |
DATETIME | 是 | 当前时间 | 更新时间 |
约束与索引:
KEY idx_gift_records_household_id (household_id)KEY idx_gift_records_record_time (record_time)KEY idx_gift_records_household_record_time (household_id, record_time)KEY idx_gift_records_record_type (record_type)KEY idx_gift_records_deleted_at (deleted_at)
推荐应用层校验:
amount与estimated_value至少要有一个非空- 纯现金记录通常要求
amount非空 - 纯礼品记录通常要求
gift_name非空 - 删除礼金记录后,需要同步重算
households.total_gift_amount
实现说明:
gift_records是明细表,households.total_gift_amount是聚合展示字段- 长辈页可只维护户级聚合状态;管理页可进一步查看或补录详细礼金明细
3.5 动态选项模块
3.5.1 option_items 表
用于维护可配置下拉项和标签池,覆盖关系分类、标签、礼金方式、记录场景等。
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
BIGINT | 是 | 自增 | 主键 |
option_group |
VARCHAR(32) | 是 | - | 选项分组,如 relation_category |
option_code |
VARCHAR(64) | 是 | - | 组内唯一编码 |
option_label |
VARCHAR(64) | 是 | - | 显示文案 |
parent_id |
BIGINT | 否 | NULL | 父选项 ID,当前主要为兼容历史结构保留 |
sort_order |
INT | 是 | 0 |
排序值 |
is_enabled |
TINYINT(1) | 是 | 1 |
是否启用 |
is_system |
TINYINT(1) | 是 | 0 |
是否系统保护项 |
extra_json |
JSON / TEXT | 否 | NULL | 扩展元数据 |
created_by |
BIGINT | 否 | NULL | 创建人账号 ID |
updated_by |
BIGINT | 否 | NULL | 最后更新人账号 ID |
created_at |
DATETIME | 是 | 当前时间 | 创建时间 |
updated_at |
DATETIME | 是 | 当前时间 | 更新时间 |
约束与索引:
UNIQUE KEY uk_option_items_group_code (option_group, option_code)UNIQUE KEY uk_option_items_group_parent_label (option_group, parent_id, option_label)KEY idx_option_items_group_enabled_sort (option_group, is_enabled, sort_order)KEY idx_option_items_parent_id (parent_id)
实现说明:
- 标签项使用
option_group = tag - 礼金方式使用
option_group = gift_method - 礼金记录场景使用
option_group = gift_scene - 若未来恢复多级选项,可继续利用
parent_id
3.5.2 推荐分组清单
relation_categorytaggift_methodgift_scene
3.6 数据库索引与校验建议
3.6.1 高频查询对应的索引目标
-
长辈页搜索既有户
- 主要字段:
head_name、phone - 对应索引:
idx_households_head_name、idx_households_phone
- 主要字段:
-
管理页按所属侧 / 关系分类 / 状态筛选
- 主要字段:
side、relation_category_option_id、attendance_status - 对应索引:
idx_households_side_relation_category、idx_households_attendance_status
- 主要字段:
-
查看某户礼金历史
- 主要字段:
household_id、record_time - 对应索引:
idx_gift_records_household_record_time
- 主要字段:
-
管理首页组合筛选与最近更新时间排序
- 主要字段:
side、invite_status、attendance_status、updated_at - 对应索引:
idx_households_side_relation_category、idx_households_attendance_status、idx_households_updated_at
- 主要字段:
-
账号登录与权限过滤
- 主要字段:
username、role、status - 对应索引:
uk_accounts_username、idx_accounts_role_status
- 主要字段:
-
审计日志筛选
- 主要字段:
created_at、actor_user_id、action_type、target_type + target_id - 对应索引:审计日志相关索引组合
- 主要字段:
3.6.2 应用层校验建议
household_code应由系统统一生成,避免手输重复- 导入 CSV 时先按
household_code判断强匹配,再结合head_name + phone做疑似重复提示 - 写入
tag_option_ids_json时,应校验每个 ID 对应的option_group=tag - 对标签相关查询尽量走应用层过滤,避免在 MVP 阶段强依赖 MySQL / SQLite 不一致的 JSON 查询语法
- 写入
gift_method_option_id时,应校验其分组为gift_method - 写入
gift_scene_option_id/scene_option_id时,应校验其分组为gift_scene - 所有被停用的
option_items默认不再用于新建和编辑,但旧数据仍可正常展示 - 更新户级礼金金额时,应优先以
gift_records重算,避免长期依赖手工汇总
4. 搜索方案设计
4.1 目标
支持:
- 中文搜索
- 拼音全拼搜索
- 首字母搜索
- 混合搜索
- 多字段统一搜索
4.2 当前实现方案
当前搜索能力已经落地在服务端 app/services/households.py 中:
- 中文原文匹配
pypinyin全拼匹配pypinyin首字母匹配- 与户主姓名、成员姓名、标签、备注等字段的统一搜索
这样做的原因:
- SSR 页面刷新与导出 CSV 复用同一套筛选语义,更容易保持一致
- 避免浏览器端额外维护一份搜索索引和拼音转换逻辑
- 当前数据规模适合在服务端完成筛选与排序
- 测试可以直接覆盖服务层行为,验证成本更低
4.3 搜索字段与后续增强建议
当前服务端搜索匹配重点覆盖:
- 户主姓名
- 手机号
- 户编码
- 备注
- 户主姓名对应的拼音全拼
- 户主姓名对应的拼音首字母
当前首页已经同时支持:
side / invite_status / attendance_status组合筛选updated_at / head_name / expected_attendee_count / actual_attendee_count / child_count / total_gift_amount排序- 筛选后的 count 与统计摘要
- 管理首页
partial=results、快速录入页partial=search-results、分享搜索页partial=results的 partial 响应分支 - 前端 300ms debounce +
fetch+ DOM 局部替换 +history.replaceState
后续如果数据量明显增大,可再考虑:
- 在后端持久化拼音字段
- 预生成搜索索引字段
- 将统计从内存汇总进一步下推到数据库查询
5. 表格与首页交互设计
5.1 表格方案建议
当前实现不依赖额外表格库,而是直接使用 SSR 表格模板:
- 管理首页使用服务端渲染的原生
<table> - 默认只读,单条编辑跳转到独立编辑页
- 通过 query params 维持搜索、筛选、排序与导出上下文
这样做的原因:
- 技术复杂度更低,适合当前核心可用版本
- 与 Tailwind 样式集成简单
- 对长辈和非技术用户更直观,避免前端重交互表格带来的认知负担
如果后续确实出现更复杂的分页、列操作、批量编辑需求,再评估是否引入更重的表格库。
5.2 首页布局与动态刷新
当前实现遵循“高频操作留在主区,低频操作收纳到右上角”的原则:
左侧边栏
├── 管理首页
├── 快速录入
├── (admin)账号管理 / 审计日志
└── (admin)分享链接
首页头部
├── 页面标题
└── 右上角账户菜单
├── 主题切换
└── 退出登录
统计总览区
├── 总户数
├── 总预计人数
├── 儿童数
└── 礼金总额
搜索与筛选区
├── 快速搜索框
├── 所属侧 / 邀请状态 / 到场状态筛选
├── 排序字段 / 排序方向
└── 搜索 / 重置
结果区(partial)
├── 当前结果与筛选后摘要
├── 快速概览卡片
├── 桌面表格
└── 移动卡片列表
动态刷新策略:
- 管理首页保留完整
GET /SSR 页面 - 新增
partial=results/X-HW-Partial: results分支,仅返回main/_household_results.html - 前端使用 300ms debounce 避免高频输入触发过多请求
- 响应成功后只替换
#household-results-region - 同步调用
history.replaceState保持 URL 与当前筛选条件一致 - 请求失败时仅记录前端错误日志,不破坏已有页面内容
5.3 编辑交互建议
流程:
- 查看列表
- 点击“修改”
- 打开单独编辑页
- 修改字段
- 点击“保存并返回搜索”或“保存”
- 提交成功
- 写入审计日志
不建议
- 默认行内直接可编辑
- 打开页面就是输入框
- 修改后自动保存
6. 快速录入页设计
6.1 页面目标
- 极简
- 单列
- 搜索后快速进入当天录礼金
- 支持“搜不到时快速新增一户并记礼金”
- 不承担完整资料维护职责
- 尽量降低改错人的风险
6.2 页面结构建议
当前实现采用“搜索页 -> 单独编辑页 / 新增页”的模式:
- 搜索目标户
- 搜索结果卡片列表实时刷新
- 点击“编辑”后进入单独编辑页
- 编辑页只保留当天高频字段
- 保存后返回搜索页
6.3 搜索页建议
搜索字段建议支持:
- 户主姓名
- 成员姓名
- 拼音 / 首字母
- 标签
- 备注
结果列表建议展示:
- 明确的“编辑”按钮
- 户主姓名
- 所属侧
- 关系分类
- 当前状态摘要
6.4 允许修改字段建议
- 到场状态
- 实际到场人数
- 儿童人数
- 需红包儿童人数
- 礼金金额
- 礼金方式
- 礼金记录场景
- 备注
6.5 只读展示字段建议
- 户主姓名
- 所属侧
- 关系分类
- 预计人数
6.6 交互建议
- 登录后默认进入该页
- 先搜索再选择目标户
- 搜索输入时使用 300ms debounce 实时刷新结果列表
- 编辑表单使用单独页面承载
- 保存成功显示明显成功提示
- 数量类字段尽量在前端做即时校验,并保留服务端兜底校验,避免用户误填后直到提交才发现问题
- 搜索页应同时提供“新增”入口,便于当天现场处理未预录来宾
6.7 ASCII 草图建议
搜索页
+--------------------------------------------------------------+
| 完善来宾信息 |
| 当前账号:爸爸专用账号 |
+--------------------------------------------------------------+
| 请输入户主姓名或手机号 |
| [______________________________] [搜索] |
| 提示:只可修改已经录入系统的来宾信息 |
+--------------------------------------------------------------+
| 搜索结果 |
+--------------------------------------------------------------+
| 王大爷一家 |
| 电话:138****8888 |
| 所属侧:男方 |
| 关系:亲戚 / 表叔 |
| 当前状态:待确认,礼金未登记 |
| [选择此户] |
+--------------------------------------------------------------+
确认页
+--------------------------------------------------------------+
| 请确认您要编辑的是以下来宾 |
+--------------------------------------------------------------+
| 户主:王大爷 |
| 电话:138****8888 |
| 所属侧:男方 |
| 关系:亲戚 / 表叔 |
| 预计人数:3 |
| 当前状态:待确认 |
| [返回搜索] [确认编辑这户] |
+--------------------------------------------------------------+
简化编辑页
+--------------------------------------------------------------+
| 编辑:王大爷一家 |
+--------------------------------------------------------------+
| 以下信息仅供核对,不可修改 |
| 户主姓名:王大爷 |
| 所属侧:男方 |
| 关系:亲戚 / 表叔 |
+--------------------------------------------------------------+
| 到场状态 |
| [待确认 v] |
| 实际来几人 [____] |
| 儿童人数 [____] |
| 需红包儿童人数 [____] |
+--------------------------------------------------------------+
| 礼金金额(元) [___________] |
| 礼金方式 [现金 v] |
| 记录场景 [婚礼当天 v] |
+--------------------------------------------------------------+
| 喜糖状态 [未发 v] |
| 伴手礼状态 [未发 v] |
| 儿童红包状态 [未发 v] |
+--------------------------------------------------------------+
| 备注 [__________________________________________] |
| [取消] [保存修改] |
+--------------------------------------------------------------+
保存确认页
+--------------------------------------------------------------+
| 请确认以下修改 |
+--------------------------------------------------------------+
| 到场状态:待确认 -> 确认出席 |
| 实际人数:0 -> 4 |
| 礼金金额:0 -> 2000 |
| 礼金方式:未填写 -> 现金 |
| [返回修改] [确认保存] |
+--------------------------------------------------------------+
7. CSV 导入导出设计
7.1 导出
支持:
- 导出全部
- 导出当前筛选结果
当前实现约定:
- 通过独立的
/csv/households/export路由导出 - 复用管理首页现有 query params(
q / side / invite_status / attendance_status / sort_by / sort_order)生成“导出当前筛选结果” - 使用 Python 标准库
csv在服务端生成响应 - 使用 UTF-8 with BOM,兼容 Excel 打开中文 CSV
- 使用
\r\n行结束符
导出字段建议:
- 户主姓名
- 联系方式
- 所属侧
- 关系分类
- 标签
- 预计人数
- 实际人数
- 儿童数
- 需红包儿童数
- 礼金金额
- 邀请状态
- 到场状态
- 伴手礼状态
- 喜糖状态
- 备注
7.2 导入流程
建议流程:
- 上传文件
- 服务端解析整份 CSV 并生成预览
- 校验表头、编码、字段值与选项编码
- 检查冲突
- 选择处理方式
- 确认导入
- 记录导入日志
当前实现约定:
- 上传入口:
/csv/households/import - 预览提交:
POST /csv/households/import/preview - 确认提交:
POST /csv/households/import/confirm - 上传文件限制:5 MB
- 编码要求:UTF-8 / UTF-8 with BOM
- 预览状态保存在
instance/csv_previews/*.json - 预览状态有 TTL,失效后必须重新上传
7.3 冲突检测建议
当前实现规则:
- 强匹配:
household_code - 次级冲突提示:
head_name + side + phone
确认导入时支持两种处理方式:
skip_conflicts:只新增明确可创建的行,跳过编码更新候选与疑似冲突行update_by_code:按户编码更新已存在户,同时新增明确可创建的行,仍跳过无编码命中的疑似冲突行
7.4 前端库建议
当前实现采用服务端方案,不依赖额外前端 CSV 库。
原因:
- 需要与 SSR 查询、筛选结果和服务端导出保持一致
- 需要统一复用服务端字段校验与选项编码校验逻辑
- 需要在确认导入前形成可审计的服务端预览结果
- 当前数据规模适合使用 Python 标准库
csv直接实现
8. 防呆与可用性设计
8.1 通用原则
- 默认只读
- 危险操作二次确认
- 删除建议软删除
- 导入先预览
- 重要提示不自动消失
- 错误提示不只靠颜色
8.2 长辈友好原则
- 单列布局
- 字号至少 16px
- 触控区域至少 44x44
- 用词尽量口语化
- 不用隐藏手势
- 不用复杂拖拽
8.3 编辑冲突控制
建议加入 version 字段实现乐观锁。
用途:
- 防止两个账号同时修改同一条记录时后保存者直接覆盖前者
- 出现冲突时提示“该记录已被其他人修改,请刷新后重试”
9. 安全与实现边界
9.1 账号密码
虽然登录机制可以简单,但仍建议:
- 密码哈希存储
- 不明文保存密码
- 停用账号禁止登录
- 记录登录时间
9.2 数据删除
建议 MVP 阶段不做真正物理删除,优先软删除。
9.3 金额字段
礼金等金额字段在后端统一使用 Decimal 语义。
10. 分阶段实现建议
当前已完成的核心可用版本闭环
- 基础项目结构、迁移、seed 与
.venv开发工作流 - 一键开发启动命令
flask dev-bootstrap,可自动迁移、初始化基础种子、初始化演示种子并启动服务 - 登录 / 登出 / Session / 角色访问边界
- 管理首页左侧边栏布局、统计前置、搜索独立一行、筛选排序独立一行
- 管理端单条编辑页
- 管理端新增一户最小创建流程
- 管理端家庭成员管理(HouseholdMember)闭环:在户级编辑页支持成员新增、编辑、删除,并写入审计日志
- 管理端礼金明细管理(gift_records)闭环:在户级编辑页支持礼金明细新增、编辑、软删除,自动重算户级礼金聚合字段并写入审计日志
- 快速录入搜索、实时刷新、单独编辑页 / 新增页闭环
- 分享搜索、实时刷新与单独编辑页闭环
- 管理首页内聚统计摘要(独立统计报表页已下线)
- light / dark 双主题切换
- 压力测试数据命令
seed-stress - 账号管理页(列表、创建、编辑、重置密码、启停)
- 审计日志页(列表、筛选、详情 before/after 快照)
- 关键认证与业务动作审计日志
- 管理首页搜索、组合筛选、排序、partial 动态刷新与筛选后统计摘要
- 中文 / 拼音 / 首字母搜索
- CSV 模板下载、导出全部、导出当前筛选、上传预览、校验、冲突处理、确认导入与导入审计日志
- Python + Playwright E2E 基础设施与核心流程测试(管理员编辑、长辈录入、管理端新增户、家庭成员 CRUD、账号管理提交链路、审计日志筛选与详情、CSV 导入导出、认证与权限边界)
- Linux 单机生产部署闭环:
ProductionConfig、Python 3.13 容器镜像、Docker 入口脚本、环境变量示例、安装/部署/校验脚本、生产部署文档与运维文档 - 当前最新一次本地全量测试:
.venv/bin/python -m pytest tests→107 passed
当前阶段边界
- 当前已形成“核心可用版 + 生产部署交付”的阶段性基线,功能范围先暂停扩展
- 后续工作以部署落地、文档保持同步、运维校验和必要缺陷修复为主
- 如未来恢复功能迭代,可优先考虑桌席安排、更完整统计分析、历史往来能力,以及 CSRF 等正式发布前安全加固