feat(会话):历史学习记录折叠面板(残片/报告分页+搜索)

StartTask.vue 新增「📚 历史学习记录」折叠区域:
- 默认折叠,点击标题展开
- 支持残片/报告两个 tab 切换
- 搜索框带 300ms 防抖,对内容 LIKE 搜索
- 分页每页 10 条
- 每条记录前标注所属 sessionNum,与当前会话内容明确区分
- 展示 v-loading 和 el-empty 两种状态

后端新增两个分页接口(上一 commit):
- GET /study-sessions/tasks/{taskNum}/fragments
- GET /study-sessions/tasks/{taskNum}/reports
This commit is contained in:
2026-07-05 10:39:56 +08:00
parent b7387e02c4
commit 88a3521f27
2 changed files with 121 additions and 1 deletions
+14
View File
@@ -36,3 +36,17 @@ export const upsertExpectation = (sessionNum: string, description: string) => {
export const getReportDraft = (sessionNum: string) => {
return request.get(`/study-sessions/${sessionNum}/report-draft`);
};
/** 分页查询任务的历史残片 */
export const getTaskFragments = (taskNum: string, page: number, size: number, keyword?: string) => {
const params: Record<string, any> = { page, size };
if (keyword) params.keyword = keyword;
return request.get(`/study-sessions/tasks/${taskNum}/fragments`, params);
};
/** 分页查询任务的历史报告 */
export const getTaskReports = (taskNum: string, page: number, size: number, keyword?: string) => {
const params: Record<string, any> = { page, size };
if (keyword) params.keyword = keyword;
return request.get(`/study-sessions/tasks/${taskNum}/reports`, params);
};
+107 -1
View File
@@ -11,7 +11,7 @@ import {
} from "@/api/studySessions";
import { useStudyFragment } from "@/components/composables/fragment";
import { getFragmentsBySession, updateFragments } from "@/api/reportFragments";
import { getExpectation, getReportDraft, upsertExpectation } from "@/api/studySessions";
import { getExpectation, getReportDraft, getTaskFragments, getTaskReports, upsertExpectation } from "@/api/studySessions";
import router from "@/router";
const {
@@ -44,6 +44,77 @@ const editingFragmentId = ref<number | null>(null);
const editFragmentContent = ref("");
const savingFragment = ref(false);
// 历史学习记录
const showHistory = ref(false);
const historyTab = ref("fragments");
const historyKeyword = ref("");
let historyDebounce: ReturnType<typeof setTimeout> | null = null;
const loadingHistory = ref(false);
const historyFragments = ref<any[]>([]);
const fragmentsTotal = ref(0);
const fragmentsPage = ref(1);
const historyReports = ref<any[]>([]);
const reportsTotal = ref(0);
const reportsPage = ref(1);
const PAGE_SIZE = 10;
const loadHistoryFragments = async () => {
loadingHistory.value = true;
try {
const res = await getTaskFragments(taskNum, fragmentsPage.value, PAGE_SIZE, historyKeyword.value || undefined);
const data = res?.data;
historyFragments.value = data?.records || [];
fragmentsTotal.value = data?.total || 0;
} catch {
historyFragments.value = [];
fragmentsTotal.value = 0;
} finally {
loadingHistory.value = false;
}
};
const loadHistoryReports = async () => {
loadingHistory.value = true;
try {
const res = await getTaskReports(taskNum, reportsPage.value, PAGE_SIZE, historyKeyword.value || undefined);
const data = res?.data;
historyReports.value = data?.records || [];
reportsTotal.value = data?.total || 0;
} catch {
historyReports.value = [];
reportsTotal.value = 0;
} finally {
loadingHistory.value = false;
}
};
const searchHistory = () => {
if (historyDebounce) clearTimeout(historyDebounce);
historyDebounce = setTimeout(() => {
fragmentsPage.value = 1;
reportsPage.value = 1;
if (historyTab.value === "fragments") loadHistoryFragments();
else loadHistoryReports();
}, 300);
};
const onHistoryTabChange = () => {
fragmentsPage.value = 1;
reportsPage.value = 1;
if (historyTab.value === "fragments") loadHistoryFragments();
else loadHistoryReports();
};
const onFragmentsPageChange = (p: number) => {
fragmentsPage.value = p;
loadHistoryFragments();
};
const onReportsPageChange = (p: number) => {
reportsPage.value = p;
loadHistoryReports();
};
const route = useRoute();
const taskNum = route.params.taskNum as string;
@@ -552,6 +623,41 @@ onUnmounted(() => {
</template>
</div>
</article>
<!-- 历史学习记录 -->
<article class="surface-card history-card">
<div class="history-header" @click="showHistory = !showHistory">
<div class="history-header-left">
<span class="history-toggle">{{ showHistory ? "▾" : "▸" }}</span>
<h3>📚 历史学习记录</h3>
</div>
<el-tag size="small" type="info" effect="plain">展开后查看该任务全部残片与报告</el-tag>
</div>
<template v-if="showHistory">
<el-input v-model="historyKeyword" placeholder="搜索历史内容…" clearable class="history-search" @input="searchHistory" @clear="searchHistory" />
<el-tabs v-model="historyTab" @tab-change="onHistoryTabChange" class="history-tabs">
<el-tab-pane label="学习残片" name="fragments">
<div v-loading="loadingHistory" class="history-list">
<div v-for="item in historyFragments" :key="item.id" class="history-item">
<span class="session-tag">{{ item.sessionNum }}</span>
<span class="history-content">{{ item.content }}</span>
</div>
<el-empty v-if="!loadingHistory && historyFragments.length === 0" description="暂未找到匹配的残片" :image-size="48" />
</div>
<el-pagination v-if="fragmentsTotal > 10" small layout="prev, pager, next" :total="fragmentsTotal" :page-size="10" :current-page="fragmentsPage" @current-change="onFragmentsPageChange" background />
</el-tab-pane>
<el-tab-pane label="学习报告" name="reports">
<div v-loading="loadingHistory" class="history-list">
<div v-for="item in historyReports" :key="item.id" class="history-item report-item">
<span class="session-tag">{{ item.sessionNum }}</span>
<p class="history-content">{{ item.content }}</p>
</div>
<el-empty v-if="!loadingHistory && historyReports.length === 0" description="暂未找到匹配的报告" :image-size="48" />
</div>
<el-pagination v-if="reportsTotal > 10" small layout="prev, pager, next" :total="reportsTotal" :page-size="10" :current-page="reportsPage" @current-change="onReportsPageChange" background />
</el-tab-pane>
</el-tabs>
</template>
</article>
</section>
<el-dialog v-model="fragmentsDialogVisible" title="生成学习残片" width="520px">