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:
@@ -36,3 +36,17 @@ export const upsertExpectation = (sessionNum: string, description: string) => {
|
|||||||
export const getReportDraft = (sessionNum: string) => {
|
export const getReportDraft = (sessionNum: string) => {
|
||||||
return request.get(`/study-sessions/${sessionNum}/report-draft`);
|
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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "@/api/studySessions";
|
} from "@/api/studySessions";
|
||||||
import { useStudyFragment } from "@/components/composables/fragment";
|
import { useStudyFragment } from "@/components/composables/fragment";
|
||||||
import { getFragmentsBySession, updateFragments } from "@/api/reportFragments";
|
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";
|
import router from "@/router";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -44,6 +44,77 @@ const editingFragmentId = ref<number | null>(null);
|
|||||||
const editFragmentContent = ref("");
|
const editFragmentContent = ref("");
|
||||||
const savingFragment = ref(false);
|
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 route = useRoute();
|
||||||
const taskNum = route.params.taskNum as string;
|
const taskNum = route.params.taskNum as string;
|
||||||
|
|
||||||
@@ -552,6 +623,41 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</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>
|
</section>
|
||||||
|
|
||||||
<el-dialog v-model="fragmentsDialogVisible" title="生成学习残片" width="520px">
|
<el-dialog v-model="fragmentsDialogVisible" title="生成学习残片" width="520px">
|
||||||
|
|||||||
Reference in New Issue
Block a user