From e5e63bd7f2e2b1e65286cea158b6ff6942b9fc57 Mon Sep 17 00:00:00 2001 From: zpc Date: Wed, 25 Mar 2026 11:54:32 +0800 Subject: [PATCH] ui --- .kiro/specs/home-more-modules/.config.kiro | 1 + .kiro/specs/home-more-modules/design.md | 417 ++++++++++++++++++ .kiro/specs/home-more-modules/requirements.md | 100 +++++ .kiro/specs/home-more-modules/tasks.md | 124 ++++++ uniapp/pages/index/index.vue | 86 ++-- 5 files changed, 694 insertions(+), 34 deletions(-) create mode 100644 .kiro/specs/home-more-modules/.config.kiro create mode 100644 .kiro/specs/home-more-modules/design.md create mode 100644 .kiro/specs/home-more-modules/requirements.md create mode 100644 .kiro/specs/home-more-modules/tasks.md diff --git a/.kiro/specs/home-more-modules/.config.kiro b/.kiro/specs/home-more-modules/.config.kiro new file mode 100644 index 0000000..3508d14 --- /dev/null +++ b/.kiro/specs/home-more-modules/.config.kiro @@ -0,0 +1 @@ +{"specId": "a387593e-0072-4eb6-8dfc-5dbd1bcc6547", "workflowType": "requirements-first", "specType": "feature"} \ No newline at end of file diff --git a/.kiro/specs/home-more-modules/design.md b/.kiro/specs/home-more-modules/design.md new file mode 100644 index 0000000..ec47e6d --- /dev/null +++ b/.kiro/specs/home-more-modules/design.md @@ -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 +/// +/// 区域标识:1=专业测评区域,2=更多区域 +/// +public int Position { get; set; } = 1; + +/// +/// 动作类型:1=跳转页面,2=弹客服二维码,3=即将上线 +/// +public int ActionType { get; set; } = 1; +``` + +**小程序 DTO 扩展**(`MiAssessment.Model.Models.Home.HomeNavigationDto`): + +```csharp +/// +/// 区域标识 +/// +public int Position { get; set; } + +/// +/// 动作类型 +/// +public int ActionType { get; set; } +``` + +### 后端 - MiAssessment.Core(服务层) + +**IHomeService 接口变更:** + +```csharp +/// +/// 获取首页导航入口列表 +/// +/// 区域标识(可选),不传返回全部 +Task> GetNavigationListAsync(int? position = null); +``` + +**HomeService 实现变更:** +- 查询条件增加 `position` 筛选(当参数非 null 时) +- Status 过滤改为 `Status == 1`(仅启用) +- Select 投影增加 `Position` 和 `ActionType` 字段 + +### 后端 - MiAssessment.Api(控制器层) + +**HomeController.GetNavigationList 变更:** + +```csharp +[HttpGet("getNavigationList")] +public async Task>> GetNavigationList([FromQuery] int? position) +``` + +增加可选 `position` 查询参数,透传给 Service 层。 + +### 后端 - MiAssessment.Admin.Business(后台管理) + +**HomeNavigationQueryRequest 扩展:** + +```csharp +/// +/// 区域标识筛选 +/// +public int? Position { get; set; } +``` + +**CreateHomeNavigationRequest / UpdateHomeNavigationRequest 扩展:** + +```csharp +/// +/// 区域标识:1=专业测评区域,2=更多区域(默认1) +/// +public int Position { get; set; } = 1; + +/// +/// 动作类型:1=跳转页面,2=弹客服二维码,3=即将上线(默认1) +/// +public int ActionType { get; set; } = 1; +``` + +**Admin HomeNavigationDto 扩展:** + +```csharp +/// +/// 区域标识 +/// +public int Position { get; set; } + +/// +/// 区域名称(专业测评区域/更多区域) +/// +public string PositionName { get; set; } = null!; + +/// +/// 动作类型 +/// +public int ActionType { get; set; } + +/// +/// 动作类型名称(跳转页面/弹客服二维码/即将上线) +/// +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} + */ +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 +/// +/// Feature: home-more-modules, Property 2: Navigation API 按 Position 过滤正确性 +/// +[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 对应一个独立的属性测试方法。单元测试和属性测试互补:单元测试验证具体场景和边界情况,属性测试验证跨所有输入的通用正确性。 diff --git a/.kiro/specs/home-more-modules/requirements.md b/.kiro/specs/home-more-modules/requirements.md new file mode 100644 index 0000000..165cbdb --- /dev/null +++ b/.kiro/specs/home-more-modules/requirements.md @@ -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` 配置项)中读取 diff --git a/.kiro/specs/home-more-modules/tasks.md b/.kiro/specs/home-more-modules/tasks.md new file mode 100644 index 0000000..c4d2930 --- /dev/null +++ b/.kiro/specs/home-more-modules/tasks.md @@ -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` 组件 diff --git a/uniapp/pages/index/index.vue b/uniapp/pages/index/index.vue index 36bfc5f..72f2a2f 100644 --- a/uniapp/pages/index/index.vue +++ b/uniapp/pages/index/index.vue @@ -49,24 +49,26 @@ 专业测评 - - - - - 即将上线 + + + + + + 即将上线 + + - - + @@ -94,11 +96,14 @@ :class="{ 'more-card--full': moreList.length % 2 === 1 && index === moreList.length - 1 }" @click="handleCardClick(item)" > - {{ item.name }} + + + 即将上线 + @@ -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%; } }