feat: add periodic practice reports

This commit is contained in:
2026-07-31 16:37:25 +08:00
parent 88ed92d78c
commit 99e23b33ec
16 changed files with 602 additions and 3 deletions

View File

@@ -9,7 +9,7 @@ import MessageList, { type DisplayMessage } from "./components/MessageList.vue";
import SessionDrawer from "./components/SessionDrawer.vue";
import SessionQuota from "./components/SessionQuota.vue";
import { ApiError, api, clearToken, getToken, streamChat } from "./services/api";
import type { ChatMessage as ApiMessage, ChatSession, GrowthProfileResult, ShareDraft, TeacherHelpCard, UserProfile } from "./types/api";
import type { ChatMessage as ApiMessage, ChatSession, GrowthProfileResult, PeriodicReport, ShareDraft, TeacherHelpCard, UserProfile } from "./types/api";
const user = ref<UserProfile | null>(null);
const sessions = ref<ChatSession[]>([]);
@@ -32,6 +32,7 @@ const shareDraft = ref<ShareDraft | null>(null);
const shareDraftContent = ref("");
const helpCardHistory = ref<TeacherHelpCard[]>([]);
const shareDraftHistory = ref<ShareDraft[]>([]);
const reportHistory = ref<PeriodicReport[]>([]);
const profileLoading = ref(false);
const finishingTopic = ref(false);
const generatingHelpCard = ref(false);
@@ -294,14 +295,16 @@ async function openGrowthProfile() {
profileDialogOpen.value = true;
profileLoading.value = true;
try {
const [profileResult, helpCards, shareDrafts] = await Promise.all([
const [profileResult, helpCards, shareDrafts, reports] = await Promise.all([
api.growthProfile(),
api.helpCards(10),
api.shareDrafts(10),
api.periodicReports(10),
]);
growthProfile.value = profileResult;
helpCardHistory.value = helpCards;
shareDraftHistory.value = shareDrafts;
reportHistory.value = reports;
} catch (error) {
handleError(error, "成长档案加载失败");
} finally {
@@ -491,6 +494,14 @@ async function copyText(text: string) {
<p>{{ item.summary }}</p>
</article>
</div>
<div v-if="reportHistory.length" class="recent-topic-summaries periodic-report-list">
<h3>周期实修报告</h3>
<article v-for="item in reportHistory" :key="`report-${item.id}`">
<time>{{ item.reportTypeLabel }} · {{ item.periodStart }} {{ item.periodEnd }} · {{ item.generatedAt }}</time>
<p class="report-title">{{ item.title }}</p>
<pre>{{ item.content }}</pre>
</article>
</div>
<div v-if="helpCardHistory.length" class="recent-topic-summaries">
<h3>最近老师求助卡</h3>
<article v-for="item in helpCardHistory" :key="`help-${item.id}`">

View File

@@ -6,6 +6,7 @@ import type {
FinishTopicResult,
GrowthProfileResult,
LoginResult,
PeriodicReport,
ShareDraft,
TeacherHelpCard,
UserProfile,
@@ -95,6 +96,7 @@ export const api = {
markShareDraftCopied: (draftId: number) => request<ShareDraft>(`/chat/share-draft/${draftId}/copied`, { method: "POST", body: JSON.stringify({}) }),
shareDrafts: (limit = 20) => request<ShareDraft[]>(`/chat/share-draft/list?limit=${limit}`),
growthProfile: () => request<GrowthProfileResult>("/user/growth-profile"),
periodicReports: (limit = 10) => request<PeriodicReport[]>(`/user/periodic-report/list?limit=${limit}`),
stop: (sessionId: number) => request<null>("/chat/stop", { method: "POST", body: JSON.stringify({ sessionId }) }),
};

View File

@@ -1810,6 +1810,19 @@ textarea:focus-visible {
line-height: 1.65;
}
.periodic-report-list .report-title {
color: var(--chat-text);
font-weight: 650;
}
.periodic-report-list pre {
max-height: 260px;
margin: 8px 0 0;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
.dialog-actions {
display: grid;
grid-template-columns: 1fr 1fr;

View File

@@ -60,6 +60,18 @@ export interface GrowthProfileResult {
recentSummaries: TopicSummary[];
}
export interface PeriodicReport {
id: number;
reportType: "weekly" | "monthly" | "stage" | string;
reportTypeLabel: string;
periodStart: string;
periodEnd: string;
title: string;
content: string;
status: "success" | "failed" | "empty" | string;
generatedAt: string;
}
export interface FinishTopicResult {
topic: Record<string, unknown>;
summary: TopicSummary & Record<string, unknown>;