feat(views): 替换长按显示为录音功能

This commit is contained in:
cc 2026-04-15 21:17:30 +08:00
parent 0aca637b06
commit 54c0bfef68
1 changed files with 83 additions and 94 deletions

View File

@ -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;