peixue-dev/peidu/docs/fixes/2026-01-23-学习记录功能完善/📋问题分析与解决方案.md

667 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 学习记录功能完善 - 问题分析与解决方案
## 📊 现状分析
### 1. 现有功能
-**成长记录功能**(已完成)
- 位置:`/user-package/pages/growth/list.vue`
- 功能:陪伴员填写的每日/周/月反馈
- 数据来源:`growth_record` 表
- 接口:`/api/growth-record/*`
- ⚠️ **学习记录功能**(数据来源不明确)
- 位置:`/user-package/pages/user/learning-record.vue`
- 功能:学习记录列表和详情
- 数据来源:**未明确**
- 接口:`/api/record/*`**接口不存在**
### 2. 核心问题
#### 问题1功能重复
- **成长记录** 和 **学习记录** 本质上是同一个功能
- 两个页面展示相同的数据(陪伴员填写的服务记录)
- 造成用户困惑和维护成本增加
#### 问题2API不存在
```javascript
// learning-record.vue 调用的API
recordApi.getRecordList() // ❌ /api/record/list 不存在
recordApi.getRecordDetail() // ❌ /api/record/detail/:id 不存在
recordApi.getStats() // ❌ /api/record/stats 不存在
```
#### 问题3数据关联不清晰
- 学习记录应该来自成长记录
- 但代码中没有明确的关联关系
- 导致数据显示不一致
## 🎯 解决方案
### 方案A统一为成长记录推荐
**核心思路**:将"学习记录"功能合并到"成长记录",统一入口和数据源
#### 优点
- ✅ 功能统一,用户体验更好
- ✅ 减少维护成本
- ✅ 数据来源明确
- ✅ 已有完整的后端支持
#### 实施步骤
**1. 更新用户中心入口**
```vue
<!-- "学习记录"改为"成长记录" -->
<view class="menu-item" @click="goPage('/user-package/pages/growth/list')">
<text class="menu-icon">📚</text>
<text>成长记录</text>
<text class="arrow">></text>
</view>
```
**2. 删除冗余页面**
- 删除 `learning-record.vue`
- 删除 `learning-record-detail.vue`
- 删除 `recordApi` 相关代码
**3. 完善成长记录功能**
- 添加统计数据展示
- 优化列表和详情页面
- 确保包含所有必要信息
---
### 方案B保留两个功能明确区分
**核心思路**:明确区分两个功能的用途
#### 功能定位
- **成长记录**:陪伴员填写的专业反馈(每日/周/月)
- **学习记录**:家长视角的服务记录汇总
#### 实施步骤
**1. 创建学习记录后端接口**
```java
@RestController
@RequestMapping("/api/record")
public class LearningRecordController {
// 获取学习记录列表(基于成长记录)
@GetMapping("/list")
public Result<Page<LearningRecordVO>> getList(
@RequestParam Long studentId,
@RequestParam(required = false) String period,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size
) {
// 从 growth_record 表查询数据
// 转换为学习记录格式
}
// 获取学习统计
@GetMapping("/stats")
public Result<LearningStatsVO> getStats(@RequestParam Long studentId) {
// 统计总学习时长、次数、平均评分等
}
}
```
**2. 数据转换逻辑**
```java
// 将成长记录转换为学习记录
LearningRecordVO vo = new LearningRecordVO();
vo.setId(growthRecord.getId());
vo.setServiceDate(growthRecord.getRecordDate());
vo.setContent(growthRecord.getContent());
vo.setTeacherName(teacher.getName());
vo.setStudentName(student.getName());
// 从签到记录获取服务时长
CheckInRecord checkIn = getCheckInByOrderId(growthRecord.getOrderId());
vo.setDuration(calculateDuration(checkIn));
// 从评价获取评分
Review review = getReviewByOrderId(growthRecord.getOrderId());
vo.setRating(review != null ? review.getRating() : null);
```
## 📋 推荐方案详细实施
### 采用方案A统一为成长记录
#### 第一步:更新前端路由和入口
**1. 修改用户中心入口**
文件位置:`peidu/uniapp/src/pages/user/index.vue`
```vue
<!-- 修改前 -->
<view class="menu-item" @click="goPage('/user-package/pages/user/learning-record')">
<text class="menu-icon">📚</text>
<text>学习记录</text>
<text class="arrow">></text>
</view>
<!-- 修改后 -->
<view class="menu-item" @click="goPage('/user-package/pages/growth/list')">
<text class="menu-icon">📚</text>
<text>成长记录</text>
<text class="arrow">></text>
</view>
```
**2. 更新pages.json配置**
删除学习记录相关页面配置:
```json
{
"path": "pages/user/learning-record",
"style": {
"navigationBarTitleText": "学习记录"
}
},
{
"path": "pages/user/learning-record-detail",
"style": {
"navigationBarTitleText": "记录详情"
}
}
```
#### 第二步:完善成长记录功能
**1. 添加统计数据展示**
修改 `peidu/uniapp/src/user-package/pages/growth/list.vue`
```vue
<template>
<view class="growth-page">
<!-- 新增统计卡片 -->
<view class="stats-card">
<view class="stat-item">
<text class="stat-value">{{ stats.totalHours }}</text>
<text class="stat-label">累计学习(小时)</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{ stats.totalSessions }}</text>
<text class="stat-label">服务次数</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{ stats.avgScore }}</text>
<text class="stat-label">平均评分</text>
</view>
</view>
<!-- 原有内容保持不变 -->
...
</view>
</template>
<script>
export default {
data() {
return {
// 新增统计数据
stats: {
totalHours: '0',
totalSessions: 0,
avgScore: '0'
},
// ... 其他数据
}
},
onLoad(options) {
this.studentId = options.studentId || this.getDefaultStudentId()
this.loadStats() // 新增:加载统计数据
this.loadRecordList()
},
methods: {
// 新增:加载统计数据
async loadStats() {
try {
const res = await request.get('/api/growth-record/parent/stats', {
studentId: this.studentId
})
if (res.code === 200 && res.data) {
this.stats = {
totalHours: res.data.totalHours || '0',
totalSessions: res.data.totalSessions || 0,
avgScore: res.data.avgScore || '0'
}
}
} catch (e) {
console.error('加载统计数据失败:', e)
}
},
// ... 其他方法
}
}
</script>
<style lang="scss" scoped>
// 新增:统计卡片样式
.stats-card {
background: linear-gradient(135deg, #2d9687 0%, #3da896 100%);
padding: 40rpx 30rpx;
display: flex;
justify-content: space-around;
align-items: center;
.stat-item {
flex: 1;
text-align: center;
.stat-value {
display: block;
font-size: 48rpx;
font-weight: bold;
color: #fff;
margin-bottom: 12rpx;
}
.stat-label {
display: block;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
}
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background: rgba(255, 255, 255, 0.3);
}
}
// ... 其他样式
</style>
```
**2. 完善详情页面信息**
修改 `peidu/uniapp/src/user-package/pages/growth/detail.vue`,确保包含:
- ✅ 服务时间、时长
- ✅ 学习内容
- ✅ 陪伴员评价
- ✅ 照片记录
- ✅ 学生表现
- ✅ 视频记录(如果有)
#### 第三步:创建后端统计接口
**文件位置**`peidu/backend/src/main/java/com/peidu/controller/GrowthRecordController.java`
```java
/**
* 获取家长端统计数据
*/
@ApiOperation("获取家长端统计数据")
@GetMapping("/parent/stats")
public Result<Map<String, Object>> getParentStats(
@RequestParam Long studentId,
HttpServletRequest request) {
try {
log.info("获取家长端统计数据: studentId={}", studentId);
// 查询该学生的所有成长记录
QueryWrapper<GrowthRecord> wrapper = new QueryWrapper<>();
wrapper.eq("student_id", studentId)
.eq("record_type", "daily")
.orderByDesc("record_date");
List<GrowthRecord> records = growthRecordService.list(wrapper);
// 统计数据
int totalMinutes = 0;
int totalSessions = records.size();
double totalRating = 0;
int ratingCount = 0;
for (GrowthRecord record : records) {
// 计算服务时长
if (record.getOrderId() != null) {
Order order = orderService.getById(record.getOrderId());
if (order != null) {
// 从签到记录获取实际服务时长
QueryWrapper<CheckInRecord> checkInWrapper = new QueryWrapper<>();
checkInWrapper.eq("order_id", record.getOrderId())
.orderByAsc("check_time");
List<CheckInRecord> checkInRecords = checkInRecordMapper.selectList(checkInWrapper);
LocalDateTime checkInTime = null;
LocalDateTime checkOutTime = null;
for (CheckInRecord checkRecord : checkInRecords) {
if ("checkin".equals(checkRecord.getCheckType()) && checkInTime == null) {
checkInTime = checkRecord.getCheckTime();
} else if ("checkout".equals(checkRecord.getCheckType())) {
checkOutTime = checkRecord.getCheckTime();
}
}
if (checkInTime != null && checkOutTime != null) {
Duration duration = Duration.between(checkInTime, checkOutTime);
totalMinutes += (int) duration.toMinutes();
}
// 获取评分
QueryWrapper<Review> reviewWrapper = new QueryWrapper<>();
reviewWrapper.eq("order_id", record.getOrderId());
Review review = reviewMapper.selectOne(reviewWrapper);
if (review != null && review.getRating() != null) {
totalRating += review.getRating();
ratingCount++;
}
}
}
}
// 构建返回数据
Map<String, Object> stats = new HashMap<>();
stats.put("totalHours", String.format("%.1f", totalMinutes / 60.0));
stats.put("totalSessions", totalSessions);
stats.put("avgScore", ratingCount > 0 ?
String.format("%.1f", totalRating / ratingCount) : "0");
log.info("统计结果: {}", stats);
return Result.success(stats);
} catch (Exception e) {
log.error("获取统计数据失败", e);
return Result.error(e.getMessage());
}
}
/**
* 获取家长端成长记录列表
*/
@ApiOperation("获取家长端成长记录列表")
@GetMapping("/parent/list")
public Result<Page<GrowthRecordVO>> getParentList(
@RequestParam Long studentId,
@RequestParam(required = false) String recordType,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
try {
log.info("获取家长端成长记录列表: studentId={}, recordType={}, page={}, size={}",
studentId, recordType, page, size);
Page<GrowthRecord> pageParam = new Page<>(page, size);
QueryWrapper<GrowthRecord> wrapper = new QueryWrapper<>();
wrapper.eq("student_id", studentId);
if (recordType != null && !"all".equals(recordType)) {
wrapper.eq("record_type", recordType);
}
wrapper.orderByDesc("record_date");
Page<GrowthRecord> recordPage = growthRecordService.page(pageParam, wrapper);
// 转换为VO
Page<GrowthRecordVO> voPage = new Page<>();
voPage.setCurrent(recordPage.getCurrent());
voPage.setSize(recordPage.getSize());
voPage.setTotal(recordPage.getTotal());
List<GrowthRecordVO> voList = new ArrayList<>();
for (GrowthRecord record : recordPage.getRecords()) {
GrowthRecordVO vo = convertToVO(record);
voList.add(vo);
}
voPage.setRecords(voList);
log.info("查询成功,共{}条记录", voPage.getTotal());
return Result.success(voPage);
} catch (Exception e) {
log.error("获取家长端成长记录列表失败", e);
return Result.error(e.getMessage());
}
}
/**
* 转换为VO对象
*/
private GrowthRecordVO convertToVO(GrowthRecord record) {
GrowthRecordVO vo = new GrowthRecordVO();
vo.setId(record.getId());
vo.setRecordDate(record.getRecordDate());
vo.setRecordType(record.getRecordType());
vo.setContent(record.getContent());
vo.setImageList(record.getImageList());
vo.setVideoList(record.getVideoList());
// 设置类型名称
String typeName = "每日反馈";
if ("weekly".equals(record.getRecordType())) {
typeName = "周反馈";
} else if ("monthly".equals(record.getRecordType())) {
typeName = "月反馈";
}
vo.setRecordTypeName(typeName);
// 获取学生信息
if (record.getStudentId() != null) {
Student student = studentMapper.selectById(record.getStudentId());
if (student != null) {
vo.setStudentName(student.getName());
}
}
// 获取教师信息
if (record.getTeacherId() != null) {
Teacher teacher = teacherMapper.selectById(record.getTeacherId());
if (teacher != null) {
vo.setTeacherName(teacher.getName());
}
}
// 获取服务时长
if (record.getOrderId() != null) {
QueryWrapper<CheckInRecord> checkInWrapper = new QueryWrapper<>();
checkInWrapper.eq("order_id", record.getOrderId())
.orderByAsc("check_time");
List<CheckInRecord> checkInRecords = checkInRecordMapper.selectList(checkInWrapper);
LocalDateTime checkInTime = null;
LocalDateTime checkOutTime = null;
for (CheckInRecord checkRecord : checkInRecords) {
if ("checkin".equals(checkRecord.getCheckType()) && checkInTime == null) {
checkInTime = checkRecord.getCheckTime();
} else if ("checkout".equals(checkRecord.getCheckType())) {
checkOutTime = checkRecord.getCheckTime();
}
}
if (checkInTime != null && checkOutTime != null) {
Duration duration = Duration.between(checkInTime, checkOutTime);
int minutes = (int) duration.toMinutes();
int hours = minutes / 60;
int mins = minutes % 60;
vo.setDurationText(hours > 0 ?
String.format("%d小时%d分钟", hours, mins) :
String.format("%d分钟", mins));
}
}
return vo;
}
```
**需要添加的依赖注入**
```java
@Autowired
private StudentMapper studentMapper;
@Autowired
private TeacherMapper teacherMapper;
@Autowired
private ReviewMapper reviewMapper;
```
#### 第四步更新VO类
**文件位置**`peidu/backend/src/main/java/com/peidu/vo/GrowthRecordVO.java`
```java
/**
* 成长记录VO
*/
@Data
public class GrowthRecordVO {
private Long id;
private Long orderId;
private Long studentId;
private String studentName; // 新增
private Long teacherId;
private String teacherName; // 新增
private LocalDate recordDate;
private String recordType;
private String recordTypeName; // 新增:类型名称
private String content;
private String imageList;
private String videoList;
private String durationText; // 新增:服务时长文本
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
```
#### 第五步:清理冗余代码
**1. 删除文件**
```bash
# 删除学习记录页面
peidu/uniapp/src/user-package/pages/user/learning-record.vue
peidu/uniapp/src/user-package/pages/user/learning-record-detail.vue
# 如果存在旧版本,也一并删除
peidu/uniapp/user-package/pages/user/learning-record.vue
```
**2. 清理API定义**
修改 `peidu/uniapp/src/api/index.js`删除recordApi
```javascript
// 删除这部分代码
export const recordApi = {
getRecordList(params) {
return request.get('/api/record/list', { params })
},
getRecordDetail(id) {
return request.get(`/api/record/detail/${id}`)
},
getStats() {
return request.get('/api/record/stats')
}
}
```
## 📝 数据关联说明
### 成长记录包含的完整信息
```
成长记录 (growth_record)
├── 基础信息
│ ├── 服务日期 (record_date)
│ ├── 记录类型 (record_type: daily/weekly/monthly)
│ └── 创建时间 (create_time)
├── 关联信息
│ ├── 订单ID (order_id) → 关联订单表
│ ├── 学生ID (student_id) → 关联学生表
│ └── 教师ID (teacher_id) → 关联教师表
├── 内容信息
│ ├── 学习内容 (content)
│ ├── 照片列表 (image_list)
│ ├── 视频列表 (video_list)
│ └── 周/月总结 (summary)
└── 扩展信息(通过关联获取)
├── 服务时长 → 从 check_in_record 表计算
├── 学生姓名 → 从 student 表获取
├── 教师姓名 → 从 teacher 表获取
└── 评价评分 → 从 review 表获取
```
### 数据流转图
```
陪伴员填写成长记录
保存到 growth_record 表
家长端查看成长记录
展示完整信息:
- 基础信息(日期、内容、照片)
- 服务时长(从签到记录计算)
- 评价评分(从评价表获取)
- 学生/教师信息(从关联表获取)
```
## ✅ 实施检查清单
### 前端修改
- [ ] 修改用户中心入口user/index.vue
- [ ] 完善成长记录列表页growth/list.vue
- [ ] 添加统计卡片
- [ ] 调用统计接口
- [ ] 确认详情页信息完整growth/detail.vue
- [ ] 删除学习记录页面文件
- [ ] 清理recordApi定义
- [ ] 更新pages.json配置
### 后端修改
- [ ] 添加家长端统计接口
- [ ] 添加家长端列表接口
- [ ] 完善GrowthRecordVO类
- [ ] 添加必要的Mapper注入
- [ ] 测试接口返回数据
### 测试验证
- [ ] 用户中心入口跳转正确
- [ ] 统计数据显示正确
- [ ] 列表数据加载正常
- [ ] 详情页信息完整
- [ ] 服务时长计算准确
- [ ] 评分显示正确
## 🎉 预期效果
完成后,家长端将拥有统一的"成长记录"功能:
1. **统计概览**
- 累计学习时长
- 服务次数
- 平均评分
2. **记录列表**
- 每日反馈
- 周反馈
- 月反馈
3. **详细信息**
- 服务时间、时长
- 学习内容
- 陪伴员评价
- 照片/视频记录
- 学生表现
4. **数据来源清晰**
- 所有数据来自 `growth_record`
- 通过关联表获取完整信息
- 数据一致性有保障