个人学习记录
This commit is contained in:
@@ -7,26 +7,46 @@
|
||||
end-placeholder="结束日期" @change="handleDateChange" />
|
||||
</div>
|
||||
|
||||
<div class="records-list">
|
||||
<div class="record-item" v-for="record in records" :key="record.id">
|
||||
<div class="record-icon">
|
||||
<i :class="getRecordIcon(record.type)"></i>
|
||||
</div>
|
||||
<div class="record-content">
|
||||
<h3>{{ record.title }}</h3>
|
||||
<p class="record-description">{{ record.description }}</p>
|
||||
<div class="record-meta">
|
||||
<span class="record-type">{{ record.typeName }}</span>
|
||||
<span class="record-duration">学习时长:{{ record.duration }}分钟</span>
|
||||
<span class="record-date">{{ record.learnDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="record-progress">
|
||||
<div class="progress-circle" :class="`progress-${record.status}`">
|
||||
<span>{{ record.progress }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 学习时长图表 -->
|
||||
<div class="chart-container">
|
||||
<h3>学习时长统计</h3>
|
||||
<div ref="chartRef" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 学习记录分页表格 -->
|
||||
<div class="records-table" v-loading="tableLoading">
|
||||
<h3>学习记录明细</h3>
|
||||
<el-table :data="tableData" stripe style="width: 100%">
|
||||
<el-table-column prop="resourceTitle" label="资源标题" width="200" />
|
||||
<el-table-column prop="resourceTypeName" label="类型" width="100" />
|
||||
<el-table-column label="学习时长" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ formatDuration(row.totalDuration) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="learnCount" label="学习次数" width="100" />
|
||||
<el-table-column prop="statDate" label="统计日期" width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.statDate ? new Date(row.statDate).toLocaleDateString('zh-CN') : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="完成状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isComplete ? 'success' : 'info'">{{ row.isComplete ? '已完成' : '学习中' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="totalElements"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
style="margin-top: 20px; justify-content: center"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,18 +54,203 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ElDatePicker } from 'element-plus';
|
||||
import { ref, onMounted, nextTick } from 'vue';
|
||||
import { ElDatePicker, ElMessage, ElTable, ElTableColumn, ElPagination, ElTag } from 'element-plus';
|
||||
import { UserCenterLayout } from '@/views/user/user-center';
|
||||
const dateRange = ref<[Date, Date] | null>(null);
|
||||
const records = ref<any[]>([]);
|
||||
import { learningRecordApi } from '@/apis/study/learning-record';
|
||||
import * as echarts from 'echarts';
|
||||
import type { ECharts } from 'echarts';
|
||||
|
||||
defineOptions({
|
||||
name: 'LearningRecordsView'
|
||||
});
|
||||
|
||||
// 默认最近7天
|
||||
const getDefaultDateRange = (): [Date, Date] => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setDate(start.getDate() - 7);
|
||||
return [start, end];
|
||||
};
|
||||
|
||||
const dateRange = ref<[Date, Date]>(getDefaultDateRange());
|
||||
const loading = ref(false);
|
||||
|
||||
// 图表相关
|
||||
const chartRef = ref<HTMLElement>();
|
||||
let chartInstance: ECharts | null = null;
|
||||
|
||||
// 分页表格相关
|
||||
const tableData = ref<any[]>([]);
|
||||
const tableLoading = ref(false);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const totalElements = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载学习记录
|
||||
// 初始化图表
|
||||
nextTick(() => {
|
||||
if (chartRef.value) {
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
loadChartData();
|
||||
}
|
||||
});
|
||||
loadTableData();
|
||||
});
|
||||
|
||||
function handleDateChange() {
|
||||
// TODO: 根据日期筛选记录
|
||||
// 日期变化时重新加载图表和表格
|
||||
loadChartData();
|
||||
loadTableData();
|
||||
}
|
||||
|
||||
// 加载图表数据
|
||||
async function loadChartData() {
|
||||
if (!chartInstance) return;
|
||||
|
||||
try {
|
||||
const [startDate, endDate] = dateRange.value;
|
||||
const startTime = startDate.toISOString().split('T')[0];
|
||||
const endTime = endDate.toISOString().split('T')[0];
|
||||
|
||||
console.log('📅 查询时间范围:', startTime, '至', endTime);
|
||||
const result = await learningRecordApi.getUserRecordRange(startTime, endTime);
|
||||
console.log('📊 图表数据返回:', result);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const dailyData = result.data.dailyDuration || [];
|
||||
console.log('📈 每日数据:', dailyData);
|
||||
|
||||
const dates = dailyData.map((item: any) => item.statDate || item.date);
|
||||
// 转换为分钟,保留1位小数(避免小时长被四舍五入为0)
|
||||
const durations = dailyData.map((item: any) => {
|
||||
const seconds = item.duration || item.totalDuration || 0;
|
||||
return Math.round(seconds / 60 * 10) / 10; // 保留1位小数
|
||||
});
|
||||
|
||||
console.log('📆 日期数组:', dates);
|
||||
console.log('⏱️ 时长数组(分钟):', durations);
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '每日学习时长(分钟)',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: (params: any) => {
|
||||
const data = params[0];
|
||||
const minutes = data.value;
|
||||
if (minutes < 1) {
|
||||
// 小于1分钟,显示秒数
|
||||
return `${data.axisValue}<br/>${data.seriesName}: ${Math.round(minutes * 60)}秒`;
|
||||
}
|
||||
return `${data.axisValue}<br/>${data.seriesName}: ${minutes}分钟`;
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
rotate: 45
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '时长(分钟)'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '学习时长',
|
||||
data: durations,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: 'rgba(198, 40, 40, 0.1)'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#C62828'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
chartInstance.setOption(option);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载图表数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
async function loadTableData() {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
const pageRequest = {
|
||||
filter: {},
|
||||
pageParam: {
|
||||
pageNumber: currentPage.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
};
|
||||
|
||||
console.log('📤 请求参数:', pageRequest);
|
||||
const result = await learningRecordApi.getUserRecordRangePage(pageRequest);
|
||||
console.log('📥 返回结果:', result);
|
||||
|
||||
if (result.success) {
|
||||
// 优先使用dataList,如果没有则使用data.dataList
|
||||
const dataList = result.dataList || result.data?.dataList || [];
|
||||
console.log('📊 表格数据:', dataList);
|
||||
|
||||
tableData.value = dataList;
|
||||
totalElements.value = result.data?.pageParam?.totalElements || result.pageParam?.totalElements || 0;
|
||||
} else {
|
||||
ElMessage.error(result.message || '加载记录失败');
|
||||
tableData.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载表格数据失败:', error);
|
||||
ElMessage.error('加载记录失败');
|
||||
tableData.value = [];
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 分页大小变化
|
||||
function handleSizeChange(size: number) {
|
||||
pageSize.value = size;
|
||||
loadTableData();
|
||||
}
|
||||
|
||||
// 当前页变化
|
||||
function handleCurrentChange(page: number) {
|
||||
currentPage.value = page;
|
||||
loadTableData();
|
||||
}
|
||||
|
||||
// 格式化学习时长(秒 → 可读格式)
|
||||
function formatDuration(seconds: number): string {
|
||||
if (!seconds || seconds === 0) return '0分钟';
|
||||
|
||||
if (seconds < 60) {
|
||||
return `${seconds}秒`;
|
||||
}
|
||||
|
||||
if (seconds < 3600) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
return `${minutes}分钟`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
|
||||
if (minutes === 0) {
|
||||
return `${hours}小时`;
|
||||
}
|
||||
|
||||
return `${hours}小时${minutes}分钟`;
|
||||
}
|
||||
|
||||
function getRecordIcon(type: string) {
|
||||
@@ -170,4 +375,44 @@ function getRecordIcon(type: string) {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin-bottom: 32px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.records-table {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -238,7 +238,7 @@ onMounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.my-achievements {
|
||||
// padding: 20px 0;
|
||||
height: 100%;
|
||||
// height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.achievements-header {
|
||||
|
||||
@@ -58,7 +58,7 @@ const menus = computed(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
height: calc(100vh - 76px);
|
||||
min-height: calc(100vh - 76px);
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user