chore: 移除上传导图 + 优化复习引导提示

- 删除 ReviewMindMap 接口及 upload/get/upsert API
- 删除 ReviewDetail 上传 UI 和思维导图预览
- 回忆复习按钮移至内容元信息区
- 引导提示改为渐进式:折叠时引导展开,展开后引导点击节点
- 提示语用告知语气,无表情符号

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cat-win
2026-07-08 23:56:03 +08:00
parent 1ea15db504
commit e65b800e01
4 changed files with 31 additions and 299 deletions
+8 -223
View File
@@ -4,11 +4,8 @@ import { useRoute, useRouter } from "vue-router";
import {
getFragmentDetail,
getReportDetail,
getReviewMindMap,
getTaskReview,
uploadReviewMindMap,
type ReviewFeedItem,
type ReviewMindMap,
} from "@/api/review";
import { getSessionDetail } from "@/api/studySessions";
import { updateFragments } from "@/api/reportFragments";
@@ -50,9 +47,6 @@ const isEditing = ref(false);
const editContent = ref("");
const saving = ref(false);
const mindMap = ref<ReviewMindMap | null>(null);
const mindMapUploading = ref(false);
const sessionInfo = ref<{
taskName: string;
sessionNum: string;
@@ -69,40 +63,6 @@ const taskSessions = ref<{
fragments: ReviewFeedItem[];
}[]>([]);
type MindMapNode = {
title?: string;
notes?: string;
children?: MindMapNode[];
};
const flattenedMindMapNodes = computed(() => {
const parsedContent = mindMap.value?.parsedContent;
if (!parsedContent) {
return [];
}
try {
const parsed = JSON.parse(parsedContent) as MindMapNode & { nodes?: MindMapNode[] };
const rows: { title: string; level: number }[] = [];
const walk = (node: MindMapNode, level: number) => {
if (node.title) {
rows.push({ title: node.title, level });
}
(node.children || []).forEach(child => walk(child, level + 1));
};
rows.push({ title: parsed.title || mindMap.value?.title || "思维导图", level: 0 });
(parsed.nodes || parsed.children || []).forEach(node => walk(node, 1));
return rows;
} catch {
return [];
}
});
const mindMapStatusType = computed(() => {
if (mindMap.value?.parseStatus === "FAILED") return "danger";
if (mindMap.value?.parseStatus === "SUCCESS") return "success";
return "info";
});
const formatDuration = (seconds?: number): string => {
if (seconds == null) return "-";
const h = Math.floor(seconds / 3600);
@@ -149,12 +109,6 @@ const loadTaskHistory = async (tn: string) => {
}
};
const loadReviewExtensions = async (tn: string) => {
const mindMapRes = await getReviewMindMap(tn).catch(() => null);
const loadedMindMap: ReviewMindMap | null = mindMapRes?.data || null;
mindMap.value = loadedMindMap;
};
const loadDetail = async () => {
loading.value = true;
isEditing.value = false;
@@ -164,7 +118,6 @@ const loadDetail = async () => {
taskNum.value = "";
sessionInfo.value = null;
taskSessions.value = [];
mindMap.value = null;
try {
let res;
if (contentType.value === "report") {
@@ -187,7 +140,6 @@ const loadDetail = async () => {
if (info?.taskNum) {
await loadTaskHistory(info.taskNum);
await loadReviewExtensions(info.taskNum);
}
} catch {
// 非关键数据
@@ -241,29 +193,6 @@ const saveEdit = async () => {
}
};
const handleMindMapFileChange = async (uploadFile: any) => {
if (!taskNum.value) {
ElMessage.warning("未找到关联任务");
return;
}
const file = uploadFile.raw as File | undefined;
if (!file) {
return;
}
mindMapUploading.value = true;
try {
const res = await uploadReviewMindMap(taskNum.value, file);
mindMap.value = res?.data || null;
if (mindMap.value?.parseStatus === "FAILED") {
ElMessage.warning("文件已上传,但解析失败");
} else {
ElMessage.success("思维导图已解析");
}
} finally {
mindMapUploading.value = false;
}
};
watch(
() => [contentType.value, contentId.value],
() => {
@@ -299,6 +228,14 @@ watch(
>
编辑
</el-button>
<el-button
v-if="taskNum"
type="success"
size="small"
@click="goToRecall"
>
回忆复习
</el-button>
</div>
<div v-if="sessionInfo" class="session-stats">
<span>实际{{ formatDuration(sessionInfo.actualTime) }}</span>
@@ -328,59 +265,6 @@ watch(
</template>
</article>
<section class="review-extension-grid" v-if="taskNum">
<article class="surface-card extension-card">
<div class="extension-head">
<h3>思维导图</h3>
<el-tag size="small" :type="mindMapStatusType" effect="plain">
{{ mindMap?.parseStatus || "未上传" }}
</el-tag>
</div>
<div class="mind-map-form">
<div class="mind-map-upload-row">
<el-upload
:auto-upload="false"
:show-file-list="false"
accept=".xmind,.md,.markdown,.opml,.mm,.txt"
:on-change="handleMindMapFileChange"
>
<el-button type="success" :loading="mindMapUploading">上传导图文件</el-button>
</el-upload>
<el-button type="success" size="small" @click="goToRecall">
回忆复习
</el-button>
<el-tag v-if="mindMap?.fileFormat" size="small" effect="plain">
{{ mindMap.fileFormat }}
</el-tag>
</div>
<div v-if="mindMap" class="mind-map-preview">
<div class="mind-map-meta">
<strong>{{ mindMap.title }}</strong>
<span v-if="mindMap.fileName">{{ mindMap.fileName }}</span>
</div>
<p v-if="mindMap.summary" class="mind-map-summary">{{ mindMap.summary }}</p>
<p v-if="mindMap.parseStatus === 'FAILED'" class="mind-map-error">
{{ mindMap.parseError || "解析失败" }}
</p>
<div v-if="flattenedMindMapNodes.length > 0" class="mind-map-outline">
<div
v-for="(node, idx) in flattenedMindMapNodes"
:key="`${idx}-${node.title}`"
class="mind-map-node"
:style="{ paddingLeft: `${node.level * 16}px` }"
>
{{ node.title }}
</div>
</div>
<pre v-else-if="mindMap.content" class="mind-map-content">{{ mindMap.content }}</pre>
</div>
<el-empty v-else description="暂无思维导图" :image-size="64" />
</div>
</article>
</section>
<!-- 该任务下的所有学习记录 -->
<article class="surface-card history-card" v-if="taskSessions.length > 0">
<h3>该任务下的所有学习记录</h3>
@@ -486,101 +370,6 @@ watch(
padding: 40px 0;
}
.review-extension-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 14px;
}
.extension-card {
padding: 20px 22px;
}
.extension-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.extension-head h3 {
margin: 0;
color: var(--green-900);
font-size: 16px;
}
.mind-map-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.mind-map-upload-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.mind-map-preview {
border: 1px solid var(--border-soft);
border-radius: 8px;
background: #fbfefc;
padding: 12px;
}
.mind-map-meta {
display: flex;
flex-direction: column;
gap: 4px;
}
.mind-map-meta strong {
color: var(--text-primary);
word-break: break-word;
}
.mind-map-meta span,
.mind-map-summary {
color: var(--text-secondary);
font-size: 13px;
word-break: break-word;
}
.mind-map-summary,
.mind-map-error {
margin: 8px 0 0;
}
.mind-map-error {
color: #c62828;
font-size: 13px;
word-break: break-word;
}
.mind-map-outline {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 4px;
}
.mind-map-node {
font-size: 13px;
line-height: 1.5;
color: var(--text-primary);
word-break: break-word;
}
.mind-map-content {
margin: 10px 0 0;
white-space: pre-wrap;
word-break: break-word;
font-size: 13px;
color: var(--text-primary);
}
/* 历史记录卡片 */
.history-card {
padding: 20px 22px;
@@ -692,10 +481,6 @@ watch(
}
@media (max-width: 900px) {
.review-extension-grid {
grid-template-columns: 1fr;
}
.form-row {
flex-direction: column;
align-items: stretch;