Files
vs100/src/views/AdminPanel.vue

2075 lines
68 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 card-game">
<h2 class="game-title">🔐 管理员登录</h2>
<div class="form-group">
<label for="username" class="text-gold">用户名:</label>
<input id="username" v-model="loginForm.username" type="text" placeholder="请输入用户名" class="form-input" />
</div>
<div class="form-group">
<label for="password" class="text-gold">密码:</label>
<input id="password" v-model="loginForm.password" type="password" placeholder="请输入密码" class="form-input" />
</div>
<div style="display: flex; gap: 15px; margin-top: 25px;">
<button @click="login" type="submit" class="btn-game" style="flex: 1; padding: 12px;">登录</button>
<button @click="goToHome" class="btn-game-secondary" style="flex: 1; padding: 12px;">返回首页</button>
</div>
<div v-if="loginError" class="error-message">{{ loginError }}</div>
</div>
</div>
<!-- 管理界面 -->
<div v-else class="management-container">
<!-- 顶部导航 -->
<div class="top-nav">
<h1 class="game-title">📊 百日大战管理系统</h1>
<div class="nav-actions">
<span class="welcome-text">欢迎您{{ currentUser.username }}</span>
<button @click="handleRefreshData" class="btn-game" style="margin-right: 10px;">刷新数据</button>
<button @click="logout" class="btn-game-secondary">退出登录</button>
</div>
</div>
<!-- 功能选项卡 -->
<div class="tabs">
<button v-for="tab in tabs" :key="tab.key" @click="currentTab = tab.key"
:class="['btn-game', { active: currentTab === tab.key }]">{{ tab.label }}</button>
</div>
<!-- 内容区域 -->
<div class="tab-content" style="max-height: calc(100vh - 200px); overflow-y: auto;">
<!-- 结束时间设置 -->
<div v-if="currentTab === 'endTime'" class="end-time-content">
<h2 class="game-subtitle"> 百日大战结束时间设置</h2>
<div class="time-setting-section">
<div class="config-item">
<label class="checkbox-label text-gold">
<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 text-gold">提示设置的结束时间将用于动态计算并显示"距离结束还有多少时间"</span>
</div>
</div>
</div>
<!-- 显示配置管理 -->
<div v-if="currentTab === 'config'" class="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 text-gold">
<input type="checkbox" v-model="localDisplayConfig.individual.showLevel">
<span class="text-gold">显示等级列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.individual.showDepartment">
<span class="text-gold">显示部门列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.individual.showAvatar">
<span class="text-gold">显示头像列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.individual.filterZeroScore">
<span class="text-gold">过滤业绩为0的记录</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span class="text-gold">移动端默认显示行数</span>
<input type="number" v-model.number="localDisplayConfig.individual.defaultDisplayRows" min="1" max="50" class="number-input">
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span class="text-gold">业绩列显示名称</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 class="text-gold">业绩显示样式</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-alignment-section">
<h4 style="color: #007bff;">英雄排名列表-列对齐设置</h4>
<div class="column-alignment-item">
<label class="text-gold">排名列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.rank" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item">
<label class="text-gold">姓名列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.name" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item">
<label class="text-gold">业绩列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.score" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item" v-if="localDisplayConfig.individual.showLevel">
<label class="text-gold">等级列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.level" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item" v-if="localDisplayConfig.individual.showDepartment">
<label class="text-gold">部门列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.department" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item" v-if="localDisplayConfig.individual.showTeam">
<label class="text-gold">战区列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.team" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item">
<label class="text-gold">奖金列对齐</label>
<select v-model="localDisplayConfig.individual.columnAlignments.bonus" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
</div>
<div class="column-widths-section">
<h4 class="text-gold">列宽设置单位像素</h4>
<div class="column-width-item">
<label class="text-gold">排名</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 class="text-gold">头像</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 class="text-gold">姓名</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.name" min="40" max="300" class="width-input">
</div>
<div class="column-width-item">
<label class="text-gold">业绩</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 class="text-gold">等级</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 class="text-gold">部门</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 class="text-gold">战区</label>
<input type="number" v-model.number="localDisplayConfig.individual.columnWidths.team" min="10" max="300" class="width-input">
</div>
<div class="column-width-item" v-if="localDisplayConfig.individual.showAvatar">
<label class="text-gold">头像</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 class="text-gold">奖金</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 class="text-gold">👥 战区排名显示选项</h3>
<div class="config-options">
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.team.showMemberCount">
<span class="text-gold">显示人数列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.team.showLeader">
<span class="text-gold">显示队长列</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.team.filterZeroScore">
<span class="text-gold">过滤业绩为0的记录</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span class="text-gold">移动端默认显示行数</span>
<input type="number" v-model.number="localDisplayConfig.team.defaultDisplayRows" min="0" max="50" class="number-input" placeholder="留空或0表示显示所有行">
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<span class="text-gold">业绩列显示名称</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 class="text-gold">业绩显示样式</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-alignment-section">
<h4 style="color: #007bff;">战区排名列表-列对齐设置</h4>
<div class="column-alignment-item">
<label class="text-gold">排名列对齐</label>
<select v-model="localDisplayConfig.team.columnAlignments.rank" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item">
<label class="text-gold">战区名列对齐</label>
<select v-model="localDisplayConfig.team.columnAlignments.name" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item">
<label class="text-gold">业绩列对齐</label>
<select v-model="localDisplayConfig.team.columnAlignments.score" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item" v-if="localDisplayConfig.team.showMemberCount">
<label class="text-gold">人数列对齐</label>
<select v-model="localDisplayConfig.team.columnAlignments.memberCount" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item" v-if="localDisplayConfig.team.showLeader">
<label class="text-gold">队长列对齐</label>
<select v-model="localDisplayConfig.team.columnAlignments.leader" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
<div class="column-alignment-item">
<label class="text-gold">奖金列对齐</label>
<select v-model="localDisplayConfig.team.columnAlignments.bonus" class="align-select">
<option value="left">靠左</option>
<option value="center">居中</option>
<option value="right">靠右</option>
</select>
</div>
</div>
<div class="column-widths-section">
<h4 class="text-gold">列宽设置单位像素</h4>
<div class="column-width-item">
<label class="text-gold">排名</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 class="text-gold">战区名</label>
<input type="number" v-model.number="localDisplayConfig.team.columnWidths.name" min="100" max="400" class="width-input">
</div>
<div class="column-width-item">
<label class="text-gold">业绩</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 class="text-gold">人数</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 class="text-gold">队长</label>
<input type="number" v-model.number="localDisplayConfig.team.columnWidths.leader" min="80" max="250" class="width-input">
</div>
<div class="column-width-item">
<label class="text-gold">奖金</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 class="game-subtitle">👤 个人排名管理</h2>
<button @click="showAddIndividual = true" class="btn-game"> 添加人员</button>
</div>
<div class="rank-table">
<div class="table-header">
<span class="rank-col">排名</span>
<span class="avatar-col">头像</span>
<span class="name-col">姓名</span>
<span class="score-col">{{ localDisplayConfig.individual.scoreColumn.displayName }}</span>
<span v-if="localDisplayConfig.individual.showLevel" class="level-col">等级</span>
<span v-if="localDisplayConfig.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="avatar-col">
<img v-if="item.avatar && item.avatar.startsWith('/')" :src="item.avatar" alt="头像" class="table-avatar">
<span v-else>{{ item.avatar || '👤' }}</span>
</span>
<span class="name-col">{{ item.name }}</span>
<span class="score-col">{{ localDisplayConfig.individual.scoreColumn.displayStyle === 'amount' ? '¥' +
item.score : item.score }}</span>
<span v-if="localDisplayConfig.individual.showLevel" class="level-col" :class="`level-${item.level}`">{{
item.level }}</span>
<span v-if="localDisplayConfig.individual.showDepartment" class="dept-col">{{ item.department }}</span>
<span class="bonus-col">¥{{ item.bonus }}</span>
<span class="action-col">
<button @click="editIndividual(item)" class="btn-game-secondary btn-edit"></button>
<button @click="deleteIndividual(item.id)" class="btn-game-secondary btn-delete">🗑</button>
</span>
</div>
</div>
</div>
<!-- 战区排名管理 -->
<div v-if="currentTab === 'team'" class="rank-content">
<div class="management-header">
<h2 class="game-subtitle">👥 战区排名管理</h2>
<button @click="showAddTeam = true" class="btn-game"> 添加战区</button>
</div>
<div class="rank-table">
<div class="table-header">
<span class="rank-col">排名</span>
<span class="name-col">战区名称</span>
<span class="score-col">{{ localDisplayConfig.team.totalScoreColumn.displayName }}</span>
<span v-if="localDisplayConfig.team.showMemberCount" class="member-col">人数</span>
<span v-if="localDisplayConfig.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">{{ localDisplayConfig.team.totalScoreColumn.displayStyle === 'amount' ? '¥' +
item.totalScore : item.totalScore }}</span>
<span v-if="localDisplayConfig.team.showMemberCount" class="member-col">{{ item.memberCount }}</span>
<span v-if="localDisplayConfig.team.showLeader" class="leader-col">{{ item.leader }}</span>
<span class="bonus-col">¥{{ item.bonus }}</span>
<span class="action-col">
<button @click="editTeam(item)" class="btn-game-secondary btn-edit"></button>
<button @click="deleteTeam(item.id)" class="btn-game-secondary btn-delete">🗑</button>
</span>
</div>
</div>
</div>
<!-- 奖金设置 -->
<div v-if="currentTab === 'bonus'" class="bonus-config-content">
<h2 class="game-subtitle">🎯 奖金设置</h2>
<div class="config-section">
<h3 class="text-gold">🏆 奖金规则配置</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 class="text-gold">名次范围:</label>
<input v-model="rule.rank" type="text" class="form-input" placeholder="如: 1-3">
</div>
<div class="form-group">
<label class="text-gold">规则描述:</label>
<input v-model="rule.description" type="text" class="form-input" placeholder="如: 前三名">
</div>
<div class="form-group">
<label class="text-gold">个人奖励:</label>
<input v-model="rule.individualBonus" type="text" class="form-input"
placeholder="如: ¥10000, ¥8000, ¥5000">
</div>
<div class="form-group">
<label class="text-gold">团队奖励:</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="btn-game-secondary">🗑 删除</button>
</div>
</div>
</div>
<button @click="addBonusRule" class="btn-game" style="margin-top: 20px;"> 添加奖金规则</button>
</div>
</div>
</div>
<!-- 冠军Logo配置 -->
<div v-if="currentTab === 'champion'" class="champion-config-content">
<h2 class="game-subtitle">🏆 冠军Logo配置</h2>
<div class="config-section">
<h3 class="text-gold">🎯 战区冠军配置</h3>
<div class="logo-upload-section">
<div class="config-item">
<label class="checkbox-label">
<span>显示类型</span>
<select v-model="championLogos.teamChampionType" class="select-input">
<option value="avatar">头像</option>
<option value="photo">照片</option>
</select>
</label>
</div>
<div class="logo-preview">
<div v-if="championLogos.teamChampion" class="logo-image-container">
<img :src="championLogos.teamChampion" alt="战区冠军" class="logo-preview-image" :style="championLogos.teamChampionType === 'photo' ? { width: championLogos.teamChampionPhotoWidth + 'px', height: championLogos.teamChampionPhotoHeight + 'px', objectFit: 'cover' } : {}">
</div>
<div v-else class="logo-placeholder">
<span>未上传图片</span>
</div>
</div>
<div class="upload-controls">
<input type="file" accept="image/*" @change="(e) => handleChampionLogoUpload(e, 'teamChampion')"
class="logo-input">
<button v-if="championLogos.teamChampion" @click="clearChampionLogo('teamChampion')" class="btn-clear">
清除图片
</button>
</div>
<div v-if="championLogos.teamChampionType === 'avatar'" class="size-control">
<label class="text-gold">头像大小</label>
<input type="number" v-model.number="championLogos.teamChampionSize" min="30" max="200"
class="width-input" placeholder="输入大小(像素)">
<span class="size-unit">px</span>
</div>
<div v-if="championLogos.teamChampionType === 'photo'" class="size-controls">
<div class="size-control">
<label class="text-gold">照片宽度</label>
<input type="number" v-model.number="championLogos.teamChampionPhotoWidth" min="50" max="400"
class="width-input" placeholder="输入宽度(像素)">
<span class="size-unit">px</span>
</div>
<div class="size-control">
<label class="text-gold">照片高度</label>
<input type="number" v-model.number="championLogos.teamChampionPhotoHeight" min="30" max="300"
class="width-input" placeholder="输入高度(像素)">
<span class="size-unit">px</span>
</div>
</div>
<p class="upload-hint">支持JPGPNGGIF格式建议尺寸200x200像素头像或400x300像素照片文件大小不超过5MB</p>
</div>
</div>
<div class="config-section">
<h3 class="text-gold">👤 英雄冠军Logo</h3>
<div class="logo-upload-section">
<div class="logo-preview">
<div v-if="championLogos.individualChampion" class="logo-image-container">
<img :src="championLogos.individualChampion" alt="英雄冠军Logo" class="logo-preview-image">
</div>
<div v-else class="logo-placeholder">
<span>未上传Logo</span>
</div>
</div>
<div class="upload-controls">
<input type="file" accept="image/*" @change="(e) => handleChampionLogoUpload(e, 'individualChampion')"
class="logo-input">
<button v-if="championLogos.individualChampion" @click="clearChampionLogo('individualChampion')"
class="btn-clear">
清除Logo
</button>
</div>
<div class="size-control">
<label class="text-gold">显示大小</label>
<input type="number" v-model.number="championLogos.individualChampionSize" min="30" max="200"
class="width-input" placeholder="输入大小(像素)">
<span class="size-unit">px</span>
</div>
<p class="upload-hint">支持JPGPNGGIF格式建议尺寸200x200像素文件大小不超过5MB</p>
</div>
</div>
<!-- 皇冠配置 -->
<div class="config-section">
<h3 class="text-gold">👑 英雄冠军皇冠配置</h3>
<div class="crown-config-section">
<div class="config-item">
<label class="checkbox-label">
<span>皇冠大小像素</span>
<input type="number" v-model.number="localDisplayConfig.crown.size" min="30" max="300"
class="width-input" placeholder="输入大小(像素)">
<span class="size-unit">px</span>
</label>
</div>
<div class="config-item">
<label class="checkbox-label">
<input type="checkbox" v-model="localDisplayConfig.crown.animationEnabled">
<span>启用皇冠浮动动画效果</span>
</label>
</div>
<p class="upload-hint">皇冠大小默认为英雄冠军头像的1.2建议设置为合适的大小以确保美观</p>
</div>
</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>
<!-- 保存按钮 -->
<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>
<div class="avatar-upload">
<div v-if="individualForm.avatar && individualForm.avatar.startsWith('/')" class="avatar-preview">
<img :src="individualForm.avatar" alt="头像预览"
style="width: 80px; height: 80px; object-fit: cover; border-radius: 50%;">
</div>
<div v-else class="avatar-emoji-preview">
{{ individualForm.avatar }}
</div>
<input type="file" accept="image/*" @change="handleAvatarUpload" class="avatar-input">
<label for="emoji-select">或选择表情:</label>
<select v-model="individualForm.avatar" id="emoji-select" class="form-input"
v-if="!individualForm.avatar.startsWith('/')">
<option value="👑">👑 皇冠</option>
<option value="🥇">🥇 金牌</option>
<option value="🥈">🥈 银牌</option>
<option value="🥉">🥉 铜牌</option>
<option value="⭐"> 星星</option>
<option value="🔥">🔥 火焰</option>
<option value="⚡"> 闪电</option>
<option value="🎯">🎯 靶心</option>
</select>
<button v-if="individualForm.avatar.startsWith('/')" @click="clearAvatar" class="btn-clear">清除头像</button>
</div>
</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>
<select v-model="individualForm.team" class="form-input">
<option value="">请选择战区</option>
<option v-for="team in localTeamRankings" :key="team.id" :value="team.name">
{{ team.name }}
</option>
</select>
</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: 'champion', label: '冠军Logo配置' },
{ key: 'endTime', label: '结束时间设置' },
{ key: 'drum', label: '战鼓配置' }
];
// 冠军Logo配置
const championLogos = ref({
teamChampion: '',
individualChampion: '',
teamChampionSize: 60, // 默认60px
individualChampionSize: 60 // 默认60px
});
// 组件挂载时初始化冠军Logo配置
onMounted(async () => {
try {
await initializeData();
// 重新加载本地数据副本
localIndividualRankings.value = [...individualRankings];
localTeamRankings.value = [...teamRankings];
localBonusRules.value = [...bonusRules];
localDisplayConfig.value = { ...displayConfig };
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);
}
});
// 处理冠军Logo上传
const handleChampionLogoUpload = async (event, type) => {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('image', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
// 删除旧的图片
if (championLogos.value[type] && championLogos.value[type].startsWith('/uploads/')) {
const oldFilename = championLogos.value[type].split('/').pop();
fetch(`/api/upload/${oldFilename}`, {
method: 'DELETE'
}).catch(error => {
console.error('删除旧文件失败:', error);
});
}
championLogos.value[type] = result.filePath;
} else {
alert('上传失败: ' + result.error);
}
} catch (error) {
console.error('上传失败:', error);
alert('上传失败,请重试');
}
// 清空文件输入
event.target.value = '';
};
// 清除冠军Logo
const clearChampionLogo = (type) => {
if (championLogos.value[type] && championLogos.value[type].startsWith('/uploads/')) {
const filename = championLogos.value[type].split('/').pop();
fetch(`/api/upload/${filename}`, {
method: 'DELETE'
}).catch(error => {
console.error('删除文件失败:', error);
});
}
championLogos.value[type] = '';
};
// 刷新数据
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,
team: ''
});
// 处理头像上传
const handleAvatarUpload = async (event) => {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('image', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
individualForm.avatar = result.filePath;
} else {
alert('上传失败: ' + result.error);
}
} catch (error) {
console.error('上传失败:', error);
alert('上传失败,请重试');
}
// 清空文件输入,允许重新选择相同文件
event.target.value = '';
};
// 清除头像
const clearAvatar = () => {
// 如果是图片,尝试删除服务器上的文件
if (individualForm.avatar.startsWith('/uploads/')) {
const filename = individualForm.avatar.split('/').pop();
fetch(`/api/upload/${filename}`, {
method: 'DELETE'
}).catch(error => {
console.error('删除文件失败:', error);
});
}
individualForm.avatar = '⭐';
};
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;
// 保存冠军Logo配置
currentConfig.displayConfig.championLogos = championLogos.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;
individualForm.team = '';
};
// 删除个人
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 lang="scss" scoped>
.btn-edit,
.btn-delete {
padding: 0;
cursor: pointer;
transition: background-color 0.3s;
}
.admin-panel {
min-height: 70vh;
margin-top: 80px;
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;
display: flex;
flex-direction: column;
gap: 15px;
}
.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: 12px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s, box-shadow 0.3s;
min-height: 44px;
/* 增加最小高度,符合可访问性标准 */
}
.form-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
/* 增加聚焦效果 */
}
.form-input:hover {
border-color: #99a5f0;
/* 悬停效果 */
}
.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);
display: flex;
flex-direction: column;
width: 100%;
max-width: 1200px;
margin: 0 auto;
height: 90vh;
}
.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-alignment-section {
margin-top: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
}
.column-alignment-item {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.align-select {
padding: 8px 12px;
border: 1px solid #6c5ce7;
border-radius: 4px;
background: #2d3748;
color: white;
font-size: 0.9rem;
min-width: 100px;
}
.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 120px 1fr 80px 80px 100px 80px 120px;
padding: 15px 10px;
font-weight: bold;
}
.team-rankings .table-header {
grid-template-columns: 60px 2fr 80px 60px 100px 80px 120px;
}
.table-row {
display: grid;
grid-template-columns: 60px 120px 1fr 80px 80px 100px 80px 120px;
}
.team-rankings .table-row {
grid-template-columns: 60px 2fr 80px 60px 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;
}
.avatar-col {
text-align: center;
font-size: 1.2rem;
}
.table-avatar {
width: 30px;
height: 30px;
object-fit: contain;
vertical-align: middle;
}
/* 等级样式 */
.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;
}
/* 头像上传样式 */
.avatar-upload {
display: flex;
flex-direction: column;
gap: 10px;
}
.avatar-preview,
.avatar-emoji-preview {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #f0f0f0;
margin-bottom: 10px;
}
.avatar-emoji-preview {
font-size: 32px;
}
.avatar-input {
margin-bottom: 10px;
}
.btn-clear {
padding: 5px 10px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.8rem;
transition: background-color 0.3s;
}
.btn-clear:hover {
background-color: #c82333;
}
/* Logo上传样式 */
.logo-upload-section {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 30px;
}
.logo-preview {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.logo-image-container {
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed #ccc;
border-radius: 10px;
overflow: hidden;
}
.logo-preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.logo-placeholder {
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed #ccc;
border-radius: 10px;
background-color: #f8f9fa;
color: #6c757d;
}
.upload-controls {
display: flex;
flex-direction: column;
gap: 10px;
}
.logo-input {
border: 1px solid #ced4da;
border-radius: 5px;
padding: 8px;
cursor: pointer;
}
.upload-hint {
font-size: 0.9rem;
color: #6c757d;
margin-top: 10px;
}
/* 操作按钮 */
.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>