kindergarten_java/docs/test-logs/admin/2026-03-14-package-test.md

273 lines
7.2 KiB
Markdown
Raw Normal View History

# 套餐数据显示问题修复测试
## 问题描述
数据库中有很多套餐和课程包数据,但超管端、学校端页面上没有显示数据。接口有返回数据,但前端无法正确解析显示。
## 问题原因
1. **字段格式不匹配**:后端 `gradeLevels` 存储的是 JSON 字符串或逗号分隔字符串,前端期望数组格式
2. **数据结构不匹配**:学校端期望的数据结构与后端返回不一致
3. **缺少字段**:前端需要 `courses` 字段(包含的课程列表),但后端未返回
## 修复方案
### 后端修改
#### 1. CoursePackageResponse.java
修改字段类型和添加新字段:
```java
// gradeLevels: String → String[]
@Schema(description = "年级水平(数组)")
private String[] gradeLevels;
// 新增字段
@Schema(description = "包含的课程")
private List<CoursePackageCourseItem> courses;
@Schema(description = "开始日期(租户套餐)")
private LocalDate startDate;
@Schema(description = "结束日期(租户套餐)")
private LocalDate endDate;
```
#### 2. CoursePackageService.java
添加 `gradeLevels` 转换逻辑和 `courses` 填充逻辑:
```java
// 解析 gradeLevels数据库中存储的是 JSON 数组字符串
String[] gradeLevelsArray = null;
if (pkg.getGradeLevels() != null && !pkg.getGradeLevels().isEmpty()) {
try {
// 尝试解析 JSON 数组格式:["小班","中班"]
gradeLevelsArray = JSON.parseArray(pkg.getGradeLevels(), String.class).toArray(new String[0]);
} catch (Exception e) {
// 如果不是 JSON 格式,尝试按逗号分隔处理
gradeLevelsArray = pkg.getGradeLevels().split(",");
}
}
// 查询套餐包含的课程
List<CoursePackageCourse> packageCourses = packageCourseMapper.selectList(
new LambdaQueryWrapper<CoursePackageCourse>()
.eq(CoursePackageCourse::getPackageId, pkg.getId())
.orderByAsc(CoursePackageCourse::getSortOrder)
);
```
#### 3. CoursePackageMapper.java
添加 MapStruct 类型转换方法:
```java
@Named("stringToArray")
default String[] stringToArray(String value) {
if (value == null || value.isEmpty()) {
return new String[0];
}
return value.split(",");
}
@Named("arrayToString")
default String arrayToString(String[] value) {
if (value == null || value.length == 0) {
return null;
}
return String.join(",", value);
}
```
#### 4. SchoolPackageController.java
修改返回类型:
```java
// 导入
import com.reading.platform.dto.response.CoursePackageResponse;
// 修改返回类型
public Result<List<CoursePackageResponse>> findTenantPackages() {
Long tenantId = SecurityUtils.getCurrentTenantId();
return Result.success(packageService.findTenantPackages(tenantId));
}
```
### 前端修改
#### 1. PackageView.vue
修改数据访问路径:
```vue
<!-- 修改前 -->
<div class="package-name">{{ item.package.name }}</div>
<span class="info-value">{{ item.package.courseCount }} 个</span>
<!-- 修改后 -->
<div class="package-name">{{ item.name }}</div>
<span class="info-value">{{ item.courseCount }} 个</span>
```
#### 2. school.ts
更新 `CoursePackage` 接口定义:
```typescript
export interface CoursePackage {
id: number;
name: string;
description?: string;
price: number;
discountPrice?: number;
discountType?: string;
gradeLevels: string[];
status: string;
courseCount: number;
tenantCount: number;
createdAt: string;
startDate?: string;
endDate?: string;
courses?: Array<{
id: number;
name: string;
gradeLevel: string;
sortOrder: number;
}>;
}
```
## 测试验证
### 超管端套餐列表接口测试
```bash
curl -X GET "http://localhost:8080/api/v1/admin/packages?page=1&pageSize=10" \
-H "Authorization: Bearer <admin_token>"
```
**返回结果**
```json
{
"code": 200,
"data": {
"list": [
{
"id": 3,
"name": "幼儿园阅读启蒙套餐",
"gradeLevels": ["小班", "中班"],
"courseCount": 5,
"tenantCount": 1,
"courses": [
{"id": 6, "name": "小猪佩奇绘本阅读", "gradeLevel": "小班", "sortOrder": 1},
{"id": 7, "name": "彩虹色的花", "gradeLevel": "小班", "sortOrder": 2},
{"id": 8, "name": "牙齿大街的新鲜事", "gradeLevel": "中班", "sortOrder": 3},
{"id": 9, "name": "猜猜我有多爱你", "gradeLevel": "小班", "sortOrder": 4},
{"id": 10, "name": "逃家小兔", "gradeLevel": "小班", "sortOrder": 5}
]
}
]
}
}
```
### 学校端套餐列表接口测试
```bash
curl -X GET "http://localhost:8080/api/v1/school/packages" \
-H "Authorization: Bearer <school_token>"
```
**返回结果**
```json
{
"code": 200,
"data": [
{
"id": 3,
"name": "幼儿园阅读启蒙套餐",
"gradeLevels": ["小班", "中班"],
"courseCount": 5,
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"courses": [
{"id": 6, "name": "小猪佩奇绘本阅读", "gradeLevel": "小班", "sortOrder": 1},
{"id": 7, "name": "彩虹色的花", "gradeLevel": "小班", "sortOrder": 2},
...
]
},
{
"id": 4,
"name": "亲子共读成长套餐",
"gradeLevels": ["小班", "中班", "大班"],
"courseCount": 5,
"startDate": "2026-02-01",
"endDate": "2027-01-31",
"courses": [...]
}
]
}
```
## 测试结果
| 接口 | 测试项 | 结果 |
|------|--------|------|
| 超管端套餐列表 | 返回数据格式 | ✅ 通过 |
| 超管端套餐列表 | gradeLevels 数组格式 | ✅ 通过 |
| 超管端套餐列表 | courses 字段填充 | ✅ 通过 |
| 超管端套餐列表 | tenantCount 统计 | ✅ 通过 |
| 学校端套餐列表 | 返回数据格式 | ✅ 通过 |
| 学校端套餐列表 | startDate/endDate 字段 | ✅ 通过 |
| 学校端套餐列表 | gradeLevels 数组格式 | ✅ 通过 |
| 学校端套餐列表 | courses 字段填充 | ✅ 通过 |
## 修改的文件
### 后端文件
| 文件 | 修改内容 |
|------|----------|
| `CoursePackageResponse.java` | gradeLevels 改为 String[],添加 courses、startDate、endDate 字段 |
| `CoursePackageService.java` | 添加 gradeLevels 转换和 courses 填充逻辑 |
| `CoursePackageMapper.java` | 添加 MapStruct 类型转换方法 |
| `SchoolPackageController.java` | 修改返回类型为 CoursePackageResponse |
| `V8__add_tenant_package_test_data.sql` | 添加租户套餐测试数据 |
### 前端文件
| 文件 | 修改内容 |
|------|----------|
| `PackageView.vue` | 修改数据访问路径 |
| `school.ts` | 更新 CoursePackage 接口定义 |
## 测试数据
V8 迁移脚本添加的测试数据:
```sql
INSERT INTO tenant_package (id, tenant_id, package_id, start_date, end_date, price_paid, status) VALUES
(1, 1, 3, '2026-01-01', '2026-12-31', 7999, 'ACTIVE'),
(2, 1, 4, '2026-02-01', '2027-01-31', 15999, 'ACTIVE');
```
## 总结
本次修复解决了前后端数据结构不匹配的问题:
1. 后端正确返回 `gradeLevels` 数组格式
2. 后端补充了前端需要的 `courses` 字段
3. 学校端套餐接口返回包含 `startDate``endDate` 字段
4. 前端正确解析并显示数据
修复后,超管端和学校端的套餐页面都能正确显示数据。
## 测试日期
2026-03-14