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:
张翔
2026-03-07 16:59:32 +08:00
parent 559bfe56e3
commit 971d51cb36
3 changed files with 1011 additions and 98 deletions
+72 -22
View File
@@ -11,7 +11,7 @@
## 文档修订历史
| 版本 | 日期 | 作者 | 修订内容 |
| ---- | ---------- | ---- | ------------------ |
| ---- | ---------- | ---- | ---------------------- |
| v1.0 | 2026-03-04 | 张翔 | 创建基础版业务概要设计 |
---
@@ -44,7 +44,6 @@
### 1.4 参考文档
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
- 《健身房管理系统业务概要设计文档》 GYM-HLD-001
---
@@ -105,6 +104,12 @@
│ │ • 用户管理 • 角色权限管理 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ UI模版定制 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • 品牌定制 • 布局调整 • 预设模板 • 配置历史 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
@@ -137,7 +142,7 @@
#### 3.1.4 异常处理
| 异常场景 | 处理方式 |
|---------|---------|
| ------------ | ---------------- |
| 手机号已存在 | 提示用户直接登录 |
| 验证码错误 | 提示用户重新输入 |
| 验证码过期 | 提示用户重新获取 |
@@ -184,7 +189,7 @@
#### 3.2.4 异常处理
| 异常场景 | 处理方式 |
|---------|---------|
| -------------- | -------------------- |
| 课程已满 | 提示用户选择其他课程 |
| 会员卡权益不足 | 提示用户购买会员卡 |
| 预约时间过短 | 提示用户提前预约 |
@@ -216,7 +221,7 @@
#### 3.3.4 异常处理
| 异常场景 | 处理方式 |
|---------|---------|
| ---------- | ------------------ |
| 会员卡无效 | 提示用户购买会员卡 |
| 会员卡过期 | 提示用户续费 |
| 签到码无效 | 提示用户重新扫描 |
@@ -248,18 +253,52 @@
#### 3.4.4 异常处理
| 异常场景 | 处理方式 |
|---------|---------|
| -------- | -------------------- |
| 支付失败 | 提示用户重新支付 |
| 支付超时 | 提示用户重新发起支付 |
---
### 3.5 UI模版定制流程
#### 3.5.1 业务场景
租户通过管理后台的可视化配置器定制自己的UI,包括品牌元素、布局结构和预设模板。
#### 3.5.2 业务流程
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 租户登录 │ → │ 打开UI │ → │ 品牌定制 │ → │ 布局调整 │ → │ 配置保存 │
│ 管理后台 │ │ 定制器 │ │ │ │ │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
```
#### 3.5.3 业务规则
- 品牌元素应用范围包括小程序和管理后台
- 布局调整支持拖拽排序和模块隐藏
- 预设模板应用后保留品牌配置
- 配置变更实时生效,无需重新部署
- 配置变更自动记录到历史
#### 3.5.4 异常处理
| 异常场景 | 处理方式 |
| ------------ | -------------------- |
| Logo上传失败 | 提示用户重新上传 |
| 配置保存失败 | 提示用户检查配置格式 |
| 模板应用失败 | 提示用户调整品牌配置 |
| 配置回滚失败 | 提示用户选择其他版本 |
---
## 四、核心业务规则
### 4.1 会员管理规则
| 规则 | 描述 |
|------|------|
| ---------------- | ------------------------------------------------------ |
| 会员唯一性 | 手机号作为会员唯一标识 |
| 会员信息完整性 | 必填字段:手机号、姓名、性别 |
| 会员信息修改权限 | 会员只能编辑自己的基本信息,前台和店长可以编辑所有信息 |
@@ -267,7 +306,7 @@
### 4.2 会员卡管理规则
| 规则 | 描述 |
|------|------|
| -------------- | ------------------------------------ |
| 会员卡类型 | 支持时长卡、次卡、储值卡 |
| 会员卡有效期 | 时长卡有有效期,次卡和储值卡无有效期 |
| 会员卡到期提醒 | 到期前7天提醒 |
@@ -276,7 +315,7 @@
### 4.3 预约管理规则
| 规则 | 描述 |
|------|------|
| ---------------- | ------------------------------- |
| 预约时间限制 | 预约需在课程开始前至少30分钟 |
| 取消预约时间限制 | 取消预约需在课程开始前至少2小时 |
| 团课容量限制 | 每节课最多20人 |
@@ -285,7 +324,7 @@
### 4.4 签到管理规则
| 规则 | 描述 |
|------|------|
| ------------ | -------------------------- |
| 签到验证 | 签到需验证会员卡有效性 |
| 签到预约验证 | 签到需验证预约信息(如有) |
| 签到记录 | 签到成功后记录到店时间 |
@@ -293,11 +332,24 @@
### 4.5 数据统计规则
| 规则 | 描述 |
|------|------|
| ------------ | -------------------- |
| 数据保留期限 | 数据保留30天 |
| 统计维度 | 支持按日、周、月统计 |
| 数据导出 | 支持数据导出 |
### 4.6 UI模版定制规则
| 规则 | 描述 |
| ------------ | ------------------------------------------ |
| 品牌元素应用 | 品牌元素应用范围包括小程序和管理后台 |
| Logo格式限制 | Logo支持PNG/JPG格式,限制2MB以内 |
| 颜色格式限制 | 颜色支持RGB和HEX格式 |
| 布局调整权限 | 布局调整支持按角色区分(店长、前台、会员) |
| 模板应用规则 | 模板应用后保留租户已有的品牌配置 |
| 配置版本管理 | 每次配置变更自动生成新版本号 |
| 配置历史保留 | 配置历史保留90天 |
| 配置实时生效 | 配置变更实时生效,无需重新部署 |
---
## 五、业务场景
@@ -398,7 +450,7 @@
### 6.1 核心实体
| 实体 | 描述 |
|------|------|
| ------------------ | ---------------- |
| 会员(Member) | 健身房注册用户 |
| 会员卡(MemberCard) | 会员购买的权益卡 |
| 权益(Benefit) | 会员卡包含的权益 |
@@ -423,23 +475,22 @@
### 7.1 性能约束
| 指标 | 要求 |
|------|------|
| API响应时间 (P99) | 200-400ms |
| 并发用户 | 支持1000并发用户 |
| 吞吐量 (QPS) | 3000-5000 |
| 数据库查询 | 查询响应时间 ≤ 500ms |
| ----------------- | ----------------- |
| API响应时间 (P99) | ≤ 500ms |
| 并发用户 | 支持100并发用户 |
| 数据库查询 | 查询响应时间 ≤ 1s |
### 7.2 可用性约束
| 指标 | 要求 |
|------|------|
| ------------ | ------------- |
| 系统可用性 | SLA ≥ 99.9% |
| 故障恢复时间 | MTTR ≤ 30分钟 |
### 7.3 安全性约束
| 指标 | 要求 |
|------|------|
| -------- | -------------------- |
| 数据加密 | 敏感数据加密存储 |
| 访问控制 | 基于角色的访问控制 |
| 操作审计 | 关键操作记录审计日志 |
@@ -447,7 +498,7 @@
### 7.4 可扩展性约束
| 指标 | 要求 |
|------|------|
| -------- | -------------- |
| 会员数量 | 最多500人 |
| 门店数量 | 单门店 |
| 团课容量 | 每节课最多20人 |
@@ -460,7 +511,7 @@
### 8.1 术语定义
| 术语 | 定义 |
|------|------|
| ------ | ------------------------------------------ |
| 会员 | 在健身房注册的用户 |
| 会员卡 | 会员购买的权益卡,包括时长卡、次卡、储值卡 |
| 权益 | 会员卡包含的时长、次数、储值、等级等权益 |
@@ -471,4 +522,3 @@
### 8.2 参考文档
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
- 《健身房管理系统业务概要设计文档》 GYM-HLD-001
+736 -3
View File
@@ -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 官方文档
+133 -3
View File
@@ -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