From 54c0bfef686f27b4ff3f0503f9b1063bee745dd4 Mon Sep 17 00:00:00 2001 From: cc <94575594@qq.com> Date: Wed, 15 Apr 2026 21:17:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(views):=20=E6=9B=BF=E6=8D=A2=E9=95=BF?= =?UTF-8?q?=E6=8C=89=E6=98=BE=E7=A4=BA=E4=B8=BA=E5=BD=95=E9=9F=B3=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/views/SpellPractice.vue | 177 +++++++++++++++++------------------- 1 file changed, 83 insertions(+), 94 deletions(-) diff --git a/src/views/SpellPractice.vue b/src/views/SpellPractice.vue index cd58626..0e686a1 100644 --- a/src/views/SpellPractice.vue +++ b/src/views/SpellPractice.vue @@ -92,88 +92,61 @@ let audioCache = new Map(); // 缓存已生成的音频 // 长按状态 const longPressState = reactive({ - replay: false, // 跟读按钮是否正在长按 + repeat: false, // 跟读按钮是否正在按住 }); -// 长按定时器 -let longPressTimer = null; -// 标记长按是否已经触发(避免重复执行) -let longPressTriggered = false; +// 录音相关 +const isRecording = ref(false); +let mediaRecorder = null; +let recordedChunks = []; -// 长按开始 -const handleLongPressStart = (buttonType) => { - // 清除之前的定时器 - if (longPressTimer) { - clearTimeout(longPressTimer); +// 开始录音 +const startRecording = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + mediaRecorder = new MediaRecorder(stream); + recordedChunks = []; + + mediaRecorder.ondataavailable = (event) => { + if (event.data.size > 0) { + recordedChunks.push(event.data); + } + }; + + mediaRecorder.onstop = () => { + const blob = new Blob(recordedChunks, { type: 'audio/webm' }); + // 可以在这里处理录音结果,比如播放或上传 + console.log('录音完成,大小:', blob.size); + // 停止所有音频轨道 + stream.getTracks().forEach(track => track.stop()); + }; + + mediaRecorder.start(); + isRecording.value = true; + } catch (err) { + console.error('无法访问麦克风:', err); + alert('无法访问麦克风,请检查权限设置'); } - - longPressTriggered = false; - longPressState[buttonType] = true; - - // 设置长按触发时间(800ms) - longPressTimer = setTimeout(() => { - longPressTriggered = true; - if (buttonType === "replay") { - // 长按跟读按钮 = 显示当前单词 - showCurrentWord(); - } - // 长按触发后重置状态 - longPressState[buttonType] = false; - }, 800); }; -// 长按结束 -const handleLongPressEnd = (buttonType) => { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; +// 停止录音 +const stopRecording = () => { + if (mediaRecorder && isRecording.value) { + mediaRecorder.stop(); + isRecording.value = false; } - // 只有当长按未触发时才重置状态(已触发时已在定时器中重置) - if (!longPressTriggered) { - longPressState[buttonType] = false; - } - longPressTriggered = false; }; -// 显示当前单词(用于听写时查看答案) -const showCurrentWord = () => { - if (!currentWord.value) return; +// 按住开始(按下即触发) +const handlePressStart = () => { + longPressState.repeat = true; + startRecording(); +}; - // 如果正在播放,先暂停 - const wasPlaying = dictationState.value === "playing"; - if (wasPlaying) { - pauseDictation(); - } - - // 创建临时显示元素 - const wordDisplay = document.createElement("div"); - wordDisplay.className = "word-tooltip"; - wordDisplay.textContent = currentWord.value; - wordDisplay.style.cssText = ` - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - padding: 2rem 3rem; - background: linear-gradient(135deg, var(--accent-5), #d97706); - color: white; - font-size: 3rem; - font-weight: 700; - border-radius: 20px; - box-shadow: 0 10px 40px rgba(245, 158, 11, 0.5); - z-index: 1000; - animation: fadeIn 0.3s ease; - `; - - document.body.appendChild(wordDisplay); - - // 3秒后自动消失 - setTimeout(() => { - wordDisplay.style.animation = "fadeOut 0.3s ease"; - setTimeout(() => { - document.body.removeChild(wordDisplay); - }, 300); - }, 2000); +// 按住结束(松开即触发) +const handlePressEnd = () => { + longPressState.repeat = false; + stopRecording(); }; // 进度计算 @@ -452,10 +425,9 @@ onUnmounted(() => { // 清理缓存的音频 URL audioCache.forEach((url) => URL.revokeObjectURL(url)); audioCache.clear(); - // 清理长按定时器 - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; + // 停止录音 + if (isRecording.value) { + stopRecording(); } }); @@ -617,14 +589,13 @@ onUnmounted(() => { @@ -676,14 +647,13 @@ onUnmounted(() => { @@ -1445,6 +1415,14 @@ onUnmounted(() => { box-shadow: 0 4px 15px rgba(245, 158, 11, 0.3); } +/* 录音中状态 */ +.replay-btn.recording { + background: linear-gradient(135deg, #ef4444, #dc2626); + color: white; + box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4); + animation: pulse 1s ease-in-out infinite; +} + /* 动画效果 */ @keyframes fadeIn { from { @@ -1468,6 +1446,17 @@ onUnmounted(() => { } } +@keyframes pulse { + 0%, 100% { + transform: scale(1); + box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4); + } + 50% { + transform: scale(1.05); + box-shadow: 0 6px 25px rgba(239, 68, 68, 0.6); + } +} + @media (max-width: 900px) { .content-grid { grid-template-columns: 1fr;