This commit is contained in:
parent
9d4f9a0722
commit
e5e63bd7f2
1
.kiro/specs/home-more-modules/.config.kiro
Normal file
1
.kiro/specs/home-more-modules/.config.kiro
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"specId": "a387593e-0072-4eb6-8dfc-5dbd1bcc6547", "workflowType": "requirements-first", "specType": "feature"}
|
||||
417
.kiro/specs/home-more-modules/design.md
Normal file
417
.kiro/specs/home-more-modules/design.md
Normal 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 应为 1,ActionType 应为 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* 有效的 Position(1 或 2)和 ActionType(1、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 对应一个独立的属性测试方法。单元测试和属性测试互补:单元测试验证具体场景和边界情况,属性测试验证跨所有输入的通用正确性。
|
||||
100
.kiro/specs/home-more-modules/requirements.md
Normal file
100
.kiro/specs/home-more-modules/requirements.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Requirements Document
|
||||
|
||||
## Introduction
|
||||
|
||||
小程序首页「更多」区域目前硬编码了两个卡片(学习方案、详细咨询),需要改为后台可配置化。复用现有 `home_navigations` 表,通过新增 `Position` 和 `ActionType` 字段来区分不同区域和点击动作类型,实现前端动态加载和后台统一管理。
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Home_Navigation**: `home_navigations` 数据库表中的一条记录,代表首页的一个导航入口卡片
|
||||
- **Position**: 导航入口所属区域标识。1=专业测评区域,2=更多区域
|
||||
- **ActionType**: 导航入口的点击动作类型。1=跳转页面(使用 LinkUrl),2=弹出客服二维码弹窗,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_API(position=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_API(position=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` 配置项)中读取
|
||||
124
.kiro/specs/home-more-modules/tasks.md
Normal file
124
.kiro/specs/home-more-modules/tasks.md
Normal 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 和 ActionType;DTO 映射增加 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` 组件
|
||||
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user