Files
happy-life-star/docs/superpowers/plans/2026-06-27-sms-login-config.md
T

1183 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 短信登录开关与通用系统配置 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:短信登录开关与通用系统配置功能完成"
```