feat: 删除战鼓相关代码 + 修复数据加载异常问题
This commit is contained in:
@@ -12,8 +12,6 @@ import {
|
||||
saveDisplayConfig as saveDisplayConfigToConfig,
|
||||
getBattleEndTime,
|
||||
saveBattleEndTime as saveBattleEndTimeToConfig,
|
||||
getDrumConfig,
|
||||
saveDrumConfig as saveDrumConfigToConfig,
|
||||
getBonusRules,
|
||||
saveBonusRules as saveBonusRulesToConfig
|
||||
} from '../services/configService';
|
||||
@@ -29,7 +27,7 @@ export let bonusRules = [
|
||||
export let systemUsers = [];
|
||||
export let displayConfig = null;
|
||||
export let battleEndTime = { date: new Date().toISOString().split('T')[0], time: '00:00:00' };
|
||||
export let drumConfig = {};
|
||||
|
||||
|
||||
// 保存结束时间
|
||||
export const saveBattleEndTime = async (endTime) => {
|
||||
@@ -59,30 +57,7 @@ export const saveDisplayConfig = async (config) => {
|
||||
return await saveDisplayConfigToConfig(config);
|
||||
};
|
||||
|
||||
// 保存战鼓配置
|
||||
export const saveDrumConfig = async (config) => {
|
||||
console.log('保存战鼓配置:', config);
|
||||
|
||||
// 深度合并配置,确保嵌套对象(如sound、animation、pattern)的属性不会丢失
|
||||
drumConfig = {
|
||||
...drumConfig,
|
||||
...config,
|
||||
sound: {
|
||||
...drumConfig.sound,
|
||||
...config.sound
|
||||
},
|
||||
animation: {
|
||||
...drumConfig.animation,
|
||||
...config.animation
|
||||
},
|
||||
pattern: {
|
||||
...drumConfig.pattern,
|
||||
...config.pattern
|
||||
}
|
||||
};
|
||||
|
||||
return await saveDrumConfigToConfig(drumConfig);
|
||||
};
|
||||
|
||||
|
||||
// 保存奖金规则
|
||||
export const saveBonusRules = async (rules) => {
|
||||
@@ -118,7 +93,7 @@ export const refreshData = async () => {
|
||||
systemUsers = await getSystemUsers();
|
||||
displayConfig = await getDisplayConfig();
|
||||
battleEndTime = await getBattleEndTime();
|
||||
drumConfig = await getDrumConfig();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('刷新数据失败:', error);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 配置文件API路径
|
||||
const CONFIG_API_URL = '/api/config';
|
||||
// 修复后(绝对路径,直接请求后端3000端口)
|
||||
const CONFIG_API_URL = 'http://localhost:3000/api/config';
|
||||
|
||||
/**
|
||||
* 读取配置文件
|
||||
@@ -106,21 +107,7 @@ const getDefaultConfig = () => ({
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
time: '00:00:00'
|
||||
},
|
||||
drumConfig: {
|
||||
showDrum: false, // 控制战鼓的显示,默认不显示
|
||||
sound: {
|
||||
volume: 1.0,
|
||||
enabled: false, // 控制声音播放,默认不播放
|
||||
soundSrc: '' // 战鼓声音来源文件路径
|
||||
},
|
||||
animation: {
|
||||
enabled: false
|
||||
},
|
||||
pattern: {
|
||||
strongBeats: [1],
|
||||
totalBeats: 4
|
||||
}
|
||||
},
|
||||
|
||||
backgroundConfig: {
|
||||
useBackgroundImage: true,
|
||||
backgroundImage: '/battle-background.jpg', // 默认战旗背景图片
|
||||
@@ -332,25 +319,7 @@ export const saveBattleEndTime = async (endTime) => {
|
||||
return await writeConfig(config);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取战鼓配置
|
||||
* @returns {Object} 战鼓配置
|
||||
*/
|
||||
export const getDrumConfig = async () => {
|
||||
const config = await readConfig();
|
||||
return config.drumConfig || getDefaultConfig().drumConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存战鼓配置
|
||||
* @param {Object} drumConfig 战鼓配置
|
||||
* @returns {boolean} 是否保存成功
|
||||
*/
|
||||
export const saveDrumConfig = async (drumConfig) => {
|
||||
const config = await readConfig();
|
||||
config.drumConfig = drumConfig;
|
||||
return await writeConfig(config);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取背景配置
|
||||
|
||||
@@ -655,172 +655,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 战鼓配置 -->
|
||||
<div v-if="currentTab === 'drum'" class="drum-config-content">
|
||||
<h2 class="game-subtitle">🥁 战鼓配置管理</h2>
|
||||
|
||||
<!-- 音效配置 -->
|
||||
<div class="config-section">
|
||||
<h3 class="text-gold">🔊 音效配置</h3>
|
||||
<div class="config-options">
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="localDrumConfig.sound.enabled">
|
||||
<span>启用音效</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>音量 (0-1):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.sound.volume" min="0" max="1" step="0.1"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>起音时间 (s):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.sound.attackTime" min="0.001" max="0.5"
|
||||
step="0.01" class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>衰减时间 (s):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.sound.decayTime" min="0.05" max="1" step="0.05"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<h4 style="margin-top: 15px; color: #666;">🎵 第一个音调</h4>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>音调类型:</span>
|
||||
<select v-model="localDrumConfig.sound.type1" class="select-input">
|
||||
<option value="sine">正弦波</option>
|
||||
<option value="square">方波</option>
|
||||
<option value="triangle">三角波</option>
|
||||
<option value="sawtooth">锯齿波</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>频率 (Hz):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.sound.frequency1" min="50" max="500"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<h4 style="margin-top: 15px; color: #666;">🎵 第二个音调</h4>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>音调类型:</span>
|
||||
<select v-model="localDrumConfig.sound.type2" class="select-input">
|
||||
<option value="sine">正弦波</option>
|
||||
<option value="square">方波</option>
|
||||
<option value="triangle">三角波</option>
|
||||
<option value="sawtooth">锯齿波</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>频率 (Hz):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.sound.frequency2" min="50" max="500"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 动画配置 -->
|
||||
<div class="config-section">
|
||||
<h3>🎬 动画配置</h3>
|
||||
<div class="config-options">
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="localDrumConfig.animation.enabled">
|
||||
<span>启用动画</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>节拍间隔 (ms):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.animation.beatInterval" min="50" max="1000"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>跳动缩放比例:</span>
|
||||
<input type="number" v-model.number="localDrumConfig.animation.beatScale" min="1.0" max="2.0"
|
||||
step="0.1" class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>跳动上下位移 (px):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.animation.beatTranslateY" min="-50" max="50"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>跳动旋转角度 (deg):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.animation.beatRotate" min="0" max="20"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>单次跳动持续时间 (ms):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.animation.beatDuration" min="50" max="500"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 节拍模式配置 -->
|
||||
<div class="config-section">
|
||||
<h3>🎵 节拍模式配置</h3>
|
||||
<div class="config-options">
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>每小节总拍数:</span>
|
||||
<input type="number" v-model.number="localDrumConfig.pattern.totalBeats" min="1" max="8"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>强拍位置 (1-4):</span>
|
||||
<input type="text" v-model="localDrumConfig.pattern.strongBeatsStr" placeholder="如: 1,4"
|
||||
class="text-input" @input="updateStrongBeats">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>强拍音量倍数:</span>
|
||||
<input type="number" v-model.number="localDrumConfig.pattern.accentMultiplier" min="1" max="3"
|
||||
step="0.1" class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>强拍频率偏移 (%):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.pattern.accentFrequencyOffset" min="-50" max="50"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label class="checkbox-label">
|
||||
<span>强拍动画增强 (%):</span>
|
||||
<input type="number" v-model.number="localDrumConfig.pattern.accentAnimation" min="0" max="100"
|
||||
class="width-input">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
@@ -954,8 +789,6 @@ import {
|
||||
saveDisplayConfig,
|
||||
battleEndTime,
|
||||
saveBattleEndTime,
|
||||
drumConfig,
|
||||
saveDrumConfig,
|
||||
refreshData,
|
||||
initializeData
|
||||
} from '../data/mockData.js';
|
||||
@@ -983,8 +816,7 @@ const tabs = [
|
||||
{ key: 'bonus', label: '奖金设置' },
|
||||
{ key: 'config', label: '显示配置' },
|
||||
{ key: 'champion', label: '冠军Logo配置' },
|
||||
{ key: 'endTime', label: '结束时间设置' },
|
||||
{ key: 'drum', label: '战鼓配置' }
|
||||
{ key: 'endTime', label: '结束时间设置' }
|
||||
];
|
||||
|
||||
// 冠军Logo配置
|
||||
@@ -1011,18 +843,11 @@ onMounted(async () => {
|
||||
localDisplayConfig.value.crownPosition.top = '-100px';
|
||||
}
|
||||
localBattleEndTime.value = { ...battleEndTime };
|
||||
localDrumConfig.value = { ...drumConfig };
|
||||
|
||||
// 初始化冠军Logo配置
|
||||
if (displayConfig.championLogos) {
|
||||
championLogos.value = { ...displayConfig.championLogos };
|
||||
}
|
||||
|
||||
// 重新处理强拍位置
|
||||
if (localDrumConfig.value.pattern && localDrumConfig.value.pattern.strongBeats) {
|
||||
localDrumConfig.value.pattern.strongBeatsStr =
|
||||
localDrumConfig.value.pattern.strongBeats.join(',') || '1,4';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error);
|
||||
}
|
||||
@@ -1138,13 +963,6 @@ const handleRefreshData = () => {
|
||||
localBonusRules.value = [...bonusRules];
|
||||
localDisplayConfig.value = { ...displayConfig };
|
||||
localBattleEndTime.value = { ...battleEndTime };
|
||||
localDrumConfig.value = { ...drumConfig };
|
||||
|
||||
// 重新处理强拍位置
|
||||
if (localDrumConfig.value.pattern && localDrumConfig.value.pattern.strongBeats) {
|
||||
localDrumConfig.value.pattern.strongBeatsStr =
|
||||
localDrumConfig.value.pattern.strongBeats.join(',') || '1,4';
|
||||
}
|
||||
|
||||
if (success) {
|
||||
alert('数据刷新成功!');
|
||||
@@ -1164,16 +982,6 @@ const localTeamRankings = ref([...teamRankings]);
|
||||
const localBonusRules = ref([...bonusRules]);
|
||||
const localDisplayConfig = ref({ ...displayConfig });
|
||||
const localBattleEndTime = ref({ ...battleEndTime });
|
||||
// 初始化本地战鼓配置副本
|
||||
const localDrumConfig = ref({ ...drumConfig });
|
||||
// 添加强拍位置的字符串表示,用于输入框
|
||||
if (localDrumConfig.value.pattern && localDrumConfig.value.pattern.strongBeats) {
|
||||
localDrumConfig.value.pattern.strongBeatsStr = localDrumConfig.value.pattern.strongBeats.join(',') || '1,4';
|
||||
} else {
|
||||
localDrumConfig.value.pattern = localDrumConfig.value.pattern || {};
|
||||
localDrumConfig.value.pattern.strongBeats = [1, 4];
|
||||
localDrumConfig.value.pattern.strongBeatsStr = '1,4';
|
||||
}
|
||||
|
||||
// 组件挂载时初始化数据
|
||||
onMounted(async () => {
|
||||
@@ -1185,36 +993,12 @@ onMounted(async () => {
|
||||
localBonusRules.value = [...bonusRules];
|
||||
localDisplayConfig.value = { ...displayConfig };
|
||||
localBattleEndTime.value = { ...battleEndTime };
|
||||
localDrumConfig.value = { ...drumConfig };
|
||||
|
||||
// 重新处理强拍位置
|
||||
if (localDrumConfig.value.pattern && localDrumConfig.value.pattern.strongBeats) {
|
||||
localDrumConfig.value.pattern.strongBeatsStr =
|
||||
localDrumConfig.value.pattern.strongBeats.join(',') || '1,4';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新强拍位置数组
|
||||
const updateStrongBeats = () => {
|
||||
try {
|
||||
const beatsStr = localDrumConfig.value.pattern.strongBeatsStr;
|
||||
if (!beatsStr) {
|
||||
localDrumConfig.value.pattern.strongBeats = [];
|
||||
return;
|
||||
}
|
||||
const beats = beatsStr.split(',')
|
||||
.map(beat => parseInt(beat.trim()))
|
||||
.filter(beat => !isNaN(beat) && beat > 0 && beat <= 8);
|
||||
localDrumConfig.value.pattern.strongBeats = beats;
|
||||
} catch (error) {
|
||||
console.error('更新强拍位置失败:', error);
|
||||
localDrumConfig.value.pattern.strongBeats = [1, 4];
|
||||
localDrumConfig.value.pattern.strongBeatsStr = '1,4';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 对话框状态
|
||||
const showAddIndividual = ref(false);
|
||||
@@ -1326,12 +1110,6 @@ const saveData = async () => {
|
||||
localIndividualRankings.value.sort((a, b) => b.score - a.score);
|
||||
localTeamRankings.value.sort((a, b) => b.totalScore - a.totalScore);
|
||||
|
||||
// 保存战鼓配置前,确保强拍位置数组是最新的
|
||||
updateStrongBeats();
|
||||
// 移除临时的字符串表示,避免保存到配置中
|
||||
const configToSave = { ...localDrumConfig.value };
|
||||
delete configToSave.pattern.strongBeatsStr;
|
||||
|
||||
// 导入必要的配置服务函数
|
||||
const { readConfig, writeConfig } = await import('../services/configService');
|
||||
|
||||
@@ -1346,7 +1124,6 @@ const saveData = async () => {
|
||||
// 保存冠军Logo配置
|
||||
currentConfig.displayConfig.championLogos = championLogos.value;
|
||||
currentConfig.battleEndTime = localBattleEndTime.value;
|
||||
currentConfig.drumConfig = configToSave;
|
||||
|
||||
// 一次性保存所有配置
|
||||
const result = await writeConfig(currentConfig);
|
||||
|
||||
@@ -7,20 +7,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 第二部分:战鼓动画(浮动并支持拖放) -->
|
||||
<section v-if="localDisplayConfig.showDrum" class="drums-section card-game" @mousedown="startDrag"
|
||||
@click="handleDrumClick" :style="{ left: drumsPosition.x + 'px', top: drumsPosition.y + 'px' }">
|
||||
<div class="drums-container">
|
||||
<!-- 战鼓动画在上面 -->
|
||||
<div class="drums-animation">
|
||||
<div class="drum glow-border" :class="{ beating: isBeating }">🥁</div>
|
||||
<div class="drum" :class="{ beating: isBeating }">🥁</div>
|
||||
<div class="trophy" style="font-size: 2.5rem; filter: drop-shadow(0 0 10px var(--gold-primary));">🏆</div>
|
||||
<div class="drum" :class="{ beating: isBeating }">🥁</div>
|
||||
<div class="drum" :class="{ beating: isBeating }">🥁</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- 任务设置模块 -->
|
||||
<section class="task-settings-section card-game">
|
||||
@@ -224,7 +211,6 @@ import {
|
||||
bonusRules,
|
||||
displayConfig,
|
||||
battleEndTime,
|
||||
drumConfig,
|
||||
initializeData
|
||||
} from '../data/mockData.js';
|
||||
import { readConfig } from '../services/configService.js';
|
||||
@@ -692,17 +678,7 @@ const hours = ref(0);
|
||||
const minutes = ref(0);
|
||||
const seconds = ref(0);
|
||||
|
||||
// 战鼓动画状态
|
||||
const isBeating = ref(false);
|
||||
let beatInterval = null;
|
||||
let countdownInterval = null;
|
||||
|
||||
// 音频上下文和战鼓音效
|
||||
let audioContext = null;
|
||||
const isPlayingSound = ref(false);
|
||||
|
||||
// 战鼓位置状态
|
||||
const drumsPosition = ref({ x: 20, y: 20 });
|
||||
// 倒计时位置状态已移除,直接在模板中使用固定位置
|
||||
// 奖金设置模块位置状态 - 使用reactive存储实际定位值
|
||||
const bonusPosition = reactive({ x: 'auto', y: 'auto' });
|
||||
@@ -726,13 +702,7 @@ function throttle(func, limit) {
|
||||
};
|
||||
}
|
||||
|
||||
// 开始拖动战鼓
|
||||
const startDrag = (e) => {
|
||||
isDragging = true;
|
||||
dragOffset.x = e.clientX - drumsPosition.value.x;
|
||||
dragOffset.y = e.clientY - drumsPosition.value.y;
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
|
||||
// 开始拖动奖金模块(鼠标事件)
|
||||
const startBonusDrag = (e) => {
|
||||
@@ -792,10 +762,6 @@ const endTouch = (e) => {
|
||||
|
||||
// 优化的拖动函数 - 使用节流减少更新频率(鼠标事件)
|
||||
const drag = throttle((e) => {
|
||||
if (isDragging) {
|
||||
drumsPosition.value.x = e.clientX - dragOffset.x;
|
||||
drumsPosition.value.y = e.clientY - dragOffset.y;
|
||||
}
|
||||
if (isBonusDragging) {
|
||||
// 计算新的位置
|
||||
const newX = e.clientX - bonusDragOffset.x;
|
||||
@@ -881,237 +847,11 @@ const calculateCountdown = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化音频上下文
|
||||
const initAudioContext = () => {
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
};
|
||||
|
||||
// 音频缓存,用于存储加载的MP3文件
|
||||
const audioBufferCache = ref({});
|
||||
|
||||
// 加载MP3文件到音频缓冲区
|
||||
const loadAudioFile = async (filePath) => {
|
||||
try {
|
||||
// 检查是否已缓存
|
||||
if (audioBufferCache.value[filePath]) {
|
||||
return audioBufferCache.value[filePath];
|
||||
}
|
||||
|
||||
// 加载音频文件
|
||||
const response = await fetch(filePath);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
||||
|
||||
// 缓存音频缓冲区
|
||||
audioBufferCache.value[filePath] = audioBuffer;
|
||||
return audioBuffer;
|
||||
} catch (error) {
|
||||
console.error('加载音频文件失败:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 播放战鼓音效
|
||||
const playDrumSound = async (isStrongBeat = false) => {
|
||||
// 检查是否启用声音播放
|
||||
if (!audioContext || isPlayingSound.value || drumConfig?.sound?.enabled === false) return;
|
||||
|
||||
isPlayingSound.value = true;
|
||||
|
||||
try {
|
||||
// 使用配置的音效参数
|
||||
const soundConfig = drumConfig?.sound || {};
|
||||
const patternConfig = drumConfig?.pattern || {};
|
||||
|
||||
// 检查是否配置了MP3文件路径
|
||||
if (soundConfig.soundSrc && soundConfig.soundSrc.trim() !== '') {
|
||||
// 使用MP3文件播放
|
||||
const audioBuffer = await loadAudioFile(soundConfig.soundSrc);
|
||||
|
||||
if (audioBuffer) {
|
||||
// 创建音频源节点
|
||||
const source = audioContext.createBufferSource();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
// 连接节点
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
// 设置音量,支持强拍音量增强
|
||||
const baseVolume = soundConfig.volume || 1.0;
|
||||
const accentMultiplier = patternConfig.accentMultiplier || 1.2;
|
||||
const volume = isStrongBeat ? baseVolume * accentMultiplier : baseVolume;
|
||||
gainNode.gain.value = volume;
|
||||
|
||||
// 播放声音
|
||||
source.start(0);
|
||||
|
||||
// 设置完成后重置播放状态
|
||||
setTimeout(() => {
|
||||
isPlayingSound.value = false;
|
||||
}, audioBuffer.duration * 1000 + 100);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有配置MP3文件或加载失败,回退到合成音效
|
||||
// 创建振荡器节点
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
// 连接节点
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
// 设置战鼓音效参数,支持强拍
|
||||
const baseVolume = soundConfig.volume || 1.0;
|
||||
// 使用pattern配置中的强拍音量倍数
|
||||
const accentMultiplier = patternConfig.accentMultiplier || 1.2;
|
||||
const volume = isStrongBeat ? baseVolume * accentMultiplier : baseVolume;
|
||||
|
||||
// 使用soundConfig中的type1
|
||||
oscillator.type = soundConfig.type1 || 'sine';
|
||||
|
||||
// 基础频率,支持强拍频率偏移
|
||||
const baseFrequency = soundConfig.frequency1 || 150;
|
||||
const frequencyOffset = isStrongBeat ? (patternConfig.accentFrequencyOffset || 0) / 100 : 0;
|
||||
const actualFrequency = baseFrequency * (1 + frequencyOffset);
|
||||
|
||||
oscillator.frequency.setValueAtTime(actualFrequency, audioContext.currentTime);
|
||||
// 频率渐变
|
||||
oscillator.frequency.exponentialRampToValueAtTime(actualFrequency * 0.5, audioContext.currentTime + 0.1);
|
||||
|
||||
// 设置音量包络
|
||||
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
|
||||
gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + (soundConfig.attackTime || 0.01));
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + (soundConfig.decayTime || 0.3));
|
||||
|
||||
// 播放声音
|
||||
oscillator.start();
|
||||
oscillator.stop(audioContext.currentTime + (soundConfig.decayTime || 0.3));
|
||||
|
||||
// 双音调效果 - 始终使用
|
||||
const oscillator2 = audioContext.createOscillator();
|
||||
const gainNode2 = audioContext.createGain();
|
||||
|
||||
oscillator2.connect(gainNode2);
|
||||
gainNode2.connect(audioContext.destination);
|
||||
|
||||
oscillator2.type = soundConfig.type2 || 'triangle';
|
||||
oscillator2.frequency.setValueAtTime(soundConfig.frequency2 || 100, audioContext.currentTime);
|
||||
|
||||
gainNode2.gain.setValueAtTime(0, audioContext.currentTime);
|
||||
gainNode2.gain.linearRampToValueAtTime(volume * 0.8, audioContext.currentTime + (soundConfig.attackTime || 0.01));
|
||||
gainNode2.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + (soundConfig.decayTime || 0.3) + 0.2);
|
||||
|
||||
oscillator2.start();
|
||||
oscillator2.stop(audioContext.currentTime + (soundConfig.decayTime || 0.3) + 0.2);
|
||||
|
||||
// 设置完成后重置播放状态
|
||||
setTimeout(() => {
|
||||
isPlayingSound.value = false;
|
||||
}, (soundConfig.decayTime || 0.3) * 1000 + 150);
|
||||
} catch (error) {
|
||||
console.error('播放战鼓音效出错:', error);
|
||||
isPlayingSound.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 修改相关函数以支持异步
|
||||
const handleDrumClick = async () => {
|
||||
// 如果音频上下文未初始化,初始化它
|
||||
if (!audioContext) {
|
||||
initAudioContext();
|
||||
}
|
||||
|
||||
// 如果音频上下文被暂停,恢复它
|
||||
if (audioContext.state === 'suspended') {
|
||||
audioContext.resume();
|
||||
}
|
||||
|
||||
// 触发战鼓动画和音效,使用配置的点击效果
|
||||
const animationConfig = drumConfig?.animation || {};
|
||||
isBeating.value = true;
|
||||
await playDrumSound(true); // 点击总是强拍
|
||||
|
||||
setTimeout(() => {
|
||||
isBeating.value = false;
|
||||
}, animationConfig.clickBeatDuration || 250);
|
||||
};
|
||||
|
||||
// 战鼓动画效果
|
||||
const startDrumAnimation = () => {
|
||||
// 检查是否显示战鼓
|
||||
if (drumConfig?.showDrum === false) return;
|
||||
|
||||
// 使用配置的动画和节拍参数
|
||||
const animationConfig = drumConfig?.animation || {};
|
||||
const patternConfig = drumConfig?.pattern || {};
|
||||
|
||||
// 检查是否启用动画
|
||||
if (animationConfig.enabled === false) return;
|
||||
|
||||
let beatCount = 0;
|
||||
// 使用配置的节拍间隔
|
||||
const interval = animationConfig.beatInterval || 200;
|
||||
|
||||
beatInterval = setInterval(() => {
|
||||
beatCount++;
|
||||
// 使用配置的节拍模式和总拍数
|
||||
const totalBeats = patternConfig.totalBeats || 4;
|
||||
const currentBeat = ((beatCount - 1) % totalBeats) + 1;
|
||||
|
||||
// 根据节拍模式确定是否是强拍
|
||||
const strongBeats = patternConfig.strongBeats || [1, 4];
|
||||
const isStrongBeat = strongBeats.includes(currentBeat);
|
||||
|
||||
// 执行动画和音效
|
||||
isBeating.value = true;
|
||||
|
||||
// 根据是否是强拍播放音效
|
||||
playDrumSound(isStrongBeat);
|
||||
|
||||
// 设置CSS变量,支持强拍动画增强
|
||||
const drums = document.querySelectorAll('.drum');
|
||||
drums.forEach(drum => {
|
||||
// 使用配置的动画参数
|
||||
drum.style.setProperty('--drum-scale', isStrongBeat ?
|
||||
(animationConfig.beatScale || 1.3) * (1 + (patternConfig.accentAnimation || 0) / 100) :
|
||||
(animationConfig.beatScale || 1.3));
|
||||
drum.style.setProperty('--drum-translate-y', isStrongBeat ?
|
||||
`${(animationConfig.beatTranslateY || -15) * (1 + (patternConfig.accentAnimation || 0) / 100)}px` :
|
||||
`${animationConfig.beatTranslateY || -15}px`);
|
||||
drum.style.setProperty('--drum-rotate', `${animationConfig.beatRotate || 5}deg`);
|
||||
drum.style.setProperty('--drum-brightness', isStrongBeat ? '1.4' : '1.3');
|
||||
drum.style.setProperty('--drum-saturation', isStrongBeat ? '1.3' : '1.2');
|
||||
});
|
||||
|
||||
// 根据节拍类型设置持续时间
|
||||
const beatDuration = isStrongBeat
|
||||
? (animationConfig.beatDuration || 150)
|
||||
: (animationConfig.beatDuration || 100);
|
||||
|
||||
setTimeout(() => {
|
||||
isBeating.value = false;
|
||||
}, beatDuration);
|
||||
}, interval);
|
||||
};
|
||||
|
||||
// 已移至文件中异步版本的handleDrumClick函数
|
||||
|
||||
// 跳转到管理员页面
|
||||
const goToAdmin = () => {
|
||||
router.push('/admin');
|
||||
};
|
||||
|
||||
// 监听窗口点击事件,用于用户交互后初始化音频上下文
|
||||
document.addEventListener('click', initAudioContext, { once: true });
|
||||
document.addEventListener('touchstart', initAudioContext, { once: true });
|
||||
|
||||
const handleResize = () => {
|
||||
// 计算并设置排名明细区域的最小高度,使其底部与视口对齐
|
||||
const rankingsSection = document.querySelector('.rankings-section');
|
||||
@@ -1156,7 +896,6 @@ onMounted(async () => {
|
||||
|
||||
calculateCountdown();
|
||||
countdownInterval = setInterval(calculateCountdown, 10); // 改为10ms更新一次以显示毫秒
|
||||
startDrumAnimation();
|
||||
// 监听窗口大小变化,确保排名明细与底部对齐
|
||||
window.addEventListener('resize', handleResize);
|
||||
handleResize(); // 初始调整
|
||||
@@ -1187,7 +926,6 @@ const handleDisplayConfigChange = () => {
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
if (beatInterval) clearInterval(beatInterval);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
|
||||
// 移除拖放相关的事件监听
|
||||
@@ -1198,12 +936,6 @@ onUnmounted(() => {
|
||||
document.removeEventListener('touchmove', touchMove);
|
||||
document.removeEventListener('touchend', endTouch);
|
||||
document.removeEventListener('touchcancel', endTouch);
|
||||
|
||||
// 清理音频资源
|
||||
if (audioContext) {
|
||||
audioContext.close();
|
||||
audioContext = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1725,97 +1457,7 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 战鼓部分 - 浮动并支持拖放 */
|
||||
.drums-section {
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
cursor: move;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.drums-section:hover {
|
||||
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.drums-section:active {
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.drums-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.drums-animation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.drum {
|
||||
transition: transform 0.1s ease, filter 0.1s ease;
|
||||
animation: idlePulse 2s infinite alternate;
|
||||
}
|
||||
|
||||
.drum.beating {
|
||||
/* 使用CSS变量方便动态调整 */
|
||||
--drum-scale: 1.3;
|
||||
--drum-translate-y: -15px;
|
||||
--drum-rotate: 5deg;
|
||||
--drum-brightness: 1.3;
|
||||
--drum-saturation: 1.2;
|
||||
|
||||
transform: scale(var(--drum-scale)) translateY(var(--drum-translate-y)) rotate(var(--drum-rotate));
|
||||
filter: brightness(var(--drum-brightness)) saturate(var(--drum-saturation));
|
||||
animation: drumBeat 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
/* 战鼓闲置时的轻微脉动动画 */
|
||||
@keyframes idlePulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* 增强跳动效果的关键帧动画 */
|
||||
@keyframes drumBeat {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(var(--drum-scale, 1.3)) translateY(var(--drum-translate-y, -15px)) rotate(var(--drum-rotate, 5deg));
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.trophy {
|
||||
animation: bounce 1s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn-game-secondary {
|
||||
@@ -2320,11 +1962,7 @@ onUnmounted(() => {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 战鼓部分调整 */
|
||||
.drums-section {
|
||||
transform: scale(0.8);
|
||||
/* 缩小战鼓元素 */
|
||||
}
|
||||
|
||||
|
||||
/* 2. 倒计时模块调整 - 移至冠军战区上方,缩小时间显示为一行 */
|
||||
.timer-float {
|
||||
|
||||
Reference in New Issue
Block a user