












在 AI 应用爆发的今天,如何构建一个既能满足自由对话,又能支持结构化测验,且完美适配 PC 端与移动端的智能交互系统?
本文将深入剖析一套基于 Vue3 + TypeScript + Ant Design Vue 的 AI 应用架构。我们将展示如何通过策略模式区分“标准问答”与“测验模式”,如何利用响应式布局实现从桌面分栏到移动抽屉的无缝切换,以及如何通过 SSE (Server-Sent Events) 实现流畅的流式打字机效果。
我们的目标是一个统一的 AI 应用入口 (AIApplication),它需要支撑两种截然不同的场景:
技术挑战:
AIApplicationWeb.vue这是系统的骨架,负责根据 chatVariant ('standard' | 'quiz') 和 设备类型 (isMobile) 动态组装组件。
// 核心逻辑:计算是否使用桌面分栏布局
const useDesktopLayout = computed(() => {
// 1. 真实 PC 端
if (!props.isMobile) return true;
// 2. 移动端但开启了全屏模式(如测验全屏查看)
return store.state.qaDrawerFullscreen;
});
布局策略:
webLeftContainer) + 右侧弹性聊天区 (webRightContainer)。Drawer 组件覆盖展示历史记录,选择后自动关闭。<template>
<div class="flex flex-row ai-application-web-container">
<!-- 动态渲染左侧历史组件 -->
<div v-if="chatVariant === 'quiz' && useDesktopLayout" class="webLeftContainer">
<AIQuizHistoryComponents @select="handleSelectConversation" />
</div>
<!-- 移动端抽屉 -->
<AIQuizMobileHistoryComponents
v-if="chatVariant === 'quiz' && !useDesktopLayout"
:visible="isOpen"
@select="handleSelectConversationAndClose"
/>
<!-- 右侧核心聊天窗口 -->
<div class="webRightContainer">
<ChatWindow
:chat-variant="chatVariant"
:is-mobile="isMobile"
/>
</div>
</div>
</template>
ChatWindow.vue这是最复杂的组件,它集成了消息渲染、流式请求、输入控制和布局适配。
根据 chatVariant 属性,动态选择后端接口和数据处理逻辑。
const sendChatMessage = (question: string, chatFiles: UploadFile[]) => {
if (props.chatVariant === "quiz") {
sendQuizChatStream(question, chatFiles); // 测验模式:/quizChat
} else {
sendStandardChatStream(question, chatFiles); // 标准模式:/sendMessage
}
};
使用 @microsoft/fetch-event-source 处理 SSE 流,实时拼接 AI 返回的内容和思考过程 (reasoning_content)。
// 标准模式流式处理片段
async onmessage(event) {
const data = JSON.parse(event.data);
const lastAiItem = conversion.value[conversion.value.length - 1];
if (data?.status === "completed") {
// 结束状态:获取引用资源,停止 loading
lastAiItem.creating = false;
const res = await getMessageRefResource({ messageId: pageOption.messageId });
lastAiItem.retrieverResources = res;
} else {
// 流式追加内容
const delta = data.choices?.[0]?.delta;
if (delta?.content) {
lastAiItem.message = (lastAiItem.message || "") + delta.content;
}
// 处理深度思考过程
if (delta?.reasoning_content) {
lastAiItem.think = (lastAiItem.think || "") + delta.reasoning_content;
}
}
scrollToBottom(); // 保持滚动到底部
}
测验模式下,进入新会话会自动触发“开始答题”,且消息映射逻辑不同(根据 messageType 区分用户/AI)。
// 自动开始答题逻辑
watch(() => props.conversationId, (newValue) => {
if (!newValue && props.chatVariant === "quiz") {
ensureQuizModelReady().then(() => {
makeConversationToAi("开始智能答题", []); // 自动发起第一问
});
}
});
左上角的菜单按钮在不同场景下表现不同:
const handleHeaderMenuClick = () => {
// 移动端测验模式特殊处理
if (isQuizLayout.value && props.isMobile && props.page === "qatest") {
if (!store.state.qaDrawerFullscreen) {
store.dispatch("setQaDrawerFullscreen", true); // 先全屏
open.value = true; // 再开历史
return;
}
}
jumpAI(1); // 常规切换
};
为了在宽屏显示器上获得更好的阅读体验,聊天内容区域和输入框被限制最大宽度 800px 并水平居中。
/* 聊天内容区域 */
.chat-content-inner {
width: 100%;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
/* 底部输入框 */
.chat-input-wrap {
width: calc(100% - 32px);
max-width: 800px;
left: 50%;
transform: translateX(-50%);
position: absolute;
bottom: 15px;
}
通过 AIQuizHistoryComponents 和 AIChatHistoryComponents 分别处理两种模式的历史列表,但共享相同的 UI 结构(选中态、新建按钮、模型展示)。
<!-- 测验历史列表 -->
<a v-for="(item, index) in quizHistory" @click="emit('select', item.sessionId)">
<div :class="['chat-history-item', { 'chat-history-item-select': selectKeys === index }]">
{{ item.sessionName || "未命名会话" }}
</div>
</a>
在快速切换会话或连续发送消息时,利用 ref 和 watch 确保 conversationId 和 mdId (模型ID) 的准确同步,避免串话。
通过 store.state.qaDrawerFullscreen 控制 CSS 宽度和组件渲染策略,实现了从“窄浮层”到“全屏应用”的无缝体验,特别适配了 iPad 等平板设备。
在标准模式下,集成文件上传功能,并在消息气泡中支持图片预览、文档下载及在线编辑入口(通过 upload-file-field 组件复用)。
这套架构成功地将复杂的 AI 交互逻辑封装在可复用的 Vue 组件中。通过 Props 驱动 和 Composables 思想,我们仅用一套代码库就支撑了“智能问答”和“在线测验”两大核心业务,同时保证了多端的极致体验。
技术栈亮点:
💡 提示:在生产环境中,建议增加消息本地缓存、弱网重试机制以及更细致的权限控制。
适用场景:企业知识库、AI 助教系统、在线考试平台、智能客服。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏!代码逻辑已详细拆解,欢迎交流~
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。