You've already forked rollingDraw
优化: 重构项目结构并修复字段配置保存问题
- 删除旧的独立子项目(admin、display),统一使用单应用架构 - 将 AdminLayout.vue 重命名为 Admin.vue - 在管理后台添加大屏预览快捷按钮 - 修复字段配置修改后刷新页面丢失的问题 - 新增 updateFields 方法确保字段配置持久化到 IndexedDB - 更新 IFLOW.md 和 README.md 文档 - 清理未使用的文件和测试数据
This commit is contained in:
@@ -9,7 +9,7 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: () => import('@/views/AdminLayout.vue'),
|
||||
component: () => import('@/views/Admin.vue'),
|
||||
children: [
|
||||
{
|
||||
path: 'participants',
|
||||
|
||||
@@ -112,6 +112,11 @@ export const useLotteryStore = defineStore('lottery', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateFields = async (newFields) => {
|
||||
fields.value = newFields
|
||||
await saveData('lottery_fields', fields.value)
|
||||
}
|
||||
|
||||
// ============ 参与者管理 ============
|
||||
const addParticipant = async (participant) => {
|
||||
participants.value.push(participant)
|
||||
@@ -454,6 +459,7 @@ export const useLotteryStore = defineStore('lottery', () => {
|
||||
addField,
|
||||
updateField,
|
||||
removeField,
|
||||
updateFields,
|
||||
|
||||
// 参与者管理
|
||||
addParticipant,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Views directory
|
||||
1156
src/views/Admin.vue
1156
src/views/Admin.vue
File diff suppressed because it is too large
Load Diff
@@ -1,412 +0,0 @@
|
||||
<template>
|
||||
<div class="admin-layout">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="header-content">
|
||||
<h1>抽奖管理系统</h1>
|
||||
<div style="display: flex; gap: 10px">
|
||||
<el-button type="info" @click="openDisplaySettings">
|
||||
<el-icon><Setting /></el-icon>
|
||||
显示设置
|
||||
</el-button>
|
||||
<el-button type="info" @click="showBackgroundDialog = true">
|
||||
<el-icon><Picture /></el-icon>
|
||||
背景设置
|
||||
</el-button>
|
||||
<el-button type="info" @click="showShortcutGuide = true">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
快捷键指南
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-container>
|
||||
<el-aside width="200px">
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
router
|
||||
class="admin-menu"
|
||||
>
|
||||
<el-menu-item index="/admin/participants">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>名单管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/admin/prizes">
|
||||
<el-icon><Present /></el-icon>
|
||||
<span>奖品管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/admin/rounds">
|
||||
<el-icon><List /></el-icon>
|
||||
<span>轮次管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/admin/winners">
|
||||
<el-icon><Trophy /></el-icon>
|
||||
<span>中奖记录</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-main>
|
||||
<router-view></router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
<!-- 快捷键指南对话框 -->
|
||||
<el-dialog v-model="showShortcutGuide" title="大屏端快捷键指南" width="500px">
|
||||
<div class="shortcut-guide">
|
||||
<div class="shortcut-item">
|
||||
<span class="shortcut-key">← →</span>
|
||||
<span class="shortcut-desc">切换轮次(左右方向键)</span>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<span class="shortcut-key">Space</span>
|
||||
<span class="shortcut-desc">开始/停止抽奖</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="showShortcutGuide = false">知道了</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 背景图片配置对话框 -->
|
||||
<el-dialog v-model="showBackgroundDialog" title="大屏端背景图片" width="500px">
|
||||
<div class="background-config">
|
||||
<div v-if="store.backgroundImage" class="current-background">
|
||||
<div class="background-label">当前背景图片:</div>
|
||||
<img :src="store.backgroundImage" class="background-preview" />
|
||||
<el-button type="danger" @click="clearBackgroundImage" style="margin-top: 10px">
|
||||
清除背景
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="upload-section">
|
||||
<div class="background-label">上传新背景:</div>
|
||||
<el-upload
|
||||
:auto-upload="false"
|
||||
:on-change="handleBackgroundChange"
|
||||
:limit="1"
|
||||
accept="image/*"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽图片到此处或 <em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
支持 JPG、PNG、GIF 等图片格式,最大支持10MB
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<div v-if="backgroundImagePreview" class="preview-section">
|
||||
<div class="background-label">预览:</div>
|
||||
<img :src="backgroundImagePreview" class="background-preview" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showBackgroundDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveBackgroundImage" :disabled="!backgroundImagePreview">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 显示设置对话框 -->
|
||||
<el-dialog v-model="showDisplaySettingsDialog" title="大屏端显示设置" width="400px">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="每行显示人数">
|
||||
<el-input-number
|
||||
v-model="tempColumnsPerRow"
|
||||
:min="1"
|
||||
:max="10"
|
||||
:step="1"
|
||||
/>
|
||||
<div style="margin-top: 8px; color: var(--color-text-light); font-size: 12px;">
|
||||
设置大屏端名单每行显示的人数,建议值:2-5
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showDisplaySettingsDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveDisplaySettings">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useLotteryStore } from '../store'
|
||||
import { User, Present, List, Trophy, Picture, QuestionFilled, UploadFilled, Setting } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const store = useLotteryStore()
|
||||
|
||||
const activeMenu = computed(() => route.path)
|
||||
|
||||
// 初始化store
|
||||
onMounted(async () => {
|
||||
await store.initialize()
|
||||
})
|
||||
|
||||
// 快捷键指南
|
||||
const showShortcutGuide = ref(false)
|
||||
|
||||
// 显示设置
|
||||
const showDisplaySettingsDialog = ref(false)
|
||||
const tempColumnsPerRow = ref(3)
|
||||
|
||||
const openDisplaySettings = () => {
|
||||
tempColumnsPerRow.value = store.columnsPerRow
|
||||
showDisplaySettingsDialog.value = true
|
||||
}
|
||||
|
||||
const saveDisplaySettings = async () => {
|
||||
try {
|
||||
await store.setColumnsPerRow(tempColumnsPerRow.value)
|
||||
showDisplaySettingsDialog.value = false
|
||||
ElMessage.success('显示设置已保存')
|
||||
} catch (error) {
|
||||
ElMessage.error('保存显示设置失败:' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 背景图片配置
|
||||
const showBackgroundDialog = ref(false)
|
||||
const backgroundImageFile = ref(null)
|
||||
const backgroundImagePreview = ref('')
|
||||
|
||||
const handleBackgroundChange = (file) => {
|
||||
const maxSize = 10 * 1024 * 1024
|
||||
if (file.raw.size > maxSize) {
|
||||
ElMessage.error('图片大小不能超过10MB,请使用较小的图片')
|
||||
backgroundImageFile.value = null
|
||||
backgroundImagePreview.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
backgroundImageFile.value = file.raw
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
backgroundImagePreview.value = e.target.result
|
||||
}
|
||||
reader.readAsDataURL(file.raw)
|
||||
}
|
||||
|
||||
const saveBackgroundImage = async () => {
|
||||
if (backgroundImagePreview.value) {
|
||||
try {
|
||||
await store.setBackgroundImage(backgroundImagePreview.value)
|
||||
showBackgroundDialog.value = false
|
||||
backgroundImagePreview.value = ''
|
||||
backgroundImageFile.value = null
|
||||
ElMessage.success('背景图片已设置')
|
||||
} catch (error) {
|
||||
ElMessage.error('保存背景图片失败:' + error.message)
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('请选择图片')
|
||||
}
|
||||
}
|
||||
|
||||
const clearBackgroundImage = async () => {
|
||||
try {
|
||||
await store.clearBackgroundImage()
|
||||
ElMessage.success('背景图片已清除')
|
||||
} catch (error) {
|
||||
ElMessage.error('清除背景图片失败:' + error.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-layout {
|
||||
min-height: 100vh;
|
||||
background: var(--color-background);
|
||||
font-family: var(--font-family-primary);
|
||||
}
|
||||
|
||||
.el-header {
|
||||
background: var(--color-background);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--spacing-2xl);
|
||||
box-shadow: var(--shadow-small);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: var(--header-height);
|
||||
}
|
||||
|
||||
.el-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--color-secondary);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-header h1 {
|
||||
margin: 0;
|
||||
font-family: var(--font-family-secondary);
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.el-header .el-button {
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-primary);
|
||||
transition: var(--transition-color), var(--transition-background);
|
||||
font-family: var(--font-family-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.el-header .el-button:hover {
|
||||
background: rgba(var(--color-primary-rgb), 0.1);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
background: var(--color-background);
|
||||
border-right: 1px solid var(--color-border);
|
||||
padding: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
.admin-menu {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.admin-menu .el-menu-item {
|
||||
margin: var(--spacing-sm) var(--spacing-lg);
|
||||
border-radius: var(--border-radius-md);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-family-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.admin-menu .el-menu-item:hover {
|
||||
background: rgba(var(--color-primary-rgb), 0.1);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.admin-menu .el-menu-item.is-active {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-white);
|
||||
}
|
||||
|
||||
.admin-menu .el-menu-item .el-icon {
|
||||
margin-right: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.el-main {
|
||||
padding: var(--spacing-3xl);
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 快捷键指南样式 */
|
||||
.shortcut-guide {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-3xl);
|
||||
padding: var(--spacing-2xl) 0;
|
||||
}
|
||||
|
||||
.shortcut-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xl);
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.shortcut-key {
|
||||
background: var(--color-secondary);
|
||||
color: var(--color-text-white);
|
||||
font-family: var(--font-family-secondary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-3xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
box-shadow: 0 4px 12px rgba(var(--color-secondary-rgb), 0.3);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-radius: var(--border-radius-lg);
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.shortcut-desc {
|
||||
color: var(--color-text-light);
|
||||
font-size: var(--font-size-base);
|
||||
text-align: center;
|
||||
line-height: var(--line-height-relaxed);
|
||||
font-family: var(--font-family-primary);
|
||||
}
|
||||
|
||||
/* 背景图片配置样式 */
|
||||
.background-config {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.current-background {
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--color-border-light);
|
||||
border-radius: var(--border-radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--color-border-light);
|
||||
border-radius: var(--border-radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.background-label {
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.background-preview {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
max-height: 250px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--border-radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
margin-top: var(--spacing-lg);
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
</style>
|
||||
@@ -4,82 +4,42 @@
|
||||
<div class="card-header">
|
||||
<span>名单管理</span>
|
||||
<div>
|
||||
<el-button type="info" size="small" @click="showFieldConfig">
|
||||
字段配置
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="showImportDialog = true">
|
||||
导入
|
||||
</el-button>
|
||||
<el-button type="success" size="small" @click="exportParticipants">
|
||||
导出
|
||||
</el-button>
|
||||
<el-button type="danger" size="small" @click="clearParticipants">
|
||||
清空
|
||||
</el-button>
|
||||
<el-button type="info" size="small" @click="showFieldConfig"> 字段配置 </el-button>
|
||||
<el-button type="primary" size="small" @click="showImportDialog = true"> 导入 </el-button>
|
||||
<el-button type="success" size="small" @click="exportParticipants"> 导出 </el-button>
|
||||
<el-button type="danger" size="small" @click="clearParticipants"> 清空 </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 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-button type="primary" @click="addParticipant" style="width: 100%"> 添加 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 参与者表格 -->
|
||||
<div class="participant-table">
|
||||
<el-table :data="store.participants" style="width: 100%" stripe max-height="400">
|
||||
<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 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="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showParticipantDetail(row)"
|
||||
v-if="store.fields.length > 1"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeParticipant(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="showParticipantDetail(row)" v-if="store.fields.length > 1"> 详情 </el-button>
|
||||
<el-button type="danger" size="small" @click="removeParticipant(row.id)"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="participant-count">
|
||||
共 {{ store.participants.length }} 人
|
||||
</div>
|
||||
|
||||
<div class="participant-count"> 共 {{ store.participants.length }} 人 </div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<!-- 字段配置对话框 -->
|
||||
<el-dialog v-model="showFieldDialog" title="字段配置" width="600px">
|
||||
<div class="field-config">
|
||||
@@ -98,25 +58,14 @@
|
||||
<el-button type="primary" @click="saveFields">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog v-model="showImportDialog" title="导入名单" width="500px">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:limit="1"
|
||||
accept=".csv"
|
||||
drag
|
||||
>
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :on-change="handleFileChange" :limit="1" accept=".csv" drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽文件到此处或 <em>点击上传</em>
|
||||
</div>
|
||||
<div class="el-upload__text"> 拖拽文件到此处或 <em>点击上传</em> </div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
支持 .csv 格式文件,第一行为字段名,后续行为数据
|
||||
</div>
|
||||
<div class="el-upload__tip"> 支持 .csv 格式文件,第一行为字段名,后续行为数据 </div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
@@ -153,27 +102,31 @@ const addField = () => {
|
||||
id: Date.now(),
|
||||
key: '',
|
||||
label: '',
|
||||
required: false
|
||||
required: false,
|
||||
})
|
||||
}
|
||||
|
||||
const removeField = (id) => {
|
||||
const removeField = id => {
|
||||
const index = tempFields.value.findIndex(f => f.id === id)
|
||||
if (index > -1) {
|
||||
tempFields.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const saveFields = () => {
|
||||
const saveFields = async () => {
|
||||
const validFields = tempFields.value.filter(f => f.key && f.label)
|
||||
if (validFields.length === 0) {
|
||||
ElMessage.error('至少需要一个有效字段')
|
||||
return
|
||||
}
|
||||
|
||||
store.fields.splice(0, store.fields.length, ...validFields)
|
||||
showFieldDialog.value = false
|
||||
ElMessage.success('字段配置已保存')
|
||||
|
||||
try {
|
||||
await store.updateFields(validFields)
|
||||
showFieldDialog.value = false
|
||||
ElMessage.success('字段配置已保存')
|
||||
} catch (error) {
|
||||
ElMessage.error('保存字段配置失败:' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 名单管理
|
||||
@@ -189,9 +142,13 @@ const initNewParticipantData = () => {
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => store.fields, () => {
|
||||
initNewParticipantData()
|
||||
}, { deep: true })
|
||||
watch(
|
||||
() => store.fields,
|
||||
() => {
|
||||
initNewParticipantData()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
initNewParticipantData()
|
||||
|
||||
@@ -201,33 +158,33 @@ const addParticipant = () => {
|
||||
ElMessage.error(`请填写必填字段:${missingFields.map(f => f.label).join('、')}`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const hasValue = Object.values(newParticipantData.value).some(v => v && v.trim())
|
||||
if (!hasValue) {
|
||||
ElMessage.error('请至少填写一个字段')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
store.addParticipant({
|
||||
id: Date.now(),
|
||||
...newParticipantData.value
|
||||
...newParticipantData.value,
|
||||
})
|
||||
|
||||
|
||||
initNewParticipantData()
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
|
||||
const showParticipantDetail = (person) => {
|
||||
const showParticipantDetail = person => {
|
||||
const details = []
|
||||
|
||||
|
||||
store.fields.forEach(field => {
|
||||
details.push(`${field.label}: ${person[field.key] || '-'}`)
|
||||
})
|
||||
|
||||
|
||||
const allKeys = Object.keys(person).filter(key => key !== 'id')
|
||||
const configuredKeys = store.fields.map(f => f.key)
|
||||
const unconfiguredKeys = allKeys.filter(key => !configuredKeys.includes(key))
|
||||
|
||||
|
||||
if (unconfiguredKeys.length > 0) {
|
||||
details.push('')
|
||||
details.push('未配置字段:')
|
||||
@@ -235,21 +192,21 @@ const showParticipantDetail = (person) => {
|
||||
details.push(`${key}: ${person[key]}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
ElMessage({
|
||||
message: details.join('\n'),
|
||||
type: 'info',
|
||||
duration: 0,
|
||||
showClose: true
|
||||
showClose: true,
|
||||
})
|
||||
}
|
||||
|
||||
const removeParticipant = (id) => {
|
||||
const removeParticipant = id => {
|
||||
store.removeParticipant(id)
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
|
||||
const handleFileChange = (file) => {
|
||||
const handleFileChange = file => {
|
||||
selectedFile.value = file.raw
|
||||
}
|
||||
|
||||
@@ -279,21 +236,19 @@ const clearParticipants = () => {
|
||||
ElMessage.warning('名单已经是空的')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(
|
||||
'确定要清空所有名单吗?此操作不可恢复。',
|
||||
'确认清空',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
store.clearParticipants()
|
||||
ElMessage.success('已清空名单')
|
||||
}).catch(() => {
|
||||
// 用户取消操作
|
||||
|
||||
ElMessageBox.confirm('确定要清空所有名单吗?此操作不可恢复。', '确认清空', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
store.clearParticipants()
|
||||
ElMessage.success('已清空名单')
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消操作
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -449,4 +404,4 @@ const clearParticipants = () => {
|
||||
box-shadow: var(--shadow-small);
|
||||
transform: none;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user