From 4faf2db730b7601525acf934347e34d5e2a8e20a Mon Sep 17 00:00:00 2001 From: cat_shark <1716967236@qq.com> Date: Sat, 26 Jul 2025 12:53:33 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A1.=20=E8=B0=83=E6=95=B4=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=EF=BC=8C=E5=B0=86API=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=AE=9A=E4=B9=89=E5=8D=95=E7=8B=AC=E6=8A=BD=E7=A6=BB?= =?UTF-8?q?=202.=20=E5=B0=86=E5=80=92=E8=AE=A1=E6=97=B6=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E8=B0=83=E6=95=B4=E5=88=B0=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E5=A4=84=E7=90=86=E8=AE=A1=E7=AE=97=203.=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=93=83=E5=A3=B0=E6=8F=90=E7=A4=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {src => public}/resource/notification.mp3 | Bin src/api/studySessions.ts | 19 ++ src/components/StartTask.vue | 212 +++++++++------------- src/components/Study.vue | 2 +- src/components/composables/useTimer.ts | 99 ++++++++++ src/utils/request.ts | 9 +- 6 files changed, 206 insertions(+), 135 deletions(-) rename {src => public}/resource/notification.mp3 (100%) create mode 100644 src/api/studySessions.ts create mode 100644 src/components/composables/useTimer.ts diff --git a/src/resource/notification.mp3 b/public/resource/notification.mp3 similarity index 100% rename from src/resource/notification.mp3 rename to public/resource/notification.mp3 diff --git a/src/api/studySessions.ts b/src/api/studySessions.ts new file mode 100644 index 0000000..ff62727 --- /dev/null +++ b/src/api/studySessions.ts @@ -0,0 +1,19 @@ +// src/api/studySession.ts +import request from "@/utils/request"; + +export const continueSession = (sessionNum: string) => { + return request.post(`/study-sessions/${sessionNum}/study-sessions/continue`); +}; + +export const pauseSession = (sessionNum: string, endTime?: Date) => { + const params = endTime ? { endTime: endTime.toISOString() } : null; + return request.post(`/study-sessions/${sessionNum}/study-sessions/pause`, null, { params }); +}; + +export const endSession = (sessionNum: string, content: string = "任务结束") => { + return request.post(`/study-sessions/${sessionNum}/study-sessions/ended`, { content }); +}; + +export const startOrContinueStudySession = (taskNum: string) => { + return request.get(`/tasks/${taskNum}/study-sessions/start-or-continue`); +}; diff --git a/src/components/StartTask.vue b/src/components/StartTask.vue index d4c1cd4..0a3d2e8 100644 --- a/src/components/StartTask.vue +++ b/src/components/StartTask.vue @@ -1,38 +1,50 @@ + @@ -180,17 +131,19 @@ onUnmounted(() => {

有效时间:{{ taskInfo.effectiveTime / 60 }}分钟

本次运行时间:{{ formatTime(elapsedTime) }}

-
-
{{ timerMinutes.toString().padStart(2, '0') }} : {{ timerSeconds.toString().padStart(2, '0') }}
+
+ {{ timerMinutes.toString().padStart(2, '0') }} : + {{ timerSeconds.toString().padStart(2, '0') }} +
-
开始 - 暂停 - 结束 + 暂停 + 休息 + 结束
@@ -203,16 +156,13 @@ onUnmounted(() => { justify-content: center; margin: 20px; } - .task-info { font-size: 18px; margin-bottom: 30px; } - .timer-section { margin: 20px; } - .actions { display: flex; gap: 10px; diff --git a/src/components/Study.vue b/src/components/Study.vue index ab1ff95..a43326b 100644 --- a/src/components/Study.vue +++ b/src/components/Study.vue @@ -84,7 +84,7 @@ onMounted(() => {

任务描述

- +

任务优先级

diff --git a/src/components/composables/useTimer.ts b/src/components/composables/useTimer.ts new file mode 100644 index 0000000..36460f2 --- /dev/null +++ b/src/components/composables/useTimer.ts @@ -0,0 +1,99 @@ +// src/composables/useTimer.ts +import { ref } from 'vue'; +import { ElMessageBox, ElMessage } from 'element-plus'; + +export function useTimer(audioUrl: string = '/public/resource/notification.mp3') { + const timerMinutes = ref(25); + const timerSeconds = ref(0); + const remainingTime = ref(25 * 60 * 1000); + const timerRunning = ref(false); + const timerIsOver = ref(false); + const audioActivated = ref(false); + + let timerInterval: number; + const audio = new Audio(audioUrl); + + const clear = () => { + clearInterval(timerInterval); + timerRunning.value = false; + }; + + const handleCountdownComplete = () => { + audio.loop = true; + audio.play(); + ElMessageBox.alert('本次番茄钟结束!请点击“休息”按钮开始休息倒计时', '提醒', { + confirmButtonText: '关闭铃声', + callback: () => { + audio.pause(); + audio.currentTime = 0; + ElMessage.success('铃声已关闭'); + }, + }); + }; + + const runCountdown = (durationMs: number, onComplete: () => void = handleCountdownComplete) => { + clearInterval(timerInterval); + const endTimeStamp = Date.now() + durationMs; + remainingTime.value = durationMs; + timerIsOver.value = false; + timerRunning.value = true; + + timerInterval = window.setInterval(() => { + const now = Date.now(); + remainingTime.value = Math.max(endTimeStamp - now, 0); + timerMinutes.value = Math.floor(remainingTime.value / 1000 / 60); + timerSeconds.value = Math.floor((remainingTime.value / 1000) % 60); + + if (remainingTime.value === 0 && !timerIsOver.value) { + timerIsOver.value = true; + timerRunning.value = false; + onComplete(); + } + }, 1000); + }; + + const checkAudioPermission = async () => { + try { + await audio.play(); + audio.pause(); + audio.currentTime = 0; + audioActivated.value = true; + return true; + } catch { + audioActivated.value = false; + return false; + } + }; + + const requestAudioPermission = () => { + return new Promise((resolve) => { + ElMessageBox.alert('点击确认激活提示音权限。', '权限激活', { + confirmButtonText: '确认', + callback: async () => { + try { + await audio.play(); + audio.pause(); + audio.currentTime = 0; + audioActivated.value = true; + resolve(true); + } catch { + resolve(false); + } + }, + }); + }); + }; + + return { + timerMinutes, + timerSeconds, + remainingTime, + timerRunning, + timerIsOver, + runCountdown, + clear, + checkAudioPermission, + requestAudioPermission, + audioActivated, + }; +} diff --git a/src/utils/request.ts b/src/utils/request.ts index 211426d..1d56b77 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { ElMessage } from "element-plus"; -const basic_url = "http://localhost:8081"; +const basic_url = "http://localhost:5157"; const axiosInstance = axios.create({ baseURL: basic_url, @@ -27,12 +27,15 @@ const validateResponse = (res: any) => { return result; }; -const get = (url: string, params: {}) => { +function get(url: string, params: Record): Promise; +function get(url: string): Promise; + +function get(url: string, params: Record = {}) { console.log("开始GET请求", basic_url + url, "参数:", params); return axiosInstance.get(url, { params }) .then(validateResponse) .catch(handleError); -}; +} const post = (url: string, data: any = null, config: any = {}) => { console.log("开始POST请求", url, "参数:", data, "配置:", config);