ui
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
zpc 2026-03-25 11:54:32 +08:00
parent 9d4f9a0722
commit e5e63bd7f2
5 changed files with 694 additions and 34 deletions

View File

@ -0,0 +1 @@
{"specId": "a387593e-0072-4eb6-8dfc-5dbd1bcc6547", "workflowType": "requirements-first", "specType": "feature"}

View File

@ -0,0 +1,417 @@
# Design Document: 首页「更多」区域模块化配置
## Overview
将小程序首页「更多」区域从硬编码改为后台可配置化。复用现有 `home_navigations` 表,新增 `Position`(区域标识)和 `ActionType`(动作类型)两个字段,实现前端按区域动态加载、后台统一管理。
核心改动范围:
1. 数据库:`home_navigations` 表新增两列 + 数据迁移
2. 后端:小程序 API 增加 `position` 筛选参数DTO 增加新字段Admin API 同步扩展
3. 小程序前端:首页拆分为两次 API 调用position=1 和 position=2「更多」区域动态渲染 + QR 弹窗
4. 后台管理前端:导航管理表单增加 Position / ActionType 选择器
## Architecture
```mermaid
graph TD
subgraph 小程序前端
A[首页 index.vue]
A -->|position=1| B[Assessment_Section]
A -->|position=2| C[More_Section]
C -->|ActionType=2| D[QR_Popup 弹窗]
end
subgraph 小程序API - MiAssessment.Api
E[HomeController.GetNavigationList]
F[SystemController.GetContactInfo]
end
subgraph 核心服务 - MiAssessment.Core
G[HomeService.GetNavigationListAsync]
H[SystemService.GetContactInfoAsync]
end
subgraph 数据层 - MiAssessment.Model
I[HomeNavigation Entity]
J[MiAssessmentDbContext]
K[(Business DB: home_navigations)]
end
subgraph 后台管理 - Admin.Business
L[ContentController navigation/*]
M[ContentService]
N[AdminBusinessDbContext]
end
B --> E
C --> E
D --> F
E --> G --> J --> K
F --> H
L --> M --> N --> K
```
### 设计决策
1. **复用 `home_navigations` 表而非新建表**:两个区域的数据结构完全一致(名称、图标、链接、排序、状态),通过 Position 字段区分即可,避免表冗余。
2. **ActionType 取代原 Status=0/2 的"即将上线"语义**:原表用 Status=0 表示下线、Status=2 表示即将上线。新设计中 Status 简化为 0禁用和 1启用"即将上线"行为由 ActionType=3 承担,语义更清晰。
3. **API 向后兼容**`position` 参数可选,不传时返回所有启用记录,确保旧版小程序不会立即崩溃。
4. **复用现有 Popup 组件**QR 弹窗直接使用 `@/components/Popup/index.vue`,传入 `imageUrl` 即可,无需新建组件。
5. **二维码 URL 复用现有 `SystemService.GetContactInfoAsync()`**:该方法已从 `configs` 表读取 `service_qrcode` 配置项,前端只需在首页加载时额外调用一次。
## Components and Interfaces
### 数据库层变更
**home_navigations 表新增列:**
| 列名 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| Position | int | 1 | 区域标识1=专业测评2=更多 |
| ActionType | int | 1 | 动作类型1=跳转页面2=弹客服二维码3=即将上线 |
### 后端 - MiAssessment.Model实体 & DTO
**HomeNavigation 实体扩展**(两处:`MiAssessment.Model.Entities` 和 `MiAssessment.Admin.Business.Entities`
```csharp
/// <summary>
/// 区域标识1=专业测评区域2=更多区域
/// </summary>
public int Position { get; set; } = 1;
/// <summary>
/// 动作类型1=跳转页面2=弹客服二维码3=即将上线
/// </summary>
public int ActionType { get; set; } = 1;
```
**小程序 DTO 扩展**`MiAssessment.Model.Models.Home.HomeNavigationDto`
```csharp
/// <summary>
/// 区域标识
/// </summary>
public int Position { get; set; }
/// <summary>
/// 动作类型
/// </summary>
public int ActionType { get; set; }
```
### 后端 - MiAssessment.Core服务层
**IHomeService 接口变更:**
```csharp
/// <summary>
/// 获取首页导航入口列表
/// </summary>
/// <param name="position">区域标识(可选),不传返回全部</param>
Task<List<HomeNavigationDto>> GetNavigationListAsync(int? position = null);
```
**HomeService 实现变更:**
- 查询条件增加 `position` 筛选(当参数非 null 时)
- Status 过滤改为 `Status == 1`(仅启用)
- Select 投影增加 `Position``ActionType` 字段
### 后端 - MiAssessment.Api控制器层
**HomeController.GetNavigationList 变更:**
```csharp
[HttpGet("getNavigationList")]
public async Task<ApiResponse<List<HomeNavigationDto>>> GetNavigationList([FromQuery] int? position)
```
增加可选 `position` 查询参数,透传给 Service 层。
### 后端 - MiAssessment.Admin.Business后台管理
**HomeNavigationQueryRequest 扩展:**
```csharp
/// <summary>
/// 区域标识筛选
/// </summary>
public int? Position { get; set; }
```
**CreateHomeNavigationRequest / UpdateHomeNavigationRequest 扩展:**
```csharp
/// <summary>
/// 区域标识1=专业测评区域2=更多区域默认1
/// </summary>
public int Position { get; set; } = 1;
/// <summary>
/// 动作类型1=跳转页面2=弹客服二维码3=即将上线默认1
/// </summary>
public int ActionType { get; set; } = 1;
```
**Admin HomeNavigationDto 扩展:**
```csharp
/// <summary>
/// 区域标识
/// </summary>
public int Position { get; set; }
/// <summary>
/// 区域名称(专业测评区域/更多区域)
/// </summary>
public string PositionName { get; set; } = null!;
/// <summary>
/// 动作类型
/// </summary>
public int ActionType { get; set; }
/// <summary>
/// 动作类型名称(跳转页面/弹客服二维码/即将上线)
/// </summary>
public string ActionTypeName { get; set; } = null!;
```
**ContentController 变更:**
- `CreateNavigation` 增加 ActionType 验证:当 ActionType=1 时 LinkUrl 必填
- `UpdateNavigation` 同上
**ContentService 变更:**
- 列表查询支持 Position 筛选
- 创建/更新映射 Position 和 ActionType 字段
- DTO 映射增加 PositionName 和 ActionTypeName 的中文转换
### 小程序前端
**api/home.js 扩展:**
```javascript
/**
* 获取首页导航入口列表
* @param {Object} [params] - 查询参数
* @param {number} [params.position] - 区域标识
* @returns {Promise<Object>}
*/
export function getNavigationList(params) {
return get('/home/getNavigationList', params)
}
```
**pages/index/index.vue 变更:**
- `navigationList` 拆分为 `assessmentList`position=1`moreList`position=2
- 新增 `qrcodeUrl` 状态,页面加载时调用 `SystemController.GetContactInfo` 获取
- 新增 `showQrPopup` 状态控制弹窗显隐
- 「更多」区域改为动态渲染 `moreList`,根据 ActionType 处理点击事件
- 复用现有 `Popup` 组件展示二维码弹窗
### 后台管理前端
**导航管理页面变更:**
- 列表页增加 Position 筛选下拉
- 列表表格增加「区域」和「动作类型」列
- 创建/编辑表单增加 Position 和 ActionType 下拉选择器
- ActionType=1 时显示 LinkUrl 输入框(必填),其他值时隐藏
## Data Models
### 数据库迁移 SQL
```sql
-- 1. 新增列
ALTER TABLE home_navigations ADD Position INT NOT NULL DEFAULT 1;
ALTER TABLE home_navigations ADD ActionType INT NOT NULL DEFAULT 1;
-- 2. 现有记录设置为专业测评区域 + 跳转页面
UPDATE home_navigations SET Position = 1, ActionType = 1 WHERE IsDeleted = 0;
-- 3. 将原 Status=0 的"即将上线"记录改为 ActionType=3, Status=1
UPDATE home_navigations SET ActionType = 3, Status = 1 WHERE Status = 0 AND IsDeleted = 0;
-- 注意Status=2 的记录也改为 ActionType=3
UPDATE home_navigations SET ActionType = 3, Status = 1 WHERE Status = 2 AND IsDeleted = 0;
-- 4. 插入「更多」区域初始数据
INSERT INTO home_navigations (Name, ImageUrl, LinkUrl, Sort, Status, Position, ActionType, CreateTime, UpdateTime, IsDeleted)
VALUES
(N'学习方案', NULL, NULL, 2, 1, 2, 3, GETDATE(), GETDATE(), 0),
(N'详细咨询', NULL, NULL, 1, 1, 2, 2, GETDATE(), GETDATE(), 0);
```
### API 请求/响应模型
**GET /api/home/getNavigationList?position=2 响应:**
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": 4,
"name": "学习方案",
"imageUrl": "https://...",
"linkUrl": null,
"status": 1,
"position": 2,
"actionType": 3
},
{
"id": 5,
"name": "详细咨询",
"imageUrl": "https://...",
"linkUrl": null,
"status": 1,
"position": 2,
"actionType": 2
}
]
}
```
**GET /api/system/getContactInfo 响应(已有接口,复用):**
```json
{
"code": 0,
"message": "success",
"data": {
"qrcodeUrl": "https://..."
}
}
```
### 枚举值定义
| 枚举 | 值 | 含义 |
|------|----|------|
| Position | 1 | 专业测评区域 |
| Position | 2 | 更多区域 |
| ActionType | 1 | 跳转页面(使用 LinkUrl |
| ActionType | 2 | 弹出客服二维码弹窗 |
| ActionType | 3 | 即将上线Toast 提示) |
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: HomeNavigation 默认值不变性
*For any* newly created HomeNavigation 实体(未显式指定 Position 和 ActionType其 Position 应为 1ActionType 应为 1。
**Validates: Requirements 1.1, 1.2, 5.5, 5.6**
### Property 2: Navigation API 按 Position 过滤正确性
*For any* 一组 home_navigations 记录(包含不同 Position、Status、IsDeleted 组合),当以指定 position 参数调用 GetNavigationListAsync 时返回结果中的每条记录都应满足Position 等于请求的 position 值、Status=1、IsDeleted=false。当不传 position 参数时,返回结果中的每条记录都应满足 Status=1 且 IsDeleted=false。
**Validates: Requirements 2.1, 2.2, 2.5**
### Property 3: Navigation API 排序正确性
*For any* 一组启用状态的 home_navigations 记录GetNavigationListAsync 返回的列表应按 Sort 字段严格降序排列。
**Validates: Requirements 2.4**
### Property 4: Admin 列表按 Position 过滤正确性
*For any* 一组 home_navigations 记录和任意 Position 筛选值Admin GetNavigationListAsync 返回结果中的每条记录的 Position 都应等于请求的筛选值。
**Validates: Requirements 5.1**
### Property 5: Admin CRUD Position/ActionType 往返一致性
*For any* 有效的 Position1 或 2和 ActionType1、2 或 3组合通过 CreateNavigationAsync 创建后再通过 GetNavigationByIdAsync 读取,返回的 Position 和 ActionType 应与创建时一致;通过 UpdateNavigationAsync 修改后再读取,返回值应与更新值一致。
**Validates: Requirements 5.2, 5.3**
### Property 6: Position/ActionType 名称映射正确性
*For any* Position 值和 ActionType 值Admin DTO 中的 PositionName 和 ActionTypeName 应与预定义的映射一致Position 1→"专业测评区域"、2→"更多区域"ActionType 1→"跳转页面"、2→"弹客服二维码"、3→"即将上线")。
**Validates: Requirements 5.4**
### Property 7: ActionType=1 时 LinkUrl 必填验证
*For any* 创建或更新导航请求,当 ActionType=1 且 LinkUrl 为空或空白字符串时,系统应拒绝该请求并返回验证错误。
**Validates: Requirements 5.9**
## Error Handling
### 后端错误处理
| 场景 | 错误码 | 错误信息 | 处理方式 |
|------|--------|----------|----------|
| position 参数非法(非 1/2 | 1000 | 参数无效 | 返回验证错误 |
| ActionType=1 但 LinkUrl 为空 | 1000 | 跳转链接不能为空 | 返回验证错误 |
| 导航记录不存在 | 3100 | 导航记录不存在 | 返回业务错误 |
| 数据库异常 | 5000 | 系统错误 | 记录日志,返回通用错误 |
| service_qrcode 配置未设置 | - | 返回空字符串 | 前端显示默认提示 |
### 前端错误处理
| 场景 | 处理方式 |
|------|----------|
| Navigation API 请求失败 | console.error 记录,保持区域为空/隐藏 |
| 二维码 URL 为空 | QR_Popup 显示"暂未配置客服二维码"文字提示 |
| 图片加载失败 | 卡片显示名称文字,图标区域留空 |
## Testing Strategy
### 单元测试xUnit + Moq
针对具体示例和边界情况:
1. **HomeService 测试**
- 传入 position=1 返回正确数据(具体示例)
- 传入 position=2 返回正确数据(具体示例)
- 不传 position 返回所有启用数据(具体示例)
- 数据库为空时返回空列表(边界情况)
2. **ContentService 测试**
- 创建导航时 Position/ActionType 默认值验证(具体示例)
- ActionType=1 且 LinkUrl 为空时创建失败(边界情况)
- ActionType=2 时 LinkUrl 可为空(边界情况)
- 更新 Position 和 ActionType 后读取一致(具体示例)
3. **名称映射测试**
- Position=1 映射为"专业测评区域"(具体示例)
- Position=2 映射为"更多区域"(具体示例)
- 未知 Position 值的处理(边界情况)
### 属性测试FsCheck
每个属性测试最少运行 100 次迭代,使用随机生成的输入数据验证通用性质。
每个测试必须以注释标注对应的设计属性:
```csharp
/// <summary>
/// Feature: home-more-modules, Property 2: Navigation API 按 Position 过滤正确性
/// </summary>
[Property]
public Property NavigationApi_FiltersByPosition_ReturnsOnlyMatchingRecords()
{
// 生成随机 HomeNavigation 列表,随机 position 参数
// 验证返回结果全部满足过滤条件
}
```
属性测试覆盖的 Properties
- Property 1: 默认值不变性
- Property 2: API 过滤正确性
- Property 3: 排序正确性
- Property 4: Admin 过滤正确性
- Property 5: CRUD 往返一致性
- Property 6: 名称映射正确性
- Property 7: LinkUrl 必填验证
每个 Correctness Property 对应一个独立的属性测试方法。单元测试和属性测试互补:单元测试验证具体场景和边界情况,属性测试验证跨所有输入的通用正确性。

View File

@ -0,0 +1,100 @@
# Requirements Document
## Introduction
小程序首页「更多」区域目前硬编码了两个卡片(学习方案、详细咨询),需要改为后台可配置化。复用现有 `home_navigations` 表,通过新增 `Position``ActionType` 字段来区分不同区域和点击动作类型,实现前端动态加载和后台统一管理。
## Glossary
- **Home_Navigation**: `home_navigations` 数据库表中的一条记录,代表首页的一个导航入口卡片
- **Position**: 导航入口所属区域标识。1=专业测评区域2=更多区域
- **ActionType**: 导航入口的点击动作类型。1=跳转页面(使用 LinkUrl2=弹出客服二维码弹窗3=即将上线(提示"即将上线"
- **Navigation_API**: 小程序端获取首页导航列表的 GET 接口 `/api/home/getNavigationList`
- **Admin_Navigation_API**: 后台管理系统中 ContentController 下的首页导航管理接口集合
- **More_Section**: 小程序首页「更多」区域,展示 Position=2 的导航入口卡片
- **Assessment_Section**: 小程序首页「专业测评」区域,展示 Position=1 的导航入口卡片
- **QR_Popup**: 客服二维码弹窗组件,当 ActionType=2 时点击卡片弹出
## Requirements
### Requirement 1: 数据库表扩展
**User Story:** As a 系统管理员, I want `home_navigations` 表支持区域和动作类型字段, so that 不同区域的导航入口可以独立配置和管理。
#### Acceptance Criteria
1. THE Home_Navigation 实体 SHALL 包含 `Position` 字段int 类型,默认值为 1
2. THE Home_Navigation 实体 SHALL 包含 `ActionType` 字段int 类型,默认值为 1
3. WHEN Position 字段值为 1 时, THE Home_Navigation SHALL 归属于 Assessment_Section
4. WHEN Position 字段值为 2 时, THE Home_Navigation SHALL 归属于 More_Section
5. WHEN ActionType 字段值为 1 时, THE Home_Navigation SHALL 表示点击后跳转 LinkUrl 指定的页面
6. WHEN ActionType 字段值为 2 时, THE Home_Navigation SHALL 表示点击后弹出 QR_Popup
7. WHEN ActionType 字段值为 3 时, THE Home_Navigation SHALL 表示点击后提示"即将上线"
8. THE 数据库迁移脚本 SHALL 将现有 3 条记录的 Position 设置为 1、ActionType 设置为 1
9. THE 数据库迁移脚本 SHALL 插入 2 条 Position=2 的初始数据「学习方案」ActionType=3和「详细咨询」ActionType=2
### Requirement 2: 小程序 API 改造
**User Story:** As a 前端开发者, I want Navigation_API 支持按区域筛选导航列表, so that 前端可以分别获取不同区域的导航数据。
#### Acceptance Criteria
1. WHEN Navigation_API 收到请求且 `position` 参数存在时, THE Navigation_API SHALL 仅返回指定 Position 的启用状态Status=1导航记录
2. WHEN Navigation_API 收到请求且 `position` 参数不存在时, THE Navigation_API SHALL 返回所有启用状态Status=1的导航记录
3. THE Navigation_API 返回的每条记录 SHALL 包含 `position``actionType` 字段
4. THE Navigation_API SHALL 按 Sort 字段降序排列返回结果
5. THE Navigation_API SHALL 过滤掉 IsDeleted=true 和 Status=0 的记录
### Requirement 3: 小程序首页「更多」区域动态渲染
**User Story:** As a 小程序用户, I want 首页「更多」区域的卡片从后台动态加载, so that 管理员可以灵活配置该区域的内容。
#### Acceptance Criteria
1. WHEN 首页加载时, THE More_Section SHALL 调用 Navigation_APIposition=2获取导航数据
2. THE More_Section SHALL 以每行 2 个卡片的网格布局展示导航入口
3. WHEN 导航数据为奇数个时, THE More_Section SHALL 将最后一个卡片宽度设置为占满整行
4. WHEN 导航数据为空时, THE More_Section SHALL 隐藏整个「更多」区块
5. WHEN 用户点击 ActionType=1 的卡片时, THE More_Section SHALL 跳转到该卡片的 LinkUrl 页面
6. WHEN 用户点击 ActionType=2 的卡片时, THE More_Section SHALL 弹出 QR_Popup 展示客服二维码
7. WHEN 用户点击 ActionType=3 的卡片时, THE More_Section SHALL 显示"即将上线"提示Toast持续 2 秒)
8. THE More_Section 的每个卡片 SHALL 展示导航名称和图标图片
### Requirement 4: 小程序首页「专业测评」区域适配
**User Story:** As a 前端开发者, I want 「专业测评」区域也从按 Position 筛选的 API 获取数据, so that 两个区域的数据来源统一且互不干扰。
#### Acceptance Criteria
1. WHEN 首页加载时, THE Assessment_Section SHALL 调用 Navigation_APIposition=1获取导航数据
2. THE Assessment_Section SHALL 保持现有的卡片样式和交互逻辑
3. WHEN 导航数据中某条记录的 ActionType=3 时, THE Assessment_Section SHALL 在该卡片上显示"即将上线"标签并禁止跳转
4. THE Assessment_Section 的每个卡片 SHALL 以 ImageUrl 图片铺满整个卡片区域mode="aspectFill"),不再使用渐变背景色+小图标+文字名称的组合方式,图片本身包含完整的卡片视觉内容
### Requirement 5: 后台管理系统导航管理改造
**User Story:** As a 后台管理员, I want 在首页导航管理页面配置 Position 和 ActionType, so that 可以灵活管理不同区域的导航入口。
#### Acceptance Criteria
1. THE Admin_Navigation_API 的列表查询接口 SHALL 支持按 Position 筛选
2. THE Admin_Navigation_API 的创建接口 SHALL 接受 Position 和 ActionType 参数
3. THE Admin_Navigation_API 的更新接口 SHALL 支持修改 Position 和 ActionType
4. THE Admin_Navigation_API 的列表返回 SHALL 包含 Position 名称(专业测评区域/更多区域)和 ActionType 名称(跳转页面/弹客服二维码/即将上线)
5. WHEN 创建导航时未指定 Position, THE Admin_Navigation_API SHALL 默认 Position 为 1
6. WHEN 创建导航时未指定 ActionType, THE Admin_Navigation_API SHALL 默认 ActionType 为 1
7. THE 后台管理前端导航管理页面 SHALL 提供 Position 下拉选择器(专业测评区域/更多区域)
8. THE 后台管理前端导航管理页面 SHALL 提供 ActionType 下拉选择器(跳转页面/弹客服二维码/即将上线)
9. WHEN ActionType 选择为"跳转页面"时, THE 后台管理前端 SHALL 显示 LinkUrl 输入框为必填
10. WHEN ActionType 选择为"弹客服二维码"或"即将上线"时, THE 后台管理前端 SHALL 隐藏或禁用 LinkUrl 输入框
### Requirement 6: 客服二维码弹窗
**User Story:** As a 小程序用户, I want 点击「详细咨询」卡片时弹出客服二维码, so that 可以方便地联系客服。
#### Acceptance Criteria
1. WHEN 用户点击 ActionType=2 的导航卡片时, THE QR_Popup SHALL 以居中弹窗形式展示客服二维码图片
2. THE QR_Popup SHALL 提供关闭按钮,点击后关闭弹窗
3. THE QR_Popup SHALL 支持点击遮罩层关闭弹窗
4. THE QR_Popup 的二维码图片 URL SHALL 从业务配置configs 表 `service_qrcode` 配置项)中读取

View File

@ -0,0 +1,124 @@
# Implementation Plan: 首页「更多」区域模块化配置
## Overview
将小程序首页「更多」区域从硬编码改为后台可配置化。复用 `home_navigations` 表,新增 `Position``ActionType` 字段,实现前端按区域动态加载、后台统一管理。实现顺序:数据库 → 后端实体/DTO → 后端服务层 → 后端控制器 → 小程序前端 → 后台管理前端。
## Tasks
- [x] 1. 数据库迁移与实体扩展
- [x] 1.1 创建 SQL 迁移脚本,为 `home_navigations` 表新增 `Position`INT NOT NULL DEFAULT 1`ActionType`INT NOT NULL DEFAULT 1将现有记录设为 Position=1, ActionType=1将 Status=0 和 Status=2 的记录改为 ActionType=3, Status=1插入 2 条 Position=2 的初始数据(学习方案 ActionType=3、详细咨询 ActionType=2
- 创建文件 `server/MiAssessment/migrations/add_position_actiontype_to_home_navigations.sql`
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9_
- [x] 1.2 扩展 `MiAssessment.Model.Entities.HomeNavigation` 实体,添加 `Position`默认1`ActionType`默认1属性更新 Status 注释为 0=禁用/1=启用
- 修改文件 `server/MiAssessment/src/MiAssessment.Model/Entities/HomeNavigation.cs`
- _Requirements: 1.1, 1.2_
- [x] 1.3 扩展 `MiAssessment.Admin.Business.Entities.HomeNavigation` 实体,添加相同的 `Position``ActionType` 属性
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Entities/HomeNavigation.cs`
- _Requirements: 1.1, 1.2_
- [ ]* 1.4 编写属性测试验证 HomeNavigation 默认值不变性
- **Property 1: HomeNavigation 默认值不变性**
- **Validates: Requirements 1.1, 1.2, 5.5, 5.6**
- 创建文件 `server/MiAssessment/tests/MiAssessment.Tests/Services/HomeNavigationPropertyTests.cs`
- [x] 2. 小程序 API 后端改造Model + Core + Api
- [x] 2.1 扩展 `MiAssessment.Model.Models.Home.HomeNavigationDto`,添加 `Position``ActionType` 属性
- 修改文件 `server/MiAssessment/src/MiAssessment.Model/Models/Home/HomeNavigationDto.cs`
- _Requirements: 2.3_
- [x] 2.2 修改 `IHomeService.GetNavigationListAsync` 接口签名,增加 `int? position = null` 参数
- 修改文件 `server/MiAssessment/src/MiAssessment.Core/Interfaces/IHomeService.cs`
- _Requirements: 2.1, 2.2_
- [x] 2.3 修改 `HomeService.GetNavigationListAsync` 实现:增加 position 筛选逻辑(非 null 时过滤Status 过滤改为 `Status == 1`Select 投影增加 Position 和 ActionType保持 Sort 降序 + IsDeleted 过滤
- 修改文件 `server/MiAssessment/src/MiAssessment.Core/Services/HomeService.cs`
- _Requirements: 2.1, 2.2, 2.4, 2.5_
- [x] 2.4 修改 `HomeController.GetNavigationList`,增加 `[FromQuery] int? position` 参数并透传给 Service
- 修改文件 `server/MiAssessment/src/MiAssessment.Api/Controllers/HomeController.cs`
- _Requirements: 2.1, 2.2_
- [ ]* 2.5 编写属性测试验证 Navigation API 按 Position 过滤正确性
- **Property 2: Navigation API 按 Position 过滤正确性**
- **Validates: Requirements 2.1, 2.2, 2.5**
- 添加到 `server/MiAssessment/tests/MiAssessment.Tests/Services/HomeNavigationPropertyTests.cs`
- [ ]* 2.6 编写属性测试验证 Navigation API 排序正确性
- **Property 3: Navigation API 排序正确性**
- **Validates: Requirements 2.4**
- 添加到 `server/MiAssessment/tests/MiAssessment.Tests/Services/HomeNavigationPropertyTests.cs`
- [x] 3. Checkpoint - 确保后端编译通过
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. 后台管理后端改造Admin.Business
- [x] 4.1 扩展 Admin DTO 和请求模型:`HomeNavigationDto` 增加 Position/PositionName/ActionType/ActionTypeName`HomeNavigationQueryRequest` 增加 Position 筛选;`CreateHomeNavigationRequest` 和 `UpdateHomeNavigationRequest` 增加 Position默认1和 ActionType默认1
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Models/Content/HomeNavigationDto.cs`
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Models/Content/HomeNavigationQueryRequest.cs`
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Models/Content/CreateHomeNavigationRequest.cs`
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Models/Content/UpdateHomeNavigationRequest.cs`
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6_
- [x] 4.2 修改 `ContentService` 导航相关方法:添加 NavigationPositionNames 和 ActionTypeNames 映射字典;列表查询支持 Position 筛选;创建/更新映射 Position 和 ActionTypeDTO 映射增加 PositionName 和 ActionTypeName创建/更新时 ActionType=1 校验 LinkUrl 必填
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Services/ContentService.cs`
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.9, 5.10_
- [x] 4.3 修改 `ContentController` 导航相关方法CreateNavigation 和 UpdateNavigation 增加 ActionType=1 时 LinkUrl 必填验证UpdateNavigationStatus 状态值范围改为 0/1
- 修改文件 `server/MiAssessment/src/MiAssessment.Admin.Business/Controllers/ContentController.cs`
- _Requirements: 5.9, 5.10_
- [ ]* 4.4 编写属性测试验证 Admin 列表按 Position 过滤正确性
- **Property 4: Admin 列表按 Position 过滤正确性**
- **Validates: Requirements 5.1**
- 添加到 `server/MiAssessment/tests/MiAssessment.Tests/Admin/NavigationPropertyTests.cs`
- [ ]* 4.5 编写属性测试验证 Admin CRUD Position/ActionType 往返一致性
- **Property 5: Admin CRUD Position/ActionType 往返一致性**
- **Validates: Requirements 5.2, 5.3**
- 添加到 `server/MiAssessment/tests/MiAssessment.Tests/Admin/NavigationPropertyTests.cs`
- [ ]* 4.6 编写属性测试验证 Position/ActionType 名称映射正确性
- **Property 6: Position/ActionType 名称映射正确性**
- **Validates: Requirements 5.4**
- 添加到 `server/MiAssessment/tests/MiAssessment.Tests/Admin/NavigationPropertyTests.cs`
- [ ]* 4.7 编写属性测试验证 ActionType=1 时 LinkUrl 必填
- **Property 7: ActionType=1 时 LinkUrl 必填验证**
- **Validates: Requirements 5.9**
- 添加到 `server/MiAssessment/tests/MiAssessment.Tests/Admin/NavigationPropertyTests.cs`
- [x] 5. Checkpoint - 确保后端全部编译通过且测试通过
- Ensure all tests pass, ask the user if questions arise.
- [x] 6. 小程序前端改造
- [x] 6.1 修改 `uniapp/api/home.js``getNavigationList` 函数增加 `params` 参数支持传入 `position`
- _Requirements: 2.1, 4.1, 3.1_
- [x] 6.2 修改 `uniapp/pages/index/index.vue`:将 `navigationList` 拆分为 `assessmentList`position=1`moreList`position=2`loadNavigationList` 拆分为两个函数分别调用 API新增 `qrcodeUrl` 状态,页面加载时调用已有的 `getContactInfo` 获取二维码 URL新增 `showQrPopup` 状态控制弹窗显隐
- _Requirements: 3.1, 4.1, 6.4_
- [x] 6.3 修改 `uniapp/pages/index/index.vue` 模板:「专业测评」区域绑定 `assessmentList`卡片改为全图片模式ImageUrl 铺满卡片,移除渐变背景色+小图标+文字名称),根据 ActionType 处理点击ActionType=3 显示即将上线标签+Toast「更多」区域改为动态渲染 `moreList`,每行 2 个卡片网格布局,奇数个时最后一个占满整行,空数据时隐藏区块;根据 ActionType 处理点击事件1=跳转、2=弹窗、3=Toast添加 QR_Popup 弹窗组件(复用 `@/components/Popup/index.vue`
- _Requirements: 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 4.2, 4.3, 4.4, 6.1, 6.2, 6.3_
- [x] 7. 后台管理前端改造
- [x] 7.1 修改 `admin-web/src/api/business/content.ts``NavigationItem` 接口增加 position/positionName/actionType/actionTypeName 字段;`NavigationQuery` 增加 position 筛选;`CreateNavigationRequest` 和 `UpdateNavigationRequest` 增加 position 和 actionType 字段
- _Requirements: 5.1, 5.2, 5.3, 5.4_
- [x] 7.2 修改 `admin-web/src/views/business/content/navigation/index.vue`:搜索表单增加 Position 下拉筛选(专业测评区域/更多区域);表格增加「区域」和「动作类型」列;状态下拉改为仅 0禁用/1启用新增/编辑表单增加 Position 下拉选择器和 ActionType 下拉选择器ActionType=1 时显示 LinkUrl 输入框为必填,其他值时隐藏 LinkUrl
- _Requirements: 5.7, 5.8, 5.9, 5.10_
- [x] 8. Final checkpoint - 确保全部测试通过
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties (FsCheck, 100+ iterations)
- SQL 迁移脚本需要在部署时手动执行,不通过 EF Core Migration
- 二维码 URL 复用现有 `SystemService.GetContactInfoAsync()` 接口,无需新建
- QR 弹窗复用现有 `@/components/Popup/index.vue` 组件

View File

@ -49,24 +49,26 @@
<view class="section-indicator"></view>
<text class="section-title">专业测评</text>
</view>
<view class="assessment-grid">
<view
class="assessment-card"
v-for="(item, index) in assessmentList"
:key="index"
@click="handleCardClick(item)"
>
<!-- 即将上线标签 -->
<view v-if="item.actionType === 3" class="coming-soon-tag">
<text>即将上线</text>
<scroll-view class="assessment-scroll" scroll-x enhanced :show-scrollbar="false">
<view class="assessment-grid">
<view
class="assessment-card"
v-for="(item, index) in assessmentList"
:key="index"
@click="handleCardClick(item)"
>
<!-- 即将上线标签 -->
<view v-if="item.actionType === 3" class="coming-soon-tag">
<text>即将上线</text>
</view>
<image
:src="item.imageUrl"
mode="aspectFill"
class="assessment-image"
/>
</view>
<image
:src="item.imageUrl"
mode="aspectFill"
class="assessment-image"
/>
</view>
</view>
</scroll-view>
</view>
<!-- 学业规划 -->
@ -94,11 +96,14 @@
:class="{ 'more-card--full': moreList.length % 2 === 1 && index === moreList.length - 1 }"
@click="handleCardClick(item)"
>
<text class="more-card__title">{{ item.name }}</text>
<!-- 即将上线标签 -->
<view v-if="item.actionType === 3" class="coming-soon-tag">
<text>即将上线</text>
</view>
<image
:src="item.imageUrl"
mode="aspectFit"
class="more-card__icon"
mode="aspectFill"
class="more-card__image"
/>
</view>
</view>
@ -371,18 +376,26 @@ onMounted(() => {
}
}
// -
// - 2
.assessment-scroll {
width: 100%;
white-space: nowrap;
}
.assessment-grid {
display: flex;
display: inline-flex;
gap: $spacing-md;
}
.assessment-card {
position: relative;
flex: 1;
// = ( - ) / 2
// = 750rpx - section-card margin 32rpx*2 - section-card padding 32rpx*2 = 622rpx
width: 299rpx;
height: 240rpx;
border-radius: $border-radius-xl;
overflow: hidden;
flex-shrink: 0;
.coming-soon-tag {
position: absolute;
@ -430,15 +443,11 @@ onMounted(() => {
}
.more-card {
position: relative;
width: calc(50% - #{$spacing-md} / 2);
display: flex;
align-items: center;
justify-content: space-between;
height: 160rpx;
padding: 0 $spacing-lg;
border-radius: $border-radius-xl;
overflow: hidden;
background-color: $bg-gray;
box-sizing: border-box;
//
@ -446,15 +455,24 @@ onMounted(() => {
width: 100%;
}
&__title {
font-size: $font-size-md;
font-weight: $font-weight-bold;
color: $text-color;
.coming-soon-tag {
position: absolute;
top: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
padding: 6rpx 20rpx;
border-radius: 0 $border-radius-xl 0 $border-radius-lg;
z-index: 1;
text {
font-size: $font-size-xs;
color: $text-white;
}
}
&__icon {
width: 80rpx;
height: 80rpx;
&__image {
width: 100%;
height: 100%;
}
}