feat: improve judge invite venue assignment UI
- Add venue selection in batch import dialog - Add individual venue assignment button per row - Display '所有场地' for chief_judge role - Hide venue assignment button for chief_judge - Change action column to vertical layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -196,3 +196,34 @@ export const exportSchedule = (competitionId) => {
|
|||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出比赛时间汇总版
|
||||||
|
* @param {Number} competitionId - 赛事ID
|
||||||
|
* @param {Number} venueId - 场地ID(可选)
|
||||||
|
* @param {String} scheduleDate - 比赛日期(可选)
|
||||||
|
*/
|
||||||
|
export const exportScheduleSummary = (competitionId, venueId, scheduleDate) => {
|
||||||
|
return request({
|
||||||
|
url: '/martial/export/schedule/summary',
|
||||||
|
method: 'get',
|
||||||
|
params: { competitionId, venueId, scheduleDate },
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出竞赛分组详细版
|
||||||
|
* @param {Number} competitionId - 赛事ID
|
||||||
|
* @param {Number} venueId - 场地ID(可选)
|
||||||
|
* @param {String} scheduleDate - 比赛日期(可选)
|
||||||
|
*/
|
||||||
|
export const exportScheduleDetail = (competitionId, venueId, scheduleDate) => {
|
||||||
|
return request({
|
||||||
|
url: '/martial/export/schedule/detail',
|
||||||
|
method: 'get',
|
||||||
|
params: { competitionId, venueId, scheduleDate },
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -264,3 +264,16 @@ export const getInviteByJudge = (competitionId, judgeId) => {
|
|||||||
params: { competitionId, judgeId }
|
params: { competitionId, judgeId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新裁判场地
|
||||||
|
* @param {Number} inviteId - 邀请ID
|
||||||
|
* @param {Number} venueId - 场地ID
|
||||||
|
*/
|
||||||
|
export const updateInviteVenue = (inviteId, venueId) => {
|
||||||
|
return request({
|
||||||
|
url: `/api/martial/judgeInvite/updateVenue/${inviteId}`,
|
||||||
|
method: 'put',
|
||||||
|
params: { venueId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -143,6 +143,11 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="venueName" label="负责场地" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.role === 'chief_judge' ? '所有场地' : (row.venueName || "-") }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="refereeType" label="裁判类型" align="center">
|
<el-table-column prop="refereeType" label="裁判类型" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.refereeType === 1 ? 'danger' : 'primary'" size="small">
|
<el-tag :type="row.refereeType === 1 ? 'danger' : 'primary'" size="small">
|
||||||
@@ -152,14 +157,19 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="contactPhone" label="联系电话" />
|
<el-table-column prop="contactPhone" label="联系电话" />
|
||||||
<el-table-column prop="contactEmail" label="擅长项目" show-overflow-tooltip />
|
<el-table-column prop="contactEmail" label="擅长项目" show-overflow-tooltip />
|
||||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||||
|
<el-button v-if="row.role !== 'chief_judge'" link type="warning" :icon="Edit" @click="handleAssignVenue(row)">
|
||||||
|
分配场地
|
||||||
|
</el-button>
|
||||||
<el-button link type="primary" :icon="View" @click="handleView(row)">
|
<el-button link type="primary" :icon="View" @click="handleView(row)">
|
||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -220,6 +230,21 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<el-button :icon="Refresh" @click="handleJudgeReset">重置</el-button>
|
<el-button :icon="Refresh" @click="handleJudgeReset">重置</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="分配场地">
|
||||||
|
<el-select
|
||||||
|
v-model="selectedVenueId"
|
||||||
|
placeholder="请选择场地"
|
||||||
|
:loading="venueLoading"
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="venue in venueList"
|
||||||
|
:key="venue.id"
|
||||||
|
:label="venue.venueName"
|
||||||
|
:value="venue.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<!-- 裁判列表 -->
|
<!-- 裁判列表 -->
|
||||||
@@ -279,6 +304,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 分配场地对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="venueDialogVisible"
|
||||||
|
title="分配场地"
|
||||||
|
width="400px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<el-form label-width="80px">
|
||||||
|
<el-form-item label="裁判姓名">
|
||||||
|
<span>{{ currentInvite?.judgeName }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="当前场地">
|
||||||
|
<span>{{ currentInvite?.venueName || "-" }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="选择场地" required>
|
||||||
|
<el-select
|
||||||
|
v-model="newVenueId"
|
||||||
|
placeholder="请选择场地"
|
||||||
|
:loading="venueLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="venue in venueList"
|
||||||
|
:key="venue.id"
|
||||||
|
:label="venue.venueName"
|
||||||
|
:value="venue.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="venueDialogVisible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!newVenueId"
|
||||||
|
:loading="venueUpdating"
|
||||||
|
@click="handleConfirmVenue"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -292,6 +360,7 @@ import {
|
|||||||
Download,
|
Download,
|
||||||
FolderOpened,
|
FolderOpened,
|
||||||
View,
|
View,
|
||||||
|
Edit,
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import {
|
import {
|
||||||
getJudgeInviteList,
|
getJudgeInviteList,
|
||||||
@@ -300,10 +369,12 @@ import {
|
|||||||
generateInviteCode,
|
generateInviteCode,
|
||||||
batchGenerateInviteCode,
|
batchGenerateInviteCode,
|
||||||
regenerateInviteCode,
|
regenerateInviteCode,
|
||||||
removeInvite
|
removeInvite,
|
||||||
|
updateInviteVenue
|
||||||
} from '@/api/martial/judgeInvite'
|
} from '@/api/martial/judgeInvite'
|
||||||
import { getCompetitionList } from '@/api/martial/competition'
|
import { getCompetitionList } from '@/api/martial/competition'
|
||||||
import { getRefereeList } from '@/api/martial/referee'
|
import { getRefereeList } from '@/api/martial/referee'
|
||||||
|
import { getVenuesByCompetition } from '@/api/martial/venue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
// 数据状态
|
// 数据状态
|
||||||
@@ -328,6 +399,17 @@ const judgeQueryParams = reactive({
|
|||||||
refereeType: null
|
refereeType: null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 场地选择
|
||||||
|
const venueList = ref([])
|
||||||
|
const selectedVenueId = ref(null)
|
||||||
|
const venueLoading = ref(false)
|
||||||
|
|
||||||
|
// 分配场地对话框
|
||||||
|
const venueDialogVisible = ref(false)
|
||||||
|
const currentInvite = ref(null)
|
||||||
|
const newVenueId = ref(null)
|
||||||
|
const venueUpdating = ref(false)
|
||||||
|
|
||||||
// 统计数据
|
// 统计数据
|
||||||
const statistics = ref({
|
const statistics = ref({
|
||||||
totalInvites: 0,
|
totalInvites: 0,
|
||||||
@@ -456,6 +538,8 @@ const handleImportFromPool = async () => {
|
|||||||
|
|
||||||
// 打开裁判选择对话框
|
// 打开裁判选择对话框
|
||||||
judgeDialogVisible.value = true
|
judgeDialogVisible.value = true
|
||||||
|
selectedVenueId.value = null
|
||||||
|
loadVenueList()
|
||||||
selectedJudges.value = []
|
selectedJudges.value = []
|
||||||
loadJudgeList()
|
loadJudgeList()
|
||||||
}
|
}
|
||||||
@@ -503,6 +587,26 @@ const handleJudgeSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 裁判搜索重置
|
// 裁判搜索重置
|
||||||
|
// 加载场地列表
|
||||||
|
const loadVenueList = async () => {
|
||||||
|
if (!queryParams.competitionId) return
|
||||||
|
venueLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getVenuesByCompetition(queryParams.competitionId)
|
||||||
|
const data = res.data?.data || res.data || {}
|
||||||
|
venueList.value = data.records || []
|
||||||
|
// 默认选择第一个场地
|
||||||
|
if (venueList.value.length > 0 && !selectedVenueId.value) {
|
||||||
|
selectedVenueId.value = venueList.value[0].id
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载场地列表失败:", error)
|
||||||
|
ElMessage.error("加载场地列表失败")
|
||||||
|
} finally {
|
||||||
|
venueLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleJudgeReset = () => {
|
const handleJudgeReset = () => {
|
||||||
judgeQueryParams.name = ''
|
judgeQueryParams.name = ''
|
||||||
judgeQueryParams.phone = ''
|
judgeQueryParams.phone = ''
|
||||||
@@ -541,6 +645,7 @@ const handleConfirmImport = async () => {
|
|||||||
competitionId: queryParams.competitionId,
|
competitionId: queryParams.competitionId,
|
||||||
judgeIds: judgeIds,
|
judgeIds: judgeIds,
|
||||||
role: 'judge',
|
role: 'judge',
|
||||||
|
venueId: selectedVenueId.value,
|
||||||
expireDays: 30
|
expireDays: 30
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -690,6 +795,48 @@ const handleRegenerateCode = async (row) => {
|
|||||||
/**
|
/**
|
||||||
* 删除邀请记录
|
* 删除邀请记录
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开分配场地对话框
|
||||||
|
*/
|
||||||
|
const handleAssignVenue = async (row) => {
|
||||||
|
currentInvite.value = row
|
||||||
|
newVenueId.value = row.venueId > 0 ? row.venueId : null
|
||||||
|
venueDialogVisible.value = true
|
||||||
|
// 加载场地列表
|
||||||
|
if (venueList.value.length === 0) {
|
||||||
|
await loadVenueList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认分配场地
|
||||||
|
*/
|
||||||
|
const handleConfirmVenue = async () => {
|
||||||
|
if (!newVenueId.value) {
|
||||||
|
ElMessage.warning("请选择场地")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
venueUpdating.value = true
|
||||||
|
const res = await updateInviteVenue(currentInvite.value.id, newVenueId.value)
|
||||||
|
|
||||||
|
if (res.data?.data || res.data?.success) {
|
||||||
|
ElMessage.success("场地分配成功")
|
||||||
|
venueDialogVisible.value = false
|
||||||
|
await fetchData()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data?.msg || "分配失败")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("分配场地失败:", error)
|
||||||
|
ElMessage.error(error.response?.data?.msg || error.message || "分配场地失败")
|
||||||
|
} finally {
|
||||||
|
venueUpdating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = async (row) => {
|
const handleDelete = async (row) => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
|
|||||||
@@ -276,7 +276,18 @@
|
|||||||
|
|
||||||
<div class="footer-actions">
|
<div class="footer-actions">
|
||||||
<el-button size="small" @click="handleSaveDraft" v-if="!isScheduleCompleted">保存草稿</el-button>
|
<el-button size="small" @click="handleSaveDraft" v-if="!isScheduleCompleted">保存草稿</el-button>
|
||||||
<el-button size="small" @click="handleExport" v-if="isScheduleCompleted">导出</el-button>
|
<el-dropdown v-if="isScheduleCompleted" @command="handleExportCommand" style="margin-right: 10px;">
|
||||||
|
<el-button size="small">
|
||||||
|
导出 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item command="summary">比赛时间汇总版</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="detail">竞赛分组详细版</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="schedule" divided>赛程表(原版)</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
<el-button size="small" type="primary" @click="handleConfirm" v-if="!isScheduleCompleted">完成编排</el-button>
|
<el-button size="small" type="primary" @click="handleConfirm" v-if="!isScheduleCompleted">完成编排</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -380,7 +391,7 @@
|
|||||||
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
||||||
import { getVenuesByCompetition } from '@/api/martial/venue'
|
import { getVenuesByCompetition } from '@/api/martial/venue'
|
||||||
import { getCompetitionDetail } from '@/api/martial/competition'
|
import { getCompetitionDetail } from '@/api/martial/competition'
|
||||||
import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule, triggerAutoArrange, moveScheduleGroup, exportSchedule } from '@/api/martial/activitySchedule'
|
import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule, triggerAutoArrange, moveScheduleGroup, exportSchedule, exportScheduleSummary, exportScheduleDetail } from '@/api/martial/activitySchedule'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MartialScheduleList',
|
name: 'MartialScheduleList',
|
||||||
@@ -1113,6 +1124,42 @@ export default {
|
|||||||
group.items.splice(itemIndex + 1, 0, temp)
|
group.items.splice(itemIndex + 1, 0, temp)
|
||||||
this.$message.success('下移成功')
|
this.$message.success('下移成功')
|
||||||
},
|
},
|
||||||
|
async handleExportCommand(command) {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
let res, filename
|
||||||
|
const competitionName = this.competitionInfo.competitionName || this.competitionId
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case "summary":
|
||||||
|
res = await exportScheduleSummary(this.competitionId)
|
||||||
|
filename = `比赛时间_${competitionName}.xlsx`
|
||||||
|
break
|
||||||
|
case "detail":
|
||||||
|
res = await exportScheduleDetail(this.competitionId)
|
||||||
|
filename = `竞赛分组_${competitionName}.xlsx`
|
||||||
|
break
|
||||||
|
case "schedule":
|
||||||
|
default:
|
||||||
|
res = await exportSchedule(this.competitionId)
|
||||||
|
filename = `赛程表_${competitionName}.xlsx`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([res.data], { type: "application/vnd.ms-excel" })
|
||||||
|
const link = document.createElement("a")
|
||||||
|
link.href = window.URL.createObjectURL(blob)
|
||||||
|
link.download = filename
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(link.href)
|
||||||
|
this.$message.success("导出成功")
|
||||||
|
} catch (error) {
|
||||||
|
console.error("导出失败:", error)
|
||||||
|
this.$message.error("导出失败,请稍后重试")
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
async handleExport() {
|
async handleExport() {
|
||||||
try {
|
try {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|||||||
Reference in New Issue
Block a user