Files
rollingDraw/admin/src/App.vue
2026-01-15 10:38:00 +08:00

1286 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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-container">
<el-container>
<el-header>
<div class="header-content">
<h1>抽奖管理系统</h1>
<div style="display: flex; gap: 10px">
<el-button type="info" @click="showBackgroundDialog = true">
<el-icon><Picture /></el-icon>
背景设置
</el-button>
<el-button type="info" @click="showShortcutGuide = true">
<el-icon><QuestionFilled /></el-icon>
快捷键指南
</el-button>
</div>
</div>
</el-header>
<el-main>
<el-row :gutter="20">
<!-- 左侧名单管理 -->
<el-col :span="8">
<el-card class="card">
<template #header>
<div class="card-header">
<span>名单管理</span>
<div>
<el-button type="info" size="small" @click="showFieldConfig">
字段配置
</el-button>
<el-button type="primary" size="small" @click="showImportDialog = true">
导入
</el-button>
<el-button type="success" size="small" @click="exportParticipants">
导出
</el-button>
</div>
</div>
</template>
<!-- 单个添加表单 -->
<div class="single-add-form">
<el-form :model="newParticipantData" label-width="80px" size="small">
<el-form-item
v-for="field in store.fields"
:key="field.id"
:label="field.label"
:required="field.required"
>
<el-input
v-model="newParticipantData[field.key]"
:placeholder="`输入${field.label}`"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addParticipant" style="width: 100%">
添加
</el-button>
</el-form-item>
</el-form>
</div>
<div class="participant-list">
<div
v-for="person in store.participants"
:key="person.id"
class="participant-item"
>
<div class="participant-card">
<div class="participant-main">
<span class="participant-name">{{ getParticipantDisplay(person) }}</span>
<el-button
type="danger"
size="small"
@click="removeParticipant(person.id)"
>
删除
</el-button>
</div>
<div class="participant-extra" v-if="store.fields.length > 1">
<el-button
type="primary"
size="small"
@click="showParticipantDetail(person)"
>
详情
</el-button>
</div>
</div>
</div>
</div>
<div class="participant-count">
{{ store.participants.length }}
</div>
</el-card>
</el-col>
<!-- 中间奖品管理 -->
<el-col :span="8">
<el-card class="card">
<template #header>
<div class="card-header">
<span>奖品管理</span>
<el-button type="primary" size="small" @click="showPrizeDialog = true">
添加奖品
</el-button>
</div>
</template>
<div class="prize-list">
<div
v-for="prize in store.prizes"
:key="prize.id"
class="prize-item"
>
<div class="prize-info">
<span class="prize-name">{{ prize.name }}</span>
<span class="prize-stock">库存: {{ prize.used }}/{{ prize.stock }}</span>
</div>
<el-button
type="danger"
size="small"
circle
@click="removePrize(prize.id)"
>
<el-icon><Delete /></el-icon>
</el-button>
</div>
</div>
</el-card>
</el-col>
<!-- 右侧轮次管理 -->
<el-col :span="8">
<el-card class="card">
<template #header>
<div class="card-header">
<span>轮次管理</span>
<el-button type="primary" size="small" @click="showRoundDialog = true">
添加轮次
</el-button>
</div>
</template>
<div class="round-list">
<div
v-for="round in store.rounds"
:key="round.id"
class="round-item"
:class="{ 'completed': round.completed }"
>
<div class="round-info">
<span class="round-name">{{ round.name }}</span>
<span class="round-detail">{{ getPrizeName(round.prizeId) }} × {{ round.count }}</span>
</div>
<div class="round-actions">
<el-button
type="danger"
size="small"
circle
@click="removeRound(round.id)"
>
<el-icon><Delete /></el-icon>
</el-button>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 中奖记录 -->
<el-row style="margin-top: 20px">
<el-col :span="24">
<el-card class="card">
<template #header>
<div class="card-header">
<span>中奖记录</span>
<div>
<el-button type="success" size="small" @click="exportWinners">
导出
</el-button>
<el-button type="danger" size="small" @click="resetLottery">
清空
</el-button>
</div>
</div>
</template>
<el-table :data="store.winners" style="width: 100%" stripe>
<el-table-column prop="name" label="姓名" width="150" />
<el-table-column prop="prizeName" label="奖品" width="150" />
<el-table-column prop="roundName" label="轮次" width="150" />
<el-table-column prop="time" label="时间" />
</el-table>
</el-card>
</el-col>
</el-row>
</el-main>
</el-container>
<!-- 字段配置对话框 -->
<el-dialog v-model="showFieldDialog" title="字段配置" width="600px">
<div class="field-config">
<div class="field-list">
<div v-for="field in tempFields" :key="field.id" class="field-item">
<el-input v-model="field.key" placeholder="字段键" size="small" style="width: 120px" />
<el-input v-model="field.label" placeholder="字段名称" size="small" style="width: 120px" />
<el-checkbox v-model="field.required">必填</el-checkbox>
<el-button type="danger" size="small" @click="removeField(field.id)" :disabled="tempFields.length <= 1">删除</el-button>
</div>
</div>
<el-button type="primary" @click="addField" style="margin-top: 15px">添加字段</el-button>
</div>
<template #footer>
<el-button @click="showFieldDialog = false">取消</el-button>
<el-button type="primary" @click="saveFields">保存</el-button>
</template>
</el-dialog>
<!-- 导入对话框 -->
<el-dialog v-model="showImportDialog" title="导入名单" width="500px">
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".csv"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
拖拽文件到此处或 <em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持 .csv 格式文件第一行为字段名后续行为数据
</div>
</template>
</el-upload>
<template #footer>
<el-button @click="showImportDialog = false">取消</el-button>
<el-button type="primary" @click="handleImport" :disabled="!selectedFile">导入</el-button>
</template>
</el-dialog>
<!-- 添加奖品对话框 -->
<el-dialog v-model="showPrizeDialog" title="添加奖品" width="400px">
<el-form :model="newPrize" label-width="80px">
<el-form-item label="奖品名称">
<el-input v-model="newPrize.name" placeholder="例如:一等奖" />
</el-form-item>
<el-form-item label="库存数量">
<el-input-number v-model="newPrize.stock" :min="1" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showPrizeDialog = false">取消</el-button>
<el-button type="primary" @click="addPrize">确定</el-button>
</template>
</el-dialog>
<!-- 添加轮次对话框 -->
<el-dialog v-model="showRoundDialog" title="添加轮次" width="400px">
<el-form :model="newRound" label-width="80px">
<el-form-item label="轮次名称">
<el-input v-model="newRound.name" placeholder="例如:第一轮" />
</el-form-item>
<el-form-item label="选择奖品">
<el-select v-model="newRound.prizeId" placeholder="请选择奖品" style="width: 100%">
<el-option
v-for="prize in store.prizes"
:key="prize.id"
:label="`${prize.name} (剩余: ${prize.stock - prize.used})`"
:value="prize.id"
/>
</el-select>
</el-form-item>
<el-form-item label="抽取人数">
<el-input-number v-model="newRound.count" :min="1" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showRoundDialog = false">取消</el-button>
<el-button type="primary" @click="addRound">确定</el-button>
</template>
</el-dialog>
<!-- 快捷键指南对话框 -->
<el-dialog v-model="showShortcutGuide" title="大屏端快捷键指南" width="500px">
<div class="shortcut-guide">
<div class="shortcut-item">
<span class="shortcut-key"> </span>
<span class="shortcut-desc">切换轮次左右方向键</span>
</div>
<div class="shortcut-item">
<span class="shortcut-key">Space</span>
<span class="shortcut-desc">开始/停止抽奖</span>
</div>
</div>
<template #footer>
<el-button type="primary" @click="showShortcutGuide = false">知道了</el-button>
</template>
</el-dialog>
<!-- 背景图片配置对话框 -->
<el-dialog v-model="showBackgroundDialog" title="大屏端背景图片" width="500px">
<div class="background-config">
<div v-if="store.backgroundImage" class="current-background">
<div class="background-label">当前背景图片</div>
<img :src="store.backgroundImage" class="background-preview" />
<el-button type="danger" @click="clearBackgroundImage" style="margin-top: 10px">
清除背景
</el-button>
</div>
<div class="upload-section">
<div class="background-label">上传新背景</div>
<el-upload
:auto-upload="false"
:on-change="handleBackgroundChange"
:limit="1"
accept="image/*"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
拖拽图片到此处或 <em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持 JPGPNGGIF 等图片格式最大支持10MB
</div>
</template>
</el-upload>
<div v-if="backgroundImagePreview" class="preview-section">
<div class="background-label">预览</div>
<img :src="backgroundImagePreview" class="background-preview" />
</div>
</div>
</div>
<template #footer>
<el-button @click="showBackgroundDialog = false">取消</el-button>
<el-button type="primary" @click="saveBackgroundImage" :disabled="!backgroundImagePreview">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { useLotteryStore } from './store'
import { Delete, UploadFilled, QuestionFilled, Picture } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
const store = useLotteryStore()
// 初始化store
onMounted(async () => {
await store.initialize()
})
// 快捷键指南
const showShortcutGuide = ref(false)
// 背景图片配置
const showBackgroundDialog = ref(false)
const backgroundImageFile = ref(null)
const backgroundImagePreview = ref('')
const handleBackgroundChange = (file) => {
// 检查文件大小限制为10MB
const maxSize = 10 * 1024 * 1024 // 10MB
if (file.raw.size > maxSize) {
ElMessage.error('图片大小不能超过10MB请使用较小的图片')
backgroundImageFile.value = null
backgroundImagePreview.value = ''
return
}
backgroundImageFile.value = file.raw
// 预览图片
const reader = new FileReader()
reader.onload = (e) => {
backgroundImagePreview.value = e.target.result
}
reader.readAsDataURL(file.raw)
}
const saveBackgroundImage = async () => {
if (backgroundImagePreview.value) {
try {
await store.setBackgroundImage(backgroundImagePreview.value)
showBackgroundDialog.value = false
backgroundImagePreview.value = ''
backgroundImageFile.value = null
ElMessage.success('背景图片已设置')
} catch (error) {
ElMessage.error('保存背景图片失败:' + error.message)
}
} else {
ElMessage.warning('请选择图片')
}
}
const clearBackgroundImage = async () => {
try {
await store.clearBackgroundImage()
ElMessage.success('背景图片已清除')
} catch (error) {
ElMessage.error('清除背景图片失败:' + error.message)
}
}
// 字段配置
const showFieldDialog = ref(false)
const tempFields = ref([])
const showFieldConfig = () => {
tempFields.value = JSON.parse(JSON.stringify(store.fields))
showFieldDialog.value = true
}
const addField = () => {
tempFields.value.push({
id: Date.now(),
key: '',
label: '',
required: false
})
}
const removeField = (id) => {
const index = tempFields.value.findIndex(f => f.id === id)
if (index > -1) {
tempFields.value.splice(index, 1)
}
}
const saveFields = () => {
// 验证字段
const validFields = tempFields.value.filter(f => f.key && f.label)
if (validFields.length === 0) {
ElMessage.error('至少需要一个有效字段')
return
}
// 更新字段
store.fields.splice(0, store.fields.length, ...validFields)
showFieldDialog.value = false
ElMessage.success('字段配置已保存')
}
// 名单管理
const newParticipantData = ref({})
const showImportDialog = ref(false)
const uploadRef = ref(null)
const selectedFile = ref(null)
// 初始化新参与者数据
const initNewParticipantData = () => {
newParticipantData.value = {}
store.fields.forEach(field => {
newParticipantData.value[field.key] = ''
})
}
// 监听字段变化,重置表单
watch(() => store.fields, () => {
initNewParticipantData()
}, { deep: true })
// 初始化
initNewParticipantData()
const addParticipant = () => {
// 验证必填字段
const missingFields = store.fields.filter(f => f.required && !newParticipantData.value[f.key]?.trim())
if (missingFields.length > 0) {
ElMessage.error(`请填写必填字段:${missingFields.map(f => f.label).join('、')}`)
return
}
// 验证至少有一个字段有值
const hasValue = Object.values(newParticipantData.value).some(v => v && v.trim())
if (!hasValue) {
ElMessage.error('请至少填写一个字段')
return
}
// 添加参与者
store.addParticipant({
id: Date.now(),
...newParticipantData.value
})
// 重置表单
initNewParticipantData()
ElMessage.success('添加成功')
}
const getParticipantDisplay = (person) => {
// 首先尝试从配置的字段中查找
const nameField = store.fields.find(f => f.key === 'name')
if (nameField && person[nameField.key]) {
return person[nameField.key]
}
// 如果没有 name 字段,尝试从配置的字段中找第一个有值的
const firstFieldWithValue = store.fields.find(f => person[f.key])
if (firstFieldWithValue) {
return person[firstFieldWithValue.key]
}
// 如果配置的字段都没有值,尝试从参与者对象的所有字段中找
const allKeys = Object.keys(person).filter(key => key !== 'id')
if (allKeys.length > 0) {
// 优先找包含 "name" 的字段
const nameKey = allKeys.find(key => key.toLowerCase().includes('name'))
if (nameKey) {
return person[nameKey]
}
// 否则返回第一个字段的值
return person[allKeys[0]]
}
return JSON.stringify(person)
}
const showParticipantDetail = (person) => {
const details = []
// 首先显示配置的字段
store.fields.forEach(field => {
details.push(`${field.label}: ${person[field.key] || '-'}`)
})
// 如果配置的字段和实际数据不匹配,显示所有字段
const allKeys = Object.keys(person).filter(key => key !== 'id')
const configuredKeys = store.fields.map(f => f.key)
const unconfiguredKeys = allKeys.filter(key => !configuredKeys.includes(key))
if (unconfiguredKeys.length > 0) {
details.push('')
details.push('未配置字段:')
unconfiguredKeys.forEach(key => {
details.push(`${key}: ${person[key]}`)
})
}
ElMessage({
message: details.join('\n'),
type: 'info',
duration: 0,
showClose: true
})
}
const removeParticipant = (id) => {
store.removeParticipant(id)
ElMessage.success('删除成功')
}
const handleFileChange = (file) => {
selectedFile.value = file.raw
}
const handleImport = async () => {
try {
if (selectedFile.value) {
const count = await store.importParticipantsFromFile(selectedFile.value)
selectedFile.value = null
uploadRef.value?.clearFiles()
showImportDialog.value = false
ElMessage.success(`成功导入 ${count}`)
} else {
ElMessage.warning('请选择文件')
}
} catch (error) {
ElMessage.error('导入失败:' + error.message)
}
}
const exportParticipants = () => {
store.exportParticipants()
ElMessage.success('导出成功')
}
// 奖品管理
const showPrizeDialog = ref(false)
const newPrize = ref({
name: '',
stock: 1
})
const addPrize = () => {
if (newPrize.value.name.trim()) {
store.addPrize(newPrize.value)
newPrize.value = { name: '', stock: 1 }
showPrizeDialog.value = false
ElMessage.success('添加成功')
}
}
const removePrize = (id) => {
store.removePrize(id)
ElMessage.success('删除成功')
}
// 轮次管理
const showRoundDialog = ref(false)
const newRound = ref({
name: '',
prizeId: null,
count: 1
})
const getPrizeName = (prizeId) => {
const prize = store.prizes.find(p => p.id === prizeId)
return prize ? prize.name : ''
}
const addRound = () => {
if (newRound.value.name.trim() && newRound.value.prizeId) {
store.addRound(newRound.value)
newRound.value = { name: '', prizeId: null, count: 1 }
showRoundDialog.value = false
ElMessage.success('添加成功')
} else {
ElMessage.warning('请填写完整信息')
}
}
const removeRound = (id) => {
store.removeRound(id)
ElMessage.success('删除成功')
}
// 重置抽奖
const resetLottery = () => {
store.resetLottery()
ElMessage.success('已重置')
}
// 导出中奖名单
const exportWinners = () => {
if (store.winners.length === 0) {
ElMessage.warning('暂无中奖记录')
return
}
store.exportWinners()
ElMessage.success('导出成功')
}
</script>
<style scoped>
.admin-container {
min-height: 100vh;
background: var(--color-background);
font-family: var(--font-family-primary);
}
.el-header {
background: var(--color-background);
border-bottom: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--spacing-2xl);
box-shadow: var(--shadow-small);
position: relative;
z-index: 10;
height: var(--header-height);
}
.el-header::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: var(--color-secondary);
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 1400px;
}
.el-header h1 {
margin: 0;
font-family: var(--font-family-secondary);
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.el-header .el-button {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-primary);
transition: var(--transition-color), var(--transition-background);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
border-radius: var(--border-radius-md);
padding: var(--spacing-md) var(--spacing-xl);
}
.el-header .el-button:hover {
background: rgba(var(--color-primary-rgb), 0.1);
border-color: var(--color-primary);
color: var(--color-primary);
transform: none;
}
.el-main {
padding: var(--spacing-3xl);
max-width: 1400px;
margin: 0 auto;
width: 100%;
}
.card {
height: 100%;
margin-bottom: var(--spacing-2xl);
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-small);
transition: var(--transition-box-shadow);
border: 1px solid var(--color-border);
}
.card:hover {
box-shadow: var(--shadow-medium);
transform: none;
}
.shortcut-card {
background: var(--color-primary);
}
.shortcut-card :deep(.el-card__header) {
background: rgba(255, 255, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.shortcut-card :deep(.el-card__header span) {
color: var(--color-text-white);
font-weight: var(--font-weight-bold);
font-family: var(--font-family-secondary);
}
.shortcut-card :deep(.el-card__body) {
background: rgba(255, 255, 255, 0.05);
}
.shortcut-guide {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: var(--spacing-3xl);
padding: var(--spacing-2xl) 0;
}
.shortcut-item {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--spacing-xl);
min-width: 140px;
}
.shortcut-key {
background: var(--color-secondary);
color: var(--color-text-white);
font-family: var(--font-family-secondary);
font-weight: var(--font-weight-bold);
font-size: var(--font-size-3xl);
margin-bottom: var(--spacing-md);
box-shadow: 0 4px 12px rgba(var(--color-secondary-rgb), 0.3);
padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--border-radius-lg);
border: 2px solid rgba(255, 255, 255, 0.2);
min-width: 80px;
text-align: center;
}
.shortcut-desc {
color: var(--color-text-light);
font-size: var(--font-size-base);
text-align: center;
line-height: var(--line-height-relaxed);
font-family: var(--font-family-primary);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--font-family-primary);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-lg);
color: var(--color-text-primary);
}
.participant-list {
max-height: 300px;
overflow-y: auto;
margin-bottom: var(--spacing-md);
}
.participant-item {
margin: var(--spacing-md) 0;
}
.participant-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg);
background: var(--color-border-light);
border-radius: var(--border-radius-md);
transition: var(--transition-background), var(--transition-border), var(--transition-box-shadow);
border: 1px solid transparent;
}
.participant-card:hover {
background: var(--color-background);
border-color: var(--color-secondary);
box-shadow: var(--shadow-small);
transform: none;
}
.participant-card:hover .participant-extra :deep(.el-button--danger) {
background: var(--color-danger);
color: var(--color-text-white);
border-color: var(--color-danger);
}
.participant-card:hover .participant-extra :deep(.el-button--primary) {
background: var(--color-primary);
color: var(--color-text-white);
border-color: var(--color-primary);
}
.participant-main {
display: flex;
align-items: center;
gap: var(--spacing-md);
flex: 1;
}
.participant-name {
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
font-family: var(--font-family-primary);
flex: 1;
}
.participant-extra {
display: flex;
gap: var(--spacing-sm);
}
.participant-extra :deep(.el-button) {
min-width: 60px;
}
.participant-list::-webkit-scrollbar {
width: 6px;
}
.participant-list::-webkit-scrollbar-track {
background: var(--color-border);
border-radius: var(--border-radius-sm);
}
.participant-list::-webkit-scrollbar-thumb {
background: var(--color-text-light);
border-radius: var(--border-radius-sm);
}
.participant-list::-webkit-scrollbar-thumb:hover {
background: var(--color-text-secondary);
}
.single-add-form {
margin-bottom: var(--spacing-lg);
padding: var(--spacing-lg);
background: var(--color-border-light);
border-radius: var(--border-radius-md);
border: 1px solid var(--color-border);
}
.single-add-form :deep(.el-form-item) {
margin-bottom: var(--spacing-md);
}
.single-add-form :deep(.el-form-item__label) {
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
font-family: var(--font-family-primary);
}
.single-add-form :deep(.el-button) {
margin-top: var(--spacing-sm);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
}
.participant-count {
text-align: right;
color: var(--color-text-light);
font-size: var(--font-size-sm);
padding-top: var(--spacing-md);
border-top: 1px solid var(--color-border);
margin-top: var(--spacing-md);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
}
.prize-list,
.round-list {
max-height: 400px;
overflow-y: auto;
}
.prize-list::-webkit-scrollbar,
.round-list::-webkit-scrollbar {
width: 6px;
}
.prize-list::-webkit-scrollbar-track,
.round-list::-webkit-scrollbar-track {
background: var(--color-border);
border-radius: var(--border-radius-sm);
}
.prize-list::-webkit-scrollbar-thumb,
.round-list::-webkit-scrollbar-thumb {
background: var(--color-text-light);
border-radius: var(--border-radius-sm);
}
.prize-list::-webkit-scrollbar-thumb:hover,
.round-list::-webkit-scrollbar-thumb:hover {
background: var(--color-text-secondary);
}
.prize-item,
.round-item {
padding: var(--spacing-lg);
margin-bottom: var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
transition: var(--transition-border), var(--transition-box-shadow);
background: var(--color-background);
}
.prize-item:hover,
.round-item:hover {
border-color: var(--color-secondary);
box-shadow: var(--shadow-small);
transform: none;
}
.round-item.completed {
opacity: 0.6;
background: var(--color-border-light);
}
.prize-info,
.round-info {
display: flex;
flex-direction: column;
margin-bottom: var(--spacing-md);
}
.prize-name,
.round-name {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
margin-bottom: var(--spacing-xs);
font-family: var(--font-family-primary);
color: var(--color-text-primary);
}
.prize-stock,
.round-detail {
color: var(--color-text-light);
font-size: var(--font-size-sm);
font-family: var(--font-family-primary);
}
.prize-actions,
.round-actions {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.prize-actions .el-button,
.round-actions .el-button {
flex: 1;
}
.field-config {
padding: var(--spacing-md);
}
.field-list {
max-height: 400px;
overflow-y: auto;
margin-bottom: var(--spacing-lg);
}
.field-list::-webkit-scrollbar {
width: 6px;
}
.field-list::-webkit-scrollbar-track {
background: var(--color-border);
border-radius: var(--border-radius-sm);
}
.field-list::-webkit-scrollbar-thumb {
background: var(--color-text-light);
border-radius: var(--border-radius-sm);
}
.field-list::-webkit-scrollbar-thumb:hover {
background: var(--color-text-secondary);
}
.field-item {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
padding: var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
transition: var(--transition-border), var(--transition-box-shadow);
background: var(--color-background);
}
.field-item:hover {
border-color: var(--color-secondary);
box-shadow: var(--shadow-small);
transform: none;
}
/* 表格样式优化 */
:deep(.el-table) {
border-radius: var(--border-radius-md);
overflow: hidden;
font-family: var(--font-family-primary);
}
:deep(.el-table th) {
background: var(--color-background);
color: var(--color-text-primary);
font-weight: var(--font-weight-semibold);
font-family: var(--font-family-secondary);
border-bottom: 1px solid var(--color-border);
}
:deep(.el-table td) {
color: var(--color-text-primary);
border-bottom: 1px solid var(--color-border);
font-size: var(--font-size-base);
}
:deep(.el-table .el-table__row:hover > td) {
background: rgba(var(--color-primary-rgb), 0.05);
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
background: rgba(var(--color-primary-rgb), 0.03);
}
/* 对话框样式优化 */
:deep(.el-dialog) {
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-large);
}
:deep(.el-dialog__header) {
background: var(--color-primary);
color: var(--color-text-white);
padding: var(--spacing-xl) var(--spacing-2xl);
}
:deep(.el-dialog__title) {
font-family: var(--font-family-secondary);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-lg);
}
:deep(.el-dialog__body) {
padding: var(--spacing-2xl);
}
:deep(.el-dialog__footer) {
padding: var(--spacing-lg) var(--spacing-2xl);
border-top: 1px solid var(--color-border);
}
/* 按钮样式优化 */
:deep(.el-button--primary) {
background: var(--color-primary);
border-color: var(--color-primary);
color: var(--color-text-white);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
}
:deep(.el-button--primary:hover) {
background: var(--color-accent);
border-color: var(--color-accent);
transform: none;
}
:deep(.el-button--success) {
background: var(--color-success);
border-color: var(--color-success);
color: var(--color-text-white);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
}
:deep(.el-button--success:hover) {
background: rgb(0, 148, 127);
border-color: rgb(0, 148, 127);
transform: none;
}
:deep(.el-button--danger) {
background: var(--color-danger);
border-color: var(--color-danger);
color: var(--color-text-white);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
}
:deep(.el-button--danger:hover) {
background: rgb(197, 48, 63);
border-color: rgb(197, 48, 63);
transform: none;
}
:deep(.el-button--info) {
background: var(--color-info);
border-color: var(--color-info);
color: var(--color-text-white);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
}
:deep(.el-button--info:hover) {
background: rgb(20, 146, 165);
border-color: rgb(20, 146, 165);
transform: none;
}
/* 表单样式优化 */
:deep(.el-form-item__label) {
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
:deep(.el-input__wrapper) {
border-radius: var(--border-radius-md);
border: 1px solid var(--color-border);
background: var(--color-background);
transition: var(--transition-border), var(--transition-box-shadow);
box-shadow: none;
}
:deep(.el-input__wrapper:hover) {
border-color: var(--color-primary);
box-shadow: none;
}
:deep(.el-input__wrapper.is-focus) {
border-color: var(--color-primary);
box-shadow: var(--shadow-small);
}
:deep(.el-select .el-input__wrapper) {
border-radius: var(--border-radius-md);
box-shadow: none;
}
:deep(.el-textarea__inner) {
border-radius: var(--border-radius-md);
border: 1px solid var(--color-border);
background: var(--color-background);
transition: var(--transition-border), var(--transition-box-shadow);
font-family: var(--font-family-primary);
font-size: var(--font-size-base);
color: var(--color-text-primary);
line-height: var(--line-height-relaxed);
box-shadow: none;
}
:deep(.el-textarea__inner:hover) {
border-color: var(--color-primary);
box-shadow: none;
}
:deep(.el-textarea__inner:focus) {
border-color: var(--color-primary);
box-shadow: var(--shadow-small);
}
:deep(.el-input__inner::placeholder),
:deep(.el-textarea__inner::placeholder) {
color: var(--color-text-light);
}
/* 上传组件样式优化 */
:deep(.el-upload-dragger) {
border-radius: var(--border-radius-lg);
border: 2px dashed var(--color-border);
transition: var(--transition-border), var(--transition-background);
background: var(--color-background);
}
:deep(.el-upload-dragger:hover) {
border-color: var(--color-primary);
background: rgba(var(--color-primary-rgb), 0.05);
}
/* 背景图片配置样式 */
.background-config {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.current-background {
padding: var(--spacing-lg);
background: var(--color-border-light);
border-radius: var(--border-radius-md);
border: 1px solid var(--color-border);
}
.upload-section {
padding: var(--spacing-lg);
background: var(--color-border-light);
border-radius: var(--border-radius-md);
border: 1px solid var(--color-border);
}
.background-label {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-md);
}
.background-preview {
width: 100%;
max-width: 400px;
max-height: 250px;
object-fit: cover;
border-radius: var(--border-radius-md);
border: 1px solid var(--color-border);
}
.preview-section {
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--color-border);
}
</style>