feat(回忆复习):回忆输入改用思维导图 + 布局优化
- 回忆输入区从文本域改为可编辑思维导图(Tab 加子/Enter 加同级) - 回忆历史点击后自动将内容还原到导图编辑区 - 增加回忆页宽屏模式(wide route meta),适配导图横向展示 - MindMapViewer 增加 height prop 支持自定义高度 - 回忆、对比、标准导图各区域全部使用 60vh 导图展示
This commit is contained in:
+5
-1
@@ -14,7 +14,7 @@ const route = useRoute();
|
|||||||
<el-header class="shell-header">
|
<el-header class="shell-header">
|
||||||
<MyHead />
|
<MyHead />
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-main class="shell-main">
|
<el-main class="shell-main" :class="{ wide: route.meta.wide }">
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<Transition name="fade-up" mode="out-in">
|
<Transition name="fade-up" mode="out-in">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<component :is="Component" :key="route.fullPath" />
|
||||||
@@ -54,6 +54,10 @@ const route = useRoute();
|
|||||||
padding: 18px 20px 24px;
|
padding: 18px 20px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shell-main.wide {
|
||||||
|
max-width: 1720px;
|
||||||
|
}
|
||||||
|
|
||||||
.shell-footer {
|
.shell-footer {
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 0 20px 18px;
|
padding: 0 20px 18px;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const props = defineProps<{
|
|||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
/** 是否按 MATCHED/MISSED 标记着色 */
|
/** 是否按 MATCHED/MISSED 标记着色 */
|
||||||
colorByCompare?: boolean;
|
colorByCompare?: boolean;
|
||||||
|
/** 画布高度,如 "520px"、"60vh",默认 480px */
|
||||||
|
height?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -143,13 +145,12 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mind-map-viewer" ref="container"></div>
|
<div class="mind-map-viewer" ref="container" :style="{ height: height || '480px' }"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mind-map-viewer {
|
.mind-map-viewer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 480px;
|
|
||||||
border: 1px solid var(--border-soft, #e0e0e0);
|
border: 1px solid var(--border-soft, #e0e0e0);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
+152
-185
@@ -19,11 +19,14 @@ 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 recallOutline = ref("");
|
|
||||||
const comparing = ref(false);
|
const comparing = ref(false);
|
||||||
const lastResult = ref<any>(null);
|
const lastResult = ref<any>(null);
|
||||||
const hasCompared = ref(false);
|
const hasCompared = ref(false);
|
||||||
|
|
||||||
|
// 回忆输入(思维导图形式)
|
||||||
|
const recallTree = ref<MindMapTreeNode | null>(null);
|
||||||
|
const recallViewer = ref<InstanceType<typeof MindMapViewer> | null>(null);
|
||||||
|
|
||||||
// 标准导图展开/折叠(默认折叠,防止剧透)
|
// 标准导图展开/折叠(默认折叠,防止剧透)
|
||||||
const standardExpanded = ref(false);
|
const standardExpanded = ref(false);
|
||||||
|
|
||||||
@@ -65,6 +68,34 @@ const ratioPercent = computed(() => {
|
|||||||
return `${(lastResult.value.recallRatio * 100).toFixed(1)}%`;
|
return `${(lastResult.value.recallRatio * 100).toFixed(1)}%`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 回忆导图的初始根节点(只给根主题,内容凭记忆补全) */
|
||||||
|
const buildEmptyRecallTree = (): MindMapTreeNode => ({
|
||||||
|
title: standard.value?.title || `任务 ${taskNum.value}`,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 将缩进大纲文本解析为树(兼容 "- " 前缀与 ├└│─ 等装饰符) */
|
||||||
|
const parseOutlineToTree = (text: string): MindMapTreeNode | null => {
|
||||||
|
const lines = (text || "").split(/\r?\n/).filter((l) => l.trim().length > 0);
|
||||||
|
if (lines.length === 0) return null;
|
||||||
|
const cleanTitle = (l: string) => l.replace(/^[\s│├└┌┐┘┬┴┼─\-*•]+/, "").trim();
|
||||||
|
const indentOf = (l: string) => {
|
||||||
|
const match = l.match(/^[\s│├└┌─]*/);
|
||||||
|
const prefix = match ? match[0] : "";
|
||||||
|
return Math.max(1, Math.floor(prefix.replace(/[^\s]/g, " ").length / 2));
|
||||||
|
};
|
||||||
|
const root: MindMapTreeNode = { title: cleanTitle(lines[0]), children: [] };
|
||||||
|
const stack: { node: MindMapTreeNode; level: number }[] = [{ node: root, level: 0 }];
|
||||||
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
const level = indentOf(lines[i]);
|
||||||
|
const node: MindMapTreeNode = { title: cleanTitle(lines[i]), children: [] };
|
||||||
|
while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();
|
||||||
|
stack[stack.length - 1].node.children!.push(node);
|
||||||
|
stack.push({ node, level });
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
};
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -75,6 +106,9 @@ const loadData = async () => {
|
|||||||
standard.value = null;
|
standard.value = null;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
if (!recallTree.value) {
|
||||||
|
recallTree.value = buildEmptyRecallTree();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,13 +133,14 @@ const handleRegenerate = async () => {
|
|||||||
|
|
||||||
// 提交回忆对比
|
// 提交回忆对比
|
||||||
const handleRecallCompare = async () => {
|
const handleRecallCompare = async () => {
|
||||||
if (!recallOutline.value.trim()) {
|
const outline = recallViewer.value?.toOutline() || "";
|
||||||
ElMessage.warning("请输入回忆内容大纲");
|
if (outline.split(/\r?\n/).filter((l) => l.trim()).length <= 1) {
|
||||||
|
ElMessage.warning("请先在回忆导图中添加节点(选中节点后按 Tab 加子节点)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
comparing.value = true;
|
comparing.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await recallCompare(taskNum.value, recallOutline.value);
|
const res = await recallCompare(taskNum.value, outline);
|
||||||
standard.value = res?.data || null;
|
standard.value = res?.data || null;
|
||||||
hasCompared.value = true;
|
hasCompared.value = true;
|
||||||
// 解析对比结果
|
// 解析对比结果
|
||||||
@@ -130,6 +165,11 @@ const handleRecallCompare = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 清空重写
|
||||||
|
const resetRecall = () => {
|
||||||
|
recallTree.value = buildEmptyRecallTree();
|
||||||
|
};
|
||||||
|
|
||||||
// 编辑标准导图
|
// 编辑标准导图
|
||||||
const startEditStandard = () => {
|
const startEditStandard = () => {
|
||||||
editOutline.value = standard.value?.outline || "";
|
editOutline.value = standard.value?.outline || "";
|
||||||
@@ -185,8 +225,9 @@ const loadHistory = async () => {
|
|||||||
const selectHistoryRecord = (record: RecallRecord) => {
|
const selectHistoryRecord = (record: RecallRecord) => {
|
||||||
try {
|
try {
|
||||||
lastResult.value = JSON.parse(record.compareResult);
|
lastResult.value = JSON.parse(record.compareResult);
|
||||||
// 重新设置 recallOutline 为历史内容
|
// 将历史回忆内容还原到导图输入区
|
||||||
recallOutline.value = record.recallContent;
|
const tree = parseOutlineToTree(record.recallContent);
|
||||||
|
if (tree) recallTree.value = tree;
|
||||||
hasCompared.value = true;
|
hasCompared.value = true;
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error("解析对比记录失败");
|
ElMessage.error("解析对比记录失败");
|
||||||
@@ -198,32 +239,6 @@ const goToReviewDetail = (sourceType: string, sourceId: number) => {
|
|||||||
router.push(`/review/detail/${sourceType.toLowerCase()}/${sourceId}`);
|
router.push(`/review/detail/${sourceType.toLowerCase()}/${sourceId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染对比结果树
|
|
||||||
const renderCompareTree = (nodes: any[], level: number = 0): string => {
|
|
||||||
if (!nodes || nodes.length === 0) return "";
|
|
||||||
return nodes
|
|
||||||
.map((node: any) => {
|
|
||||||
const notes = node.notes || "";
|
|
||||||
const isMatched = notes.startsWith("MATCHED|");
|
|
||||||
const isMissed = notes.startsWith("MISSED|");
|
|
||||||
const marker = isMatched ? "✅ " : isMissed ? "❌ " : " ";
|
|
||||||
const title = node.title || "(未命名)";
|
|
||||||
const indent = " ".repeat(level);
|
|
||||||
const childrenHtml = renderCompareTree(node.children, level + 1);
|
|
||||||
return `${indent}${marker}${title}\n${childrenHtml}`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const resultText = computed(() => {
|
|
||||||
if (!lastResult.value?.matchedTree) return "";
|
|
||||||
const tree = lastResult.value.matchedTree;
|
|
||||||
// 根节点标题
|
|
||||||
let text = `${tree.title || "思维导图"}\n\n`;
|
|
||||||
text += renderCompareTree(tree.children || tree.nodes || [], 1);
|
|
||||||
return text;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取遗漏项列表
|
// 获取遗漏项列表
|
||||||
const missedItems = computed(() => {
|
const missedItems = computed(() => {
|
||||||
if (!lastResult.value?.matchedTree) return [];
|
if (!lastResult.value?.matchedTree) return [];
|
||||||
@@ -284,7 +299,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 记忆历史 -->
|
<!-- 回忆历史 -->
|
||||||
<article class="surface-card" v-if="showHistory && historyRecords.length > 0">
|
<article class="surface-card" v-if="showHistory && historyRecords.length > 0">
|
||||||
<h3>回忆历史</h3>
|
<h3>回忆历史</h3>
|
||||||
<div class="history-list">
|
<div class="history-list">
|
||||||
@@ -299,111 +314,99 @@ onMounted(() => {
|
|||||||
<span class="history-detail">✅{{ record.matchedCount }} ❌{{ record.missedCount }} ➕{{ record.extraCount }}</span>
|
<span class="history-detail">✅{{ record.matchedCount }} ❌{{ record.missedCount }} ➕{{ record.extraCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-divider />
|
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- 两大块:回忆输入区 + 标准导图 -->
|
<!-- 回忆输入:可编辑思维导图 -->
|
||||||
<div class="recall-layout">
|
<article class="surface-card recall-panel">
|
||||||
<!-- 左侧:回忆输入 -->
|
<div class="panel-header">
|
||||||
<article class="surface-card recall-panel">
|
|
||||||
<h3>🧠 你的回忆</h3>
|
<h3>🧠 你的回忆</h3>
|
||||||
<p class="panel-hint">
|
<div class="panel-actions">
|
||||||
凭记忆写下该任务的知识点大纲(缩进表示层级),写完后点击"提交对比"。
|
|
||||||
</p>
|
|
||||||
<el-input
|
|
||||||
v-model="recallOutline"
|
|
||||||
type="textarea"
|
|
||||||
:rows="12"
|
|
||||||
placeholder="Java 并发编程
|
|
||||||
├─ 线程基础
|
|
||||||
│ ├─ 创建线程
|
|
||||||
│ └─ 线程状态
|
|
||||||
├─ 锁机制
|
|
||||||
│ ├─ Synchronized
|
|
||||||
│ └─ ReentrantLock"
|
|
||||||
/>
|
|
||||||
<div class="recall-actions">
|
|
||||||
<el-button type="primary" :loading="comparing" @click="handleRecallCompare">
|
<el-button type="primary" :loading="comparing" @click="handleRecallCompare">
|
||||||
提交对比
|
提交对比
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="hasCompared" @click="recallOutline = ''">
|
<el-button v-if="hasCompared" @click="resetRecall">
|
||||||
清空重写
|
清空重写
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="panel-hint">
|
||||||
|
凭记忆在导图上补全该任务的知识点:选中节点后 Tab 加子节点、Enter 加同级节点、双击修改文字。
|
||||||
|
</p>
|
||||||
|
<MindMapViewer ref="recallViewer" :tree="recallTree" :editable="true" height="60vh" />
|
||||||
|
</article>
|
||||||
|
|
||||||
<!-- 对比结果 -->
|
<!-- 对比结果 -->
|
||||||
<div v-if="hasCompared && compareTree" class="compare-result">
|
<article class="surface-card" v-if="hasCompared && compareTree">
|
||||||
<h4>📊 对比结果</h4>
|
<h3>📊 对比结果</h3>
|
||||||
<p class="panel-hint">绿色=回忆命中,红色=遗漏。点击带标签的节点可回看原文。</p>
|
<p class="panel-hint">绿色=回忆命中,红色=遗漏。点击带标签的节点可回看原文。</p>
|
||||||
<MindMapViewer
|
<MindMapViewer
|
||||||
:tree="compareTree"
|
:tree="compareTree"
|
||||||
:color-by-compare="true"
|
:color-by-compare="true"
|
||||||
@node-click="goToNodeSource"
|
height="60vh"
|
||||||
/>
|
@node-click="goToNodeSource"
|
||||||
</div>
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- 右侧:标准导图 -->
|
<!-- 标准导图 -->
|
||||||
<article class="surface-card standard-panel">
|
<article class="surface-card standard-panel">
|
||||||
<div class="standard-header">
|
<div class="panel-header">
|
||||||
<h3>📖 标准思维导图</h3>
|
<h3>📖 标准思维导图</h3>
|
||||||
<div class="standard-actions">
|
<div class="panel-actions">
|
||||||
<el-button size="small" @click="standardExpanded = !standardExpanded">
|
<el-button size="small" @click="standardExpanded = !standardExpanded">
|
||||||
{{ standardExpanded ? "折叠" : "展开" }}
|
{{ standardExpanded ? "折叠" : "展开" }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="standardExpanded"
|
v-if="standardExpanded"
|
||||||
size="small"
|
size="small"
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="handleRegenerate"
|
@click="handleRegenerate"
|
||||||
>
|
>
|
||||||
重新生成
|
重新生成
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="standardExpanded && !editingStandard"
|
v-if="standardExpanded && !editingStandard"
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="startEditStandard"
|
@click="startEditStandard"
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="!standard" class="empty-state">
|
<div v-if="!standard" class="empty-state">
|
||||||
<el-empty description="暂无标准思维导图" :image-size="64">
|
<el-empty description="暂无标准思维导图" :image-size="64">
|
||||||
<el-button type="primary" :loading="loading" @click="loadData">
|
<el-button type="primary" :loading="loading" @click="loadData">
|
||||||
生成
|
生成
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-empty>
|
</el-empty>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="editingStandard" class="edit-area">
|
<div v-else-if="editingStandard" class="edit-area">
|
||||||
<p class="panel-hint">直接在导图上编辑(Tab 加子节点 / Enter 加同级 / 双击改文字),保存后生效。</p>
|
<p class="panel-hint">直接在导图上编辑(Tab 加子节点 / Enter 加同级 / 双击改文字),保存后生效。</p>
|
||||||
<MindMapViewer ref="editViewer" :tree="standardTree" :editable="true" />
|
<MindMapViewer ref="editViewer" :tree="standardTree" :editable="true" height="60vh" />
|
||||||
<div class="edit-actions">
|
<div class="edit-actions">
|
||||||
<el-button @click="cancelStandardEdit">取消</el-button>
|
<el-button @click="cancelStandardEdit">取消</el-button>
|
||||||
<el-button type="primary" :loading="savingStandard" @click="saveStandardEditFromViewer">
|
<el-button type="primary" :loading="savingStandard" @click="saveStandardEditFromViewer">
|
||||||
保存更新
|
保存更新
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else-if="standardExpanded" class="standard-content">
|
<div v-else-if="standardExpanded" class="standard-content">
|
||||||
<div class="standard-meta">
|
<div class="standard-meta">
|
||||||
<span>来源:{{ standard.generator }}</span>
|
<span>来源:{{ standard.generator }}</span>
|
||||||
<span>参考:{{ standard.sourceReportCount }} 份报告 + {{ standard.sourceFragmentCount }} 份残片</span>
|
<span>参考:{{ standard.sourceReportCount }} 份报告 + {{ standard.sourceFragmentCount }} 份残片</span>
|
||||||
</div>
|
|
||||||
<MindMapViewer v-if="standardTree" :tree="standardTree" @node-click="goToNodeSource" />
|
|
||||||
<pre v-else class="standard-outline">{{ standard.outline || standard.content }}</pre>
|
|
||||||
<p v-if="standard.summary" class="generator-info">{{ standard.summary }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<MindMapViewer v-if="standardTree" :tree="standardTree" height="60vh" @node-click="goToNodeSource" />
|
||||||
|
<pre v-else class="standard-outline">{{ standard.outline || standard.content }}</pre>
|
||||||
|
<p v-if="standard.summary" class="generator-info">{{ standard.summary }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else class="collapsed-hint">
|
<div v-else class="collapsed-hint">
|
||||||
<p>标准导图已生成。展开后可查看或编辑。建议先凭记忆写回忆大纲再对照。</p>
|
<p>标准导图已生成。展开后可查看或编辑。建议先凭记忆补全回忆导图再对照。</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 遗漏项 + 额外项详情 -->
|
<!-- 遗漏项 + 额外项详情 -->
|
||||||
<div v-if="hasCompared && lastResult" class="detail-section">
|
<div v-if="hasCompared && lastResult" class="detail-section">
|
||||||
@@ -444,8 +447,6 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@@ -468,6 +469,12 @@ onMounted(() => {
|
|||||||
border: 1px solid var(--border-soft, #e0e0e0);
|
border: 1px solid var(--border-soft, #e0e0e0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.surface-card h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: var(--green-900);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-card {
|
.stats-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
@@ -496,24 +503,24 @@ onMounted(() => {
|
|||||||
.missed { color: #c62828; }
|
.missed { color: #c62828; }
|
||||||
.extra { color: #1565c0; }
|
.extra { color: #1565c0; }
|
||||||
|
|
||||||
.recall-layout {
|
.panel-header {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr;
|
align-items: center;
|
||||||
gap: 14px;
|
justify-content: space-between;
|
||||||
align-items: start;
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
.panel-header h3 {
|
||||||
.recall-layout {
|
margin: 0;
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.recall-panel h3,
|
.panel-actions {
|
||||||
.standard-panel h3 {
|
display: flex;
|
||||||
margin: 0 0 8px;
|
gap: 6px;
|
||||||
color: var(--green-900);
|
flex-shrink: 0;
|
||||||
font-size: 16px;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-hint {
|
.panel-hint {
|
||||||
@@ -523,31 +530,6 @@ onMounted(() => {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recall-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.standard-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.standard-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.standard-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.standard-meta {
|
.standard-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -589,28 +571,6 @@ onMounted(() => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compare-result {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compare-result h4 {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
color: var(--green-800);
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compare-tree {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.7;
|
|
||||||
background: #fafafa;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid var(--border-soft, #e0e0e0);
|
|
||||||
max-height: 500px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-area {
|
.edit-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -624,9 +584,16 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-section {
|
.detail-section {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.detail-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section h3 {
|
.detail-section h3 {
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: "/review/recall/:taskNum",
|
path: "/review/recall/:taskNum",
|
||||||
component: () => import("@/components/ReviewRecall.vue"),
|
component: () => import("@/components/ReviewRecall.vue"),
|
||||||
meta: { requiresAuth: true, title: "回忆复习" },
|
meta: { requiresAuth: true, title: "回忆复习", wide: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/start-task/:taskNum",
|
path: "/start-task/:taskNum",
|
||||||
|
|||||||
Reference in New Issue
Block a user