feat(practice): 添加长按操作功能
This commit is contained in:
parent
d3453f1a34
commit
3a00bd64db
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue