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

15 KiB
Raw Blame History

学习记录功能完善 - 实施步骤

🎯 目标

将"学习记录"功能统一到"成长记录",消除功能重复,明确数据来源

📋 实施步骤

步骤1修改前端入口5分钟

文件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完善成长记录列表页15分钟

文件peidu/uniapp/src/user-package/pages/growth/list.vue

2.1 添加统计数据

<template>page-header 后面添加:

<!-- 统计卡片 -->
<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属性

data() {
  return {
    // 新增统计数据
    stats: {
      totalHours: '0',
      totalSessions: 0,
      avgScore: '0'
    },
    // ... 保持原有数据
  }
}

2.3 添加loadStats方法

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中调用

onLoad(options) {
  console.log('=== 成长记录页面加载 ===')
  this.studentId = options.studentId || this.getDefaultStudentId()
  console.log('studentId:', this.studentId)
  this.loadStats()  // 新增这一行
  this.loadRecordList()
}

2.5 添加样式

<style> 中添加:

.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

在类的开头添加必要的依赖注入:

@Autowired
private com.peidu.mapper.StudentMapper studentMapper;

@Autowired
private com.peidu.mapper.TeacherMapper teacherMapper;

@Autowired
private com.peidu.mapper.ReviewMapper reviewMapper;

然后添加两个新接口(在文件末尾添加):

/**
 * 获取家长端统计数据
 */
@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

添加新字段:

/**
 * 学生姓名
 */
private String studentName;

/**
 * 教师姓名
 */
private String teacherName;

/**
 * 记录类型名称
 */
private String recordTypeName;

/**
 * 服务时长文本
 */
private String durationText;

步骤5清理冗余代码5分钟

5.1 删除学习记录页面文件

# 在项目根目录执行
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 的定义:

// 删除这部分代码大约在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

// 找到export default删除recordApi
export default {
  // ... 其他API
  // recordApi,  // 删除这一行
  // ... 其他API
}

步骤6编译和测试10分钟

6.1 编译后端

cd peidu/backend
mvn clean compile

6.2 重启后端服务

# 停止现有服务
# 启动新服务

6.3 编译前端

在HBuilderX中

  1. 右键项目
  2. 选择"运行" → "运行到小程序模拟器"
  3. 等待编译完成

6.4 测试功能

  1. 测试入口

    • 打开家长端
    • 进入"我的"页面
    • 点击"成长记录"
    • 应该跳转到成长记录列表页
  2. 测试统计数据

    • 查看顶部统计卡片
    • 应该显示:累计学习时长、服务次数、平均评分
  3. 测试列表数据

    • 查看记录列表
    • 应该显示所有成长记录
    • 每条记录应包含:日期、类型、学生、陪伴员、服务时长、内容预览
  4. 测试详情页

    • 点击任意记录
    • 应该显示完整的成长记录详情

验证清单

  • 用户中心"成长记录"入口正常
  • 统计数据显示正确
  • 列表数据加载正常
  • 详情页信息完整
  • 服务时长计算准确
  • 评分显示正确(如果有评价)
  • 照片/视频正常显示
  • 学习记录页面已删除
  • recordApi已清理
  • 后端编译无错误
  • 前端编译无错误

🎉 完成标志

当所有验证项都通过后,学习记录功能完善工作即完成!

家长端现在拥有统一的"成长记录"功能,数据来源清晰,信息完整。