fix: venue tab filtering using String() comparison for large IDs
- Fixed JavaScript precision loss issue with large venue IDs - Changed Number() to String() comparison in filteredCompetitionGroups - Changed Number() to String() comparison in venueData computed property - Added ExportPreview component Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot]
parent
c04193bf84
commit
d7af8d1394
@@ -188,6 +188,15 @@ export const saveDispatch = (data) => {
|
||||
* 导出赛程表
|
||||
* @param {Number} competitionId - 赛事ID
|
||||
*/
|
||||
// 预览赛程表数据
|
||||
export const getScheduleExportPreview = (competitionId, template, venueId) => {
|
||||
return request({
|
||||
url: "/api/martial/export/schedule/preview",
|
||||
method: "get",
|
||||
params: { competitionId, template, venueId }
|
||||
})
|
||||
}
|
||||
|
||||
export const exportSchedule = (competitionId) => {
|
||||
return request({
|
||||
url: '/martial/export/schedule',
|
||||
@@ -197,7 +206,7 @@ export const exportSchedule = (competitionId) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Export schedule template 2 (competition time format)
|
||||
|
||||
export const exportScheduleTemplate2 = (competitionId, venueId, venueName, timeSlot) => {
|
||||
return request({
|
||||
url: '/martial/export/schedule2',
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="导出预览"
|
||||
:model-value="visible"
|
||||
@update:model-value="handleVisibleChange"
|
||||
width="90%"
|
||||
top="5vh"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="export-options">
|
||||
<el-form :inline="true" size="small">
|
||||
<el-form-item label="导出模板">
|
||||
<el-select v-model="exportTemplate" placeholder="选择模板" style="width: 200px">
|
||||
<el-option label="模板1-详细赛程表" value="template1" />
|
||||
<el-option label="模板2-比赛时间表" value="template2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="场地" v-if="exportTemplate === 'template2'">
|
||||
<el-select v-model="selectedVenue" placeholder="选择场地" style="width: 150px">
|
||||
<el-option label="全部场地" :value="null" />
|
||||
<el-option v-for="venue in venues" :key="venue.id" :label="venue.venueName" :value="venue.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadPreviewData" :loading="loading">刷新预览</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="preview-table" v-loading="loading">
|
||||
<el-table v-if="exportTemplate === 'template1'" :data="previewData" border stripe max-height="500" style="width: 100%">
|
||||
<el-table-column prop="scheduleDate" label="比赛日期" width="120" />
|
||||
<el-table-column prop="timeSlot" label="时间段" width="100" />
|
||||
<el-table-column prop="venueName" label="场地" width="120" />
|
||||
<el-table-column prop="projectName" label="项目名称" width="180" />
|
||||
<el-table-column prop="category" label="组别" width="80" />
|
||||
<el-table-column prop="athleteName" label="运动员姓名" width="120" />
|
||||
<el-table-column prop="teamName" label="单位/队伍" width="150" />
|
||||
<el-table-column prop="sortOrder" label="出场顺序" width="90" />
|
||||
<el-table-column prop="status" label="状态" width="80" />
|
||||
</el-table>
|
||||
|
||||
<el-table v-if="exportTemplate === 'template2'" :data="previewData" border stripe max-height="500" style="width: 100%">
|
||||
<el-table-column prop="sequenceNo" label="序号" width="80" />
|
||||
<el-table-column prop="projectName" label="项目" width="200" />
|
||||
<el-table-column prop="participantCount" label="人数" width="80" />
|
||||
<el-table-column prop="groupCount" label="组数" width="80" />
|
||||
<el-table-column prop="durationMinutes" label="时间" width="80" />
|
||||
<el-table-column prop="tableNo" label="表号" width="100" />
|
||||
</el-table>
|
||||
|
||||
<div v-if="previewData.length === 0 && !loading" class="empty-tip">暂无数据</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-stats" v-if="previewData.length > 0">
|
||||
<span>共 {{ previewData.length }} 条记录</span>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleExport" :loading="exporting">确认导出</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getScheduleExportPreview, exportSchedule, exportScheduleTemplate2 } from '@/api/martial/activitySchedule'
|
||||
|
||||
export default {
|
||||
name: 'ExportPreview',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
competitionId: { type: [String, Number], required: true },
|
||||
competitionName: { type: String, default: '' },
|
||||
venues: { type: Array, default: () => [] }
|
||||
},
|
||||
emits: ['update:visible'],
|
||||
data() {
|
||||
return {
|
||||
exportTemplate: 'template1',
|
||||
selectedVenue: null,
|
||||
previewData: [],
|
||||
loading: false,
|
||||
exporting: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) { if (val) this.loadPreviewData() },
|
||||
exportTemplate() { this.loadPreviewData() },
|
||||
selectedVenue() { if (this.exportTemplate === 'template2') this.loadPreviewData() }
|
||||
},
|
||||
methods: {
|
||||
handleVisibleChange(val) { this.$emit('update:visible', val) },
|
||||
async loadPreviewData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getScheduleExportPreview(this.competitionId, this.exportTemplate, this.selectedVenue)
|
||||
this.previewData = res.data?.data || res.data || []
|
||||
} catch (error) {
|
||||
console.error('加载预览数据失败:', error)
|
||||
this.$message.error('加载预览数据失败')
|
||||
this.previewData = []
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async handleExport() {
|
||||
this.exporting = true
|
||||
try {
|
||||
let res, filename
|
||||
if (this.exportTemplate === 'template1') {
|
||||
res = await exportSchedule(this.competitionId)
|
||||
filename = '赛程表_' + (this.competitionName || this.competitionId) + '.xlsx'
|
||||
} else {
|
||||
const venue = this.venues.find(v => v.id === this.selectedVenue)
|
||||
const venueName = venue ? venue.venueName : null
|
||||
res = await exportScheduleTemplate2(this.competitionId, this.selectedVenue, venueName, null)
|
||||
filename = '比赛时间_' + (venueName || '全部场地') + '_' + (this.competitionName || this.competitionId) + '.xlsx'
|
||||
}
|
||||
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('导出成功')
|
||||
this.handleClose()
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
this.$message.error('导出失败')
|
||||
} finally {
|
||||
this.exporting = false
|
||||
}
|
||||
},
|
||||
handleClose() { this.$emit('update:visible', false) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.export-options { margin-bottom: 20px; padding: 15px; background: #f5f7fa; border-radius: 4px; }
|
||||
.preview-table { min-height: 300px; }
|
||||
.preview-stats { margin-top: 15px; color: #666; font-size: 14px; }
|
||||
.empty-tip { text-align: center; padding: 60px 0; color: #999; }
|
||||
</style>
|
||||
@@ -7,12 +7,13 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<el-button
|
||||
v-if="!isScheduleCompleted"
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="autoArrangeLoading"
|
||||
@click="handleAutoArrange"
|
||||
>
|
||||
{{ isScheduleCompleted ? '重新编排' : '自动编排' }}
|
||||
自动编排
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="showExceptionDialog">
|
||||
异常组 <el-badge :value="exceptionList.length" :hidden="exceptionList.length === 0" />
|
||||
@@ -277,15 +278,7 @@
|
||||
|
||||
<div class="footer-actions">
|
||||
<el-button size="small" @click="handleSaveDraft" v-if="!isScheduleCompleted">保存草稿</el-button>
|
||||
<el-dropdown v-if="isScheduleCompleted" @command="handleExportCommand" trigger="click">
|
||||
<el-button size="small">导出 <i class="el-icon-arrow-down el-icon--right"></i></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="template1">模板1 - 详细赛程表</el-dropdown-item>
|
||||
<el-dropdown-item command="template2">模板2 - 比赛时间表</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button v-if="isScheduleCompleted" size="small" @click="exportPreviewVisible = true">导出</el-button>
|
||||
<el-button size="small" type="primary" @click="handleConfirm" v-if="!isScheduleCompleted">完成编排</el-button>
|
||||
</div>
|
||||
|
||||
@@ -382,6 +375,13 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 导出预览对话框 -->
|
||||
<ExportPreview
|
||||
v-model:visible="exportPreviewVisible"
|
||||
:competition-id="competitionId"
|
||||
:competition-name="competitionInfo.competitionName"
|
||||
:venues="venues"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -391,10 +391,12 @@ import { getVenuesByCompetition } from '@/api/martial/venue'
|
||||
import { getCompetitionDetail } from '@/api/martial/competition'
|
||||
import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule, triggerAutoArrange, moveScheduleGroup, exportSchedule, exportScheduleTemplate2 } from '@/api/martial/activitySchedule'
|
||||
import { updateCheckInStatus, getScheduleConfig } from '@/api/martial/schedulePlan'
|
||||
import ExportPreview from './ExportPreview.vue'
|
||||
|
||||
export default {
|
||||
name: 'MartialScheduleList',
|
||||
components: {
|
||||
ExportPreview,
|
||||
ArrowDown,
|
||||
ArrowRight
|
||||
},
|
||||
@@ -408,6 +410,7 @@ export default {
|
||||
selectedVenueIdForVenueTab: null, // 选中的场地ID(场地Tab)
|
||||
confirmDialogVisible: false,
|
||||
isScheduleCompleted: false, // 是否已完成编排
|
||||
exportPreviewVisible: false, // 导出预览对话框
|
||||
loading: false,
|
||||
autoArrangeLoading: false, // 自动编排加载状态
|
||||
venues: [], // 场地列表(从后端加载)
|
||||
@@ -448,7 +451,7 @@ export default {
|
||||
|
||||
const filtered = this.competitionGroups.filter(group => {
|
||||
console.log(`分组"${group.title}": venueId=${group.venueId}(${typeof group.venueId}), timeSlotIndex=${group.timeSlotIndex}(${typeof group.timeSlotIndex})`)
|
||||
const venueMatch = group.venueId === this.selectedVenueId || Number(group.venueId) === Number(this.selectedVenueId)
|
||||
const venueMatch = String(group.venueId) === String(this.selectedVenueId)
|
||||
const timeMatch = group.timeSlotIndex === this.selectedTime
|
||||
console.log(` 场地匹配:${venueMatch}, 时间匹配:${timeMatch}`)
|
||||
return venueMatch && timeMatch
|
||||
@@ -472,8 +475,7 @@ export default {
|
||||
// 如果选中了场地,进一步过滤
|
||||
if (this.selectedVenueIdForVenueTab) {
|
||||
groupsInTimeSlot = groupsInTimeSlot.filter(group => {
|
||||
return group.venueId === this.selectedVenueIdForVenueTab ||
|
||||
Number(group.venueId) === Number(this.selectedVenueIdForVenueTab)
|
||||
return String(group.venueId) === String(this.selectedVenueIdForVenueTab)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user