You've already forked rollingDraw
新增: 新增大屏端显示设置和性能优化
This commit is contained in:
@@ -14,6 +14,13 @@ export const useLotteryStore = defineStore('lottery', () => {
|
|||||||
const displayMode = ref('scroll')
|
const displayMode = ref('scroll')
|
||||||
const backgroundImage = ref('')
|
const backgroundImage = ref('')
|
||||||
const columnsPerRow = ref(3) // 每行显示的人数
|
const columnsPerRow = ref(3) // 每行显示的人数
|
||||||
|
const displayFontColor = ref('#FFFFFF') // 大屏端字体颜色
|
||||||
|
const prizeNameFontColor = ref('#00B4D8') // 奖品名称字体颜色
|
||||||
|
const roundNameFontSize = ref(36) // 轮次名称字体大小
|
||||||
|
const prizeNameFontSize = ref(64) // 奖品名称字体大小
|
||||||
|
const participantFontSize = ref(32) // 人名字体大小
|
||||||
|
const displayFields = ref([]) // 大屏端显示的字段列表
|
||||||
|
const showFieldLabels = ref(true) // 是否显示字段标签
|
||||||
const isInitialized = ref(false)
|
const isInitialized = ref(false)
|
||||||
|
|
||||||
// 从IndexedDB初始化数据
|
// 从IndexedDB初始化数据
|
||||||
@@ -35,6 +42,13 @@ export const useLotteryStore = defineStore('lottery', () => {
|
|||||||
displayMode.value = data.lottery_displayMode || 'scroll'
|
displayMode.value = data.lottery_displayMode || 'scroll'
|
||||||
backgroundImage.value = data.lottery_backgroundImage || ''
|
backgroundImage.value = data.lottery_backgroundImage || ''
|
||||||
columnsPerRow.value = data.lottery_columnsPerRow || 3
|
columnsPerRow.value = data.lottery_columnsPerRow || 3
|
||||||
|
displayFontColor.value = data.lottery_displayFontColor || '#FFFFFF'
|
||||||
|
prizeNameFontColor.value = data.lottery_prizeNameFontColor || '#00B4D8'
|
||||||
|
roundNameFontSize.value = data.lottery_roundNameFontSize || 36
|
||||||
|
prizeNameFontSize.value = data.lottery_prizeNameFontSize || 64
|
||||||
|
participantFontSize.value = data.lottery_participantFontSize || 32
|
||||||
|
displayFields.value = data.lottery_displayFields || []
|
||||||
|
showFieldLabels.value = data.lottery_showFieldLabels !== undefined ? data.lottery_showFieldLabels : true
|
||||||
|
|
||||||
console.log('Initialized background image:', backgroundImage.value ? 'Yes' : 'No')
|
console.log('Initialized background image:', backgroundImage.value ? 'Yes' : 'No')
|
||||||
console.log('Initialization completed')
|
console.log('Initialization completed')
|
||||||
@@ -392,10 +406,18 @@ export const useLotteryStore = defineStore('lottery', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exportWinners = () => {
|
const exportWinners = () => {
|
||||||
const headers = ['姓名', '奖品', '轮次', '时间']
|
// 动态生成表头
|
||||||
const rows = winners.value.map(w => [w.name, w.prizeName, w.roundName, w.time])
|
const headers = fields.value.map(f => f.label)
|
||||||
|
headers.push('奖品', '轮次', '时间')
|
||||||
|
|
||||||
const csv = [headers.join(','), ...rows].join('\n')
|
// 动态生成数据行
|
||||||
|
const rows = winners.value.map(w => {
|
||||||
|
const participantFields = fields.value.map(f => w.participant ? (w.participant[f.key] || '') : '')
|
||||||
|
participantFields.push(w.prizeName, w.roundName, w.time)
|
||||||
|
return participantFields
|
||||||
|
})
|
||||||
|
|
||||||
|
const csv = [headers.join(','), ...rows.map(row => row.join(','))].join('\n')
|
||||||
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' })
|
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' })
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
@@ -438,6 +460,41 @@ export const useLotteryStore = defineStore('lottery', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setDisplayFontColor = async (color) => {
|
||||||
|
displayFontColor.value = color
|
||||||
|
await saveData('lottery_displayFontColor', displayFontColor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPrizeNameFontColor = async (color) => {
|
||||||
|
prizeNameFontColor.value = color
|
||||||
|
await saveData('lottery_prizeNameFontColor', prizeNameFontColor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setRoundNameFontSize = async (size) => {
|
||||||
|
roundNameFontSize.value = size
|
||||||
|
await saveData('lottery_roundNameFontSize', roundNameFontSize.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPrizeNameFontSize = async (size) => {
|
||||||
|
prizeNameFontSize.value = size
|
||||||
|
await saveData('lottery_prizeNameFontSize', prizeNameFontSize.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setParticipantFontSize = async (size) => {
|
||||||
|
participantFontSize.value = size
|
||||||
|
await saveData('lottery_participantFontSize', participantFontSize.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDisplayFields = async (fields) => {
|
||||||
|
displayFields.value = fields
|
||||||
|
await saveData('lottery_displayFields', displayFields.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setShowFieldLabels = async (show) => {
|
||||||
|
showFieldLabels.value = show
|
||||||
|
await saveData('lottery_showFieldLabels', showFieldLabels.value)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
fields,
|
fields,
|
||||||
@@ -450,6 +507,13 @@ export const useLotteryStore = defineStore('lottery', () => {
|
|||||||
displayMode,
|
displayMode,
|
||||||
backgroundImage,
|
backgroundImage,
|
||||||
columnsPerRow,
|
columnsPerRow,
|
||||||
|
displayFontColor,
|
||||||
|
prizeNameFontColor,
|
||||||
|
roundNameFontSize,
|
||||||
|
prizeNameFontSize,
|
||||||
|
participantFontSize,
|
||||||
|
displayFields,
|
||||||
|
showFieldLabels,
|
||||||
isInitialized,
|
isInitialized,
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
@@ -491,6 +555,13 @@ export const useLotteryStore = defineStore('lottery', () => {
|
|||||||
switchDisplayMode,
|
switchDisplayMode,
|
||||||
setColumnsPerRow,
|
setColumnsPerRow,
|
||||||
setBackgroundImage,
|
setBackgroundImage,
|
||||||
clearBackgroundImage
|
clearBackgroundImage,
|
||||||
|
setDisplayFontColor,
|
||||||
|
setPrizeNameFontColor,
|
||||||
|
setRoundNameFontSize,
|
||||||
|
setPrizeNameFontSize,
|
||||||
|
setParticipantFontSize,
|
||||||
|
setDisplayFields,
|
||||||
|
setShowFieldLabels
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
/* Element Plus Custom Styles */
|
/* Element Plus Custom Styles - 基于参考图设计风格 */
|
||||||
|
|
||||||
/* Element Plus Variables Override */
|
/* Element Plus Variables Override */
|
||||||
:root {
|
:root {
|
||||||
--el-color-primary: var(--color-primary);
|
--el-color-primary: var(--color-primary);
|
||||||
--el-color-primary-light-3: #8fa3f7;
|
--el-color-primary-light-3: rgba(var(--color-primary-rgb), 0.3);
|
||||||
--el-color-primary-light-5: #b8c5f8;
|
--el-color-primary-light-5: rgba(var(--color-primary-rgb), 0.5);
|
||||||
--el-color-primary-light-7: #dce0fa;
|
--el-color-primary-light-7: rgba(var(--color-primary-rgb), 0.7);
|
||||||
--el-color-primary-light-8: #ebeefc;
|
--el-color-primary-light-8: rgba(var(--color-primary-rgb), 0.8);
|
||||||
--el-color-primary-light-9: #f5f6fe;
|
--el-color-primary-light-9: rgba(var(--color-primary-rgb), 0.9);
|
||||||
--el-color-primary-dark-2: #5369d6;
|
--el-color-primary-dark-2: var(--color-primary-dark);
|
||||||
|
|
||||||
--el-color-success: var(--color-success);
|
--el-color-success: var(--color-success);
|
||||||
--el-color-warning: var(--color-warning);
|
--el-color-warning: var(--color-warning);
|
||||||
--el-color-danger: var(--color-danger);
|
--el-color-danger: var(--color-danger);
|
||||||
--el-color-info: var(--color-info);
|
--el-color-info: var(--color-info);
|
||||||
|
|
||||||
--el-border-radius-base: var(--border-radius-md);
|
--el-border-radius-base: var(--border-radius-sm);
|
||||||
--el-border-radius-small: var(--border-radius-sm);
|
--el-border-radius-small: 4px;
|
||||||
--el-border-radius-round: 20px;
|
--el-border-radius-round: 20px;
|
||||||
--el-border-radius-circle: 100%;
|
--el-border-radius-circle: 100%;
|
||||||
|
|
||||||
@@ -32,54 +32,74 @@
|
|||||||
.el-button {
|
.el-button {
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-sm);
|
||||||
transition: all var(--transition-normal);
|
transition: all var(--transition-normal);
|
||||||
}
|
|
||||||
|
|
||||||
.el-button:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: var(--shadow-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-button:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-button--primary {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
|
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-button:hover {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button:active {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--primary {
|
||||||
|
background: var(--color-info);
|
||||||
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
.el-button--primary:hover {
|
.el-button--primary:hover {
|
||||||
background: linear-gradient(135deg, var(--color-primary-dark-2) 0%, var(--color-secondary) 100%);
|
background: #1565C0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button--success {
|
.el-button--success {
|
||||||
background: var(--color-success);
|
background: var(--color-success);
|
||||||
border: none;
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--success:hover {
|
||||||
|
background: #27AE60;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button--danger {
|
.el-button--danger {
|
||||||
background: var(--color-danger);
|
background: var(--color-danger);
|
||||||
border: none;
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--danger:hover {
|
||||||
|
background: #C0392B;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button--warning {
|
.el-button--warning {
|
||||||
background: var(--color-warning);
|
background: var(--color-warning);
|
||||||
border: none;
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--warning:hover {
|
||||||
|
background: #F39C12;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button--info {
|
.el-button--info {
|
||||||
background: var(--color-info);
|
background: var(--color-background-tertiary);
|
||||||
border: none;
|
color: var(--color-text-primary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--info:hover {
|
||||||
|
background: #E9ECEF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Element Plus Card Customization */
|
/* Element Plus Card Customization */
|
||||||
.el-card {
|
.el-card {
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
transition: all var(--transition-normal);
|
transition: all var(--transition-normal);
|
||||||
border: 1px solid var(--color-border);
|
border: none;
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-card:hover {
|
.el-card:hover {
|
||||||
@@ -89,9 +109,10 @@
|
|||||||
.el-card__header {
|
.el-card__header {
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
padding: var(--spacing-lg) var(--spacing-xl);
|
padding: var(--spacing-lg) var(--spacing-xl);
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-card__body {
|
.el-card__body {
|
||||||
@@ -100,19 +121,19 @@
|
|||||||
|
|
||||||
/* Element Plus Dialog Customization */
|
/* Element Plus Dialog Customization */
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-large);
|
box-shadow: var(--shadow-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog__header {
|
.el-dialog__header {
|
||||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
|
background: var(--color-background);
|
||||||
color: var(--color-text-white);
|
color: var(--color-text-primary);
|
||||||
padding: var(--spacing-xl) var(--spacing-2xl);
|
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||||
border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog__title {
|
.el-dialog__title {
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
}
|
}
|
||||||
@@ -128,10 +149,11 @@
|
|||||||
|
|
||||||
/* Element Plus Input Customization */
|
/* Element Plus Input Customization */
|
||||||
.el-input__wrapper {
|
.el-input__wrapper {
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-sm);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
transition: all var(--transition-normal);
|
transition: all var(--transition-normal);
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input__wrapper:hover {
|
.el-input__wrapper:hover {
|
||||||
@@ -151,7 +173,7 @@
|
|||||||
|
|
||||||
/* Element Plus Select Customization */
|
/* Element Plus Select Customization */
|
||||||
.el-select .el-input__wrapper {
|
.el-select .el-input__wrapper {
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select-dropdown__item {
|
.el-select-dropdown__item {
|
||||||
@@ -186,12 +208,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-table th {
|
.el-table th {
|
||||||
background: var(--color-background);
|
background: var(--color-background-secondary);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-primary);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
border-bottom: 2px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-table td {
|
.el-table td {
|
||||||
@@ -201,11 +223,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-table .el-table__row:hover > td {
|
.el-table .el-table__row:hover > td {
|
||||||
background: rgba(var(--color-primary-rgb), 0.05);
|
background: var(--color-background-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-table--striped .el-table__body tr.el-table__row--striped td {
|
.el-table--striped .el-table__body tr.el-table__row--striped td {
|
||||||
background: rgba(var(--color-primary-rgb), 0.03);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-table__body tr.current-row > td {
|
.el-table__body tr.current-row > td {
|
||||||
@@ -214,7 +236,7 @@
|
|||||||
|
|
||||||
/* Element Plus Upload Customization */
|
/* Element Plus Upload Customization */
|
||||||
.el-upload-dragger {
|
.el-upload-dragger {
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-md);
|
||||||
border: 2px dashed var(--color-border);
|
border: 2px dashed var(--color-border);
|
||||||
transition: all var(--transition-normal);
|
transition: all var(--transition-normal);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
@@ -233,7 +255,7 @@
|
|||||||
|
|
||||||
/* Element Plus Message Customization */
|
/* Element Plus Message Customization */
|
||||||
.el-message {
|
.el-message {
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-large);
|
box-shadow: var(--shadow-large);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
@@ -246,7 +268,7 @@
|
|||||||
|
|
||||||
/* Element Plus Message Box Customization */
|
/* Element Plus Message Box Customization */
|
||||||
.el-message-box {
|
.el-message-box {
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-large);
|
box-shadow: var(--shadow-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +278,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-message-box__title {
|
.el-message-box__title {
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
}
|
}
|
||||||
@@ -304,37 +326,33 @@
|
|||||||
|
|
||||||
/* Element Plus Tag Customization */
|
/* Element Plus Tag Customization */
|
||||||
.el-tag {
|
.el-tag {
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-sm);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tag--primary {
|
.el-tag--primary {
|
||||||
background: rgba(var(--color-primary-rgb), 0.1);
|
background: var(--color-info-light);
|
||||||
color: var(--color-primary);
|
color: var(--color-info);
|
||||||
border-color: var(--color-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tag--success {
|
.el-tag--success {
|
||||||
background: rgba(var(--color-success), 0.1);
|
background: var(--color-success-light);
|
||||||
color: var(--color-success);
|
color: var(--color-success);
|
||||||
border-color: var(--color-success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tag--warning {
|
.el-tag--warning {
|
||||||
background: rgba(var(--color-warning), 0.1);
|
background: var(--color-warning-light);
|
||||||
color: var(--color-warning);
|
color: var(--color-warning);
|
||||||
border-color: var(--color-warning);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tag--danger {
|
.el-tag--danger {
|
||||||
background: rgba(var(--color-danger), 0.1);
|
background: var(--color-danger-light);
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
border-color: var(--color-danger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tag--info {
|
.el-tag--info {
|
||||||
background: rgba(var(--color-info), 0.1);
|
background: rgba(var(--color-primary-rgb), 0.1);
|
||||||
color: var(--color-info);
|
color: var(--color-primary);
|
||||||
border-color: var(--color-info);
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Global Styles */
|
/* Global Styles - 基于参考图设计风格 */
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -17,7 +17,7 @@ body {
|
|||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
line-height: var(--line-height-normal);
|
line-height: var(--line-height-normal);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
background: var(--color-background);
|
background: var(--color-background-secondary);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
@@ -25,12 +25,17 @@ body {
|
|||||||
#app {
|
#app {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar Styling */
|
.el-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styling - 更符合参考图的轻量级设计 */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 6px;
|
||||||
height: 8px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@@ -49,19 +54,36 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Typography */
|
/* Typography */
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-secondary);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
line-height: var(--line-height-tight);
|
line-height: var(--line-height-tight);
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 { font-size: var(--font-size-4xl); }
|
h1 {
|
||||||
h2 { font-size: var(--font-size-3xl); }
|
font-size: var(--font-size-4xl);
|
||||||
h3 { font-size: var(--font-size-2xl); }
|
}
|
||||||
h4 { font-size: var(--font-size-xl); }
|
h2 {
|
||||||
h5 { font-size: var(--font-size-lg); }
|
font-size: var(--font-size-3xl);
|
||||||
h6 { font-size: var(--font-size-base); }
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-bottom: var(--spacing-md);
|
margin-bottom: var(--spacing-md);
|
||||||
@@ -76,11 +98,12 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--color-secondary);
|
color: var(--color-primary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lists */
|
/* Lists */
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin-bottom: var(--spacing-md);
|
margin-bottom: var(--spacing-md);
|
||||||
padding-left: var(--spacing-2xl);
|
padding-left: var(--spacing-2xl);
|
||||||
}
|
}
|
||||||
@@ -121,7 +144,8 @@ table {
|
|||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
@@ -133,7 +157,7 @@ th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr:hover {
|
tr:hover {
|
||||||
background: var(--color-border-light);
|
background: var(--color-background-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Forms */
|
/* Forms */
|
||||||
@@ -167,75 +191,197 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
.text-center { text-align: center; }
|
.text-center {
|
||||||
.text-left { text-align: left; }
|
text-align: center;
|
||||||
.text-right { text-align: right; }
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.text-primary { color: var(--color-primary); }
|
.text-primary {
|
||||||
.text-secondary { color: var(--color-secondary); }
|
color: var(--color-primary);
|
||||||
.text-success { color: var(--color-success); }
|
}
|
||||||
.text-danger { color: var(--color-danger); }
|
.text-secondary {
|
||||||
.text-warning { color: var(--color-warning); }
|
color: var(--color-secondary);
|
||||||
.text-light { color: var(--color-text-light); }
|
}
|
||||||
|
.text-success {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
.text-danger {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
.text-warning {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
.text-light {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-primary { background: var(--color-primary); }
|
.bg-primary {
|
||||||
.bg-secondary { background: var(--color-secondary); }
|
background: var(--color-primary);
|
||||||
.bg-success { background: var(--color-success); }
|
}
|
||||||
.bg-danger { background: var(--color-danger); }
|
.bg-secondary {
|
||||||
.bg-warning { background: var(--color-warning); }
|
background: var(--color-secondary);
|
||||||
|
}
|
||||||
|
.bg-success {
|
||||||
|
background: var(--color-success);
|
||||||
|
}
|
||||||
|
.bg-danger {
|
||||||
|
background: var(--color-danger);
|
||||||
|
}
|
||||||
|
.bg-warning {
|
||||||
|
background: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
.mt-0 { margin-top: 0; }
|
.mt-0 {
|
||||||
.mt-1 { margin-top: var(--spacing-xs); }
|
margin-top: 0;
|
||||||
.mt-2 { margin-top: var(--spacing-sm); }
|
}
|
||||||
.mt-3 { margin-top: var(--spacing-md); }
|
.mt-1 {
|
||||||
.mt-4 { margin-top: var(--spacing-lg); }
|
margin-top: var(--spacing-xs);
|
||||||
.mt-5 { margin-top: var(--spacing-xl); }
|
}
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
.mt-5 {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.mb-0 { margin-bottom: 0; }
|
.mb-0 {
|
||||||
.mb-1 { margin-bottom: var(--spacing-xs); }
|
margin-bottom: 0;
|
||||||
.mb-2 { margin-bottom: var(--spacing-sm); }
|
}
|
||||||
.mb-3 { margin-bottom: var(--spacing-md); }
|
.mb-1 {
|
||||||
.mb-4 { margin-bottom: var(--spacing-lg); }
|
margin-bottom: var(--spacing-xs);
|
||||||
.mb-5 { margin-bottom: var(--spacing-xl); }
|
}
|
||||||
|
.mb-2 {
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
.mb-5 {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.p-0 { padding: 0; }
|
.p-0 {
|
||||||
.p-1 { padding: var(--spacing-xs); }
|
padding: 0;
|
||||||
.p-2 { padding: var(--spacing-sm); }
|
}
|
||||||
.p-3 { padding: var(--spacing-md); }
|
.p-1 {
|
||||||
.p-4 { padding: var(--spacing-lg); }
|
padding: var(--spacing-xs);
|
||||||
.p-5 { padding: var(--spacing-xl); }
|
}
|
||||||
|
.p-2 {
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
.p-3 {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
.p-4 {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.d-flex { display: flex; }
|
.d-flex {
|
||||||
.d-inline-flex { display: inline-flex; }
|
display: flex;
|
||||||
.d-block { display: block; }
|
}
|
||||||
.d-inline-block { display: inline-block; }
|
.d-inline-flex {
|
||||||
.d-none { display: none; }
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.d-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.d-inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.d-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-row { flex-direction: row; }
|
.flex-row {
|
||||||
.flex-column { flex-direction: column; }
|
flex-direction: row;
|
||||||
.flex-wrap { flex-wrap: wrap; }
|
}
|
||||||
.flex-nowrap { flex-wrap: nowrap; }
|
.flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.flex-nowrap {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.justify-start { justify-content: flex-start; }
|
.justify-start {
|
||||||
.justify-center { justify-content: center; }
|
justify-content: flex-start;
|
||||||
.justify-end { justify-content: flex-end; }
|
}
|
||||||
.justify-between { justify-content: space-between; }
|
.justify-center {
|
||||||
.justify-around { justify-content: space-around; }
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.justify-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
.align-start { align-items: flex-start; }
|
.align-start {
|
||||||
.align-center { align-items: center; }
|
align-items: flex-start;
|
||||||
.align-end { align-items: flex-end; }
|
}
|
||||||
.align-stretch { align-items: stretch; }
|
.align-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.align-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
.align-stretch {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-0 { gap: 0; }
|
.gap-0 {
|
||||||
.gap-1 { gap: var(--spacing-xs); }
|
gap: 0;
|
||||||
.gap-2 { gap: var(--spacing-sm); }
|
}
|
||||||
.gap-3 { gap: var(--spacing-md); }
|
.gap-1 {
|
||||||
.gap-4 { gap: var(--spacing-lg); }
|
gap: var(--spacing-xs);
|
||||||
.gap-5 { gap: var(--spacing-xl); }
|
}
|
||||||
|
.gap-2 {
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
.gap-3 {
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
.gap-4 {
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
.gap-5 {
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.w-100 { width: 100%; }
|
.w-100 {
|
||||||
.h-100 { height: 100%; }
|
width: 100%;
|
||||||
.position-relative { position: relative; }
|
}
|
||||||
.position-absolute { position: absolute; }
|
.h-100 {
|
||||||
.position-fixed { position: fixed; }
|
height: 100%;
|
||||||
|
}
|
||||||
|
.position-relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.position-absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.position-fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +1,62 @@
|
|||||||
/* CSS Variables */
|
/* CSS Variables - 基于参考图设计风格 */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Colors */
|
/* Colors */
|
||||||
--color-primary: #667eea;
|
/* 主色 - 青绿色 */
|
||||||
--color-primary-rgb: 102, 126, 234;
|
--color-primary: #00C897;
|
||||||
--color-secondary: #764ba2;
|
--color-primary-rgb: 0, 200, 151;
|
||||||
--color-secondary-rgb: 118, 75, 162;
|
--color-primary-dark: #00A67C;
|
||||||
--color-accent: #f093fb;
|
|
||||||
--color-accent-rgb: 240, 147, 251;
|
|
||||||
|
|
||||||
--color-success: #00b894;
|
/* 辅助色 */
|
||||||
--color-danger: #e74c3c;
|
--color-secondary: #00B4D8;
|
||||||
--color-info: #14b8a6;
|
--color-secondary-rgb: 0, 180, 216;
|
||||||
--color-warning: #f39c12;
|
|
||||||
|
|
||||||
--color-background: #f5f7fa;
|
/* 功能色 */
|
||||||
--color-border: #e2e8f0;
|
--color-success: #2ECC71;
|
||||||
--color-border-light: #f8fafc;
|
--color-success-rgb: 46, 204, 113;
|
||||||
|
--color-success-light: #E8F5E9;
|
||||||
|
|
||||||
--color-text-primary: #2d3748;
|
--color-danger: #E74C3C;
|
||||||
--color-text-secondary: #4a5568;
|
--color-danger-rgb: 231, 76, 60;
|
||||||
--color-text-light: #718096;
|
--color-danger-light: #FDE8E8;
|
||||||
--color-text-white: #ffffff;
|
|
||||||
|
--color-info: #1877F2;
|
||||||
|
--color-info-rgb: 24, 119, 242;
|
||||||
|
--color-info-light: #E3F2FD;
|
||||||
|
|
||||||
|
--color-warning: #FFA500;
|
||||||
|
--color-warning-rgb: 255, 165, 0;
|
||||||
|
--color-warning-light: #FFF3E0;
|
||||||
|
|
||||||
|
/* 背景色 */
|
||||||
|
--color-background: #FFFFFF;
|
||||||
|
--color-background-secondary: #FAFAFA;
|
||||||
|
--color-background-tertiary: #F8F9FA;
|
||||||
|
|
||||||
|
/* 深色导航栏 */
|
||||||
|
--color-sidebar-bg: #221E1E;
|
||||||
|
--color-header-bg: #221E1E;
|
||||||
|
|
||||||
|
/* 边框色 */
|
||||||
|
--color-border: #E9ECEF;
|
||||||
|
--color-border-light: #F0F0F0;
|
||||||
|
|
||||||
|
/* 文字颜色 */
|
||||||
|
--color-text-primary: #333333;
|
||||||
|
--color-text-secondary: #666666;
|
||||||
|
--color-text-light: #6C757D;
|
||||||
|
--color-text-white: #FFFFFF;
|
||||||
|
--color-text-disabled: #999999;
|
||||||
|
|
||||||
/* Spacing */
|
/* Spacing */
|
||||||
--spacing-xs: 4px;
|
--spacing-xs: 4px;
|
||||||
--spacing-sm: 8px;
|
--spacing-sm: 8px;
|
||||||
--spacing-md: 12px;
|
--spacing-md: 12px;
|
||||||
--spacing-lg: 16px;
|
--spacing-lg: 16px;
|
||||||
--spacing-xl: 24px;
|
--spacing-xl: 20px;
|
||||||
--spacing-2xl: 32px;
|
--spacing-2xl: 24px;
|
||||||
--spacing-3xl: 48px;
|
--spacing-3xl: 32px;
|
||||||
--spacing-4xl: 64px;
|
--spacing-4xl: 48px;
|
||||||
|
|
||||||
/* Typography */
|
/* Typography */
|
||||||
--font-family-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
--font-family-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
@@ -60,10 +86,10 @@
|
|||||||
--border-radius-lg: 12px;
|
--border-radius-lg: 12px;
|
||||||
--border-radius-xl: 16px;
|
--border-radius-xl: 16px;
|
||||||
|
|
||||||
/* Shadows */
|
/* Shadows - 轻量级阴影 */
|
||||||
--shadow-small: 0 1px 3px rgba(0, 0, 0, 0.1);
|
--shadow-small: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
--shadow-medium: 0 4px 6px rgba(0, 0, 0, 0.1);
|
--shadow-medium: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
--shadow-large: 0 10px 25px rgba(0, 0, 0, 0.15);
|
--shadow-large: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
|
||||||
/* Transitions */
|
/* Transitions */
|
||||||
--transition-fast: 150ms;
|
--transition-fast: 150ms;
|
||||||
@@ -76,7 +102,7 @@
|
|||||||
--transition-box-shadow: box-shadow var(--transition-normal) ease;
|
--transition-box-shadow: box-shadow var(--transition-normal) ease;
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
--header-height: 64px;
|
--header-height: 60px;
|
||||||
--sidebar-width: 240px;
|
--sidebar-width: 240px;
|
||||||
--content-max-width: 1200px;
|
--content-max-width: 1400px;
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 显示设置对话框 -->
|
<!-- 显示设置对话框 -->
|
||||||
<el-dialog v-model="showDisplaySettingsDialog" title="大屏端显示设置" width="400px">
|
<el-dialog v-model="showDisplaySettingsDialog" title="大屏端显示设置" width="800px">
|
||||||
<el-form label-width="120px">
|
<el-form label-width="120px">
|
||||||
<el-form-item label="每行显示人数">
|
<el-form-item label="每行显示人数">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@@ -131,6 +131,71 @@
|
|||||||
设置大屏端名单每行显示的人数,建议值:2-5
|
设置大屏端名单每行显示的人数,建议值:2-5
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="字体颜色">
|
||||||
|
<el-color-picker v-model="tempDisplayFontColor" />
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
设置大屏端文字颜色(轮次名称、人名)
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="奖品名称颜色">
|
||||||
|
<el-color-picker v-model="tempPrizeNameFontColor" />
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
设置奖品名称的文字颜色
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="轮次名称字号">
|
||||||
|
<el-input-number
|
||||||
|
v-model="tempRoundNameFontSize"
|
||||||
|
:min="16"
|
||||||
|
:max="120"
|
||||||
|
:step="1"
|
||||||
|
/>
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
设置轮次名称的字体大小,建议值:28-48
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="奖品名称字号">
|
||||||
|
<el-input-number
|
||||||
|
v-model="tempPrizeNameFontSize"
|
||||||
|
:min="16"
|
||||||
|
:max="120"
|
||||||
|
:step="1"
|
||||||
|
/>
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
设置奖品名称的字体大小,建议值:48-80
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="人名字号">
|
||||||
|
<el-input-number
|
||||||
|
v-model="tempParticipantFontSize"
|
||||||
|
:min="16"
|
||||||
|
:max="72"
|
||||||
|
:step="1"
|
||||||
|
/>
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
设置人名的字体大小,建议值:24-48
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示字段">
|
||||||
|
<el-checkbox-group v-model="tempDisplayFields">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="field in store.fields"
|
||||||
|
:key="field.key"
|
||||||
|
:label="field.key"
|
||||||
|
>
|
||||||
|
{{ field.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
选择在大屏端显示的字段,至少选择一个
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示字段标签">
|
||||||
|
<el-switch v-model="tempShowFieldLabels" />
|
||||||
|
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||||
|
开启后显示"姓名: 张三"格式,关闭后只显示"张三"
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="showDisplaySettingsDialog = false">取消</el-button>
|
<el-button @click="showDisplaySettingsDialog = false">取消</el-button>
|
||||||
@@ -169,15 +234,42 @@ const showShortcutGuide = ref(false)
|
|||||||
// 显示设置
|
// 显示设置
|
||||||
const showDisplaySettingsDialog = ref(false)
|
const showDisplaySettingsDialog = ref(false)
|
||||||
const tempColumnsPerRow = ref(3)
|
const tempColumnsPerRow = ref(3)
|
||||||
|
const tempDisplayFontColor = ref('#FFFFFF')
|
||||||
|
const tempPrizeNameFontColor = ref('#00B4D8')
|
||||||
|
const tempRoundNameFontSize = ref(36)
|
||||||
|
const tempPrizeNameFontSize = ref(64)
|
||||||
|
const tempParticipantFontSize = ref(32)
|
||||||
|
const tempDisplayFields = ref([])
|
||||||
|
const tempShowFieldLabels = ref(true)
|
||||||
|
|
||||||
const openDisplaySettings = () => {
|
const openDisplaySettings = () => {
|
||||||
tempColumnsPerRow.value = store.columnsPerRow
|
tempColumnsPerRow.value = store.columnsPerRow
|
||||||
|
tempDisplayFontColor.value = store.displayFontColor
|
||||||
|
tempPrizeNameFontColor.value = store.prizeNameFontColor
|
||||||
|
tempRoundNameFontSize.value = store.roundNameFontSize
|
||||||
|
tempPrizeNameFontSize.value = store.prizeNameFontSize
|
||||||
|
tempParticipantFontSize.value = store.participantFontSize
|
||||||
|
tempDisplayFields.value = store.displayFields.length > 0 ? [...store.displayFields] : store.fields.map(f => f.key)
|
||||||
|
tempShowFieldLabels.value = store.showFieldLabels
|
||||||
showDisplaySettingsDialog.value = true
|
showDisplaySettingsDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveDisplaySettings = async () => {
|
const saveDisplaySettings = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 验证至少选择一个字段
|
||||||
|
if (tempDisplayFields.value.length === 0) {
|
||||||
|
ElMessage.error('请至少选择一个显示字段')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await store.setColumnsPerRow(tempColumnsPerRow.value)
|
await store.setColumnsPerRow(tempColumnsPerRow.value)
|
||||||
|
await store.setDisplayFontColor(tempDisplayFontColor.value)
|
||||||
|
await store.setPrizeNameFontColor(tempPrizeNameFontColor.value)
|
||||||
|
await store.setRoundNameFontSize(tempRoundNameFontSize.value)
|
||||||
|
await store.setPrizeNameFontSize(tempPrizeNameFontSize.value)
|
||||||
|
await store.setParticipantFontSize(tempParticipantFontSize.value)
|
||||||
|
await store.setDisplayFields(tempDisplayFields.value)
|
||||||
|
await store.setShowFieldLabels(tempShowFieldLabels.value)
|
||||||
showDisplaySettingsDialog.value = false
|
showDisplaySettingsDialog.value = false
|
||||||
ElMessage.success('显示设置已保存')
|
ElMessage.success('显示设置已保存')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -242,8 +334,8 @@ const clearBackgroundImage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-header {
|
.el-header {
|
||||||
background: var(--color-background);
|
background: var(--color-header-bg);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid transparent;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -255,13 +347,7 @@ const clearBackgroundImage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-header::before {
|
.el-header::before {
|
||||||
content: '';
|
display: none;
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 2px;
|
|
||||||
background: var(--color-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
@@ -276,30 +362,30 @@ const clearBackgroundImage = async () => {
|
|||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-secondary);
|
||||||
font-size: var(--font-size-2xl);
|
font-size: var(--font-size-2xl);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-header .el-button {
|
.el-header .el-button {
|
||||||
background: transparent;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-white);
|
||||||
transition: var(--transition-color), var(--transition-background);
|
transition: var(--transition-color), var(--transition-background);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-sm);
|
||||||
padding: var(--spacing-md) var(--spacing-xl);
|
padding: var(--spacing-md) var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-header .el-button:hover {
|
.el-header .el-button:hover {
|
||||||
background: rgba(var(--color-primary-rgb), 0.1);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border-color: var(--color-primary);
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
color: var(--color-primary);
|
color: var(--color-text-white);
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-aside {
|
.el-aside {
|
||||||
background: var(--color-background);
|
background: var(--color-sidebar-bg);
|
||||||
border-right: 1px solid var(--color-border);
|
border-right: 1px solid transparent;
|
||||||
padding: var(--spacing-lg) 0;
|
padding: var(--spacing-lg) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,14 +397,14 @@ const clearBackgroundImage = async () => {
|
|||||||
.admin-menu .el-menu-item {
|
.admin-menu .el-menu-item {
|
||||||
margin: var(--spacing-sm) var(--spacing-lg);
|
margin: var(--spacing-sm) var(--spacing-lg);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-white);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-menu .el-menu-item:hover {
|
.admin-menu .el-menu-item:hover {
|
||||||
background: rgba(var(--color-primary-rgb), 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
color: var(--color-primary);
|
color: var(--color-text-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-menu .el-menu-item.is-active {
|
.admin-menu .el-menu-item.is-active {
|
||||||
@@ -331,7 +417,7 @@ const clearBackgroundImage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-main {
|
.el-main {
|
||||||
padding: var(--spacing-3xl);
|
padding: var(--spacing-2xl);
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,52 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="display-container" @keydown="handleKeydown" tabindex="0" :style="{ backgroundImage: store.backgroundImage ? `url(${store.backgroundImage})` : 'none' }">
|
||||||
class="display-container"
|
|
||||||
@keydown="handleKeydown"
|
|
||||||
tabindex="0"
|
|
||||||
:style="{ backgroundImage: store.backgroundImage ? `url(${store.backgroundImage})` : 'none' }"
|
|
||||||
>
|
|
||||||
<!-- 快捷键提示 -->
|
|
||||||
<div class="shortcut-hints" v-if="!store.isRolling">
|
|
||||||
<div class="hint-item">
|
|
||||||
<span class="key">← →</span>
|
|
||||||
<span class="desc">切换轮次</span>
|
|
||||||
</div>
|
|
||||||
<div class="hint-item">
|
|
||||||
<span class="key">Space</span>
|
|
||||||
<span class="desc">开始/停止</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 当前轮次信息 -->
|
<!-- 当前轮次信息 -->
|
||||||
<div class="current-round-info" v-if="selectedRound">
|
<div class="current-round-info" :class="{ centered: store.displayMode === 'scroll' && !store.isRolling }" v-if="selectedRound">
|
||||||
<div class="round-name">{{ selectedRound.name }}</div>
|
<div class="round-name">{{ selectedRound.name }}</div>
|
||||||
<div class="prize-name">{{ getPrizeName(selectedRound.prizeId) }}</div>
|
<div class="prize-name">{{ getPrizeName(selectedRound.prizeId) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 滚动模式 -->
|
<!-- 滚动模式 -->
|
||||||
<div v-if="store.displayMode === 'scroll'" class="scroll-mode">
|
<div v-if="store.displayMode === 'scroll' && store.isRolling" class="scroll-mode">
|
||||||
<div class="scroll-item" :class="{ 'highlight': true }">
|
<div class="scroll-list" :style="{ 'grid-template-columns': `repeat(${store.columnsPerRow}, 1fr)` }">
|
||||||
{{ getParticipantName(displayList[highlightIndex]) }}
|
<div v-for="(participant, index) in getDisplayParticipants()" :key="participant.id || index" class="scroll-item">
|
||||||
</div>
|
<div class="scroll-name">{{ getParticipantName(participant) }}</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 抽奖结果模式 -->
|
|
||||||
<div v-else class="result-mode">
|
|
||||||
<div class="winners-list" :style="{ 'grid-template-columns': `repeat(${store.columnsPerRow}, 1fr)` }">
|
|
||||||
<div
|
|
||||||
v-for="(winner, index) in currentWinners"
|
|
||||||
:key="winner.id || index"
|
|
||||||
class="winner-item"
|
|
||||||
:style="{ animationDelay: `${index * 0.2}s` }"
|
|
||||||
>
|
|
||||||
<div class="winner-name">{{ getParticipantName(winner.participant) }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 状态指示器 -->
|
<!-- 抽奖结果模式 -->
|
||||||
<div class="status-indicator" :class="{ 'active': store.isRolling }">
|
<div v-else-if="store.displayMode === 'result'" class="result-mode">
|
||||||
{{ store.isRolling ? '抽奖中...' : '等待抽奖' }}
|
<div class="winners-list" :style="{ 'grid-template-columns': `repeat(${store.columnsPerRow}, 1fr)` }">
|
||||||
|
<div v-for="(winner, index) in currentWinners" :key="winner.id || index" class="winner-item">
|
||||||
|
<div class="winner-name">{{ getParticipantName(winner.participant) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -66,12 +41,12 @@ const displayList = computed(() => {
|
|||||||
return store.participants
|
return store.participants
|
||||||
})
|
})
|
||||||
|
|
||||||
const getPrizeName = (prizeId) => {
|
const getPrizeName = prizeId => {
|
||||||
const prize = store.prizes.find(p => p.id === prizeId)
|
const prize = store.prizes.find(p => p.id === prizeId)
|
||||||
return prize ? prize.name : ''
|
return prize ? prize.name : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const getParticipantName = (person) => {
|
const getParticipantName = person => {
|
||||||
if (!person) return '无数据'
|
if (!person) return '无数据'
|
||||||
if (typeof person === 'string') return person
|
if (typeof person === 'string') return person
|
||||||
|
|
||||||
@@ -83,16 +58,27 @@ const getParticipantName = (person) => {
|
|||||||
// 收集所有配置字段的值
|
// 收集所有配置字段的值
|
||||||
const displayParts = []
|
const displayParts = []
|
||||||
|
|
||||||
store.fields.forEach(field => {
|
// 只显示选中的字段
|
||||||
const value = person[field.key]
|
const fieldsToDisplay = store.displayFields.length > 0 ? store.displayFields : store.fields.map(f => f.key)
|
||||||
if (value && value.trim()) {
|
|
||||||
displayParts.push(`${field.label}: ${value}`)
|
fieldsToDisplay.forEach(fieldKey => {
|
||||||
|
const field = store.fields.find(f => f.key === fieldKey)
|
||||||
|
if (field) {
|
||||||
|
const value = person[field.key]
|
||||||
|
if (value && value.trim()) {
|
||||||
|
// 根据设置决定是否显示字段标签
|
||||||
|
if (store.showFieldLabels) {
|
||||||
|
displayParts.push(`${field.label}: ${value}`)
|
||||||
|
} else {
|
||||||
|
displayParts.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果有配置字段的值,返回所有字段
|
// 如果有配置字段的值,返回所有字段
|
||||||
if (displayParts.length > 0) {
|
if (displayParts.length > 0) {
|
||||||
return displayParts.join(' | ')
|
return displayParts.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果配置的字段都没有值,尝试从参与者对象的所有字段中找
|
// 如果配置的字段都没有值,尝试从参与者对象的所有字段中找
|
||||||
@@ -111,19 +97,25 @@ const getParticipantName = (person) => {
|
|||||||
return '无数据'
|
return '无数据'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getParticipantDetails = (person) => {
|
const getDisplayParticipants = () => {
|
||||||
// 已经在 getParticipantName 中显示所有字段,这里返回空
|
const count = selectedRound.value ? selectedRound.value.count : store.columnsPerRow || 1
|
||||||
return ''
|
const startIndex = highlightIndex.value
|
||||||
|
const participants = []
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const index = (startIndex + i) % displayList.value.length
|
||||||
|
participants.push(displayList.value[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
return participants
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentWinners = computed(() => {
|
const currentWinners = computed(() => {
|
||||||
if (!selectedRound.value) return []
|
if (!selectedRound.value) return []
|
||||||
return store.winners
|
return store.winners.filter(w => w.roundId === selectedRound.value.id).sort((a, b) => new Date(a.time) - new Date(b.time))
|
||||||
.filter(w => w.roundId === selectedRound.value.id)
|
|
||||||
.sort((a, b) => new Date(a.time) - new Date(b.time))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleKeydown = (e) => {
|
const handleKeydown = e => {
|
||||||
// 空格键:开始/停止抽奖
|
// 空格键:开始/停止抽奖
|
||||||
if (e.code === 'Space') {
|
if (e.code === 'Space') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -155,7 +147,7 @@ const handleKeydown = (e) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectRound = (round) => {
|
const selectRound = round => {
|
||||||
selectedRound.value = round
|
selectedRound.value = round
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +170,8 @@ const startScroll = () => {
|
|||||||
if (highlightInterval) clearInterval(highlightInterval)
|
if (highlightInterval) clearInterval(highlightInterval)
|
||||||
|
|
||||||
highlightInterval = setInterval(() => {
|
highlightInterval = setInterval(() => {
|
||||||
highlightIndex.value = (highlightIndex.value + 1) % displayList.value.length
|
const step = selectedRound.value ? selectedRound.value.count : store.columnsPerRow || 1
|
||||||
|
highlightIndex.value = (highlightIndex.value + step) % displayList.value.length
|
||||||
}, 80)
|
}, 80)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,83 +239,37 @@ onUnmounted(() => {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-container::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-container > * {
|
.display-container > * {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 快捷键提示 */
|
|
||||||
.shortcut-hints {
|
|
||||||
position: fixed;
|
|
||||||
top: var(--spacing-xl);
|
|
||||||
left: var(--spacing-xl);
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-lg);
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint-item {
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: var(--border-radius-md);
|
|
||||||
padding: var(--spacing-md) var(--spacing-lg);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.key {
|
|
||||||
background: var(--color-secondary);
|
|
||||||
color: var(--color-text-white);
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
min-width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 2px 8px rgba(var(--color-secondary-rgb), 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.desc {
|
|
||||||
color: var(--color-text-white);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-family: var(--font-family-primary);
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 当前轮次信息 */
|
/* 当前轮次信息 */
|
||||||
.current-round-info {
|
.current-round-info {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: var(--spacing-4xl);
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-round-info.centered {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
margin-bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-name {
|
.round-name {
|
||||||
font-size: var(--font-size-4xl);
|
font-size: v-bind('store.roundNameFontSize + "px"');
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: bold;
|
||||||
color: var(--color-text-white);
|
color: v-bind('store.displayFontColor');
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: 15px;
|
||||||
text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
|
|
||||||
font-family: var(--font-family-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-name {
|
.prize-name {
|
||||||
font-size: 64px;
|
font-size: v-bind('store.prizeNameFontSize + "px"');
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: bold;
|
||||||
color: var(--color-secondary);
|
color: v-bind('store.prizeNameFontColor');
|
||||||
text-shadow: 0 0 30px rgba(var(--color-secondary-rgb), 0.8);
|
|
||||||
font-family: var(--font-family-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动模式 */
|
/* 滚动模式 */
|
||||||
@@ -334,33 +281,34 @@ onUnmounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 var(--spacing-4xl);
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 30px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-item {
|
.scroll-item {
|
||||||
font-size: 64px;
|
font-size: v-bind('store.participantFontSize + "px"');
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: bold;
|
||||||
color: var(--color-text-white);
|
color: v-bind('store.displayFontColor');
|
||||||
padding: var(--spacing-2xl) var(--spacing-4xl);
|
padding: 20px 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: normal;
|
background: rgba(255, 255, 255, 0.15);
|
||||||
transition: var(--transition-normal);
|
border-radius: 12px;
|
||||||
font-family: var(--font-family-secondary);
|
display: flex;
|
||||||
line-height: 1.4;
|
align-items: center;
|
||||||
word-wrap: break-word;
|
justify-content: center;
|
||||||
word-break: break-all;
|
|
||||||
background: rgba(var(--color-secondary-rgb), 0.2);
|
|
||||||
border-radius: var(--border-radius-xl);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 2px solid rgba(var(--color-secondary-rgb), 0.5);
|
|
||||||
text-shadow: 0 0 30px rgba(var(--color-secondary-rgb), 0.8);
|
|
||||||
max-width: 1200px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-item.highlight {
|
.scroll-name {
|
||||||
transform: scale(1.05);
|
white-space: pre-line;
|
||||||
background: rgba(var(--color-secondary-rgb), 0.4);
|
word-wrap: break-word;
|
||||||
border-color: rgba(var(--color-secondary-rgb), 0.8);
|
word-break: break-all;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 结果模式 */
|
/* 结果模式 */
|
||||||
@@ -371,84 +319,34 @@ onUnmounted(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0 var(--spacing-4xl);
|
padding: 0 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.winners-list {
|
.winners-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: var(--spacing-2xl);
|
gap: 30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1600px;
|
max-width: 1600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.winner-item {
|
.winner-item {
|
||||||
font-size: 32px;
|
font-size: v-bind('store.participantFontSize + "px"');
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: bold;
|
||||||
color: var(--color-text-white);
|
color: v-bind('store.displayFontColor');
|
||||||
padding: var(--spacing-xl) var(--spacing-2xl);
|
padding: 20px 30px;
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
border-radius: var(--border-radius-xl);
|
border-radius: 12px;
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
||||||
animation: fadeInUp 0.6s ease forwards;
|
|
||||||
opacity: 0;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
font-family: var(--font-family-secondary);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.4;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.winner-name {
|
.winner-name {
|
||||||
|
white-space: pre-line;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(30px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 状态指示器 */
|
|
||||||
.status-indicator {
|
|
||||||
position: fixed;
|
|
||||||
bottom: var(--spacing-4xl);
|
|
||||||
right: var(--spacing-4xl);
|
|
||||||
padding: var(--spacing-lg) var(--spacing-3xl);
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 50px;
|
|
||||||
color: var(--color-text-white);
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
transition: var(--transition-normal);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
font-family: var(--font-family-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator.active {
|
|
||||||
background: rgba(var(--color-secondary-rgb), 0.3);
|
|
||||||
animation: pulse 1.5s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,84 +1,226 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-card class="card">
|
<div class="participants-container">
|
||||||
<template #header>
|
<!-- 页面标题和操作按钮 -->
|
||||||
<div class="card-header">
|
<div class="page-header">
|
||||||
<span>名单管理</span>
|
<h2 class="page-title">名单管理</h2>
|
||||||
<div>
|
<div class="action-buttons">
|
||||||
<el-button type="info" size="small" @click="showFieldConfig"> 字段配置 </el-button>
|
<el-button type="info" size="default" @click="showFieldConfig">
|
||||||
<el-button type="primary" size="small" @click="showImportDialog = true"> 导入 </el-button>
|
<el-icon><Setting /></el-icon>
|
||||||
<el-button type="success" size="small" @click="exportParticipants"> 导出 </el-button>
|
字段配置
|
||||||
<el-button type="danger" size="small" @click="clearParticipants"> 清空 </el-button>
|
</el-button>
|
||||||
</div>
|
<el-button type="primary" size="default" @click="showImportDialog = true">
|
||||||
|
<el-icon><Upload /></el-icon>
|
||||||
|
导入
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" size="default" @click="exportParticipants">
|
||||||
|
<el-icon><Download /></el-icon>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" size="default" @click="clearParticipants">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
清空
|
||||||
|
</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>
|
||||||
|
|
||||||
<!-- 参与者表格 -->
|
<!-- 统计信息 -->
|
||||||
<div class="participant-table">
|
<div class="stats-card">
|
||||||
<el-table :data="store.participants" style="width: 100%" stripe max-height="400">
|
<div class="stat-item">
|
||||||
<el-table-column v-for="field in store.fields" :key="field.id" :prop="field.key" :label="field.label" :min-width="120" show-overflow-tooltip />
|
<div class="stat-icon" style="background: var(--color-primary-light)">
|
||||||
<el-table-column label="操作" fixed="right" width="150" align="center">
|
<el-icon><User /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ store.participants.length }}</div>
|
||||||
|
<div class="stat-label">总人数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 单个添加表单 -->
|
||||||
|
<el-card class="form-card" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
添加参与者
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form :model="newParticipantData" label-width="150px" size="default">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="10" v-for="field in store.fields" :key="field.id">
|
||||||
|
<el-form-item :label="field.label" :required="field.required">
|
||||||
|
<el-input
|
||||||
|
v-model="newParticipantData[field.key]"
|
||||||
|
:placeholder="`请输入${field.label}`"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="addParticipant" size="default">
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
添加
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="initNewParticipantData" size="default">
|
||||||
|
<el-icon><RefreshLeft /></el-icon>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 参与者列表 -->
|
||||||
|
<el-card class="list-card" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<el-icon><List /></el-icon>
|
||||||
|
参与者列表
|
||||||
|
</span>
|
||||||
|
<span class="card-subtitle">共 {{ store.participants.length }} 人</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table
|
||||||
|
:data="paginatedParticipants"
|
||||||
|
style="width: 100%"
|
||||||
|
stripe
|
||||||
|
max-height="500"
|
||||||
|
:empty-text="'暂无参与者,请先添加或导入'"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
v-for="field in store.fields"
|
||||||
|
:key="field.id"
|
||||||
|
:prop="field.key"
|
||||||
|
:label="field.label"
|
||||||
|
:min-width="120"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" fixed="right" width="180" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" size="small" @click="showParticipantDetail(row)" v-if="store.fields.length > 1"> 详情 </el-button>
|
<el-button
|
||||||
<el-button type="danger" size="small" @click="removeParticipant(row.id)"> 删除 </el-button>
|
type="info"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="showParticipantDetail(row)"
|
||||||
|
v-if="store.fields.length > 1"
|
||||||
|
>
|
||||||
|
<el-icon><View /></el-icon>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="removeParticipant(row.id)"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="participant-count"> 共 {{ store.participants.length }} 人 </div>
|
<!-- 分页 -->
|
||||||
</el-card>
|
<div class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
<!-- 字段配置对话框 -->
|
v-model:current-page="currentPage"
|
||||||
<el-dialog v-model="showFieldDialog" title="字段配置" width="600px">
|
v-model:page-size="pageSize"
|
||||||
<div class="field-config">
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
<div class="field-list">
|
:total="store.participants.length"
|
||||||
<div v-for="field in tempFields" :key="field.id" class="field-item">
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
<el-input v-model="field.key" placeholder="字段键" size="small" style="width: 120px" />
|
@size-change="handleSizeChange"
|
||||||
<el-input v-model="field.label" placeholder="字段名称" size="small" style="width: 120px" />
|
@current-change="handlePageChange"
|
||||||
<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>
|
</div>
|
||||||
<el-button type="primary" @click="addField" style="margin-top: 15px">添加字段</el-button>
|
</el-card>
|
||||||
</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-dialog v-model="showFieldDialog" title="字段配置" width="600px">
|
||||||
<el-upload ref="uploadRef" :auto-upload="false" :on-change="handleFileChange" :limit="1" accept=".csv" drag>
|
<div class="field-config">
|
||||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
<div class="field-list">
|
||||||
<div class="el-upload__text"> 拖拽文件到此处或 <em>点击上传</em> </div>
|
<div v-for="field in tempFields" :key="field.id" class="field-item">
|
||||||
<template #tip>
|
<el-input
|
||||||
<div class="el-upload__tip"> 支持 .csv 格式文件,第一行为字段名,后续行为数据 </div>
|
v-model="field.key"
|
||||||
|
placeholder="字段键"
|
||||||
|
size="default"
|
||||||
|
style="width: 140px"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-model="field.label"
|
||||||
|
placeholder="字段名称"
|
||||||
|
size="default"
|
||||||
|
style="width: 140px"
|
||||||
|
/>
|
||||||
|
<el-checkbox v-model="field.required">必填</el-checkbox>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="removeField(field.id)"
|
||||||
|
:disabled="tempFields.length <= 1"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" @click="addField" style="margin-top: 15px; width: 100%">
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
添加字段
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showFieldDialog = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveFields">保存</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-upload>
|
</el-dialog>
|
||||||
<template #footer>
|
|
||||||
<el-button @click="showImportDialog = false">取消</el-button>
|
<!-- 导入对话框 -->
|
||||||
<el-button type="primary" @click="handleImport" :disabled="!selectedFile">导入</el-button>
|
<el-dialog v-model="showImportDialog" title="导入名单" width="500px">
|
||||||
</template>
|
<el-upload
|
||||||
</el-dialog>
|
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>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import { useLotteryStore } from '../../store'
|
import { useLotteryStore } from '../../store'
|
||||||
import { UploadFilled } from '@element-plus/icons-vue'
|
import {
|
||||||
|
UploadFilled,
|
||||||
|
Setting,
|
||||||
|
Upload,
|
||||||
|
Download,
|
||||||
|
Delete,
|
||||||
|
User,
|
||||||
|
Plus,
|
||||||
|
Check,
|
||||||
|
RefreshLeft,
|
||||||
|
List,
|
||||||
|
View
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const store = useLotteryStore()
|
const store = useLotteryStore()
|
||||||
@@ -88,6 +230,25 @@ onMounted(async () => {
|
|||||||
await store.initialize()
|
await store.initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 分页相关
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
const paginatedParticipants = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * pageSize.value
|
||||||
|
const end = start + pageSize.value
|
||||||
|
return store.participants.slice(start, end)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
currentPage.value = page
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
pageSize.value = size
|
||||||
|
currentPage.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
// 字段配置
|
// 字段配置
|
||||||
const showFieldDialog = ref(false)
|
const showFieldDialog = ref(false)
|
||||||
const tempFields = ref([])
|
const tempFields = ref([])
|
||||||
@@ -253,112 +414,202 @@ const clearParticipants = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.participants-container {
|
||||||
border-radius: var(--border-radius-lg);
|
padding: var(--spacing-2xl);
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
padding-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons .el-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计卡片 */
|
||||||
|
.stats-card {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
border: 1px solid var(--color-border);
|
flex: 1;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: var(--font-size-3xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片通用样式 */
|
||||||
|
.form-card,
|
||||||
|
.list-card {
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
border: none;
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.single-add-form {
|
/* 表单样式 */
|
||||||
|
.form-card :deep(.el-form-item) {
|
||||||
margin-bottom: var(--spacing-lg);
|
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) {
|
.form-card :deep(.el-form-item__label) {
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.single-add-form :deep(.el-form-item__label) {
|
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.single-add-form :deep(.el-button) {
|
.form-card :deep(.el-button) {
|
||||||
margin-top: var(--spacing-sm);
|
display: flex;
|
||||||
font-family: var(--font-family-primary);
|
align-items: center;
|
||||||
font-weight: var(--font-weight-medium);
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table {
|
/* 表格样式 */
|
||||||
margin-bottom: var(--spacing-md);
|
.list-card :deep(.el-table) {
|
||||||
}
|
|
||||||
|
|
||||||
.participant-table :deep(.el-table) {
|
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table th) {
|
.list-card :deep(.el-table th) {
|
||||||
background: var(--color-background);
|
background: var(--color-background-secondary);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-primary);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table td) {
|
.list-card :deep(.el-table td) {
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table .el-table__row:hover > td) {
|
.list-card :deep(.el-table .el-table__row:hover > td) {
|
||||||
background: rgba(var(--color-primary-rgb), 0.05);
|
background: var(--color-background-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
.list-card :deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||||
background: rgba(var(--color-primary-rgb), 0.03);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table__body-wrapper) {
|
.list-card :deep(.el-table__body-wrapper) {
|
||||||
max-height: 400px;
|
max-height: 500px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
||||||
background: var(--color-border);
|
background: var(--color-border);
|
||||||
border-radius: var(--border-radius-sm);
|
border-radius: var(--border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
||||||
background: var(--color-text-light);
|
background: var(--color-text-light);
|
||||||
border-radius: var(--border-radius-sm);
|
border-radius: var(--border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-table :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
|
||||||
background: var(--color-text-secondary);
|
background: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-count {
|
/* 分页容器 */
|
||||||
text-align: right;
|
.pagination-container {
|
||||||
color: var(--color-text-light);
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-subtitle {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
padding-top: var(--spacing-md);
|
color: var(--color-text-light);
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
margin-top: var(--spacing-md);
|
|
||||||
font-family: var(--font-family-primary);
|
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 字段配置样式 */
|
||||||
.field-config {
|
.field-config {
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
@@ -392,16 +643,15 @@ const clearParticipants = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
margin-bottom: var(--spacing-md);
|
margin-bottom: var(--spacing-md);
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border-light);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
transition: var(--transition-border), var(--transition-box-shadow);
|
transition: var(--transition-border), var(--transition-box-shadow);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-item:hover {
|
.field-item:hover {
|
||||||
border-color: var(--color-secondary);
|
border-color: var(--color-primary);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
transform: none;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,57 +1,123 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-card class="card">
|
<div class="prizes-container">
|
||||||
<template #header>
|
<!-- 页面标题和操作按钮 -->
|
||||||
<div class="card-header">
|
<div class="page-header">
|
||||||
<span>奖品管理</span>
|
<h2 class="page-title">奖品管理</h2>
|
||||||
<el-button type="primary" size="small" @click="showPrizeDialog = true">
|
<div class="action-buttons">
|
||||||
|
<el-button type="primary" size="default" @click="showPrizeDialog = true">
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
添加奖品
|
添加奖品
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
<div class="prize-list">
|
<!-- 统计信息 -->
|
||||||
<div
|
<div class="stats-card">
|
||||||
v-for="prize in store.prizes"
|
<div class="stat-item">
|
||||||
:key="prize.id"
|
<div class="stat-icon" style="background: var(--color-warning-light)">
|
||||||
class="prize-item"
|
<el-icon><Present /></el-icon>
|
||||||
>
|
</div>
|
||||||
<div class="prize-info">
|
<div class="stat-content">
|
||||||
<span class="prize-name">{{ prize.name }}</span>
|
<div class="stat-value">{{ store.prizes.length }}</div>
|
||||||
<span class="prize-stock">库存: {{ prize.used }}/{{ prize.stock }}</span>
|
<div class="stat-label">奖品种类</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon" style="background: var(--color-info-light)">
|
||||||
|
<el-icon><Box /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ totalStock }}</div>
|
||||||
|
<div class="stat-label">总库存</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon" style="background: var(--color-success-light)">
|
||||||
|
<el-icon><CircleCheck /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ totalUsed }}</div>
|
||||||
|
<div class="stat-label">已发放</div>
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
circle
|
|
||||||
@click="removePrize(prize.id)"
|
|
||||||
>
|
|
||||||
<el-icon><Delete /></el-icon>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 添加奖品对话框 -->
|
<!-- 奖品列表 -->
|
||||||
<el-dialog v-model="showPrizeDialog" title="添加奖品" width="400px">
|
<el-card class="list-card" shadow="never">
|
||||||
<el-form :model="newPrize" label-width="80px">
|
<template #header>
|
||||||
<el-form-item label="奖品名称">
|
<div class="card-header">
|
||||||
<el-input v-model="newPrize.name" placeholder="例如:一等奖" />
|
<span class="card-title">
|
||||||
</el-form-item>
|
<el-icon><List /></el-icon>
|
||||||
<el-form-item label="库存数量">
|
奖品列表
|
||||||
<el-input-number v-model="newPrize.stock" :min="1" />
|
</span>
|
||||||
</el-form-item>
|
</div>
|
||||||
</el-form>
|
</template>
|
||||||
<template #footer>
|
<div class="prize-list">
|
||||||
<el-button @click="showPrizeDialog = false">取消</el-button>
|
<div v-for="prize in store.prizes" :key="prize.id" class="prize-item">
|
||||||
<el-button type="primary" @click="addPrize">确定</el-button>
|
<div class="prize-icon">
|
||||||
</template>
|
<el-icon><Present /></el-icon>
|
||||||
</el-dialog>
|
</div>
|
||||||
|
<div class="prize-content">
|
||||||
|
<div class="prize-name">{{ prize.name }}</div>
|
||||||
|
<div class="prize-stock">
|
||||||
|
<span class="stock-label">库存:</span>
|
||||||
|
<span class="stock-value">{{ prize.used }}/{{ prize.stock }}</span>
|
||||||
|
<el-progress
|
||||||
|
:percentage="stockPercentage(prize)"
|
||||||
|
:status="getStockStatus(prize)"
|
||||||
|
:stroke-width="8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="prize-actions">
|
||||||
|
<el-button type="danger" size="small" link @click="removePrize(prize.id)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-if="store.prizes.length === 0" description="暂无奖品,请先添加" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 添加奖品对话框 -->
|
||||||
|
<el-dialog v-model="showPrizeDialog" title="添加奖品" width="500px">
|
||||||
|
<el-form :model="newPrize" label-width="100px" size="default">
|
||||||
|
<el-form-item label="奖品名称" required>
|
||||||
|
<el-input
|
||||||
|
v-model="newPrize.name"
|
||||||
|
placeholder="例如:一等奖"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="库存数量" required>
|
||||||
|
<el-input-number
|
||||||
|
v-model="newPrize.stock"
|
||||||
|
:min="1"
|
||||||
|
:max="9999"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useLotteryStore } from '../../store'
|
import { useLotteryStore } from '../../store'
|
||||||
import { Delete } from '@element-plus/icons-vue'
|
import {
|
||||||
|
Delete,
|
||||||
|
Plus,
|
||||||
|
Present,
|
||||||
|
Box,
|
||||||
|
CircleCheck,
|
||||||
|
List
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const store = useLotteryStore()
|
const store = useLotteryStore()
|
||||||
@@ -61,6 +127,15 @@ onMounted(async () => {
|
|||||||
await store.initialize()
|
await store.initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const totalStock = computed(() => {
|
||||||
|
return store.prizes.reduce((sum, prize) => sum + prize.stock, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalUsed = computed(() => {
|
||||||
|
return store.prizes.reduce((sum, prize) => sum + prize.used, 0)
|
||||||
|
})
|
||||||
|
|
||||||
// 奖品管理
|
// 奖品管理
|
||||||
const showPrizeDialog = ref(false)
|
const showPrizeDialog = ref(false)
|
||||||
const newPrize = ref({
|
const newPrize = ref({
|
||||||
@@ -68,13 +143,35 @@ const newPrize = ref({
|
|||||||
stock: 1
|
stock: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 计算库存百分比
|
||||||
|
const stockPercentage = (prize) => {
|
||||||
|
if (prize.stock === 0) return 0
|
||||||
|
return Math.round((prize.used / prize.stock) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取库存状态
|
||||||
|
const getStockStatus = (prize) => {
|
||||||
|
const percentage = stockPercentage(prize)
|
||||||
|
if (percentage >= 100) return 'exception'
|
||||||
|
if (percentage >= 80) return 'warning'
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
|
||||||
const addPrize = () => {
|
const addPrize = () => {
|
||||||
if (newPrize.value.name.trim()) {
|
if (!newPrize.value.name.trim()) {
|
||||||
store.addPrize(newPrize.value)
|
ElMessage.error('请输入奖品名称')
|
||||||
newPrize.value = { name: '', stock: 1 }
|
return
|
||||||
showPrizeDialog.value = false
|
|
||||||
ElMessage.success('添加成功')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newPrize.value.stock < 1) {
|
||||||
|
ElMessage.error('库存数量必须大于0')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
store.addPrize(newPrize.value)
|
||||||
|
newPrize.value = { name: '', stock: 1 }
|
||||||
|
showPrizeDialog.value = false
|
||||||
|
ElMessage.success('添加成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
const removePrize = (id) => {
|
const removePrize = (id) => {
|
||||||
@@ -84,80 +181,190 @@ const removePrize = (id) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.prizes-container {
|
||||||
border-radius: var(--border-radius-lg);
|
padding: var(--spacing-2xl);
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
padding-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons .el-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计卡片 */
|
||||||
|
.stats-card {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
border: 1px solid var(--color-border);
|
flex: 1;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: var(--font-size-3xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表卡片 */
|
||||||
|
.list-card {
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
border: none;
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 奖品列表 */
|
||||||
.prize-list {
|
.prize-list {
|
||||||
max-height: 400px;
|
display: flex;
|
||||||
overflow-y: auto;
|
flex-direction: column;
|
||||||
}
|
gap: var(--spacing-md);
|
||||||
|
|
||||||
.prize-list::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prize-list::-webkit-scrollbar-track {
|
|
||||||
background: var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.prize-list::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--color-text-light);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.prize-list::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-text-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-item {
|
.prize-item {
|
||||||
padding: var(--spacing-lg);
|
display: flex;
|
||||||
margin-bottom: var(--spacing-md);
|
align-items: center;
|
||||||
border: 1px solid var(--color-border);
|
gap: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
border: 1px solid var(--color-border-light);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
transition: var(--transition-border), var(--transition-box-shadow);
|
transition: var(--transition-border), var(--transition-box-shadow);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-item:hover {
|
.prize-item:hover {
|
||||||
border-color: var(--color-secondary);
|
border-color: var(--color-primary);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
transform: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-info {
|
.prize-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
background: var(--color-warning-light);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--color-warning);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prize-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-name {
|
.prize-name {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
margin-bottom: var(--spacing-xs);
|
margin-bottom: var(--spacing-md);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-stock {
|
.prize-stock {
|
||||||
color: var(--color-text-light);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-label {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-family: var(--font-family-primary);
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-value {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prize-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prize-actions .el-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,70 +1,154 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-card class="card">
|
<div class="rounds-container">
|
||||||
<template #header>
|
<!-- 页面标题和操作按钮 -->
|
||||||
<div class="card-header">
|
<div class="page-header">
|
||||||
<span>轮次管理</span>
|
<h2 class="page-title">轮次管理</h2>
|
||||||
<el-button type="primary" size="small" @click="showRoundDialog = true">
|
<div class="action-buttons">
|
||||||
|
<el-button type="primary" size="default" @click="showRoundDialog = true">
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
添加轮次
|
添加轮次
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
<div class="round-list">
|
<!-- 统计信息 -->
|
||||||
<div
|
<div class="stats-card">
|
||||||
v-for="round in store.rounds"
|
<div class="stat-item">
|
||||||
:key="round.id"
|
<div class="stat-icon" style="background: var(--color-info-light)">
|
||||||
class="round-item"
|
<el-icon><List /></el-icon>
|
||||||
: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>
|
||||||
<div class="round-actions">
|
<div class="stat-content">
|
||||||
<el-button
|
<div class="stat-value">{{ store.rounds.length }}</div>
|
||||||
type="danger"
|
<div class="stat-label">总轮次</div>
|
||||||
size="small"
|
</div>
|
||||||
circle
|
</div>
|
||||||
@click="removeRound(round.id)"
|
<div class="stat-item">
|
||||||
>
|
<div class="stat-icon" style="background: var(--color-success-light)">
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><CircleCheck /></el-icon>
|
||||||
</el-button>
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ completedRounds }}</div>
|
||||||
|
<div class="stat-label">已完成</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon" style="background: var(--color-warning-light)">
|
||||||
|
<el-icon><Clock /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ pendingRounds }}</div>
|
||||||
|
<div class="stat-label">待进行</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 添加轮次对话框 -->
|
<!-- 轮次列表 -->
|
||||||
<el-dialog v-model="showRoundDialog" title="添加轮次" width="400px">
|
<el-card class="list-card" shadow="never">
|
||||||
<el-form :model="newRound" label-width="80px">
|
<template #header>
|
||||||
<el-form-item label="轮次名称">
|
<div class="card-header">
|
||||||
<el-input v-model="newRound.name" placeholder="例如:第一轮" />
|
<span class="card-title">
|
||||||
</el-form-item>
|
<el-icon><List /></el-icon>
|
||||||
<el-form-item label="选择奖品">
|
轮次列表
|
||||||
<el-select v-model="newRound.prizeId" placeholder="请选择奖品" style="width: 100%">
|
</span>
|
||||||
<el-option
|
</div>
|
||||||
v-for="prize in store.prizes"
|
</template>
|
||||||
:key="prize.id"
|
<div class="round-list">
|
||||||
:label="`${prize.name} (剩余: ${prize.stock - prize.used})`"
|
<div
|
||||||
:value="prize.id"
|
v-for="round in store.rounds"
|
||||||
|
:key="round.id"
|
||||||
|
class="round-item"
|
||||||
|
:class="{ 'completed': round.completed }"
|
||||||
|
>
|
||||||
|
<div class="round-index">
|
||||||
|
<span class="index-number">{{ getRoundIndex(round) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="round-content">
|
||||||
|
<div class="round-name">{{ round.name }}</div>
|
||||||
|
<div class="round-detail">
|
||||||
|
<span class="detail-label">奖品:</span>
|
||||||
|
<span class="detail-value">{{ getPrizeName(round.prizeId) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="round-detail">
|
||||||
|
<span class="detail-label">抽取人数:</span>
|
||||||
|
<span class="detail-value">{{ round.count }}人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="round-status">
|
||||||
|
<el-tag
|
||||||
|
:type="round.completed ? 'success' : 'info'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ round.completed ? '已完成' : '待进行' }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="round-actions">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="removeRound(round.id)"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-if="store.rounds.length === 0" description="暂无轮次,请先添加" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 添加轮次对话框 -->
|
||||||
|
<el-dialog v-model="showRoundDialog" title="添加轮次" width="500px">
|
||||||
|
<el-form :model="newRound" label-width="100px" size="default">
|
||||||
|
<el-form-item label="轮次名称" required>
|
||||||
|
<el-input
|
||||||
|
v-model="newRound.name"
|
||||||
|
placeholder="例如:第一轮"
|
||||||
|
clearable
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="选择奖品" required>
|
||||||
<el-form-item label="抽取人数">
|
<el-select
|
||||||
<el-input-number v-model="newRound.count" :min="1" />
|
v-model="newRound.prizeId"
|
||||||
</el-form-item>
|
placeholder="请选择奖品"
|
||||||
</el-form>
|
style="width: 100%"
|
||||||
<template #footer>
|
clearable
|
||||||
<el-button @click="showRoundDialog = false">取消</el-button>
|
>
|
||||||
<el-button type="primary" @click="addRound">确定</el-button>
|
<el-option
|
||||||
</template>
|
v-for="prize in availablePrizes"
|
||||||
</el-dialog>
|
:key="prize.id"
|
||||||
|
:label="`${prize.name} (剩余: ${prize.stock - prize.used})`"
|
||||||
|
:value="prize.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="抽取人数" required>
|
||||||
|
<el-input-number
|
||||||
|
v-model="newRound.count"
|
||||||
|
:min="1"
|
||||||
|
:max="9999"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useLotteryStore } from '../../store'
|
import { useLotteryStore } from '../../store'
|
||||||
import { Delete } from '@element-plus/icons-vue'
|
import {
|
||||||
|
Delete,
|
||||||
|
Plus,
|
||||||
|
List,
|
||||||
|
CircleCheck,
|
||||||
|
Clock
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const store = useLotteryStore()
|
const store = useLotteryStore()
|
||||||
@@ -74,6 +158,32 @@ onMounted(async () => {
|
|||||||
await store.initialize()
|
await store.initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const completedRounds = computed(() => {
|
||||||
|
return store.rounds.filter(r => r.completed).length
|
||||||
|
})
|
||||||
|
|
||||||
|
const pendingRounds = computed(() => {
|
||||||
|
return store.rounds.filter(r => !r.completed).length
|
||||||
|
})
|
||||||
|
|
||||||
|
const availablePrizes = computed(() => {
|
||||||
|
return store.prizes.filter(prize => {
|
||||||
|
const available = prize.stock - prize.used
|
||||||
|
return available > 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取轮次索引
|
||||||
|
const getRoundIndex = (round) => {
|
||||||
|
return store.rounds.findIndex(r => r.id === round.id) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPrizeName = (prizeId) => {
|
||||||
|
const prize = store.prizes.find(p => p.id === prizeId)
|
||||||
|
return prize ? prize.name : ''
|
||||||
|
}
|
||||||
|
|
||||||
// 轮次管理
|
// 轮次管理
|
||||||
const showRoundDialog = ref(false)
|
const showRoundDialog = ref(false)
|
||||||
const newRound = ref({
|
const newRound = ref({
|
||||||
@@ -82,20 +192,26 @@ const newRound = ref({
|
|||||||
count: 1
|
count: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
const getPrizeName = (prizeId) => {
|
|
||||||
const prize = store.prizes.find(p => p.id === prizeId)
|
|
||||||
return prize ? prize.name : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const addRound = () => {
|
const addRound = () => {
|
||||||
if (newRound.value.name.trim() && newRound.value.prizeId) {
|
if (!newRound.value.name.trim()) {
|
||||||
store.addRound(newRound.value)
|
ElMessage.error('请输入轮次名称')
|
||||||
newRound.value = { name: '', prizeId: null, count: 1 }
|
return
|
||||||
showRoundDialog.value = false
|
|
||||||
ElMessage.success('添加成功')
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('请填写完整信息')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!newRound.value.prizeId) {
|
||||||
|
ElMessage.error('请选择奖品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newRound.value.count < 1) {
|
||||||
|
ElMessage.error('抽取人数必须大于0')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
store.addRound(newRound.value)
|
||||||
|
newRound.value = { name: '', prizeId: null, count: 1 }
|
||||||
|
showRoundDialog.value = false
|
||||||
|
ElMessage.success('添加成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeRound = (id) => {
|
const removeRound = (id) => {
|
||||||
@@ -105,61 +221,134 @@ const removeRound = (id) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.rounds-container {
|
||||||
border-radius: var(--border-radius-lg);
|
padding: var(--spacing-2xl);
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
padding-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons .el-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计卡片 */
|
||||||
|
.stats-card {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
border: 1px solid var(--color-border);
|
flex: 1;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: var(--font-size-3xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表卡片 */
|
||||||
|
.list-card {
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
border: none;
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 轮次列表 */
|
||||||
.round-list {
|
.round-list {
|
||||||
max-height: 400px;
|
display: flex;
|
||||||
overflow-y: auto;
|
flex-direction: column;
|
||||||
}
|
gap: var(--spacing-md);
|
||||||
|
|
||||||
.round-list::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.round-list::-webkit-scrollbar-track {
|
|
||||||
background: var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.round-list::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--color-text-light);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.round-list::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-text-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-item {
|
.round-item {
|
||||||
padding: var(--spacing-lg);
|
display: flex;
|
||||||
margin-bottom: var(--spacing-md);
|
align-items: center;
|
||||||
border: 1px solid var(--color-border);
|
gap: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
border: 1px solid var(--color-border-light);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
transition: var(--transition-border), var(--transition-box-shadow);
|
transition: var(--transition-border), var(--transition-box-shadow);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-item:hover {
|
.round-item:hover {
|
||||||
border-color: var(--color-secondary);
|
border-color: var(--color-primary);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
transform: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-item.completed {
|
.round-item.completed {
|
||||||
@@ -167,33 +356,68 @@ const removeRound = (id) => {
|
|||||||
background: var(--color-border-light);
|
background: var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-info {
|
.round-index {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-number {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
background: var(--color-primary-light);
|
||||||
|
color: var(--color-primary);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.round-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-name {
|
.round-name {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
margin-bottom: var(--spacing-xs);
|
margin-bottom: var(--spacing-md);
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-detail {
|
.round-detail {
|
||||||
color: var(--color-text-light);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-family: var(--font-family-primary);
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.round-status {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-actions {
|
.round-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
margin-top: var(--spacing-md);
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-actions .el-button {
|
.round-actions .el-button {
|
||||||
flex: 1;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,32 +1,123 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-card class="card">
|
<div class="winners-container">
|
||||||
<template #header>
|
<!-- 页面标题和操作按钮 -->
|
||||||
<div class="card-header">
|
<div class="page-header">
|
||||||
<span>中奖记录</span>
|
<h2 class="page-title">中奖记录</h2>
|
||||||
<div>
|
<div class="action-buttons">
|
||||||
<el-button type="success" size="small" @click="exportWinners">
|
<el-button type="success" size="default" @click="exportWinners">
|
||||||
导出
|
<el-icon><Download /></el-icon>
|
||||||
</el-button>
|
导出
|
||||||
<el-button type="danger" size="small" @click="resetLottery">
|
</el-button>
|
||||||
清空
|
<el-button type="danger" size="default" @click="resetLottery">
|
||||||
</el-button>
|
<el-icon><Delete /></el-icon>
|
||||||
|
清空
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<div class="stats-card">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon" style="background: var(--color-success-light)">
|
||||||
|
<el-icon><Trophy /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ store.winners.length }}</div>
|
||||||
|
<div class="stat-label">中奖人数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon" style="background: var(--color-info-light)">
|
||||||
|
<el-icon><Present /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ uniquePrizes }}</div>
|
||||||
|
<div class="stat-label">奖品种类</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon" style="background: var(--color-warning-light)">
|
||||||
|
<el-icon><List /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{ uniqueRounds }}</div>
|
||||||
|
<div class="stat-label">涉及轮次</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-table :data="store.winners" style="width: 100%" stripe>
|
<!-- 中奖记录列表 -->
|
||||||
<el-table-column prop="name" label="姓名" width="150" />
|
<el-card class="list-card" shadow="never">
|
||||||
<el-table-column prop="prizeName" label="奖品" width="150" />
|
<template #header>
|
||||||
<el-table-column prop="roundName" label="轮次" width="150" />
|
<div class="card-header">
|
||||||
<el-table-column prop="time" label="时间" />
|
<span class="card-title">
|
||||||
</el-table>
|
<el-icon><List /></el-icon>
|
||||||
</el-card>
|
中奖记录
|
||||||
|
</span>
|
||||||
|
<span class="card-subtitle">共 {{ store.winners.length }} 条</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table
|
||||||
|
:data="paginatedWinners"
|
||||||
|
style="width: 100%"
|
||||||
|
stripe
|
||||||
|
max-height="500"
|
||||||
|
:empty-text="'暂无中奖记录'"
|
||||||
|
>
|
||||||
|
<!-- 动态生成字段列 -->
|
||||||
|
<el-table-column
|
||||||
|
v-for="field in store.fields"
|
||||||
|
:key="field.key"
|
||||||
|
:prop="field.key"
|
||||||
|
:label="field.label"
|
||||||
|
min-width="120"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.participant ? row.participant[field.key] : '' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="prizeName" label="奖品" min-width="120" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="success" size="small">{{ row.prizeName }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="roundName" label="轮次" min-width="120" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="info" size="small">{{ row.roundName }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="time" label="中奖时间" min-width="180" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="store.winners.length"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useLotteryStore } from '../../store'
|
import { useLotteryStore } from '../../store'
|
||||||
import { ElMessage } from 'element-plus'
|
import {
|
||||||
|
Download,
|
||||||
|
Delete,
|
||||||
|
Trophy,
|
||||||
|
Present,
|
||||||
|
List
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const store = useLotteryStore()
|
const store = useLotteryStore()
|
||||||
|
|
||||||
@@ -35,10 +126,59 @@ onMounted(async () => {
|
|||||||
await store.initialize()
|
await store.initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 分页相关
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
const paginatedWinners = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * pageSize.value
|
||||||
|
const end = start + pageSize.value
|
||||||
|
return store.winners.slice(start, end)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
currentPage.value = page
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
pageSize.value = size
|
||||||
|
currentPage.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const uniquePrizes = computed(() => {
|
||||||
|
const prizes = new Set(store.winners.map(w => w.prizeName))
|
||||||
|
return prizes.size
|
||||||
|
})
|
||||||
|
|
||||||
|
const uniqueRounds = computed(() => {
|
||||||
|
const rounds = new Set(store.winners.map(w => w.roundName))
|
||||||
|
return rounds.size
|
||||||
|
})
|
||||||
|
|
||||||
// 重置抽奖
|
// 重置抽奖
|
||||||
const resetLottery = () => {
|
const resetLottery = () => {
|
||||||
store.resetLottery()
|
if (store.winners.length === 0) {
|
||||||
ElMessage.success('已重置')
|
ElMessage.warning('暂无中奖记录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确定要清空所有中奖记录吗?此操作不可恢复。',
|
||||||
|
'确认清空',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
store.resetLottery()
|
||||||
|
ElMessage.success('已清空中奖记录')
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消操作
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出中奖名单
|
// 导出中奖名单
|
||||||
@@ -53,47 +193,179 @@ const exportWinners = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.winners-container {
|
||||||
border-radius: var(--border-radius-lg);
|
padding: var(--spacing-2xl);
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
padding-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons .el-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计卡片 */
|
||||||
|
.stats-card {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-small);
|
box-shadow: var(--shadow-small);
|
||||||
border: 1px solid var(--color-border);
|
flex: 1;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--color-text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: var(--font-size-3xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表卡片 */
|
||||||
|
.list-card {
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
border: none;
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table) {
|
.card-subtitle {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格样式 */
|
||||||
|
.list-card :deep(.el-table) {
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: var(--font-family-primary);
|
font-family: var(--font-family-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table th) {
|
.list-card :deep(.el-table th) {
|
||||||
background: var(--color-background);
|
background: var(--color-background-secondary);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
font-family: var(--font-family-secondary);
|
font-family: var(--font-family-primary);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table td) {
|
.list-card :deep(.el-table td) {
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table .el-table__row:hover > td) {
|
.list-card :deep(.el-table .el-table__row:hover > td) {
|
||||||
background: rgba(var(--color-primary-rgb), 0.05);
|
background: var(--color-background-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
.list-card :deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||||
background: rgba(var(--color-primary-rgb), 0.03);
|
background: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card :deep(.el-table__body-wrapper) {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
||||||
|
background: var(--color-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
||||||
|
background: var(--color-text-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
|
||||||
|
background: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页容器 */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user