feat: 同步UI模版定制功能到PRD、HLD、LLD文档
PRD更新: - 新增2.6 UI模版定制模块 - 包含品牌定制、布局调整、预设模板、配置历史、可视化配置器五个子模块 - 每个子模块包含功能描述、用户故事、功能点、业务规则、验收标准 HLD更新: - 业务范围中新增UI模版定制模块 - 新增3.5 UI模版定制流程(业务场景、业务流程、业务规则、异常处理) - 新增4.6 UI模版定制规则(品牌元素应用、Logo格式限制、颜色格式限制等8条规则) LLD更新: - 新增2.6 UI模版定制模块(模块概述、数据模型设计、核心业务逻辑) - 数据模型包含4个表:tenant_ui_config、ui_template、ui_config_history、ui_resource - 核心业务逻辑包含4个Service:BrandConfigService、LayoutConfigService、TemplateService、ConfigHistoryService - 新增3.5 UI模版定制模块API(10个API接口,涵盖品牌定制、布局调整、模板管理、配置历史) 所有文档已保持一致性,UI模版定制功能已完整同步到产品需求、概要设计、详细设计文档中
This commit is contained in:
+142
-92
@@ -10,8 +10,8 @@
|
||||
|
||||
## 文档修订历史
|
||||
|
||||
| 版本 | 日期 | 作者 | 修订内容 |
|
||||
| ---- | ---------- | ---- | ------------------ |
|
||||
| 版本 | 日期 | 作者 | 修订内容 |
|
||||
| ---- | ---------- | ---- | ---------------------- |
|
||||
| v1.0 | 2026-03-04 | 张翔 | 创建基础版业务概要设计 |
|
||||
|
||||
---
|
||||
@@ -32,19 +32,18 @@
|
||||
|
||||
### 1.3 术语定义
|
||||
|
||||
| 术语 | 定义 |
|
||||
| 术语 | 定义 |
|
||||
| ----------------------------- | ------------------------------------------------ |
|
||||
| 租户(Tenant) | 系统的多租户架构中的独立业务实体,如一个连锁品牌 |
|
||||
| 门店(Store) | 租户下的具体经营场所 |
|
||||
| 会员(Member) | 在门店注册的用户 |
|
||||
| 权益(Benefit) | 会员卡包含的时长、次数、储值、等级等权益 |
|
||||
| 可预约资源(Bookable Resource) | 团课等可被预约的对象 |
|
||||
| 时段(Slot) | 资源的可预约时间窗口 |
|
||||
| 租户(Tenant) | 系统的多租户架构中的独立业务实体,如一个连锁品牌 |
|
||||
| 门店(Store) | 租户下的具体经营场所 |
|
||||
| 会员(Member) | 在门店注册的用户 |
|
||||
| 权益(Benefit) | 会员卡包含的时长、次数、储值、等级等权益 |
|
||||
| 可预约资源(Bookable Resource) | 团课等可被预约的对象 |
|
||||
| 时段(Slot) | 资源的可预约时间窗口 |
|
||||
|
||||
### 1.4 参考文档
|
||||
|
||||
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
|
||||
- 《健身房管理系统业务概要设计文档》 GYM-HLD-001
|
||||
|
||||
---
|
||||
|
||||
@@ -52,21 +51,21 @@
|
||||
|
||||
### 2.1 业务目标
|
||||
|
||||
| 目标维度 | 目标描述 | 成功指标 |
|
||||
| 目标维度 | 目标描述 | 成功指标 |
|
||||
| -------- | ---------------------- | -------------------------------- |
|
||||
| 用户体验 | 提升会员预约和签到体验 | 预约成功率 ≥ 95%,签到耗时 ≤ 3秒 |
|
||||
| 运营效率 | 降低人工操作成本 | 人工处理时间减少 50% |
|
||||
| 数据价值 | 提供基础数据支持 | 数据报表使用率 ≥ 80% |
|
||||
| 运营效率 | 降低人工操作成本 | 人工处理时间减少 50% |
|
||||
| 数据价值 | 提供基础数据支持 | 数据报表使用率 ≥ 80% |
|
||||
|
||||
### 2.2 用户角色
|
||||
|
||||
| 角色 | 描述 | 主要功能 |
|
||||
| 角色 | 描述 | 主要功能 |
|
||||
| ---------- | -------------- | ---------------------------- |
|
||||
| 会员 | 健身房注册用户 | 预约课程、签到、查看个人信息 |
|
||||
| 教练 | 健身房教练 | 排课、团课签到管理 |
|
||||
| 前台 | 门店前台人员 | 会员接待、签到辅助、会员管理 |
|
||||
| 店长 | 门店管理者 | 单店全功能管理、数据查看 |
|
||||
| 超级管理员 | 平台最高权限 | 全平台管理、系统配置 |
|
||||
| 会员 | 健身房注册用户 | 预约课程、签到、查看个人信息 |
|
||||
| 教练 | 健身房教练 | 排课、团课签到管理 |
|
||||
| 前台 | 门店前台人员 | 会员接待、签到辅助、会员管理 |
|
||||
| 店长 | 门店管理者 | 单店全功能管理、数据查看 |
|
||||
| 超级管理员 | 平台最高权限 | 全平台管理、系统配置 |
|
||||
|
||||
### 2.3 业务范围
|
||||
|
||||
@@ -105,6 +104,12 @@
|
||||
│ │ • 用户管理 • 角色权限管理 │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ UI模版定制 │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ • 品牌定制 • 布局调整 • 预设模板 • 配置历史 │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -136,11 +141,11 @@
|
||||
|
||||
#### 3.1.4 异常处理
|
||||
|
||||
| 异常场景 | 处理方式 |
|
||||
|---------|---------|
|
||||
| 异常场景 | 处理方式 |
|
||||
| ------------ | ---------------- |
|
||||
| 手机号已存在 | 提示用户直接登录 |
|
||||
| 验证码错误 | 提示用户重新输入 |
|
||||
| 验证码过期 | 提示用户重新获取 |
|
||||
| 验证码错误 | 提示用户重新输入 |
|
||||
| 验证码过期 | 提示用户重新获取 |
|
||||
|
||||
---
|
||||
|
||||
@@ -183,11 +188,11 @@
|
||||
|
||||
#### 3.2.4 异常处理
|
||||
|
||||
| 异常场景 | 处理方式 |
|
||||
|---------|---------|
|
||||
| 课程已满 | 提示用户选择其他课程 |
|
||||
| 会员卡权益不足 | 提示用户购买会员卡 |
|
||||
| 预约时间过短 | 提示用户提前预约 |
|
||||
| 异常场景 | 处理方式 |
|
||||
| -------------- | -------------------- |
|
||||
| 课程已满 | 提示用户选择其他课程 |
|
||||
| 会员卡权益不足 | 提示用户购买会员卡 |
|
||||
| 预约时间过短 | 提示用户提前预约 |
|
||||
|
||||
---
|
||||
|
||||
@@ -215,11 +220,11 @@
|
||||
|
||||
#### 3.3.4 异常处理
|
||||
|
||||
| 异常场景 | 处理方式 |
|
||||
|---------|---------|
|
||||
| 异常场景 | 处理方式 |
|
||||
| ---------- | ------------------ |
|
||||
| 会员卡无效 | 提示用户购买会员卡 |
|
||||
| 会员卡过期 | 提示用户续费 |
|
||||
| 签到码无效 | 提示用户重新扫描 |
|
||||
| 会员卡过期 | 提示用户续费 |
|
||||
| 签到码无效 | 提示用户重新扫描 |
|
||||
|
||||
---
|
||||
|
||||
@@ -247,56 +252,103 @@
|
||||
|
||||
#### 3.4.4 异常处理
|
||||
|
||||
| 异常场景 | 处理方式 |
|
||||
|---------|---------|
|
||||
| 支付失败 | 提示用户重新支付 |
|
||||
| 异常场景 | 处理方式 |
|
||||
| -------- | -------------------- |
|
||||
| 支付失败 | 提示用户重新支付 |
|
||||
| 支付超时 | 提示用户重新发起支付 |
|
||||
|
||||
---
|
||||
|
||||
### 3.5 UI模版定制流程
|
||||
|
||||
#### 3.5.1 业务场景
|
||||
|
||||
租户通过管理后台的可视化配置器定制自己的UI,包括品牌元素、布局结构和预设模板。
|
||||
|
||||
#### 3.5.2 业务流程
|
||||
|
||||
```
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ 租户登录 │ → │ 打开UI │ → │ 品牌定制 │ → │ 布局调整 │ → │ 配置保存 │
|
||||
│ 管理后台 │ │ 定制器 │ │ │ │ │ │ │
|
||||
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
#### 3.5.3 业务规则
|
||||
|
||||
- 品牌元素应用范围包括小程序和管理后台
|
||||
- 布局调整支持拖拽排序和模块隐藏
|
||||
- 预设模板应用后保留品牌配置
|
||||
- 配置变更实时生效,无需重新部署
|
||||
- 配置变更自动记录到历史
|
||||
|
||||
#### 3.5.4 异常处理
|
||||
|
||||
| 异常场景 | 处理方式 |
|
||||
| ------------ | -------------------- |
|
||||
| Logo上传失败 | 提示用户重新上传 |
|
||||
| 配置保存失败 | 提示用户检查配置格式 |
|
||||
| 模板应用失败 | 提示用户调整品牌配置 |
|
||||
| 配置回滚失败 | 提示用户选择其他版本 |
|
||||
|
||||
---
|
||||
|
||||
## 四、核心业务规则
|
||||
|
||||
### 4.1 会员管理规则
|
||||
|
||||
| 规则 | 描述 |
|
||||
|------|------|
|
||||
| 会员唯一性 | 手机号作为会员唯一标识 |
|
||||
| 会员信息完整性 | 必填字段:手机号、姓名、性别 |
|
||||
| 规则 | 描述 |
|
||||
| ---------------- | ------------------------------------------------------ |
|
||||
| 会员唯一性 | 手机号作为会员唯一标识 |
|
||||
| 会员信息完整性 | 必填字段:手机号、姓名、性别 |
|
||||
| 会员信息修改权限 | 会员只能编辑自己的基本信息,前台和店长可以编辑所有信息 |
|
||||
|
||||
### 4.2 会员卡管理规则
|
||||
|
||||
| 规则 | 描述 |
|
||||
|------|------|
|
||||
| 会员卡类型 | 支持时长卡、次卡、储值卡 |
|
||||
| 会员卡有效期 | 时长卡有有效期,次卡和储值卡无有效期 |
|
||||
| 会员卡到期提醒 | 到期前7天提醒 |
|
||||
| 会员卡续费 | 续费后权益立即生效 |
|
||||
| 规则 | 描述 |
|
||||
| -------------- | ------------------------------------ |
|
||||
| 会员卡类型 | 支持时长卡、次卡、储值卡 |
|
||||
| 会员卡有效期 | 时长卡有有效期,次卡和储值卡无有效期 |
|
||||
| 会员卡到期提醒 | 到期前7天提醒 |
|
||||
| 会员卡续费 | 续费后权益立即生效 |
|
||||
|
||||
### 4.3 预约管理规则
|
||||
|
||||
| 规则 | 描述 |
|
||||
|------|------|
|
||||
| 预约时间限制 | 预约需在课程开始前至少30分钟 |
|
||||
| 规则 | 描述 |
|
||||
| ---------------- | ------------------------------- |
|
||||
| 预约时间限制 | 预约需在课程开始前至少30分钟 |
|
||||
| 取消预约时间限制 | 取消预约需在课程开始前至少2小时 |
|
||||
| 团课容量限制 | 每节课最多20人 |
|
||||
| 预约权益扣减 | 预约成功后扣减权益 |
|
||||
| 团课容量限制 | 每节课最多20人 |
|
||||
| 预约权益扣减 | 预约成功后扣减权益 |
|
||||
|
||||
### 4.4 签到管理规则
|
||||
|
||||
| 规则 | 描述 |
|
||||
|------|------|
|
||||
| 签到验证 | 签到需验证会员卡有效性 |
|
||||
| 规则 | 描述 |
|
||||
| ------------ | -------------------------- |
|
||||
| 签到验证 | 签到需验证会员卡有效性 |
|
||||
| 签到预约验证 | 签到需验证预约信息(如有) |
|
||||
| 签到记录 | 签到成功后记录到店时间 |
|
||||
| 签到记录 | 签到成功后记录到店时间 |
|
||||
|
||||
### 4.5 数据统计规则
|
||||
|
||||
| 规则 | 描述 |
|
||||
|------|------|
|
||||
| 数据保留期限 | 数据保留30天 |
|
||||
| 统计维度 | 支持按日、周、月统计 |
|
||||
| 数据导出 | 支持数据导出 |
|
||||
| 规则 | 描述 |
|
||||
| ------------ | -------------------- |
|
||||
| 数据保留期限 | 数据保留30天 |
|
||||
| 统计维度 | 支持按日、周、月统计 |
|
||||
| 数据导出 | 支持数据导出 |
|
||||
|
||||
### 4.6 UI模版定制规则
|
||||
|
||||
| 规则 | 描述 |
|
||||
| ------------ | ------------------------------------------ |
|
||||
| 品牌元素应用 | 品牌元素应用范围包括小程序和管理后台 |
|
||||
| Logo格式限制 | Logo支持PNG/JPG格式,限制2MB以内 |
|
||||
| 颜色格式限制 | 颜色支持RGB和HEX格式 |
|
||||
| 布局调整权限 | 布局调整支持按角色区分(店长、前台、会员) |
|
||||
| 模板应用规则 | 模板应用后保留租户已有的品牌配置 |
|
||||
| 配置版本管理 | 每次配置变更自动生成新版本号 |
|
||||
| 配置历史保留 | 配置历史保留90天 |
|
||||
| 配置实时生效 | 配置变更实时生效,无需重新部署 |
|
||||
|
||||
---
|
||||
|
||||
@@ -397,14 +449,14 @@
|
||||
|
||||
### 6.1 核心实体
|
||||
|
||||
| 实体 | 描述 |
|
||||
|------|------|
|
||||
| 会员(Member) | 健身房注册用户 |
|
||||
| 实体 | 描述 |
|
||||
| ------------------ | ---------------- |
|
||||
| 会员(Member) | 健身房注册用户 |
|
||||
| 会员卡(MemberCard) | 会员购买的权益卡 |
|
||||
| 权益(Benefit) | 会员卡包含的权益 |
|
||||
| 团课(GroupClass) | 集体课程 |
|
||||
| 预约(Booking) | 会员预约记录 |
|
||||
| 签到(CheckIn) | 会员签到记录 |
|
||||
| 权益(Benefit) | 会员卡包含的权益 |
|
||||
| 团课(GroupClass) | 集体课程 |
|
||||
| 预约(Booking) | 会员预约记录 |
|
||||
| 签到(CheckIn) | 会员签到记录 |
|
||||
|
||||
### 6.2 实体关系
|
||||
|
||||
@@ -422,36 +474,35 @@
|
||||
|
||||
### 7.1 性能约束
|
||||
|
||||
| 指标 | 要求 |
|
||||
|------|------|
|
||||
| API响应时间 (P99) | 200-400ms |
|
||||
| 并发用户 | 支持1000并发用户 |
|
||||
| 吞吐量 (QPS) | 3000-5000 |
|
||||
| 数据库查询 | 查询响应时间 ≤ 500ms |
|
||||
| 指标 | 要求 |
|
||||
| ----------------- | ----------------- |
|
||||
| API响应时间 (P99) | ≤ 500ms |
|
||||
| 并发用户 | 支持100并发用户 |
|
||||
| 数据库查询 | 查询响应时间 ≤ 1s |
|
||||
|
||||
### 7.2 可用性约束
|
||||
|
||||
| 指标 | 要求 |
|
||||
|------|------|
|
||||
| 系统可用性 | SLA ≥ 99.9% |
|
||||
| 指标 | 要求 |
|
||||
| ------------ | ------------- |
|
||||
| 系统可用性 | SLA ≥ 99.9% |
|
||||
| 故障恢复时间 | MTTR ≤ 30分钟 |
|
||||
|
||||
### 7.3 安全性约束
|
||||
|
||||
| 指标 | 要求 |
|
||||
|------|------|
|
||||
| 数据加密 | 敏感数据加密存储 |
|
||||
| 访问控制 | 基于角色的访问控制 |
|
||||
| 指标 | 要求 |
|
||||
| -------- | -------------------- |
|
||||
| 数据加密 | 敏感数据加密存储 |
|
||||
| 访问控制 | 基于角色的访问控制 |
|
||||
| 操作审计 | 关键操作记录审计日志 |
|
||||
|
||||
### 7.4 可扩展性约束
|
||||
|
||||
| 指标 | 要求 |
|
||||
|------|------|
|
||||
| 会员数量 | 最多500人 |
|
||||
| 门店数量 | 单门店 |
|
||||
| 指标 | 要求 |
|
||||
| -------- | -------------- |
|
||||
| 会员数量 | 最多500人 |
|
||||
| 门店数量 | 单门店 |
|
||||
| 团课容量 | 每节课最多20人 |
|
||||
| 数据保留 | 保留30天 |
|
||||
| 数据保留 | 保留30天 |
|
||||
|
||||
---
|
||||
|
||||
@@ -459,16 +510,15 @@
|
||||
|
||||
### 8.1 术语定义
|
||||
|
||||
| 术语 | 定义 |
|
||||
|------|------|
|
||||
| 会员 | 在健身房注册的用户 |
|
||||
| 术语 | 定义 |
|
||||
| ------ | ------------------------------------------ |
|
||||
| 会员 | 在健身房注册的用户 |
|
||||
| 会员卡 | 会员购买的权益卡,包括时长卡、次卡、储值卡 |
|
||||
| 权益 | 会员卡包含的时长、次数、储值、等级等权益 |
|
||||
| 团课 | 集体课程,由教练带领多个会员一起上课 |
|
||||
| 预约 | 会员预约团课 |
|
||||
| 签到 | 会员到店记录 |
|
||||
| 权益 | 会员卡包含的时长、次数、储值、等级等权益 |
|
||||
| 团课 | 集体课程,由教练带领多个会员一起上课 |
|
||||
| 预约 | 会员预约团课 |
|
||||
| 签到 | 会员到店记录 |
|
||||
|
||||
### 8.2 参考文档
|
||||
|
||||
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
|
||||
- 《健身房管理系统业务概要设计文档》 GYM-HLD-001
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
|
||||
- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001
|
||||
- 《健身房管理系统详细设计文档》 GYM-LLD-000
|
||||
- Spring Boot 3 官方文档
|
||||
- R2DBC 规范文档
|
||||
- PostgreSQL 官方文档
|
||||
@@ -874,6 +873,474 @@ public class UserService {
|
||||
|
||||
---
|
||||
|
||||
### 2.6 UI模版定制模块
|
||||
|
||||
#### 2.6.1 模块概述
|
||||
|
||||
UI模版定制模块是基础版的核心基础模块,负责管理租户的UI定制配置,包括:
|
||||
|
||||
- 品牌定制(Logo、颜色、背景图等)
|
||||
- 布局调整(模块顺序、模块隐藏等)
|
||||
- 预设模板(模板选择、模板应用等)
|
||||
- 配置历史(配置回滚、配置对比等)
|
||||
|
||||
#### 2.6.2 数据模型设计
|
||||
|
||||
**租户UI配置表 (tenant_ui_config)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE tenant_ui_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
brand_config JSONB NOT NULL DEFAULT '{}',
|
||||
layout_config JSONB NOT NULL DEFAULT '{}',
|
||||
template_id BIGINT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_by BIGINT,
|
||||
deleted_at TIMESTAMP DEFAULT NULL,
|
||||
|
||||
CONSTRAINT uk_tenant_ui_config UNIQUE (tenant_id, version),
|
||||
CONSTRAINT fk_tenant_ui_config_template FOREIGN KEY (template_id) REFERENCES ui_template(id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenant_ui_config_tenant ON tenant_ui_config(tenant_id);
|
||||
CREATE INDEX idx_tenant_ui_config_is_active ON tenant_ui_config(is_active);
|
||||
```
|
||||
|
||||
**预设模板表 (ui_template)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE ui_template (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
code VARCHAR(64) NOT NULL,
|
||||
type VARCHAR(32) NOT NULL,
|
||||
description VARCHAR(512),
|
||||
thumbnail VARCHAR(512),
|
||||
preview_image VARCHAR(512),
|
||||
config JSONB NOT NULL DEFAULT '{}',
|
||||
status SMALLINT DEFAULT 1,
|
||||
sort_order INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_by BIGINT,
|
||||
deleted_at TIMESTAMP DEFAULT NULL,
|
||||
|
||||
CONSTRAINT uk_ui_template_code UNIQUE (code)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ui_template_type ON ui_template(type);
|
||||
CREATE INDEX idx_ui_template_status ON ui_template(status);
|
||||
```
|
||||
|
||||
**配置历史表 (ui_config_history)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE ui_config_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
config_id BIGINT NOT NULL,
|
||||
version INT NOT NULL,
|
||||
brand_config JSONB NOT NULL DEFAULT '{}',
|
||||
layout_config JSONB NOT NULL DEFAULT '{}',
|
||||
template_id BIGINT,
|
||||
change_reason VARCHAR(512),
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
|
||||
CONSTRAINT fk_ui_config_history_config FOREIGN KEY (config_id) REFERENCES tenant_ui_config(id),
|
||||
CONSTRAINT fk_ui_config_history_template FOREIGN KEY (template_id) REFERENCES ui_template(id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ui_config_history_tenant ON ui_config_history(tenant_id);
|
||||
CREATE INDEX idx_ui_config_history_config ON ui_config_history(config_id);
|
||||
```
|
||||
|
||||
**资源文件表 (ui_resource)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE ui_resource (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
resource_type VARCHAR(32) NOT NULL,
|
||||
resource_name VARCHAR(128) NOT NULL,
|
||||
file_path VARCHAR(512) NOT NULL,
|
||||
file_size BIGINT,
|
||||
file_type VARCHAR(32),
|
||||
width INT,
|
||||
height INT,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
deleted_at TIMESTAMP DEFAULT NULL,
|
||||
|
||||
CONSTRAINT uk_ui_resource UNIQUE (tenant_id, resource_type, resource_name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ui_resource_tenant ON ui_resource(tenant_id);
|
||||
CREATE INDEX idx_ui_resource_type ON ui_resource(resource_type);
|
||||
```
|
||||
|
||||
#### 2.6.3 核心业务逻辑
|
||||
|
||||
**品牌配置Service**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class BrandConfigService {
|
||||
|
||||
@Autowired
|
||||
private TenantUiConfigRepository tenantUiConfigRepository;
|
||||
|
||||
@Autowired
|
||||
private UiResourceRepository uiResourceRepository;
|
||||
|
||||
@Autowired
|
||||
private FileStorageService fileStorageService;
|
||||
|
||||
/**
|
||||
* 上传Logo
|
||||
*/
|
||||
@Transactional
|
||||
public UiResource uploadLogo(Long tenantId, MultipartFile file, Long userId) {
|
||||
validateLogoFile(file);
|
||||
|
||||
String filePath = fileStorageService.upload(file);
|
||||
|
||||
UiResource resource = new UiResource();
|
||||
resource.setTenantId(tenantId);
|
||||
resource.setResourceType("logo");
|
||||
resource.setResourceName(file.getOriginalFilename());
|
||||
resource.setFilePath(filePath);
|
||||
resource.setFileSize(file.getSize());
|
||||
resource.setFileType(file.getContentType());
|
||||
|
||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||
resource.setWidth(image.getWidth());
|
||||
resource.setHeight(image.getHeight());
|
||||
|
||||
resource = uiResourceRepository.save(resource);
|
||||
|
||||
updateBrandConfig(tenantId, "logo", filePath, userId);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Logo文件
|
||||
*/
|
||||
private void validateLogoFile(MultipartFile file) {
|
||||
if (file.getSize() > 2 * 1024 * 1024) {
|
||||
throw new BusinessException("Logo文件大小不能超过2MB");
|
||||
}
|
||||
|
||||
String contentType = file.getContentType();
|
||||
if (!"image/png".equals(contentType) && !"image/jpeg".equals(contentType)) {
|
||||
throw new BusinessException("Logo文件格式只支持PNG或JPG");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新品牌配置
|
||||
*/
|
||||
@Transactional
|
||||
public void updateBrandConfig(Long tenantId, String key, String value, Long userId) {
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
JsonObject brandConfig = config.getBrandConfig();
|
||||
brandConfig.addProperty(key, value);
|
||||
|
||||
config.setBrandConfig(brandConfig);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置品牌颜色
|
||||
*/
|
||||
@Transactional
|
||||
public void setBrandColor(Long tenantId, String colorType, String color, Long userId) {
|
||||
validateColorFormat(color);
|
||||
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
JsonObject brandConfig = config.getBrandConfig();
|
||||
brandConfig.addProperty(colorType, color);
|
||||
|
||||
config.setBrandConfig(brandConfig);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证颜色格式
|
||||
*/
|
||||
private void validateColorFormat(String color) {
|
||||
if (color.matches("^#[0-9A-Fa-f]{6}$")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (color.matches("^rgb\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\)$")) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BusinessException("颜色格式不正确,请使用HEX或RGB格式");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**布局配置Service**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class LayoutConfigService {
|
||||
|
||||
@Autowired
|
||||
private TenantUiConfigRepository tenantUiConfigRepository;
|
||||
|
||||
/**
|
||||
* 更新模块顺序
|
||||
*/
|
||||
@Transactional
|
||||
public void updateModuleOrder(Long tenantId, List<String> moduleOrder, Long userId) {
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
JsonObject layoutConfig = config.getLayoutConfig();
|
||||
JsonArray moduleOrderArray = new JsonArray();
|
||||
moduleOrder.forEach(moduleOrderArray::add);
|
||||
|
||||
layoutConfig.add("moduleOrder", moduleOrderArray);
|
||||
|
||||
config.setLayoutConfig(layoutConfig);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏/显示模块
|
||||
*/
|
||||
@Transactional
|
||||
public void toggleModuleVisibility(Long tenantId, String moduleCode, boolean visible, Long userId) {
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
JsonObject layoutConfig = config.getLayoutConfig();
|
||||
JsonArray hiddenModules = layoutConfig.has("hiddenModules")
|
||||
? layoutConfig.getAsJsonArray("hiddenModules")
|
||||
: new JsonArray();
|
||||
|
||||
if (visible) {
|
||||
hiddenModules.removeIf(element -> element.getAsString().equals(moduleCode));
|
||||
} else {
|
||||
if (!hiddenModules.contains(new JsonPrimitive(moduleCode))) {
|
||||
hiddenModules.add(moduleCode);
|
||||
}
|
||||
}
|
||||
|
||||
layoutConfig.add("hiddenModules", hiddenModules);
|
||||
|
||||
config.setLayoutConfig(layoutConfig);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置首页布局类型
|
||||
*/
|
||||
@Transactional
|
||||
public void setHomeLayoutType(Long tenantId, String layoutType, Long userId) {
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
JsonObject layoutConfig = config.getLayoutConfig();
|
||||
layoutConfig.addProperty("homeLayoutType", layoutType);
|
||||
|
||||
config.setLayoutConfig(layoutConfig);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**模板管理Service**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class TemplateService {
|
||||
|
||||
@Autowired
|
||||
private UiTemplateRepository uiTemplateRepository;
|
||||
|
||||
@Autowired
|
||||
private TenantUiConfigRepository tenantUiConfigRepository;
|
||||
|
||||
@Autowired
|
||||
private UiConfigHistoryRepository uiConfigHistoryRepository;
|
||||
|
||||
/**
|
||||
* 获取所有可用模板
|
||||
*/
|
||||
public List<UiTemplate> getAvailableTemplates() {
|
||||
return uiTemplateRepository.findByStatus(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用模板
|
||||
*/
|
||||
@Transactional
|
||||
public void applyTemplate(Long tenantId, Long templateId, Long userId) {
|
||||
UiTemplate template = uiTemplateRepository.findById(templateId)
|
||||
.orElseThrow(() -> new BusinessException("模板不存在"));
|
||||
|
||||
if (template.getStatus() != 1) {
|
||||
throw new BusinessException("模板不可用");
|
||||
}
|
||||
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
saveConfigToHistory(config);
|
||||
|
||||
JsonObject templateConfig = template.getConfig();
|
||||
JsonObject layoutConfig = templateConfig.getAsJsonObject("layoutConfig");
|
||||
|
||||
config.setLayoutConfig(layoutConfig);
|
||||
config.setTemplateId(templateId);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置到历史
|
||||
*/
|
||||
private void saveConfigToHistory(TenantUiConfig config) {
|
||||
UiConfigHistory history = new UiConfigHistory();
|
||||
history.setTenantId(config.getTenantId());
|
||||
history.setConfigId(config.getId());
|
||||
history.setVersion(config.getVersion());
|
||||
history.setBrandConfig(config.getBrandConfig());
|
||||
history.setLayoutConfig(config.getLayoutConfig());
|
||||
history.setTemplateId(config.getTemplateId());
|
||||
|
||||
uiConfigHistoryRepository.save(history);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板详情
|
||||
*/
|
||||
public UiTemplate getTemplateDetail(Long templateId) {
|
||||
return uiTemplateRepository.findById(templateId)
|
||||
.orElseThrow(() -> new BusinessException("模板不存在"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**配置历史Service**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ConfigHistoryService {
|
||||
|
||||
@Autowired
|
||||
private UiConfigHistoryRepository uiConfigHistoryRepository;
|
||||
|
||||
@Autowired
|
||||
private TenantUiConfigRepository tenantUiConfigRepository;
|
||||
|
||||
/**
|
||||
* 获取配置历史列表
|
||||
*/
|
||||
public List<UiConfigHistory> getConfigHistory(Long tenantId) {
|
||||
return uiConfigHistoryRepository.findByTenantIdOrderByCreatedAtDesc(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚到历史版本
|
||||
*/
|
||||
@Transactional
|
||||
public void rollbackToVersion(Long tenantId, Long historyId, Long userId) {
|
||||
UiConfigHistory history = uiConfigHistoryRepository.findById(historyId)
|
||||
.orElseThrow(() -> new BusinessException("历史版本不存在"));
|
||||
|
||||
if (!history.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("无权访问该历史版本");
|
||||
}
|
||||
|
||||
TenantUiConfig config = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
config.setBrandConfig(history.getBrandConfig());
|
||||
config.setLayoutConfig(history.getLayoutConfig());
|
||||
config.setTemplateId(history.getTemplateId());
|
||||
config.setVersion(config.getVersion() + 1);
|
||||
config.setUpdatedBy(userId);
|
||||
|
||||
tenantUiConfigRepository.save(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比配置
|
||||
*/
|
||||
public Map<String, Object> compareConfigs(Long tenantId, Long historyId) {
|
||||
TenantUiConfig currentConfig = tenantUiConfigRepository.findActiveByTenantId(tenantId)
|
||||
.orElseThrow(() -> new BusinessException("租户配置不存在"));
|
||||
|
||||
UiConfigHistory historyConfig = uiConfigHistoryRepository.findById(historyId)
|
||||
.orElseThrow(() -> new BusinessException("历史版本不存在"));
|
||||
|
||||
Map<String, Object> diff = new HashMap<>();
|
||||
diff.put("brandConfigDiff", compareJsonObjects(
|
||||
currentConfig.getBrandConfig(),
|
||||
historyConfig.getBrandConfig()
|
||||
));
|
||||
diff.put("layoutConfigDiff", compareJsonObjects(
|
||||
currentConfig.getLayoutConfig(),
|
||||
historyConfig.getLayoutConfig()
|
||||
));
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较JSON对象
|
||||
*/
|
||||
private Map<String, Object> compareJsonObjects(JsonObject obj1, JsonObject obj2) {
|
||||
Map<String, Object> diff = new HashMap<>();
|
||||
|
||||
Set<String> allKeys = new HashSet<>();
|
||||
obj1.keySet().forEach(allKeys::add);
|
||||
obj2.keySet().forEach(allKeys::add);
|
||||
|
||||
for (String key : allKeys) {
|
||||
if (!obj1.has(key)) {
|
||||
diff.put(key, "新增: " + obj2.get(key));
|
||||
} else if (!obj2.has(key)) {
|
||||
diff.put(key, "删除: " + obj1.get(key));
|
||||
} else if (!obj1.get(key).equals(obj2.get(key))) {
|
||||
diff.put(key, "修改: " + obj1.get(key) + " -> " + obj2.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、API设计
|
||||
|
||||
### 3.1 会员模块API
|
||||
@@ -1028,6 +1495,273 @@ Response:
|
||||
|
||||
---
|
||||
|
||||
### 3.5 UI模版定制模块API
|
||||
|
||||
#### 3.5.1 上传Logo
|
||||
|
||||
```
|
||||
POST /api/v1/ui-config/logo
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
Request:
|
||||
{
|
||||
"file": <binary>,
|
||||
"tenantId": 1
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "上传成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"resourceType": "logo",
|
||||
"resourceName": "logo.png",
|
||||
"filePath": "/uploads/logo/1/logo.png",
|
||||
"fileSize": 102400,
|
||||
"fileType": "image/png",
|
||||
"width": 200,
|
||||
"height": 200,
|
||||
"createdAt": "2026-03-07T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.2 设置品牌颜色
|
||||
|
||||
```
|
||||
POST /api/v1/ui-config/brand/color
|
||||
|
||||
Request:
|
||||
{
|
||||
"tenantId": 1,
|
||||
"colorType": "primaryColor",
|
||||
"color": "#FF5733"
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "设置成功",
|
||||
"data": {
|
||||
"primaryColor": "#FF5733",
|
||||
"secondaryColor": "#FFC300",
|
||||
"updatedAt": "2026-03-07T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.3 更新模块顺序
|
||||
|
||||
```
|
||||
POST /api/v1/ui-config/layout/module-order
|
||||
|
||||
Request:
|
||||
{
|
||||
"tenantId": 1,
|
||||
"moduleOrder": ["dashboard", "member", "booking", "checkin", "statistics"]
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": {
|
||||
"moduleOrder": ["dashboard", "member", "booking", "checkin", "statistics"],
|
||||
"updatedAt": "2026-03-07T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.4 隐藏/显示模块
|
||||
|
||||
```
|
||||
POST /api/v1/ui-config/layout/module-visibility
|
||||
|
||||
Request:
|
||||
{
|
||||
"tenantId": 1,
|
||||
"moduleCode": "statistics",
|
||||
"visible": false
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": {
|
||||
"hiddenModules": ["statistics"],
|
||||
"updatedAt": "2026-03-07T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.5 获取可用模板列表
|
||||
|
||||
```
|
||||
GET /api/v1/ui-config/templates
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "简约风格",
|
||||
"code": "simple",
|
||||
"type": "简约",
|
||||
"description": "简洁清爽的设计风格",
|
||||
"thumbnail": "/templates/simple/thumbnail.png",
|
||||
"previewImage": "/templates/simple/preview.png",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "运动风格",
|
||||
"code": "sport",
|
||||
"type": "运动",
|
||||
"description": "活力四射的运动风格",
|
||||
"thumbnail": "/templates/sport/thumbnail.png",
|
||||
"previewImage": "/templates/sport/preview.png",
|
||||
"sortOrder": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.6 应用模板
|
||||
|
||||
```
|
||||
POST /api/v1/ui-config/template/apply
|
||||
|
||||
Request:
|
||||
{
|
||||
"tenantId": 1,
|
||||
"templateId": 1
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "应用成功",
|
||||
"data": {
|
||||
"templateId": 1,
|
||||
"templateName": "简约风格",
|
||||
"appliedAt": "2026-03-07T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.7 获取配置历史
|
||||
|
||||
```
|
||||
GET /api/v1/ui-config/history?tenantId=1
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"version": 1,
|
||||
"templateId": 1,
|
||||
"changeReason": "应用简约风格模板",
|
||||
"createdAt": "2026-03-07T10:00:00Z",
|
||||
"createdBy": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"version": 2,
|
||||
"templateId": 2,
|
||||
"changeReason": "切换到运动风格模板",
|
||||
"createdAt": "2026-03-07T11:00:00Z",
|
||||
"createdBy": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.8 回滚到历史版本
|
||||
|
||||
```
|
||||
POST /api/v1/ui-config/history/rollback
|
||||
|
||||
Request:
|
||||
{
|
||||
"tenantId": 1,
|
||||
"historyId": 1
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "回滚成功",
|
||||
"data": {
|
||||
"version": 3,
|
||||
"rolledBackFrom": 1,
|
||||
"rolledBackAt": "2026-03-07T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.9 对比配置
|
||||
|
||||
```
|
||||
GET /api/v1/ui-config/history/compare?tenantId=1&historyId=1
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "查询成功",
|
||||
"data": {
|
||||
"brandConfigDiff": {
|
||||
"primaryColor": "修改: #FF5733 -> #FFC300",
|
||||
"logo": "删除: /uploads/logo/1/logo.png"
|
||||
},
|
||||
"layoutConfigDiff": {
|
||||
"moduleOrder": "修改: [\"dashboard\", \"member\"] -> [\"member\", \"dashboard\"]",
|
||||
"homeLayoutType": "新增: card"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.10 获取当前配置
|
||||
|
||||
```
|
||||
GET /api/v1/ui-config/current?tenantId=1
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "查询成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"tenantId": 1,
|
||||
"version": 3,
|
||||
"brandConfig": {
|
||||
"logo": "/uploads/logo/1/logo.png",
|
||||
"primaryColor": "#FF5733",
|
||||
"secondaryColor": "#FFC300",
|
||||
"brandName": "我的健身房",
|
||||
"slogan": "健康生活,从现在开始"
|
||||
},
|
||||
"layoutConfig": {
|
||||
"moduleOrder": ["dashboard", "member", "booking", "checkin"],
|
||||
"hiddenModules": ["statistics"],
|
||||
"homeLayoutType": "card"
|
||||
},
|
||||
"templateId": 1,
|
||||
"isActive": true,
|
||||
"updatedAt": "2026-03-07T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、缓存策略
|
||||
|
||||
### 4.1 缓存设计
|
||||
@@ -1319,7 +2053,7 @@ public class GlobalExceptionHandler {
|
||||
| 系统指标 | 磁盘使用率 | ≤ 80% |
|
||||
| 应用指标 | API响应时间 | ≤ 500ms |
|
||||
| 应用指标 | 错误率 | ≤ 1% |
|
||||
| 应用指标 | 并发数 | ≤ 100 |
|
||||
| 应用指标 | 并发用户数 | ≤ 100 |
|
||||
|
||||
---
|
||||
|
||||
@@ -1340,7 +2074,6 @@ public class GlobalExceptionHandler {
|
||||
|
||||
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
|
||||
- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001
|
||||
- 《健身房管理系统详细设计文档》 GYM-LLD-000
|
||||
- Spring Boot 3 官方文档
|
||||
- R2DBC 规范文档
|
||||
- PostgreSQL 官方文档
|
||||
|
||||
@@ -275,6 +275,137 @@
|
||||
|
||||
---
|
||||
|
||||
### 2.6 UI模版定制模块
|
||||
|
||||
#### 2.6.1 品牌定制
|
||||
|
||||
**功能描述**:租户通过可视化配置器定制品牌元素,包括Logo、颜色、背景图等。
|
||||
|
||||
**用户故事**:作为一个租户,我希望能够上传自己的Logo和设置品牌颜色,以便在系统中展示我的品牌特色。
|
||||
|
||||
**功能点**:
|
||||
- Logo上传(支持拖拽上传、自动裁剪、多尺寸缩略图)
|
||||
- 品牌主色调设置(颜色选择器、预设色板)
|
||||
- 品牌辅助色设置
|
||||
- 背景图上传(支持轮播背景)
|
||||
- 品牌名称和Slogan设置
|
||||
- 实时预览所有品牌元素
|
||||
|
||||
**业务规则**:
|
||||
- Logo支持PNG/JPG格式,限制2MB以内
|
||||
- 颜色支持RGB和HEX格式
|
||||
- 品牌元素应用范围包括小程序和管理后台
|
||||
- 配置变更实时生效,无需重新部署
|
||||
|
||||
**验收标准**:
|
||||
- Logo上传成功率 ≥ 95%
|
||||
- 实时预览响应时间 ≤ 200ms
|
||||
- 品牌元素应用一致性 100%
|
||||
|
||||
#### 2.6.2 布局调整
|
||||
|
||||
**功能描述**:租户通过拖拽式界面调整页面模块的显示顺序和布局结构。
|
||||
|
||||
**用户故事**:作为一个租户,我希望能够调整页面的模块顺序和隐藏不需要的功能,以便优化用户体验。
|
||||
|
||||
**功能点**:
|
||||
- 模块顺序调整(拖拽排序)
|
||||
- 模块隐藏/显示开关
|
||||
- 首页布局类型选择(卡片式、列表式、轮播式)
|
||||
- 导航菜单自定义(添加/编辑/删除菜单项)
|
||||
- 模块分组管理
|
||||
- 批量操作(全选、反选、批量隐藏)
|
||||
- 布局调整撤销/重做
|
||||
|
||||
**业务规则**:
|
||||
- 模块顺序调整支持跨区域移动
|
||||
- 隐藏的模块不显示但数据保留
|
||||
- 布局调整按角色区分(店长、前台、会员)
|
||||
- 布局变更自动保存到配置历史
|
||||
|
||||
**验收标准**:
|
||||
- 拖拽操作流畅度 ≥ 90%
|
||||
- 布局变更响应时间 ≤ 300ms
|
||||
- 模块隐藏成功率 100%
|
||||
|
||||
#### 2.6.3 预设模板
|
||||
|
||||
**功能描述**:系统提供3-5个精心设计的预设模板,租户可以直接选择并应用。
|
||||
|
||||
**用户故事**:作为一个租户,我希望能够从预设模板中选择适合我的模板,快速完成UI定制。
|
||||
|
||||
**功能点**:
|
||||
- 模板预览(缩略图、大图预览)
|
||||
- 模板类型筛选(简约、运动、科技、高端)
|
||||
- 一键应用模板
|
||||
- 模板收藏功能
|
||||
- 模板对比功能(并排对比、差异高亮)
|
||||
- 模板应用前确认对话框
|
||||
- 模板预览模式(正式应用前预览效果)
|
||||
|
||||
**业务规则**:
|
||||
- 模板应用后保留租户已有的品牌配置
|
||||
- 模板支持版本控制和灰度发布
|
||||
- 模板切换支持配置合并
|
||||
- 禁用的模板不可选择
|
||||
|
||||
**验收标准**:
|
||||
- 模板加载成功率 ≥ 98%
|
||||
- 模板应用成功率 ≥ 95%
|
||||
- 模板切换响应时间 ≤ 500ms
|
||||
|
||||
#### 2.6.4 配置历史
|
||||
|
||||
**功能描述**:记录租户的配置变更历史,支持配置回滚和对比。
|
||||
|
||||
**用户故事**:作为一个租户,我希望能够查看配置变更历史,并在需要时回滚到之前的配置。
|
||||
|
||||
**功能点**:
|
||||
- 配置历史列表查看
|
||||
- 配置版本对比(新旧配置差异)
|
||||
- 配置回滚到历史版本
|
||||
- 配置导出(JSON文件)
|
||||
- 配置导入(从JSON文件恢复)
|
||||
- 变更原因记录
|
||||
|
||||
**业务规则**:
|
||||
- 每次配置变更自动生成新版本号
|
||||
- 配置历史保留90天
|
||||
- 回滚操作需要确认
|
||||
- 配置对比高亮显示差异
|
||||
|
||||
**验收标准**:
|
||||
- 配置保存成功率 ≥ 99%
|
||||
- 配置回滚成功率 ≥ 98%
|
||||
- 配置对比准确性 100%
|
||||
|
||||
#### 2.6.5 可视化配置器
|
||||
|
||||
**功能描述**:提供直观的可视化配置界面,降低租户定制UI的技术门槛。
|
||||
|
||||
**用户故事**:作为一个租户,我希望通过可视化的拖拽界面来定制UI,而不需要编写代码。
|
||||
|
||||
**功能点**:
|
||||
- 三区域布局(品牌配置区、布局配置区、模板选择区)
|
||||
- 拖拽式模块排序
|
||||
- 实时预览(支持多设备尺寸切换)
|
||||
- 智能提示(颜色搭配建议、Logo尺寸建议、模板推荐)
|
||||
- 快捷操作(一键重置、一键预览、一键保存、一键发布)
|
||||
- 配置导出/导入
|
||||
|
||||
**业务规则**:
|
||||
- 所有配置变更实时反映在预览区
|
||||
- 预览区模拟真实页面结构
|
||||
- 拖拽操作提供视觉反馈
|
||||
- 配置器支持键盘快捷键
|
||||
|
||||
**验收标准**:
|
||||
- 配置器加载时间 ≤ 1秒
|
||||
- 实时预览延迟 ≤ 100ms
|
||||
- 拖拽操作流畅度 ≥ 95%
|
||||
|
||||
---
|
||||
|
||||
## 三、非功能需求
|
||||
|
||||
### 3.1 性能需求
|
||||
@@ -381,6 +512,5 @@
|
||||
|
||||
### 7.2 参考文档
|
||||
|
||||
- 《健身房管理系统产品设计文档》 GYM-PRD-001
|
||||
- 《健身房管理系统业务概要设计文档》 GYM-HLD-001
|
||||
- 《健身房管理系统详细设计文档》 GYM-LLD-000
|
||||
- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001
|
||||
- 《健身房管理系统基础版详细设计文档》 GYM-LLD-BASIC-001
|
||||
|
||||
Reference in New Issue
Block a user