chore: 支持上传图片及sass语法

This commit is contained in:
2025-11-13 14:55:45 +08:00
parent d8275a2781
commit 6f842ef328
5 changed files with 1113 additions and 5 deletions

View File

@@ -358,6 +358,53 @@
</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">🎯 战区冠军Logo</h3>
<div class="logo-upload-section">
<div class="logo-preview">
<div v-if="championLogos.teamChampionLogo" class="logo-image-container">
<img :src="championLogos.teamChampionLogo" 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, 'teamChampion')" class="logo-input">
<button v-if="championLogos.teamChampionLogo" @click="clearChampionLogo('teamChampion')" class="btn-clear">
清除Logo
</button>
</div>
<p class="upload-hint">支持JPGPNGGIF格式建议尺寸200x200像素文件大小不超过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.individualChampionLogo" class="logo-image-container">
<img :src="championLogos.individualChampionLogo" 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.individualChampionLogo" @click="clearChampionLogo('individualChampion')" class="btn-clear">
清除Logo
</button>
</div>
<p class="upload-hint">支持JPGPNGGIF格式建议尺寸200x200像素文件大小不超过5MB</p>
</div>
</div>
</div>
<!-- 战鼓配置 -->
<div v-if="currentTab === 'drum'" class="drum-config-content">
<h2 class="game-subtitle">🥁 战鼓配置管理</h2>
@@ -640,6 +687,30 @@
<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">
@@ -742,10 +813,96 @@ const tabs = [
{ key: 'team', label: '战队排名' },
{ key: 'bonus', label: '奖金设置' },
{ key: 'config', label: '显示配置' },
{ key: 'champion', label: '冠军Logo配置' },
{ key: 'endTime', label: '结束时间设置' },
{ key: 'drum', label: '战鼓配置' }
];
// 冠军Logo配置
const championLogos = ref({
teamChampionLogo: '',
individualChampionLogo: ''
});
// 组件挂载时初始化冠军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}Logo`] && championLogos.value[`${type}Logo`].startsWith('/uploads/')) {
const oldFilename = championLogos.value[`${type}Logo`].split('/').pop();
fetch(`/api/upload/${oldFilename}`, {
method: 'DELETE'
}).catch(error => {
console.error('删除旧文件失败:', error);
});
}
championLogos.value[`${type}Logo`] = result.filePath;
} else {
alert('上传失败: ' + result.error);
}
} catch (error) {
console.error('上传失败:', error);
alert('上传失败,请重试');
}
// 清空文件输入
event.target.value = '';
};
// 清除冠军Logo
const clearChampionLogo = (type) => {
if (championLogos.value[`${type}Logo`] && championLogos.value[`${type}Logo`].startsWith('/uploads/')) {
const filename = championLogos.value[`${type}Logo`].split('/').pop();
fetch(`/api/upload/${filename}`, {
method: 'DELETE'
}).catch(error => {
console.error('删除文件失败:', error);
});
}
championLogos.value[`${type}Logo`] = '';
};
// 刷新数据
const handleRefreshData = () => {
try {
@@ -855,6 +1012,49 @@ const individualForm = reactive({
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: '',
@@ -918,6 +1118,8 @@ const saveData = async () => {
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;
@@ -1467,6 +1669,109 @@ const deleteBonusRule = (index) => {
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;