# 短信登录开关与通用系统配置 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：短信登录开关与通用系统配置功能完成"
```
