fix bugs
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-12-11 16:56:19 +08:00
parent ab69968bda
commit 5b806e29b7
45 changed files with 13744 additions and 6364 deletions

View File

@@ -8,12 +8,10 @@ import request from '@/axios';
* @param {Number} size - 每页条数,默认10
* @param {Object} params - 查询参数
* @param {Number} params.competitionId - 赛事ID
* @param {String} params.activityDate - 活动日期(可选)
* @param {Number} params.activityType - 活动类型(可选)
*/
export const getActivityScheduleList = (current, size, params) => {
return request({
url: '/api/blade-martial/activitySchedule/list',
url: '/martial/activitySchedule/list',
method: 'get',
params: {
current,
@@ -29,42 +27,27 @@ export const getActivityScheduleList = (current, size, params) => {
*/
export const getActivityScheduleDetail = (id) => {
return request({
url: '/api/blade-martial/activitySchedule/detail',
url: '/martial/activitySchedule/detail',
method: 'get',
params: { id }
})
}
/**
* 新增活动日程
* 新增或修改活动日程
* @param {Object} data - 活动日程数据
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.activityDate - 活动日期
* @param {String} data.startTime - 开始时间
* @param {String} data.endTime - 结束时间
* @param {Number} data.activityType - 活动类型1开幕式2闭幕式3比赛4培训5会议6其他
* @param {String} data.activityName - 活动名称
* @param {String} data.activityLocation - 活动地点
* @param {String} data.activityDescription - 活动描述
* @param {String} data.organizer - 组织者
* @param {String} data.participants - 参与人员
* @param {Number} data.sortOrder - 排序序号
* @param {String} data.competitionId - 赛事ID
* @param {String} data.scheduleDate - 日程日期
* @param {String} data.scheduleTime - 日程时间
* @param {String} data.eventName - 活动项目
* @param {String} data.venue - 地点
* @param {String} data.description - 描述
* @param {String} data.remark - 备注
* @param {Number} data.sortOrder - 排序
*/
export const addActivity = (data) => {
export const submitActivitySchedule = (data) => {
return request({
url: '/api/blade-martial/activitySchedule/save',
method: 'post',
data
})
}
/**
* 修改活动日程
* @param {Object} data - 活动日程数据
*/
export const updateActivity = (data) => {
return request({
url: '/api/blade-martial/activitySchedule/update',
url: '/martial/activitySchedule/submit',
method: 'post',
data
})
@@ -74,218 +57,52 @@ export const updateActivity = (data) => {
* 删除活动日程
* @param {String} ids - 活动日程ID,多个用逗号分隔
*/
export const removeActivity = (ids) => {
export const removeActivitySchedule = (ids) => {
return request({
url: '/api/blade-martial/activitySchedule/remove',
url: '/martial/activitySchedule/remove',
method: 'post',
params: { ids }
})
}
/**
* 批量添加活动日程
* @param {Array} data - 活动日程数据数组
*/
export const batchAddActivities = (data) => {
return request({
url: '/api/blade-martial/activitySchedule/batch-save',
method: 'post',
data
})
}
// ==================== 赛程编排接口 ====================
/**
* 获取某日期的活动日程
* @param {Number} competitionId - 赛事ID
* @param {String} activityDate - 活动日期
*/
export const getActivitiesByDate = (competitionId, activityDate) => {
return request({
url: '/api/blade-martial/activitySchedule/list-by-date',
method: 'get',
params: { competitionId, activityDate }
})
}
/**
* 获取日期范围内的活动日程
* @param {Number} competitionId - 赛事ID
* @param {String} startDate - 开始日期
* @param {String} endDate - 结束日期
*/
export const getActivitiesByDateRange = (competitionId, startDate, endDate) => {
return request({
url: '/api/blade-martial/activitySchedule/list-by-range',
method: 'get',
params: { competitionId, startDate, endDate }
})
}
/**
* 获取某类型的活动日程
* @param {Number} competitionId - 赛事ID
* @param {Number} activityType - 活动类型
*/
export const getActivitiesByType = (competitionId, activityType) => {
return request({
url: '/api/blade-martial/activitySchedule/list-by-type',
method: 'get',
params: { competitionId, activityType }
})
}
/**
* 获取赛事的所有活动日程(不分页)
* 获取赛程编排结果
* @param {Number} competitionId - 赛事ID
*/
export const getAllActivities = (competitionId) => {
export const getScheduleResult = (competitionId) => {
return request({
url: '/api/blade-martial/activitySchedule/all',
method: 'get',
params: { competitionId }
})
}
/**
* 调整活动日程顺序
* @param {Object} data - 调整参数
* @param {Number} data.id - 活动日程ID
* @param {Number} data.targetOrder - 目标顺序
*/
export const adjustActivityOrder = (data) => {
return request({
url: '/api/blade-martial/activitySchedule/adjust-order',
method: 'post',
data
})
}
/**
* 检查活动时间冲突
* @param {Object} data - 检查参数
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.activityDate - 活动日期
* @param {String} data.startTime - 开始时间
* @param {String} data.endTime - 结束时间
* @param {String} data.activityLocation - 活动地点
* @param {Number} data.excludeId - 排除的活动ID编辑时使用
*/
export const checkActivityConflict = (data) => {
return request({
url: '/api/blade-martial/activitySchedule/check-conflict',
method: 'post',
data
})
}
/**
* 复制活动日程到其他日期
* @param {Object} data - 复制参数
* @param {Number} data.sourceActivityId - 源活动ID
* @param {String} data.targetDate - 目标日期
*/
export const copyActivity = (data) => {
return request({
url: '/api/blade-martial/activitySchedule/copy',
method: 'post',
data
})
}
/**
* 获取活动日程日历视图数据
* @param {Number} competitionId - 赛事ID
* @param {String} month - 月份格式YYYY-MM
*/
export const getActivityCalendar = (competitionId, month) => {
return request({
url: '/api/blade-martial/activitySchedule/calendar',
method: 'get',
params: { competitionId, month }
})
}
/**
* 发布活动日程
* @param {Number} id - 活动日程ID
*/
export const publishActivity = (id) => {
return request({
url: '/api/blade-martial/activitySchedule/publish',
method: 'post',
params: { id }
})
}
/**
* 取消活动日程
* @param {Number} id - 活动日程ID
* @param {String} cancelReason - 取消原因
*/
export const cancelActivity = (id, cancelReason) => {
return request({
url: '/api/blade-martial/activitySchedule/cancel',
method: 'post',
params: { id },
data: { cancelReason }
})
}
/**
* 完成活动日程
* @param {Number} id - 活动日程ID
* @param {String} completionNote - 完成备注
*/
export const completeActivity = (id, completionNote) => {
return request({
url: '/api/blade-martial/activitySchedule/complete',
method: 'post',
params: { id },
data: { completionNote }
})
}
/**
* 导出活动日程
* @param {Object} params - 导出参数
*/
export const exportActivities = (params) => {
return request({
url: '/api/blade-martial/activitySchedule/export',
method: 'get',
params,
responseType: 'blob'
})
}
/**
* 导入活动日程
* @param {Number} competitionId - 赛事ID
* @param {File} file - Excel文件
*/
export const importActivities = (competitionId, file) => {
const formData = new FormData()
formData.append('competitionId', competitionId)
formData.append('file', file)
return request({
url: '/api/blade-martial/activitySchedule/import',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/**
* 打印活动日程表
* @param {Number} competitionId - 赛事ID
*/
export const printActivitySchedule = (competitionId) => {
return request({
url: '/api/blade-martial/activitySchedule/print',
url: '/martial/schedule/result',
method: 'get',
params: { competitionId },
responseType: 'blob'
timeout: 30000 // 设置30秒超时,因为编排数据较大
})
}
/**
* 保存并锁定赛程编排
* @param {Number} competitionId - 赛事ID
*/
export const saveAndLockSchedule = (competitionId) => {
return request({
url: '/martial/schedule/save-and-lock',
method: 'post',
data: { competitionId }
})
}
/**
* 保存编排草稿
* @param {Object} data - 编排草稿数据
* @param {Number} data.competitionId - 赛事ID
* @param {Boolean} data.isDraft - 是否为草稿
* @param {Array} data.competitionGroups - 竞赛分组数据
*/
export const saveDraftSchedule = (data) => {
return request({
url: '/martial/schedule/save-draft',
method: 'post',
data
})
}

View File

@@ -10,7 +10,7 @@ import request from '@/axios';
*/
export const getBannerList = (current, size, params) => {
return request({
url: '/api/blade-martial/banner/list',
url: '/api/martial/banner/list',
method: 'get',
params: {
current,
@@ -26,31 +26,26 @@ export const getBannerList = (current, size, params) => {
*/
export const getBannerDetail = (id) => {
return request({
url: '/api/blade-martial/banner/detail',
url: '/api/martial/banner/detail',
method: 'get',
params: { id }
})
}
/**
* 新增轮播图
* 新增或修改轮播图
* @param {Object} data - 轮播图数据
* @param {String} data.title - 轮播图标题
* @param {Number} data.position - 显示位置(1-首页,2-赛事详情,3-其他)
* @param {String} data.imageUrl - 轮播图图片URL
* @param {String} data.linkUrl - 跳转链接
* @param {Number} data.sortOrder - 排序顺序
* @param {String} data.startTime - 开始显示时间
* @param {String} data.endTime - 结束显示时间
*/
export const addBanner = (data) => {
export const submitBanner = (data) => {
return request({
url: '/api/blade-martial/banner/save',
method: 'post',
data
})
}
/**
* 修改轮播图
* @param {Object} data - 轮播图数据
*/
export const updateBanner = (data) => {
return request({
url: '/api/blade-martial/banner/update',
url: '/api/martial/banner/submit',
method: 'post',
data
})
@@ -62,31 +57,8 @@ export const updateBanner = (data) => {
*/
export const removeBanner = (ids) => {
return request({
url: '/api/blade-martial/banner/remove',
url: '/api/martial/banner/remove',
method: 'post',
params: { ids }
})
}
/**
* 获取启用的轮播图列表(小程序端使用)
*/
export const getActiveBannerList = () => {
return request({
url: '/api/blade-martial/banner/active-list',
method: 'get'
})
}
/**
* 修改轮播图状态
* @param {Number} id - 轮播图ID
* @param {Number} status - 状态0-禁用 1-启用)
*/
export const updateBannerStatus = (id, status) => {
return request({
url: '/api/blade-martial/banner/update-status',
method: 'post',
params: { id, status }
})
}

View File

@@ -1,5 +1,80 @@
import request from '@/axios';
// ==================== 赛事管理接口 ====================
/**
* 赛事列表查询
* @param {Number} current - 当前页
* @param {Number} size - 每页条数
* @param {Object} params - 查询参数
*/
export const getCompetitionList = (current, size, params) => {
return request({
url: '/api/martial/competition/list',
method: 'get',
params: {
current,
size,
...params
}
})
}
/**
* 获取赛事详情
* @param {Number} id - 赛事ID
*/
export const getCompetitionDetail = (id) => {
return request({
url: '/api/martial/competition/detail',
method: 'get',
params: { id }
})
}
/**
* 新增或修改赛事
* @param {Object} data - 赛事数据
* @param {Number} data.id - ID修改时必传
* @param {String} data.competitionName - 赛事名称
* @param {String} data.competitionCode - 赛事编码
* @param {String} data.organizer - 主办单位
* @param {String} data.location - 地区
* @param {String} data.venue - 详细地点
* @param {String} data.registrationStartTime - 报名开始时间
* @param {String} data.registrationEndTime - 报名结束时间
* @param {String} data.competitionStartTime - 比赛开始时间
* @param {String} data.competitionEndTime - 比赛结束时间
* @param {String} data.introduction - 赛事简介
* @param {String} data.posterImages - 宣传图片(JSON)
* @param {String} data.contactPerson - 联系人
* @param {String} data.contactPhone - 联系电话
* @param {String} data.contactEmail - 联系邮箱
* @param {String} data.rules - 竞赛规则
* @param {String} data.requirements - 参赛要求
* @param {String} data.awards - 奖项设置
* @param {String} data.regulationFiles - 规程文件(JSON)
*/
export const submitCompetition = (data) => {
return request({
url: '/api/martial/competition/submit',
method: 'post',
data
})
}
/**
* 删除赛事
* @param {String} ids - 赛事ID,多个用逗号分隔
*/
export const removeCompetition = (ids) => {
return request({
url: '/api/martial/competition/remove',
method: 'post',
params: { ids }
})
}
// ==================== 武术赛事订单管理接口 ====================
/**
@@ -255,89 +330,3 @@ export const removeVenue = (ids) => {
params: { ids }
})
}
// ==================== 赛事管理接口 ====================
/**
* 新增赛事
* @param {Object} data - 赛事数据
* @param {String} data.competitionName - 赛事名称
* @param {String} data.organizer - 主办单位
* @param {String} data.location - 地区
* @param {String} data.venue - 详细地点
* @param {String} data.registrationStartTime - 报名开始时间
* @param {String} data.registrationEndTime - 报名结束时间
* @param {String} data.competitionStartTime - 比赛开始时间
* @param {String} data.competitionEndTime - 比赛结束时间
* @param {String} data.introduction - 赛事简介
* @param {Array} data.posterImages - 宣传图片
* @param {String} data.contactPerson - 联系人
* @param {String} data.contactPhone - 联系电话
* @param {String} data.contactEmail - 联系邮箱
* @param {String} data.rules - 竞赛规则
* @param {String} data.requirements - 参赛要求
* @param {String} data.awards - 奖项设置
* @param {Array} data.regulationFiles - 规程文件
* @param {Array} data.schedule - 活动日程
*/
export const addCompetition = (data) => {
return request({
url: '/api/blade-martial/competition/save',
method: 'post',
data
})
}
/**
* 赛事列表查询
* @param {Number} current - 当前页
* @param {Number} size - 每页条数
* @param {Object} params - 查询参数
*/
export const getCompetitionList = (current, size, params) => {
return request({
url: '/api/blade-martial/competition/list',
method: 'get',
params: {
current,
size,
...params
}
})
}
/**
* 获取赛事详情
* @param {Number} id - 赛事ID
*/
export const getCompetitionDetail = (id) => {
return request({
url: '/api/blade-martial/competition/detail',
method: 'get',
params: { id }
})
}
/**
* 修改赛事
* @param {Object} data - 赛事数据
*/
export const updateCompetition = (data) => {
return request({
url: '/api/blade-martial/competition/update',
method: 'post',
data
})
}
/**
* 删除赛事
* @param {String} ids - 赛事ID,多个用逗号分隔
*/
export const removeCompetition = (ids) => {
return request({
url: '/api/blade-martial/competition/remove',
method: 'post',
params: { ids }
})
}

View File

@@ -14,7 +14,7 @@ import request from '@/axios';
*/
export const getInfoPublishList = (current, size, params) => {
return request({
url: '/api/blade-martial/infoPublish/list',
url: '/api/martial/infoPublish/list',
method: 'get',
params: {
current,
@@ -30,15 +30,16 @@ export const getInfoPublishList = (current, size, params) => {
*/
export const getInfoPublishDetail = (id) => {
return request({
url: '/api/blade-martial/infoPublish/detail',
url: '/api/martial/infoPublish/detail',
method: 'get',
params: { id }
})
}
/**
* 发布信息
* 新增或修改信息(提交)
* @param {Object} data - 信息数据
* @param {Number} data.id - ID修改时必传
* @param {Number} data.competitionId - 赛事ID
* @param {Number} data.infoType - 信息类型1公告2通知3新闻4规则5其他
* @param {String} data.title - 标题
@@ -50,21 +51,9 @@ export const getInfoPublishDetail = (id) => {
* @param {Number} data.isImportant - 是否重要0否1是
* @param {String} data.publishTime - 发布时间(可选,为空则立即发布)
*/
export const publishInfo = (data) => {
export const submitInfo = (data) => {
return request({
url: '/api/blade-martial/infoPublish/publish',
method: 'post',
data
})
}
/**
* 修改信息
* @param {Object} data - 信息数据
*/
export const updateInfo = (data) => {
return request({
url: '/api/blade-martial/infoPublish/update',
url: '/api/martial/infoPublish/submit',
method: 'post',
data
})
@@ -76,7 +65,7 @@ export const updateInfo = (data) => {
*/
export const removeInfo = (ids) => {
return request({
url: '/api/blade-martial/infoPublish/remove',
url: '/api/martial/infoPublish/remove',
method: 'post',
params: { ids }
})

143
src/api/martial/order.js Normal file
View File

@@ -0,0 +1,143 @@
import request from '@/axios';
// ==================== 订单管理接口 ====================
// 注意:后端实际路径为 /martial/registrationOrder
/**
* 订单分页查询
* @param {Number} current - 当前页,默认1
* @param {Number} size - 每页条数,默认10
* @param {Object} params - 查询参数
* @param {String} params.keyword - 关键词(订单号/用户名)
* @param {Number} params.status - 订单状态(0-待支付,1-已支付,2-已取消,3-已退款)
* @param {Number} params.competitionId - 赛事ID
*/
export const getOrderList = (current, size, params = {}) => {
return request({
url: '/api/martial/registrationOrder/list',
method: 'get',
params: {
current,
size,
...params
}
})
}
/**
* 获取订单详情
* @param {Number} id - 订单主键ID
*/
export const getOrderDetail = (id) => {
return request({
url: '/api/martial/registrationOrder/detail',
method: 'get',
params: { id }
})
}
/**
* 创建订单
* @param {Object} data - 订单数据
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.userName - 用户名
* @param {String} data.userPhone - 用户手机号
* @param {Number} data.amount - 订单金额
* @param {Array} data.participants - 参赛人员列表
*/
export const createOrder = (data) => {
return request({
url: '/api/martial/registrationOrder/submit',
method: 'post',
data
})
}
/**
* 更新订单状态
* @param {Number} id - 订单ID
* @param {Number} status - 订单状态
*/
export const updateOrderStatus = (id, status) => {
return request({
url: '/api/martial/registrationOrder/update-status',
method: 'post',
params: { id, status }
})
}
/**
* 删除订单
* @param {String} ids - 订单ID,多个用逗号分隔
*/
export const removeOrder = (ids) => {
return request({
url: '/api/martial/registrationOrder/remove',
method: 'post',
params: { ids }
})
}
/**
* 获取订单报名详情(包含参赛人员、项目统计等)
* @param {Number} orderId - 订单ID
* 注意:此接口后端暂未实现,需要添加
*/
export const getOrderRegistrationDetail = (orderId) => {
return request({
url: '/api/martial/registrationOrder/registration-detail',
method: 'get',
params: { orderId }
})
}
/**
* 获取订单的参赛人员列表
* @param {Number} orderId - 订单ID可选
* @param {Number} competitionId - 赛事ID可选
* 注意:此接口后端暂未实现,可以使用 martial/athlete/list 接口替代
*/
export const getOrderParticipants = (orderIdOrCompetitionId) => {
// 支持传入订单ID或赛事ID
const params = { current: 1, size: 10000 }
// 判断参数类型如果是对象直接使用否则判断是orderId还是competitionId
if (typeof orderIdOrCompetitionId === 'object') {
Object.assign(params, orderIdOrCompetitionId)
} else if (orderIdOrCompetitionId) {
// 默认作为competitionId使用
params.competitionId = orderIdOrCompetitionId
}
return request({
url: '/api/martial/athlete/list',
method: 'get',
params
})
}
/**
* 获取订单的项目统计
* @param {Number} orderId - 订单ID
* 注意:此接口后端暂未实现,需要添加
*/
export const getOrderProjectStats = (orderId) => {
return request({
url: '/api/martial/registrationOrder/project-stats',
method: 'get',
params: { orderId }
})
}
/**
* 获取订单的金额统计
* @param {Number} orderId - 订单ID
* 注意:此接口后端暂未实现,需要添加
*/
export const getOrderAmountStats = (orderId) => {
return request({
url: '/api/martial/registrationOrder/amount-stats',
method: 'get',
params: { orderId }
})
}

View File

@@ -11,7 +11,7 @@ import request from '@/axios';
*/
export const getParticipantList = (competitionId, current, size, params = {}) => {
return request({
url: '/api/blade-martial/participant/list',
url: '/api/martial/athlete/list',
method: 'get',
params: {
competitionId,
@@ -28,14 +28,14 @@ export const getParticipantList = (competitionId, current, size, params = {}) =>
*/
export const getParticipantDetail = (id) => {
return request({
url: '/api/blade-martial/participant/detail',
url: '/api/martial/athlete/detail',
method: 'get',
params: { id }
})
}
/**
* 新增参赛选手
* 新增或修改参赛选手
* @param {Object} data - 选手数据
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.playerName - 选手姓名
@@ -53,7 +53,7 @@ export const getParticipantDetail = (id) => {
*/
export const addParticipant = (data) => {
return request({
url: '/api/blade-martial/participant/save',
url: '/api/martial/athlete/submit',
method: 'post',
data
})
@@ -65,7 +65,7 @@ export const addParticipant = (data) => {
*/
export const updateParticipant = (data) => {
return request({
url: '/api/blade-martial/participant/update',
url: '/api/martial/athlete/submit',
method: 'post',
data
})
@@ -77,7 +77,7 @@ export const updateParticipant = (data) => {
*/
export const removeParticipant = (ids) => {
return request({
url: '/api/blade-martial/participant/remove',
url: '/api/martial/athlete/remove',
method: 'post',
params: { ids }
})
@@ -90,7 +90,7 @@ export const removeParticipant = (ids) => {
*/
export const updateOrder = (id, orderNum) => {
return request({
url: '/api/blade-martial/participant/update-order',
url: '/api/martial/athlete/update-order',
method: 'post',
params: { id, orderNum }
})
@@ -107,7 +107,7 @@ export const importParticipants = (competitionId, file) => {
formData.append('file', file)
return request({
url: '/api/blade-martial/participant/import',
url: '/api/martial/athlete/import',
method: 'post',
data: formData,
headers: {
@@ -122,7 +122,7 @@ export const importParticipants = (competitionId, file) => {
*/
export const exportParticipants = (competitionId) => {
return request({
url: '/api/blade-martial/participant/export',
url: '/api/martial/athlete/export',
method: 'get',
params: { competitionId },
responseType: 'blob'
@@ -135,7 +135,7 @@ export const exportParticipants = (competitionId) => {
*/
export const batchUpdateOrder = (data) => {
return request({
url: '/api/blade-martial/participant/batch-update-order',
url: '/api/martial/athlete/batch-update-order',
method: 'post',
data
})

View File

@@ -10,11 +10,10 @@ import request from '@/axios';
* @param {Number} params.competitionId - 赛事ID
* @param {String} params.projectName - 项目名称(可选)
* @param {String} params.category - 分组类别(可选)
* @param {String} params.eventType - 项目类型(可选)
*/
export const getProjectList = (current, size, params) => {
return request({
url: '/api/blade-martial/project/list',
url: '/api/martial/project/list',
method: 'get',
params: {
current,
@@ -30,41 +29,33 @@ export const getProjectList = (current, size, params) => {
*/
export const getProjectDetail = (id) => {
return request({
url: '/api/blade-martial/project/detail',
url: '/api/martial/project/detail',
method: 'get',
params: { id }
})
}
/**
* 新增项目
* 新增或修改项目
* @param {Object} data - 项目数据
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.projectName - 项目名称
* @param {String} data.projectCode - 项目编码
* @param {String} data.category - 分组类别(男子、女子、团体
* @param {String} data.eventType - 项目类型(套路、散打等)
* @param {Number} data.registrationFee - 报名费
* @param {String} data.registrationStartTime - 报名开始时间
* @param {String} data.registrationEndTime - 报名结束时间
* @param {Number} data.maxParticipants - 最大参赛人数
* @param {String} data.rules - 比赛规则
* @param {String} data.category - 别(男子组/女子组
* @param {Number} data.type - 类型(1-个人,2-双人,3-集体)
* @param {Number} data.minParticipants - 最少参赛人数
* @param {Number} data.maxParticipants - 最多参赛人数
* @param {Number} data.minAge - 最小年龄
* @param {Number} data.maxAge - 最大年龄
* @param {Number} data.genderLimit - 性别限制(0-不限,1-仅男,2-仅女)
* @param {Number} data.estimatedDuration - 预估时长(分钟)
* @param {Number} data.price - 报名费用
* @param {Number} data.difficultyCoefficient - 难度系数
* @param {String} data.description - 项目描述
*/
export const addProject = (data) => {
export const submitProject = (data) => {
return request({
url: '/api/blade-martial/project/save',
method: 'post',
data
})
}
/**
* 修改项目
* @param {Object} data - 项目数据
*/
export const updateProject = (data) => {
return request({
url: '/api/blade-martial/project/update',
url: '/api/martial/project/submit',
method: 'post',
data
})
@@ -76,63 +67,24 @@ export const updateProject = (data) => {
*/
export const removeProject = (ids) => {
return request({
url: '/api/blade-martial/project/remove',
url: '/api/martial/project/remove',
method: 'post',
params: { ids }
})
}
/**
* 获取赛事的项目列表(不分页)
* 获取赛事的项目列表(不分页,用于下拉选择
* @param {Number} competitionId - 赛事ID
*/
export const getProjectsByCompetition = (competitionId) => {
return request({
url: '/api/blade-martial/project/list-by-competition',
url: '/api/martial/project/list',
method: 'get',
params: { competitionId }
})
}
/**
* 批量导入项目
* @param {Number} competitionId - 赛事ID
* @param {File} file - Excel文件
*/
export const importProjects = (competitionId, file) => {
const formData = new FormData()
formData.append('competitionId', competitionId)
formData.append('file', file)
return request({
url: '/api/blade-martial/project/import',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
params: {
competitionId,
current: 1,
size: 1000 // 获取全部项目
}
})
}
/**
* 导出项目模板
*/
export const exportProjectTemplate = () => {
return request({
url: '/api/blade-martial/project/export-template',
method: 'get',
responseType: 'blob'
})
}
/**
* 导出项目列表
* @param {Object} params - 查询参数
*/
export const exportProjects = (params) => {
return request({
url: '/api/blade-martial/project/export',
method: 'get',
params,
responseType: 'blob'
})
}

View File

@@ -1,18 +1,19 @@
import request from '@/axios';
// ==================== 评委管理接口 ====================
// ==================== 裁判管理接口 ====================
/**
* 评委分页查询
* 裁判分页查询
* @param {Number} current - 当前页,默认1
* @param {Number} size - 每页条数,默认10
* @param {Object} params - 查询参数
* @param {String} params.keyword - 关键词搜索(姓名/手机号)
* @param {Number} params.refereeType - 裁判类型1-主裁判2-普通裁判)
* @param {String} params.name - 裁判姓名
* @param {String} params.phone - 手机号
* @param {Number} params.refereeType - 裁判类型1-裁判长2-普通裁判)
*/
export const getRefereeList = (current, size, params) => {
return request({
url: '/api/blade-martial/referee/list',
url: '/api/martial/judge/list',
method: 'get',
params: {
current,
@@ -23,69 +24,47 @@ export const getRefereeList = (current, size, params) => {
}
/**
* 获取评委详情
* @param {Number} id - 评委主键ID
* 获取裁判详情
* @param {Number} id - 裁判主键ID
*/
export const getRefereeDetail = (id) => {
return request({
url: '/api/blade-martial/referee/detail',
url: '/api/martial/judge/detail',
method: 'get',
params: { id }
})
}
/**
* 新增评委
* @param {Object} data - 评委数据
* 新增或修改裁判(统一提交接口)
* @param {Object} data - 裁判数据
* @param {Number} data.id - 主键ID编辑时传入
* @param {String} data.name - 姓名
* @param {Number} data.gender - 性别1-男2-女)
* @param {String} data.phone - 手机号
* @param {String} data.idCard - 身份证号
* @param {Number} data.refereeType - 裁判类型1-裁判2-普通裁判)
* @param {Number} data.refereeType - 裁判类型1-裁判2-普通裁判)
* @param {String} data.level - 等级/职称
* @param {String} data.specialty - 擅长项目
* @param {String} data.photoUrl - 照片URL
* @param {String} data.remark - 备注
*/
export const addReferee = (data) => {
export const submitReferee = (data) => {
return request({
url: '/api/blade-martial/referee/save',
url: '/api/martial/judge/submit',
method: 'post',
data
})
}
/**
* 修改评委
* @param {Object} data - 评委数据
*/
export const updateReferee = (data) => {
return request({
url: '/api/blade-martial/referee/update',
method: 'post',
data
})
}
/**
* 删除评委
* @param {String} ids - 评委ID,多个用逗号分隔
* 删除裁判
* @param {String} ids - 裁判ID,多个用逗号分隔
*/
export const removeReferee = (ids) => {
return request({
url: '/api/blade-martial/referee/remove',
url: '/api/martial/judge/remove',
method: 'post',
params: { ids }
})
}
/**
* 获取可用评委列表(用于下拉选择)
* @param {Number} refereeType - 裁判类型(可选)
*/
export const getAvailableReferees = (refereeType) => {
return request({
url: '/api/blade-martial/referee/available',
method: 'get',
params: { refereeType }
})
}

View File

@@ -7,10 +7,14 @@ import request from '@/axios';
* @param {Number} current - 当前页,默认1
* @param {Number} size - 每页条数,默认10
* @param {Object} params - 查询参数
* @param {Number} params.competitionId - 赛事ID
* @param {Number} params.projectId - 项目ID
* @param {Number} params.venueId - 场地ID
* @param {Number} params.athleteId - 选手ID
*/
export const getScoreList = (current, size, params) => {
return request({
url: '/api/blade-martial/score/list',
url: '/api/martial/score/list',
method: 'get',
params: {
current,
@@ -26,7 +30,7 @@ export const getScoreList = (current, size, params) => {
*/
export const getScoreDetail = (id) => {
return request({
url: '/api/blade-martial/score/detail',
url: '/api/martial/score/detail',
method: 'get',
params: { id }
})
@@ -34,75 +38,29 @@ export const getScoreDetail = (id) => {
/**
* 获取选手的所有裁判评分
* @param {Number} playerId - 选手ID
* @param {Number} projectId - 比赛项目ID
* @param {Number} athleteId - 选手ID
* @param {Number} projectId - 项目ID
*/
export const getPlayerScores = (playerId, projectId) => {
export const getAthleteScores = (athleteId, projectId) => {
return request({
url: '/api/blade-martial/score/player-scores',
url: '/api/martial/score/list',
method: 'get',
params: { playerId, projectId }
params: {
athleteId,
projectId,
current: 1,
size: 1000
}
})
}
/**
* 导出评分数据
* @param {Object} params - 查询参数
*/
export const exportScores = (params) => {
return request({
url: '/api/blade-martial/score/export',
method: 'get',
params,
responseType: 'blob'
})
}
/**
* 获取场地列表
* @param {Number} competitionId - 赛事ID
*/
export const getVenueList = (competitionId) => {
return request({
url: '/api/blade-martial/venue/list',
method: 'get',
params: { competitionId }
})
}
/**
* 获取比赛项目列表
* @param {Number} competitionId - 赛事ID
* @param {Number} venueId - 场地ID可选
*/
export const getProjectList = (competitionId, venueId) => {
return request({
url: '/api/blade-martial/project/list',
method: 'get',
params: { competitionId, venueId }
})
}
// ==================== 评分提交接口 ====================
/**
* 提交评分
* @param {Object} data - 评分数据
* @param {Number} data.competitionId - 赛事ID
* @param {Number} data.athleteId - 运动员ID
* @param {Number} data.projectId - 项目ID
* @param {Number} data.scheduleId - 赛程ID
* @param {Number} data.venueId - 场地ID
* @param {Number} data.judgeId - 裁判ID
* @param {String} data.judgeName - 裁判姓名
* @param {Number} data.score - 评分
* @param {Number} data.originalScore - 原始分
* @param {Array} data.deductionItems - 扣分项ID数组
* @param {String} data.note - 备注
*/
export const submitScore = (data) => {
return request({
url: '/api/blade-martial/score/submit',
url: '/api/martial/score/submit',
method: 'post',
data
})
@@ -114,7 +72,7 @@ export const submitScore = (data) => {
*/
export const removeScore = (ids) => {
return request({
url: '/api/blade-martial/score/remove',
url: '/api/martial/score/remove',
method: 'post',
params: { ids }
})
@@ -122,54 +80,26 @@ export const removeScore = (ids) => {
/**
* 获取异常评分列表
* @param {Object} params - 查询参数
* @param {Number} params.competitionId - 赛事ID
* @param {Number} params.projectId - 项目ID可选
* @param {Number} athleteId - 选手ID
* @param {Number} projectId - 项目ID
*/
export const getAnomalies = (params) => {
export const getAnomalies = (athleteId, projectId) => {
return request({
url: '/api/blade-martial/score/anomalies',
url: '/api/martial/score/anomalies',
method: 'get',
params
params: { athleteId, projectId }
})
}
/**
* 验证评分
* @param {Object} data - 验证数据
* @param {Number} data.athleteId - 运动员ID
* @param {Number} data.projectId - 项目ID
* @param {Number} data.score - 评分
* @param {Number} athleteId - 选手ID
* @param {Number} projectId - 项目ID
*/
export const validateScores = (data) => {
export const validateScores = (athleteId, projectId) => {
return request({
url: '/api/blade-martial/score/validate',
url: '/api/martial/score/validate',
method: 'post',
data
})
}
/**
* 批量提交评分
* @param {Array} data - 评分数据数组
*/
export const batchSubmitScores = (data) => {
return request({
url: '/api/blade-martial/score/batch-submit',
method: 'post',
data
})
}
/**
* 获取裁判待评分列表
* @param {Number} judgeId - 裁判ID
* @param {Number} competitionId - 赛事ID
*/
export const getPendingScores = (judgeId, competitionId) => {
return request({
url: '/api/blade-martial/score/pending',
method: 'get',
params: { judgeId, competitionId }
params: { athleteId, projectId }
})
}

79
src/api/martial/venue.js Normal file
View File

@@ -0,0 +1,79 @@
import request from '@/axios';
// ==================== 场地管理接口 ====================
/**
* 场地列表查询
* @param {Number} current - 当前页
* @param {Number} size - 每页条数
* @param {Object} params - 查询参数
* @param {Number} params.competitionId - 赛事ID
*/
export const getVenueList = (current, size, params) => {
return request({
url: '/api/martial/venue/list',
method: 'get',
params: {
current,
size,
...params
}
})
}
/**
* 获取场地详情
* @param {Number} id - 场地ID
*/
export const getVenueDetail = (id) => {
return request({
url: '/api/martial/venue/detail',
method: 'get',
params: { id }
})
}
/**
* 新增或修改场地
* @param {Object} data - 场地数据
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.venueName - 场地名称
* @param {Number} data.capacity - 容纳人数
* @param {String} data.location - 位置
* @param {String} data.description - 描述
*/
export const submitVenue = (data) => {
return request({
url: '/api/martial/venue/submit',
method: 'post',
data
})
}
/**
* 删除场地
* @param {String} ids - 场地ID,多个用逗号分隔
*/
export const removeVenue = (ids) => {
return request({
url: '/api/martial/venue/remove',
method: 'post',
params: { ids }
})
}
/**
* 获取赛事的场地列表(不分页)
* @param {Number} competitionId - 赛事ID
*/
export const getVenuesByCompetition = (competitionId) => {
return request({
url: '/api/martial/venue/list',
method: 'get',
params: {
competitionId,
current: 1,
size: 1000
}
})
}

View File

@@ -12,20 +12,23 @@
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item>
<el-input
v-model="searchForm.keyword"
v-model="searchForm.title"
placeholder="搜索轮播图标题"
clearable
size="small"
style="width: 240px"
>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
<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.status" placeholder="状态" clearable size="small" style="width: 120px">
<el-select v-model="searchForm.position" placeholder="显示位置" clearable size="small" style="width: 120px">
<el-option label="全部" :value="null"></el-option>
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
<el-option label="首页" :value="1"></el-option>
<el-option label="赛事详情" :value="2"></el-option>
<el-option label="其他" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item>
@@ -53,51 +56,57 @@
:preview-src-list="[scope.row.imageUrl]"
style="width: 100px; height: 50px; cursor: pointer;"
fit="cover"
/>
:hide-on-click-modal="false"
:preview-teleported="true"
>
<template #error>
<div class="image-error">
<el-icon><Picture /></el-icon>
<div>加载失败</div>
</div>
</template>
</el-image>
<span v-else>暂无图片</span>
</template>
</el-table-column>
<el-table-column prop="position" label="显示位置" width="100" align="center">
<template #default="scope">
<el-tag :type="getPositionType(scope.row.position)" size="small">
{{ getPositionText(scope.row.position) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="linkUrl" label="跳转链接" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="sortOrder" label="排序" width="80" align="center"></el-table-column>
<el-table-column prop="status" label="状态" width="150" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
active-text="启用"
inactive-text="禁用"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160"></el-table-column>
<el-table-column prop="clickCount" label="点击次数" width="100" align="center"></el-table-column>
<el-table-column prop="startTime" label="开始时间" width="160"></el-table-column>
<el-table-column prop="endTime" label="结束时间" width="160"></el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
<el-button type="primary" link size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" link size="small" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-if="pagination.total > 0"
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"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
small
></el-pagination>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
:visible.sync="dialogVisible"
width="600px"
:close-on-click-modal="false"
@close="handleDialogClose"
@@ -107,14 +116,31 @@
<el-input v-model="formData.title" placeholder="请输入轮播图标题" clearable></el-input>
</el-form-item>
<el-form-item label="图片链接" prop="imageUrl">
<el-input v-model="formData.imageUrl" placeholder="请输入图片链接地址" clearable>
<template #append>
<el-button @click="formData.imageUrl = 'https://via.placeholder.com/800x400'">使用示例</el-button>
</template>
</el-input>
<div v-if="formData.imageUrl" style="margin-top: 10px;">
<el-image :src="formData.imageUrl" style="width: 200px; height: 100px;" fit="cover" />
<el-form-item label="显示位置" prop="position">
<el-select v-model="formData.position" placeholder="请选择显示位置" style="width: 100%">
<el-option label="首页" :value="1"></el-option>
<el-option label="赛事详情" :value="2"></el-option>
<el-option label="其他" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="图片" prop="imageUrl">
<div class="image-upload-wrapper">
<div v-if="formData.imageUrl" class="image-preview">
<el-image
:src="formData.imageUrl"
class="preview-image"
fit="cover"
:preview-src-list="[formData.imageUrl]"
/>
<div class="image-actions">
<el-button type="primary" size="small" @click.stop="handleChangeImage">更换图片</el-button>
<el-button type="danger" size="small" @click.stop="handleRemoveImage">删除图片</el-button>
</div>
</div>
<el-button v-else type="primary" icon="el-icon-upload" @click.stop="handleOpenUpload">
上传图片
</el-button>
</div>
</el-form-item>
@@ -126,25 +152,62 @@
<el-input-number v-model="formData.sortOrder" :min="0" :max="999" style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="formData.startTime"
type="datetime"
placeholder="选择开始显示时间"
style="width: 100%"
value-format="YYYY-MM-DD HH:mm:ss"
></el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="formData.endTime"
type="datetime"
placeholder="选择结束显示时间"
style="width: 100%"
value-format="YYYY-MM-DD HH:mm:ss"
></el-date-picker>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 图片上传对话框 -->
<el-dialog
title="上传轮播图"
v-model="uploadDialogVisible"
width="555px"
append-to-body
:close-on-click-modal="false"
>
<avue-form
ref="uploadForm"
:option="uploadOption"
v-model="uploadForm"
:upload-after="uploadAfter"
/>
</el-dialog>
</div>
</template>
<script>
import { getBannerList, submitBanner, removeBanner } from '@/api/martial/banner'
import { Picture } from '@element-plus/icons-vue'
export default {
name: 'BannerList',
components: {
Picture
},
data() {
return {
loading: false,
@@ -152,39 +215,12 @@ export default {
dialogVisible: false,
dialogTitle: '新增轮播图',
isEdit: false,
uploadDialogVisible: false,
uploadForm: {},
searchForm: {
keyword: '',
status: null
title: '',
position: null
},
allTableData: [
{
id: 1,
title: '2025年全国武术锦标赛',
imageUrl: 'https://via.placeholder.com/800x400/dc2626/ffffff?text=2025年全国武术锦标赛',
linkUrl: '/martial/competition/list',
sortOrder: 1,
status: 1,
createTime: '2025-11-20 10:00:00'
},
{
id: 2,
title: '青少年武术大赛',
imageUrl: 'https://via.placeholder.com/800x400/991b1b/ffffff?text=青少年武术大赛',
linkUrl: '/martial/competition/list',
sortOrder: 2,
status: 1,
createTime: '2025-11-21 11:00:00'
},
{
id: 3,
title: '传统武术邀请赛',
imageUrl: 'https://via.placeholder.com/800x400/dc2626/ffffff?text=传统武术邀请赛',
linkUrl: '/martial/competition/list',
sortOrder: 3,
status: 0,
createTime: '2025-11-22 14:00:00'
}
],
tableData: [],
pagination: {
current: 1,
@@ -194,23 +230,45 @@ export default {
formData: {
id: null,
title: '',
position: 1,
imageUrl: '',
linkUrl: '',
sortOrder: 0,
status: 1
startTime: '',
endTime: ''
},
rules: {
title: [
{ required: true, message: '请输入轮播图标题', trigger: 'blur' }
],
position: [
{ required: true, message: '请选择显示位置', trigger: 'change' }
],
imageUrl: [
{ required: true, message: '请输入图片链接', trigger: 'blur' }
],
sortOrder: [
{ required: true, message: '请输入排序', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' }
]
},
uploadOption: {
submitBtn: false,
emptyBtn: false,
column: [
{
label: '轮播图上传',
prop: 'bannerImage',
type: 'upload',
drag: true,
loadText: '图片上传中,请稍等',
span: 24,
accept: 'image/*',
tip: '建议尺寸800x400像素支持 JPG、PNG 格式,大小不超过 2MB',
propsHttp: {
res: 'data',
},
action: '/blade-resource/oss/endpoint/put-file'
}
]
}
}
@@ -219,87 +277,84 @@ export default {
this.loadBannerList()
},
methods: {
// 从 localStorage 加载数据
// 加载轮播图列表
loadBannerList() {
const savedData = localStorage.getItem('bannerList')
if (savedData) {
try {
this.allTableData = JSON.parse(savedData)
} catch (e) {
console.error('加载轮播图数据失败', e)
}
} else {
// 首次加载,保存默认数据
this.saveBannerList()
}
this.fetchData()
},
// 保存数据到 localStorage
saveBannerList() {
localStorage.setItem('bannerList', JSON.stringify(this.allTableData))
},
// 获取数据
fetchData() {
this.loading = true
const params = {}
if (this.searchForm.title) {
params.title = this.searchForm.title
}
if (this.searchForm.position !== null && this.searchForm.position !== '') {
params.position = this.searchForm.position
}
setTimeout(() => {
// 过滤数据
let filteredData = [...this.allTableData]
getBannerList(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
this.pagination.total = responseData.total || 0
// 调试:打印每条记录的 imageUrl
this.tableData.forEach((item, index) => {
console.log(`记录 ${index + 1} - imageUrl:`, item.imageUrl)
})
}
})
.catch(err => {
console.error('加载轮播图列表失败', err)
this.$message.error('加载轮播图列表失败')
})
.finally(() => {
this.loading = false
})
},
// 搜索过滤
if (this.searchForm.keyword) {
const keyword = this.searchForm.keyword.toLowerCase()
filteredData = filteredData.filter(item =>
item.title.toLowerCase().includes(keyword)
)
}
// 获取位置文本
getPositionText(position) {
const positionMap = {
1: '首页',
2: '赛事详情',
3: '其他'
}
return positionMap[position] || '未知'
},
// 状态过滤
if (this.searchForm.status !== null && this.searchForm.status !== '') {
filteredData = filteredData.filter(item => item.status === this.searchForm.status)
}
// 按排序字段排序
filteredData.sort((a, b) => a.sortOrder - b.sortOrder)
this.pagination.total = filteredData.length
// 分页处理
const start = (this.pagination.current - 1) * this.pagination.size
const end = start + this.pagination.size
this.tableData = filteredData.slice(start, end)
this.loading = false
}, 300)
// 获取位置标签类型
getPositionType(position) {
const typeMap = {
1: 'success',
2: 'primary',
3: 'info'
}
return typeMap[position] || ''
},
// 搜索
handleSearch() {
this.pagination.current = 1
this.fetchData()
this.loadBannerList()
},
// 重置
handleReset() {
this.searchForm = {
keyword: '',
status: null
title: '',
position: null
}
this.pagination.current = 1
this.fetchData()
this.loadBannerList()
},
// 分页
handleSizeChange(size) {
this.pagination.size = size
this.fetchData()
this.loadBannerList()
},
handleCurrentChange(current) {
this.pagination.current = current
this.fetchData()
this.loadBannerList()
},
// 新增
@@ -324,61 +379,47 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = this.allTableData.findIndex(item => item.id === row.id)
if (index !== -1) {
this.allTableData.splice(index, 1)
this.saveBannerList()
this.$message.success('删除成功')
this.fetchData()
}
this.loading = true
removeBanner(row.id.toString())
.then(res => {
this.$message.success('删除成功')
this.loadBannerList()
})
.catch(err => {
console.error('删除失败', err)
this.$message.error('删除失败')
})
.finally(() => {
this.loading = false
})
}).catch(() => {})
},
// 状态变更
handleStatusChange(row) {
const index = this.allTableData.findIndex(item => item.id === row.id)
if (index !== -1) {
this.allTableData[index].status = row.status
this.saveBannerList()
this.$message.success('状态更新成功')
}
},
// 提交表单
handleSubmit() {
this.$refs.bannerForm.validate((valid) => {
if (valid) {
this.submitLoading = true
setTimeout(() => {
if (this.isEdit) {
// 编辑
const index = this.allTableData.findIndex(item => item.id === this.formData.id)
if (index !== -1) {
this.allTableData[index] = { ...this.formData }
this.$message.success('修改成功')
}
} else {
// 新增
const newId = this.allTableData.length > 0
? Math.max(...this.allTableData.map(item => item.id)) + 1
: 1
const now = new Date()
const createTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
const submitData = { ...this.formData }
// 如果是编辑模式,需要传入 id
if (!this.isEdit) {
delete submitData.id
}
this.allTableData.push({
...this.formData,
id: newId,
createTime
})
this.$message.success('新增成功')
}
this.saveBannerList()
this.dialogVisible = false
this.fetchData()
this.submitLoading = false
}, 500)
submitBanner(submitData)
.then(res => {
this.$message.success(this.isEdit ? '修改成功' : '新增成功')
this.dialogVisible = false
this.loadBannerList()
})
.catch(err => {
console.error('保存失败', err)
this.$message.error(this.isEdit ? '修改失败' : '新增失败')
})
.finally(() => {
this.submitLoading = false
})
}
})
},
@@ -389,11 +430,62 @@ export default {
this.formData = {
id: null,
title: '',
position: 1,
imageUrl: '',
linkUrl: '',
sortOrder: 0,
status: 1
startTime: '',
endTime: ''
}
},
// 打开上传对话框
handleOpenUpload() {
console.log('打开上传对话框')
this.uploadDialogVisible = true
this.uploadForm = {}
},
// 更换图片
handleChangeImage() {
console.log('更换图片')
this.uploadDialogVisible = true
this.uploadForm = {}
},
// 删除图片
handleRemoveImage() {
console.log('删除图片')
this.$confirm('确定删除当前图片吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.formData.imageUrl = ''
this.$message.success('删除成功')
}).catch(() => {})
},
// 上传成功回调
uploadAfter(res, done, loading, column) {
console.log('uploadAfter 触发')
console.log('上传响应:', res)
console.log('done:', done)
console.log('loading:', loading)
console.log('column:', column)
if (res && res.link) {
this.formData.imageUrl = res.link
this.$message.success('图片上传成功')
this.uploadDialogVisible = false
} else if (res && res.url) {
this.formData.imageUrl = res.url
this.$message.success('图片上传成功')
this.uploadDialogVisible = false
} else {
this.$message.error('上传失败,未获取到图片地址')
}
done()
}
}
}
@@ -430,5 +522,44 @@ export default {
.dialog-footer {
text-align: right;
}
.image-upload-wrapper {
.image-preview {
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
.preview-image {
width: 100%;
height: 200px;
display: block;
margin-bottom: 10px;
border-radius: 4px;
}
.image-actions {
display: flex;
gap: 10px;
justify-content: center;
}
}
}
.image-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
font-size: 12px;
color: #999;
background-color: #f5f7fa;
.el-icon {
font-size: 24px;
margin-bottom: 5px;
}
}
}
</style>

View File

@@ -291,6 +291,271 @@
</el-table-column>
</el-table>
</div>
<!-- 项目列表 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-medal"></i>
项目列表
</div>
<el-form-item label="">
<el-button
v-if="mode !== 'view'"
type="primary"
icon="el-icon-plus"
size="small"
@click="handleAddProject"
>
添加项目
</el-button>
</el-form-item>
<el-table
:data="formData.projects"
border
style="width: 100%"
>
<el-table-column
label="项目名称"
min-width="150"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.projectName"
placeholder="例如:太极拳"
size="small"
/>
<span v-else>{{ scope.row.projectName }}</span>
</template>
</el-table-column>
<el-table-column
label="项目代码"
width="120"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.projectCode"
placeholder="例如TJQ001"
size="small"
/>
<span v-else>{{ scope.row.projectCode }}</span>
</template>
</el-table-column>
<el-table-column
label="组别"
width="120"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.category"
placeholder="例如:成年男子组"
size="small"
/>
<span v-else>{{ scope.row.category }}</span>
</template>
</el-table-column>
<el-table-column
label="参赛人数限制"
width="130"
align="center"
>
<template #default="scope">
<el-input-number
v-if="mode !== 'view'"
v-model="scope.row.maxParticipants"
:min="1"
:max="9999"
size="small"
style="width: 100%"
/>
<span v-else>{{ scope.row.maxParticipants || '不限' }}</span>
</template>
</el-table-column>
<el-table-column
label="项目说明"
min-width="200"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.description"
placeholder="请输入项目说明"
size="small"
/>
<span v-else>{{ scope.row.description }}</span>
</template>
</el-table-column>
<el-table-column
v-if="mode !== 'view'"
label="操作"
width="80"
align="center"
>
<template #default="scope">
<el-button
type="danger"
link
size="small"
icon="el-icon-delete"
@click="handleDeleteProject(scope.$index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 场地配置 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-office-building"></i>
场地配置
</div>
<el-form-item label="">
<el-button
v-if="mode !== 'view'"
type="primary"
icon="el-icon-plus"
size="small"
@click="handleAddVenue"
>
添加场地
</el-button>
</el-form-item>
<el-table
:data="formData.venues"
border
style="width: 100%"
>
<el-table-column
label="场地名称"
width="150"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.venueName"
placeholder="例如主比赛场A"
size="small"
/>
<span v-else>{{ scope.row.venueName }}</span>
</template>
</el-table-column>
<el-table-column
label="场地编号"
width="120"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.venueCode"
placeholder="例如A001"
size="small"
/>
<span v-else>{{ scope.row.venueCode }}</span>
</template>
</el-table-column>
<el-table-column
label="场地类型"
width="120"
>
<template #default="scope">
<el-select
v-if="mode !== 'view'"
v-model="scope.row.venueType"
placeholder="请选择"
size="small"
style="width: 100%"
>
<el-option label="室内" value="indoor" />
<el-option label="室外" value="outdoor" />
</el-select>
<span v-else>{{ scope.row.venueType === 'indoor' ? '室内' : '室外' }}</span>
</template>
</el-table-column>
<el-table-column
label="容纳人数"
width="120"
align="center"
>
<template #default="scope">
<el-input-number
v-if="mode !== 'view'"
v-model="scope.row.capacity"
:min="1"
:max="99999"
size="small"
style="width: 100%"
/>
<span v-else>{{ scope.row.capacity }}</span>
</template>
</el-table-column>
<el-table-column
label="位置"
min-width="150"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.location"
placeholder="例如体育馆1楼东侧"
size="small"
/>
<span v-else>{{ scope.row.location }}</span>
</template>
</el-table-column>
<el-table-column
label="备注"
min-width="200"
>
<template #default="scope">
<el-input
v-if="mode !== 'view'"
v-model="scope.row.remark"
placeholder="请输入备注"
size="small"
/>
<span v-else>{{ scope.row.remark }}</span>
</template>
</el-table-column>
<el-table-column
v-if="mode !== 'view'"
label="操作"
width="80"
align="center"
>
<template #default="scope">
<el-button
type="danger"
link
size="small"
icon="el-icon-delete"
@click="handleDeleteVenue(scope.$index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
</div>
</template>
@@ -317,7 +582,9 @@ export default {
rules: '',
requirements: '',
awards: '',
schedule: []
schedule: [],
projects: [],
venues: []
},
formRules: {
competitionName: [
@@ -386,6 +653,37 @@ export default {
this.formData.schedule.splice(index, 1);
},
// 项目列表管理
handleAddProject() {
this.formData.projects.push({
projectName: '',
projectCode: '',
category: '',
maxParticipants: null,
description: ''
});
},
handleDeleteProject(index) {
this.formData.projects.splice(index, 1);
},
// 场地配置管理
handleAddVenue() {
this.formData.venues.push({
venueName: '',
venueCode: '',
venueType: 'indoor',
capacity: null,
location: '',
remark: ''
});
},
handleDeleteVenue(index) {
this.formData.venues.splice(index, 1);
},
handleSave() {
this.$refs.formRef.validate((valid) => {
if (valid) {

File diff suppressed because it is too large Load Diff

View File

@@ -417,76 +417,6 @@ export default {
data() {
return {
competitionList: [
{
id: 1,
competitionName: '2025年全国武术锦标赛',
organizer: '国家体育总局武术运动管理中心',
location: '北京',
venue: '国家奥林匹克体育中心',
registrationTime: '2025-01-01 至 2025-02-28',
competitionTime: '2025-03-15 至 2025-03-20',
status: 1, // 1-未开始 2-报名中 3-进行中 4-已结束
introduction: '全国最高水平的武术竞技赛事',
posterImages: [],
contactPerson: '张主任',
contactPhone: '010-12345678',
contactEmail: 'contact@wushu.cn',
rules: '参赛选手必须持有国家二级运动员及以上证书',
requirements: '年龄18-35岁身体健康',
awards: '冠军奖金10万元亚军5万元季军3万元',
regulationFiles: [],
schedule: [
{ date: '2025-03-15', time: '09:00-12:00', event: '开幕式', venue: '主场馆' },
{ date: '2025-03-16', time: '09:00-18:00', event: '太极拳比赛', venue: 'A馆' },
{ date: '2025-03-17', time: '09:00-18:00', event: '长拳比赛', venue: 'B馆' },
]
},
{
id: 2,
competitionName: '2025年青少年武术大赛',
organizer: '中国武术协会',
location: '上海',
venue: '上海体育馆',
registrationTime: '2025-02-01 至 2025-03-31',
competitionTime: '2025-04-10 至 2025-04-15',
status: 2,
introduction: '面向青少年的武术竞技赛事',
posterImages: [],
contactPerson: '李教练',
contactPhone: '021-87654321',
contactEmail: 'youth@wushu.org',
rules: '年龄限制6-18岁',
requirements: '需提供学生证明',
awards: '设金银铜奖及优秀奖',
regulationFiles: [],
schedule: [
{ date: '2025-04-10', time: '09:00-12:00', event: '开幕式', venue: '主场馆' },
{ date: '2025-04-11', time: '09:00-18:00', event: '少年组比赛', venue: 'A馆' },
]
},
{
id: 3,
competitionName: '2025年传统武术邀请赛',
organizer: '中华武术联合会',
location: '杭州',
venue: '杭州国际博览中心',
registrationTime: '2025-03-01 至 2025-04-30',
competitionTime: '2025-05-20 至 2025-05-25',
status: 1,
introduction: '传统武术项目展示与竞技',
posterImages: [],
contactPerson: '王馆长',
contactPhone: '0571-23456789',
contactEmail: 'traditional@wushu.com',
rules: '限传统武术门派参赛',
requirements: '需提供师承证明',
awards: '金银铜奖及最佳表演奖',
regulationFiles: [],
schedule: [
{ date: '2025-05-20', time: '14:00-17:00', event: '报到', venue: '接待中心' },
{ date: '2025-05-21', time: '09:00-18:00', event: '初赛', venue: '比赛馆' },
]
}
],
dialogVisible: false,
dialogMode: 'create', // create, edit, view

View File

@@ -8,7 +8,7 @@
<el-form-item>
<el-input
v-model="searchForm.keyword"
placeholder="搜索订单号/用户"
placeholder="搜索赛事名称"
clearable
size="small"
style="width: 240px"
@@ -17,13 +17,18 @@
</el-input>
</el-form-item>
<el-form-item>
<el-input
<el-select
v-model="searchForm.status"
placeholder="状态"
placeholder="赛事状态"
clearable
size="small"
style="width: 180px"
></el-input>
>
<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>
@@ -39,12 +44,18 @@
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
<el-table-column prop="orderNo" label="订单号" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="userName" label="用户" width="100"></el-table-column>
<el-table-column prop="competitionName" label="赛事" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="amount" label="金额" width="100" align="center">
<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 class="amount-text">¥{{ scope.row.amount }}</span>
<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">
@@ -54,12 +65,11 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160"></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)" :disabled="!scope.row.canSchedule">编排</el-button>
<el-button type="warning" size="small" @click="handleDispatch(scope.row)" :disabled="!scope.row.canDispatch">调度</el-button>
<el-button type="success" size="small" @click="handleSchedule(scope.row)">编排</el-button>
<el-button type="warning" size="small" @click="handleDispatch(scope.row)">调度</el-button>
</template>
</el-table-column>
</el-table>
@@ -80,6 +90,8 @@
</template>
<script>
import { getCompetitionList } from '@/api/martial/competition'
export default {
name: 'MartialOrderList',
data() {
@@ -89,64 +101,6 @@ export default {
keyword: '',
status: null
},
// 使用静态数据,方便演示功能
allTableData: [
{
id: 1,
orderNo: 'ORD20251127001',
userName: '张三',
competitionName: '第三十届武术大赛',
amount: 999.00,
status: 1,
createTime: '2025-11-27 10:30:00',
canSchedule: true, // 可以编排
canDispatch: false // 未完成编排,不可调度
},
{
id: 2,
orderNo: 'ORD20251127002',
userName: '李四',
competitionName: '第三十届武术大赛',
amount: 1245.00,
status: 1,
createTime: '2025-11-27 11:00:00',
canSchedule: true,
canDispatch: true // 已完成编排,可以调度
},
{
id: 3,
orderNo: 'ORD20251127003',
userName: '王五',
competitionName: '青少年武术锦标赛',
amount: 1580.00,
status: 1,
createTime: '2025-11-27 14:20:00',
canSchedule: true,
canDispatch: false
},
{
id: 4,
orderNo: 'ORD20251126001',
userName: '赵六',
competitionName: '第三十届武术大赛',
amount: 2300.00,
status: 0,
createTime: '2025-11-26 09:15:00',
canSchedule: false,
canDispatch: false
},
{
id: 5,
orderNo: 'ORD20251126002',
userName: '孙七',
competitionName: '全国武术公开赛',
amount: 1850.00,
status: 1,
createTime: '2025-11-26 16:45:00',
canSchedule: true,
canDispatch: false
}
],
tableData: [],
pagination: {
current: 1,
@@ -156,101 +110,118 @@ export default {
}
},
mounted() {
// 初始化时直接加载数据
this.fetchData()
this.loadCompetitionList()
},
methods: {
fetchData() {
// 加载赛事列表
loadCompetitionList() {
this.loading = true
const params = {}
// 模拟API请求延迟
setTimeout(() => {
// 过滤数据
let filteredData = [...this.allTableData]
if (this.searchForm.keyword) {
params.competitionName = this.searchForm.keyword
}
if (this.searchForm.status !== null && this.searchForm.status !== '') {
params.status = this.searchForm.status
}
// 搜索过滤
if (this.searchForm.keyword) {
const keyword = this.searchForm.keyword.toLowerCase()
filteredData = filteredData.filter(item =>
item.orderNo.toLowerCase().includes(keyword) ||
item.userName.toLowerCase().includes(keyword) ||
item.competitionName.toLowerCase().includes(keyword)
)
}
// 状态过滤
if (this.searchForm.status !== null && this.searchForm.status !== '') {
filteredData = filteredData.filter(item =>
item.status === parseInt(this.searchForm.status)
)
}
this.pagination.total = filteredData.length
// 分页处理
const start = (this.pagination.current - 1) * this.pagination.size
const end = start + this.pagination.size
this.tableData = filteredData.slice(start, end)
this.loading = false
}, 300)
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
}
})
.catch(err => {
console.error('加载赛事列表失败', err)
this.$message.error('加载赛事列表失败')
})
.finally(() => {
this.loading = false
})
},
handleSearch() {
this.pagination.current = 1
this.fetchData()
this.loadCompetitionList()
},
handleSizeChange(size) {
this.pagination.size = size
this.fetchData()
this.pagination.current = 1
this.loadCompetitionList()
},
handleCurrentChange(current) {
this.pagination.current = current
this.fetchData()
this.loadCompetitionList()
},
// 查看报名详情
// 查看报名详情 - 传递赛事ID
handleRegistrationDetail(row) {
this.$router.push({
path: '/martial/registration/detail',
query: { orderId: row.id }
query: { competitionId: row.id }
})
},
// 编排
// 编排 - 传递赛事ID
handleSchedule(row) {
if (!row.canSchedule) {
this.$message.warning('该订单暂不可编排')
return
}
this.$router.push({
path: '/martial/schedule/list',
query: { orderId: row.id }
query: { competitionId: row.id }
})
},
// 调度
// 调度 - 传递赛事ID
handleDispatch(row) {
if (!row.canDispatch) {
this.$message.warning('请先完成编排')
return
}
this.$router.push({
path: '/martial/dispatch/list',
query: { orderId: row.id }
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 = {
0: 'warning',
1: 'success',
2: 'info',
3: 'danger'
1: 'info', // 未开始
2: 'success', // 报名中
3: 'warning', // 进行中
4: 'info' // 已结束
}
return statusMap[status] || 'info'
},
getStatusText(status) {
const statusMap = {
0: '待支付',
1: '已支付',
2: '已取消',
3: '已退款'
1: '未开始',
2: '报名中',
3: '进行中',
4: '已结束'
}
return statusMap[status] || '未知'
}

View File

@@ -19,14 +19,16 @@
size="small"
style="width: 240px"
>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
<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 competitionOptions"
v-for="item in allCompetitionOptions"
:key="item.id"
:label="item.competitionName"
:value="item.id"
@@ -40,6 +42,7 @@
</el-form>
<el-table
v-loading="loading"
:data="displayList"
border
stripe
@@ -154,12 +157,25 @@
</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">
<el-card shadow="hover" v-loading="loading">
<div class="page-header">
<el-button
icon="el-icon-arrow-left"
@@ -265,7 +281,7 @@
@change="handleCompetitionChange"
>
<el-option
v-for="item in competitionOptions"
v-for="item in availableCompetitionOptions"
:key="item.id"
:label="item.competitionName"
:value="item.id"
@@ -274,11 +290,20 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="参赛项目" prop="projectName">
<el-input
v-model="formData.projectName"
placeholder="请输入参赛项目"
/>
<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>
@@ -339,109 +364,39 @@
</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
},
competitionOptions: [
{ id: 1, competitionName: '2025年全国武术锦标赛' },
{ id: 2, competitionName: '2025年青少年武术大赛' },
{ id: 3, competitionName: '2025年传统武术邀请赛' }
],
participantList: [
{
id: 1,
competitionId: 1,
competitionName: '2025年全国武术锦标赛',
playerName: '张伟',
gender: 1,
age: 25,
contactPhone: '13800138001',
organization: '北京武术队',
idCard: '110101199001011234',
projectName: '太极拳',
category: '成年男子组',
orderNum: 1,
introduction: '国家一级运动员',
remark: '',
attachments: []
},
{
id: 2,
competitionId: 1,
competitionName: '2025年全国武术锦标赛',
playerName: '李娜',
gender: 2,
age: 22,
contactPhone: '13800138002',
organization: '上海武术队',
idCard: '310101199201011234',
projectName: '长拳',
category: '成年女子组',
orderNum: 2,
introduction: '国家二级运动员',
remark: '',
attachments: []
},
{
id: 3,
competitionId: 2,
competitionName: '2025年青少年武术大赛',
playerName: '王小明',
gender: 1,
age: 16,
contactPhone: '13800138003',
organization: '广州市体校',
idCard: '440101200801011234',
projectName: '剑术',
category: '少年男子组',
orderNum: 1,
introduction: '市级青少年冠军',
remark: '',
attachments: []
},
{
id: 4,
competitionId: 2,
competitionName: '2025年青少年武术大赛',
playerName: '赵小红',
gender: 2,
age: 15,
contactPhone: '13800138004',
organization: '深圳市体校',
idCard: '440301200901011234',
projectName: '刀术',
category: '少年女子组',
orderNum: 2,
introduction: '省级青少年亚军',
remark: '',
attachments: []
},
{
id: 5,
competitionId: 3,
competitionName: '2025年传统武术邀请赛',
playerName: '孙师傅',
gender: 1,
age: 45,
contactPhone: '13800138005',
organization: '武当派',
idCard: '420101197901011234',
projectName: '太极剑',
category: '中年组',
orderNum: 1,
introduction: '武当第十五代传人',
remark: '',
attachments: []
}
],
pagination: {
current: 1,
size: 10,
total: 0
},
competitionOptions: [], // 已发布的可报名赛事列表(用于新建)
allCompetitionOptions: [], // 所有赛事列表(用于搜索和编辑)
projectOptions: [], // 项目列表
participantList: [],
formData: {
orderId: null,
competitionId: null,
competitionName: '',
playerName: '',
@@ -450,7 +405,7 @@ export default {
contactPhone: '',
organization: '',
idCard: '',
projectName: '',
projectId: null,
category: '',
orderNum: 1,
introduction: '',
@@ -474,8 +429,8 @@ export default {
competitionId: [
{ required: true, message: '请选择赛事', trigger: 'change' }
],
projectName: [
{ required: true, message: '请输入参赛项目', trigger: 'blur' }
projectId: [
{ required: true, message: '请选择参赛项目', trigger: 'change' }
]
}
};
@@ -490,21 +445,16 @@ export default {
return titleMap[this.currentView] || '参赛选手信息';
},
displayList() {
let list = [...this.participantList];
// 关键词搜索
if (this.searchForm.keyword) {
list = list.filter(item =>
item.playerName.includes(this.searchForm.keyword)
);
return this.participantList;
},
// 根据不同模式返回不同的赛事选项
availableCompetitionOptions() {
// 编辑和查看模式:显示所有赛事(因为可能编辑已过报名期的选手)
if (this.currentView === 'edit' || this.currentView === 'view') {
return this.allCompetitionOptions;
}
// 赛事筛选
if (this.searchForm.competitionId) {
list = list.filter(item => item.competitionId === this.searchForm.competitionId);
}
return list;
// 新建模式:只显示可报名的赛事
return this.competitionOptions;
}
},
watch: {
@@ -516,59 +466,199 @@ export default {
}
},
mounted() {
this.loadParticipantList();
this.loadCompetitionOptions();
this.loadAvailableCompetitions();
this.loadAllCompetitions();
},
methods: {
initPage() {
const { mode, id } = this.$route.query;
this.currentView = mode || 'list';
this.participantId = id ? parseInt(id) : null;
// 不使用 parseInt保持 ID 为字符串避免精度丢失
this.participantId = id || null;
if (this.currentView !== 'list' && this.participantId) {
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() {
const savedData = localStorage.getItem('participantList');
if (savedData) {
try {
this.participantList = JSON.parse(savedData);
} catch (e) {
console.error('加载数据失败', e);
}
} else {
this.saveParticipantList();
}
},
this.loading = true;
const params = {};
loadCompetitionOptions() {
const competitionData = localStorage.getItem('competitionList');
if (competitionData) {
try {
this.competitionOptions = JSON.parse(competitionData);
} catch (e) {
console.error('加载赛事数据失败', e);
}
if (this.searchForm.keyword) {
params.playerName = this.searchForm.keyword;
}
},
saveParticipantList() {
localStorage.setItem('participantList', JSON.stringify(this.participantList));
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() {
const participant = this.participantList.find(item => item.id === this.participantId);
if (participant) {
this.formData = { ...participant };
}
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() {
// 搜索逻辑已在 computed 中实现
this.pagination.current = 1;
this.loadParticipantList();
},
handleReset() {
@@ -576,6 +666,8 @@ export default {
keyword: '',
competitionId: null
};
this.pagination.current = 1;
this.loadParticipantList();
},
handleCreate() {
@@ -612,56 +704,157 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = this.participantList.findIndex(item => item.id === row.id);
if (index !== -1) {
this.participantList.splice(index, 1);
this.saveParticipantList();
this.$message.success('删除成功');
}
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.competitionOptions.find(item => item.id === 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.competitionOptions.find(item => item.id === this.formData.competitionId);
const competition = this.availableCompetitionOptions.find(item => item.id === this.formData.competitionId);
if (competition) {
this.formData.competitionName = competition.competitionName;
}
}
if (this.currentView === 'create') {
const newId = this.participantList.length > 0
? Math.max(...this.participantList.map(item => item.id)) + 1
: 1;
const newParticipant = {
...this.formData,
id: newId
};
this.participantList.push(newParticipant);
this.$message.success('添加成功');
} else if (this.currentView === 'edit') {
const index = this.participantList.findIndex(item => item.id === this.participantId);
if (index !== -1) {
this.participantList[index] = {
...this.participantList[index],
...this.formData
};
this.$message.success('保存成功');
}
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);
}
this.saveParticipantList();
this.backToList();
// 临时方案: 如果没有 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('请完善必填信息');
}
@@ -676,6 +869,7 @@ export default {
resetFormData() {
this.formData = {
orderId: null,
competitionId: null,
competitionName: '',
playerName: '',
@@ -684,7 +878,7 @@ export default {
contactPhone: '',
organization: '',
idCard: '',
projectName: '',
projectId: null,
category: '',
orderNum: 1,
introduction: '',

View File

@@ -16,14 +16,16 @@
size="small"
style="width: 240px"
>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
<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 competitionOptions"
v-for="item in allCompetitionOptions"
:key="item.id"
:label="item.competitionName"
:value="item.id"
@@ -37,6 +39,7 @@
</el-form>
<el-table
v-loading="loading"
:data="displayList"
border
stripe
@@ -152,10 +155,23 @@
</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-dialog
v-model="dialogVisible"
:title="dialogTitle"
:visible.sync="dialogVisible"
width="800px"
:close-on-click-modal="false"
@close="handleDialogClose"
@@ -256,11 +272,20 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="参赛项目" prop="projectName">
<el-input
v-model="formData.projectName"
placeholder="请输入参赛项目"
/>
<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>
@@ -316,129 +341,60 @@
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
v-if="dialogMode !== 'view'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
type="primary"
@click="switchToEdit"
>
编辑
</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
v-if="dialogMode !== 'view'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
type="primary"
@click="switchToEdit"
>
编辑
</el-button>
</div>
</template>
</el-dialog>
</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: 'ParticipantList',
data() {
return {
loading: false,
searchForm: {
keyword: '',
competitionId: null
},
competitionOptions: [
{ id: 1, competitionName: '2025年全国武术锦标赛' },
{ id: 2, competitionName: '2025年青少年武术大赛' },
{ id: 3, competitionName: '2025年传统武术邀请赛' }
],
participantList: [
{
id: 1,
competitionId: 1,
competitionName: '2025年全国武术锦标赛',
playerName: '张伟',
gender: 1,
age: 25,
contactPhone: '13800138001',
organization: '北京武术队',
idCard: '110101199001011234',
projectName: '太极拳',
category: '成年男子组',
orderNum: 1,
introduction: '国家一级运动员',
remark: '',
attachments: []
},
{
id: 2,
competitionId: 1,
competitionName: '2025年全国武术锦标赛',
playerName: '李娜',
gender: 2,
age: 22,
contactPhone: '13800138002',
organization: '上海武术队',
idCard: '310101199201011234',
projectName: '长拳',
category: '成年女子组',
orderNum: 2,
introduction: '国家二级运动员',
remark: '',
attachments: []
},
{
id: 3,
competitionId: 2,
competitionName: '2025年青少年武术大赛',
playerName: '王小明',
gender: 1,
age: 16,
contactPhone: '13800138003',
organization: '广州市体校',
idCard: '440101200801011234',
projectName: '剑术',
category: '少年男子组',
orderNum: 1,
introduction: '市级青少年冠军',
remark: '',
attachments: []
},
{
id: 4,
competitionId: 2,
competitionName: '2025年青少年武术大赛',
playerName: '赵小红',
gender: 2,
age: 15,
contactPhone: '13800138004',
organization: '深圳市体校',
idCard: '440301200901011234',
projectName: '刀术',
category: '少年女子组',
orderNum: 2,
introduction: '省级青少年亚军',
remark: '',
attachments: []
},
{
id: 5,
competitionId: 3,
competitionName: '2025年传统武术邀请赛',
playerName: '孙师傅',
gender: 1,
age: 45,
contactPhone: '13800138005',
organization: '武当派',
idCard: '420101197901011234',
projectName: '太极剑',
category: '中年组',
orderNum: 1,
introduction: '武当第十五代传人',
remark: '',
attachments: []
}
],
pagination: {
current: 1,
size: 10,
total: 0
},
competitionOptions: [], // 可报名的赛事列表
allCompetitionOptions: [], // 所有赛事列表(用于搜索过滤)
projectOptions: [], // 项目列表
participantList: [],
dialogVisible: false,
dialogMode: 'create', // create, edit, view
submitLoading: false,
@@ -452,7 +408,7 @@ export default {
contactPhone: '',
organization: '',
idCard: '',
projectName: '',
projectId: null,
category: '',
orderNum: 1,
introduction: '',
@@ -476,8 +432,8 @@ export default {
competitionId: [
{ required: true, message: '请选择赛事', trigger: 'change' }
],
projectName: [
{ required: true, message: '请输入参赛项目', trigger: 'blur' }
projectId: [
{ required: true, message: '请选择参赛项目', trigger: 'change' }
]
}
};
@@ -492,58 +448,180 @@ export default {
return titleMap[this.dialogMode] || '参赛选手信息';
},
displayList() {
let list = [...this.participantList];
// 关键词搜索
if (this.searchForm.keyword) {
list = list.filter(item =>
item.playerName.includes(this.searchForm.keyword)
);
}
// 赛事筛选
if (this.searchForm.competitionId) {
list = list.filter(item => item.competitionId === this.searchForm.competitionId);
}
return list;
return this.participantList;
}
},
mounted() {
this.loadParticipantList();
this.loadAvailableCompetitions();
this.loadAllCompetitions();
},
methods: {
loadParticipantList() {
// 从 localStorage 加载数据
const savedData = localStorage.getItem('participantList');
if (savedData) {
try {
this.participantList = JSON.parse(savedData);
} catch (e) {
console.error('加载数据失败', e);
}
} else {
// 首次加载,保存默认数据
this.saveParticipantList();
}
// 加载可报名的赛事(从已发布的信息中获取)
loadAvailableCompetitions() {
// 获取已发布的赛事信息publishStatus = 1 表示已发布)
getInfoPublishList(1, 100, { publishStatus: 1 })
.then(res => {
console.log('已发布信息列表返回数据:', res);
const responseData = res.data?.data;
if (responseData && responseData.records) {
const now = new Date();
// 加载赛事选项
const competitionData = localStorage.getItem('competitionList');
if (competitionData) {
try {
this.competitionOptions = JSON.parse(competitionData);
} catch (e) {
console.error('加载赛事数据失败', e);
}
}
// 从已发布的信息中提取赛事ID
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 {
this.$message.warning('当前没有已发布的赛事');
this.competitionOptions = [];
}
}
})
.catch(err => {
console.error('加载已发布信息列表失败', err);
this.$message.error('加载已发布信息列表失败');
});
},
saveParticipantList() {
localStorage.setItem('participantList', JSON.stringify(this.participantList));
// 加载已发布的赛事详细信息,并过滤出可报名的赛事
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 => {
// 必须是已发布的赛事
if (!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) {
this.$message.warning('当前没有可以报名的赛事');
}
}
})
.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);
});
},
// 编辑时加载所有赛事到下拉框
loadAllCompetitionsForEdit() {
return getCompetitionList(1, 100, {})
.then(res => {
const responseData = res.data?.data;
if (responseData && responseData.records) {
// 编辑模式下competitionOptions 应该包含所有赛事
this.competitionOptions = responseData.records.map(item => ({
id: item.id,
competitionName: item.competitionName,
registrationStartTime: item.registrationStartTime,
registrationEndTime: item.registrationEndTime
}));
console.log('编辑模式 - 所有赛事列表:', this.competitionOptions);
}
})
.catch(err => {
console.error('加载所有赛事失败', err);
this.$message.error('加载所有赛事失败');
throw 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;
});
},
handlePageChange(current) {
this.pagination.current = current;
this.loadParticipantList();
},
handleSizeChange(size) {
this.pagination.size = size;
this.pagination.current = 1;
this.loadParticipantList();
},
handleSearch() {
// 搜索逻辑已在 computed 中实现
this.pagination.current = 1;
this.loadParticipantList();
},
handleReset() {
@@ -551,27 +629,99 @@ export default {
keyword: '',
competitionId: null
};
this.pagination.current = 1;
this.loadParticipantList();
},
handleCreate() {
this.dialogMode = 'create';
this.currentParticipantId = null;
this.resetFormData();
// 新建模式:重新加载可报名的赛事
this.loadAvailableCompetitions();
this.dialogVisible = true;
},
handleView(row) {
this.dialogMode = 'view';
this.currentParticipantId = row.id;
this.formData = { ...row };
this.dialogVisible = true;
this.loading = true;
// 先加载所有赛事
this.loadAllCompetitionsForEdit().then(() => {
// 赛事加载完成后,再加载参赛人员详情
return getParticipantDetail(row.id);
}).then(res => {
const detailData = res.data?.data;
console.log('查看模式 - 参赛人员详情数据:', detailData);
if (detailData) {
// 确保 ID 字段是数字类型
this.formData = {
...detailData,
competitionId: detailData.competitionId ? Number(detailData.competitionId) : null,
projectId: detailData.projectId ? Number(detailData.projectId) : null,
gender: detailData.gender ? Number(detailData.gender) : 1,
age: detailData.age ? Number(detailData.age) : null,
orderNum: detailData.orderNum ? Number(detailData.orderNum) : 1
};
// 加载该赛事的项目列表(即使是查看模式也需要显示项目名称)
if (this.formData.competitionId) {
return this.loadProjectsByCompetition(this.formData.competitionId);
}
}
}).then(() => {
this.dialogVisible = true;
this.loading = false;
}).catch(err => {
console.error('加载失败', err);
this.$message.error('加载参赛人员详情失败');
this.loading = false;
});
},
handleEdit(row) {
this.dialogMode = 'edit';
this.currentParticipantId = row.id;
this.formData = { ...row };
this.dialogVisible = true;
this.loading = true;
// 先加载所有赛事
this.loadAllCompetitionsForEdit().then(() => {
// 赛事加载完成后,再加载参赛人员详情
return getParticipantDetail(row.id);
}).then(res => {
const detailData = res.data?.data;
console.log('参赛人员详情数据:', detailData);
if (detailData) {
// 确保 ID 字段是数字类型
this.formData = {
...detailData,
competitionId: detailData.competitionId ? Number(detailData.competitionId) : null,
projectId: detailData.projectId ? Number(detailData.projectId) : null,
gender: detailData.gender ? Number(detailData.gender) : 1,
age: detailData.age ? Number(detailData.age) : null,
orderNum: detailData.orderNum ? Number(detailData.orderNum) : 1
};
console.log('转换后的 formData.competitionId:', this.formData.competitionId);
console.log('转换后的 formData.projectId:', this.formData.projectId);
// 加载该赛事的项目列表
if (this.formData.competitionId) {
return this.loadProjectsByCompetition(this.formData.competitionId);
}
}
}).then(() => {
console.log('当前 competitionOptions:', this.competitionOptions);
console.log('当前 projectOptions:', this.projectOptions);
this.dialogVisible = true;
this.loading = false;
}).catch(err => {
console.error('加载失败', err);
this.$message.error('加载参赛人员详情失败');
this.loading = false;
});
},
switchToEdit() {
@@ -584,12 +734,19 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = this.participantList.findIndex(item => item.id === row.id);
if (index !== -1) {
this.participantList.splice(index, 1);
this.saveParticipantList();
this.$message.success('删除成功');
}
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(() => {});
},
@@ -598,6 +755,47 @@ export default {
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 Promise.resolve();
}
return getProjectsByCompetition(competitionId)
.then(res => {
console.log('项目列表返回数据:', res);
const responseData = res.data?.data;
if (responseData && responseData.records) {
this.projectOptions = responseData.records.map(item => ({
id: item.id,
projectName: item.projectName,
projectCode: item.projectCode,
category: item.category
}));
console.log('可选项目列表:', this.projectOptions);
}
})
.catch(err => {
console.error('加载项目列表失败', err);
this.$message.error('加载项目列表失败');
throw err;
});
},
handleSubmit() {
@@ -613,34 +811,40 @@ export default {
}
}
setTimeout(() => {
if (this.dialogMode === 'create') {
// 新建
const newId = this.participantList.length > 0
? Math.max(...this.participantList.map(item => item.id)) + 1
: 1;
const newParticipant = {
...this.formData,
id: newId
};
this.participantList.push(newParticipant);
this.$message.success('添加成功');
} else if (this.dialogMode === 'edit') {
// 编辑
const index = this.participantList.findIndex(item => item.id === this.currentParticipantId);
if (index !== -1) {
this.participantList[index] = {
...this.participantList[index],
...this.formData
};
this.$message.success('保存成功');
}
}
const submitData = { ...this.formData };
this.saveParticipantList();
this.dialogVisible = false;
this.submitLoading = false;
}, 500);
if (this.dialogMode === 'create') {
// 新建
addParticipant(submitData)
.then(res => {
this.$message.success('添加成功');
this.dialogVisible = false;
this.loadParticipantList();
})
.catch(err => {
console.error('添加失败', err);
this.$message.error('添加失败');
})
.finally(() => {
this.submitLoading = false;
});
} else if (this.dialogMode === 'edit') {
// 编辑
submitData.id = this.currentParticipantId;
updateParticipant(submitData)
.then(res => {
this.$message.success('保存成功');
this.dialogVisible = false;
this.loadParticipantList();
})
.catch(err => {
console.error('保存失败', err);
this.$message.error('保存失败');
})
.finally(() => {
this.submitLoading = false;
});
}
} else {
this.$message.error('请完善必填信息');
}
@@ -662,7 +866,7 @@ export default {
contactPhone: '',
organization: '',
idCard: '',
projectName: '',
projectId: null,
category: '',
orderNum: 1,
introduction: '',

View File

@@ -10,19 +10,30 @@
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item>
<el-input
v-model="searchForm.keyword"
placeholder="搜索姓名/手机号"
v-model="searchForm.name"
placeholder="搜索姓名"
clearable
size="small"
style="width: 240px"
style="width: 200px"
>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
<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.refereeType" placeholder="裁判类型" clearable size="small" style="width: 180px">
<el-input
v-model="searchForm.phone"
placeholder="搜索手机号"
clearable
size="small"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-select v-model="searchForm.refereeType" placeholder="裁判类型" clearable size="small" style="width: 150px">
<el-option label="全部" :value="null"></el-option>
<el-option label="裁判" :value="1"></el-option>
<el-option label="裁判" :value="1"></el-option>
<el-option label="普通裁判" :value="2"></el-option>
</el-select>
</el-form-item>
@@ -86,7 +97,7 @@
<!-- 新增/编辑弹窗 -->
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
v-model="dialogVisible"
width="600px"
:close-on-click-modal="false"
@close="handleDialogClose"
@@ -151,15 +162,19 @@
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import { getRefereeList, submitReferee, removeReferee } from '@/api/martial/referee'
export default {
name: 'RefereeList',
data() {
@@ -170,71 +185,10 @@ export default {
dialogTitle: '新增评委',
isEdit: false,
searchForm: {
keyword: '',
name: '',
phone: '',
refereeType: null
},
allTableData: [
{
id: 1,
name: '王大伟',
gender: 1,
phone: '13800138001',
idCard: '110101197001011234',
refereeType: 1,
level: '国家一级裁判',
specialty: '太极拳、长拳',
remark: '经验丰富,专业能力强',
createTime: '2025-11-20 10:00:00'
},
{
id: 2,
name: '李美丽',
gender: 2,
phone: '13800138002',
idCard: '110101198001011234',
refereeType: 2,
level: '国家二级裁判',
specialty: '剑术、刀术',
remark: '认真负责',
createTime: '2025-11-21 11:00:00'
},
{
id: 3,
name: '张强',
gender: 1,
phone: '13800138003',
idCard: '110101197501011234',
refereeType: 2,
level: '国家一级裁判',
specialty: '棍术、枪术',
remark: '',
createTime: '2025-11-22 14:00:00'
},
{
id: 4,
name: '刘芳',
gender: 2,
phone: '13800138004',
idCard: '110101198501011234',
refereeType: 1,
level: '国际级裁判',
specialty: '太极拳、太极剑',
remark: '国际武术裁判',
createTime: '2025-11-23 15:00:00'
},
{
id: 5,
name: '陈建国',
gender: 1,
phone: '13800138005',
idCard: '110101197801011234',
refereeType: 2,
level: '国家二级裁判',
specialty: '长拳、南拳',
remark: '',
createTime: '2025-11-24 16:00:00'
}
],
tableData: [],
pagination: {
current: 1,
@@ -276,91 +230,74 @@ export default {
this.loadRefereeList()
},
methods: {
// 从 localStorage 加载数据
// 加载裁判列表
loadRefereeList() {
const savedData = localStorage.getItem('refereeList')
if (savedData) {
try {
this.allTableData = JSON.parse(savedData)
} catch (e) {
console.error('加载评委数据失败', e)
}
} else {
// 首次加载,保存默认数据
this.saveRefereeList()
}
this.fetchData()
},
// 保存数据到 localStorage
saveRefereeList() {
localStorage.setItem('refereeList', JSON.stringify(this.allTableData))
},
// 获取数据
fetchData() {
this.loading = true
const params = {}
setTimeout(() => {
// 过滤数据
let filteredData = [...this.allTableData]
// 搜索条件
if (this.searchForm.name) {
params.name = this.searchForm.name
}
if (this.searchForm.phone) {
params.phone = this.searchForm.phone
}
if (this.searchForm.refereeType !== null && this.searchForm.refereeType !== '') {
params.refereeType = this.searchForm.refereeType
}
// 搜索过滤
if (this.searchForm.keyword) {
const keyword = this.searchForm.keyword.toLowerCase()
filteredData = filteredData.filter(item =>
item.name.toLowerCase().includes(keyword) ||
item.phone.includes(keyword)
)
}
// 类型过滤
if (this.searchForm.refereeType !== null && this.searchForm.refereeType !== '') {
filteredData = filteredData.filter(item => item.refereeType === this.searchForm.refereeType)
}
this.pagination.total = filteredData.length
// 分页处理
const start = (this.pagination.current - 1) * this.pagination.size
const end = start + this.pagination.size
this.tableData = filteredData.slice(start, end)
this.loading = false
}, 300)
getRefereeList(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
this.pagination.total = responseData.total || 0
}
})
.catch(err => {
console.error('加载裁判列表失败', err)
this.$message.error('加载裁判列表失败')
})
.finally(() => {
this.loading = false
})
},
// 搜索
handleSearch() {
this.pagination.current = 1
this.fetchData()
this.loadRefereeList()
},
// 重置
handleReset() {
this.searchForm = {
keyword: '',
name: '',
phone: '',
refereeType: null
}
this.pagination.current = 1
this.fetchData()
this.loadRefereeList()
},
// 分页
handleSizeChange(size) {
this.pagination.size = size
this.fetchData()
this.pagination.current = 1
this.loadRefereeList()
},
handleCurrentChange(current) {
this.pagination.current = current
this.fetchData()
this.loadRefereeList()
},
// 新增
handleAdd() {
this.dialogTitle = '新增评委'
this.isEdit = false
this.resetFormData()
this.dialogVisible = true
},
@@ -379,13 +316,19 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = this.allTableData.findIndex(item => item.id === row.id)
if (index !== -1) {
this.allTableData.splice(index, 1)
this.saveRefereeList()
this.$message.success('删除成功')
this.fetchData()
}
this.loading = true
removeReferee(row.id.toString())
.then(res => {
this.$message.success('删除成功')
this.loadRefereeList()
})
.catch(err => {
console.error('删除失败', err)
this.$message.error('删除失败')
})
.finally(() => {
this.loading = false
})
}).catch(() => {})
},
@@ -395,35 +338,33 @@ export default {
if (valid) {
this.submitLoading = true
setTimeout(() => {
if (this.isEdit) {
// 编辑
const index = this.allTableData.findIndex(item => item.id === this.formData.id)
if (index !== -1) {
this.allTableData[index] = { ...this.formData }
this.$message.success('修改成功')
}
} else {
// 新增
const newId = this.allTableData.length > 0
? Math.max(...this.allTableData.map(item => item.id)) + 1
: 1
const now = new Date()
const createTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
const submitData = { ...this.formData }
this.allTableData.push({
...this.formData,
id: newId,
createTime
})
this.$message.success('新增成功')
}
this.saveRefereeList()
this.dialogVisible = false
this.fetchData()
// 如果是编辑,确保有 id
if (this.isEdit && !submitData.id) {
this.$message.error('编辑数据异常,请重新操作')
this.submitLoading = false
}, 500)
return
}
// 如果是新增,删除 id 字段
if (!this.isEdit) {
delete submitData.id
}
submitReferee(submitData)
.then(res => {
this.$message.success(this.isEdit ? '修改成功' : '新增成功')
this.dialogVisible = false
this.loadRefereeList()
})
.catch(err => {
console.error('提交失败', err)
this.$message.error('提交失败')
})
.finally(() => {
this.submitLoading = false
})
}
})
},
@@ -431,6 +372,11 @@ export default {
// 关闭弹窗
handleDialogClose() {
this.$refs.refereeForm.resetFields()
this.resetFormData()
},
// 重置表单数据
resetFormData() {
this.formData = {
id: null,
name: '',

View File

@@ -1,12 +1,12 @@
<template>
<div class="martial-registration-container">
<div class="martial-registration-container" v-loading="loading">
<div class="page-header">
<el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
<h2 class="page-title">报名详情</h2>
</div>
<div class="competition-info">
<h3 class="section-title">赛事名称</h3>
<h3 class="section-title">{{ competitionInfo.competitionName || '赛事信息' }}</h3>
<p class="detail-id">ID: {{ competitionInfo.id }}</p>
<el-row :gutter="15" class="info-row">
@@ -93,9 +93,6 @@
<!-- 参赛人数统计Tab -->
<div v-show="activeTab === 'participants'" class="tab-content">
<div class="tab-hint">
在营业分组: 本操作 提现栏标签 提名栏标签 比赛栏标签
</div>
<el-table :data="participantsData" border stripe size="small">
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
<el-table-column prop="schoolUnit" label="单位" min-width="180">
@@ -120,9 +117,6 @@
<!-- 项目时间统计Tab -->
<div v-show="activeTab === 'projectTime'" class="tab-content">
<div class="tab-hint">
项目时间统计
</div>
<el-table :data="projectTimeData" border stripe size="small">
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目" min-width="150">
@@ -160,121 +154,345 @@
</template>
<script>
import {
getOrderDetail,
getOrderRegistrationDetail,
getOrderProjectStats,
getOrderAmountStats
} from '@/api/martial/order'
import { getCompetitionDetail } from '@/api/martial/competition'
import { getProjectDetail } from '@/api/martial/project'
import { getParticipantList } from '@/api/martial/participant'
export default {
name: 'MartialRegistrationDetail',
data() {
return {
orderId: null,
competitionId: null, // 改为赛事ID
loading: false,
activeTab: 'participants',
competitionInfo: {
id: '123456889900',
organizer: '第三十届武术大赛主办单位',
location: '四川省 成都市',
venue: '武侯区人民医院',
registrationTime: '2025-11-10 16:00:00 - 2025-11-28 16:00:00',
competitionTime: '2025-11-10 16:00:00 - 2025-11-28 16:00:00',
totalParticipants: 99999,
totalAmount: 12345,
status: '进赛中'
id: '',
competitionName: '', // 添加赛事名称
organizer: '',
location: '',
venue: '',
registrationTime: '',
competitionTime: '',
totalParticipants: 0,
totalAmount: 0,
status: ''
},
participantsData: [
{
schoolUnit: '清河小学',
category: '集体',
individual: 1,
dual: 1,
team1101: 1,
workers: 4,
female: 5,
total: 12
},
{
schoolUnit: '方山镇小学校',
category: '',
individual: 0,
dual: 0,
team1101: 0,
workers: 0,
female: 0,
total: 0
},
{
schoolUnit: '少林寺武术学校',
category: '单人',
individual: 3,
dual: 2,
team1101: 2,
workers: 6,
female: 8,
total: 21
},
{
schoolUnit: '访河社区',
category: '集体',
individual: 2,
dual: 1,
team1101: 1,
workers: 3,
female: 4,
total: 11
}
],
projectTimeData: [
{
projectName: '小学组小组赛男女类',
hint: '剩余功能在位置提现上,显示出运动类别名称的位置',
participantCategory: '集体',
teamCount: 1,
singleTeamPeople: 10,
estimatedDuration: 4
},
{
projectName: '中学组个人赛',
participantCategory: '单人',
teamCount: 3,
singleTeamPeople: 1,
estimatedDuration: 2
},
{
projectName: '少年组对抗赛',
participantCategory: '双人',
teamCount: 2,
singleTeamPeople: 2,
estimatedDuration: 3
}
],
amountStatsData: [
{
schoolUnit: '清河小学',
projectCount: 5,
totalAmount: 9300
},
{
schoolUnit: '方山镇小学校',
projectCount: 0,
totalAmount: 0
},
{
schoolUnit: '少林寺武术学校',
projectCount: 8,
totalAmount: 15600
},
{
schoolUnit: '访河社区',
projectCount: 4,
totalAmount: 7200
}
]
participantsData: [],
projectTimeData: [],
amountStatsData: [],
projectCache: new Map(), // 添加项目信息缓存避免重复API调用
participantsCache: null // 缓存参赛者列表,避免重复查询
}
},
mounted() {
this.orderId = this.$route.query.orderId
// 使用静态数据不调用API
this.competitionId = this.$route.query.competitionId // 改为获取赛事ID
if (this.competitionId) {
this.loadCompetitionInfo(this.competitionId)
this.loadRegistrationStats()
this.loadParticipantsStats()
this.loadProjectTimeStats()
this.loadAmountStats()
} else {
this.$message.warning('未获取到赛事ID')
}
},
methods: {
// 统一获取参赛者列表(带缓存)
async getParticipants() {
if (this.participantsCache !== null) {
return this.participantsCache
}
try {
const res = await getParticipantList(this.competitionId, 1, 10000)
const participants = res.data?.data?.records || res.data?.data || []
this.participantsCache = participants
return participants
} catch (err) {
console.error('查询参赛者列表失败:', err)
return []
}
},
// 统一的项目信息获取方法(带缓存)
async getProjectInfo(projectId) {
if (!projectId) return null
// 先从缓存中查找
if (this.projectCache.has(projectId)) {
return this.projectCache.get(projectId)
}
// 缓存中没有则调用API
try {
const projectRes = await getProjectDetail(projectId)
const projectInfo = projectRes.data?.data
if (projectInfo) {
// 存入缓存
this.projectCache.set(projectId, projectInfo)
return projectInfo
}
} catch (err) {
console.error(`查询项目${projectId}详情失败:`, err)
}
return null
},
// 批量预加载项目信息(一次性加载所有需要的项目)
async preloadProjectInfo(participants) {
const projectIds = new Set()
participants.forEach(p => {
const projectId = p.projectId || p.project_id
if (projectId && !this.projectCache.has(projectId)) {
projectIds.add(projectId)
}
})
// 并行加载所有项目信息
if (projectIds.size > 0) {
const promises = Array.from(projectIds).map(id => this.getProjectInfo(id))
await Promise.all(promises)
}
},
// 加载赛事信息
async loadCompetitionInfo(competitionId) {
try {
const res = await getCompetitionDetail(competitionId)
console.log('赛事详情返回:', res)
const compData = res.data?.data
if (compData) {
this.competitionInfo.id = compData.id
this.competitionInfo.competitionName = compData.competitionName || compData.competition_name || ''
this.competitionInfo.organizer = compData.organizer || ''
this.competitionInfo.location = compData.location || ''
this.competitionInfo.venue = compData.venue || ''
// 格式化时间范围(兼容驼峰和下划线命名)
const regStartTime = compData.registrationStartTime || compData.registration_start_time
const regEndTime = compData.registrationEndTime || compData.registration_end_time
const compStartTime = compData.competitionStartTime || compData.competition_start_time
const compEndTime = compData.competitionEndTime || compData.competition_end_time
if (regStartTime && regEndTime) {
this.competitionInfo.registrationTime = `${regStartTime} - ${regEndTime}`
}
if (compStartTime && compEndTime) {
this.competitionInfo.competitionTime = `${compStartTime} - ${compEndTime}`
}
// 设置赛事状态
this.competitionInfo.status = this.getCompetitionStatus(compData.status)
}
} catch (err) {
console.error('加载赛事信息失败', err)
this.$message.error('加载赛事信息失败')
}
},
// 加载报名统计信息(统计该赛事下所有订单)
async loadRegistrationStats() {
try {
// 使用缓存的参赛者列表
const participants = await this.getParticipants()
console.log('运动员列表返回:', participants)
this.competitionInfo.totalParticipants = participants.length
// 预加载所有项目信息(一次性并行加载)
await this.preloadProjectInfo(participants)
// 计算总金额(从缓存中获取)
let totalAmount = 0
const projectIds = new Set()
for (const athlete of participants) {
const projectId = athlete.projectId || athlete.project_id
if (projectId && !projectIds.has(projectId)) {
projectIds.add(projectId)
const project = this.projectCache.get(projectId)
if (project) {
totalAmount += parseFloat(project.price || 0)
}
}
}
this.competitionInfo.totalAmount = totalAmount.toFixed(2)
} catch (err) {
console.error('加载报名统计失败', err)
this.$message.warning('加载报名统计数据失败,请稍后重试')
}
},
// 加载参赛人数统计(该赛事的所有运动员)
async loadParticipantsStats() {
try {
// 使用缓存的参赛者列表
const participants = await this.getParticipants()
console.log('参赛人员列表返回:', participants)
// 按单位分组统计
const unitMap = new Map()
participants.forEach(p => {
// 兼容驼峰和下划线命名
const unit = p.organization || p.teamName || p.team_name || '未知单位'
if (!unitMap.has(unit)) {
unitMap.set(unit, {
schoolUnit: unit,
category: '',
individual: 0,
dual: 0,
team1101: 0,
workers: 0,
female: 0,
total: 0
})
}
const stat = unitMap.get(unit)
stat.total++
if (p.gender === 2) stat.female++
})
this.participantsData = Array.from(unitMap.values())
} catch (err) {
console.error('加载参赛人员统计失败', err)
this.$message.warning('加载参赛人员统计失败')
// 使用空数组作为默认值
this.participantsData = []
}
},
// 加载项目时间统计(该赛事的所有项目及参赛人数)
async loadProjectTimeStats() {
try {
// 使用缓存的参赛者列表
const participants = await this.getParticipants()
// 2. 按项目ID分组
const projectMap = new Map()
participants.forEach(athlete => {
// 兼容驼峰和下划线命名
const projectId = athlete.projectId || athlete.project_id
if (projectId) {
if (!projectMap.has(projectId)) {
projectMap.set(projectId, [])
}
projectMap.get(projectId).push(athlete)
}
})
// 3. 从缓存中获取项目信息并统计(项目信息已经在 loadRegistrationStats 中预加载)
const projectStats = []
for (const [projectId, athleteList] of projectMap) {
const project = this.projectCache.get(projectId)
if (project) {
projectStats.push({
projectName: project.projectName || project.project_name || '未知项目',
participantCategory: project.category || '',
teamCount: 1, // 简化处理设为1
singleTeamPeople: athleteList.length,
estimatedDuration: project.estimatedDuration || project.estimated_duration || 0
})
} else {
// 如果缓存中没有理论上<E8AEBA><E4B88A><EFBFBD>应该发生添加基本信息
projectStats.push({
projectName: `项目ID:${projectId}`,
participantCategory: '',
teamCount: 1,
singleTeamPeople: athleteList.length,
estimatedDuration: 0
})
}
}
this.projectTimeData = projectStats
} catch (err) {
console.error('加载项目统计失败', err)
this.$message.warning('加载项目统计失败')
this.projectTimeData = []
}
},
// 加载金额统计(该赛事所有单位的报名金额)
async loadAmountStats() {
try {
// 使用缓存的参赛者列表
const participants = await this.getParticipants()
// 2. 按单位分组并统计
const unitMap = new Map()
for (const athlete of participants) {
// 兼容驼峰和下划线命名
const unit = athlete.organization || '未知单位'
const projectId = athlete.projectId || athlete.project_id
if (!unitMap.has(unit)) {
unitMap.set(unit, {
projectIds: new Set(),
projectPrices: new Map()
})
}
const stat = unitMap.get(unit)
// 添加项目IDSet自动去重
if (projectId) {
stat.projectIds.add(projectId)
// 从缓存中获取价格不再重复调用API
if (!stat.projectPrices.has(projectId)) {
const project = this.projectCache.get(projectId)
const price = project ? (project.price || 0) : 0
stat.projectPrices.set(projectId, parseFloat(price))
}
}
}
// 3. 计算每个单位的总金额
const amountStats = []
for (const [unit, stat] of unitMap) {
let totalAmount = 0
// 遍历该单位的所有项目,累加价格
for (const price of stat.projectPrices.values()) {
totalAmount += price
}
amountStats.push({
schoolUnit: unit,
projectCount: stat.projectIds.size,
totalAmount: totalAmount.toFixed(2)
})
}
this.amountStatsData = amountStats
} catch (err) {
console.error('加载金额统计失败', err)
this.$message.warning('加载金额统计失败')
this.amountStatsData = []
}
},
getCompetitionStatus(status) {
const statusMap = {
1: '未开始',
2: '报名中',
3: '进行中',
4: '已结束'
}
return statusMap[status] || '未知'
},
goBack() {
this.$router.go(-1)
},
handleExport() {
this.$message.success('导出功能开发中')
}

View File

@@ -1,8 +1,15 @@
<template>
<div class="martial-schedule-container">
<div class="page-header">
<el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
<h2 class="page-title">编排</h2>
<div class="header-left">
<el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
<h2 class="page-title">编排</h2>
</div>
<div class="header-right">
<el-button size="small" type="danger" @click="showExceptionDialog">
异常组 <el-badge :value="exceptionList.length" :hidden="exceptionList.length === 0" />
</el-button>
</div>
</div>
<div class="tabs-section">
@@ -25,6 +32,25 @@
<!-- 竞赛分组 Tab -->
<div v-show="activeTab === 'competition'" class="tab-content">
<!-- 场地列表 -->
<div class="venue-list">
<div class="venue-buttons">
<el-button
v-for="venue in venues"
:key="venue.id"
size="small"
:type="selectedVenueId === venue.id ? 'primary' : ''"
@click="selectedVenueId = venue.id"
>
{{ venue.venueName }}
</el-button>
<div v-if="venues.length === 0" class="no-venue-hint">
暂无场地信息请先在赛事管理中配置场地
</div>
</div>
</div>
<div class="time-selector">
<el-button
v-for="(time, index) in timeSlots"
@@ -37,7 +63,7 @@
</el-button>
</div>
<div v-for="(group, index) in competitionGroups" :key="index" class="competition-group">
<div v-for="(group, index) in filteredCompetitionGroups" :key="group.id" class="competition-group">
<div class="group-header">
<div class="group-info">
<span class="group-title">{{ group.title }}</span>
@@ -46,28 +72,28 @@
<span class="group-meta">{{ group.code }}</span>
</div>
<div class="group-actions">
<el-dropdown trigger="click" @command="(cmd) => handleVenueCommand(cmd, index)">
<el-button size="small" type="primary">
{{ group.selectedVenue || '一号场地' }}<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="venue1">一号场地</el-dropdown-item>
<el-dropdown-item command="venue2">二号场地</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button size="small" type="danger">赛程</el-button>
<el-button size="small" type="warning" @click="handleMoveGroup(group)">
移动
</el-button>
</div>
</div>
<el-table :data="group.items" border stripe size="small">
<el-table-column label="序号" type="index" width="60" align="center"></el-table-column>
<el-table-column prop="schoolUnit" label="学校/单位" min-width="200"></el-table-column>
<el-table-column label="操作" width="100" align="center">
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === '已签到' ? 'success' : scope.row.status === '异常' ? 'danger' : 'info'" size="small">
{{ scope.row.status || '未签到' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="scope">
<el-button
type="text"
size="small"
@click="handleMoveUp(index, scope.$index)"
@click="handleMoveUp(group, scope.$index)"
:disabled="scope.$index === 0 || isScheduleCompleted"
title="上移"
class="move-btn"
@@ -77,13 +103,23 @@
<el-button
type="text"
size="small"
@click="handleMoveDown(index, scope.$index)"
@click="handleMoveDown(group, scope.$index)"
:disabled="scope.$index === group.items.length - 1 || isScheduleCompleted"
title="下移"
class="move-btn"
>
<img src="/img/图标 4@3x.png" class="move-icon" alt="下移" />
</el-button>
<el-button
v-if="(scope.row.status || '未签到') === '未签到'"
type="text"
size="small"
@click="markAsException(group, scope.$index)"
:disabled="isScheduleCompleted"
style="color: #f56c6c;"
>
异常
</el-button>
</template>
</el-table-column>
</el-table>
@@ -130,8 +166,9 @@
</div>
<div class="footer-actions">
<el-button size="small" @click="handleSaveDraft" v-if="!isScheduleCompleted">保存草稿</el-button>
<el-button size="small" @click="handleExport" v-if="isScheduleCompleted">导出</el-button>
<el-button size="small" type="primary" @click="handleConfirm" v-else>完成编排</el-button>
<el-button size="small" type="primary" @click="handleConfirm" v-if="!isScheduleCompleted">完成编排</el-button>
</div>
<!-- 确认对话框 -->
@@ -149,115 +186,495 @@
<el-button type="primary" @click="confirmComplete">确定</el-button>
</span>
</el-dialog>
<!-- 移动分组对话框 -->
<el-dialog
title="移动竞赛分组"
:visible.sync="moveDialogVisible"
width="500px"
center
>
<el-form label-width="100px">
<el-form-item label="目标场地">
<el-select v-model="moveTargetVenueId" placeholder="请选择场地" style="width: 100%;">
<el-option
v-for="venue in venues"
:key="venue.id"
:label="venue.venueName"
:value="venue.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="目标时间段">
<el-select v-model="moveTargetTimeSlot" placeholder="请选择时间段" style="width: 100%;">
<el-option
v-for="(time, index) in timeSlots"
:key="index"
:label="time"
:value="index"
></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="moveDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmMoveGroup">确定</el-button>
</span>
</el-dialog>
<!-- 异常组对话框 -->
<el-dialog
title="异常组参赛人员"
:visible.sync="exceptionDialogVisible"
width="700px"
center
>
<el-table :data="exceptionList" border stripe size="small" max-height="400">
<el-table-column label="序号" type="index" width="60" align="center"></el-table-column>
<el-table-column prop="groupTitle" label="分组" min-width="180"></el-table-column>
<el-table-column prop="schoolUnit" label="学校/单位" min-width="150"></el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag type="danger" size="small">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button
type="text"
size="small"
@click="removeFromException(scope.$index)"
style="color: #409eff;"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="exceptionList.length === 0" style="text-align: center; padding: 40px; color: #909399;">
暂无异常参赛人员
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="exceptionDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getVenuesByCompetition } from '@/api/martial/venue'
import { getCompetitionDetail } from '@/api/martial/competition'
import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule } from '@/api/martial/activitySchedule'
export default {
name: 'MartialScheduleList',
data() {
return {
competitionId: null,
orderId: null,
activeTab: 'competition',
selectedTime: 0,
selectedVenueId: null, // 选中的场地ID
confirmDialogVisible: false,
isScheduleCompleted: false, // 是否已完成编排
timeSlots: [
'2025年11月6日上午8:30',
'2025年11月6日下午13:00',
'2025年11月6日下午13:00',
'2025年11月6日下午13:00'
],
competitionGroups: [
{
title: '1. 小学组小组赛男女类',
type: '集体',
count: '2队',
code: '1101',
selectedVenue: '一号场地',
items: [
{ schoolUnit: '清河小学' },
{ schoolUnit: '访河社区' }
]
},
{
title: '1. 小学组小组赛男女类',
type: '单人',
count: '3队',
code: '1组',
selectedVenue: '二号场地',
items: [
{ schoolUnit: '少林寺武校' },
{ schoolUnit: '访河社区' },
{ schoolUnit: '少林寺武校' }
]
},
{
title: '2. 中学组决赛',
type: '集体',
count: '4队',
code: '2101',
selectedVenue: '一号场地',
items: [
{ schoolUnit: '成都体育学院' },
{ schoolUnit: '武侯实验中学' },
{ schoolUnit: '石室中学' },
{ schoolUnit: '七中育才' }
]
}
],
venueData: [
{
project: '小学组小组赛男女类',
hint: '剩余功能在位置换现上,显示出已比赛名称的位置',
participant: '集体',
team: 1,
number: 1,
duration: 2,
status: '*101'
},
{
project: '中学组决赛',
participant: '单人',
team: 2,
number: 5,
duration: 3,
status: '*102'
},
{
project: '少年组对抗赛',
participant: '双人',
team: 1,
number: 3,
duration: 2,
status: ''
}
]
loading: false,
venues: [], // 场地列表(从后端加载)
competitionInfo: {
competitionName: '',
competitionStartTime: '',
competitionEndTime: ''
},
timeSlots: [], // 时间段列表(根据比赛时间动态生成)
competitionGroups: [], // 竞赛分组(从后端加载)
// 移动分组相关
moveDialogVisible: false,
moveTargetVenueId: null,
moveTargetTimeSlot: null,
moveGroupIndex: null,
// 异常组相关
exceptionDialogVisible: false,
exceptionList: [] // 异常参赛人员列表
}
},
computed: {
// 根据选中的场地和时间段过滤竞赛分组
filteredCompetitionGroups() {
console.log('=== 过滤调试信息 ===')
console.log('selectedVenueId:', this.selectedVenueId, 'type:', typeof this.selectedVenueId)
console.log('selectedTime:', this.selectedTime, 'type:', typeof this.selectedTime)
console.log('competitionGroups总数:', this.competitionGroups.length)
if (!this.selectedVenueId || this.selectedTime === null) {
console.log('场地或时间未选中,返回空数组')
return []
}
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 timeMatch = group.timeSlotIndex === this.selectedTime
console.log(` 场地匹配:${venueMatch}, 时间匹配:${timeMatch}`)
return venueMatch && timeMatch
})
console.log('过滤后的分组数量:', filtered.length)
return filtered
},
// 场地标签页的数据 - 根据选中的时间段动态生成
venueData() {
if (this.selectedTime === null) {
return []
}
// 获取选中时间段的所有分组
const groupsInTimeSlot = this.competitionGroups.filter(
group => group.timeSlotIndex === this.selectedTime
)
// 将分组转换为场地视图的数据格式
return groupsInTimeSlot.map(group => ({
project: group.title,
participant: group.type,
team: 1, // 队伍数量,可以从分组中计算
number: group.items?.length || 0, // 参赛人数
duration: 0, // 合计时间,如果后端有数据可以显示
status: group.venueName, // 显示场地名称
venueName: group.venueName,
hint: '' // 可以根据需要添加提示信息
}))
}
},
mounted() {
// 从路由获取赛事ID和订单ID
this.competitionId = this.$route.query.competitionId
this.orderId = this.$route.query.orderId
// 使用静态数据不调用API
if (this.competitionId) {
this.loadCompetitionInfo()
this.loadVenues()
this.loadScheduleData()
} else {
this.$message.warning('未获取到赛事ID')
}
},
methods: {
goBack() {
this.$router.go(-1)
},
handleVenueCommand(command, groupIndex) {
const venueName = command === 'venue1' ? '一号场地' : '二号场地'
this.competitionGroups[groupIndex].selectedVenue = venueName
this.$message.success(`已选择${venueName}`)
// 加载赛事信息
async loadCompetitionInfo() {
try {
this.loading = true
const res = await getCompetitionDetail(this.competitionId)
const data = res.data?.data
if (data) {
this.competitionInfo.competitionName = data.competitionName || data.competition_name || ''
this.competitionInfo.competitionStartTime = data.competitionStartTime || data.competition_start_time
this.competitionInfo.competitionEndTime = data.competitionEndTime || data.competition_end_time
// 生成时间段
this.generateTimeSlots()
}
} catch (err) {
console.error('加载赛事信息失败', err)
this.$message.error('加载赛事信息失败')
} finally {
this.loading = false
}
},
handleMoveUp(groupIndex, itemIndex) {
// 根据开始和结束时间生成时间段列表
generateTimeSlots() {
const startTime = this.competitionInfo.competitionStartTime
const endTime = this.competitionInfo.competitionEndTime
if (!startTime || !endTime) {
this.$message.warning('赛事时间信息不完整,使用默认时间段')
this.timeSlots = [
'2025年11月6日 上午8:30',
'2025年11月6日 下午13:30'
]
return
}
const slots = []
const start = new Date(startTime)
const end = new Date(endTime)
// 遍历每一天
let currentDate = new Date(start)
let dayIndex = 1
while (currentDate <= end) {
const year = currentDate.getFullYear()
const month = currentDate.getMonth() + 1
const day = currentDate.getDate()
const dateStr = `${year}${month}${day}`
// 添加上午时段 8:30
slots.push(`${dateStr} 上午8:30`)
// 添加下午时段 13:30
slots.push(`${dateStr} 下午13:30`)
// 下一天
currentDate.setDate(currentDate.getDate() + 1)
dayIndex++
}
this.timeSlots = slots
console.log('生成的时间段:', this.timeSlots)
},
// 加载场地列表
async loadVenues() {
try {
this.loading = true
const res = await getVenuesByCompetition(this.competitionId)
const venuesData = res.data?.data?.records || res.data?.data || []
if (venuesData.length === 0) {
this.$message.warning('该赛事暂无场地信息')
this.venues = []
} else {
this.venues = venuesData.map(v => ({
id: v.id,
venueName: v.venueName || v.venue_name,
capacity: v.capacity || 0,
location: v.location,
description: v.description
}))
// 默认选中第一个场地
if (this.venues.length > 0) {
this.selectedVenueId = this.venues[0].id
}
console.log('加载的场地数据:', this.venues)
}
} catch (err) {
console.error('加载场地失败', err)
this.$message.error('加载场地失败')
this.venues = []
} finally {
this.loading = false
}
},
// 加载编排数据
async loadScheduleData() {
try {
this.loading = true
const res = await getScheduleResult(this.competitionId)
const data = res.data?.data
console.log('=== API返回的编排数据 ===')
console.log('完整响应:', res.data)
console.log('data:', data)
if (data) {
// 判断是否有草稿或已完成的编排
this.isScheduleCompleted = data.isCompleted || false
// 加载竞赛分组数据
if (data.competitionGroups && data.competitionGroups.length > 0) {
this.competitionGroups = data.competitionGroups.map(group => ({
id: group.id,
title: group.title,
type: group.type,
count: group.count,
code: group.code,
venueId: group.venueId,
venueName: group.venueName,
timeSlot: group.timeSlot,
timeSlotIndex: group.timeSlotIndex,
items: (group.participants || []).map(p => ({
id: p.id,
schoolUnit: p.schoolUnit,
status: p.status || '未签到',
sortOrder: p.sortOrder
}))
}))
// 加载异常组数据
this.exceptionList = []
this.competitionGroups.forEach(group => {
group.items.forEach(item => {
if (item.status === '异常') {
this.exceptionList.push({
groupId: group.id,
groupTitle: group.title,
participantId: item.id,
schoolUnit: item.schoolUnit,
status: item.status
})
}
})
})
console.log('解析后的competitionGroups:', this.competitionGroups)
console.log('当前selectedVenueId:', this.selectedVenueId)
console.log('当前selectedTime:', this.selectedTime)
this.$message.success(data.isDraft ? '已加载草稿数据' : '已加载编排数据')
} else {
// 后端还没有数据,使用默认数据进行展示
this.competitionGroups = []
console.log('后端暂无编排数据,等待后端生成')
}
}
} catch (err) {
console.error('加载编排数据失败', err)
if (err.response?.status === 404) {
console.log('编排数据不存在,等待后端生成')
} else {
this.$message.error('加载编排数据失败: ' + (err.message || ''))
}
this.competitionGroups = []
} finally {
this.loading = false
}
},
// 移动分组
handleMoveGroup(group) {
if (this.isScheduleCompleted) {
this.$message.warning('编排已完成,无法移动')
return
}
this.moveGroupIndex = this.competitionGroups.findIndex(g => g.id === group.id)
this.moveTargetVenueId = group.venueId || null
this.moveTargetTimeSlot = group.timeSlotIndex || 0
this.moveDialogVisible = true
},
// 确认移动分组
confirmMoveGroup() {
if (!this.moveTargetVenueId) {
this.$message.warning('请选择目标场地')
return
}
if (this.moveTargetTimeSlot === null) {
this.$message.warning('请选择目标时间段')
return
}
const group = this.competitionGroups[this.moveGroupIndex]
const targetVenue = this.venues.find(v => v.id === this.moveTargetVenueId)
group.venueId = this.moveTargetVenueId
group.venueName = targetVenue ? targetVenue.venueName : ''
group.timeSlotIndex = this.moveTargetTimeSlot
group.timeSlot = this.timeSlots[this.moveTargetTimeSlot]
this.$message.success(`已移动到 ${group.venueName} - ${group.timeSlot}`)
this.moveDialogVisible = false
},
// 标记为异常
markAsException(group, itemIndex) {
if (this.isScheduleCompleted) {
this.$message.warning('编排已完成,无法标记异常')
return
}
const item = group.items[itemIndex]
// 修改状态为异常
item.status = '异常'
// 添加到异常组列表
this.exceptionList.push({
groupId: group.id,
groupTitle: group.title,
participantId: item.id,
schoolUnit: item.schoolUnit,
status: '异常'
})
this.$message.success(`已将 ${item.schoolUnit} 标记为异常`)
},
// 显示异常组对话框
showExceptionDialog() {
this.exceptionDialogVisible = true
},
// 从异常组移除
removeFromException(index) {
const exceptionItem = this.exceptionList[index]
// 在竞赛分组中找到对应的参赛人员并恢复状态
for (let group of this.competitionGroups) {
if (group.id === exceptionItem.groupId) {
for (let item of group.items) {
if (item.id === exceptionItem.participantId) {
item.status = '未签到'
break
}
}
break
}
}
// 从异常列表中移除
this.exceptionList.splice(index, 1)
this.$message.success(`已将 ${exceptionItem.schoolUnit} 从异常组移除`)
},
// 保存草稿
async handleSaveDraft() {
try {
this.loading = true
// 构建保存数据
const saveData = {
competitionId: this.competitionId,
isDraft: true,
competitionGroups: this.competitionGroups.map(group => ({
id: group.id,
title: group.title,
type: group.type,
count: group.count,
code: group.code,
venueId: group.venueId,
venueName: group.venueName,
timeSlot: group.timeSlot,
timeSlotIndex: group.timeSlotIndex,
participants: group.items.map((item, index) => ({
id: item.id,
schoolUnit: item.schoolUnit,
status: item.status,
sortOrder: index + 1
}))
}))
}
// 调用保存草稿接口
await saveDraftSchedule(saveData)
console.log('保存草稿数据:', saveData)
this.$message.success('草稿保存成功')
} catch (err) {
console.error('保存草稿失败', err)
this.$message.error('保存草稿失败: ' + (err.message || ''))
} finally {
this.loading = false
}
},
handleVenueCommand(command, groupIndex) {
// 这个方法已废弃,保留以防万一
},
handleMoveUp(group, itemIndex) {
if (itemIndex === 0 || this.isScheduleCompleted) return
const group = this.competitionGroups[groupIndex]
const temp = group.items[itemIndex]
group.items.splice(itemIndex, 1)
group.items.splice(itemIndex - 1, 0, temp)
this.$message.success('上移成功')
},
handleMoveDown(groupIndex, itemIndex) {
const group = this.competitionGroups[groupIndex]
handleMoveDown(group, itemIndex) {
if (itemIndex === group.items.length - 1 || this.isScheduleCompleted) return
const temp = group.items[itemIndex]
group.items.splice(itemIndex, 1)
@@ -270,11 +687,49 @@ export default {
handleConfirm() {
this.confirmDialogVisible = true
},
confirmComplete() {
// 确认完成编排
this.isScheduleCompleted = true
this.confirmDialogVisible = false
this.$message.success('编排已完成,现在可以进行调度操作')
async confirmComplete() {
try {
this.loading = true
// 1. 先保存当前的草稿数据
const saveData = {
competitionId: this.competitionId,
isDraft: true,
competitionGroups: this.competitionGroups.map(group => ({
id: group.id,
title: group.title,
type: group.type,
count: group.count,
code: group.code,
venueId: group.venueId,
venueName: group.venueName,
timeSlot: group.timeSlot,
timeSlotIndex: group.timeSlotIndex,
participants: group.items.map((item, index) => ({
id: item.id,
schoolUnit: item.schoolUnit,
status: item.status,
sortOrder: index + 1
}))
}))
}
await saveDraftSchedule(saveData)
console.log('保存草稿成功,准备锁定')
// 2. 然后调用锁定接口
await saveAndLockSchedule(saveData)
// 3. 更新UI状态
this.isScheduleCompleted = true
this.confirmDialogVisible = false
this.$message.success('编排已完成并锁定')
} catch (err) {
console.error('完成编排失败', err)
this.$message.error('完成编排失败: ' + (err.message || ''))
} finally {
this.loading = false
}
}
}
}
@@ -288,9 +743,22 @@ export default {
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
gap: 10px;
.header-left {
display: flex;
align-items: center;
gap: 10px;
}
.header-right {
display: flex;
align-items: center;
gap: 10px;
}
.page-title {
margin: 0;
font-size: 18px;
@@ -315,8 +783,37 @@ export default {
.time-selector {
margin-bottom: 15px;
padding: 8px;
background: #f5f7fa;
// padding: 8px;
// background: #f5f7fa;
}
// 场地列表样式
.venue-list {
margin-bottom: 10px;
background: #ffffff;
border: 1px solid #ffffff;
border-radius: 4px;
.venue-list-title {
font-size: 14px;
font-weight: 600;
color: #ffffff;
margin-bottom: 10px;
}
.venue-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
.no-venue-hint {
padding: 8px 12px;
color: #f59e0b;
font-size: 13px;
background: #fef3c7;
border-radius: 4px;
}
}
}
.competition-group {

View File

@@ -7,6 +7,24 @@
<!-- 查询栏 -->
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="所属赛事">
<el-select
v-model="searchForm.competitionId"
placeholder="请选择赛事"
clearable
filterable
size="small"
style="width: 200px"
@change="handleCompetitionChange"
>
<el-option
v-for="item in competitionOptions"
:key="item.id"
:label="item.competitionName"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="比赛项目">
<el-select
v-model="searchForm.projectId"
@@ -18,27 +36,9 @@
@change="handleSearch"
>
<el-option
v-for="item in projectList"
v-for="item in projectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="场地">
<el-select
v-model="searchForm.venueId"
placeholder="请选择场地"
clearable
filterable
size="small"
style="width: 150px"
@change="handleSearch"
>
<el-option
v-for="item in venueList"
:key="item.id"
:label="item.name"
:label="item.projectName"
:value="item.id"
></el-option>
</el-select>
@@ -162,33 +162,26 @@
</template>
<script>
import { getScoreList, getAthleteScores } from '@/api/martial/score'
import { getProjectList } from '@/api/martial/project'
import { getCompetitionList } from '@/api/martial/competition'
import { getParticipantDetail } from '@/api/martial/participant'
export default {
name: 'ScoreManagement',
data() {
return {
loading: false,
searchForm: {
competitionId: null,
projectId: null,
venueId: null,
playerName: ''
},
projectList: [
{ id: 1, name: '女子组长拳' },
{ id: 2, name: '男子组陈氏太极拳' },
{ id: 3, name: '女子组双剑(含长穗双剑)' },
{ id: 4, name: '男子组杨氏太极拳' },
{ id: 5, name: '女子组刀术' },
{ id: 6, name: '男子组棍术' },
{ id: 7, name: '女子组枪术' },
{ id: 8, name: '男子组剑术' }
],
venueList: [
{ id: 1, name: '第一场地' },
{ id: 2, name: '第二场地' },
{ id: 3, name: '第三场地' },
{ id: 4, name: '第四场地' },
{ id: 5, name: '第五场地' }
],
competitionOptions: [],
projectOptions: [],
venueOptions: [],
scoreList: [],
allTableData: [
{
id: 1,
@@ -291,109 +284,279 @@ export default {
}
},
mounted() {
this.loadCompetitions()
this.loadScoreList()
},
methods: {
// 从 localStorage 加载数据
loadScoreList() {
const savedData = localStorage.getItem('scoreList')
if (savedData) {
try {
this.allTableData = JSON.parse(savedData)
} catch (e) {
console.error('加载评分数据失败', e)
}
} else {
// 首次加载,保存默认数据
this.saveScoreList()
// 加载赛事列表
loadCompetitions() {
getCompetitionList(1, 100, {})
.then(res => {
const responseData = res.data?.data
if (responseData && responseData.records) {
this.competitionOptions = responseData.records.map(item => ({
id: item.id,
competitionName: item.competitionName
}))
}
})
.catch(err => {
console.error('加载赛事列表失败', err)
})
},
// 加载项目列表
loadProjects() {
if (!this.searchForm.competitionId) {
this.projectOptions = []
return
}
this.fetchData()
getProjectList(1, 100, { competitionId: this.searchForm.competitionId })
.then(res => {
const responseData = res.data?.data
if (responseData && responseData.records) {
this.projectOptions = responseData.records.map(item => ({
id: item.id,
projectName: item.projectName
}))
}
})
.catch(err => {
console.error('加载项目列表失败', err)
})
},
// 保存数据到 localStorage
saveScoreList() {
localStorage.setItem('scoreList', JSON.stringify(this.allTableData))
},
// 获取评分数据
fetchData() {
// 加载评分列表
async loadScoreList() {
this.loading = true
const params = {}
setTimeout(() => {
// 过滤数据
let filteredData = [...this.allTableData]
if (this.searchForm.competitionId) {
params.competitionId = this.searchForm.competitionId
}
if (this.searchForm.projectId) {
params.projectId = this.searchForm.projectId
}
if (this.searchForm.venueId) {
params.venueId = this.searchForm.venueId
}
if (this.searchForm.projectId) {
const project = this.projectList.find(p => p.id === this.searchForm.projectId)
if (project) {
filteredData = filteredData.filter(item => item.projectName === project.name)
}
try {
const res = await getScoreList(this.pagination.current, this.pagination.size, params)
console.log('评分列表返回数据:', res)
console.log('===== 调试:后端返回的数据结构 =====')
const responseData = res.data?.data
if (responseData && responseData.records && responseData.records.length > 0) {
console.log('第一条评分记录:', responseData.records[0])
console.log('记录字段:', Object.keys(responseData.records[0]))
console.log('是否包含 projectName:', 'projectName' in responseData.records[0])
console.log('是否包含 venueName:', 'venueName' in responseData.records[0])
console.log('是否包含 playerName:', 'playerName' in responseData.records[0])
console.log('projectId 值:', responseData.records[0].projectId)
console.log('venueId 值:', responseData.records[0].venueId)
console.log('athleteId 值:', responseData.records[0].athleteId)
}
console.log('======================================')
if (this.searchForm.venueId) {
const venue = this.venueList.find(v => v.id === this.searchForm.venueId)
if (venue) {
filteredData = filteredData.filter(item => item.venueName === venue.name)
}
if (responseData && responseData.records) {
this.scoreList = responseData.records
// 补充关联数据(项目名称、场地名称、选手名称)
await this.enrichScoreData(responseData.records)
// 按选手分组评分数据
this.processScoreData(this.scoreList)
this.pagination.total = this.tableData.length
}
if (this.searchForm.playerName) {
filteredData = filteredData.filter(item =>
item.playerName.includes(this.searchForm.playerName)
)
}
this.pagination.total = filteredData.length
// 分页
const start = (this.pagination.current - 1) * this.pagination.size
const end = start + this.pagination.size
this.tableData = filteredData.slice(start, end)
} catch (err) {
console.error('加载评分列表失败', err)
this.$message.error('加载评分列表失败')
} finally {
this.loading = false
}, 300)
}
},
// 补充评分数据中缺失的关联字段
async enrichScoreData(scores) {
if (!scores || scores.length === 0) return
// 收集所有唯一的 ID
const projectIds = new Set()
const athleteIds = new Set()
scores.forEach(score => {
if (score.projectId) projectIds.add(score.projectId)
if (score.athleteId) athleteIds.add(score.athleteId)
})
// 批量查询项目信息
const projectMap = new Map()
if (projectIds.size > 0) {
try {
// 获取所有项目
const projectRes = await getProjectList(1, 1000, {})
const projectData = projectRes.data?.data
if (projectData && projectData.records) {
projectData.records.forEach(project => {
projectMap.set(project.id, project.projectName)
})
}
} catch (err) {
console.error('加载项目列表失败', err)
}
}
// 批量查询选手信息
const athleteMap = new Map()
if (athleteIds.size > 0) {
try {
// 逐个查询选手详情(因为没有批量接口)
const athletePromises = Array.from(athleteIds).map(id =>
getParticipantDetail(id).catch(err => {
console.error(`查询选手 ${id} 失败:`, err)
return null
})
)
const athleteResults = await Promise.all(athletePromises)
athleteResults.forEach(res => {
if (res && res.data?.data) {
const athlete = res.data.data
athleteMap.set(athlete.id, {
playerName: athlete.playerName,
teamName: athlete.teamName || athlete.organization,
idCard: athlete.idCard,
playerNo: athlete.orderNum ? `NO-${athlete.orderNum}` : ''
})
}
})
} catch (err) {
console.error('加载选手信息失败', err)
}
}
// 为每条评分记录补充关联字段
scores.forEach(score => {
// 补充项目名称
if (score.projectId && projectMap.has(score.projectId)) {
score.projectName = projectMap.get(score.projectId)
}
// 补充选手信息
if (score.athleteId && athleteMap.has(score.athleteId)) {
const athleteInfo = athleteMap.get(score.athleteId)
score.playerName = athleteInfo.playerName
score.teamName = athleteInfo.teamName
score.idCard = athleteInfo.idCard
score.playerNo = athleteInfo.playerNo
}
// 场地名称暂时使用场地ID显示因为没有场地API
if (score.venueId) {
score.venueName = `场地${score.venueId}`
}
})
console.log('数据补充完成,示例记录:', scores[0])
},
// 处理评分数据,按选手分组
processScoreData(scores) {
const athleteMap = new Map()
scores.forEach(score => {
const key = `${score.athleteId}-${score.projectId}`
if (!athleteMap.has(key)) {
athleteMap.set(key, {
id: score.athleteId,
athleteId: score.athleteId,
projectId: score.projectId,
projectName: score.projectName || '',
venueName: score.venueName || '',
playerName: score.playerName || '',
teamName: score.teamName || '',
idCard: score.idCard || '',
playerNo: score.playerNo || '',
judgeScores: [],
scoreDetails: [],
totalScore: 0
})
}
const athlete = athleteMap.get(key)
athlete.judgeScores.push(parseFloat(score.score) || 0)
athlete.scoreDetails.push({
judgeName: score.judgeName || '未知裁判',
score: parseFloat(score.score) || 0,
deductions: score.deductionItemsText || '无',
note: score.note || '',
scoreTime: score.scoreTime || score.createTime || ''
})
})
// 计算总分(平均分)
this.tableData = Array.from(athleteMap.values()).map(athlete => {
if (athlete.judgeScores.length > 0) {
const sum = athlete.judgeScores.reduce((a, b) => a + b, 0)
athlete.totalScore = sum / athlete.judgeScores.length
}
return athlete
})
// 根据选手姓名过滤
if (this.searchForm.playerName) {
this.tableData = this.tableData.filter(item =>
item.playerName.includes(this.searchForm.playerName)
)
}
// 更新裁判列数
const maxJudges = Math.max(...this.tableData.map(item => item.judgeScores.length), 0)
this.judgeColumns = Array(maxJudges).fill(null)
},
// 查询
handleSearch() {
this.pagination.current = 1
this.fetchData()
this.loadScoreList()
},
// 重置
handleReset() {
this.searchForm = {
competitionId: null,
projectId: null,
venueId: null,
playerName: ''
}
this.projectOptions = []
this.handleSearch()
},
// 赛事变化
handleCompetitionChange() {
this.searchForm.projectId = null
this.projectOptions = []
this.loadProjects()
},
// 分页大小变化
handleSizeChange(size) {
this.pagination.size = size
this.fetchData()
this.pagination.current = 1
this.loadScoreList()
},
// 当前页变化
handleCurrentChange(current) {
this.pagination.current = current
this.fetchData()
this.loadScoreList()
},
// 查看详情
handleViewDetail(row) {
this.currentDetail = {
...row,
scoreDetails: [
{ judgeName: '裁判1', score: row.judgeScores[0], deductions: '无', note: '', scoreTime: '2025-11-29 10:30:00' },
{ judgeName: '裁判2', score: row.judgeScores[1], deductions: '无', note: '', scoreTime: '2025-11-29 10:30:05' },
{ judgeName: '裁判3', score: row.judgeScores[2], deductions: '扣分项描述', note: '动作不规范', scoreTime: '2025-11-29 10:30:10' },
{ judgeName: '裁判4', score: row.judgeScores[3], deductions: '无', note: '', scoreTime: '2025-11-29 10:30:15' },
{ judgeName: '裁判5', score: row.judgeScores[4], deductions: '无', note: '表现优秀', scoreTime: '2025-11-29 10:30:20' }
]
}
this.currentDetail = { ...row }
this.detailDialogVisible = true
},