# 技术方案与详细设计 > 说明:本文件保留了项目早期方案与演进背景。若其中出现旧 `/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 双主题切换,切换入口位于右上角用户菜单 - 使用 `` 驱动暗色主题样式切换 - 使用 `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` 通过 Flask `url_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. 目录结构建议 建议后续项目结构: ```text 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-a` - `mother-side-b` - `uncle-entry` 登录后进入固定的快速录入页,不让其接触复杂管理页。 好处: - 使用路径简单 - 可以知道是谁完成了搜索后的修改 - 审计日志天然具备来源追踪 ### 快速录入页访问流建议 建议访问流如下: 1. 管理员在后台创建长辈专用账号 2. 为该账号分配 `entry_only` 角色 3. 将专用登录入口提供给对应长辈 4. 长辈使用账号密码登录 5. 登录成功后系统自动跳转到 `/quick-entry` 6. 录入人员先搜索系统中已存在的户 7. 选择目标户后进入单独编辑页;若搜不到,可直接新增一户并记礼金 8. 所有修改行为自动写入 `updated_by` 9. 审计日志记录本次登录与保存修改行为 说明: - “专属入口”建议理解为“专用登录入口 + 专属账号身份”;公开分享页另走分享链接模式 - 本次婚宴涉及的户应由管理员预先录入系统,长辈账号不负责纯新增 - 这样既简单,也便于后续审计和权限控制 ## 3.2 数据库建模约定 ### 3.2.1 MVP 表范围 当前阶段保持 **6 张核心表**,避免过早拆成过多小表: 1. `accounts` 2. `households` 3. `household_members` 4. `gift_records` 5. `audit_logs` 6. `option_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_at` - `household_members`:MVP 中可直接物理删除,但删除动作要记审计日志;若后续需要支持恢复误删成员,可再补 `deleted_at` - `option_items`:默认不物理删除,通过 `is_enabled` 停启、`is_system` 保护系统项 - `audit_logs`:默认只追加、不删除 外键约定: - `household_members.household_id -> households.id` - `gift_records.household_id -> households.id` - `households.created_by / updated_by -> accounts.id` - `household_members.created_by / updated_by -> accounts.id` - `gift_records.created_by / updated_by -> accounts.id` - `option_items.created_by / updated_by -> accounts.id` - `audit_logs.actor_user_id -> accounts.id`(可空,保留用户名快照) - `households.relation_category_option_id -> option_items.id` - `households.gift_method_option_id -> option_items.id` - `households.gift_scene_option_id -> option_items.id` - `gift_records.method_option_id -> option_items.id` - `gift_records.scene_option_id -> option_items.id` - `option_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 枚举与动态选项约定 为兼顾“够用就行”和“后续可配置”,当前采用两类策略: 1. **固定枚举,代码常量维护** - `accounts.role`: `admin` / `editor` / `entry_only` - `accounts.status`: `active` / `disabled` - `households.side`: `groom_side` / `bride_side` / `both_side` - `households.invite_status`: `not_invited` / `invited` / `confirmed` / `declined` - `households.attendance_status`: `pending` / `attending` / `absent` / `partial` - `households.favor_status`: `not_given` / `given` - `households.candy_status`: `not_given` / `given` - `households.child_red_packet_status`: `not_given` / `partial` / `given` - `gift_records.record_type`: `cash_gift` / `physical_gift` / `pre_wedding_cash` / `wedding_day_cash` / `post_wedding_cash` 2. **可配置选项,使用 `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 >= 0` - `expected_attendee_count >= 0` - `child_count >= 0` - `red_packet_child_count >= 0` - `red_packet_child_count <= child_count` - `actual_attendee_count` 不应明显大于成员数与预计人数总和,若超出则给出提示 - `phone` 允许为空,但若填写应做基础格式校验 实现说明: - `entry_only` 角色默认不能修改户的核心身份字段,如户主姓名、所属侧、关系分类、标签 - `entry_only` 角色仅允许修改到场、礼金、备注等当天高频字段 - `gift_method_option_id` 与 `gift_scene_option_id` 用于户级“当前主状态”,详细礼金流水仍写入 `gift_records` - `total_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_category` - `tag` - `gift_method` - `gift_scene` ## 3.6 数据库索引与校验建议 ### 3.6.1 高频查询对应的索引目标 1. **长辈页搜索既有户** - 主要字段:`head_name`、`phone` - 对应索引:`idx_households_head_name`、`idx_households_phone` 2. **管理页按所属侧 / 关系分类 / 状态筛选** - 主要字段:`side`、`relation_category_option_id`、`attendance_status` - 对应索引:`idx_households_side_relation_category`、`idx_households_attendance_status` 3. **查看某户礼金历史** - 主要字段:`household_id`、`record_time` - 对应索引:`idx_gift_records_household_record_time` 4. **管理首页组合筛选与最近更新时间排序** - 主要字段:`side`、`invite_status`、`attendance_status`、`updated_at` - 对应索引:`idx_households_side_relation_category`、`idx_households_attendance_status`、`idx_households_updated_at` 5. **账号登录与权限过滤** - 主要字段:`username`、`role`、`status` - 对应索引:`uk_accounts_username`、`idx_accounts_role_status` 6. **审计日志筛选** - 主要字段:`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 表格模板: - 管理首页使用服务端渲染的原生 `` - 默认只读,单条编辑跳转到独立编辑页 - 通过 query params 维持搜索、筛选、排序与导出上下文 这样做的原因: - 技术复杂度更低,适合当前核心可用版本 - 与 Tailwind 样式集成简单 - 对长辈和非技术用户更直观,避免前端重交互表格带来的认知负担 如果后续确实出现更复杂的分页、列操作、批量编辑需求,再评估是否引入更重的表格库。 ## 5.2 首页布局与动态刷新 当前实现遵循“高频操作留在主区,低频操作收纳到右上角”的原则: ```text 左侧边栏 ├── 管理首页 ├── 快速录入 ├── (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 编辑交互建议 流程: 1. 查看列表 2. 点击“修改” 3. 打开单独编辑页 4. 修改字段 5. 点击“保存并返回搜索”或“保存” 7. 提交成功 8. 写入审计日志 ### 不建议 - 默认行内直接可编辑 - 打开页面就是输入框 - 修改后自动保存 ## 6. 快速录入页设计 ## 6.1 页面目标 - 极简 - 单列 - 搜索后快速进入当天录礼金 - 支持“搜不到时快速新增一户并记礼金” - 不承担完整资料维护职责 - 尽量降低改错人的风险 ## 6.2 页面结构建议 当前实现采用“搜索页 -> 单独编辑页 / 新增页”的模式: 1. 搜索目标户 2. 搜索结果卡片列表实时刷新 3. 点击“编辑”后进入单独编辑页 4. 编辑页只保留当天高频字段 5. 保存后返回搜索页 ## 6.3 搜索页建议 搜索字段建议支持: - 户主姓名 - 成员姓名 - 拼音 / 首字母 - 标签 - 备注 结果列表建议展示: - 明确的“编辑”按钮 - 户主姓名 - 所属侧 - 关系分类 - 当前状态摘要 ## 6.4 允许修改字段建议 - 到场状态 - 实际到场人数 - 儿童人数 - 需红包儿童人数 - 礼金金额 - 礼金方式 - 礼金记录场景 - 备注 ## 6.5 只读展示字段建议 - 户主姓名 - 所属侧 - 关系分类 - 预计人数 ## 6.6 交互建议 - 登录后默认进入该页 - 先搜索再选择目标户 - 搜索输入时使用 300ms debounce 实时刷新结果列表 - 编辑表单使用单独页面承载 - 保存成功显示明显成功提示 - 数量类字段尽量在前端做即时校验,并保留服务端兜底校验,避免用户误填后直到提交才发现问题 - 搜索页应同时提供“新增”入口,便于当天现场处理未预录来宾 ## 6.7 ASCII 草图建议 ### 搜索页 ```text +--------------------------------------------------------------+ | 完善来宾信息 | | 当前账号:爸爸专用账号 | +--------------------------------------------------------------+ | 请输入户主姓名或手机号 | | [______________________________] [搜索] | | 提示:只可修改已经录入系统的来宾信息 | +--------------------------------------------------------------+ | 搜索结果 | +--------------------------------------------------------------+ | 王大爷一家 | | 电话:138****8888 | | 所属侧:男方 | | 关系:亲戚 / 表叔 | | 当前状态:待确认,礼金未登记 | | [选择此户] | +--------------------------------------------------------------+ ``` ### 确认页 ```text +--------------------------------------------------------------+ | 请确认您要编辑的是以下来宾 | +--------------------------------------------------------------+ | 户主:王大爷 | | 电话:138****8888 | | 所属侧:男方 | | 关系:亲戚 / 表叔 | | 预计人数:3 | | 当前状态:待确认 | | [返回搜索] [确认编辑这户] | +--------------------------------------------------------------+ ``` ### 简化编辑页 ```text +--------------------------------------------------------------+ | 编辑:王大爷一家 | +--------------------------------------------------------------+ | 以下信息仅供核对,不可修改 | | 户主姓名:王大爷 | | 所属侧:男方 | | 关系:亲戚 / 表叔 | +--------------------------------------------------------------+ | 到场状态 | | [待确认 v] | | 实际来几人 [____] | | 儿童人数 [____] | | 需红包儿童人数 [____] | +--------------------------------------------------------------+ | 礼金金额(元) [___________] | | 礼金方式 [现金 v] | | 记录场景 [婚礼当天 v] | +--------------------------------------------------------------+ | 喜糖状态 [未发 v] | | 伴手礼状态 [未发 v] | | 儿童红包状态 [未发 v] | +--------------------------------------------------------------+ | 备注 [__________________________________________] | | [取消] [保存修改] | +--------------------------------------------------------------+ ``` ### 保存确认页 ```text +--------------------------------------------------------------+ | 请确认以下修改 | +--------------------------------------------------------------+ | 到场状态:待确认 -> 确认出席 | | 实际人数: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 导入流程 建议流程: 1. 上传文件 2. 服务端解析整份 CSV 并生成预览 3. 校验表头、编码、字段值与选项编码 4. 检查冲突 5. 选择处理方式 6. 确认导入 7. 记录导入日志 当前实现约定: - 上传入口:`/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 等正式发布前安全加固