feat: 学习页展示任务学习统计
This commit is contained in:
+125
-2
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
@@ -12,6 +12,15 @@ interface TaskItem {
|
|||||||
priority: number | string;
|
priority: number | string;
|
||||||
taskNum: string;
|
taskNum: string;
|
||||||
taskId: number;
|
taskId: number;
|
||||||
|
reportCount: number;
|
||||||
|
fragmentCount: number;
|
||||||
|
effectiveTime: number;
|
||||||
|
statsLoaded: boolean;
|
||||||
|
sessionCount: number;
|
||||||
|
todayEffectiveTime: number;
|
||||||
|
weekEffectiveTime: number;
|
||||||
|
avgEffectiveTime: number;
|
||||||
|
avgEffectivenessRatio: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks = ref<TaskItem[]>([]);
|
const tasks = ref<TaskItem[]>([]);
|
||||||
@@ -22,9 +31,40 @@ const selectedTask = computed(() =>
|
|||||||
tasks.value.find((item) => item.taskId === selectedTaskId.value) || null
|
tasks.value.find((item) => item.taskId === selectedTaskId.value) || null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formatEffectiveTime = (seconds: number): string => {
|
||||||
|
if (!seconds || seconds <= 0) return "0分钟";
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
if (hours > 0) return `${hours}小时${minutes}分钟`;
|
||||||
|
return `${minutes}分钟`;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadTaskStats(taskNum: string) {
|
||||||
|
const task = tasks.value.find(t => t.taskNum === taskNum);
|
||||||
|
if (!task || task.statsLoaded) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/review/tasks/${taskNum}`);
|
||||||
|
if (res?.data) {
|
||||||
|
task.reportCount = res.data.reportCount ?? 0;
|
||||||
|
task.fragmentCount = res.data.fragmentCount ?? 0;
|
||||||
|
task.effectiveTime = res.data.effectiveTime ?? 0;
|
||||||
|
task.sessionCount = res.data.sessionCount ?? 0;
|
||||||
|
task.todayEffectiveTime = res.data.todayEffectiveTime ?? 0;
|
||||||
|
task.weekEffectiveTime = res.data.weekEffectiveTime ?? 0;
|
||||||
|
task.avgEffectiveTime = res.data.avgEffectiveTime ?? 0;
|
||||||
|
task.avgEffectivenessRatio = res.data.avgEffectivenessRatio ?? 0;
|
||||||
|
task.statsLoaded = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 静默处理,不影响主流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fetchTasks = async () => {
|
const fetchTasks = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
|
const previousSelectedTaskId = selectedTaskId.value;
|
||||||
const res = await request.get("/tasks", { pageSize: 100, pageNum: 1 });
|
const res = await request.get("/tasks", { pageSize: 100, pageNum: 1 });
|
||||||
tasks.value = (res?.data?.records || []).map((record: any) => ({
|
tasks.value = (res?.data?.records || []).map((record: any) => ({
|
||||||
title: record.taskName,
|
title: record.taskName,
|
||||||
@@ -34,8 +74,23 @@ const fetchTasks = async () => {
|
|||||||
priority: Number.isFinite(record.taskPriority) ? Math.round(record.taskPriority) : "-",
|
priority: Number.isFinite(record.taskPriority) ? Math.round(record.taskPriority) : "-",
|
||||||
taskNum: record.taskNum,
|
taskNum: record.taskNum,
|
||||||
taskId: record.id,
|
taskId: record.id,
|
||||||
|
reportCount: 0,
|
||||||
|
fragmentCount: 0,
|
||||||
|
effectiveTime: 0,
|
||||||
|
statsLoaded: false,
|
||||||
|
sessionCount: 0,
|
||||||
|
todayEffectiveTime: 0,
|
||||||
|
weekEffectiveTime: 0,
|
||||||
|
avgEffectiveTime: 0,
|
||||||
|
avgEffectivenessRatio: 0,
|
||||||
}));
|
}));
|
||||||
selectedTaskId.value = tasks.value[0]?.taskId ?? null;
|
const targetTask = tasks.value.find(item => item.taskId === previousSelectedTaskId) || tasks.value[0];
|
||||||
|
const nextSelectedTaskId = targetTask?.taskId ?? null;
|
||||||
|
const shouldLoadStats = nextSelectedTaskId === selectedTaskId.value;
|
||||||
|
selectedTaskId.value = nextSelectedTaskId;
|
||||||
|
if (targetTask && shouldLoadStats) {
|
||||||
|
await loadTaskStats(targetTask.taskNum);
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
tasks.value = [];
|
tasks.value = [];
|
||||||
selectedTaskId.value = null;
|
selectedTaskId.value = null;
|
||||||
@@ -45,6 +100,13 @@ const fetchTasks = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(selectedTaskId, (newId) => {
|
||||||
|
const task = tasks.value.find(t => t.taskId === newId);
|
||||||
|
if (task) {
|
||||||
|
loadTaskStats(task.taskNum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const startTask = () => {
|
const startTask = () => {
|
||||||
if (!selectedTask.value) {
|
if (!selectedTask.value) {
|
||||||
ElMessage.warning("请先选择一个任务");
|
ElMessage.warning("请先选择一个任务");
|
||||||
@@ -128,6 +190,44 @@ onMounted(() => {
|
|||||||
</el-link>
|
</el-link>
|
||||||
<p v-else class="empty-text">暂未填写材料地址</p>
|
<p v-else class="empty-text">暂未填写材料地址</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-block" v-if="selectedTask.statsLoaded">
|
||||||
|
<p class="label">学习统计</p>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">学习次数</span>
|
||||||
|
<span class="stat-value">{{ selectedTask.sessionCount }} 次</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">今日有效学习</span>
|
||||||
|
<span class="stat-value">{{ formatEffectiveTime(selectedTask.todayEffectiveTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">本周有效学习</span>
|
||||||
|
<span class="stat-value">{{ formatEffectiveTime(selectedTask.weekEffectiveTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">累计有效学习</span>
|
||||||
|
<span class="stat-value">{{ formatEffectiveTime(selectedTask.effectiveTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">平均学习时长</span>
|
||||||
|
<span class="stat-value">{{ formatEffectiveTime(selectedTask.avgEffectiveTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">平均有效时间比</span>
|
||||||
|
<span class="stat-value">{{ (selectedTask.avgEffectivenessRatio * 100).toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">学习报告</span>
|
||||||
|
<span class="stat-value">{{ selectedTask.reportCount }} 篇</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">学习残片</span>
|
||||||
|
<span class="stat-value">{{ selectedTask.fragmentCount }} 条</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-empty v-else description="暂无任务,请先创建任务" />
|
<el-empty v-else description="暂无任务,请先创建任务" />
|
||||||
</article>
|
</article>
|
||||||
@@ -241,6 +341,29 @@ onMounted(() => {
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.action-card {
|
.action-card {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user