feat: 学习会话页展示当前会话碎片列表并支持编辑

- 会话加载时获取碎片列表,创建碎片后自动刷新
- 碎片列表支持内联编辑(textarea + 保存/取消)
- 修复多根节点问题,用 div 包裹模板内容以兼容 Transition 组件
This commit is contained in:
2026-05-30 15:51:09 +08:00
parent a82c25b45c
commit 887756f4ff
+132 -1
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from "vue"; import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { useTimer } from "@/components/composables/useTimer"; import { useTimer } from "@/components/composables/useTimer";
@@ -10,6 +10,7 @@ import {
startOrContinueStudySession, startOrContinueStudySession,
} from "@/api/studySessions"; } from "@/api/studySessions";
import { useStudyFragment } from "@/components/composables/fragment"; import { useStudyFragment } from "@/components/composables/fragment";
import { getFragmentsBySession, updateFragments } from "@/api/reportFragments";
import router from "@/router"; import router from "@/router";
const { const {
@@ -23,6 +24,16 @@ const {
const summaryDialogVisible = ref(false); const summaryDialogVisible = ref(false);
const summaryContent = ref(""); const summaryContent = ref("");
// 当前会话碎片列表
interface FragmentItem {
id: number;
content: string;
}
const fragmentsList = ref<FragmentItem[]>([]);
const editingFragmentId = ref<number | null>(null);
const editFragmentContent = ref("");
const savingFragment = ref(false);
const route = useRoute(); const route = useRoute();
const taskNum = route.params.taskNum as string; const taskNum = route.params.taskNum as string;
@@ -213,6 +224,7 @@ const loadTaskSession = async () => {
} else { } else {
syncDisplay(taskInfo.value.pointerPosition || 25 * 60 * 1000); syncDisplay(taskInfo.value.pointerPosition || 25 * 60 * 1000);
} }
loadFragments();
} catch (err: any) { } catch (err: any) {
ElMessage.error(err.message || "加载任务失败"); ElMessage.error(err.message || "加载任务失败");
} }
@@ -227,6 +239,53 @@ const initPage = async () => {
await loadTaskSession(); await loadTaskSession();
}; };
// 碎片列表
const loadFragments = async () => {
if (!taskInfo.value.sessionNum) return;
try {
const res = await getFragmentsBySession(taskInfo.value.sessionNum);
if (res?.code === 200) {
fragmentsList.value = res.data || [];
}
} catch {
// 非关键数据
}
};
const startEditFragment = (fragment: FragmentItem) => {
editingFragmentId.value = fragment.id;
editFragmentContent.value = fragment.content;
};
const cancelEditFragment = () => {
editingFragmentId.value = null;
editFragmentContent.value = "";
};
const saveEditFragment = async (id: number) => {
if (!editFragmentContent.value.trim()) {
ElMessage.warning("内容不能为空");
return;
}
savingFragment.value = true;
try {
const res = await updateFragments(id, editFragmentContent.value);
if (res?.code === 200) {
const item = fragmentsList.value.find((f) => f.id === id);
if (item) item.content = editFragmentContent.value;
editingFragmentId.value = null;
editFragmentContent.value = "";
ElMessage.success("保存成功");
} else {
ElMessage.error(res?.message || "保存失败");
}
} catch (e: any) {
ElMessage.error(e.message || "请求失败");
} finally {
savingFragment.value = false;
}
};
onMounted(() => { onMounted(() => {
displayInterval = window.setInterval(() => { displayInterval = window.setInterval(() => {
nowTimestamp.value = Date.now(); nowTimestamp.value = Date.now();
@@ -234,6 +293,13 @@ onMounted(() => {
initPage(); initPage();
}); });
// 碎片创建成功后刷新列表
watch(fragmentsDialogVisible, (newVal, oldVal) => {
if (oldVal === true && newVal === false) {
loadFragments();
}
});
onUnmounted(() => { onUnmounted(() => {
if (displayInterval) { if (displayInterval) {
clearInterval(displayInterval); clearInterval(displayInterval);
@@ -243,6 +309,7 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<div>
<section class="start-page"> <section class="start-page">
<div class="task-info-bar"> <div class="task-info-bar">
<el-tag type="success" effect="plain">任务编号{{ taskInfo.taskNum }}</el-tag> <el-tag type="success" effect="plain">任务编号{{ taskInfo.taskNum }}</el-tag>
@@ -298,6 +365,33 @@ onUnmounted(() => {
</div> </div>
</article> </article>
</div> </div>
<!-- 当前会话学习残片 -->
<article class="surface-card fragments-card" v-if="fragmentsList.length > 0">
<h3>当前会话学习残片</h3>
<div
v-for="fragment in fragmentsList"
:key="fragment.id"
class="fragment-item"
>
<template v-if="editingFragmentId === fragment.id">
<el-input
v-model="editFragmentContent"
type="textarea"
:rows="3"
placeholder="请输入学习内容"
/>
<div class="fragment-edit-actions">
<el-button size="small" @click="cancelEditFragment">取消</el-button>
<el-button size="small" type="primary" :loading="savingFragment" @click="saveEditFragment(fragment.id)">保存</el-button>
</div>
</template>
<template v-else>
<span class="fragment-content">{{ fragment.content }}</span>
<el-button size="small" @click="startEditFragment(fragment)">编辑</el-button>
</template>
</div>
</article>
</section> </section>
<el-dialog v-model="fragmentsDialogVisible" title="生成学习残片" width="520px"> <el-dialog v-model="fragmentsDialogVisible" title="生成学习残片" width="520px">
@@ -325,6 +419,7 @@ onUnmounted(() => {
<el-button type="success" @click="endTimer(summaryContent)">确认结束</el-button> <el-button type="success" @click="endTimer(summaryContent)">确认结束</el-button>
</template> </template>
</el-dialog> </el-dialog>
</div>
</template> </template>
<style scoped> <style scoped>
@@ -425,6 +520,42 @@ onUnmounted(() => {
margin: 0; margin: 0;
} }
.fragments-card {
padding: 20px;
}
.fragments-card h3 {
margin: 0 0 14px;
color: var(--green-900);
}
.fragment-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid var(--border-soft);
}
.fragment-item:last-child {
border-bottom: none;
}
.fragment-content {
flex: 1;
font-size: 14px;
line-height: 1.6;
color: var(--text-primary);
word-break: break-word;
}
.fragment-edit-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 8px;
}
@media (max-width: 1100px) { @media (max-width: 1100px) {
.session-grid { .session-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;