Files
vs100/src/views/AdminPanel.vue

1600 lines
47 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="admin-panel">
<!-- 登录界面 -->
<div v-if="!isLoggedIn" class="login-container">
<div class="login-form">
<h2 class="login-title">🔐 管理员登录</h2>
<div class="form-group">
<label for="username">用户名:</label>
<input
id="username"
v-model="loginForm.username"
type="text"
placeholder="请输入用户名"
class="form-input"
/>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input
id="password"
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
class="form-input"
/>
</div>
<button @click="login" class="login-btn">登录</button>
<button @click="goToHome" class="home-btn">返回首页</button>
<div v-if="loginError" class="error-message">{{ loginError }}</div>
</div>
</div>
<!-- 管理界面 --><div v-else class="management-container">
<!-- 顶部导航 -->
<div class="top-nav">
<h1 class="nav-title">📊 百人大战管理系统</h1>
<div class="nav-actions">
<span class="welcome-text">欢迎您{{ currentUser.username }}</span>
<button @click="handleRefreshData" class="refresh-btn" style="margin-right: 10px;">刷新数据</button>
<button @click="logout" class="logout-btn">退出登录</button>
</div>
</div>
<!-- 功能选项卡 -->
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.key"
@click="currentTab = tab.key"
:class="['tab-btn', { active: currentTab === tab.key }]"
>{{ tab.label }}</button>
</div>
<!-- 内容区域 --><div class="tab-content">
<!-- 结束时间设置 --><div v-if="currentTab === 'endTime'" class="end-time-content">
<h2 class="config-title"> 百人大战结束时间设置</h2>
<div class="time-setting-section">
<div class="config-item">
<label class="checkbox-label">
<span>结束日期</span>
<input
type="date"
v-model="localBattleEndTime.date"
class="text-input"
>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span>结束时间</span>
<input
type="time"
step="1"
v-model="localBattleEndTime.time"
class="text-input"
>
</label>
</div>
<div class="config-item">
<span class="info-text">提示设置的结束时间将用于动态计算并显示"距离结束还有多少时间"</span>
</div>
</div>
</div>
<!-- 显示配置管理 --><div v-if="currentTab === 'config'" class="config-content">
<h2 class="config-title"> 显示配置管理</h2>
<!-- 个人排名显示配置 -->
<div class="config-section">
<h3>👤 个人排名显示选项</h3>
<div class="config-options">
<div class="config-item">
<label class="checkbox-label">
<input
type="checkbox"
v-model="localDisplayConfig.individual.showLevel"
>
<span>显示等级列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input
type="checkbox"
v-model="localDisplayConfig.individual.showDepartment"
>
<span>显示部门列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span>签单金额列显示名称</span>
<input type="text" v-model="localDisplayConfig.individual.scoreColumn.displayName"
placeholder="输入显示名称" class="text-input">
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span>签单金额显示样式</span>
<select v-model="localDisplayConfig.individual.scoreColumn.displayStyle" class="select-input">
<option value="amount">金额¥符号</option>
<option value="number">普通数字</option>
</select>
</label>
</div>
<div class="column-widths-section">
<h4>列宽设置单位像素</h4>
<div class="column-width-item">
<label>排名列</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.rank" min="40" max="200" class="width-input">
</div>
<div class="column-width-item">
<label>头像列</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.avatar" min="40" max="200" class="width-input">
</div>
<div class="column-width-item">
<label>姓名列</label>
<select v-model.number="localDisplayConfig.individual.columnWidths.name" class="width-select">
<option :value="1">自动填充</option>
<option value="100">100px</option>
<option value="150">150px</option>
<option value="200">200px</option>
<option value="250">250px</option>
</select>
</div>
<div class="column-width-item">
<label>签单金额列</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.score" min="60" max="200" class="width-input">
</div>
<div class="column-width-item" v-if="localDisplayConfig.individual.showLevel">
<label>等级列</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.level" min="60" max="200" class="width-input">
</div>
<div class="column-width-item" v-if="localDisplayConfig.individual.showDepartment">
<label>部门列</label>
<select v-model.number="localDisplayConfig.individual.columnWidths.department" class="width-select">
<option :value="1">自动填充</option>
<option value="100">100px</option>
<option value="150">150px</option>
<option value="200">200px</option>
</select>
</div>
<div class="column-width-item">
<label>奖金列</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.bonus" min="60" max="200" class="width-input">
</div>
</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="localDisplayConfig.team.showMemberCount"
>
<span>显示人数列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input
type="checkbox"
v-model="localDisplayConfig.team.showLeader"
>
<span>显示队长列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span>签单金额列显示名称</span>
<input type="text" v-model="localDisplayConfig.team.totalScoreColumn.displayName"
placeholder="输入显示名称" class="text-input">
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span>签单金额显示样式</span>
<select v-model="localDisplayConfig.team.totalScoreColumn.displayStyle" class="select-input">
<option value="amount">金额¥符号</option>
<option value="number">普通数字</option>
</select>
</label>
</div>
<div class="column-widths-section">
<h4>列宽设置单位像素</h4>
<div class="column-width-item">
<label>排名列</label>
<input type="number" v-model.number="localDisplayConfig.team.columnWidths.rank" min="40" max="200" class="width-input">
</div>
<div class="column-width-item">
<label>战队名列</label>
<select v-model.number="localDisplayConfig.team.columnWidths.name" class="width-select">
<option :value="1">自动填充</option>
<option value="150">150px</option>
<option value="200">200px</option>
<option value="250">250px</option>
<option value="300">300px</option>
</select>
</div>
<div class="column-width-item">
<label>签单金额列</label>
<input type="number" v-model.number="localDisplayConfig.team.columnWidths.score" min="60" max="200" class="width-input">
</div>
<div class="column-width-item" v-if="localDisplayConfig.team.showMemberCount">
<label>人数列</label>
<input type="number" v-model.number="localDisplayConfig.team.columnWidths.memberCount" min="60" max="100" class="width-input">
</div>
<div class="column-width-item" v-if="localDisplayConfig.team.showLeader">
<label>队长列</label>
<select v-model.number="localDisplayConfig.team.columnWidths.leader" class="width-select">
<option :value="1">自动填充</option>
<option value="100">100px</option>
<option value="150">150px</option>
<option value="200">200px</option>
</select>
</div>
<div class="column-width-item">
<label>奖金列</label>
<input type="number" v-model.number="localDisplayConfig.team.columnWidths.bonus" min="60" max="200" class="width-input">
</div>
</div>
</div>
</div>
</div>
<!-- 个人排名管理 --><div v-if="currentTab === 'individual'" class="rank-content">
<div class="management-header">
<h2>👤 个人排名管理</h2>
<button @click="showAddIndividual = true" class="add-btn"> 添加人员</button>
</div>
<div class="rank-table">
<div class="table-header">
<span class="rank-col">排名</span>
<span class="name-col">姓名</span>
<span class="score-col">{{ displayConfig.individual.scoreColumn.displayName }}</span>
<span v-if="displayConfig.individual.showLevel" class="level-col">等级</span>
<span v-if="displayConfig.individual.showDepartment" class="dept-col">部门</span>
<span class="bonus-col">奖金</span>
<span class="action-col">操作</span>
</div>
<div
v-for="(item, index) in localIndividualRankings"
:key="item.id"
class="table-row"
:class="{ 'highlight': index === 0 }"
>
<span class="rank-col">{{ index + 1 }}</span>
<span class="name-col">{{ item.name }}</span>
<span class="score-col">{{ displayConfig.individual.scoreColumn.displayStyle === 'amount' ? '¥' + item.score : item.score }}</span>
<span v-if="displayConfig.individual.showLevel" class="level-col" :class="`level-${item.level}`">{{ item.level }}</span>
<span v-if="displayConfig.individual.showDepartment" class="dept-col">{{ item.department }}</span>
<span class="bonus-col">¥{{ item.bonus }}</span>
<span class="action-col">
<button @click="editIndividual(item)" class="edit-btn"> 编辑</button>
<button @click="deleteIndividual(item.id)" class="delete-btn">🗑 删除</button>
</span>
</div>
</div>
</div>
<!-- 战队排名管理 --><div v-if="currentTab === 'team'" class="rank-content">
<div class="management-header">
<h2>👥 战队排名管理</h2>
<button @click="showAddTeam = true" class="add-btn"> 添加战队</button>
</div>
<div class="rank-table">
<div class="table-header">
<span class="rank-col">排名</span>
<span class="name-col">战队名称</span>
<span class="score-col">{{ displayConfig.team.totalScoreColumn.displayName }}</span>
<span v-if="displayConfig.team.showMemberCount" class="member-col">人数</span>
<span v-if="displayConfig.team.showLeader" class="leader-col">队长</span>
<span class="bonus-col">奖金</span>
<span class="action-col">操作</span>
</div>
<div
v-for="(item, index) in localTeamRankings"
:key="item.id"
class="table-row"
:class="{ 'highlight': index === 0 }"
>
<span class="rank-col">{{ index + 1 }}</span>
<span class="name-col">{{ item.name }}</span>
<span class="score-col">{{ displayConfig.team.totalScoreColumn.displayStyle === 'amount' ? '¥' + item.totalScore : item.totalScore }}</span>
<span v-if="displayConfig.team.showMemberCount" class="member-col">{{ item.memberCount }}</span>
<span v-if="displayConfig.team.showLeader" class="leader-col">{{ item.leader }}</span>
<span class="bonus-col">¥{{ item.bonus }}</span>
<span class="action-col">
<button @click="editTeam(item)" class="edit-btn"> 编辑</button>
<button @click="deleteTeam(item.id)" class="delete-btn">🗑 删除</button>
</span>
</div>
</div>
</div>
<!-- 奖金设置 -->
<div v-if="currentTab === 'bonus'" class="bonus-config-content">
<h2 class="config-title">🎯 奖金设置</h2>
<div class="config-section">
<h3>🏆 奖金规则配置</h3>
<div class="bonus-rules-list">
<div
v-for="(rule, index) in localBonusRules"
:key="index"
class="bonus-rule-item"
>
<div class="rule-form">
<div class="form-group">
<label>名次范围:</label>
<input v-model="rule.rank" type="text" class="form-input" placeholder="如: 1-3">
</div>
<div class="form-group">
<label>规则描述:</label>
<input v-model="rule.description" type="text" class="form-input" placeholder="如: 前三名">
</div>
<div class="form-group">
<label>个人奖励:</label>
<input v-model="rule.individualBonus" type="text" class="form-input" placeholder="如: ¥10000, ¥8000, ¥5000">
</div>
<div class="form-group">
<label>团队奖励:</label>
<input v-model="rule.teamBonus" type="text" class="form-input" placeholder="如: ¥50000, ¥30000, ¥20000">
</div>
<div class="form-actions">
<button @click="deleteBonusRule(index)" class="delete-btn">🗑 删除</button>
</div>
</div>
</div>
<button @click="addBonusRule" class="add-btn" style="margin-top: 20px;"> 添加奖金规则</button>
</div>
</div>
</div>
<!-- 战鼓配置 -->
<div v-if="currentTab === 'drum'" class="drum-config-content">
<h2 class="config-title">🥁 战鼓配置管理</h2>
<!-- 音效配置 -->
<div class="config-section">
<h3>🔊 音效配置</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>
<!-- 保存按钮 --><div class="save-section">
<button @click="saveData" class="save-btn">💾 保存所有数据</button>
<button @click="goBack" class="back-btn">🏠 返回首页</button>
</div>
</div>
<!-- 添加/编辑个人对话框 --><div v-if="showAddIndividual || showEditIndividual" class="modal-overlay">
<div class="modal">
<h3 class="modal-title">{{ editingIndividual ? '编辑人员' : '添加人员' }}</h3>
<form @submit.prevent="saveIndividual">
<div class="form-group">
<label>姓名:</label>
<input v-model="individualForm.name" type="text" required class="form-input">
</div>
<div class="form-group">
<label>签单金额:</label>
<input v-model.number="individualForm.score" type="number" required class="form-input">
</div>
<div class="form-group">
<label>等级:</label>
<select v-model="individualForm.level" class="form-input">
<option value="SSS">SSS</option>
<option value="SS">SS</option>
<option value="S">S</option>
<option value="A">A</option>
<option value="B">B</option>
</select>
</div>
<div class="form-group">
<label>部门:</label>
<input v-model="individualForm.department" type="text" required class="form-input">
</div>
<div class="form-group">
<label>奖金:</label>
<input v-model.number="individualForm.bonus" type="number" required class="form-input">
</div>
<div class="form-actions">
<button type="submit" class="confirm-btn">确定</button>
<button type="button" @click="cancelEditIndividual" class="cancel-btn">取消</button>
</div>
</form>
</div>
</div>
<!-- 添加/编辑战队对话框 --><div v-if="showAddTeam || showEditTeam" class="modal-overlay">
<div class="modal">
<h3 class="modal-title">{{ editingTeam ? '编辑战队' : '添加战队' }}</h3>
<form @submit.prevent="saveTeam">
<div class="form-group">
<label>战队名称:</label>
<input v-model="teamForm.name" type="text" required class="form-input">
</div>
<div class="form-group">
<label>签单金额:</label>
<input v-model.number="teamForm.totalScore" type="number" required class="form-input">
</div>
<div class="form-group">
<label>人数:</label>
<input v-model.number="teamForm.memberCount" type="number" required class="form-input">
</div>
<div class="form-group">
<label>队长:</label>
<input v-model="teamForm.leader" type="text" required class="form-input">
</div>
<div class="form-group">
<label>奖金:</label>
<input v-model.number="teamForm.bonus" type="number" required class="form-input">
</div>
<div class="form-actions">
<button type="submit" class="confirm-btn">确定</button>
<button type="button" @click="cancelEditTeam" class="cancel-btn">取消</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import {
individualRankings,
teamRankings,
systemUsers,
bonusRules,
saveIndividualRankings,
saveTeamRankings,
saveBonusRules,
displayConfig,
saveDisplayConfig,
battleEndTime,
saveBattleEndTime,
drumConfig,
saveDrumConfig,
refreshData,
initializeData
} from '../data/mockData.js';
const router = useRouter();
// 返回首页
const goToHome = () => {
router.push('/');
};
// 登录相关
const isLoggedIn = ref(false);
const loginForm = reactive({
username: '',
password: ''
});
const loginError = ref('');
const currentUser = ref({});
// 标签页相关
const tabs = [
{ key: 'individual', label: '个人排名' },
{ key: 'team', label: '战队排名' },
{ key: 'bonus', label: '奖金设置' },
{ key: 'config', label: '显示配置' },
{ key: 'endTime', label: '结束时间设置' },
{ key: 'drum', label: '战鼓配置' }
];
// 刷新数据
const handleRefreshData = () => {
try {
const success = refreshData();
// 重新加载本地数据副本
localIndividualRankings.value = [...individualRankings];
localTeamRankings.value = [...teamRankings];
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('数据刷新成功!');
} else {
alert('数据刷新失败,请重试。');
}
} catch (error) {
console.error('数据刷新失败:', error);
alert('数据刷新失败,请重试。');
}
};
const currentTab = ref('individual');
// 本地数据副本
const localIndividualRankings = ref([...individualRankings]);
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 () => {
try {
await initializeData();
// 重新加载本地数据副本
localIndividualRankings.value = [...individualRankings];
localTeamRankings.value = [...teamRankings];
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);
const showEditIndividual = ref(false);
const editingIndividual = ref(null);
const showAddTeam = ref(false);
const showEditTeam = ref(false);
const editingTeam = ref(null);
// 表单数据
const individualForm = reactive({
id: null,
name: '',
score: 0,
level: 'A',
avatar: '⭐',
department: '',
completedTasks: 0,
bonus: 0
});
const teamForm = reactive({
id: null,
name: '',
totalScore: 0,
memberCount: 5,
level: 'A',
leader: '',
completedTasks: 0,
bonus: 0
});
// 登录
const login = () => {
const user = systemUsers.find(
u => u.username === loginForm.username && u.password === loginForm.password
);
if (user) {
isLoggedIn.value = true;
currentUser.value = user;
loginError.value = '';
} else {
loginError.value = '用户名或密码错误';
}
};
// 登出
const logout = () => {
isLoggedIn.value = false;
currentUser.value = {};
loginForm.username = '';
loginForm.password = '';
};
// 返回首页
const goBack = () => {
router.push('/');
};
// 保存数据
const saveData = async () => {
try {
// 按分数重新排序
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');
// 一次性读取、修改并保存所有配置,避免竞态条件
const currentConfig = await readConfig();
// 更新所有配置项
currentConfig.individualRankings = localIndividualRankings.value;
currentConfig.teamRankings = localTeamRankings.value;
currentConfig.bonusRules = localBonusRules.value;
currentConfig.displayConfig = localDisplayConfig.value;
currentConfig.battleEndTime = localBattleEndTime.value;
currentConfig.drumConfig = configToSave;
// 一次性保存所有配置
const result = await writeConfig(currentConfig);
// 检查保存是否成功
if (result) {
alert('数据保存成功!');
} else {
alert('数据保存失败,请检查网络连接后重试。');
}
} catch (error) {
console.error('保存数据时发生错误:', error);
alert('保存数据失败,请重试。');
}
};
// 添加/编辑个人
const editIndividual = (item) => {
editingIndividual.value = item;
Object.assign(individualForm, item);
showEditIndividual.value = true;
};
const saveIndividual = () => {
if (editingIndividual.value) {
// 编辑现有人员
const index = localIndividualRankings.value.findIndex(
i => i.id === individualForm.id
);
if (index !== -1) {
localIndividualRankings.value[index] = { ...individualForm };
}
} else {
// 添加新人员
individualForm.id = Date.now();
localIndividualRankings.value.push({ ...individualForm });
}
cancelEditIndividual();
};
const cancelEditIndividual = () => {
showAddIndividual.value = false;
showEditIndividual.value = false;
editingIndividual.value = null;
resetIndividualForm();
};
const resetIndividualForm = () => {
individualForm.id = null;
individualForm.name = '';
individualForm.score = 0;
individualForm.level = 'A';
individualForm.avatar = '⭐';
individualForm.department = '';
individualForm.completedTasks = 0;
individualForm.bonus = 0;
};
// 删除个人
const deleteIndividual = (id) => {
if (confirm('确定要删除这个人员吗?')) {
localIndividualRankings.value = localIndividualRankings.value.filter(
item => item.id !== id
);
}
};
// 添加/编辑战队
const editTeam = (item) => {
editingTeam.value = item;
Object.assign(teamForm, item);
showEditTeam.value = true;
};
const saveTeam = () => {
if (editingTeam.value) {
// 编辑现有战队
const index = localTeamRankings.value.findIndex(
i => i.id === teamForm.id
);
if (index !== -1) {
localTeamRankings.value[index] = { ...teamForm };
}
} else {
// 添加新战队
teamForm.id = Date.now();
localTeamRankings.value.push({ ...teamForm });
}
cancelEditTeam();
};
const cancelEditTeam = () => {
showAddTeam.value = false;
showEditTeam.value = false;
editingTeam.value = null;
resetTeamForm();
};
const resetTeamForm = () => {
teamForm.id = null;
teamForm.name = '';
teamForm.totalScore = 0;
teamForm.memberCount = 5;
teamForm.level = 'A';
teamForm.leader = '';
teamForm.completedTasks = 0;
teamForm.bonus = 0;
};
// 删除战队
const deleteTeam = (id) => {
if (confirm('确定要删除这个战队吗?')) {
localTeamRankings.value = localTeamRankings.value.filter(
item => item.id !== id
);
}
};
// 添加奖金规则
const addBonusRule = () => {
localBonusRules.value.push({
rank: '',
description: '',
individualBonus: '',
teamBonus: ''
});
};
// 删除奖金规则
const deleteBonusRule = (index) => {
if (confirm('确定要删除这条奖金规则吗?')) {
localBonusRules.value.splice(index, 1);
}
};
</script>
<style scoped>
.admin-panel {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
}
/* 登录界面 */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
}
.login-form {
background: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
min-width: 300px;
}
.login-title {
color: #667eea;
text-align: center;
margin-bottom: 30px;
font-size: 1.8rem;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #666;
font-weight: bold;
}
.form-input {
width: 100%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s;
}
.form-input:focus {
outline: none;
border-color: #667eea;
}
.login-btn {
width: calc(50% - 5px);
padding: 12px;
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
cursor: pointer;
transition: transform 0.3s;
margin-right: 10px;
}
.login-btn:hover {
transform: translateY(-2px);
}
.home-btn {
width: calc(50% - 5px);
padding: 12px;
background: linear-gradient(45deg, #28a745, #20c997);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
cursor: pointer;
transition: transform 0.3s;
}
.home-btn:hover {
transform: translateY(-2px);
}
.error-message {
color: #e74c3c;
text-align: center;
margin-top: 15px;
}
/* 管理界面 */
.management-container {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.top-nav {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-title {
margin: 0;
font-size: 1.8rem;
}
.nav-actions {
display: flex;
align-items: center;
gap: 20px;
}
.welcome-text {
font-size: 1.1rem;
}
.logout-btn {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid white;
padding: 8px 20px;
border-radius: 30px;
cursor: pointer;
transition: background 0.3s;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
/* 标签页 */
.tabs {
display: flex;
background: #f8f9fa;
border-bottom: 2px solid #dee2e6;
}
.tab-btn {
flex: 1;
padding: 15px;
border: none;
background: none;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s;
}
.tab-btn.active {
background: white;
color: #667eea;
border-bottom: 3px solid #667eea;
}
.tab-content {
padding: 30px;
}
/* 配置页面样式 */
.config-content {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
}
.config-title {
color: #667eea;
margin-bottom: 30px;
text-align: center;
}
.end-time-content {
padding: 20px;
background: rgba(255, 255, 255, 0.9);
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.time-setting-section {
background: white;
padding: 20px;
border-radius: 10px;
border: 1px solid #ddd;
}
.info-text {
color: #666;
font-size: 0.9rem;
font-style: italic;
margin-top: 15px;
display: block;
}
.config-section {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.config-section h3 {
color: #495057;
margin-bottom: 15px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.config-options {
display: flex;
flex-direction: column;
gap: 15px;
}
.config-item {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 10px;
background: #f8f9fa;
border-radius: 8px;
}
.column-widths-section {
margin-top: 20px;
padding: 15px;
background: #e3f2fd;
border-radius: 10px;
border: 1px solid #bbdefb;
}
.column-widths-section h4 {
margin-top: 0;
margin-bottom: 15px;
color: #1976d2;
font-size: 1.1rem;
}
.column-width-item {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 8px;
background: white;
border-radius: 5px;
}
.column-width-item label {
width: 100px;
font-weight: 500;
color: #333;
}
.width-input {
width: 80px;
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
.width-select {
width: 120px;
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 1.1rem;
color: #495057;
}
.checkbox-label input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.text-input,
.select-input {
padding: 5px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
margin-left: 10px;
}
.text-input {
flex: 1;
max-width: 200px;
}
.select-input {
min-width: 150px;
}
/* 管理内容 */
.management-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.management-header h2 {
color: #495057;
margin: 0;
}
.add-btn {
background: linear-gradient(45deg, #28a745, #20c997);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
transition: transform 0.3s;
}
.add-btn:hover {
transform: translateY(-2px);
}
/* 表格样式 */
.rank-table {
border-radius: 10px;
overflow: hidden;
border: 1px solid #dee2e6;
}
.table-header {
background: linear-gradient(45deg, #6c757d, #adb5bd);
color: white;
display: grid;
grid-template-columns: 60px 1fr 80px 80px 100px 80px 120px;
padding: 15px 10px;
font-weight: bold;
}
.team-rankings .table-header {
grid-template-columns: 60px 1fr 80px 60px 100px 80px 120px;
}
.table-row {
display: grid;
grid-template-columns: 60px 1fr 80px 80px 100px 80px 120px;
padding: 15px 10px;
border-bottom: 1px solid #dee2e6;
transition: background-color 0.3s;
}
.team-rankings .table-row {
grid-template-columns: 60px 1fr 80px 60px 100px 80px 120px;
}
.table-row:hover {
background-color: #f8f9fa;
}
.table-row.highlight {
background: linear-gradient(135deg, #ffd32a, #ff7979);
color: white;
}
/* 等级样式 */
.level-SSS {
color: #ffd700;
font-weight: bold;
}
.level-SS {
color: #c0c0c0;
font-weight: bold;
}
.level-S {
color: #cd7f32;
font-weight: bold;
}
.level-A {
color: #4caf50;
font-weight: bold;
}
.level-B {
color: #2196f3;
font-weight: bold;
}
/* 操作按钮 */
.action-col {
display: flex;
gap: 5px;
}
.edit-btn,
.delete-btn {
padding: 5px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: transform 0.3s;
}
.edit-btn {
background: #ffc107;
color: #212529;
}
.delete-btn {
background: #dc3545;
color: white;
}
.edit-btn:hover,
.delete-btn:hover {
transform: scale(1.05);
}
/* 保存部分 */
.save-section {
padding: 30px;
text-align: center;
background: #f8f9fa;
display: flex;
justify-content: center;
gap: 20px;
}
.save-btn,
.back-btn {
padding: 12px 30px;
border: none;
border-radius: 30px;
font-size: 1.1rem;
cursor: pointer;
transition: transform 0.3s;
}
.save-btn {
background: linear-gradient(45deg, #17a2b8, #138496);
color: white;
}
.back-btn {
background: linear-gradient(45deg, #6c757d, #5a6268);
color: white;
}
.save-btn:hover,
.back-btn:hover {
transform: scale(1.05);
}
/* 模态框 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal {
background: white;
padding: 30px;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
min-width: 400px;
}
.modal-title {
color: #667eea;
text-align: center;
margin-bottom: 20px;
}
.form-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
.confirm-btn,
.cancel-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: transform 0.3s;
}
.confirm-btn {
background: linear-gradient(45deg, #28a745, #20c997);
color: white;
}
.cancel-btn {
background: linear-gradient(45deg, #6c757d, #5a6268);
color: white;
}
.confirm-btn:hover,
.cancel-btn:hover {
transform: translateY(-2px);
}
/* 响应式设计 */
@media (max-width: 768px) {
.top-nav {
flex-direction: column;
gap: 15px;
}
.table-header,
.table-row {
font-size: 0.8rem;
}
.action-col {
flex-direction: column;
}
.modal {
min-width: auto;
margin: 20px;
}
}
</style>