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;

View File

@@ -55,7 +55,8 @@
<!-- 战区冠军 -->
<div class="team-champion">
<div class="team-logo">
🏅
<img v-if="localDisplayConfig.championLogos?.teamChampion" :src="localDisplayConfig.championLogos.teamChampion" alt="战区冠军" class="champion-logo">
<span v-else>🏅</span>
</div>
<div class="champion-name">
{{ teamRankings[0]?.name || '暂无冠军' }}
@@ -98,7 +99,9 @@
<!-- 英雄冠军 -->
<div class="individual-champion">
<div class="individual-avatar">
{{ individualRankings[0]?.avatar || '👤' }}
<img v-if="individualRankings[0]?.avatar && individualRankings[0].avatar.startsWith('/')" :src="individualRankings[0].avatar" alt="冠军头像" class="avatar-image avatar-image-champion">
<img v-else-if="localDisplayConfig.championLogos?.individualChampion" :src="localDisplayConfig.championLogos.individualChampion" alt="英雄冠军" class="champion-logo">
<span v-else>{{ individualRankings[0]?.avatar || '👤' }}</span>
</div>
<div class="champion-name">
{{ individualRankings[0]?.name || '暂无冠军' }}
@@ -129,7 +132,10 @@
}"
>
<span class="rank-col">{{ index + 1 }}</span>
<span class="avatar-col">{{ item.avatar }}</span>
<span class="avatar-col">
<img v-if="item.avatar && item.avatar.startsWith('/')" :src="item.avatar" alt="头像" class="avatar-image">
<span v-else>{{ item.avatar }}</span>
</span>
<span class="name-col">{{ item.name }}</span>
<span v-if="localDisplayConfig.individual?.showTeam" class="team-col">{{ item.team || '-' }}</span>
<span class="score-col">{{ localDisplayConfig.individual?.scoreColumn?.displayStyle === 'amount' ? '¥' + item.score : item.score }}</span>
@@ -849,7 +855,12 @@ onUnmounted(() => {
});
</script>
<style scoped>
<style lang="scss" scoped>
.avatar-image-champion {
width: 120px;
}
/* 冠军模块样式 */
.champion-section {
display: flex;
@@ -882,6 +893,19 @@ onUnmounted(() => {
display: inline-block;
}
.champion-logo {
width: 60px;
height: 60px;
object-fit: contain;
}
.avatar-image {
width: 30px;
height: 30px;
object-fit: contain;
vertical-align: middle;
}
.champion-title {
font-size: 0.9rem;
color: #999;