实现赛程表导出功能

- 添加 exportSchedule API 调用后端导出接口
- 实现 handleExport 方法,支持下载 Excel 文件
- 文件名格式:赛程表_赛事名称.xlsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
DevOps
2025-12-24 13:45:09 +08:00
parent ea4650b912
commit 694b955cef
5 changed files with 2395 additions and 3 deletions

View File

@@ -183,3 +183,16 @@ export const saveDispatch = (data) => {
data data
}) })
} }
/**
* 导出赛程表
* @param {Number} competitionId - 赛事ID
*/
export const exportSchedule = (competitionId) => {
return request({
url: '/martial/export/schedule',
method: 'get',
params: { competitionId },
responseType: 'blob'
})
}

View File

@@ -0,0 +1,355 @@
<template>
<div class="martial-order-container">
<el-card shadow="hover">
<div class="page-header">
<h2 class="page-title">订单管理</h2>
</div>
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item>
<el-input
v-model="searchForm.keyword"
placeholder="搜索赛事名称"
clearable
size="small"
style="width: 240px"
>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-select
v-model="searchForm.status"
placeholder="赛事状态"
clearable
size="small"
style="width: 180px"
>
<el-option label="未开始" :value="1"></el-option>
<el-option label="报名中" :value="2"></el-option>
<el-option label="进行中" :value="3"></el-option>
<el-option label="已结束" :value="4"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearch">查询</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableData"
border
stripe
size="small"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
<el-table-column prop="competitionName" label="赛事名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="competitionCode" label="赛事编号" width="150"></el-table-column>
<el-table-column prop="organizer" label="主办单位" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="location" label="举办地点" width="120"></el-table-column>
<el-table-column prop="registrationTime" label="报名时间" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ formatDateRange(scope.row.registrationStartTime, scope.row.registrationEndTime) }}</span>
</template>
</el-table-column>
<el-table-column prop="competitionTime" label="比赛时间" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ formatDateRange(scope.row.competitionStartTime, scope.row.competitionEndTime) }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)" size="small">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="320" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleRegistrationDetail(scope.row)">报名详情</el-button>
<el-button type="success" size="small" @click="handleSchedule(scope.row)">编排</el-button>
<el-button
type="warning"
size="small"
@click="handleDispatch(scope.row)"
:title="isScheduleCompleted(scope.row.id) ? '进入调度' : '请先完成编排'"
>
调度
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.size"
layout="total, sizes, prev, pager, next"
:total="pagination.total"
small
></el-pagination>
</el-card>
</div>
</template>
<script>
import { getCompetitionList } from '@/api/martial/competition'
import { getScheduleResult } from '@/api/martial/activitySchedule'
export default {
name: 'MartialOrderList',
data() {
return {
loading: false,
searchForm: {
keyword: '',
status: null
},
tableData: [],
pagination: {
current: 1,
size: 10,
total: 0
},
scheduleStatusMap: {} // 存储每个赛事的编排状态
}
},
mounted() {
this.loadCompetitionList()
},
activated() {
// 当页面被激活时(从其他页面返回),重新加载编排状态
if (this.tableData.length > 0) {
this.loadScheduleStatus()
}
},
methods: {
// 加载赛事列表
loadCompetitionList() {
this.loading = true
const params = {}
if (this.searchForm.keyword) {
params.competitionName = this.searchForm.keyword
}
if (this.searchForm.status !== null && this.searchForm.status !== '') {
params.status = this.searchForm.status
}
getCompetitionList(this.pagination.current, this.pagination.size, params)
.then(res => {
console.log('赛事列表返回数据:', res)
const responseData = res.data?.data
if (responseData && responseData.records) {
// 处理赛事数据,兼容驼峰和下划线命名
this.tableData = responseData.records.map(competition => ({
id: competition.id,
competitionName: competition.competitionName || competition.competition_name,
competitionCode: competition.competitionCode || competition.competition_code,
organizer: competition.organizer,
location: competition.location,
venue: competition.venue,
registrationStartTime: competition.registrationStartTime || competition.registration_start_time,
registrationEndTime: competition.registrationEndTime || competition.registration_end_time,
competitionStartTime: competition.competitionStartTime || competition.competition_start_time,
competitionEndTime: competition.competitionEndTime || competition.competition_end_time,
status: competition.status,
createTime: competition.createTime || competition.create_time
}))
this.pagination.total = responseData.total || 0
// 加载每个赛事的编排状态
this.loadScheduleStatus()
}
})
.catch(err => {
console.error('加载赛事列表失败', err)
this.$message.error('加载赛事列表失败')
})
.finally(() => {
this.loading = false
})
},
// 加载编排状态
async loadScheduleStatus() {
for (const competition of this.tableData) {
try {
const res = await getScheduleResult(competition.id)
if (res.data?.data) {
this.$set(this.scheduleStatusMap, competition.id, res.data.data.isCompleted || false)
} else {
this.$set(this.scheduleStatusMap, competition.id, false)
}
} catch (err) {
// 如果获取失败,默认为未完成
this.$set(this.scheduleStatusMap, competition.id, false)
}
}
},
// 检查编排是否完成
isScheduleCompleted(competitionId) {
return this.scheduleStatusMap[competitionId] === true
},
handleSearch() {
this.pagination.current = 1
this.loadCompetitionList()
},
handleSizeChange(size) {
this.pagination.size = size
this.pagination.current = 1
this.loadCompetitionList()
},
handleCurrentChange(current) {
this.pagination.current = current
this.loadCompetitionList()
},
// 查看报名详情 - 传递赛事ID
handleRegistrationDetail(row) {
this.$router.push({
path: '/martial/registration/detail',
query: { competitionId: row.id }
})
},
// 编排 - 传递赛事ID
handleSchedule(row) {
this.$router.push({
path: '/martial/schedule/list',
query: { competitionId: row.id }
})
},
// 调度 - 传递赛事ID
handleDispatch(row) {
// 检查编排是否完成
if (!this.isScheduleCompleted(row.id)) {
this.$message.warning('请先完成编排后再进行调度')
return
}
this.$router.push({
path: '/martial/dispatch/list',
query: { competitionId: row.id }
})
},
// 格式化日期范围
formatDateRange(startTime, endTime) {
if (!startTime || !endTime) return '-'
// 简单格式化,只显示日期部分
const start = startTime.split(' ')[0]
const end = endTime.split(' ')[0]
return `${start} ~ ${end}`
},
getStatusType(status) {
const statusMap = {
1: 'info', // 未开始
2: 'success', // 报名中
3: 'warning', // 进行中
4: 'info' // 已结束
}
return statusMap[status] || 'info'
},
getStatusText(status) {
const statusMap = {
1: '未开始',
2: '报名中',
3: '进行中',
4: '已结束'
}
return statusMap[status] || '未知'
}
}
}
</script>
<style lang="scss" scoped>
.martial-order-container {
min-height: 100%;
padding: 15px;
background: #fff;
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.page-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #2c3e50;
}
}
.tip-message {
display: flex;
gap: 12px;
padding: 10px 14px;
margin-bottom: 15px;
background: linear-gradient(90deg, #ffd54f 0%, #ffecb3 100%);
border: 1px solid #ffc107;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(255, 193, 7, 0.2);
.tip-header {
flex-shrink: 0;
.tip-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
background: #ff9800;
color: #fff;
font-size: 11px;
font-weight: bold;
border-radius: 50%;
}
}
.tip-content {
flex: 1;
color: #5d4037;
font-size: 12px;
line-height: 1.5;
.tip-subtitle {
margin-top: 3px;
color: #6d4c41;
font-size: 11px;
font-style: italic;
}
}
}
.search-form {
margin-bottom: 15px;
}
.amount-text {
color: #e6a23c;
font-weight: 600;
font-size: 14px;
}
.pagination {
margin-top: 15px;
text-align: right;
}
}
</style>

View File

@@ -0,0 +1,990 @@
<template>
<div class="participant-container">
<!-- 列表视图 -->
<div v-if="currentView === 'list'" class="list-view">
<el-card shadow="hover">
<div class="list-header">
<h2 class="page-title">参赛选手管理</h2>
<el-button type="primary" icon="el-icon-plus" @click="handleCreate">
添加选手
</el-button>
</div>
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item>
<el-input
v-model="searchForm.keyword"
placeholder="搜索选手姓名"
clearable
size="small"
style="width: 240px"
>
<template #prefix>
<i class="el-input__icon el-icon-search"></i>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-select v-model="searchForm.competitionId" placeholder="选择赛事" clearable size="small" style="width: 200px">
<el-option label="全部赛事" :value="null" />
<el-option
v-for="item in allCompetitionOptions"
:key="item.id"
:label="item.competitionName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearch">查询</el-button>
<el-button size="small" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="displayList"
border
stripe
style="width: 100%"
class="data-table"
>
<el-table-column
type="index"
label="序号"
width="60"
align="center"
/>
<el-table-column
prop="playerName"
label="选手姓名"
width="120"
/>
<el-table-column
prop="competitionName"
label="所属赛事"
min-width="180"
show-overflow-tooltip
/>
<el-table-column
prop="projectName"
label="参赛项目"
width="120"
/>
<el-table-column
prop="category"
label="组别"
width="100"
/>
<el-table-column
label="性别"
width="80"
align="center"
>
<template #default="scope">
<el-tag :type="scope.row.gender === 1 ? 'primary' : 'danger'" size="small">
{{ scope.row.gender === 1 ? '男' : '女' }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="age"
label="年龄"
width="80"
align="center"
/>
<el-table-column
prop="organization"
label="所属单位"
min-width="150"
show-overflow-tooltip
/>
<el-table-column
prop="contactPhone"
label="联系电话"
width="120"
/>
<el-table-column
prop="orderNum"
label="出场顺序"
width="100"
align="center"
/>
<el-table-column
label="操作"
width="220"
fixed="right"
align="center"
>
<template #default="scope">
<el-button
type="primary"
link
size="small"
icon="el-icon-view"
@click="handleView(scope.row)"
>
查看
</el-button>
<el-button
type="warning"
link
size="small"
icon="el-icon-edit"
@click="handleEdit(scope.row)"
>
编辑
</el-button>
<el-button
type="danger"
link
size="small"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-if="pagination.total > 0"
@size-change="handleSizeChange"
@current-change="handlePageChange"
:current-page="pagination.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.size"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top: 20px; text-align: right"
/>
</el-card>
</div>
<!-- 表单视图 -->
<div v-else class="form-view">
<el-card shadow="hover" v-loading="loading">
<div class="page-header">
<el-button
icon="el-icon-arrow-left"
@click="backToList"
>
返回列表
</el-button>
<h2 class="page-title">{{ pageTitle }}</h2>
<div class="header-actions" v-if="currentView !== 'view'">
<el-button @click="backToList">取消</el-button>
<el-button type="primary" @click="handleSave">
{{ currentView === 'create' ? '创建' : '保存' }}
</el-button>
</div>
<div class="header-actions" v-else>
<el-button type="primary" @click="switchToEdit">编辑</el-button>
</div>
</div>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
:disabled="currentView === 'view'"
class="participant-form"
>
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-user"></i>
基本信息
</div>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="选手姓名" prop="playerName">
<el-input
v-model="formData.playerName"
placeholder="请输入选手姓名"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="formData.gender">
<el-radio :label="1"></el-radio>
<el-radio :label="2"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄" prop="age">
<el-input-number
v-model="formData.age"
:min="6"
:max="100"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="身份证号" prop="idCard">
<el-input
v-model="formData.idCard"
placeholder="请输入身份证号"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input
v-model="formData.contactPhone"
placeholder="请输入联系电话"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="所属单位" prop="organization">
<el-input
v-model="formData.organization"
placeholder="请输入所属单位"
/>
</el-form-item>
</div>
<!-- 赛事信息 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-trophy"></i>
赛事信息
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属赛事" prop="competitionId">
<el-select
v-model="formData.competitionId"
placeholder="请选择赛事"
style="width: 100%"
@change="handleCompetitionChange"
>
<el-option
v-for="item in availableCompetitionOptions"
:key="item.id"
:label="item.competitionName"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="参赛项目" prop="projectId">
<el-select
v-model="formData.projectId"
placeholder="请选择参赛项目"
style="width: 100%"
@change="handleProjectChange"
>
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="item.projectName"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="组别" prop="category">
<el-input
v-model="formData.category"
placeholder="例如:成年男子组"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出场顺序" prop="orderNum">
<el-input-number
v-model="formData.orderNum"
:min="1"
:max="9999"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 其他信息 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-document"></i>
其他信息
</div>
<el-form-item label="选手简介" prop="introduction">
<el-input
v-model="formData.introduction"
type="textarea"
:rows="4"
placeholder="请输入选手简介"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
/>
</el-form-item>
</div>
</el-form>
</el-card>
</div>
</div>
</template>
<script>
import { getCompetitionList } from '@/api/martial/competition'
import { getInfoPublishList } from '@/api/martial/infoPublish'
import { getProjectsByCompetition } from '@/api/martial/project'
import {
getParticipantList,
getParticipantDetail,
addParticipant,
updateParticipant,
removeParticipant
} from '@/api/martial/participant'
export default {
name: 'ParticipantManagement',
data() {
return {
loading: false,
currentView: 'list', // list, create, edit, view
participantId: null,
searchForm: {
keyword: '',
competitionId: null
},
pagination: {
current: 1,
size: 10,
total: 0
},
competitionOptions: [], // 已发布的可报名赛事列表(用于新建)
allCompetitionOptions: [], // 所有赛事列表(用于搜索和编辑)
projectOptions: [], // 项目列表
participantList: [],
formData: {
orderId: null,
competitionId: null,
competitionName: '',
playerName: '',
gender: 1,
age: null,
contactPhone: '',
organization: '',
idCard: '',
projectId: null,
category: '',
orderNum: 1,
introduction: '',
remark: '',
attachments: []
},
formRules: {
playerName: [
{ required: true, message: '请输入选手姓名', trigger: 'blur' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
age: [
{ required: true, message: '请输入年龄', trigger: 'blur' }
],
contactPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
competitionId: [
{ required: true, message: '请选择赛事', trigger: 'change' }
],
projectId: [
{ required: true, message: '请选择参赛项目', trigger: 'change' }
]
}
};
},
computed: {
pageTitle() {
const titleMap = {
create: '添加参赛选手',
edit: '编辑参赛选手',
view: '查看参赛选手'
};
return titleMap[this.currentView] || '参赛选手信息';
},
displayList() {
return this.participantList;
},
// 根据不同模式返回不同的赛事选项
availableCompetitionOptions() {
// 编辑和查看模式:显示所有赛事(因为可能编辑已过报名期的选手)
if (this.currentView === 'edit' || this.currentView === 'view') {
return this.allCompetitionOptions;
}
// 新建模式:只显示可报名的赛事
return this.competitionOptions;
}
},
watch: {
'$route.query': {
handler(query) {
this.initPage();
},
immediate: true
}
},
mounted() {
this.loadAvailableCompetitions();
this.loadAllCompetitions();
},
methods: {
initPage() {
const { mode, id } = this.$route.query;
this.currentView = mode || 'list';
// 不使用 parseInt保持 ID 为字符串避免精度丢失
this.participantId = id || null;
if (this.currentView === 'list') {
this.loadParticipantList();
} else if (this.currentView !== 'list' && this.participantId) {
this.loadParticipantData();
} else if (this.currentView === 'create') {
this.resetFormData();
}
},
// 加载可报名的赛事(从已发布的信息中获取)
loadAvailableCompetitions() {
getInfoPublishList(1, 100, { isPublished: 1 })
.then(res => {
console.log('已发布信息列表返回数据:', res);
const responseData = res.data?.data;
if (responseData && responseData.records) {
const publishedCompetitionIds = new Set(
responseData.records
.filter(item => item.competitionId)
.map(item => item.competitionId)
);
console.log('已发布的赛事ID列表:', Array.from(publishedCompetitionIds));
if (publishedCompetitionIds.size > 0) {
this.loadPublishedCompetitions(Array.from(publishedCompetitionIds));
} else {
// 如果没有发布信息,直接加载所有赛事作为可报名赛事
console.log('没有已发布信息,加载所有赛事');
this.loadPublishedCompetitions([]);
}
}
})
.catch(err => {
console.error('加载已发布信息列表失败', err);
// 出错时也加载所有赛事
this.loadPublishedCompetitions([]);
});
},
// 加载已发布的赛事详细信息,并过滤出可报名的赛事
loadPublishedCompetitions(competitionIds) {
getCompetitionList(1, 100, {})
.then(res => {
console.log('赛事列表返回数据:', res);
const responseData = res.data?.data;
if (responseData && responseData.records) {
const now = new Date();
this.competitionOptions = responseData.records
.filter(item => {
// 如果没有发布信息competitionIds为空数组则显示所有在报名期内的赛事
if (competitionIds.length > 0 && !competitionIds.includes(item.id)) {
return false;
}
// 检查报名时间
if (!item.registrationStartTime || !item.registrationEndTime) {
return false;
}
const regStart = new Date(item.registrationStartTime);
const regEnd = new Date(item.registrationEndTime);
return now >= regStart && now <= regEnd;
})
.map(item => ({
id: item.id,
competitionName: item.competitionName,
registrationStartTime: item.registrationStartTime,
registrationEndTime: item.registrationEndTime
}));
console.log('可报名的赛事列表:', this.competitionOptions);
if (this.competitionOptions.length === 0) {
console.log('当前没有可以报名的赛事(报名时间范围外)');
}
}
})
.catch(err => {
console.error('加载赛事列表失败', err);
this.$message.error('加载赛事列表失败');
});
},
// 加载所有赛事(用于搜索过滤)
loadAllCompetitions() {
getCompetitionList(1, 100, {})
.then(res => {
const responseData = res.data?.data;
if (responseData && responseData.records) {
this.allCompetitionOptions = responseData.records.map(item => ({
id: item.id,
competitionName: item.competitionName
}));
}
})
.catch(err => {
console.error('加载所有赛事失败', err);
});
},
loadParticipantList() {
this.loading = true;
const params = {};
if (this.searchForm.keyword) {
params.playerName = this.searchForm.keyword;
}
if (this.searchForm.competitionId) {
params.competitionId = this.searchForm.competitionId;
}
getParticipantList(null, this.pagination.current, this.pagination.size, params)
.then(res => {
console.log('参赛人员列表返回数据:', res);
const responseData = res.data?.data;
if (responseData && responseData.records) {
this.participantList = responseData.records;
this.pagination.total = responseData.total || 0;
}
})
.catch(err => {
console.error('加载参赛人员列表失败', err);
this.$message.error('加载参赛人员列表失败');
})
.finally(() => {
this.loading = false;
});
},
loadParticipantData() {
if (!this.participantId) return;
this.loading = true;
getParticipantDetail(this.participantId)
.then(res => {
const detailData = res.data?.data;
if (detailData) {
this.formData = { ...detailData };
// 将 attachments 字符串转换为数组(前端需要数组格式)
if (typeof this.formData.attachments === 'string') {
try {
this.formData.attachments = JSON.parse(this.formData.attachments);
} catch (e) {
console.warn('解析 attachments 失败,使用空数组', e);
this.formData.attachments = [];
}
} else if (!this.formData.attachments) {
this.formData.attachments = [];
}
// 加载该赛事的项目列表
if (detailData.competitionId) {
this.loadProjectsByCompetition(detailData.competitionId);
}
}
})
.catch(err => {
console.error('加载参赛人员详情失败', err);
this.$message.error('加载参赛人员详情失败');
})
.finally(() => {
this.loading = false;
});
},
handlePageChange(current) {
this.pagination.current = current;
this.loadParticipantList();
},
handleSizeChange(size) {
this.pagination.size = size;
this.pagination.current = 1;
this.loadParticipantList();
},
handleSearch() {
this.pagination.current = 1;
this.loadParticipantList();
},
handleReset() {
this.searchForm = {
keyword: '',
competitionId: null
};
this.pagination.current = 1;
this.loadParticipantList();
},
handleCreate() {
this.$router.push({
path: '/martial/participant/list',
query: { mode: 'create' }
});
},
handleView(row) {
this.$router.push({
path: '/martial/participant/list',
query: { mode: 'view', id: row.id }
});
},
handleEdit(row) {
this.$router.push({
path: '/martial/participant/list',
query: { mode: 'edit', id: row.id }
});
},
switchToEdit() {
this.$router.push({
path: '/martial/participant/list',
query: { mode: 'edit', id: this.participantId }
});
},
handleDelete(row) {
this.$confirm('确定要删除该选手吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true;
removeParticipant(row.id.toString())
.then(res => {
this.$message.success('删除成功');
this.loadParticipantList();
})
.catch(err => {
console.error('删除失败', err);
this.$message.error('删除失败');
})
.finally(() => {
this.loading = false;
});
}).catch(() => {});
},
handleCompetitionChange(competitionId) {
// 从可用的选项列表中查找赛事
const competition = this.availableCompetitionOptions.find(item => item.id === competitionId);
if (competition) {
this.formData.competitionName = competition.competitionName;
}
// 加载该赛事的项目列表
this.loadProjectsByCompetition(competitionId);
// 清空已选项目
this.formData.projectId = null;
},
handleProjectChange(projectId) {
const project = this.projectOptions.find(item => item.id === projectId);
if (project) {
// 自动填充组别信息
if (project.category && !this.formData.category) {
this.formData.category = project.category;
}
}
},
loadProjectsByCompetition(competitionId) {
if (!competitionId) {
this.projectOptions = [];
return;
}
console.log('加载赛事项目赛事ID:', competitionId);
getProjectsByCompetition(competitionId)
.then(res => {
console.log('项目列表返回数据:', res);
const responseData = res.data?.data;
// 兼容两种数据格式分页数据有records和直接数组
let projectList = [];
if (responseData) {
if (Array.isArray(responseData)) {
// 直接是数组
projectList = responseData;
console.log('返回的是直接数组,长度:', projectList.length);
} else if (responseData.records && Array.isArray(responseData.records)) {
// 分页数据
projectList = responseData.records;
console.log('返回的是分页数据,记录数:', projectList.length);
} else {
console.warn('未知的数据格式:', responseData);
}
}
if (projectList.length > 0) {
this.projectOptions = projectList.map(item => ({
id: item.id,
projectName: item.projectName,
projectCode: item.projectCode,
category: item.category
}));
console.log('可选项目列表:', this.projectOptions);
} else {
this.projectOptions = [];
console.log('该赛事没有项目数据');
this.$message.warning('该赛事还没有配置项目,请先添加项目');
}
})
.catch(err => {
console.error('加载项目列表失败', err);
this.$message.error('加载项目列表失败: ' + (err.message || '未知错误'));
this.projectOptions = [];
});
},
handleSave() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.loading = true;
// 确保有赛事名称
if (!this.formData.competitionName) {
const competition = this.availableCompetitionOptions.find(item => item.id === this.formData.competitionId);
if (competition) {
this.formData.competitionName = competition.competitionName;
}
}
const submitData = { ...this.formData };
console.log('=== 提交前的 formData ===', this.formData);
console.log('formData.orderId:', this.formData.orderId);
// 将 attachments 数组转换为 JSON 字符串(后端需要 String 类型)
if (Array.isArray(submitData.attachments)) {
submitData.attachments = JSON.stringify(submitData.attachments);
}
// 临时方案: 如果没有 orderId使用 competitionId 作为 orderId
// 警告: 这是临时解决方案,后续应修改数据库表结构或后端逻辑
if (!submitData.orderId && submitData.competitionId) {
submitData.orderId = submitData.competitionId;
console.warn('⚠️ 临时方案: 使用 competitionId 作为 orderId', submitData.competitionId);
}
console.log('=== 提交的数据 submitData ===', submitData);
console.log('submitData.orderId:', submitData.orderId);
if (this.currentView === 'create') {
// 新建
addParticipant(submitData)
.then(res => {
this.$message.success('添加成功');
this.backToList();
})
.catch(err => {
console.error('添加失败', err);
this.$message.error('添加失败');
})
.finally(() => {
this.loading = false;
});
} else if (this.currentView === 'edit') {
// 编辑
submitData.id = this.participantId;
updateParticipant(submitData)
.then(res => {
this.$message.success('保存成功');
this.backToList();
})
.catch(err => {
console.error('保存失败', err);
this.$message.error('保存失败');
})
.finally(() => {
this.loading = false;
});
}
} else {
this.$message.error('请完善必填信息');
}
});
},
backToList() {
this.$router.push({
path: '/martial/participant/list'
});
},
resetFormData() {
this.formData = {
orderId: null,
competitionId: null,
competitionName: '',
playerName: '',
gender: 1,
age: null,
contactPhone: '',
organization: '',
idCard: '',
projectId: null,
category: '',
orderNum: 1,
introduction: '',
remark: '',
attachments: []
};
}
}
};
</script>
<style lang="scss" scoped>
.participant-container {
padding: 20px;
background: #fff;
border-radius: 8px;
min-height: calc(100vh - 120px);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.page-title {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #333;
}
.search-form {
margin-bottom: 20px;
}
.data-table {
:deep(.el-table__header) {
th {
background-color: #fafafa;
color: #333;
font-weight: 600;
}
}
:deep(.el-button--text) {
padding: 0 8px;
}
}
.page-header {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
.page-title {
flex: 1;
margin: 0;
font-size: 24px;
font-weight: 600;
color: #333;
}
.header-actions {
display: flex;
gap: 10px;
}
}
.participant-form {
max-width: 1200px;
}
.form-section {
margin-bottom: 30px;
padding: 20px;
background: #fafafa;
border-radius: 8px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
font-size: 16px;
font-weight: 600;
color: #333;
i {
color: #dc2626;
font-size: 18px;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
</style>

View File

@@ -380,7 +380,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 } from '@/api/martial/activitySchedule' import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule, triggerAutoArrange, moveScheduleGroup, exportSchedule } from '@/api/martial/activitySchedule'
export default { export default {
name: 'MartialScheduleList', name: 'MartialScheduleList',
@@ -1113,8 +1113,23 @@ export default {
group.items.splice(itemIndex + 1, 0, temp) group.items.splice(itemIndex + 1, 0, temp)
this.$message.success('下移成功') this.$message.success('下移成功')
}, },
handleExport() { async handleExport() {
this.$message.success('导出功能开发中') try {
this.loading = true
const res = await exportSchedule(this.competitionId)
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = `赛程表_${this.competitionInfo.competitionName || this.competitionId}.xlsx`
link.click()
window.URL.revokeObjectURL(link.href)
this.$message.success('导出成功')
} catch (error) {
console.error('导出失败:', error)
this.$message.error('导出失败,请稍后重试')
} finally {
this.loading = false
}
}, },
handleConfirm() { handleConfirm() {
this.confirmDialogVisible = true this.confirmDialogVisible = true

File diff suppressed because it is too large Load Diff