| | |
| | | </el-form-item> |
| | | |
| | | <!-- 图片/视频上传 --> |
| | | <el-divider content-position="left">图片/视频</el-divider> |
| | | <el-divider content-position="left"> |
| | | 图片/视频 |
| | | <span class="media-description">支持jpg/png/mp4,最多3个文件</span> |
| | | </el-divider> |
| | | |
| | | <el-form-item label="媒体文件"> |
| | | <div class="media-upload-section"> |
| | |
| | | <!-- 添加按钮 --> |
| | | <el-upload |
| | | v-if="form.mediaFiles.length < 3" |
| | | class="media-uploader" |
| | | class="media-uploader media-uploader-left" |
| | | :show-file-list="false" |
| | | :before-upload="beforeMediaUpload" |
| | | action="#" |
| | |
| | | <div class="upload-placeholder"> |
| | | <el-icon class="upload-icon"><Plus /></el-icon> |
| | | <div class="upload-text">添加图片/视频</div> |
| | | <div class="upload-tip">支持jpg/png/mp4,最多3个文件</div> |
| | | </div> |
| | | </el-upload> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="form.stages && form.stages.length > 0" class="stages-list"> |
| | | <div v-if="form.value && form.value.stages && form.value.stages.length > 0" class="stages-list"> |
| | | <div v-for="(stage, index) in sortedFormStages" :key="index" class="stage-item"> |
| | | <div class="stage-info"> |
| | | <div class="stage-header"> |
| | |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="180" align="center"> |
| | | <el-table-column label="操作" width="100" align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button size="small" @click="editJudge(row, $index)">编辑</el-button> |
| | | <el-button size="small" type="danger" @click="removeJudge($index)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | |
| | | <!-- 阶段选择 --> |
| | | <div style="margin-bottom: 16px;"> |
| | | <el-form-item label="添加到阶段:" label-width="100px"> |
| | | <el-select v-model="selectedStageOption" style="width: 100%;" @change="handleStageChange"> |
| | | <!-- 只显示比赛阶段 --> |
| | | <el-form-item label="负责阶段:" label-width="100px"> |
| | | <!-- 调试信息 --> |
| | | |
| | | <el-select v-model="selectedStageOptions" multiple style="width: 100%;" placeholder="请选择负责的阶段"> |
| | | <!-- 使用计算属性 --> |
| | | <el-option |
| | | v-for="stage in form.stages" |
| | | :key="stage.id" |
| | | :label="stage.name" |
| | | :value="stage.id ? stage.id.toString() : ''" |
| | | v-for="option in stageOptions" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | <el-select v-model="currentStudent.lastStageId" placeholder="请选择阶段" style="width: 100%"> |
| | | <el-option label="无" :value="null" /> |
| | | <el-option |
| | | v-for="stage in form.stages" |
| | | v-for="stage in (form.value?.stages || [])" |
| | | :key="stage.id" |
| | | :label="stage.name" |
| | | :value="stage.id" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, computed, nextTick } from 'vue' |
| | | import { useRouter, useRoute } from 'vue-router' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, VideoPlay, Clock, User, UserFilled, Search } from '@element-plus/icons-vue' |
| | |
| | | // 评委选择相关 |
| | | const allJudges = ref([]) |
| | | const judgeSearchText = ref('') |
| | | const selectedStageOption = ref('all') |
| | | const selectedStageOptions = ref([]) |
| | | const selectedJudges = ref([]) |
| | | const judgeLoading = ref(false) |
| | | |
| | |
| | | }) |
| | | }) |
| | | |
| | | // 用于下拉框的阶段选项 |
| | | const stageOptions = computed(() => { |
| | | if (!form.value?.stages) { |
| | | return [] |
| | | } |
| | | |
| | | return form.value.stages |
| | | .filter(stage => stage && stage.id != null) |
| | | .map(stage => ({ |
| | | label: stage.name, |
| | | value: stage.id.toString(), |
| | | stage: stage |
| | | })) |
| | | }) |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | name: [ |
| | |
| | | judgeLoading.value = true |
| | | const judges = await getAllJudges() |
| | | allJudges.value = judges || [] |
| | | console.log('加载评委列表成功:', allJudges.value.length, '个评委') |
| | | } catch (error) { |
| | | console.error('加载评委列表失败:', error) |
| | | ElMessage.error('加载评委列表失败: ' + error.message) |
| | |
| | | try { |
| | | loading.value = true |
| | | const activity = await getActivity(route.params.id) |
| | | |
| | | if (activity) { |
| | | form.value = { |
| | | id: activity.id, |
| | |
| | | // 加载并回填已上传媒体:targetType=2 假设为“活动”,如不同请调整 |
| | | try { |
| | | const medias = await getMediasByTarget(MediaTargetType.ACTIVITY, parseInt(activity.id)) |
| | | console.log('=== 加载活动媒体调试信息 ===') |
| | | console.log('活动ID:', activity.id) |
| | | console.log('获取到的媒体数据:', medias) |
| | | |
| | | form.value.mediaFiles = (medias || []).map(m => { |
| | | console.log('处理媒体文件:', m) |
| | | const isImage = (m.mediaType === 1) || (m.fileExt && ['jpg','jpeg','png','gif','webp'].includes(m.fileExt.toLowerCase())) |
| | | const isVideo = (m.mediaType === 2) || (m.fileExt && ['mp4','mov','m4v','avi','mkv'].includes(m.fileExt.toLowerCase())) |
| | | const mediaItem = { |
| | |
| | | uploaded: true, // 标记为已上传,不需要重新上传 |
| | | file: null // 已保存的文件没有file对象 |
| | | } |
| | | console.log('转换后的媒体项:', mediaItem) |
| | | return mediaItem |
| | | }) |
| | | console.log('最终的mediaFiles:', form.value.mediaFiles) |
| | | |
| | | // 设置阶段数量选择器的值 |
| | | selectedStageCount.value = form.value.stages.length || 1 |
| | | } catch (e) { |
| | | console.error('加载活动媒体失败:', e) |
| | | } |
| | | |
| | | // 设置阶段数量选择器的值 |
| | | selectedStageCount.value = (form.value && form.value.stages) ? form.value.stages.length || 1 : 1 |
| | | } |
| | | } catch (error) { |
| | | console.error('加载比赛数据失败:', error) |
| | |
| | | // 阶段管理 |
| | | // 阶段数量变化处理 |
| | | const onStageCountChange = (count) => { |
| | | if (!count) return |
| | | if (!count || !form.value || !form.value.stages) return |
| | | |
| | | // 如果当前阶段数量少于选择的数量,自动添加阶段 |
| | | while (form.value.stages.length < count) { |
| | |
| | | |
| | | // 获取阶段在原始数组中的索引 |
| | | const getOriginalStageIndex = (stage) => { |
| | | if (!form.value || !form.value.stages) return -1 |
| | | return form.value.stages.findIndex(s => s === stage) |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | const removeStage = async (index) => { |
| | | if (!form.value || !form.value.stages) return |
| | | |
| | | try { |
| | | await ElMessageBox.confirm('确定要删除这个阶段吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | |
| | | } |
| | | |
| | | const saveStage = async () => { |
| | | if (!form.value || !form.value.stages) return |
| | | |
| | | try { |
| | | await stageFormRef.value.validate() |
| | | |
| | |
| | | } |
| | | |
| | | const removeJudge = async (index) => { |
| | | if (!form.value || !form.value.judges) { |
| | | ElMessage.error('表单数据未初始化') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm('确定要删除这个评委吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | |
| | | } |
| | | |
| | | const getJudgeStages = (judge) => { |
| | | if (!judge.stageIds) return [] |
| | | if (!judge.stageIds || !form.value || !form.value.stages) return [] |
| | | |
| | | const stages = [] |
| | | |
| | | // 只检查比赛阶段 |
| | | if (form.value.stages) { |
| | | const matchedStages = form.value.stages.filter(stage => judge.stageIds.includes(stage.id)) |
| | | matchedStages.forEach(stage => { |
| | | // 检查比赛阶段 |
| | | judge.stageIds.forEach(stageId => { |
| | | // 处理实际阶段ID |
| | | const stage = form.value.stages.find(s => s.id === stageId) |
| | | if (stage) { |
| | | stages.push({ |
| | | id: stage.id, |
| | | name: stage.name |
| | | }) |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | return stages |
| | | } |
| | | |
| | | const resetJudgeDialog = () => { |
| | | judgeSearchText.value = '' |
| | | // 默认选择第一个阶段 |
| | | selectedStageOption.value = form.value.stages && form.value.stages.length > 0 |
| | | ? form.value.stages[0].id?.toString() || '' |
| | | : '' |
| | | // 清空阶段选择 |
| | | selectedStageOptions.value = [] |
| | | selectedJudges.value = [] |
| | | } |
| | | |
| | | const handleJudgeSearch = (value) => { |
| | | console.log('搜索评委:', value) |
| | | // 搜索评委 |
| | | } |
| | | |
| | | const handleStageChange = (value) => { |
| | | console.log('选择阶段:', value) |
| | | } |
| | | |
| | | |
| | | const handleJudgeSelectionChange = (value) => { |
| | | console.log('选择评委:', value) |
| | | // 选择评委 |
| | | } |
| | | |
| | | const toggleSelectAll = () => { |
| | |
| | | return |
| | | } |
| | | |
| | | if (!form.value || !form.value.judges) { |
| | | ElMessage.error('表单数据未初始化') |
| | | return |
| | | } |
| | | |
| | | // 如果有阶段但没有选择阶段,则提示 |
| | | if (form.value && form.value.stages && form.value.stages.length > 0 && selectedStageOptions.value.length === 0) { |
| | | ElMessage.warning('请选择至少一个负责阶段') |
| | | return |
| | | } |
| | | |
| | | let addedCount = 0 |
| | | |
| | | selectedJudges.value.forEach(judgeId => { |
| | |
| | | // 检查是否已经存在 |
| | | const existingJudge = form.value.judges.find(j => j.id === judgeId) |
| | | if (existingJudge) { |
| | | // 更新现有评委的阶段 |
| | | const stageId = parseInt(selectedStageOption.value) |
| | | if (!existingJudge.stageIds.includes(stageId)) { |
| | | existingJudge.stageIds.push(stageId) |
| | | // 更新现有评委的阶段,合并新选择的阶段 |
| | | if (selectedStageOptions.value.length > 0) { |
| | | selectedStageOptions.value.forEach(stageId => { |
| | | const stageIdInt = parseInt(stageId) |
| | | if (!existingJudge.stageIds.includes(stageIdInt)) { |
| | | existingJudge.stageIds.push(stageIdInt) |
| | | } |
| | | }) |
| | | } |
| | | } else { |
| | | // 添加新评委 |
| | | const stageIds = [parseInt(selectedStageOption.value)] |
| | | // 添加新评委,包含所有选择的阶段 |
| | | const stageIds = selectedStageOptions.value.length > 0 |
| | | ? selectedStageOptions.value.map(id => parseInt(id)) |
| | | : [] |
| | | |
| | | const newJudge = { |
| | | id: judge.id, |
| | |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // 清空选择 |
| | | selectedJudges.value = [] |
| | | selectedStageOptions.value = [] |
| | | |
| | | judgeDialogVisible.value = false |
| | | ElMessage.success(`成功添加 ${addedCount} 个评委`) |
| | |
| | | } |
| | | |
| | | const getLastStage = (student) => { |
| | | if (!student.lastStageId || !form.value.stages) return '无' |
| | | if (!student.lastStageId || !form.value || !form.value.stages) return '无' |
| | | const stage = form.value.stages.find(s => s.id === student.lastStageId) |
| | | return stage ? stage.name : '无' |
| | | } |
| | |
| | | uploaded: false // 标记为未上传 |
| | | } |
| | | |
| | | if (form.value && form.value.mediaFiles) { |
| | | form.value.mediaFiles.push(mediaFile) |
| | | ElMessage.success('文件已选择,点击更新按钮时将上传') |
| | | } |
| | | |
| | | return false // 阻止el-upload的默认上传 |
| | | } |
| | | |
| | | const beforeMediaUpload = (file) => { |
| | | if (!form.value || !form.value.mediaFiles) return false |
| | | |
| | | if (form.value.mediaFiles.length >= 3) { |
| | | ElMessage.error('最多只能上传3个文件!') |
| | | return false |
| | |
| | | |
| | | // 处理媒体文件上传 |
| | | const handleMediaUpload = async (activityId) => { |
| | | if (!form.value || !form.value.mediaFiles) return |
| | | |
| | | try { |
| | | for (const mediaFile of form.value.mediaFiles) { |
| | | // 跳过已经有 id 的媒体文件(已保存的) |
| | |
| | | } |
| | | |
| | | try { |
| | | console.log('开始上传文件:', mediaFile.name) |
| | | // 1. 上传文件到服务器 |
| | | const uploadResult = await uploadFile(mediaFile.file) |
| | | console.log('文件上传成功:', uploadResult) |
| | | |
| | | // 2. 保存媒体信息到数据库 |
| | | const mediaInput = { |
| | |
| | | targetType: MediaTargetType.ACTIVITY, // 活动 |
| | | targetId: parseInt(activityId) // 转换为数字类型 |
| | | } |
| | | |
| | | console.log('准备保存媒体信息:', mediaInput) |
| | | console.log('活动ID:', activityId) |
| | | const savedMedia = await saveMedia(mediaInput) |
| | | console.log(`媒体文件 ${mediaFile.name} 上传并保存成功:`, savedMedia) |
| | | |
| | | // 更新媒体文件信息 |
| | | mediaFile.id = savedMedia.id |
| | |
| | | // 提交表单 |
| | | const handleSubmit = async () => { |
| | | if (submitting.value) return |
| | | if (!form.value) return |
| | | |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | |
| | | |
| | | // 准备保存数据,只包含后端支持的字段 |
| | | const saveData = { |
| | | id: form.value.id, |
| | | pid: form.value.pid || 0, |
| | | name: form.value.name, |
| | | description: form.value.description, |
| | | signupDeadline: form.value.signupDeadline, |
| | |
| | | ratingSchemeId: form.value.ratingSchemeId, |
| | | playerMax: form.value.playerMax, |
| | | state: form.value.state || 1, |
| | | stages: form.value.stages ? form.value.stages.map(stage => ({ |
| | | id: stage.id, |
| | | stages: form.value.stages ? form.value.stages.map(stage => { |
| | | const stageData = { |
| | | name: stage.name, |
| | | description: stage.description, |
| | | matchTime: stage.matchTime, |
| | | address: stage.address, |
| | | ratingSchemeId: stage.ratingSchemeId, |
| | | playerMax: stage.playerMax, |
| | | sortOrder: stage.sortOrder, |
| | | state: stage.state || 1 |
| | | })) : [], |
| | | judges: form.value.judges ? form.value.judges.map(judge => ({ |
| | | } |
| | | // 只在有有效ID时才添加id字段 |
| | | if (stage.id) { |
| | | stageData.id = stage.id |
| | | } |
| | | // 只在有有效ratingSchemeId时才添加该字段 |
| | | if (stage.ratingSchemeId) { |
| | | stageData.ratingSchemeId = stage.ratingSchemeId |
| | | } |
| | | return stageData |
| | | }) : [], |
| | | judges: form.value.judges ? form.value.judges.filter(judge => judge.id && judge.name).map(judge => ({ |
| | | judgeId: judge.id, |
| | | judgeName: judge.name, |
| | | stageIds: judge.stageIds || [] |
| | | })) : [] |
| | | } |
| | | |
| | | // 如果是编辑模式,添加id字段 |
| | | if (isEdit.value && form.value.id) { |
| | | saveData.id = form.value.id |
| | | } |
| | | |
| | | // 如果有pid,添加pid字段 |
| | | if (form.value.pid) { |
| | | saveData.pid = form.value.pid |
| | | } |
| | | |
| | | const result = await saveActivity(saveData) |
| | |
| | | await loadActivity() |
| | | |
| | | // 如果是新建模式且没有阶段,自动创建一个阶段 |
| | | if (!isEdit.value && form.value.stages.length === 0) { |
| | | if (!isEdit.value && form.value && form.value.stages && form.value.stages.length === 0) { |
| | | onStageCountChange(1) |
| | | } |
| | | }) |
| | |
| | | flex: 1; |
| | | padding-left: 8px; |
| | | } |
| | | |
| | | /* 媒体描述文本样式 */ |
| | | .media-description { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | font-weight: normal; |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | /* 媒体上传按钮 */ |
| | | .media-uploader-left { |
| | | margin-left: 16px; |
| | | } |
| | | |
| | | .media-container { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | </style> |