feat: 学习会话页展示当前会话碎片列表并支持编辑
- 会话加载时获取碎片列表,创建碎片后自动刷新 - 碎片列表支持内联编辑(textarea + 保存/取消) - 修复多根节点问题,用 div 包裹模板内容以兼容 Transition 组件
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user