19 KiB
19 KiB
学习记录功能完善 - 问题分析与解决方案
📊 现状分析
1. 现有功能
-
✅ 成长记录功能(已完成)
- 位置:
/user-package/pages/growth/list.vue - 功能:陪伴员填写的每日/周/月反馈
- 数据来源:
growth_record表 - 接口:
/api/growth-record/*
- 位置:
-
⚠️ 学习记录功能(数据来源不明确)
- 位置:
/user-package/pages/user/learning-record.vue - 功能:学习记录列表和详情
- 数据来源:未明确
- 接口:
/api/record/*(接口不存在)
- 位置:
2. 核心问题
问题1:功能重复
- 成长记录 和 学习记录 本质上是同一个功能
- 两个页面展示相同的数据(陪伴员填写的服务记录)
- 造成用户困惑和维护成本增加
问题2:API不存在
// learning-record.vue 调用的API
recordApi.getRecordList() // ❌ /api/record/list 不存在
recordApi.getRecordDetail() // ❌ /api/record/detail/:id 不存在
recordApi.getStats() // ❌ /api/record/stats 不存在
问题3:数据关联不清晰
- 学习记录应该来自成长记录
- 但代码中没有明确的关联关系
- 导致数据显示不一致
🎯 解决方案
方案A:统一为成长记录(推荐)✅
核心思路:将"学习记录"功能合并到"成长记录",统一入口和数据源
优点
- ✅ 功能统一,用户体验更好
- ✅ 减少维护成本
- ✅ 数据来源明确
- ✅ 已有完整的后端支持
实施步骤
1. 更新用户中心入口
<!-- 将"学习记录"改为"成长记录" -->
<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. 创建学习记录后端接口
@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. 数据转换逻辑
// 将成长记录转换为学习记录
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
<!-- 修改前 -->
<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配置
删除学习记录相关页面配置:
{
"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:
<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
/**
* 获取家长端统计数据
*/
@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;
}
需要添加的依赖注入:
@Autowired
private StudentMapper studentMapper;
@Autowired
private TeacherMapper teacherMapper;
@Autowired
private ReviewMapper reviewMapper;
第四步:更新VO类
文件位置:peidu/backend/src/main/java/com/peidu/vo/GrowthRecordVO.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. 删除文件
# 删除学习记录页面
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:
// 删除这部分代码
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注入
- 测试接口返回数据
测试验证
- 用户中心入口跳转正确
- 统计数据显示正确
- 列表数据加载正常
- 详情页信息完整
- 服务时长计算准确
- 评分显示正确
🎉 预期效果
完成后,家长端将拥有统一的"成长记录"功能:
-
统计概览
- 累计学习时长
- 服务次数
- 平均评分
-
记录列表
- 每日反馈
- 周反馈
- 月反馈
-
详细信息
- 服务时间、时长
- 学习内容
- 陪伴员评价
- 照片/视频记录
- 学生表现
-
数据来源清晰
- 所有数据来自
growth_record表 - 通过关联表获取完整信息
- 数据一致性有保障
- 所有数据来自