重构赛事信息(修改树状图前)
This commit is contained in:
parent
0274eb5407
commit
605d60ec7e
@ -3,239 +3,225 @@ import axiosInstance from './axiosConfig';
|
|||||||
/**
|
/**
|
||||||
* 添加赛事
|
* 添加赛事
|
||||||
* @param {Object} tournamentData - 赛事数据
|
* @param {Object} tournamentData - 赛事数据
|
||||||
* @param {string} tournamentData.name - 赛事名称
|
* @param {number} tournamentData.id - 数据库中id
|
||||||
* @param {string} tournamentData.format - 赛事类型(single, double, count)
|
* @param {string} tournamentData.name - 名称
|
||||||
|
* @param {string} tournamentData.format - 类型(single, double, count)
|
||||||
* @param {string} tournamentData.organizer - 组织者
|
* @param {string} tournamentData.organizer - 组织者
|
||||||
* @param {string} tournamentData.qq_code - QQ号
|
* @param {string} tournamentData.qq_code - QQ号
|
||||||
* @param {string} tournamentData.status - 状态(prepare, finish, starting)
|
* @param {string} tournamentData.status - 状态(prepare, finish, starting)
|
||||||
* @param {string} tournamentData.start_time - 开始时间(格式年/月/日,例2025/05/24)
|
* @param {string} tournamentData.start_time - 开始时间(格式年/月/日,例2025/05/24)
|
||||||
* @param {string} tournamentData.end_time - 结束时间(格式年/月/日,例2025/05/24)
|
* @param {string} tournamentData.end_time - 结束时间(格式年/月/日,例2025/05/24)
|
||||||
* @returns {Promise} 返回添加赛事的响应数据
|
* @returns {Promise<Object>} 返回添加赛事的响应数据
|
||||||
*/
|
*/
|
||||||
export const addTournament = async (tournamentData) => {
|
export const addTournament = async (tournamentData) => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.post('/tournament/add', tournamentData)
|
const response = await axiosInstance.post('/tournament/add', tournamentData);
|
||||||
return response.data
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('添加赛事失败:', {
|
console.error('添加赛事失败:', error);
|
||||||
status: error.response?.status,
|
throw error;
|
||||||
data: error.response?.data,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取赛事列表
|
* 获取赛事列表
|
||||||
* @returns {Promise} 返回赛事列表数据
|
* @returns {Promise<Array<Object>>} 返回赛事列表数据
|
||||||
*/
|
*/
|
||||||
export const getTournamentList = async () => {
|
export const getTournamentList = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get('/tournament/getlist')
|
const response = await axiosInstance.get('/tournament/getlist');
|
||||||
return response.data
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取赛事列表失败:', {
|
console.error('获取赛事列表失败:', error);
|
||||||
status: error.response?.status,
|
throw error;
|
||||||
data: error.response?.data,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
// 更新赛事
|
* 更新赛事
|
||||||
export const updateTournament = async (id, data) => {
|
* @param {number} id - 赛事ID
|
||||||
|
* @param {Object} tournamentData - 赛事数据
|
||||||
|
* @param {number} tournamentData.id - 数据库中id
|
||||||
|
* @param {string} tournamentData.name - 名称
|
||||||
|
* @param {string} tournamentData.format - 类型(single, double, count)
|
||||||
|
* @param {string} tournamentData.organizer - 组织者
|
||||||
|
* @param {string} tournamentData.qq_code - QQ号
|
||||||
|
* @param {string} tournamentData.status - 状态(prepare, finish, starting)
|
||||||
|
* @param {string} tournamentData.start_time - 开始时间(格式年/月/日,例2025/05/24)
|
||||||
|
* @param {string} tournamentData.end_time - 结束时间(格式年/月/日,例2025/05/24)
|
||||||
|
* @returns {Promise<Object>} 返回更新赛事的响应数据
|
||||||
|
*/
|
||||||
|
export const updateTournament = async (id, tournamentData) => {
|
||||||
try {
|
try {
|
||||||
console.log('更新赛事,发送数据:', data)
|
const response = await axiosInstance.put(`/tournament/update/${id}`, tournamentData);
|
||||||
const response = await axiosInstance.put(`/tournament/update/${id}`, {
|
return response.data;
|
||||||
name: data.name,
|
|
||||||
format: data.format,
|
|
||||||
organizer: data.organizer,
|
|
||||||
qq_code: data.qq_code,
|
|
||||||
start_time: data.start_time,
|
|
||||||
end_time: data.end_time,
|
|
||||||
status: data.status
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新赛事失败:', error)
|
console.error('更新赛事失败:', error);
|
||||||
if (error.response) {
|
throw error;
|
||||||
console.error('错误详情:', {
|
|
||||||
status: error.response.status,
|
|
||||||
data: error.response.data,
|
|
||||||
headers: error.response.headers,
|
|
||||||
config: error.config
|
|
||||||
})
|
|
||||||
// 如果有详细的错误信息,抛出它
|
|
||||||
if (error.response.data?.detail) {
|
|
||||||
throw new Error(error.response.data.detail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 删除赛事
|
/**
|
||||||
|
* 删除赛事
|
||||||
|
* @param {number} id - 赛事ID
|
||||||
|
* @returns {Promise<Object>} 返回删除赛事的响应数据
|
||||||
|
*/
|
||||||
export const deleteTournament = async (id) => {
|
export const deleteTournament = async (id) => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.delete(`/tournament/delete/${id}`)
|
const response = await axiosInstance.delete(`/tournament/delete/${id}`);
|
||||||
return response.data
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除赛事失败:', error)
|
console.error('删除赛事失败:', error);
|
||||||
throw error
|
throw error;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
// 添加报名结果
|
* 添加玩家报名
|
||||||
export const addSignUpResult = async (data) => {
|
* @param {Object} signupData - 报名数据
|
||||||
|
* @param {number} signupData.id - 数据库中id
|
||||||
|
* @param {string} signupData.type - 类型
|
||||||
|
* @param {string} signupData.teamname - 队伍名称
|
||||||
|
* @param {string} signupData.faction - 阵营(allied、soviet、empire、ob、voice、random)
|
||||||
|
* @param {string} signupData.username - 用户名
|
||||||
|
* @param {string} signupData.qq - QQ号
|
||||||
|
* @returns {Promise<Object>} 返回添加报名的响应数据
|
||||||
|
*/
|
||||||
|
export const addSignUp = async (signupData) => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.post('/tournament/signup_result/add', {
|
const response = await axiosInstance.post('/tournament/signup/add', signupData);
|
||||||
tournament_id: parseInt(data.tournament_id),
|
return response.data;
|
||||||
tournament_name: data.tournament_name,
|
|
||||||
team_name: data.team_name,
|
|
||||||
sign_name: data.sign_name.trim(),
|
|
||||||
win: '0',
|
|
||||||
lose: '0',
|
|
||||||
status: 'tie'
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求错误:', error)
|
console.error('添加报名失败:', error);
|
||||||
if (error.response?.data?.detail) {
|
throw error;
|
||||||
throw new Error(error.response.data.detail)
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取参赛结果列表
|
/**
|
||||||
|
* 获取玩家报名列表
|
||||||
|
* @returns {Promise<Array<Object>>} 返回报名列表数据
|
||||||
|
*/
|
||||||
|
export const getSignUpList = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get('/tournament/signup/getlist');
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取报名列表失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新玩家报名
|
||||||
|
* @param {number} id - 报名ID
|
||||||
|
* @param {Object} signupData - 报名数据
|
||||||
|
* @param {number} signupData.id - 数据库中id
|
||||||
|
* @param {string} signupData.type - 类型
|
||||||
|
* @param {string} signupData.teamname - 队伍名称
|
||||||
|
* @param {string} signupData.faction - 阵营(allied、soviet、empire、ob、voice、random)
|
||||||
|
* @param {string} signupData.username - 用户名
|
||||||
|
* @param {string} signupData.qq - QQ号
|
||||||
|
* @returns {Promise<Object>} 返回更新报名的响应数据
|
||||||
|
*/
|
||||||
|
export const updateSignUp = async (id, signupData) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.put(`/tournament/signup/update/${id}`, signupData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新报名失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除玩家报名
|
||||||
|
* @param {number} id - 报名ID
|
||||||
|
* @returns {Promise<Object>} 返回删除报名的响应数据
|
||||||
|
*/
|
||||||
|
export const deleteSignUp = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.delete(`/tournament/signup/delete/${id}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除报名失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加报名结果
|
||||||
|
* @param {Object} resultData - 结果数据
|
||||||
|
* @param {number} resultData.tournament_id - 赛事id(int)
|
||||||
|
* @param {string} resultData.tournament_name - 赛事名称
|
||||||
|
* @param {string} resultData.team_name - 队伍名称(可选)
|
||||||
|
* @param {string} resultData.sign_name - 参赛人员名称
|
||||||
|
* @param {string} resultData.win - 参赛人员胜利局数(str)
|
||||||
|
* @param {string} resultData.lose - 参赛人员失败局数(str)
|
||||||
|
* @param {string} resultData.status - 参赛人员对局状态(win,lose,tie)
|
||||||
|
* @param {string} resultData.round - 轮数(str)
|
||||||
|
* @param {string} resultData.rival_name - 对方name(str)
|
||||||
|
* @returns {Promise<Object>} 返回添加报名结果的响应数据
|
||||||
|
*/
|
||||||
|
export const addSignUpResult = async (resultData) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.post('/tournament/signup_result/add', resultData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加报名结果失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取报名结果列表
|
||||||
|
* @returns {Promise<Array<Object>>} 返回报名结果列表数据
|
||||||
|
*/
|
||||||
export const getSignUpResultList = async () => {
|
export const getSignUpResultList = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get('/tournament/signup_result/getlist')
|
const response = await axiosInstance.get('/tournament/signup_result/getlist');
|
||||||
return response.data
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取参赛结果列表失败:', {
|
console.error('获取报名结果列表失败:', error);
|
||||||
status: error.response?.status,
|
throw error;
|
||||||
data: error.response?.data,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 更新参赛结果
|
/**
|
||||||
export const updateSignUpResult = async (id, data) => {
|
* 更新报名结果
|
||||||
|
* @param {number} id - 结果ID
|
||||||
|
* @param {Object} resultData - 结果数据
|
||||||
|
* @param {number} resultData.tournament_id - 赛事id(int)
|
||||||
|
* @param {string} resultData.tournament_name - 赛事名称
|
||||||
|
* @param {string} resultData.team_name - 队伍名称(可选)
|
||||||
|
* @param {string} resultData.sign_name - 参赛人员名称
|
||||||
|
* @param {string} resultData.win - 参赛人员胜利局数(str)
|
||||||
|
* @param {string} resultData.lose - 参赛人员失败局数(str)
|
||||||
|
* @param {string} resultData.status - 参赛人员对局状态(win,lose,tie)
|
||||||
|
* @param {string} resultData.round - 轮数(str)
|
||||||
|
* @param {string} resultData.rival_name - 对方name(str)
|
||||||
|
* @returns {Promise<Object>} 返回更新报名结果的响应数据
|
||||||
|
*/
|
||||||
|
export const updateSignUpResult = async (id, resultData) => {
|
||||||
try {
|
try {
|
||||||
// // 更新报名信息 (这部分逻辑根据您的要求被注释掉)
|
const response = await axiosInstance.put(`/tournament/signup_result/update/${id}`, resultData);
|
||||||
// console.log('更新报名信息...')
|
return response.data;
|
||||||
// await axiosInstance.put(`/tournament/signup/update/${id}`, {
|
|
||||||
// tournament_id: parseInt(data.tournament_id),
|
|
||||||
// type: data.team_name ? 'teamname' : 'individual',
|
|
||||||
// teamname: data.team_name || '',
|
|
||||||
// faction: data.faction || 'random',
|
|
||||||
// username: data.sign_name,
|
|
||||||
// qq: data.qq || ''
|
|
||||||
// })
|
|
||||||
// console.log('报名信息更新成功')
|
|
||||||
|
|
||||||
// 更新报名结果
|
|
||||||
console.log('更新报名结果...')
|
|
||||||
await axiosInstance.put(`/tournament/signup_result/update/${id}`, {
|
|
||||||
tournament_id: parseInt(data.tournament_id),
|
|
||||||
tournament_name: data.tournament_name,
|
|
||||||
team_name: data.team_name || null,
|
|
||||||
sign_name: data.sign_name,
|
|
||||||
win: data.win || '0',
|
|
||||||
lose: data.lose || '0',
|
|
||||||
status: data.status || 'tie'
|
|
||||||
})
|
|
||||||
console.log('报名结果更新成功')
|
|
||||||
|
|
||||||
return { success: true }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新参赛结果失败:', {
|
console.error('更新报名结果失败:', error);
|
||||||
status: error.response?.status,
|
throw error;
|
||||||
data: error.response?.data,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 删除参赛选手
|
/**
|
||||||
|
* 删除报名结果
|
||||||
|
* @param {number} id - 结果ID
|
||||||
|
* @returns {Promise<Object>} 返回删除报名结果的响应数据
|
||||||
|
*/
|
||||||
export const deleteSignUpResult = async (id) => {
|
export const deleteSignUpResult = async (id) => {
|
||||||
try {
|
try {
|
||||||
// 删除报名结果
|
const response = await axiosInstance.delete(`/tournament/signup_result/delete/${id}`);
|
||||||
console.log('删除报名结果...')
|
return response.data;
|
||||||
await axiosInstance.delete(`/tournament/signup_result/delete/${id}`)
|
|
||||||
console.log('报名结果删除成功')
|
|
||||||
|
|
||||||
// // 删除报名信息 (这部分逻辑根据您的要求被注释掉)
|
|
||||||
// console.log('删除报名信息...')
|
|
||||||
// await axiosInstance.delete(`/tournament/signup/delete/${id}`)
|
|
||||||
// console.log('报名信息删除成功')
|
|
||||||
|
|
||||||
return { success: true }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除参赛选手失败:', {
|
console.error('删除报名结果失败:', error);
|
||||||
status: error.response?.status,
|
throw error;
|
||||||
data: error.response?.data,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加报名
|
|
||||||
export const addSignUp = async (data) => {
|
|
||||||
try {
|
|
||||||
console.log('开始报名流程,数据:', data)
|
|
||||||
|
|
||||||
// 调用报名 API
|
|
||||||
console.log('调用报名 API...')
|
|
||||||
await axiosInstance.post('/tournament/signup/add', {
|
|
||||||
tournament_id: data.id,
|
|
||||||
type: data.type,
|
|
||||||
teamname: data.team_name || '',
|
|
||||||
faction: data.faction || 'random',
|
|
||||||
username: data.sign_name,
|
|
||||||
qq: data.qq || ''
|
|
||||||
})
|
|
||||||
console.log('报名 API 调用成功')
|
|
||||||
|
|
||||||
// 调用报名结果 API
|
|
||||||
console.log('调用报名结果 API...')
|
|
||||||
await axiosInstance.post('/tournament/signup_result/add', {
|
|
||||||
tournament_id: data.id,
|
|
||||||
tournament_name: data.tournament_name,
|
|
||||||
team_name: data.team_name || null,
|
|
||||||
sign_name: data.sign_name,
|
|
||||||
win: '0',
|
|
||||||
lose: '0',
|
|
||||||
status: 'tie'
|
|
||||||
})
|
|
||||||
console.log('报名结果 API 调用成功')
|
|
||||||
|
|
||||||
return {
|
|
||||||
signup: { success: true },
|
|
||||||
result: { success: true }
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('报名请求错误:', {
|
|
||||||
message: error.message,
|
|
||||||
response: error.response?.data,
|
|
||||||
status: error.response?.status,
|
|
||||||
config: error.config
|
|
||||||
})
|
|
||||||
|
|
||||||
// 如果是服务器返回的错误信息,直接使用
|
|
||||||
if (error.response?.data?.detail) {
|
|
||||||
throw new Error(error.response.data.detail)
|
|
||||||
}
|
|
||||||
// 其他错误,包装成更友好的错误信息
|
|
||||||
throw new Error('报名失败,请检查网络连接后重试')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
@ -60,26 +60,26 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: 'competition',
|
path: 'competition',
|
||||||
name: 'Competition',
|
name: 'Competition',
|
||||||
component: () => import('@/views/index/Competition.vue'),
|
component: () => import('@/views/competition/Competition.vue'),
|
||||||
// meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-competitor'] }
|
// meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-competitor'] }
|
||||||
meta: { requiresAuth: true}
|
meta: { requiresAuth: true}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'competition/add',
|
path: 'competition/add',
|
||||||
name: 'AddCompetition',
|
name: 'AddCompetition',
|
||||||
component: () => import('@/views/index/AddContestant.vue'),
|
component: () => import('@/views/competition/AddContestant.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'competition/detail',
|
path: 'competition/detail',
|
||||||
name: 'CompetitionDetail',
|
name: 'CompetitionDetail',
|
||||||
component: () => import('@/views/index/CompetitionDetail.vue'),
|
component: () => import('@/views/competition/CompetitionDetail.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'competition/signup',
|
path: 'competition/signup',
|
||||||
name: 'CompetitionSignUp',
|
name: 'CompetitionSignUp',
|
||||||
component: () => import('@/views/index/CompetitionSignUp.vue'),
|
component: () => import('@/views/competition/CompetitionSignUp.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
@ -3,62 +3,62 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>添加新赛事</h1>
|
<h1>添加新赛事</h1>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="btn-excel" @click="handleExcelImport">
|
<!-- <button class="btn-excel" @click="handleExcelImport">-->
|
||||||
<i class="fas fa-file-excel"></i>
|
<!-- <i class="fas fa-file-excel"></i>-->
|
||||||
通过表格添加
|
<!-- 通过表格添加-->
|
||||||
</button>
|
<!-- </button>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Excel导入弹窗 -->
|
<!-- <!– Excel导入弹窗 –>-->
|
||||||
<div v-if="showExcelDialog" class="excel-dialog-overlay">
|
<!-- <div v-if="showExcelDialog" class="excel-dialog-overlay">-->
|
||||||
<div class="excel-dialog">
|
<!-- <div class="excel-dialog">-->
|
||||||
<h3>通过Excel导入赛事信息</h3>
|
<!-- <h3>通过Excel导入赛事信息</h3>-->
|
||||||
<div class="excel-upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleFileDrop"
|
<!-- <div class="excel-upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleFileDrop"-->
|
||||||
:class="{ 'is-dragover': isDragover }">
|
<!-- :class="{ 'is-dragover': isDragover }">-->
|
||||||
<input type="file" ref="fileInput" accept=".xlsx" @change="handleFileSelect" style="display: none">
|
<!-- <input type="file" ref="fileInput" accept=".xlsx" @change="handleFileSelect" style="display: none">-->
|
||||||
<div class="upload-content">
|
<!-- <div class="upload-content">-->
|
||||||
<i class="fas fa-file-excel"></i>
|
<!-- <i class="fas fa-file-excel"></i>-->
|
||||||
<p>点击或拖拽Excel文件到此处</p>
|
<!-- <p>点击或拖拽Excel文件到此处</p>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div v-if="eventPreviewData.length > 0" class="preview-table">
|
<!-- <div v-if="eventPreviewData.length > 0" class="preview-table">-->
|
||||||
<h4>赛事信息表预览</h4>
|
<!-- <h4>赛事信息表预览</h4>-->
|
||||||
<table>
|
<!-- <table>-->
|
||||||
<thead>
|
<!-- <thead>-->
|
||||||
<tr>
|
<!-- <tr>-->
|
||||||
<th v-for="h in eventPreviewHeaders" :key="h">{{ h }}</th>
|
<!-- <th v-for="h in eventPreviewHeaders" :key="h">{{ h }}</th>-->
|
||||||
</tr>
|
<!-- </tr>-->
|
||||||
</thead>
|
<!-- </thead>-->
|
||||||
<tbody>
|
<!-- <tbody>-->
|
||||||
<tr v-for="(item, index) in eventPreviewData" :key="index">
|
<!-- <tr v-for="(item, index) in eventPreviewData" :key="index">-->
|
||||||
<td v-for="h in eventPreviewHeaders" :key="h">{{ item[h] }}</td>
|
<!-- <td v-for="h in eventPreviewHeaders" :key="h">{{ item[h] }}</td>-->
|
||||||
</tr>
|
<!-- </tr>-->
|
||||||
</tbody>
|
<!-- </tbody>-->
|
||||||
</table>
|
<!-- </table>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div v-if="playerPreviewData.length > 0" class="preview-table">
|
<!-- <div v-if="playerPreviewData.length > 0" class="preview-table">-->
|
||||||
<h4>选手报名表预览</h4>
|
<!-- <h4>选手报名表预览</h4>-->
|
||||||
<table>
|
<!-- <table>-->
|
||||||
<thead>
|
<!-- <thead>-->
|
||||||
<tr>
|
<!-- <tr>-->
|
||||||
<th v-for="h in playerPreviewHeaders" :key="h">{{ h }}</th>
|
<!-- <th v-for="h in playerPreviewHeaders" :key="h">{{ h }}</th>-->
|
||||||
</tr>
|
<!-- </tr>-->
|
||||||
</thead>
|
<!-- </thead>-->
|
||||||
<tbody>
|
<!-- <tbody>-->
|
||||||
<tr v-for="(item, index) in playerPreviewData" :key="index">
|
<!-- <tr v-for="(item, index) in playerPreviewData" :key="index">-->
|
||||||
<td v-for="h in playerPreviewHeaders" :key="h">{{ item[h] }}</td>
|
<!-- <td v-for="h in playerPreviewHeaders" :key="h">{{ item[h] }}</td>-->
|
||||||
</tr>
|
<!-- </tr>-->
|
||||||
</tbody>
|
<!-- </tbody>-->
|
||||||
</table>
|
<!-- </table>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div class="dialog-actions">
|
<!-- <div class="dialog-actions">-->
|
||||||
<button class="confirm-btn" @click="confirmImport" :disabled="eventPreviewData.length === 0">确认导入</button>
|
<!-- <button class="confirm-btn" @click="confirmImport" :disabled="eventPreviewData.length === 0">确认导入</button>-->
|
||||||
<button class="cancel-btn" @click="closeExcelDialog">取消</button>
|
<!-- <button class="cancel-btn" @click="closeExcelDialog">取消</button>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<form @submit.prevent="handleSubmit" class="contest-form">
|
<form @submit.prevent="handleSubmit" class="contest-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -119,7 +119,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { addTournament, addSignUp, getTournamentList } from '@/api/tournament'
|
import { addTournament, addSignUp, getTournamentList } from '@/api/tournament.js'
|
||||||
import * as XLSX from 'xlsx'
|
import * as XLSX from 'xlsx'
|
||||||
import SuccessDialog from '@/components/SuccessDialog.vue'
|
import SuccessDialog from '@/components/SuccessDialog.vue'
|
||||||
import ErrorDialog from '@/components/ErrorDialog.vue'
|
import ErrorDialog from '@/components/ErrorDialog.vue'
|
@ -1,271 +1,488 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import TournamentBracket from '@/components/TournamentBracket.vue';
|
|
||||||
|
|
||||||
// 比赛状态
|
|
||||||
const activeTab = ref('upcoming'); // upcoming, ongoing, completed
|
|
||||||
const tournaments = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
// 当前选中的比赛ID
|
|
||||||
const selectedTournamentId = ref(null);
|
|
||||||
|
|
||||||
// 页面加载时获取比赛列表
|
|
||||||
onMounted(() => {
|
|
||||||
fetchTournaments();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取比赛列表
|
|
||||||
const fetchTournaments = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
// 这里应该调用API获取比赛列表
|
|
||||||
// 模拟数据
|
|
||||||
tournaments.value = [
|
|
||||||
{ id: 1, name: '2023年夏季赛', status: 'completed', startDate: '2023-06-01', endDate: '2023-08-30' },
|
|
||||||
{ id: 2, name: '2023年秋季赛', status: 'ongoing', startDate: '2023-09-01', endDate: '2023-11-30' },
|
|
||||||
{ id: 3, name: '2024年春季赛', status: 'upcoming', startDate: '2024-03-01', endDate: '2024-05-30' }
|
|
||||||
];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取比赛列表失败:', error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择比赛
|
|
||||||
const selectTournament = (tournamentId) => {
|
|
||||||
selectedTournamentId.value = tournamentId;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 刷新参赛者列表
|
|
||||||
const refreshParticipants = () => {
|
|
||||||
// 这里可以添加刷新逻辑
|
|
||||||
console.log('刷新参赛者列表');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 过滤比赛列表
|
|
||||||
const filteredTournaments = () => {
|
|
||||||
return tournaments.value.filter(tournament => {
|
|
||||||
if (activeTab.value === 'upcoming') return tournament.status === 'upcoming';
|
|
||||||
if (activeTab.value === 'ongoing') return tournament.status === 'ongoing';
|
|
||||||
if (activeTab.value === 'completed') return tournament.status === 'completed';
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="competition-container">
|
<div class="competition-page">
|
||||||
<h1 class="page-title">比赛中心</h1>
|
<div class="page-header">
|
||||||
|
<h1>赛程信息</h1>
|
||||||
<!-- 比赛类型选项卡 -->
|
<div class="header-subtitle">
|
||||||
<div class="tabs">
|
<span class="date-range">点击即可查看和报名</span>
|
||||||
<div
|
|
||||||
class="tab"
|
|
||||||
:class="{ active: activeTab === 'upcoming' }"
|
|
||||||
@click="activeTab = 'upcoming'"
|
|
||||||
>
|
|
||||||
即将开始
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="tab"
|
|
||||||
:class="{ active: activeTab === 'ongoing' }"
|
|
||||||
@click="activeTab = 'ongoing'"
|
|
||||||
>
|
|
||||||
正在进行
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="tab"
|
|
||||||
:class="{ active: activeTab === 'completed' }"
|
|
||||||
@click="activeTab = 'completed'"
|
|
||||||
>
|
|
||||||
已结束
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 比赛列表 -->
|
<div class="action-bar">
|
||||||
<div class="tournament-list">
|
<div class="left-actions">
|
||||||
<div v-if="loading" class="loading">加载中...</div>
|
<button class="btn-common btn-gradient" @click="addNewCompetition">
|
||||||
<div v-else-if="filteredTournaments().length === 0" class="no-data">
|
<i class="fas fa-plus"></i>
|
||||||
暂无{{ activeTab === 'upcoming' ? '即将开始' : activeTab === 'ongoing' ? '正在进行' : '已结束' }}的比赛
|
添加赛程
|
||||||
</div>
|
</button>
|
||||||
<div
|
<button
|
||||||
v-else
|
class="btn-common btn-light"
|
||||||
v-for="tournament in filteredTournaments()"
|
@click="refreshCompetitions"
|
||||||
:key="tournament.id"
|
:disabled="isLoading"
|
||||||
class="tournament-card"
|
|
||||||
:class="{ active: selectedTournamentId === tournament.id }"
|
|
||||||
@click="selectTournament(tournament.id)"
|
|
||||||
>
|
>
|
||||||
<div class="tournament-header">
|
<i class="fas" :class="isLoading ? 'fa-spinner fa-spin' : 'fa-sync-alt'"></i>
|
||||||
<h3>{{ tournament.name }}</h3>
|
{{ isLoading ? '刷新中...' : '刷新赛程' }}
|
||||||
<span class="status-badge" :class="tournament.status">
|
</button>
|
||||||
{{ tournament.status === 'upcoming' ? '即将开始' :
|
</div>
|
||||||
tournament.status === 'ongoing' ? '正在进行' : '已结束' }}
|
<div class="right-actions">
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="搜索赛程..."
|
||||||
|
@input="handleSearch"
|
||||||
|
>
|
||||||
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
</div>
|
||||||
|
<select v-model="filterStatus" @change="handleFilter" class="filter-select">
|
||||||
|
<option value="all">全部状态</option>
|
||||||
|
<option value="ongoing">进行中</option>
|
||||||
|
<option value="finished">已结束</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误提示 -->
|
||||||
|
<div v-if="errorMessage" class="error-message">
|
||||||
|
<i class="fas fa-exclamation-circle"></i>
|
||||||
|
{{ errorMessage }}
|
||||||
|
<button class="retry-btn" @click="refreshCompetitions">
|
||||||
|
重试
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container" :class="{ 'loading': isLoading }">
|
||||||
|
<table class="competition-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>序号</th>
|
||||||
|
<th>赛程名称</th>
|
||||||
|
<th>开始时间</th>
|
||||||
|
<th>结束时间</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>组织者</th>
|
||||||
|
<th>QQ号</th>
|
||||||
|
<th>赛制类型</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(competition, index) in filteredCompetitions"
|
||||||
|
:key="index"
|
||||||
|
class="competition-row"
|
||||||
|
@click="handleView(competition)">
|
||||||
|
<td>{{ index + 1 }}</td>
|
||||||
|
<td class="competition-name">{{ competition.name }}</td>
|
||||||
|
<td>{{ formatDate(competition.start_time) }}</td>
|
||||||
|
<td>{{ formatDate(competition.end_time) }}</td>
|
||||||
|
<td>
|
||||||
|
<span :class="['status-tag', competition.status]">
|
||||||
|
{{ competition.status === 'prepare' ? '筹备中' :
|
||||||
|
competition.status === 'starting' ? '进行中' : '已结束' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</td>
|
||||||
<div class="tournament-dates">
|
<td>{{ competition.organizer }}</td>
|
||||||
<span>开始日期: {{ tournament.startDate }}</span>
|
<td>{{ competition.qq_code }}</td>
|
||||||
<span>结束日期: {{ tournament.endDate }}</span>
|
<td>{{ competition.format === 'single' ? '单败淘汰' :
|
||||||
</div>
|
competition.format === 'double' ? '双败淘汰' : '积分赛' }}</td>
|
||||||
</div>
|
<td class="action-cell">
|
||||||
|
<button class="action-btn view" @click.stop="handleSignUp(competition)" :disabled="competition.status === 'finish'">
|
||||||
|
报名
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="isLoading" class="loading-overlay">
|
||||||
|
<i class="fas fa-spinner fa-spin"></i>
|
||||||
|
<span>加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 比赛详情 -->
|
<!-- 空状态显示 -->
|
||||||
<div v-if="selectedTournamentId" class="tournament-detail">
|
<div v-else-if="filteredCompetitions.length === 0" class="empty-state">
|
||||||
<TournamentBracket
|
<i class="fas fa-calendar-times"></i>
|
||||||
:tournamentId="selectedTournamentId"
|
<p>暂无赛程信息</p>
|
||||||
@refreshPlayers="refreshParticipants"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="select-prompt">
|
|
||||||
请从左侧选择一个比赛查看详情
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { getTournamentList } from '@/api/tournament.js'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const competitions = ref([])
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const filterStatus = ref('all')
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const filteredCompetitions = computed(() => {
|
||||||
|
let result = competitions.value
|
||||||
|
// 搜索过滤
|
||||||
|
if (searchQuery.value) {
|
||||||
|
const query = searchQuery.value.toLowerCase()
|
||||||
|
result = result.filter(comp =>
|
||||||
|
comp.name.toLowerCase().includes(query) ||
|
||||||
|
comp.organizer.toLowerCase().includes(query)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态过滤
|
||||||
|
if (filterStatus.value !== 'all') {
|
||||||
|
result = result.filter(comp =>
|
||||||
|
filterStatus.value === 'ongoing' ? comp.status === 'starting' : comp.status === 'finish'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const formatDate = (date) => {
|
||||||
|
return date.replace(/\//g, '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时重置过滤
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFilter = () => {
|
||||||
|
// 过滤时重置搜索
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleView = (competition) => {
|
||||||
|
router.push({
|
||||||
|
path: '/competition/detail',
|
||||||
|
query: {
|
||||||
|
id: competition.id,
|
||||||
|
name: competition.name,
|
||||||
|
start_time: competition.start_time,
|
||||||
|
end_time: competition.end_time,
|
||||||
|
organizer: competition.organizer,
|
||||||
|
qq_code: competition.qq_code,
|
||||||
|
format: competition.format,
|
||||||
|
status: competition.status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSignUp = (competition) => {
|
||||||
|
router.push({
|
||||||
|
name: 'CompetitionSignUp',
|
||||||
|
query: {
|
||||||
|
id: competition.id,
|
||||||
|
name: competition.name,
|
||||||
|
start_time: competition.start_time,
|
||||||
|
end_time: competition.end_time,
|
||||||
|
organizer: competition.organizer,
|
||||||
|
qq_code: competition.qq_code,
|
||||||
|
format: competition.format,
|
||||||
|
status: competition.status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNewCompetition = () => {
|
||||||
|
router.push('/competition/add')
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshCompetitions = async () => {
|
||||||
|
try {
|
||||||
|
isLoading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
const data = await getTournamentList()
|
||||||
|
competitions.value = data
|
||||||
|
console.log('刷新赛程数据成功')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取赛程数据失败:', error)
|
||||||
|
errorMessage.value = error.response?.data?.message || '获取赛程数据失败,请重试'
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
refreshCompetitions()
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.competition-container {
|
.competition-page {
|
||||||
display: flex;
|
padding: 16px;
|
||||||
flex-direction: column;
|
max-width: 1400px;
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-header {
|
||||||
font-size: 24px;
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.page-header h1 {
|
||||||
display: flex;
|
font-size: 22px;
|
||||||
margin-bottom: 20px;
|
color: #1a237e;
|
||||||
border-bottom: 1px solid #ddd;
|
margin: 0 0 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.header-subtitle {
|
||||||
padding: 10px 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #666;
|
color: #666;
|
||||||
border-bottom: 2px solid transparent;
|
font-size: 13px;
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:hover {
|
.action-bar {
|
||||||
color: #007acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active {
|
|
||||||
color: #007acc;
|
|
||||||
border-bottom: 2px solid #007acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-card {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-card:hover {
|
|
||||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-card.active {
|
|
||||||
border-color: #007acc;
|
|
||||||
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tournament-header h3 {
|
.left-actions,
|
||||||
margin: 0;
|
.right-actions {
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.upcoming {
|
|
||||||
background-color: #e6f7ff;
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.ongoing {
|
|
||||||
background-color: #f6ffed;
|
|
||||||
color: #52c41a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.completed {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-dates {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
gap: 10px;
|
||||||
font-size: 14px;
|
align-items: center;
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading, .no-data, .select-prompt {
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tournament-detail {
|
|
||||||
margin-top: 20px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.competition-container {
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title, .tabs {
|
.search-box {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
padding: 6px 10px 6px 28px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: white;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-common {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #b6d2ff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-gradient {
|
||||||
|
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-gradient:hover {
|
||||||
|
background: linear-gradient(90deg, #416bdf 0%, #71eaeb 100%);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-light {
|
||||||
|
background: white;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-light:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.competition-table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 800px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.competition-table th,
|
||||||
|
.competition-table td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.competition-table th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a237e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.competition-row {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.competition-row:hover {
|
||||||
|
background-color: #f0f7ff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.competition-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a237e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.prepare { background-color: #e6a23c; color: #fff; }
|
||||||
|
.status-tag.starting { background-color: #67c23a; color: #fff; }
|
||||||
|
.status-tag.finish { background-color: #909399; color: #fff; }
|
||||||
|
|
||||||
|
.action-cell {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background-color: #fef0f0;
|
||||||
|
color: #f56c6c;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #f56c6c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #409EFF;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container.loading {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-common:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:disabled {
|
||||||
|
background: #e0e0e0 !important;
|
||||||
|
color: #b0b0b0 !important;
|
||||||
|
cursor: not-allowed;
|
||||||
|
border: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.competition-page {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-actions, .right-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tournament-list {
|
.table-container {
|
||||||
width: 30%;
|
margin: 0 -12px;
|
||||||
margin-right: 2%;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tournament-detail, .select-prompt {
|
.competition-table th, .competition-table td {
|
||||||
width: 68%;
|
padding: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input, .filter-select {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -24,14 +24,14 @@
|
|||||||
{{ statusMap[competition.status] }}
|
{{ statusMap[competition.status] }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="edit-controls" v-if="isOrganizer">
|
<!-- <div class="edit-controls" v-if="isOrganizer">
|
||||||
<button class="edit-mode-btn" @click="toggleEditMode">
|
<button class="edit-mode-btn" @click="toggleEditMode">
|
||||||
{{ isEditMode ? '退出编辑' : '编辑对阵图' }}
|
{{ isEditMode ? '退出编辑' : '编辑对阵图' }}
|
||||||
</button>
|
</button>
|
||||||
<button v-if="isEditMode" class="save-btn" @click="saveChanges" :disabled="isSaving">
|
<button v-if="isEditMode" class="save-btn" @click="saveChanges" :disabled="isSaving">
|
||||||
{{ isSaving ? '保存中...' : '保存修改' }}
|
{{ isSaving ? '保存中...' : '保存修改' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 报名玩家列表 -->
|
<!-- 报名玩家列表 -->
|
||||||
@ -46,6 +46,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>玩家名称</th>
|
<th>玩家名称</th>
|
||||||
<th>队伍名称</th>
|
<th>队伍名称</th>
|
||||||
|
<th>阵营</th>
|
||||||
|
<th>QQ</th>
|
||||||
<th>胜场</th>
|
<th>胜场</th>
|
||||||
<th>负场</th>
|
<th>负场</th>
|
||||||
<th>状态</th>
|
<th>状态</th>
|
||||||
@ -55,12 +57,14 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="player in registeredPlayers" :key="player.id">
|
<tr v-for="player in registeredPlayers" :key="player.id">
|
||||||
<td>{{ player.sign_name }}</td>
|
<td>{{ player.sign_name }}</td>
|
||||||
<td>{{ player.team_name || '个人' }}</td>
|
<td>{{ player.team_name === ' ' ? '个人' : player.team_name}}</td>
|
||||||
|
<td>{{ formatFaction(player.faction) }}</td>
|
||||||
|
<td>{{ player.qq || '-' }}</td>
|
||||||
<td>{{ player.win }}</td>
|
<td>{{ player.win }}</td>
|
||||||
<td>{{ player.lose }}</td>
|
<td>{{ player.lose }}</td>
|
||||||
<td>{{ player.status }}</td>
|
<td>{{ player.status }}</td>
|
||||||
<td class="action-buttons">
|
<td class="action-buttons">
|
||||||
<button class="edit-player-btn" @click="handleEditPlayer(player)" >修改</button>
|
<!-- <button class="edit-player-btn" @click="handleEditPlayer(player)" >修改</button>-->
|
||||||
<button class="remove-btn" @click="handleRemovePlayer(player.id)">移除</button>
|
<button class="remove-btn" @click="handleRemovePlayer(player.id)">移除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -238,11 +242,13 @@ import {
|
|||||||
getTournamentList,
|
getTournamentList,
|
||||||
updateTournament,
|
updateTournament,
|
||||||
deleteTournament,
|
deleteTournament,
|
||||||
|
getSignUpList,
|
||||||
getSignUpResultList,
|
getSignUpResultList,
|
||||||
updateSignUpResult,
|
updateSignUpResult,
|
||||||
deleteSignUpResult
|
deleteSignUpResult,
|
||||||
} from '@/api/tournament'
|
deleteSignUp
|
||||||
import { getStoredUser } from '@/utils/jwt'
|
} from '@/api/tournament.js'
|
||||||
|
import { getStoredUser } from '@/utils/jwt.js'
|
||||||
import SuccessDialog from '@/components/SuccessDialog.vue'
|
import SuccessDialog from '@/components/SuccessDialog.vue'
|
||||||
import ErrorDialog from '@/components/ErrorDialog.vue'
|
import ErrorDialog from '@/components/ErrorDialog.vue'
|
||||||
|
|
||||||
@ -324,10 +330,18 @@ const formatType = (type) => {
|
|||||||
return formatMap[type] || type
|
return formatMap[type] || type
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 格式化阵营
|
// 格式化阵营
|
||||||
// const formatFaction = (faction) => {
|
const formatFaction = (faction) => {
|
||||||
// return factionMap[faction] || faction
|
const factionMap = {
|
||||||
// }
|
'allied': '盟军',
|
||||||
|
'soviet': '苏联',
|
||||||
|
'empire': '帝国',
|
||||||
|
'ob': 'OB',
|
||||||
|
'voice': '解说',
|
||||||
|
'random': '随机'
|
||||||
|
}
|
||||||
|
return factionMap[faction] || faction
|
||||||
|
}
|
||||||
|
|
||||||
// 返回列表
|
// 返回列表
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
@ -482,15 +496,40 @@ const fetchTournamentDetail = async () => {
|
|||||||
const fetchRegisteredPlayers = async () => {
|
const fetchRegisteredPlayers = async () => {
|
||||||
try {
|
try {
|
||||||
const tournamentId = parseInt(route.query.id)
|
const tournamentId = parseInt(route.query.id)
|
||||||
const response = await getSignUpResultList()
|
|
||||||
// 调试日志
|
// 同时获取报名信息和结果信息
|
||||||
console.log('报名玩家原始数据:', response)
|
const [signupList, resultList] = await Promise.all([
|
||||||
|
getSignUpList(),
|
||||||
|
getSignUpResultList()
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('报名信息原始数据:', signupList)
|
||||||
|
console.log('结果信息原始数据:', resultList)
|
||||||
console.log('当前赛事ID:', tournamentId)
|
console.log('当前赛事ID:', tournamentId)
|
||||||
// 只保留 tournament_id 等于当前赛事 id 的玩家
|
|
||||||
registeredPlayers.value = response.filter(player =>
|
// 筛选当前赛事的报名信息
|
||||||
player.tournament_id === tournamentId
|
const tournamentSignups = signupList.filter(signup =>
|
||||||
|
signup.tournament_id === tournamentId
|
||||||
)
|
)
|
||||||
console.log('筛选后的玩家数据:', registeredPlayers.value)
|
|
||||||
|
// 筛选当前赛事的结果信息
|
||||||
|
const tournamentResults = resultList.filter(result =>
|
||||||
|
result.tournament_id === tournamentId
|
||||||
|
)
|
||||||
|
|
||||||
|
// 合并报名信息和结果信息,添加阵营信息
|
||||||
|
registeredPlayers.value = tournamentResults.map(result => {
|
||||||
|
// 查找对应的报名信息
|
||||||
|
const signup = tournamentSignups.find(s => s.username === result.sign_name)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
faction: signup?.faction || 'random', // 添加阵营信息
|
||||||
|
qq: signup?.qq || '' // 添加QQ信息
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('合并后的玩家数据:', registeredPlayers.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取报名玩家列表失败:', error)
|
console.error('获取报名玩家列表失败:', error)
|
||||||
}
|
}
|
||||||
@ -501,7 +540,33 @@ const handleRemovePlayer = async (playerId) => {
|
|||||||
if (!confirm('确定要移除该玩家吗?')) return
|
if (!confirm('确定要移除该玩家吗?')) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteSignUpResult(playerId)
|
// 获取玩家信息,用于查找对应的报名记录
|
||||||
|
const player = registeredPlayers.value.find(p => p.id === playerId)
|
||||||
|
if (!player) {
|
||||||
|
throw new Error('未找到玩家信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取报名列表,查找对应的报名记录
|
||||||
|
const signupList = await getSignUpList()
|
||||||
|
const signupRecord = signupList.find(s =>
|
||||||
|
s.tournament_id === parseInt(route.query.id) &&
|
||||||
|
s.username === player.sign_name
|
||||||
|
)
|
||||||
|
|
||||||
|
// 同时删除报名信息和报名结果
|
||||||
|
const deletePromises = []
|
||||||
|
|
||||||
|
// 删除报名结果
|
||||||
|
deletePromises.push(deleteSignUpResult(playerId))
|
||||||
|
|
||||||
|
// 如果找到对应的报名记录,也删除报名信息
|
||||||
|
if (signupRecord) {
|
||||||
|
deletePromises.push(deleteSignUp(signupRecord.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有删除操作完成
|
||||||
|
await Promise.all(deletePromises)
|
||||||
|
|
||||||
await fetchRegisteredPlayers() // 刷新列表
|
await fetchRegisteredPlayers() // 刷新列表
|
||||||
successDialog.value = { visible: true, message: '移除成功!' }
|
successDialog.value = { visible: true, message: '移除成功!' }
|
||||||
} catch (error) {
|
} catch (error) {
|
@ -142,7 +142,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, onMounted, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { addSignUp } from '@/api/tournament'
|
import {addSignUp, addSignUpResult} from '@/api/tournament.js'
|
||||||
import SuccessDialog from '@/components/SuccessDialog.vue'
|
import SuccessDialog from '@/components/SuccessDialog.vue'
|
||||||
import ErrorDialog from '@/components/ErrorDialog.vue'
|
import ErrorDialog from '@/components/ErrorDialog.vue'
|
||||||
|
|
||||||
@ -256,26 +256,32 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
id: parseInt(competitionInfo.value.id),
|
tournament_id: parseInt(competitionInfo.value.id),
|
||||||
tournament_name: competitionInfo.value.name,
|
|
||||||
type: signupForm.value.type,
|
type: signupForm.value.type,
|
||||||
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : '',
|
teamname: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
|
||||||
sign_name: signName,
|
username: signupForm.value.username,
|
||||||
faction: signupForm.value.faction,
|
faction: signupForm.value.faction,
|
||||||
qq_code: String(competitionInfo.value.qq_code) // 确保 qq_code 是字符串类型
|
qq: signupForm.value.qq
|
||||||
|
}
|
||||||
|
const signupResultData = {
|
||||||
|
tournament_id: parseInt(competitionInfo.value.id),
|
||||||
|
tournament_name: competitionInfo.value.name,
|
||||||
|
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
|
||||||
|
sign_name: signupForm.value.username,
|
||||||
|
win: '0',
|
||||||
|
lose: '0',
|
||||||
|
status: 'tie',
|
||||||
|
round: '0',
|
||||||
|
rival_name: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('提交的报名数据:', submitData)
|
|
||||||
const result = await addSignUp(submitData)
|
const result = await addSignUp(submitData)
|
||||||
console.log('报名结果:', result)
|
const resultSignup = await addSignUpResult(signupResultData)
|
||||||
|
console.log('提交的报名数据:', submitData, signupResultData)
|
||||||
if (result.signup && result.result) {
|
console.log('报名结果:', result, resultSignup)
|
||||||
successDialog.value = { visible: true, message: '报名成功!' }
|
successDialog.value = { visible: true, message: '报名成功!' }
|
||||||
|
setTimeout(() => {
|
||||||
router.push('/competition')
|
router.push('/competition')
|
||||||
} else {
|
}, 1500)
|
||||||
console.error('报名结果不完整:', result)
|
|
||||||
throw new Error('报名数据不完整,请重试')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('报名失败:', error)
|
console.error('报名失败:', error)
|
||||||
console.error('错误详情:', {
|
console.error('错误详情:', {
|
@ -1,488 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="competition-page">
|
|
||||||
<div class="page-header">
|
|
||||||
<h1>赛程信息</h1>
|
|
||||||
<div class="header-subtitle">
|
|
||||||
<span class="date-range">点击即可查看和报名</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-bar">
|
|
||||||
<div class="left-actions">
|
|
||||||
<button class="btn-common btn-gradient" @click="addNewCompetition">
|
|
||||||
<i class="fas fa-plus"></i>
|
|
||||||
添加赛程
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn-common btn-light"
|
|
||||||
@click="refreshCompetitions"
|
|
||||||
:disabled="isLoading"
|
|
||||||
>
|
|
||||||
<i class="fas" :class="isLoading ? 'fa-spinner fa-spin' : 'fa-sync-alt'"></i>
|
|
||||||
{{ isLoading ? '刷新中...' : '刷新赛程' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-actions">
|
|
||||||
<div class="search-box">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
v-model="searchQuery"
|
|
||||||
placeholder="搜索赛程..."
|
|
||||||
@input="handleSearch"
|
|
||||||
>
|
|
||||||
<i class="fas fa-search search-icon"></i>
|
|
||||||
</div>
|
|
||||||
<select v-model="filterStatus" @change="handleFilter" class="filter-select">
|
|
||||||
<option value="all">全部状态</option>
|
|
||||||
<option value="ongoing">进行中</option>
|
|
||||||
<option value="finished">已结束</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 错误提示 -->
|
|
||||||
<div v-if="errorMessage" class="error-message">
|
|
||||||
<i class="fas fa-exclamation-circle"></i>
|
|
||||||
{{ errorMessage }}
|
|
||||||
<button class="retry-btn" @click="refreshCompetitions">
|
|
||||||
重试
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container" :class="{ 'loading': isLoading }">
|
|
||||||
<table class="competition-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>序号</th>
|
|
||||||
<th>赛程名称</th>
|
|
||||||
<th>开始时间</th>
|
|
||||||
<th>结束时间</th>
|
|
||||||
<th>状态</th>
|
|
||||||
<th>组织者</th>
|
|
||||||
<th>QQ号</th>
|
|
||||||
<th>赛制类型</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(competition, index) in filteredCompetitions"
|
|
||||||
:key="index"
|
|
||||||
class="competition-row"
|
|
||||||
@click="handleView(competition)">
|
|
||||||
<td>{{ index + 1 }}</td>
|
|
||||||
<td class="competition-name">{{ competition.name }}</td>
|
|
||||||
<td>{{ formatDate(competition.start_time) }}</td>
|
|
||||||
<td>{{ formatDate(competition.end_time) }}</td>
|
|
||||||
<td>
|
|
||||||
<span :class="['status-tag', competition.status]">
|
|
||||||
{{ competition.status === 'prepare' ? '筹备中' :
|
|
||||||
competition.status === 'starting' ? '进行中' : '已结束' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ competition.organizer }}</td>
|
|
||||||
<td>{{ competition.qq_code }}</td>
|
|
||||||
<td>{{ competition.format === 'single' ? '单败淘汰' :
|
|
||||||
competition.format === 'double' ? '双败淘汰' : '积分赛' }}</td>
|
|
||||||
<td class="action-cell">
|
|
||||||
<button class="action-btn view" @click.stop="handleSignUp(competition)" :disabled="competition.status === 'finish'">
|
|
||||||
报名
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-if="isLoading" class="loading-overlay">
|
|
||||||
<i class="fas fa-spinner fa-spin"></i>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 空状态显示 -->
|
|
||||||
<div v-else-if="filteredCompetitions.length === 0" class="empty-state">
|
|
||||||
<i class="fas fa-calendar-times"></i>
|
|
||||||
<p>暂无赛程信息</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { getTournamentList } from '@/api/tournament'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
// 状态管理
|
|
||||||
const competitions = ref([])
|
|
||||||
const searchQuery = ref('')
|
|
||||||
const filterStatus = ref('all')
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const errorMessage = ref('')
|
|
||||||
|
|
||||||
// 计算属性
|
|
||||||
const filteredCompetitions = computed(() => {
|
|
||||||
let result = competitions.value
|
|
||||||
// 搜索过滤
|
|
||||||
if (searchQuery.value) {
|
|
||||||
const query = searchQuery.value.toLowerCase()
|
|
||||||
result = result.filter(comp =>
|
|
||||||
comp.name.toLowerCase().includes(query) ||
|
|
||||||
comp.organizer.toLowerCase().includes(query)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态过滤
|
|
||||||
if (filterStatus.value !== 'all') {
|
|
||||||
result = result.filter(comp =>
|
|
||||||
filterStatus.value === 'ongoing' ? comp.status === 'starting' : comp.status === 'finish'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
const formatDate = (date) => {
|
|
||||||
return date.replace(/\//g, '-')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSearch = () => {
|
|
||||||
// 搜索时重置过滤
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFilter = () => {
|
|
||||||
// 过滤时重置搜索
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleView = (competition) => {
|
|
||||||
router.push({
|
|
||||||
path: '/competition/detail',
|
|
||||||
query: {
|
|
||||||
id: competition.id,
|
|
||||||
name: competition.name,
|
|
||||||
start_time: competition.start_time,
|
|
||||||
end_time: competition.end_time,
|
|
||||||
organizer: competition.organizer,
|
|
||||||
qq_code: competition.qq_code,
|
|
||||||
format: competition.format,
|
|
||||||
status: competition.status
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSignUp = (competition) => {
|
|
||||||
router.push({
|
|
||||||
name: 'CompetitionSignUp',
|
|
||||||
query: {
|
|
||||||
id: competition.id,
|
|
||||||
name: competition.name,
|
|
||||||
start_time: competition.start_time,
|
|
||||||
end_time: competition.end_time,
|
|
||||||
organizer: competition.organizer,
|
|
||||||
qq_code: competition.qq_code,
|
|
||||||
format: competition.format,
|
|
||||||
status: competition.status
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const addNewCompetition = () => {
|
|
||||||
router.push('/competition/add')
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshCompetitions = async () => {
|
|
||||||
try {
|
|
||||||
isLoading.value = true
|
|
||||||
errorMessage.value = ''
|
|
||||||
const data = await getTournamentList()
|
|
||||||
competitions.value = data
|
|
||||||
console.log('刷新赛程数据成功')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取赛程数据失败:', error)
|
|
||||||
errorMessage.value = error.response?.data?.message || '获取赛程数据失败,请重试'
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
refreshCompetitions()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.competition-page {
|
|
||||||
padding: 16px;
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
color: #1a237e;
|
|
||||||
margin: 0 0 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle {
|
|
||||||
color: #666;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-actions,
|
|
||||||
.right-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
position: relative;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box input {
|
|
||||||
padding: 6px 10px 6px 28px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon {
|
|
||||||
position: absolute;
|
|
||||||
left: 8px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-select {
|
|
||||||
padding: 6px 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
background: white;
|
|
||||||
min-width: 100px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-common {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #b6d2ff;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-gradient {
|
|
||||||
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-gradient:hover {
|
|
||||||
background: linear-gradient(90deg, #416bdf 0%, #71eaeb 100%);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-light {
|
|
||||||
background: white;
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-light:hover {
|
|
||||||
background: #f5f7fa;
|
|
||||||
border-color: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
overflow-x: auto;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
position: relative;
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-table {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 800px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-table th,
|
|
||||||
.competition-table td {
|
|
||||||
padding: 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-table th {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1a237e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-row {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-row:hover {
|
|
||||||
background-color: #f0f7ff;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-name {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1a237e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-tag {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-tag.prepare { background-color: #e6a23c; color: #fff; }
|
|
||||||
.status-tag.starting { background-color: #67c23a; color: #fff; }
|
|
||||||
.status-tag.finish { background-color: #909399; color: #fff; }
|
|
||||||
|
|
||||||
.action-cell {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
|
|
||||||
color: white;
|
|
||||||
font-size: 13px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
padding: 30px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
background-color: #fef0f0;
|
|
||||||
color: #f56c6c;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn {
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 4px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
background: #f56c6c;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
color: #409EFF;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container.loading {
|
|
||||||
opacity: 0.6;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-common:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn:disabled {
|
|
||||||
background: #e0e0e0 !important;
|
|
||||||
color: #b0b0b0 !important;
|
|
||||||
cursor: not-allowed;
|
|
||||||
border: none;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.competition-page {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-bar {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-actions, .right-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
margin: 0 -12px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competition-table th, .competition-table td {
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box input, .filter-select {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-tag {
|
|
||||||
font-size: 10px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
x
Reference in New Issue
Block a user