feat:任务开始界面-任务开始/暂停/结束功能

This commit is contained in:
2025-07-08 09:12:38 +08:00
parent bf2c474058
commit 07bf872149
5 changed files with 229 additions and 10 deletions
+211 -2
View File
@@ -1,11 +1,220 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import {ElButton, ElMessage, ElMessageBox, ElProgress} from 'element-plus';
import { useRoute } from "vue-router";
import request from "@/utils/request";
const route = useRoute();
const taskNum = route.params.taskNum;
const taskInfo = ref({
sessionNum: '',
sessionState: '--',
taskName: '',
taskNum: taskNum,
startTime: '',
endTime: '',
lastStartTime: '',
actualTime: 0,
effectiveTime: 0,
effectivenessRatio: '--',
});
const timerMinutes = ref(25);
const timerSeconds = ref(0);
const timerRunning = ref(false);
let timerInterval: number;
let timer : number;
const currentCycle = computed(() => Math.floor(taskInfo.value.effectiveTime/60 / 25) + 1);
const currentTime = ref(Date.now());
const elapsedTime = computed(() => {
if (!taskInfo.value.startTime) return { hours: 0, minutes: 0, seconds: 0 };
const start = new Date(taskInfo.value.startTime).getTime();
const now = currentTime.value;
const diff = Math.max(Math.floor((now - start) / 1000), 0);
return {
hours: Math.floor(diff / 3600),
minutes: Math.floor((diff % 3600) / 60),
seconds: diff % 60,
};
});
const formatTime = (time: { hours: number; minutes: number; seconds: number }) => {
return `${time.hours}${time.minutes}${time.seconds}`;
};
const progress = computed(() => ((timerMinutes.value * 60 + timerSeconds.value) / (25 * 60)) * 100);
const audio = new Audio('/resource/notification.mp3');
const startTimer = async () => {
if ("PAUSED" === taskInfo.value.sessionState) {
await request.post(`/study-sessions/${taskInfo.value.sessionNum}/study-sessions/continue`, null, {params: null}).then(req => {
if (req.code === 200) {
ElMessage.success('任务开始');
}
});
}
timerRunning.value = true;
timerInterval = window.setInterval(() => {
if (timerRunning.value) {
if (timerSeconds.value === 0) {
if (timerMinutes.value === 0) {
endTimer();
// 弹窗 + 播放铃声
audio.loop = true; // 循环播放,避免弹窗没关闭铃声自动停
audio.play();
ElMessageBox.alert('本次番茄钟结束!', '提醒', {
confirmButtonText: '关闭铃声',
callback: () => {
audio.pause();
audio.currentTime = 0; // 重置音频进度
ElMessage.success('铃声已关闭');
}
});
} else {
timerMinutes.value--;
timerSeconds.value = 59;
}
} else {
timerSeconds.value--;
}
}else {
clearInterval(timerInterval);
}
}, 1000);
};
const stopTimer = async () => {
await request.post(`/study-sessions/${taskInfo.value.sessionNum}/study-sessions/pause`, null, {params:null}).then(req =>{
if (req.code === 200) {
ElMessage.success('任务暂停');
}
});
timerRunning.value = false;
};
const endTimer = async () => {
await request.post(`/study-sessions/${taskInfo.value.sessionNum}/study-sessions/ended`, {content:"测试关闭"}, {params:null}).then(req =>{
if (req.code === 200) {
ElMessage.success('任务结束');
}
});
clearInterval(timerInterval);
timerRunning.value = false;
};
const handleTimerException = async (adjustedEnd: Date) => {
await request.post(`/study-sessions/${taskInfo.value.sessionNum}/study-sessions/pause`, null, {
params: {
endTime: adjustedEnd.toISOString(),
}
});
ElMessage.warning('上次学习任务开始时间过长,未及时结束,有效时间仅增加25分钟');
// 重新加载数据
startOrContinueStudySession()
};
const initClocker = async () => {
const end = new Date(taskInfo.value.endTime).getTime();
const lastStart = new Date(taskInfo.value.lastStartTime).getTime();
const now = new Date().getTime();
let diff = Math.floor((now - lastStart) / 1000);
// 判断任务是否超时(状态为正在进行,且最后一次开始时间到现在超过25分钟)
console.log(new Date().toISOString())
if (taskInfo.value.sessionState === 'ONGOING' && now - lastStart > 25 * 60 * 1000) {
// 超时了,触发暂停
const adjustedEnd = new Date(lastStart + 25 * 60000);
await handleTimerException(adjustedEnd);
// 准备开始下一次倒计时
timerMinutes.value = 25;
timerSeconds.value = 0;
} else if (taskInfo.value.sessionState === 'ONGOING' ) {
// 没超时,恢复进度
timerMinutes.value = 25 - Math.floor(diff / 60);
timerSeconds.value = 60 - diff % 60;
startTimer();
} else {
// 准备开始下一次倒计时
timerMinutes.value = 25;
timerSeconds.value = 0;
}
};
const startOrContinueStudySession = () => {
request.get(`/tasks/${taskNum}/study-sessions/start-or-continue`, {})
.then(res => {
Object.assign(taskInfo.value, res.data);
initClocker();
})
.catch(err => {
ElMessage.error(err.message);
});
};
onMounted(() => {
startOrContinueStudySession();
timer = setInterval(() => {
currentTime.value = Date.now();
}, 1000);
});
onUnmounted(() => {
clearInterval(timerInterval)
clearInterval(timer);
});
</script> </script>
<template> <template>
任务开始页面 <div class="task-container">
<div class="task-info">
<p>当前任务{{ taskInfo.taskName }}</p>
<p>当前循环{{ currentCycle }}</p>
<p>有效时间{{ taskInfo.effectiveTime / 60 }}分钟</p>
<p>本次运行时间{{ formatTime(elapsedTime) }}</p>
</div>
<div class="timer-section">
<el-progress type="circle" :percentage="progress" :width="200">
<div>{{ timerMinutes.toString().padStart(2, '0') }} : {{ timerSeconds.toString().padStart(2, '0') }}</div>
</el-progress>
</div>
<div class="actions">
<el-button @click="startTimer" :disabled="timerRunning">开始</el-button>
<el-button @click="stopTimer" :disabled = "!timerRunning">暂停</el-button>
<el-button @click="endTimer" >结束</el-button>
</div>
</div>
</template> </template>
<style scoped> <style scoped>
.task-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 20px;
}
</style> .task-info {
font-size: 18px;
margin-bottom: 30px;
}
.timer-section {
margin: 20px;
}
.actions {
display: flex;
gap: 10px;
}
</style>
+2 -2
View File
@@ -15,7 +15,7 @@ const fetchTasks = () => {
tasks.splice(0, tasks.length, ...res.data.records.map((record: any) => ({ tasks.splice(0, tasks.length, ...res.data.records.map((record: any) => ({
title: record.taskName, title: record.taskName,
description: record.taskDescription || '', description: record.taskDescription || '',
progress: record.lastLearningStatus || '未开始', lastLearningStatus: record.lastLearningStatus || '未开始',
priority: Math.round(record.taskPriority), priority: Math.round(record.taskPriority),
taskNum: record.taskNum, taskNum: record.taskNum,
taskId: record.id, taskId: record.id,
@@ -25,7 +25,7 @@ const fetchTasks = () => {
}; };
const startTask = () => { const startTask = () => {
router.push("/start-task") router.push({name:'start-task',params:{taskNum:selectedTask.value.taskNum}});
console.log('任务已开始:', selectedTask.value); console.log('任务已开始:', selectedTask.value);
}; };
Binary file not shown.
+2 -1
View File
@@ -30,7 +30,8 @@ const router = createRouter({
component: Review component: Review
}, },
{ {
path:'/start-task', path:'/start-task/:taskNum',
name:'start-task',
component: StartTask component: StartTask
}, },
{ {
+14 -5
View File
@@ -34,11 +34,20 @@ const get = (url: string, params: {}) => {
.catch(handleError); .catch(handleError);
}; };
const post = (url: string, data: any) => { const post = (url: string, data: any = null, config: any = {}) => {
console.log("开始POST请求", basic_url + url, "参数:", data); console.log("开始POST请求", url, "参数:", data, "配置:", config);
return axiosInstance.post(url, data)
.then(validateResponse) if (config.params) {
.catch(handleError); // 如果传入 config.params,则作为 URL 参数
return axiosInstance.post(url, data, { params: config.params })
.then(validateResponse)
.catch(handleError);
} else {
// 默认 POST JSON
return axiosInstance.post(url, data)
.then(validateResponse)
.catch(handleError);
}
}; };
const put = (url: string, data: any) => { const put = (url: string, data: any) => {