feat(复习): 节点选择交互 + 移除 session 维度
- MindMapViewer 新增 selectable 模式,点击节点 emit node-select 事件 - ReviewRecall.vue 移除会话选择器,改为节点选择交互 - 支持 URL ?focusPath= 参数自动预设复习起点 - 节点选择弹窗确认后回忆导图根节点=选中节点标题 - ReviewDetail 和 Welcome 中调用 findNode API 匹配节点后跳转 - API: recallCompare 使用 focusPath 替代 sessionNum - API: 新增 findNode 端点
This commit is contained in:
@@ -23,7 +23,7 @@ export interface RecallRecord {
|
|||||||
id: number;
|
id: number;
|
||||||
taskNum: string;
|
taskNum: string;
|
||||||
standardMapId: number;
|
standardMapId: number;
|
||||||
sessionNum?: string;
|
focusPath?: string;
|
||||||
recallContent: string;
|
recallContent: string;
|
||||||
compareResult: string;
|
compareResult: string;
|
||||||
recallRatio: number;
|
recallRatio: number;
|
||||||
@@ -33,6 +33,12 @@ export interface RecallRecord {
|
|||||||
createdTime: string;
|
createdTime: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FindNodeResult {
|
||||||
|
path: string;
|
||||||
|
nodeTitle: string;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取/自动生成标准思维导图 */
|
/** 获取/自动生成标准思维导图 */
|
||||||
export const getStandardMindMap = (taskNum: string) => {
|
export const getStandardMindMap = (taskNum: string) => {
|
||||||
return request.get(`/review/standard-mind-map/${taskNum}`);
|
return request.get(`/review/standard-mind-map/${taskNum}`);
|
||||||
@@ -48,13 +54,18 @@ export const updateStandardMindMap = (taskNum: string, outline: string) => {
|
|||||||
return request.put(`/review/standard-mind-map/${taskNum}`, { outline });
|
return request.put(`/review/standard-mind-map/${taskNum}`, { outline });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 用户提交回忆大纲,与标准导图对比(可选指定会话维度) */
|
/** 用户提交回忆大纲,与标准导图对比(可选指定起始节点路径) */
|
||||||
export const recallCompare = (taskNum: string, recallOutline: string, sessionNum?: string) => {
|
export const recallCompare = (taskNum: string, recallOutline: string, focusPath?: string) => {
|
||||||
const body: Record<string, any> = { recallOutline };
|
const body: Record<string, any> = { recallOutline };
|
||||||
if (sessionNum) body.sessionNum = sessionNum;
|
if (focusPath) body.focusPath = focusPath;
|
||||||
return request.post(`/review/standard-mind-map/${taskNum}/recall`, body);
|
return request.post(`/review/standard-mind-map/${taskNum}/recall`, body);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 在标准导图中查找与内容最匹配的节点 */
|
||||||
|
export const findNode = (taskNum: string, content: string) => {
|
||||||
|
return request.post(`/review/standard-mind-map/${taskNum}/find-node`, { content });
|
||||||
|
};
|
||||||
|
|
||||||
/** 获取该任务的所有回忆对比记录 */
|
/** 获取该任务的所有回忆对比记录 */
|
||||||
export const listRecallRecords = (taskNum: string) => {
|
export const listRecallRecords = (taskNum: string) => {
|
||||||
return request.get(`/review/standard-mind-map/${taskNum}/recall-records`);
|
return request.get(`/review/standard-mind-map/${taskNum}/recall-records`);
|
||||||
|
|||||||
@@ -44,11 +44,6 @@ export const getActiveSession = (excludeTaskNum?: string) => {
|
|||||||
return request.get('/study-sessions/active', params);
|
return request.get('/study-sessions/active', params);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 获取任务的所有会话简要信息(复习维度选择用) */
|
|
||||||
export const getTaskSessions = (taskNum: string) => {
|
|
||||||
return request.get(`/study-sessions/tasks/${taskNum}/sessions`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 分页查询任务的历史残片 */
|
/** 分页查询任务的历史残片 */
|
||||||
export const getTaskFragments = (taskNum: string, page: number, size: number, keyword?: string) => {
|
export const getTaskFragments = (taskNum: string, page: number, size: number, keyword?: string) => {
|
||||||
const params: Record<string, any> = { page, size };
|
const params: Record<string, any> = { page, size };
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ const props = defineProps<{
|
|||||||
colorByCompare?: boolean;
|
colorByCompare?: boolean;
|
||||||
/** 画布高度,如 "520px"、"60vh",默认 480px */
|
/** 画布高度,如 "520px"、"60vh",默认 480px */
|
||||||
height?: string;
|
height?: string;
|
||||||
|
/** 节点可选择模式(点击节点触发 node-select 事件) */
|
||||||
|
selectable?: boolean;
|
||||||
|
/** 当前选中的节点路径(用于高亮,仅在 selectable 模式下生效) */
|
||||||
|
selectedPath?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -32,11 +36,27 @@ const emit = defineEmits<{
|
|||||||
(e: "change", outline: string): void;
|
(e: "change", outline: string): void;
|
||||||
/** 点击带溯源信息的节点 */
|
/** 点击带溯源信息的节点 */
|
||||||
(e: "node-click", node: { sourceType: string; sourceId: number }): void;
|
(e: "node-click", node: { sourceType: string; sourceId: number }): void;
|
||||||
|
/** 选择模式下点击节点,返回标题+路径 */
|
||||||
|
(e: "node-select", node: { title: string; path: string; childCount: number }): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const container = ref<HTMLElement | null>(null);
|
const container = ref<HTMLElement | null>(null);
|
||||||
let instance: MindElixirInstance | null = null;
|
let instance: MindElixirInstance | null = null;
|
||||||
let nodeCounter = 0;
|
let nodeCounter = 0;
|
||||||
|
/** 节点路径映射:elixir node id → { title, path, childCount } */
|
||||||
|
let nodePathMap: Record<string, { title: string; path: string; childCount: number }> = {};
|
||||||
|
|
||||||
|
/** 递归构建节点路径映射 */
|
||||||
|
const buildPathMap = (node: MindMapTreeNode, ancestors: string[]): { title: string; path: string; childCount: number } => {
|
||||||
|
const title = node.title || "(未命名)";
|
||||||
|
const path = [...ancestors, title].join(" / ");
|
||||||
|
const childCount = (node.children || []).reduce((sum, c) => sum + 1 + countAllDescendants(c), 0);
|
||||||
|
return { title, path, childCount };
|
||||||
|
};
|
||||||
|
|
||||||
|
const countAllDescendants = (node: MindMapTreeNode): number => {
|
||||||
|
return (node.children || []).reduce((sum, c) => sum + 1 + countAllDescendants(c), 0);
|
||||||
|
};
|
||||||
|
|
||||||
const COLOR_MATCHED = { background: "#c8e6c9", color: "#1b5e20" };
|
const COLOR_MATCHED = { background: "#c8e6c9", color: "#1b5e20" };
|
||||||
const COLOR_MISSED = { background: "#ffcdd2", color: "#b71c1c" };
|
const COLOR_MISSED = { background: "#ffcdd2", color: "#b71c1c" };
|
||||||
@@ -48,15 +68,20 @@ const compareStatus = (notes?: string): "MATCHED" | "MISSED" | null => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toElixirNode = (node: MindMapTreeNode): any => {
|
const toElixirNode = (node: MindMapTreeNode, ancestors: string[] = []): any => {
|
||||||
nodeCounter += 1;
|
nodeCounter += 1;
|
||||||
|
const elixirId = `n${nodeCounter}`;
|
||||||
const result: any = {
|
const result: any = {
|
||||||
id: `n${nodeCounter}`,
|
id: elixirId,
|
||||||
topic: node.title || "(未命名)",
|
topic: node.title || "(未命名)",
|
||||||
children: (node.children || []).map(toElixirNode),
|
children: (node.children || []).map((c) => toElixirNode(c, [...ancestors, node.title || "(未命名)"])),
|
||||||
};
|
};
|
||||||
|
// 记录路径映射
|
||||||
|
if (props.selectable) {
|
||||||
|
nodePathMap[elixirId] = buildPathMap(node, ancestors);
|
||||||
|
}
|
||||||
if (node.sourceType && node.sourceId != null) {
|
if (node.sourceType && node.sourceId != null) {
|
||||||
result.hyperLink = ""; // 占位,溯源通过 dangerouslySetInnerHTML 外的 tags 展示
|
result.hyperLink = "";
|
||||||
result.tags = [node.sourceType === "REPORT" ? "报告" : node.sourceType === "FRAGMENT" ? "残片" : "应用"];
|
result.tags = [node.sourceType === "REPORT" ? "报告" : node.sourceType === "FRAGMENT" ? "残片" : "应用"];
|
||||||
result.sourceType = node.sourceType;
|
result.sourceType = node.sourceType;
|
||||||
result.sourceId = node.sourceId;
|
result.sourceId = node.sourceId;
|
||||||
@@ -104,6 +129,7 @@ const render = () => {
|
|||||||
instance.destroy();
|
instance.destroy();
|
||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
||||||
|
nodePathMap = {};
|
||||||
instance = new MindElixir({
|
instance = new MindElixir({
|
||||||
el: container.value,
|
el: container.value,
|
||||||
direction: MindElixir.SIDE,
|
direction: MindElixir.SIDE,
|
||||||
@@ -122,6 +148,13 @@ const render = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.bus.addListener("selectNewNode", (node: any) => {
|
instance.bus.addListener("selectNewNode", (node: any) => {
|
||||||
|
if (props.selectable && node?.id) {
|
||||||
|
const info = nodePathMap[node.id];
|
||||||
|
if (info) {
|
||||||
|
emit("node-select", info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (node?.sourceType && node?.sourceId != null) {
|
if (node?.sourceType && node?.sourceId != null) {
|
||||||
emit("node-click", { sourceType: String(node.sourceType), sourceId: Number(node.sourceId) });
|
emit("node-click", { sourceType: String(node.sourceType), sourceId: Number(node.sourceId) });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,32 @@ import {
|
|||||||
} from "@/api/review";
|
} from "@/api/review";
|
||||||
import { getSessionDetail } from "@/api/studySessions";
|
import { getSessionDetail } from "@/api/studySessions";
|
||||||
import { updateFragments } from "@/api/reportFragments";
|
import { updateFragments } from "@/api/reportFragments";
|
||||||
|
import { findNode } from "@/api/standardMindMap";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
/** 调用 findNode API 匹配标准导图中的最近节点,跳转到回忆页 */
|
||||||
|
const goToRecall = async () => {
|
||||||
|
if (!content.value || !content.value.trim()) {
|
||||||
|
router.push(`/review/recall/${taskNum.value}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await findNode(taskNum.value, content.value.substring(0, 500));
|
||||||
|
const data = res?.data;
|
||||||
|
if (data?.path) {
|
||||||
|
router.push(`/review/recall/${taskNum.value}?focusPath=${encodeURIComponent(data.path)}`);
|
||||||
|
} else {
|
||||||
|
router.push(`/review/recall/${taskNum.value}`);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
router.push(`/review/recall/${taskNum.value}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const contentType = computed(() => route.params.type as string);
|
const contentType = computed(() => route.params.type as string);
|
||||||
const contentId = computed(() => Number(route.params.id));
|
const contentId = computed(() => Number(route.params.id));
|
||||||
|
|
||||||
@@ -327,7 +347,7 @@ watch(
|
|||||||
>
|
>
|
||||||
<el-button type="success" :loading="mindMapUploading">上传导图文件</el-button>
|
<el-button type="success" :loading="mindMapUploading">上传导图文件</el-button>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<el-button type="success" size="small" @click="router.push(`/review/recall/${taskNum}`)">
|
<el-button type="success" size="small" @click="goToRecall">
|
||||||
回忆复习
|
回忆复习
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-tag v-if="mindMap?.fileFormat" size="small" effect="plain">
|
<el-tag v-if="mindMap?.fileFormat" size="small" effect="plain">
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import {
|
|||||||
recallCompare,
|
recallCompare,
|
||||||
updateStandardMindMap,
|
updateStandardMindMap,
|
||||||
listRecallRecords,
|
listRecallRecords,
|
||||||
|
findNode,
|
||||||
type StandardMindMap,
|
type StandardMindMap,
|
||||||
type RecallRecord,
|
type RecallRecord,
|
||||||
} from "@/api/standardMindMap";
|
} from "@/api/standardMindMap";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import MindMapViewer, { type MindMapTreeNode } from "@/components/MindMapViewer.vue";
|
import MindMapViewer, { type MindMapTreeNode } from "@/components/MindMapViewer.vue";
|
||||||
import { getTaskSessions } from "@/api/studySessions";
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -30,10 +30,10 @@ let regeneratingTimer: ReturnType<typeof setInterval> | null = null;
|
|||||||
const lastResult = ref<any>(null);
|
const lastResult = ref<any>(null);
|
||||||
const hasCompared = ref(false);
|
const hasCompared = ref(false);
|
||||||
|
|
||||||
// 复习维度选择
|
// 起点节点选择
|
||||||
const sessions = ref<{ sessionNum: string; startTime: string; expectation: string; reportCount: number; fragmentCount: number }[]>([]);
|
const focusPath = ref((route.query.focusPath as string) || "");
|
||||||
const selectedSession = ref("");
|
const focusConfirmVisible = ref(false);
|
||||||
const loadingSessions = ref(false);
|
const selectedNodeInfo = ref<{ title: string; path: string; childCount: number } | null>(null);
|
||||||
|
|
||||||
// 回忆输入(思维导图形式)
|
// 回忆输入(思维导图形式)
|
||||||
const recallTree = ref<MindMapTreeNode | null>(null);
|
const recallTree = ref<MindMapTreeNode | null>(null);
|
||||||
@@ -192,7 +192,7 @@ const handleRecallCompare = async () => {
|
|||||||
comparingSeconds.value = 0;
|
comparingSeconds.value = 0;
|
||||||
comparingTimer = setInterval(() => { comparingSeconds.value++; }, 1000);
|
comparingTimer = setInterval(() => { comparingSeconds.value++; }, 1000);
|
||||||
try {
|
try {
|
||||||
const res = await recallCompare(taskNum.value, outline, selectedSession.value || undefined);
|
const res = await recallCompare(taskNum.value, outline, focusPath.value || undefined);
|
||||||
standard.value = res?.data || null;
|
standard.value = res?.data || null;
|
||||||
hasCompared.value = true;
|
hasCompared.value = true;
|
||||||
// 解析对比结果
|
// 解析对比结果
|
||||||
@@ -318,29 +318,26 @@ const extraNodes = computed(() => {
|
|||||||
return raw.map((item: any) => typeof item === "string" ? { title: item } : item);
|
return raw.map((item: any) => typeof item === "string" ? { title: item } : item);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载会话列表(复习维度选择器用)
|
// 节点选择回调(selectable 模式下点击节点)
|
||||||
const loadSessions = async () => {
|
const handleNodeSelect = (node: { title: string; path: string; childCount: number }) => {
|
||||||
loadingSessions.value = true;
|
selectedNodeInfo.value = node;
|
||||||
try {
|
focusConfirmVisible.value = true;
|
||||||
const res = await getTaskSessions(taskNum.value);
|
|
||||||
sessions.value = res?.data || [];
|
|
||||||
} catch {
|
|
||||||
sessions.value = [];
|
|
||||||
} finally {
|
|
||||||
loadingSessions.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 格式化会话标签
|
// 确认以选中节点为起点
|
||||||
const formatSessionLabel = (s: { sessionNum: string; startTime: string; expectation: string; reportCount: number; fragmentCount: number }) => {
|
const confirmFocus = () => {
|
||||||
const date = s.startTime ? s.startTime.replace("T", " ").substring(0, 16) : s.sessionNum;
|
if (selectedNodeInfo.value) {
|
||||||
const hint = s.expectation ? s.expectation.substring(0, 30) : "";
|
focusPath.value = selectedNodeInfo.value.path;
|
||||||
return `${date} ${hint ? "— " + hint : ""}`;
|
recallTree.value = {
|
||||||
|
title: selectedNodeInfo.value.title,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
focusConfirmVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData();
|
loadData();
|
||||||
loadSessions();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -355,24 +352,25 @@ onMounted(() => {
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 复习维度选择器 -->
|
<!-- 起点节点指示 -->
|
||||||
<div class="review-scope surface-card" v-if="sessions.length > 0 || loadingSessions">
|
<div class="review-scope surface-card" v-if="focusPath">
|
||||||
<span class="scope-label">复习范围</span>
|
<span class="scope-label">复习起点</span>
|
||||||
<el-select v-model="selectedSession" placeholder="选择复习范围" clearable size="small" :loading="loadingSessions">
|
<el-tag size="small" type="success" effect="light">{{ focusPath }}</el-tag>
|
||||||
<el-option label="全部任务" value="" />
|
<el-button size="small" text @click="focusPath = ''">重选节点</el-button>
|
||||||
<el-option
|
|
||||||
v-for="s in sessions"
|
|
||||||
:key="s.sessionNum"
|
|
||||||
:label="formatSessionLabel(s)"
|
|
||||||
:value="s.sessionNum"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-tag v-if="selectedSession" size="small" type="success" effect="light">
|
|
||||||
已过滤:仅匹配该会话知识点
|
|
||||||
</el-tag>
|
|
||||||
<span v-else-if="!selectedSession" class="scope-hint">不过滤,对比全部标准导图</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 节点选择确认弹窗 -->
|
||||||
|
<el-dialog v-model="focusConfirmVisible" title="确认复习起点" width="400px">
|
||||||
|
<p v-if="selectedNodeInfo">
|
||||||
|
以 「<strong>{{ selectedNodeInfo.title }}</strong>」 为起点,
|
||||||
|
复习该知识点下的 <strong>{{ selectedNodeInfo.childCount }}</strong> 个子知识点?
|
||||||
|
</p>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="focusConfirmVisible = false">取消</el-button>
|
||||||
|
<el-button type="success" @click="confirmFocus">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
<div class="stats-card surface-card" v-if="hasCompared && lastResult">
|
<div class="stats-card surface-card" v-if="hasCompared && lastResult">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
@@ -404,7 +402,7 @@ onMounted(() => {
|
|||||||
@click="selectHistoryRecord(record)"
|
@click="selectHistoryRecord(record)"
|
||||||
>
|
>
|
||||||
<span class="history-time">{{ record.createdTime?.replace("T", " ")?.substring(0, 16) }}</span>
|
<span class="history-time">{{ record.createdTime?.replace("T", " ")?.substring(0, 16) }}</span>
|
||||||
<el-tag v-if="record.sessionNum" size="small" type="success" effect="plain" class="session-badge">会话</el-tag>
|
<el-tag v-if="record.focusPath" size="small" type="success" effect="plain" class="session-badge" :title="record.focusPath">节点</el-tag>
|
||||||
<el-tag v-else size="small" type="info" effect="plain" class="session-badge">全部</el-tag>
|
<el-tag v-else size="small" type="info" effect="plain" class="session-badge">全部</el-tag>
|
||||||
<span class="history-ratio">覆盖率 {{ (record.recallRatio * 100).toFixed(1) }}%</span>
|
<span class="history-ratio">覆盖率 {{ (record.recallRatio * 100).toFixed(1) }}%</span>
|
||||||
<span class="history-detail">✅{{ record.matchedCount }} ❌{{ record.missedCount }} ➕{{ record.extraCount }}</span>
|
<span class="history-detail">✅{{ record.matchedCount }} ❌{{ record.missedCount }} ➕{{ record.extraCount }}</span>
|
||||||
@@ -493,11 +491,16 @@ onMounted(() => {
|
|||||||
<span>来源:{{ standard.generator }}</span>
|
<span>来源:{{ standard.generator }}</span>
|
||||||
<span>参考:{{ standard.sourceReportCount }} 份报告 + {{ standard.sourceFragmentCount }} 份残片</span>
|
<span>参考:{{ standard.sourceReportCount }} 份报告 + {{ standard.sourceFragmentCount }} 份残片</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="!hasCompared && !focusPath" class="select-hint">
|
||||||
|
点击导图节点选择复习起点,选好后开始回忆填空
|
||||||
|
</p>
|
||||||
<MindMapViewer
|
<MindMapViewer
|
||||||
v-if="standardTree"
|
v-if="standardTree"
|
||||||
:tree="compareTree || standardTree"
|
:tree="compareTree || standardTree"
|
||||||
:color-by-compare="!!compareTree"
|
:color-by-compare="!!compareTree"
|
||||||
|
:selectable="!editingStandard && !focusPath && !hasCompared"
|
||||||
height="60vh"
|
height="60vh"
|
||||||
|
@node-select="handleNodeSelect"
|
||||||
@node-click="goToNodeSource"
|
@node-click="goToNodeSource"
|
||||||
/>
|
/>
|
||||||
<pre v-else class="standard-outline">{{ standard.outline || standard.content }}</pre>
|
<pre v-else class="standard-outline">{{ standard.outline || standard.content }}</pre>
|
||||||
@@ -816,4 +819,14 @@ onMounted(() => {
|
|||||||
.session-badge {
|
.session-badge {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-hint {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #e6a23c;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #fdf6ec;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #e6a23c;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -99,6 +99,29 @@ const openRecallDetail = () => {
|
|||||||
router.push(`/review/detail/${group.navigateType}/${group.navigateId}`);
|
router.push(`/review/detail/${group.navigateType}/${group.navigateId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 从回忆卡片跳转到回忆复习页(先匹配标准导图节点) */
|
||||||
|
const goToRecallFromCard = async () => {
|
||||||
|
const group = recallCard.value.group;
|
||||||
|
if (!group) return;
|
||||||
|
recallCard.value.visible = false;
|
||||||
|
const tn = group.taskNum;
|
||||||
|
const content = group.content || "";
|
||||||
|
if (tn && content) {
|
||||||
|
try {
|
||||||
|
const { findNode } = await import("@/api/standardMindMap");
|
||||||
|
const res = await findNode(tn, content.substring(0, 500));
|
||||||
|
const data = res?.data;
|
||||||
|
if (data?.path) {
|
||||||
|
router.push(`/review/recall/${tn}?focusPath=${encodeURIComponent(data.path)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch { /* fall through */ }
|
||||||
|
router.push(`/review/recall/${tn}`);
|
||||||
|
} else {
|
||||||
|
router.push("/review");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const startStudy = () => {
|
const startStudy = () => {
|
||||||
router.push("/study");
|
router.push("/study");
|
||||||
};
|
};
|
||||||
@@ -197,6 +220,7 @@ onMounted(() => {
|
|||||||
回忆好了,展开对照
|
回忆好了,展开对照
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else type="success" @click="openRecallDetail">进入详情页</el-button>
|
<el-button v-else type="success" @click="openRecallDetail">进入详情页</el-button>
|
||||||
|
<el-button type="success" plain @click="goToRecallFromCard">前往回忆复习</el-button>
|
||||||
<el-button @click="recallCard.visible = false">关闭</el-button>
|
<el-button @click="recallCard.visible = false">关闭</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|||||||
Reference in New Issue
Block a user