|
|
@@ -6,15 +6,24 @@
|
|
|
<template>
|
|
|
|
|
|
<div class="my-work" >
|
|
|
-
|
|
|
<div class="my-work-slider">
|
|
|
- <el-button class="back" color="#4A90C4" style="color: #FFFFFF;" @click="handleBack">返回</el-button>
|
|
|
+ <el-button class="back" color="#4A90C4" style="color: #FFFFFF;" @click="handleBack" :disabled="isExamMode">返回</el-button>
|
|
|
<AIChatter :dialogueId="dataForm.dialogueId" :messages="dataForm.messages" :is-teacher="false" />
|
|
|
<Dictionaries />
|
|
|
</div>
|
|
|
+ <div
|
|
|
+ v-if="isExamMode"
|
|
|
+ class="exam-countdown-banner"
|
|
|
+ :style="{ top: bannerTop + 'px', right: bannerRight + 'px' }"
|
|
|
+ @mousedown="startDrag"
|
|
|
+ >
|
|
|
+ <el-tag type="warning">考试模式</el-tag>
|
|
|
+ <span class="countdown-time">{{ formattedTime }}</span>
|
|
|
+ </div>
|
|
|
<div class="my-work-input">
|
|
|
<Edit
|
|
|
:engagement="dataForm.engagement"
|
|
|
+ :is-exam-mode="isExamMode"
|
|
|
/>
|
|
|
</div>
|
|
|
<Slider :dialogueId="dataForm.dialogueId" :engagement="dataForm.engagement"/>
|
|
|
@@ -27,19 +36,20 @@
|
|
|
import useApis from "@/apis";
|
|
|
import {useStore} from "@/store";
|
|
|
import router from "@/router"
|
|
|
- import {onMounted, onUnmounted, reactive} from "vue";
|
|
|
- import type {engagementVO} from "@/types/vo";
|
|
|
+ import {onMounted, onUnmounted, reactive, computed, ref, watch} from "vue";
|
|
|
+ import type {engagementVO, IAssignment, ExamTimeVO} from "@/types/vo";
|
|
|
import type {IDialogue} from "@/types/dialogue.ts";
|
|
|
import Edit from "@/views/Home/component/Edit/index.vue";
|
|
|
import Slider from '@/views/Home/component/Silder/index.vue'
|
|
|
import "./index.scss"
|
|
|
import {useRoute} from 'vue-router'
|
|
|
- import {ElMessageBox} from "element-plus";
|
|
|
+ import {ElMessageBox, ElMessage} from "element-plus";
|
|
|
import IndexedDB from "@/utils/indexedDBUtil";
|
|
|
import {isModified, registerAllEventListeners, removeAllEventListeners} from "@/views/EditPage/userWritingRecord";
|
|
|
import emitter from "@/utils/emitter";
|
|
|
+ import {useExamCountdown} from "@/hooks/useExamCountdown";
|
|
|
+ import dayjs from "dayjs";
|
|
|
|
|
|
- // 获得路由中的assignmentId
|
|
|
const route = useRoute()
|
|
|
const assignmentId = Number(route.params.assignmentId)
|
|
|
|
|
|
@@ -50,13 +60,151 @@
|
|
|
const dataForm = reactive<{
|
|
|
messages: IDialogue[],
|
|
|
dialogueId: string;
|
|
|
- engagement: engagementVO
|
|
|
+ engagement: engagementVO;
|
|
|
+ assignment: IAssignment;
|
|
|
}>({
|
|
|
messages: [],
|
|
|
dialogueId: '',
|
|
|
- engagement: {} as engagementVO
|
|
|
+ engagement: {} as engagementVO,
|
|
|
+ assignment: {} as IAssignment
|
|
|
+ })
|
|
|
+
|
|
|
+ const examTimeInfo = ref<ExamTimeVO | null>(null)
|
|
|
+ const isExamMode = computed(() => dataForm.assignment?.examMode ?? false)
|
|
|
+
|
|
|
+ let getHTML = ref("")
|
|
|
+ let getText = ref("")
|
|
|
+ let getQuillContent = reactive({})
|
|
|
+
|
|
|
+ const bannerTop = ref(50)
|
|
|
+ const bannerRight = ref(20)
|
|
|
+ let isDragging = false
|
|
|
+ let startX = 0
|
|
|
+ let startY = 0
|
|
|
+ let startRight = 0
|
|
|
+ let startTop = 0
|
|
|
+
|
|
|
+ const startDrag = (e: MouseEvent) => {
|
|
|
+ isDragging = true
|
|
|
+ startX = e.clientX
|
|
|
+ startY = e.clientY
|
|
|
+ startRight = bannerRight.value
|
|
|
+ startTop = bannerTop.value
|
|
|
+ document.addEventListener('mousemove', onDrag)
|
|
|
+ document.addEventListener('mouseup', stopDrag)
|
|
|
+ }
|
|
|
+
|
|
|
+ const onDrag = (e: MouseEvent) => {
|
|
|
+ if (!isDragging) return
|
|
|
+ const deltaX = startX - e.clientX
|
|
|
+ const deltaY = e.clientY - startY
|
|
|
+ bannerRight.value = startRight + deltaX
|
|
|
+ bannerTop.value = startTop + deltaY
|
|
|
+ }
|
|
|
+
|
|
|
+ const stopDrag = () => {
|
|
|
+ isDragging = false
|
|
|
+ document.removeEventListener('mousemove', onDrag)
|
|
|
+ document.removeEventListener('mouseup', stopDrag)
|
|
|
+ }
|
|
|
+
|
|
|
+ emitter.on('get-html', (value: string) => {
|
|
|
+ getHTML.value = value
|
|
|
})
|
|
|
|
|
|
+ emitter.on('get-text', (value: string) => {
|
|
|
+ getText.value = value
|
|
|
+ })
|
|
|
+
|
|
|
+ emitter.on('get-quill-content', (value: Object) => {
|
|
|
+ Object.assign(getQuillContent, value)
|
|
|
+ })
|
|
|
+
|
|
|
+ const handleStage = async () => {
|
|
|
+ try {
|
|
|
+ const _res = await apis.engagementStage(store.user.id, assignmentId, JSON.stringify(getQuillContent), getText.value, getHTML.value);
|
|
|
+ emitter.emit('click-stage');
|
|
|
+ emitter.emit('stage-success');
|
|
|
+ ElMessage.success(_res.data.data);
|
|
|
+ return _res;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('暂存失败', error);
|
|
|
+ ElMessage.error('暂存失败');
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmit = async () => {
|
|
|
+ const endTime = dayjs(dataForm.assignment.endTime);
|
|
|
+ const currentTime = dayjs();
|
|
|
+ let supplementarySubmission = false
|
|
|
+ if (currentTime.isAfter(endTime)) {
|
|
|
+ supplementarySubmission = true
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await apis.engagementSubmit(store.user.id, assignmentId, supplementarySubmission)
|
|
|
+ ElMessage.success('提交成功');
|
|
|
+ router.push("/home/myHomework").then(() => {
|
|
|
+ window.location.reload()
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交失败', error);
|
|
|
+ ElMessage.error('提交失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let countdownController: ReturnType<typeof useExamCountdown> | null = null
|
|
|
+
|
|
|
+ const initCountdown = () => {
|
|
|
+ if (!examTimeInfo.value || examTimeInfo.value.remainingSeconds <= 0) return
|
|
|
+
|
|
|
+ countdownController = useExamCountdown({
|
|
|
+ startTime: new Date(),
|
|
|
+ duration: 0,
|
|
|
+ initialRemainingSeconds: examTimeInfo.value.remainingSeconds,
|
|
|
+ onAutoSubmit: async () => {
|
|
|
+ ElMessage.warning('考试时间已到,正在自动提交...');
|
|
|
+ emitter.emit('exam-mode-change', false);
|
|
|
+ try {
|
|
|
+ await handleStage();
|
|
|
+ await handleSubmit();
|
|
|
+ } catch {
|
|
|
+ ElMessage.error('自动交卷失败,请手动提交');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onWarning: () => {
|
|
|
+ ElMessage.success('距离考试结束还有5分钟,请注意暂存您的内容!');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ triggerUpdate.value++;
|
|
|
+ }
|
|
|
+
|
|
|
+ const triggerUpdate = ref(0);
|
|
|
+
|
|
|
+ watch(examTimeInfo, (newVal) => {
|
|
|
+ if (newVal && isExamMode.value && !countdownController) {
|
|
|
+ initCountdown();
|
|
|
+ }
|
|
|
+ }, { immediate: true });
|
|
|
+
|
|
|
+ const isWarning = computed(() => {
|
|
|
+ triggerUpdate.value;
|
|
|
+ return countdownController?.isWarning.value ?? false;
|
|
|
+ });
|
|
|
+ const formattedTime = computed(() => {
|
|
|
+ triggerUpdate.value;
|
|
|
+ return countdownController?.formattedTime.value ?? '00:00:00';
|
|
|
+ });
|
|
|
+
|
|
|
+ const startCountdown = () => {
|
|
|
+ countdownController?.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ const stopCountdown = () => {
|
|
|
+ countdownController?.stop();
|
|
|
+ }
|
|
|
+
|
|
|
const handleBack = () => {
|
|
|
if(isModified.value){
|
|
|
ElMessageBox.confirm(
|
|
|
@@ -84,7 +232,24 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 获取数据
|
|
|
+ async function fetchAssignmentData() {
|
|
|
+ return apis.getAssignmentById(assignmentId)
|
|
|
+ .then((res: any) => {
|
|
|
+ dataForm.assignment = res.data.data;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ async function fetchExamTime() {
|
|
|
+ return apis.getExamTime(store.user.id, assignmentId)
|
|
|
+ .then((res: any) => {
|
|
|
+ examTimeInfo.value = res.data.data;
|
|
|
+ dataForm.engagement.submitted = examTimeInfo.value?.submitted ?? false;
|
|
|
+ })
|
|
|
+ .catch((err: any) => {
|
|
|
+ console.log('获取考试时间失败', err);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
async function fetchData(){
|
|
|
await apis.getAIDialogues(assignmentId, store.user.id)
|
|
|
.then((_res:any) => {
|
|
|
@@ -105,15 +270,11 @@
|
|
|
messages: dataForm.messages,
|
|
|
engagement: _res.data.data
|
|
|
})
|
|
|
- // if(!dataForm.engagement){
|
|
|
- // fetchData()
|
|
|
- // }
|
|
|
}).catch((_err:any) => {
|
|
|
console.log("错误", _err)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- // 保存AI对话内容
|
|
|
const saveAIDialogue = async () => {
|
|
|
if (dataForm.dialogueId && dataForm.messages.length > 0) {
|
|
|
try {
|
|
|
@@ -125,24 +286,42 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 监听提交前的保存对话事件
|
|
|
emitter.on('save-ai-dialogue-before-submit', async () => {
|
|
|
await saveAIDialogue()
|
|
|
})
|
|
|
|
|
|
- onMounted( () => {
|
|
|
- fetchData()
|
|
|
- // indexedDB.initDB("eventId")
|
|
|
+ emitter.on('exam-submitted', () => {
|
|
|
+ dataForm.engagement.submitted = true;
|
|
|
+ examTimeInfo.value = examTimeInfo.value ? { ...examTimeInfo.value, submitted: true } : null;
|
|
|
+ })
|
|
|
+
|
|
|
+ onMounted(async () => {
|
|
|
+ await fetchAssignmentData();
|
|
|
+ await Promise.all([fetchData(), fetchExamTime()]);
|
|
|
+
|
|
|
+ if (isExamMode.value && dataForm.engagement.submitted) {
|
|
|
+ ElMessage.warning('您已完成交卷,无法再次编辑');
|
|
|
+ router.push(`/home/homeworkDetail/${assignmentId}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isExamMode.value) {
|
|
|
+ emitter.emit('exam-mode-change', true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isExamMode.value && examTimeInfo.value && examTimeInfo.value.remainingSeconds > 0) {
|
|
|
+ initCountdown();
|
|
|
+ startCountdown();
|
|
|
+ }
|
|
|
+
|
|
|
registerAllEventListeners(indexedDB, store.user.id, assignmentId);
|
|
|
})
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
- // indexedDB.clearDB()
|
|
|
- // indexedDB.closeDB()
|
|
|
- // indexedDB.deleteDBAll()
|
|
|
+ stopCountdown();
|
|
|
removeAllEventListeners();
|
|
|
- // 清理事件监听器
|
|
|
emitter.off('save-ai-dialogue-before-submit');
|
|
|
+ emitter.emit('exam-mode-change', false);
|
|
|
})
|
|
|
|
|
|
</script>
|