feat: add class share drafts

This commit is contained in:
2026-07-31 16:07:18 +08:00
parent d8fde93b69
commit 429636f3e8
15 changed files with 410 additions and 5 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, TeacherHelpCard, UserProfile } from "./types/api";
import type { ChatMessage as ApiMessage, ChatSession, GrowthProfileResult, ShareDraft, TeacherHelpCard, UserProfile } from "./types/api";
const user = ref<UserProfile | null>(null);
const sessions = ref<ChatSession[]>([]);
@@ -24,12 +24,16 @@ const sessionOperationPending = ref(false);
const logoutDialogOpen = ref(false);
const profileDialogOpen = ref(false);
const helpCardDialogOpen = ref(false);
const shareDraftDialogOpen = ref(false);
const growthProfile = ref<GrowthProfileResult | null>(null);
const helpCard = ref<TeacherHelpCard | null>(null);
const helpCardContent = ref("");
const shareDraft = ref<ShareDraft | null>(null);
const shareDraftContent = ref("");
const profileLoading = ref(false);
const finishingTopic = ref(false);
const generatingHelpCard = ref(false);
const generatingShareDraft = ref(false);
const statusText = ref("连接后端中");
const toastText = ref("");
const followingOutput = ref(true);
@@ -251,6 +255,39 @@ async function copyHelpCard() {
}
}
async function generateShareDraft() {
if (!activeSessionId.value || sending.value || generatingShareDraft.value) return;
generatingShareDraft.value = true;
try {
const result = await api.generateShareDraft(activeSessionId.value);
shareDraft.value = result;
shareDraftContent.value = result.content;
shareDraftDialogOpen.value = true;
showToast("分享稿已生成,可编辑后复制");
await refreshProfile();
} catch (error) {
handleError(error, "分享稿生成失败");
} finally {
generatingShareDraft.value = false;
}
}
async function copyShareDraft() {
if (!shareDraftContent.value.trim()) {
showToast("分享稿内容为空");
return;
}
try {
await copyText(shareDraftContent.value);
if (shareDraft.value) {
shareDraft.value = await api.markShareDraftCopied(shareDraft.value.id);
}
showToast("已复制,可粘贴到班级群");
} catch (error) {
handleError(error, "复制失败,请手动选择文本复制");
}
}
async function openGrowthProfile() {
profileDialogOpen.value = true;
profileLoading.value = true;
@@ -394,9 +431,11 @@ async function copyText(text: string) {
:entitlement="user.entitlement"
:finishing="finishingTopic"
:generating-help-card="generatingHelpCard"
:generating-share-draft="generatingShareDraft"
@finish-topic="finishCurrentTopic"
@open-profile="openGrowthProfile"
@generate-help-card="generateHelpCard"
@generate-share-draft="generateShareDraft"
/>
<MessageList
ref="messageList"
@@ -459,6 +498,19 @@ async function copyText(text: string) {
</template>
</AppDialog>
<AppDialog v-if="shareDraftDialogOpen" title="班级分享稿" labelled-by="share-draft-title" @close="shareDraftDialogOpen = false">
<section class="help-card-dialog">
<p>这只是分享草稿系统不会自动发送到任何群请删掉不想公开的隐私内容并按自己的真实状态修改后再复制</p>
<textarea v-model="shareDraftContent" aria-label="班级分享稿内容" />
</section>
<template #footer>
<div class="dialog-actions">
<button type="button" class="dialog-secondary" @click="shareDraftDialogOpen = false">关闭</button>
<button type="button" class="dialog-primary" @click="copyShareDraft">复制分享稿</button>
</div>
</template>
</AppDialog>
<AppDialog v-if="logoutDialogOpen" title="退出登录" labelled-by="logout-dialog-title" @close="logoutDialogOpen = false">
<p class="confirm-copy">确定退出当前账号吗</p>
<template #footer>

View File

@@ -10,12 +10,14 @@ const props = defineProps<{
entitlement?: UserEntitlementSummary | null;
finishing?: boolean;
generatingHelpCard?: boolean;
generatingShareDraft?: boolean;
}>();
defineEmits<{
finishTopic: [];
openProfile: [];
generateHelpCard: [];
generateShareDraft: [];
}>();
const hasEntitlement = computed(() => Boolean(props.entitlement));
@@ -54,6 +56,15 @@ const limitText = computed(() => displayLimit.value === null ? "不限" : String
>
{{ generatingHelpCard ? '生成中' : '求助卡' }}
</button>
<button
v-if="entitlement?.allowShareDraft"
type="button"
class="quota-link"
:disabled="generatingShareDraft"
@click="$emit('generateShareDraft')"
>
{{ generatingShareDraft ? '生成中' : '分享稿' }}
</button>
<strong>{{ displayUsed }}/{{ limitText }}</strong>
</div>
</section>

View File

@@ -6,6 +6,7 @@ import type {
FinishTopicResult,
GrowthProfileResult,
LoginResult,
ShareDraft,
TeacherHelpCard,
UserProfile,
} from "../types/api";
@@ -90,6 +91,9 @@ export const api = {
generateHelpCard: (sessionId: number) => request<TeacherHelpCard>(`/chat/session/${sessionId}/help-card`, { method: "POST", body: JSON.stringify({}) }),
markHelpCardCopied: (cardId: number) => request<TeacherHelpCard>(`/chat/help-card/${cardId}/copied`, { method: "POST", body: JSON.stringify({}) }),
helpCards: (limit = 20) => request<TeacherHelpCard[]>(`/chat/help-card/list?limit=${limit}`),
generateShareDraft: (sessionId: number) => request<ShareDraft>(`/chat/session/${sessionId}/share-draft`, { method: "POST", body: JSON.stringify({}) }),
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"),
stop: (sessionId: number) => request<null>("/chat/stop", { method: "POST", body: JSON.stringify({ sessionId }) }),
};

View File

@@ -1204,9 +1204,10 @@ textarea:focus-visible {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin: 12px 16px 0;
padding: 0 14px;
padding: 8px 14px;
border: 1px solid rgba(217, 230, 225, 0.75);
border-radius: 13px;
background: var(--chat-brand-soft);
@@ -1234,6 +1235,8 @@ textarea:focus-visible {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
flex: 0 0 auto;
}

View File

@@ -79,6 +79,18 @@ export interface TeacherHelpCard {
createdAt: string;
}
export interface ShareDraft {
id: number;
userId: number;
topicSessionId: number;
summaryId?: number | null;
content: string;
source: string;
copied: boolean;
copiedAt?: string | null;
createdAt: string;
}
export interface LoginResult {
token: string;
expiredAt: string;