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:
@@ -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 官方文档
|
||||
|
||||
Reference in New Issue
Block a user