feat(views): 替换长按显示为录音功能
This commit is contained in:
parent
0aca637b06
commit
54c0bfef68
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -617,14 +589,13 @@ onUnmounted(() => {
|
|||
</button>
|
||||
<button
|
||||
class="control-btn replay-btn"
|
||||
:class="{ pressing: longPressState.replay }"
|
||||
@click="replayCurrentWord"
|
||||
@mousedown="handleLongPressStart('replay')"
|
||||
@mouseup="handleLongPressEnd('replay')"
|
||||
@mouseleave="handleLongPressEnd('replay')"
|
||||
@touchstart.prevent="handleLongPressStart('replay')"
|
||||
@touchend="handleLongPressEnd('replay')"
|
||||
@touchcancel="handleLongPressEnd('replay')"
|
||||
:class="{ pressing: longPressState.repeat, recording: isRecording }"
|
||||
@mousedown="handlePressStart"
|
||||
@mouseup="handlePressEnd"
|
||||
@mouseleave="handlePressEnd"
|
||||
@touchstart.prevent="handlePressStart"
|
||||
@touchend="handlePressEnd"
|
||||
@touchcancel="handlePressEnd"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -636,7 +607,7 @@ onUnmounted(() => {
|
|||
<path
|
||||
stroke-linecap="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>
|
||||
</button>
|
||||
|
|
@ -676,14 +647,13 @@ onUnmounted(() => {
|
|||
</button>
|
||||
<button
|
||||
class="control-btn replay-btn"
|
||||
:class="{ pressing: longPressState.replay }"
|
||||
@click="replayCurrentWord"
|
||||
@mousedown="handleLongPressStart('replay')"
|
||||
@mouseup="handleLongPressEnd('replay')"
|
||||
@mouseleave="handleLongPressEnd('replay')"
|
||||
@touchstart.prevent="handleLongPressStart('replay')"
|
||||
@touchend="handleLongPressEnd('replay')"
|
||||
@touchcancel="handleLongPressEnd('replay')"
|
||||
:class="{ pressing: longPressState.repeat, recording: isRecording }"
|
||||
@mousedown="handlePressStart"
|
||||
@mouseup="handlePressEnd"
|
||||
@mouseleave="handlePressEnd"
|
||||
@touchstart.prevent="handlePressStart"
|
||||
@touchend="handlePressEnd"
|
||||
@touchcancel="handlePressEnd"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -695,7 +665,7 @@ onUnmounted(() => {
|
|||
<path
|
||||
stroke-linecap="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>
|
||||
</button>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue