feat(practice): 添加长按操作功能

This commit is contained in:
cc 2026-04-15 20:58:41 +08:00
parent d3453f1a34
commit 3a00bd64db
1 changed files with 157 additions and 4 deletions

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, computed, onUnmounted } from "vue";
import { ref, computed, onUnmounted, reactive } from "vue";
import { useRouter } from "vue-router";
import {
DOUBAO_APP_ID,
@ -90,6 +90,87 @@ const completedWords = ref(0);
let audioInstance = null;
let audioCache = new Map(); //
//
const longPressState = reactive({
pause: false, //
replay: false, //
});
//
let longPressTimer = null;
//
const handleLongPressStart = (buttonType) => {
//
if (longPressTimer) {
clearTimeout(longPressTimer);
}
longPressState[buttonType] = true;
// 800ms
longPressTimer = setTimeout(() => {
if (buttonType === "pause" && dictationState.value === "playing") {
// =
stopDictation();
} else if (buttonType === "replay") {
// =
showCurrentWord();
}
longPressState[buttonType] = false;
}, 800);
};
//
const handleLongPressEnd = (buttonType) => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
longPressState[buttonType] = false;
};
//
const showCurrentWord = () => {
if (!currentWord.value) return;
//
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 progress = computed(() => {
if (wordList.value.length === 0) return 0;
@ -366,6 +447,11 @@ onUnmounted(() => {
// URL
audioCache.forEach((url) => URL.revokeObjectURL(url));
audioCache.clear();
//
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
</script>
@ -506,7 +592,16 @@ onUnmounted(() => {
<!-- 播放中 -->
<template v-else-if="dictationState === 'playing'">
<button class="control-btn pause-btn" @click="pauseDictation">
<button
class="control-btn pause-btn"
:class="{ pressing: longPressState.pause }"
@click="pauseDictation"
@mousedown="handleLongPressStart('pause')"
@mouseup="handleLongPressEnd('pause')"
@mouseleave="handleLongPressEnd('pause')"
@touchstart.prevent="handleLongPressStart('pause')"
@touchend="handleLongPressEnd('pause')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -521,7 +616,16 @@ onUnmounted(() => {
/>
</svg>
</button>
<button class="control-btn replay-btn" @click="replayCurrentWord">
<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')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -570,7 +674,16 @@ onUnmounted(() => {
/>
</svg>
</button>
<button class="control-btn replay-btn" @click="replayCurrentWord">
<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')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -1319,6 +1432,46 @@ onUnmounted(() => {
transform: scale(1.05);
}
/* 长按状态 */
.control-btn.pressing {
transform: scale(0.95);
opacity: 0.8;
}
.pause-btn.pressing {
background: linear-gradient(135deg, #dc2626, #b91c1c);
box-shadow: 0 2px 10px rgba(239, 68, 68, 0.4);
}
.replay-btn.pressing {
background: linear-gradient(135deg, var(--accent-5), #d97706);
color: white;
box-shadow: 0 4px 15px rgba(245, 158, 11, 0.3);
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8);
}
}
@media (max-width: 900px) {
.content-grid {
grid-template-columns: 1fr;