1183 lines
29 KiB
Markdown
1183 lines
29 KiB
Markdown
# 短信登录开关与通用系统配置 Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 新建通用系统配置表和管理后台设置页面,通过「短信登录开关」控制小程序登录页是否显示手机号短信登录入口。
|
||
|
||
**Architecture:** 新建 `t_system_config` key-value 配置表,后端提供管理端 CRUD 接口和公开查询接口,管理后台新增「系统设置」页面动态渲染配置项,小程序登录页调用公开接口获取登录方式配置并条件渲染。
|
||
|
||
**Tech Stack:** Spring Boot 2.7 + MyBatis-Plus + Redis / Vue 3 + Element Plus / UniApp (Vue 3)
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-06-27-sms-login-config-design.md`
|
||
|
||
---
|
||
|
||
### Task 1: 数据库建表和初始数据
|
||
|
||
**Files:**
|
||
- Execute: SQL on MySQL database
|
||
|
||
- [ ] **Step 1: 执行建表 SQL**
|
||
|
||
```sql
|
||
CREATE TABLE t_system_config (
|
||
id VARCHAR(64) PRIMARY KEY,
|
||
config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置唯一标识',
|
||
config_value VARCHAR(500) NOT NULL DEFAULT '' COMMENT '配置值',
|
||
value_type VARCHAR(20) NOT NULL DEFAULT 'string' COMMENT '值类型: boolean/string/number/json',
|
||
config_group VARCHAR(50) NOT NULL DEFAULT 'system' COMMENT '配置分组: system/login/notification',
|
||
config_name VARCHAR(100) NOT NULL DEFAULT '' COMMENT '显示名称',
|
||
description VARCHAR(300) DEFAULT '' COMMENT '配置说明',
|
||
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序号',
|
||
is_visible TINYINT NOT NULL DEFAULT 1 COMMENT '是否在管理后台可见: 0-隐藏, 1-显示',
|
||
create_by VARCHAR(64) DEFAULT NULL,
|
||
create_time DATETIME DEFAULT NULL,
|
||
update_by VARCHAR(64) DEFAULT NULL,
|
||
update_time DATETIME DEFAULT NULL,
|
||
is_deleted TINYINT NOT NULL DEFAULT 0
|
||
) COMMENT = '系统配置表';
|
||
```
|
||
|
||
- [ ] **Step 2: 插入初始数据(短信登录开关)**
|
||
|
||
```sql
|
||
INSERT INTO t_system_config (id, config_key, config_value, value_type, config_group, config_name, description, sort_order, is_visible, create_time, update_time, is_deleted)
|
||
VALUES (
|
||
REPLACE(UUID(), '-', ''),
|
||
'sms_login_enabled',
|
||
'false',
|
||
'boolean',
|
||
'login',
|
||
'短信登录',
|
||
'是否启用手机号+短信验证码登录方式',
|
||
1,
|
||
1,
|
||
NOW(),
|
||
NOW(),
|
||
0
|
||
);
|
||
```
|
||
|
||
- [ ] **Step 3: 验证数据**
|
||
|
||
```sql
|
||
SELECT * FROM t_system_config WHERE is_deleted = 0;
|
||
```
|
||
|
||
Expected: 一行数据,`config_key = 'sms_login_enabled'`,`config_value = 'false'`
|
||
|
||
---
|
||
|
||
### Task 2: 后端 Entity — SystemConfig.java
|
||
|
||
**Files:**
|
||
- Create: `server/src/main/java/com/emotion/entity/SystemConfig.java`
|
||
|
||
- [ ] **Step 1: 创建 SystemConfig 实体**
|
||
|
||
```java
|
||
package com.emotion.entity;
|
||
|
||
import com.baomidou.mybatisplus.annotation.*;
|
||
import com.emotion.common.BaseEntity;
|
||
import lombok.experimental.SuperBuilder;
|
||
import lombok.NoArgsConstructor;
|
||
import lombok.AllArgsConstructor;
|
||
import lombok.Data;
|
||
import lombok.EqualsAndHashCode;
|
||
|
||
/**
|
||
* 系统配置实体
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@Data
|
||
@EqualsAndHashCode(callSuper = true)
|
||
@SuperBuilder
|
||
@NoArgsConstructor
|
||
@AllArgsConstructor
|
||
@TableName("t_system_config")
|
||
public class SystemConfig extends BaseEntity {
|
||
|
||
/**
|
||
* 配置唯一标识
|
||
*/
|
||
@TableField("config_key")
|
||
private String configKey;
|
||
|
||
/**
|
||
* 配置值
|
||
*/
|
||
@TableField("config_value")
|
||
private String configValue;
|
||
|
||
/**
|
||
* 值类型: boolean/string/number/json
|
||
*/
|
||
@TableField("value_type")
|
||
private String valueType;
|
||
|
||
/**
|
||
* 配置分组: system/login/notification
|
||
*/
|
||
@TableField("config_group")
|
||
private String configGroup;
|
||
|
||
/**
|
||
* 显示名称
|
||
*/
|
||
@TableField("config_name")
|
||
private String configName;
|
||
|
||
/**
|
||
* 配置说明
|
||
*/
|
||
@TableField("description")
|
||
private String description;
|
||
|
||
/**
|
||
* 排序号
|
||
*/
|
||
@TableField("sort_order")
|
||
private Integer sortOrder;
|
||
|
||
/**
|
||
* 是否在管理后台可见: 0-隐藏, 1-显示
|
||
*/
|
||
@TableField("is_visible")
|
||
private Integer isVisible;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/entity/SystemConfig.java
|
||
git commit -m "feat:新增系统配置实体类"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 后端 Mapper — SystemConfigMapper.java
|
||
|
||
**Files:**
|
||
- Create: `server/src/main/java/com/emotion/mapper/SystemConfigMapper.java`
|
||
|
||
- [ ] **Step 1: 创建 SystemConfigMapper**
|
||
|
||
```java
|
||
package com.emotion.mapper;
|
||
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import com.emotion.entity.SystemConfig;
|
||
import org.apache.ibatis.annotations.Mapper;
|
||
|
||
/**
|
||
* 系统配置Mapper接口
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@Mapper
|
||
public interface SystemConfigMapper extends BaseMapper<SystemConfig> {
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/mapper/SystemConfigMapper.java
|
||
git commit -m "feat:新增系统配置Mapper"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 后端 DTOs
|
||
|
||
**Files:**
|
||
- Create: `server/src/main/java/com/emotion/dto/request/SystemConfigUpdateRequest.java`
|
||
- Create: `server/src/main/java/com/emotion/dto/response/SystemConfigResponse.java`
|
||
- Create: `server/src/main/java/com/emotion/dto/response/LoginConfigResponse.java`
|
||
|
||
- [ ] **Step 1: 创建 SystemConfigUpdateRequest**
|
||
|
||
```java
|
||
package com.emotion.dto.request;
|
||
|
||
import lombok.Data;
|
||
|
||
import javax.validation.constraints.NotBlank;
|
||
|
||
/**
|
||
* 系统配置更新请求
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@Data
|
||
public class SystemConfigUpdateRequest {
|
||
|
||
/**
|
||
* 配置唯一标识
|
||
*/
|
||
@NotBlank(message = "配置标识不能为空")
|
||
private String configKey;
|
||
|
||
/**
|
||
* 配置值
|
||
*/
|
||
@NotBlank(message = "配置值不能为空")
|
||
private String configValue;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 SystemConfigResponse**
|
||
|
||
```java
|
||
package com.emotion.dto.response;
|
||
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 系统配置响应
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@Data
|
||
public class SystemConfigResponse {
|
||
|
||
/**
|
||
* 配置唯一标识
|
||
*/
|
||
private String configKey;
|
||
|
||
/**
|
||
* 配置值
|
||
*/
|
||
private String configValue;
|
||
|
||
/**
|
||
* 值类型: boolean/string/number/json
|
||
*/
|
||
private String valueType;
|
||
|
||
/**
|
||
* 配置分组
|
||
*/
|
||
private String configGroup;
|
||
|
||
/**
|
||
* 显示名称
|
||
*/
|
||
private String configName;
|
||
|
||
/**
|
||
* 配置说明
|
||
*/
|
||
private String description;
|
||
|
||
/**
|
||
* 排序号
|
||
*/
|
||
private Integer sortOrder;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 LoginConfigResponse**
|
||
|
||
```java
|
||
package com.emotion.dto.response;
|
||
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 登录方式配置响应
|
||
* 返回给小程序,告知当前启用的登录方式
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@Data
|
||
public class LoginConfigResponse {
|
||
|
||
/**
|
||
* 是否启用微信登录
|
||
*/
|
||
private Boolean wechatLoginEnabled;
|
||
|
||
/**
|
||
* 是否启用短信登录
|
||
*/
|
||
private Boolean smsLoginEnabled;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/dto/request/SystemConfigUpdateRequest.java
|
||
git add server/src/main/java/com/emotion/dto/response/SystemConfigResponse.java
|
||
git add server/src/main/java/com/emotion/dto/response/LoginConfigResponse.java
|
||
git commit -m "feat:新增系统配置相关DTO"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 后端 Service 接口 — SystemConfigService.java
|
||
|
||
**Files:**
|
||
- Create: `server/src/main/java/com/emotion/service/SystemConfigService.java`
|
||
|
||
- [ ] **Step 1: 创建 SystemConfigService 接口**
|
||
|
||
```java
|
||
package com.emotion.service;
|
||
|
||
import com.baomidou.mybatisplus.extension.service.IService;
|
||
import com.emotion.dto.request.SystemConfigUpdateRequest;
|
||
import com.emotion.dto.response.LoginConfigResponse;
|
||
import com.emotion.dto.response.SystemConfigResponse;
|
||
import com.emotion.entity.SystemConfig;
|
||
|
||
import java.util.List;
|
||
|
||
/**
|
||
* 系统配置服务接口
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
public interface SystemConfigService extends IService<SystemConfig> {
|
||
|
||
/**
|
||
* 获取所有可见配置列表
|
||
*/
|
||
List<SystemConfigResponse> getVisibleConfigList();
|
||
|
||
/**
|
||
* 批量更新配置
|
||
*/
|
||
void batchUpdateConfigs(List<SystemConfigUpdateRequest> requests);
|
||
|
||
/**
|
||
* 获取配置值(带 Redis 缓存)
|
||
*
|
||
* @param configKey 配置标识
|
||
* @return 配置值字符串,不存在返回 null
|
||
*/
|
||
String getConfigValue(String configKey);
|
||
|
||
/**
|
||
* 获取登录方式配置
|
||
*/
|
||
LoginConfigResponse getLoginConfig();
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/service/SystemConfigService.java
|
||
git commit -m "feat:新增系统配置服务接口"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 后端 Service 实现 — SystemConfigServiceImpl.java
|
||
|
||
**Files:**
|
||
- Create: `server/src/main/java/com/emotion/service/impl/SystemConfigServiceImpl.java`
|
||
|
||
- [ ] **Step 1: 创建 SystemConfigServiceImpl**
|
||
|
||
```java
|
||
package com.emotion.service.impl;
|
||
|
||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
import com.emotion.dto.request.SystemConfigUpdateRequest;
|
||
import com.emotion.dto.response.LoginConfigResponse;
|
||
import com.emotion.dto.response.SystemConfigResponse;
|
||
import com.emotion.entity.SystemConfig;
|
||
import com.emotion.mapper.SystemConfigMapper;
|
||
import com.emotion.service.SystemConfigService;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.data.redis.core.RedisTemplate;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import java.util.List;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.stream.Collectors;
|
||
|
||
/**
|
||
* 系统配置服务实现类
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
public class SystemConfigServiceImpl extends ServiceImpl<SystemConfigMapper, SystemConfig> implements SystemConfigService {
|
||
|
||
@Autowired
|
||
private RedisTemplate<String, Object> redisTemplate;
|
||
|
||
private static final String CACHE_PREFIX = "system_config:";
|
||
private static final long CACHE_EXPIRE_HOURS = 24;
|
||
private static final String SMS_LOGIN_ENABLED_KEY = "sms_login_enabled";
|
||
|
||
@Override
|
||
public List<SystemConfigResponse> getVisibleConfigList() {
|
||
LambdaQueryWrapper<SystemConfig> wrapper = new LambdaQueryWrapper<>();
|
||
wrapper.eq(SystemConfig::getIsVisible, 1)
|
||
.orderByAsc(SystemConfig::getSortOrder);
|
||
List<SystemConfig> configs = list(wrapper);
|
||
return configs.stream().map(this::convertToResponse).collect(Collectors.toList());
|
||
}
|
||
|
||
@Override
|
||
public void batchUpdateConfigs(List<SystemConfigUpdateRequest> requests) {
|
||
for (SystemConfigUpdateRequest request : requests) {
|
||
LambdaUpdateWrapper<SystemConfig> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(SystemConfig::getConfigKey, request.getConfigKey())
|
||
.set(SystemConfig::getConfigValue, request.getConfigValue());
|
||
update(updateWrapper);
|
||
|
||
// 清除缓存
|
||
redisTemplate.delete(CACHE_PREFIX + request.getConfigKey());
|
||
log.info("更新系统配置: key={}, value={}", request.getConfigKey(), request.getConfigValue());
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public String getConfigValue(String configKey) {
|
||
// 先查 Redis 缓存
|
||
String cacheKey = CACHE_PREFIX + configKey;
|
||
String cached = (String) redisTemplate.opsForValue().get(cacheKey);
|
||
if (cached != null) {
|
||
return cached;
|
||
}
|
||
|
||
// 查数据库
|
||
LambdaQueryWrapper<SystemConfig> wrapper = new LambdaQueryWrapper<>();
|
||
wrapper.eq(SystemConfig::getConfigKey, configKey);
|
||
SystemConfig config = getOne(wrapper);
|
||
if (config == null) {
|
||
return null;
|
||
}
|
||
|
||
// 写入缓存
|
||
redisTemplate.opsForValue().set(cacheKey, config.getConfigValue(), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
||
return config.getConfigValue();
|
||
}
|
||
|
||
@Override
|
||
public LoginConfigResponse getLoginConfig() {
|
||
LoginConfigResponse response = new LoginConfigResponse();
|
||
response.setWechatLoginEnabled(true);
|
||
|
||
String smsEnabled = getConfigValue(SMS_LOGIN_ENABLED_KEY);
|
||
response.setSmsLoginEnabled("true".equalsIgnoreCase(smsEnabled));
|
||
|
||
return response;
|
||
}
|
||
|
||
/**
|
||
* 实体转响应对象
|
||
*/
|
||
private SystemConfigResponse convertToResponse(SystemConfig config) {
|
||
SystemConfigResponse response = new SystemConfigResponse();
|
||
response.setConfigKey(config.getConfigKey());
|
||
response.setConfigValue(config.getConfigValue());
|
||
response.setValueType(config.getValueType());
|
||
response.setConfigGroup(config.getConfigGroup());
|
||
response.setConfigName(config.getConfigName());
|
||
response.setDescription(config.getDescription());
|
||
response.setSortOrder(config.getSortOrder());
|
||
return response;
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/service/impl/SystemConfigServiceImpl.java
|
||
git commit -m "feat:新增系统配置服务实现(含Redis缓存)"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 7: 后端管理端 Controller — SystemConfigController.java
|
||
|
||
**Files:**
|
||
- Create: `server/src/main/java/com/emotion/controller/SystemConfigController.java`
|
||
|
||
- [ ] **Step 1: 创建 SystemConfigController**
|
||
|
||
此 controller 的路径为 `/admin/systemConfig/**`,自动被 `AdminAuthInterceptor` 保护,不需要登录用户 JWT。
|
||
|
||
```java
|
||
package com.emotion.controller;
|
||
|
||
import com.emotion.common.Result;
|
||
import com.emotion.dto.request.SystemConfigUpdateRequest;
|
||
import com.emotion.dto.response.SystemConfigResponse;
|
||
import com.emotion.service.SystemConfigService;
|
||
import io.swagger.v3.oas.annotations.Operation;
|
||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.validation.annotation.Validated;
|
||
import org.springframework.web.bind.annotation.*;
|
||
|
||
import java.util.List;
|
||
|
||
/**
|
||
* 系统配置管理控制器(管理端)
|
||
*
|
||
* @author system
|
||
* @date 2026-06-27
|
||
*/
|
||
@RestController
|
||
@RequestMapping("/admin/systemConfig")
|
||
@Tag(name = "系统配置管理", description = "系统配置的查询和更新功能")
|
||
public class SystemConfigController {
|
||
|
||
@Autowired
|
||
private SystemConfigService systemConfigService;
|
||
|
||
/**
|
||
* 获取所有可见配置列表
|
||
*/
|
||
@GetMapping("/list")
|
||
@Operation(summary = "获取配置列表", description = "获取所有可见的系统配置列表")
|
||
public Result<List<SystemConfigResponse>> list() {
|
||
List<SystemConfigResponse> configs = systemConfigService.getVisibleConfigList();
|
||
return Result.success(configs);
|
||
}
|
||
|
||
/**
|
||
* 批量更新配置
|
||
*/
|
||
@PutMapping("/update")
|
||
@Operation(summary = "批量更新配置", description = "批量更新系统配置值")
|
||
public Result<Void> update(@RequestBody @Validated List<SystemConfigUpdateRequest> requests) {
|
||
systemConfigService.batchUpdateConfigs(requests);
|
||
return Result.success("更新成功", null);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/controller/SystemConfigController.java
|
||
git commit -m "feat:新增系统配置管理端Controller"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 后端公开接口 — AuthController 新增 loginConfig
|
||
|
||
**Files:**
|
||
- Modify: `server/src/main/java/com/emotion/controller/AuthController.java`
|
||
|
||
- [ ] **Step 1: 在 AuthController 中添加 loginConfig 接口**
|
||
|
||
在 `AuthController.java` 中添加 `SystemService` 注入和新接口方法。
|
||
|
||
添加 import:
|
||
```java
|
||
import com.emotion.dto.response.LoginConfigResponse;
|
||
```
|
||
|
||
添加 service 注入(在已有的 `@Autowired` 字段后面):
|
||
```java
|
||
@Autowired
|
||
private SystemConfigService systemConfigService;
|
||
```
|
||
|
||
添加新接口方法(放在 `wechatLogin` 方法后面):
|
||
```java
|
||
/**
|
||
* 获取登录方式配置
|
||
*/
|
||
@GetMapping("/loginConfig")
|
||
@Operation(summary = "获取登录方式配置", description = "返回当前启用的登录方式,供小程序登录页动态渲染")
|
||
public Result<LoginConfigResponse> getLoginConfig() {
|
||
LoginConfigResponse response = systemConfigService.getLoginConfig();
|
||
return Result.success(response);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 在 WebMvcConfig 白名单中放行**
|
||
|
||
修改 `server/src/main/java/com/emotion/config/WebMvcConfig.java`,在 JWT 拦截器的 `excludePathPatterns` 中添加一行:
|
||
|
||
```java
|
||
"/auth/loginConfig", // 登录方式配置接口(免登录)
|
||
```
|
||
|
||
添加位置:在 `"/auth/wechat/login"` 行的上方或下方。
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add server/src/main/java/com/emotion/controller/AuthController.java
|
||
git add server/src/main/java/com/emotion/config/WebMvcConfig.java
|
||
git commit -m "feat:新增公开接口 /auth/loginConfig 并放行白名单"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 9: 后端编译验证
|
||
|
||
- [ ] **Step 1: 编译后端**
|
||
|
||
```bash
|
||
cd server
|
||
mvn clean install -DskipTests
|
||
```
|
||
|
||
Expected: `BUILD SUCCESS`
|
||
|
||
- [ ] **Step 2: 如果有编译错误,修复后重新编译直到通过**
|
||
|
||
- [ ] **Step 3: Commit(如果有修复)**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "fix:修复系统配置编译错误"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 10: 管理后台 — API 和路由配置
|
||
|
||
**Files:**
|
||
- Create: `web-admin/src/api/systemConfig.ts`
|
||
- Modify: `web-admin/src/router/index.ts`
|
||
- Modify: `web-admin/src/config/menu.ts`
|
||
|
||
- [ ] **Step 1: 创建 systemConfig API 文件**
|
||
|
||
`web-admin/src/api/systemConfig.ts`:
|
||
|
||
```ts
|
||
import request from '@/utils/request'
|
||
|
||
// 获取所有可见配置列表
|
||
export function getSystemConfigList() {
|
||
return request({
|
||
url: '/admin/systemConfig/list',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
// 批量更新配置
|
||
export function updateSystemConfig(data: { configKey: string; configValue: string }[]) {
|
||
return request({
|
||
url: '/admin/systemConfig/update',
|
||
method: 'put',
|
||
data
|
||
})
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 添加路由配置**
|
||
|
||
在 `web-admin/src/router/index.ts` 中,在 `dictionary` 路由块之前添加:
|
||
|
||
```ts
|
||
{
|
||
path: '/system',
|
||
component: Layout,
|
||
redirect: '/system/settings',
|
||
meta: { title: '系统设置', icon: 'Setting' },
|
||
children: [
|
||
{
|
||
path: 'settings',
|
||
name: 'SystemSettings',
|
||
component: () => import('@/views/system/SystemSettings.vue'),
|
||
meta: { title: '基础设置' }
|
||
}
|
||
]
|
||
},
|
||
```
|
||
|
||
- [ ] **Step 3: 添加菜单配置**
|
||
|
||
在 `web-admin/src/config/menu.ts` 的 `menuConfig` 数组中,在 `AI配置管理` 项之前添加:
|
||
|
||
```ts
|
||
{
|
||
path: '/system',
|
||
title: '系统设置',
|
||
icon: 'Setting'
|
||
},
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add web-admin/src/api/systemConfig.ts
|
||
git add web-admin/src/router/index.ts
|
||
git add web-admin/src/config/menu.ts
|
||
git commit -m "feat:管理后台新增系统设置路由和菜单"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 11: 管理后台 — SystemSettings.vue 页面
|
||
|
||
**Files:**
|
||
- Create: `web-admin/src/views/system/SystemSettings.vue`
|
||
|
||
- [ ] **Step 1: 创建 SystemSettings 页面**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="system-settings">
|
||
<div class="page-header">
|
||
<h2>基础设置</h2>
|
||
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
||
</div>
|
||
|
||
<el-card v-if="loading" v-loading="true" class="loading-card" />
|
||
|
||
<div v-else class="config-groups">
|
||
<el-card v-for="(items, group) in groupedConfigs" :key="group" class="config-group">
|
||
<template #header>
|
||
<span class="group-title">{{ groupLabels[group] || group }}</span>
|
||
</template>
|
||
|
||
<div v-for="item in items" :key="item.configKey" class="config-item">
|
||
<div class="config-label">
|
||
<span class="config-name">{{ item.configName }}</span>
|
||
<span class="config-desc">{{ item.description }}</span>
|
||
</div>
|
||
<div class="config-control">
|
||
<el-switch
|
||
v-if="item.valueType === 'boolean'"
|
||
v-model="editValues[item.configKey]"
|
||
:active-value="'true'"
|
||
:inactive-value="'false'"
|
||
/>
|
||
<el-input
|
||
v-else-if="item.valueType === 'string'"
|
||
v-model="editValues[item.configKey]"
|
||
style="width: 300px"
|
||
/>
|
||
<el-input-number
|
||
v-else-if="item.valueType === 'number'"
|
||
v-model="editValues[item.configKey]"
|
||
style="width: 200px"
|
||
/>
|
||
<span v-else>{{ editValues[item.configKey] }}</span>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-empty v-if="Object.keys(groupedConfigs).length === 0" description="暂无配置项" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { getSystemConfigList, updateSystemConfig } from '@/api/systemConfig'
|
||
|
||
interface ConfigItem {
|
||
configKey: string
|
||
configValue: string
|
||
valueType: string
|
||
configGroup: string
|
||
configName: string
|
||
description: string
|
||
sortOrder: number
|
||
}
|
||
|
||
const loading = ref(false)
|
||
const saving = ref(false)
|
||
const configs = ref<ConfigItem[]>([])
|
||
const editValues = ref<Record<string, string | boolean>>({})
|
||
|
||
const groupLabels: Record<string, string> = {
|
||
system: '系统配置',
|
||
login: '登录设置',
|
||
notification: '通知设置'
|
||
}
|
||
|
||
const groupedConfigs = computed(() => {
|
||
const groups: Record<string, ConfigItem[]> = {}
|
||
for (const item of configs.value) {
|
||
if (!groups[item.configGroup]) {
|
||
groups[item.configGroup] = []
|
||
}
|
||
groups[item.configGroup].push(item)
|
||
}
|
||
return groups
|
||
})
|
||
|
||
const fetchConfigs = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await getSystemConfigList()
|
||
configs.value = res.data || []
|
||
// 初始化编辑值
|
||
const values: Record<string, string | boolean> = {}
|
||
for (const item of configs.value) {
|
||
if (item.valueType === 'boolean') {
|
||
values[item.configKey] = item.configValue
|
||
} else {
|
||
values[item.configKey] = item.configValue
|
||
}
|
||
}
|
||
editValues.value = values
|
||
} catch (error) {
|
||
ElMessage.error('获取配置列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const handleSave = async () => {
|
||
saving.value = true
|
||
try {
|
||
const updates = configs.value.map(item => ({
|
||
configKey: item.configKey,
|
||
configValue: String(editValues.value[item.configKey])
|
||
}))
|
||
await updateSystemConfig(updates)
|
||
ElMessage.success('保存成功')
|
||
await fetchConfigs()
|
||
} catch (error) {
|
||
ElMessage.error('保存失败')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchConfigs()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.system-settings {
|
||
padding: 20px;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.page-header h2 {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.loading-card {
|
||
height: 200px;
|
||
}
|
||
|
||
.config-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.group-title {
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.config-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px 0;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.config-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.config-label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.config-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.config-desc {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
</style>
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add web-admin/src/views/system/SystemSettings.vue
|
||
git commit -m "feat:管理后台新增系统设置页面"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 12: 小程序 — 新增 getLoginConfig 接口
|
||
|
||
**Files:**
|
||
- Modify: `mini-program/src/services/auth.js`
|
||
|
||
- [ ] **Step 1: 在 auth.js 中添加 getLoginConfig 方法**
|
||
|
||
在 `mini-program/src/services/auth.js` 的 `getSmsCode` 方法之前,添加:
|
||
|
||
```js
|
||
/**
|
||
* 获取登录方式配置
|
||
* @returns {Promise<Object>} 登录方式配置
|
||
*/
|
||
export const getLoginConfig = async () => {
|
||
const response = await get('/auth/loginConfig')
|
||
return response
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 在底部的 default 导出对象中添加 `getLoginConfig`**
|
||
|
||
```js
|
||
export default {
|
||
getLoginConfig,
|
||
getSmsCode,
|
||
login,
|
||
wechatLogin,
|
||
logout,
|
||
refreshToken,
|
||
validateToken,
|
||
validateTokenStrict,
|
||
getCurrentUserInfo,
|
||
checkPhone
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add mini-program/src/services/auth.js
|
||
git commit -m "feat:小程序新增获取登录方式配置接口"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 13: 小程序 — 登录页条件渲染
|
||
|
||
**Files:**
|
||
- Modify: `mini-program/src/pages/login/index.vue`
|
||
|
||
- [ ] **Step 1: 修改 template — 用 v-if 控制 SMS 相关元素**
|
||
|
||
在 `mini-program/src/pages/login/index.vue` 中:
|
||
|
||
1. 在「微信一键登录」按钮的 `</view>` 之后,「分隔线」的 `<view class="login-divider">` 上添加 `v-if`:
|
||
|
||
将:
|
||
```html
|
||
<view class="login-divider">
|
||
<view></view>
|
||
<text>或使用手机号登录</text>
|
||
<view></view>
|
||
</view>
|
||
```
|
||
改为:
|
||
```html
|
||
<view class="login-divider" v-if="loginConfig.smsLoginEnabled">
|
||
<view></view>
|
||
<text>或使用手机号登录</text>
|
||
<view></view>
|
||
</view>
|
||
```
|
||
|
||
2. 在 `<view class="form">` 上添加 `v-if`:
|
||
|
||
将:
|
||
```html
|
||
<view class="form">
|
||
```
|
||
改为:
|
||
```html
|
||
<view class="form" v-if="loginConfig.smsLoginEnabled">
|
||
```
|
||
|
||
3. 在「开启旅程」按钮上添加 `v-if`:
|
||
|
||
将:
|
||
```html
|
||
<view
|
||
class="btn-primary login-btn"
|
||
:class="{ disabled: !canSubmit || loading }"
|
||
@click="handleLogin"
|
||
>
|
||
```
|
||
改为:
|
||
```html
|
||
<view
|
||
v-if="loginConfig.smsLoginEnabled"
|
||
class="btn-primary login-btn"
|
||
:class="{ disabled: !canSubmit || loading }"
|
||
@click="handleLogin"
|
||
>
|
||
```
|
||
|
||
- [ ] **Step 2: 修改 script — 添加 loginConfig 状态和接口调用**
|
||
|
||
1. 添加 import:在现有的 `import { getSmsCode } from '../../services/auth.js'` 行,改为:
|
||
|
||
```js
|
||
import { getSmsCode, getLoginConfig } from '../../services/auth.js'
|
||
```
|
||
|
||
2. 添加 `loginConfig` ref:在 `const isCountingDown = ref(false)` 行之后添加:
|
||
|
||
```js
|
||
const loginConfig = ref({ wechatLoginEnabled: true, smsLoginEnabled: false })
|
||
```
|
||
|
||
3. 在 `onMounted` 回调中添加获取登录配置的逻辑,将现有的 `onMounted` 改为:
|
||
|
||
```js
|
||
onMounted(async () => {
|
||
const windowInfo = uni.getWindowInfo()
|
||
statusBarHeight.value = windowInfo.statusBarHeight || 20
|
||
safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20
|
||
safeAreaBottom.value = windowInfo.safeAreaInsets?.bottom || 0
|
||
|
||
try {
|
||
const res = await getLoginConfig()
|
||
if (res.data) {
|
||
loginConfig.value = res.data
|
||
}
|
||
} catch (e) {
|
||
// 接口失败时默认只显示微信登录
|
||
loginConfig.value = { wechatLoginEnabled: true, smsLoginEnabled: false }
|
||
}
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add mini-program/src/pages/login/index.vue
|
||
git commit -m "feat:小程序登录页根据配置动态显示短信登录入口"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 14: 部署后端到服务器
|
||
|
||
- [ ] **Step 1: 部署后端**
|
||
|
||
```bash
|
||
cd G:\IdeaProjects\emotion-museun
|
||
python deploy.py backend
|
||
```
|
||
|
||
Expected: 部署成功,无报错
|
||
|
||
- [ ] **Step 2: 验证后端接口**
|
||
|
||
用 curl 或浏览器验证:
|
||
|
||
```bash
|
||
curl https://lifescript.happylifeos.com/api/auth/loginConfig
|
||
```
|
||
|
||
Expected 响应(短信默认关闭):
|
||
```json
|
||
{"code":200,"message":"操作成功","data":{"wechatLoginEnabled":true,"smsLoginEnabled":false},"timestamp":...}
|
||
```
|
||
|
||
---
|
||
|
||
### Task 15: 管理后台验收
|
||
|
||
- [ ] **Step 1: 打开管理后台**
|
||
|
||
访问 `https://lifescript.happylifeos.com/emotion-museum-admin/`
|
||
|
||
- [ ] **Step 2: 验证侧边栏出现「系统设置」菜单**
|
||
|
||
- [ ] **Step 3: 点击进入「基础设置」页面,确认显示「短信登录」开关**
|
||
|
||
- [ ] **Step 4: 确认开关默认为关闭状态**
|
||
|
||
- [ ] **Step 5: 打开开关并点击「保存」,确认保存成功**
|
||
|
||
- [ ] **Step 6: 再次调用 loginConfig 接口验证值已更新**
|
||
|
||
```bash
|
||
curl https://lifescript.happylifeos.com/api/auth/loginConfig
|
||
```
|
||
|
||
Expected: `smsLoginEnabled` 变为 `true`
|
||
|
||
- [ ] **Step 7: 将开关改回关闭状态(为小程序验收做准备)**
|
||
|
||
---
|
||
|
||
### Task 16: 小程序 H5 验收
|
||
|
||
- [ ] **Step 1: 启动小程序 H5 开发服务器**
|
||
|
||
```bash
|
||
cd mini-program
|
||
npm run dev:h5
|
||
```
|
||
|
||
- [ ] **Step 2: 浏览器打开 http://localhost:5284**
|
||
|
||
- [ ] **Step 3: 验证登录页只显示「微信一键登录」按钮,无手机号表单**
|
||
|
||
- [ ] **Step 4: 在管理后台打开短信登录开关**
|
||
|
||
- [ ] **Step 5: 刷新小程序 H5 登录页,验证出现手机号+验证码表单**
|
||
|
||
- [ ] **Step 6: 关闭开关,刷新登录页,验证手机号表单消失**
|
||
|
||
- [ ] **Step 7: 确认浏览器 Console 无任何报错**
|
||
|
||
- [ ] **Step 8: 将开关恢复为关闭状态**
|
||
|
||
---
|
||
|
||
### Task 17: 最终提交
|
||
|
||
- [ ] **Step 1: 确认所有代码已提交**
|
||
|
||
```bash
|
||
git status
|
||
```
|
||
|
||
Expected: working tree clean
|
||
|
||
- [ ] **Step 2: 如果没有全部提交,补充提交**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "feat:短信登录开关与通用系统配置功能完成"
|
||
```
|