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;