feat(views): 替换长按显示为录音功能
This commit is contained in:
parent
0aca637b06
commit
54c0bfef68
|
|
@ -92,88 +92,61 @@ let audioCache = new Map(); // 缓存已生成的音频
|
||||||
|
|
||||||
// 长按状态
|
// 长按状态
|
||||||
const longPressState = reactive({
|
const longPressState = reactive({
|
||||||
replay: false, // 跟读按钮是否正在长按
|
repeat: false, // 跟读按钮是否正在按住
|
||||||
});
|
});
|
||||||
|
|
||||||
// 长按定时器
|
// 录音相关
|
||||||
let longPressTimer = null;
|
const isRecording = ref(false);
|
||||||
// 标记长按是否已经触发(避免重复执行)
|
let mediaRecorder = null;
|
||||||
let longPressTriggered = false;
|
let recordedChunks = [];
|
||||||
|
|
||||||
// 长按开始
|
// 开始录音
|
||||||
const handleLongPressStart = (buttonType) => {
|
const startRecording = async () => {
|
||||||
// 清除之前的定时器
|
try {
|
||||||
if (longPressTimer) {
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
clearTimeout(longPressTimer);
|
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) => {
|
const stopRecording = () => {
|
||||||
if (longPressTimer) {
|
if (mediaRecorder && isRecording.value) {
|
||||||
clearTimeout(longPressTimer);
|
mediaRecorder.stop();
|
||||||
longPressTimer = null;
|
isRecording.value = false;
|
||||||
}
|
}
|
||||||
// 只有当长按未触发时才重置状态(已触发时已在定时器中重置)
|
|
||||||
if (!longPressTriggered) {
|
|
||||||
longPressState[buttonType] = false;
|
|
||||||
}
|
|
||||||
longPressTriggered = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 显示当前单词(用于听写时查看答案)
|
// 按住开始(按下即触发)
|
||||||
const showCurrentWord = () => {
|
const handlePressStart = () => {
|
||||||
if (!currentWord.value) return;
|
longPressState.repeat = true;
|
||||||
|
startRecording();
|
||||||
|
};
|
||||||
|
|
||||||
// 如果正在播放,先暂停
|
// 按住结束(松开即触发)
|
||||||
const wasPlaying = dictationState.value === "playing";
|
const handlePressEnd = () => {
|
||||||
if (wasPlaying) {
|
longPressState.repeat = false;
|
||||||
pauseDictation();
|
stopRecording();
|
||||||
}
|
|
||||||
|
|
||||||
// 创建临时显示元素
|
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 进度计算
|
// 进度计算
|
||||||
|
|
@ -452,10 +425,9 @@ onUnmounted(() => {
|
||||||
// 清理缓存的音频 URL
|
// 清理缓存的音频 URL
|
||||||
audioCache.forEach((url) => URL.revokeObjectURL(url));
|
audioCache.forEach((url) => URL.revokeObjectURL(url));
|
||||||
audioCache.clear();
|
audioCache.clear();
|
||||||
// 清理长按定时器
|
// 停止录音
|
||||||
if (longPressTimer) {
|
if (isRecording.value) {
|
||||||
clearTimeout(longPressTimer);
|
stopRecording();
|
||||||
longPressTimer = null;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -617,14 +589,13 @@ onUnmounted(() => {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="control-btn replay-btn"
|
class="control-btn replay-btn"
|
||||||
:class="{ pressing: longPressState.replay }"
|
:class="{ pressing: longPressState.repeat, recording: isRecording }"
|
||||||
@click="replayCurrentWord"
|
@mousedown="handlePressStart"
|
||||||
@mousedown="handleLongPressStart('replay')"
|
@mouseup="handlePressEnd"
|
||||||
@mouseup="handleLongPressEnd('replay')"
|
@mouseleave="handlePressEnd"
|
||||||
@mouseleave="handleLongPressEnd('replay')"
|
@touchstart.prevent="handlePressStart"
|
||||||
@touchstart.prevent="handleLongPressStart('replay')"
|
@touchend="handlePressEnd"
|
||||||
@touchend="handleLongPressEnd('replay')"
|
@touchcancel="handlePressEnd"
|
||||||
@touchcancel="handleLongPressEnd('replay')"
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -636,7 +607,7 @@ onUnmounted(() => {
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -676,14 +647,13 @@ onUnmounted(() => {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="control-btn replay-btn"
|
class="control-btn replay-btn"
|
||||||
:class="{ pressing: longPressState.replay }"
|
:class="{ pressing: longPressState.repeat, recording: isRecording }"
|
||||||
@click="replayCurrentWord"
|
@mousedown="handlePressStart"
|
||||||
@mousedown="handleLongPressStart('replay')"
|
@mouseup="handlePressEnd"
|
||||||
@mouseup="handleLongPressEnd('replay')"
|
@mouseleave="handlePressEnd"
|
||||||
@mouseleave="handleLongPressEnd('replay')"
|
@touchstart.prevent="handlePressStart"
|
||||||
@touchstart.prevent="handleLongPressStart('replay')"
|
@touchend="handlePressEnd"
|
||||||
@touchend="handleLongPressEnd('replay')"
|
@touchcancel="handlePressEnd"
|
||||||
@touchcancel="handleLongPressEnd('replay')"
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -695,7 +665,7 @@ onUnmounted(() => {
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1445,6 +1415,14 @@ onUnmounted(() => {
|
||||||
box-shadow: 0 4px 15px rgba(245, 158, 11, 0.3);
|
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 {
|
@keyframes fadeIn {
|
||||||
from {
|
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) {
|
@media (max-width: 900px) {
|
||||||
.content-grid {
|
.content-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue