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

19 KiB
Raw Blame History

学习记录功能完善 - 问题分析与解决方案

📊 现状分析

1. 现有功能

  • 成长记录功能(已完成)

    • 位置:/user-package/pages/growth/list.vue
    • 功能:陪伴员填写的每日/周/月反馈
    • 数据来源:growth_record
    • 接口:/api/growth-record/*
  • ⚠️ 学习记录功能(数据来源不明确)

    • 位置:/user-package/pages/user/learning-record.vue
    • 功能:学习记录列表和详情
    • 数据来源:未明确
    • 接口:/api/record/*接口不存在

2. 核心问题

问题1功能重复

  • 成长记录学习记录 本质上是同一个功能
  • 两个页面展示相同的数据(陪伴员填写的服务记录)
  • 造成用户困惑和维护成本增加

问题2API不存在

// 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注入
  • 测试接口返回数据

测试验证

  • 用户中心入口跳转正确
  • 统计数据显示正确
  • 列表数据加载正常
  • 详情页信息完整
  • 服务时长计算准确
  • 评分显示正确

🎉 预期效果

完成后,家长端将拥有统一的"成长记录"功能:

  1. 统计概览

    • 累计学习时长
    • 服务次数
    • 平均评分
  2. 记录列表

    • 每日反馈
    • 周反馈
    • 月反馈
  3. 详细信息

    • 服务时间、时长
    • 学习内容
    • 陪伴员评价
    • 照片/视频记录
    • 学生表现
  4. 数据来源清晰

    • 所有数据来自 growth_record
    • 通过关联表获取完整信息
    • 数据一致性有保障