feat(前端):AI 操作超时 + 加载提示

- axios 全局超时 5s → 180s(3 分钟),避免 AI 响应未到即断开
- 回忆对比按钮等待时显示旋转动画 + 已等待秒数 + 30s 后安抚文案
- 结束会话报告草稿生成时显示等待提示 + 秒数统计
This commit is contained in:
2026-07-04 16:16:33 +08:00
parent a259e0ac1a
commit 4e1c71e4d5
3 changed files with 65 additions and 1 deletions
+32
View File
@@ -20,6 +20,8 @@ const taskNum = computed(() => route.params.taskNum as string);
const loading = ref(false); const loading = ref(false);
const standard = ref<StandardMindMap | null>(null); const standard = ref<StandardMindMap | null>(null);
const comparing = ref(false); const comparing = ref(false);
const comparingSeconds = ref(0);
let comparingTimer: ReturnType<typeof setInterval> | null = null;
const lastResult = ref<any>(null); const lastResult = ref<any>(null);
const hasCompared = ref(false); const hasCompared = ref(false);
@@ -139,6 +141,8 @@ const handleRecallCompare = async () => {
return; return;
} }
comparing.value = true; comparing.value = true;
comparingSeconds.value = 0;
comparingTimer = setInterval(() => { comparingSeconds.value++; }, 1000);
try { try {
const res = await recallCompare(taskNum.value, outline); const res = await recallCompare(taskNum.value, outline);
standard.value = res?.data || null; standard.value = res?.data || null;
@@ -162,6 +166,7 @@ const handleRecallCompare = async () => {
ElMessage.error(e.message || "对比失败"); ElMessage.error(e.message || "对比失败");
} finally { } finally {
comparing.value = false; comparing.value = false;
if (comparingTimer) { clearInterval(comparingTimer); comparingTimer = null; }
} }
}; };
@@ -328,6 +333,10 @@ onMounted(() => {
清空重写 清空重写
</el-button> </el-button>
</div> </div>
<p v-if="comparing" class="ai-waiting">
AI 正在对比分析中已等待 {{ comparingSeconds }}
<template v-if="comparingSeconds > 30">内容较多请耐心等待</template>
</p>
</div> </div>
<p class="panel-hint"> <p class="panel-hint">
凭记忆在导图上补全该任务的知识点选中节点后 Tab 加子节点Enter 加同级节点双击修改文字 凭记忆在导图上补全该任务的知识点选中节点后 Tab 加子节点Enter 加同级节点双击修改文字
@@ -530,6 +539,29 @@ onMounted(() => {
line-height: 1.5; line-height: 1.5;
} }
.ai-waiting {
font-size: 13px;
color: #e6a23c;
margin: 4px 0 0;
display: flex;
align-items: center;
gap: 6px;
}
.ai-waiting::before {
content: "";
width: 14px;
height: 14px;
border: 2px solid #f3d19e;
border-top-color: #e6a23c;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.standard-meta { .standard-meta {
display: flex; display: flex;
gap: 12px; gap: 12px;
+32
View File
@@ -25,6 +25,8 @@ const {
const summaryDialogVisible = ref(false); const summaryDialogVisible = ref(false);
const summaryContent = ref(""); const summaryContent = ref("");
const summaryLoading = ref(false); const summaryLoading = ref(false);
const summaryLoadingSeconds = ref(0);
let summaryLoadingTimer: ReturnType<typeof setInterval> | null = null;
// 学习预期 // 学习预期
const expectationDialogVisible = ref(false); const expectationDialogVisible = ref(false);
@@ -240,6 +242,8 @@ const openSummaryDialog = async () => {
// 有残片且未填写过总结时,用 AI 聚合草稿作为编辑起点 // 有残片且未填写过总结时,用 AI 聚合草稿作为编辑起点
if (!summaryContent.value.trim() && fragmentsList.value.length > 0) { if (!summaryContent.value.trim() && fragmentsList.value.length > 0) {
summaryLoading.value = true; summaryLoading.value = true;
summaryLoadingSeconds.value = 0;
summaryLoadingTimer = setInterval(() => { summaryLoadingSeconds.value++; }, 1000);
try { try {
const res = await getReportDraft(taskInfo.value.sessionNum); const res = await getReportDraft(taskInfo.value.sessionNum);
if (res?.code === 200 && res.data) { if (res?.code === 200 && res.data) {
@@ -249,12 +253,14 @@ const openSummaryDialog = async () => {
// 草稿失败不阻塞手写 // 草稿失败不阻塞手写
} finally { } finally {
summaryLoading.value = false; summaryLoading.value = false;
if (summaryLoadingTimer) { clearInterval(summaryLoadingTimer); summaryLoadingTimer = null; }
} }
} }
}; };
const closeSummaryDialog = () => { const closeSummaryDialog = () => {
summaryDialogVisible.value = false; summaryDialogVisible.value = false;
if (summaryLoadingTimer) { clearInterval(summaryLoadingTimer); summaryLoadingTimer = null; }
}; };
const restTimer = async () => { const restTimer = async () => {
@@ -586,6 +592,9 @@ onUnmounted(() => {
<span class="expectation-label">本次预期</span> <span class="expectation-label">本次预期</span>
<span>{{ expectationSaved }}</span> <span>{{ expectationSaved }}</span>
</div> </div>
<p v-if="summaryLoading" class="ai-waiting">
AI 正在聚合残片生成草稿已等待 {{ summaryLoadingSeconds }}
</p>
<el-input <el-input
v-loading="summaryLoading" v-loading="summaryLoading"
type="textarea" type="textarea"
@@ -644,6 +653,29 @@ onUnmounted(() => {
color: var(--text-secondary); color: var(--text-secondary);
} }
.ai-waiting {
font-size: 13px;
color: #e6a23c;
margin: 0 0 8px;
display: flex;
align-items: center;
gap: 6px;
}
.ai-waiting::before {
content: "";
width: 14px;
height: 14px;
border: 2px solid #f3d19e;
border-top-color: #e6a23c;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.summary-expectation { .summary-expectation {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
+1 -1
View File
@@ -6,7 +6,7 @@ const basic_url = import.meta.env.VITE_BASE_URL;
const axiosInstance = axios.create({ const axiosInstance = axios.create({
baseURL: basic_url, baseURL: basic_url,
timeout: 5000, timeout: 180000,
withCredentials: true withCredentials: true
}); });