feat: 新增用户端个人中心
This commit is contained in:
@@ -6,6 +6,7 @@ import ChatComposer from "./components/ChatComposer.vue";
|
||||
import ChatHeader from "./components/ChatHeader.vue";
|
||||
import LoginPanel from "./components/LoginPanel.vue";
|
||||
import MessageList, { type DisplayMessage } from "./components/MessageList.vue";
|
||||
import PersonalCenterDialog from "./components/PersonalCenterDialog.vue";
|
||||
import SessionDrawer from "./components/SessionDrawer.vue";
|
||||
import SessionQuota from "./components/SessionQuota.vue";
|
||||
import { ApiError, api, clearToken, getToken, streamChat } from "./services/api";
|
||||
@@ -22,7 +23,8 @@ const loadingSession = ref(false);
|
||||
const loadingSessions = ref(false);
|
||||
const sessionOperationPending = ref(false);
|
||||
const logoutDialogOpen = ref(false);
|
||||
const profileDialogOpen = ref(false);
|
||||
const personalCenterOpen = ref(false);
|
||||
const personalCenterSection = ref<"overview" | "review" | "records">("overview");
|
||||
const helpCardDialogOpen = ref(false);
|
||||
const shareDraftDialogOpen = ref(false);
|
||||
const practiceReview = ref<PracticeReviewResult | null>(null);
|
||||
@@ -33,7 +35,7 @@ const shareDraftContent = ref("");
|
||||
const helpCardHistory = ref<TeacherHelpCard[]>([]);
|
||||
const shareDraftHistory = ref<ShareDraft[]>([]);
|
||||
const reportHistory = ref<PeriodicReport[]>([]);
|
||||
const profileLoading = ref(false);
|
||||
const personalCenterLoading = ref(false);
|
||||
const finishingTopic = ref(false);
|
||||
const generatingHelpCard = ref(false);
|
||||
const generatingShareDraft = ref(false);
|
||||
@@ -254,16 +256,6 @@ async function watchTopicSettlement(summaryId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function settlementStatusLabel(status: string) {
|
||||
return {
|
||||
pending: "等待沉淀",
|
||||
running: "沉淀中",
|
||||
success: "已完成",
|
||||
fallback: "已降级完成",
|
||||
failed: "沉淀失败",
|
||||
}[status] || status;
|
||||
}
|
||||
|
||||
async function generateHelpCard() {
|
||||
if (!activeSessionId.value || sending.value || generatingHelpCard.value) return;
|
||||
generatingHelpCard.value = true;
|
||||
@@ -330,24 +322,27 @@ async function copyShareDraft() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openPracticeReview() {
|
||||
profileDialogOpen.value = true;
|
||||
profileLoading.value = true;
|
||||
async function openPersonalCenter(section: "overview" | "review" | "records" = "overview") {
|
||||
personalCenterSection.value = section;
|
||||
personalCenterOpen.value = true;
|
||||
personalCenterLoading.value = true;
|
||||
try {
|
||||
const [profileResult, helpCards, shareDrafts, reports] = await Promise.all([
|
||||
const [latestUser, profileResult, helpCards, shareDrafts, reports] = await Promise.all([
|
||||
api.profile(),
|
||||
api.practiceReview(),
|
||||
api.helpCards(10),
|
||||
api.shareDrafts(10),
|
||||
api.periodicReports(10),
|
||||
]);
|
||||
user.value = latestUser;
|
||||
practiceReview.value = profileResult;
|
||||
helpCardHistory.value = helpCards;
|
||||
shareDraftHistory.value = shareDrafts;
|
||||
reportHistory.value = reports;
|
||||
} catch (error) {
|
||||
handleError(error, "近期实修回顾加载失败");
|
||||
handleError(error, "个人中心加载失败");
|
||||
} finally {
|
||||
profileLoading.value = false;
|
||||
personalCenterLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +435,7 @@ function clearUserState() {
|
||||
sessions.value = [];
|
||||
messages.value = [];
|
||||
activeSessionId.value = null;
|
||||
personalCenterOpen.value = false;
|
||||
statusText.value = "请先登录";
|
||||
}
|
||||
|
||||
@@ -475,7 +471,12 @@ async function copyText(text: string) {
|
||||
<div v-if="booting" class="booting">正在启动...</div>
|
||||
<LoginPanel v-else-if="!user" @logged-in="onLoggedIn" />
|
||||
<template v-else>
|
||||
<ChatHeader :user="user" :status-text="statusText" @open-history="drawerOpen = true" @logout="logoutDialogOpen = true" />
|
||||
<ChatHeader
|
||||
:user="user"
|
||||
:status-text="statusText"
|
||||
@open-history="drawerOpen = true"
|
||||
@open-personal-center="openPersonalCenter('overview')"
|
||||
/>
|
||||
<SessionQuota
|
||||
:used="user.todayUsed"
|
||||
:limit="user.dailyLimit"
|
||||
@@ -484,7 +485,7 @@ async function copyText(text: string) {
|
||||
:generating-help-card="generatingHelpCard"
|
||||
:generating-share-draft="generatingShareDraft"
|
||||
@finish-topic="finishCurrentTopic"
|
||||
@open-profile="openPracticeReview"
|
||||
@open-profile="openPersonalCenter('review')"
|
||||
@generate-help-card="generateHelpCard"
|
||||
@generate-share-draft="generateShareDraft"
|
||||
/>
|
||||
@@ -512,49 +513,18 @@ async function copyText(text: string) {
|
||||
|
||||
<div v-if="toastText" class="chat-toast" role="status">{{ toastText }}</div>
|
||||
|
||||
<AppDialog v-if="profileDialogOpen" title="近期实修回顾" labelled-by="growth-profile-title" @close="profileDialogOpen = false">
|
||||
<section class="growth-profile-dialog" :aria-busy="profileLoading">
|
||||
<p v-if="profileLoading" class="profile-empty">正在加载近期回顾...</p>
|
||||
<template v-else-if="practiceReview?.profile">
|
||||
<pre>{{ practiceReview.profile.reviewText }}</pre>
|
||||
<div v-if="practiceReview.profile.currentFocus" class="growth-profile-fields">
|
||||
<p><strong>近期关注</strong>{{ practiceReview.profile.currentFocus }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="profile-empty">还没有近期回顾。你可以在完成一次主题对话后点击“沉淀本主题”。</p>
|
||||
<div v-if="practiceReview?.recentSummaries.length" class="recent-topic-summaries">
|
||||
<h3>最近主题沉淀</h3>
|
||||
<article v-for="item in practiceReview.recentSummaries" :key="item.id">
|
||||
<time>{{ item.generatedAt }} · {{ settlementStatusLabel(item.status) }}</time>
|
||||
<p v-if="item.status === 'success' || item.status === 'fallback'">{{ item.summary }}</p>
|
||||
<p v-else-if="item.status === 'failed'">沉淀暂时失败,对话内容仍已保留。</p>
|
||||
<p v-else>正在后台整理本主题的实修记录,可以先关闭页面或继续聊天。</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}`">
|
||||
<time>{{ item.createdAt }} · {{ item.copied ? '已复制' : '未复制' }}</time>
|
||||
<p>{{ item.content.slice(0, 140) }}{{ item.content.length > 140 ? '…' : '' }}</p>
|
||||
</article>
|
||||
</div>
|
||||
<div v-if="shareDraftHistory.length" class="recent-topic-summaries">
|
||||
<h3>最近班级分享稿</h3>
|
||||
<article v-for="item in shareDraftHistory" :key="`share-${item.id}`">
|
||||
<time>{{ item.createdAt }} · {{ item.copied ? '已复制' : '未复制' }}</time>
|
||||
<p>{{ item.content.slice(0, 140) }}{{ item.content.length > 140 ? '…' : '' }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</AppDialog>
|
||||
<PersonalCenterDialog
|
||||
v-if="personalCenterOpen && user"
|
||||
:user="user"
|
||||
:initial-section="personalCenterSection"
|
||||
:loading="personalCenterLoading"
|
||||
:practice-review="practiceReview"
|
||||
:reports="reportHistory"
|
||||
:help-cards="helpCardHistory"
|
||||
:share-drafts="shareDraftHistory"
|
||||
@close="personalCenterOpen = false"
|
||||
@logout="personalCenterOpen = false; logoutDialogOpen = true"
|
||||
/>
|
||||
|
||||
<AppDialog v-if="helpCardDialogOpen" title="老师求助卡" labelled-by="help-card-title" @close="helpCardDialogOpen = false">
|
||||
<section class="help-card-dialog">
|
||||
|
||||
Reference in New Issue
Block a user