peixue-dev/peidu/docs/fixes/2026-01-23-学习记录功能完善/✅实施步骤.md

514 lines
15 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修改前端入口5分钟
**文件**`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完善成长记录列表页15分钟
**文件**`peidu/uniapp/src/user-package/pages/growth/list.vue`
#### 2.1 添加统计数据
`<template>``page-header` 后面添加:
```vue
<!-- 统计卡片 -->
<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>
```
#### 2.2 添加data属性
```javascript
data() {
return {
// 新增统计数据
stats: {
totalHours: '0',
totalSessions: 0,
avgScore: '0'
},
// ... 保持原有数据
}
}
```
#### 2.3 添加loadStats方法
```javascript
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)
}
},
// ... 保持原有方法
}
```
#### 2.4 在onLoad中调用
```javascript
onLoad(options) {
console.log('=== 成长记录页面加载 ===')
this.studentId = options.studentId || this.getDefaultStudentId()
console.log('studentId:', this.studentId)
this.loadStats() // 新增这一行
this.loadRecordList()
}
```
#### 2.5 添加样式
`<style>` 中添加:
```scss
.stats-card {
background: linear-gradient(135deg, #2d9687 0%, #3da896 100%);
padding: 40rpx 30rpx;
display: flex;
justify-content: space-around;
align-items: center;
margin-bottom: 20rpx;
.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);
}
}
```
### 步骤3添加后端统计接口20分钟
**文件**`peidu/backend/src/main/java/com/peidu/controller/GrowthRecordController.java`
在类的开头添加必要的依赖注入:
```java
@Autowired
private com.peidu.mapper.StudentMapper studentMapper;
@Autowired
private com.peidu.mapper.TeacherMapper teacherMapper;
@Autowired
private com.peidu.mapper.ReviewMapper reviewMapper;
```
然后添加两个新接口(在文件末尾添加):
```java
/**
* 获取家长端统计数据
*/
@ApiOperation("获取家长端统计数据")
@GetMapping("/parent/stats")
public Result<Map<String, Object>> getParentStats(
@RequestParam Long studentId) {
try {
log.info("获取家长端统计数据: studentId={}", studentId);
// 查询该学生的所有成长记录
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.peidu.entity.GrowthRecord> wrapper =
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
wrapper.eq("student_id", studentId)
.eq("record_type", "daily")
.orderByDesc("record_date");
java.util.List<com.peidu.entity.GrowthRecord> records = growthRecordService.list(wrapper);
// 统计数据
int totalMinutes = 0;
int totalSessions = records.size();
double totalRating = 0;
int ratingCount = 0;
for (com.peidu.entity.GrowthRecord record : records) {
// 计算服务时长
if (record.getOrderId() != null) {
com.peidu.entity.Order order = orderService.getById(record.getOrderId());
if (order != null) {
// 从签到记录获取实际服务时长
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.peidu.entity.CheckInRecord> checkInWrapper =
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
checkInWrapper.eq("order_id", record.getOrderId())
.orderByAsc("check_time");
java.util.List<com.peidu.entity.CheckInRecord> checkInRecords = checkInRecordMapper.selectList(checkInWrapper);
java.time.LocalDateTime checkInTime = null;
java.time.LocalDateTime checkOutTime = null;
for (com.peidu.entity.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) {
java.time.Duration duration = java.time.Duration.between(checkInTime, checkOutTime);
totalMinutes += (int) duration.toMinutes();
}
// 获取评分
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.peidu.entity.Review> reviewWrapper =
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
reviewWrapper.eq("order_id", record.getOrderId());
com.peidu.entity.Review review = reviewMapper.selectOne(reviewWrapper);
if (review != null && review.getRating() != null) {
totalRating += review.getRating();
ratingCount++;
}
}
}
}
// 构建返回数据
java.util.Map<String, Object> stats = new java.util.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<com.baomidou.mybatisplus.extension.plugins.pagination.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);
com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.peidu.entity.GrowthRecord> pageParam =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, size);
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.peidu.entity.GrowthRecord> wrapper =
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
wrapper.eq("student_id", studentId);
if (recordType != null && !"all".equals(recordType)) {
wrapper.eq("record_type", recordType);
}
wrapper.orderByDesc("record_date");
com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.peidu.entity.GrowthRecord> recordPage =
growthRecordService.page(pageParam, wrapper);
// 转换为VO
com.baomidou.mybatisplus.extension.plugins.pagination.Page<GrowthRecordVO> voPage =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
voPage.setCurrent(recordPage.getCurrent());
voPage.setSize(recordPage.getSize());
voPage.setTotal(recordPage.getTotal());
java.util.List<GrowthRecordVO> voList = new java.util.ArrayList<>();
for (com.peidu.entity.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(com.peidu.entity.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) {
com.peidu.entity.Student student = studentMapper.selectById(record.getStudentId());
if (student != null) {
vo.setStudentName(student.getName());
}
}
// 获取教师信息
if (record.getTeacherId() != null) {
com.peidu.entity.Teacher teacher = teacherMapper.selectById(record.getTeacherId());
if (teacher != null) {
vo.setTeacherName(teacher.getName());
}
}
// 获取服务时长
if (record.getOrderId() != null) {
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.peidu.entity.CheckInRecord> checkInWrapper =
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
checkInWrapper.eq("order_id", record.getOrderId())
.orderByAsc("check_time");
java.util.List<com.peidu.entity.CheckInRecord> checkInRecords = checkInRecordMapper.selectList(checkInWrapper);
java.time.LocalDateTime checkInTime = null;
java.time.LocalDateTime checkOutTime = null;
for (com.peidu.entity.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) {
java.time.Duration duration = java.time.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;
}
```
### 步骤4更新VO类5分钟
**文件**`peidu/backend/src/main/java/com/peidu/vo/GrowthRecordVO.java`
添加新字段:
```java
/**
* 学生姓名
*/
private String studentName;
/**
* 教师姓名
*/
private String teacherName;
/**
* 记录类型名称
*/
private String recordTypeName;
/**
* 服务时长文本
*/
private String durationText;
```
### 步骤5清理冗余代码5分钟
#### 5.1 删除学习记录页面文件
```bash
# 在项目根目录执行
del peidu\uniapp\src\user-package\pages\user\learning-record.vue
del peidu\uniapp\src\user-package\pages\user\learning-record-detail.vue
```
#### 5.2 清理API定义
**文件**`peidu/uniapp/src/api/index.js`
找到并删除 `recordApi` 的定义:
```javascript
// 删除这部分代码大约在436行
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')
}
}
```
同时删除导出列表中的 `recordApi`
```javascript
// 找到export default删除recordApi
export default {
// ... 其他API
// recordApi, // 删除这一行
// ... 其他API
}
```
### 步骤6编译和测试10分钟
#### 6.1 编译后端
```bash
cd peidu/backend
mvn clean compile
```
#### 6.2 重启后端服务
```bash
# 停止现有服务
# 启动新服务
```
#### 6.3 编译前端
在HBuilderX中
1. 右键项目
2. 选择"运行" → "运行到小程序模拟器"
3. 等待编译完成
#### 6.4 测试功能
1. **测试入口**
- 打开家长端
- 进入"我的"页面
- 点击"成长记录"
- ✅ 应该跳转到成长记录列表页
2. **测试统计数据**
- 查看顶部统计卡片
- ✅ 应该显示:累计学习时长、服务次数、平均评分
3. **测试列表数据**
- 查看记录列表
- ✅ 应该显示所有成长记录
- ✅ 每条记录应包含:日期、类型、学生、陪伴员、服务时长、内容预览
4. **测试详情页**
- 点击任意记录
- ✅ 应该显示完整的成长记录详情
## ✅ 验证清单
- [ ] 用户中心"成长记录"入口正常
- [ ] 统计数据显示正确
- [ ] 列表数据加载正常
- [ ] 详情页信息完整
- [ ] 服务时长计算准确
- [ ] 评分显示正确(如果有评价)
- [ ] 照片/视频正常显示
- [ ] 学习记录页面已删除
- [ ] recordApi已清理
- [ ] 后端编译无错误
- [ ] 前端编译无错误
## 🎉 完成标志
当所有验证项都通过后,学习记录功能完善工作即完成!
家长端现在拥有统一的"成长记录"功能,数据来源清晰,信息完整。