重构赛事信息(修改树状图前)
This commit is contained in:
parent
0274eb5407
commit
605d60ec7e
@ -3,239 +3,225 @@ import axiosInstance from './axiosConfig';
|
||||
/**
|
||||
* 添加赛事
|
||||
* @param {Object} tournamentData - 赛事数据
|
||||
* @param {string} tournamentData.name - 赛事名称
|
||||
* @param {string} tournamentData.format - 赛事类型(single, double, count)
|
||||
* @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} 返回添加赛事的响应数据
|
||||
* @returns {Promise<Object>} 返回添加赛事的响应数据
|
||||
*/
|
||||
export const addTournament = async (tournamentData) => {
|
||||
try {
|
||||
const response = await axiosInstance.post('/tournament/add', tournamentData)
|
||||
return response.data
|
||||
const response = await axiosInstance.post('/tournament/add', tournamentData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('添加赛事失败:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
message: error.message
|
||||
})
|
||||
throw error
|
||||
console.error('添加赛事失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取赛事列表
|
||||
* @returns {Promise} 返回赛事列表数据
|
||||
* @returns {Promise<Array<Object>>} 返回赛事列表数据
|
||||
*/
|
||||
export const getTournamentList = async () => {
|
||||
try {
|
||||
const response = await axiosInstance.get('/tournament/getlist')
|
||||
return response.data
|
||||
const response = await axiosInstance.get('/tournament/getlist');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取赛事列表失败:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
message: error.message
|
||||
})
|
||||
throw error
|
||||
console.error('获取赛事列表失败:', error);
|
||||
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 {
|
||||
console.log('更新赛事,发送数据:', data)
|
||||
const response = await axiosInstance.put(`/tournament/update/${id}`, {
|
||||
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
|
||||
const response = await axiosInstance.put(`/tournament/update/${id}`, tournamentData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('更新赛事失败:', error)
|
||||
if (error.response) {
|
||||
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
|
||||
console.error('更新赛事失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 删除赛事
|
||||
/**
|
||||
* 删除赛事
|
||||
* @param {number} id - 赛事ID
|
||||
* @returns {Promise<Object>} 返回删除赛事的响应数据
|
||||
*/
|
||||
export const deleteTournament = async (id) => {
|
||||
try {
|
||||
const response = await axiosInstance.delete(`/tournament/delete/${id}`)
|
||||
return response.data
|
||||
const response = await axiosInstance.delete(`/tournament/delete/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('删除赛事失败:', error)
|
||||
throw error
|
||||
console.error('删除赛事失败:', 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 {
|
||||
const response = await axiosInstance.post('/tournament/signup_result/add', {
|
||||
tournament_id: parseInt(data.tournament_id),
|
||||
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
|
||||
const response = await axiosInstance.post('/tournament/signup/add', signupData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('请求错误:', error)
|
||||
if (error.response?.data?.detail) {
|
||||
throw new Error(error.response.data.detail)
|
||||
}
|
||||
throw error
|
||||
console.error('添加报名失败:', error);
|
||||
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 () => {
|
||||
try {
|
||||
const response = await axiosInstance.get('/tournament/signup_result/getlist')
|
||||
return response.data
|
||||
const response = await axiosInstance.get('/tournament/signup_result/getlist');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取参赛结果列表失败:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
message: error.message
|
||||
})
|
||||
throw error
|
||||
console.error('获取报名结果列表失败:', error);
|
||||
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 {
|
||||
// // 更新报名信息 (这部分逻辑根据您的要求被注释掉)
|
||||
// console.log('更新报名信息...')
|
||||
// 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 }
|
||||
const response = await axiosInstance.put(`/tournament/signup_result/update/${id}`, resultData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('更新参赛结果失败:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
message: error.message
|
||||
})
|
||||
throw error
|
||||
console.error('更新报名结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 删除参赛选手
|
||||
/**
|
||||
* 删除报名结果
|
||||
* @param {number} id - 结果ID
|
||||
* @returns {Promise<Object>} 返回删除报名结果的响应数据
|
||||
*/
|
||||
export const deleteSignUpResult = async (id) => {
|
||||
try {
|
||||
// 删除报名结果
|
||||
console.log('删除报名结果...')
|
||||
await axiosInstance.delete(`/tournament/signup_result/delete/${id}`)
|
||||
console.log('报名结果删除成功')
|
||||
|
||||
// // 删除报名信息 (这部分逻辑根据您的要求被注释掉)
|
||||
// console.log('删除报名信息...')
|
||||
// await axiosInstance.delete(`/tournament/signup/delete/${id}`)
|
||||
// console.log('报名信息删除成功')
|
||||
|
||||
return { success: true }
|
||||
const response = await axiosInstance.delete(`/tournament/signup_result/delete/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('删除参赛选手失败:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
message: error.message
|
||||
})
|
||||
throw error
|
||||
console.error('删除报名结果失败:', error);
|
||||
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',
|
||||
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}
|
||||
},
|
||||
{
|
||||
path: 'competition/add',
|
||||
name: 'AddCompetition',
|
||||
component: () => import('@/views/index/AddContestant.vue'),
|
||||
component: () => import('@/views/competition/AddContestant.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: 'competition/detail',
|
||||
name: 'CompetitionDetail',
|
||||
component: () => import('@/views/index/CompetitionDetail.vue'),
|
||||
component: () => import('@/views/competition/CompetitionDetail.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: 'competition/signup',
|
||||
name: 'CompetitionSignUp',
|
||||
component: () => import('@/views/index/CompetitionSignUp.vue'),
|
||||
component: () => import('@/views/competition/CompetitionSignUp.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
// {
|
||||
|
@ -3,62 +3,62 @@
|
||||
<div class="page-header">
|
||||
<h1>添加新赛事</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn-excel" @click="handleExcelImport">
|
||||
<i class="fas fa-file-excel"></i>
|
||||
通过表格添加
|
||||
</button>
|
||||
<!-- <button class="btn-excel" @click="handleExcelImport">-->
|
||||
<!-- <i class="fas fa-file-excel"></i>-->
|
||||
<!-- 通过表格添加-->
|
||||
<!-- </button>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Excel导入弹窗 -->
|
||||
<div v-if="showExcelDialog" class="excel-dialog-overlay">
|
||||
<div class="excel-dialog">
|
||||
<h3>通过Excel导入赛事信息</h3>
|
||||
<div class="excel-upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleFileDrop"
|
||||
:class="{ 'is-dragover': isDragover }">
|
||||
<input type="file" ref="fileInput" accept=".xlsx" @change="handleFileSelect" style="display: none">
|
||||
<div class="upload-content">
|
||||
<i class="fas fa-file-excel"></i>
|
||||
<p>点击或拖拽Excel文件到此处</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="eventPreviewData.length > 0" class="preview-table">
|
||||
<h4>赛事信息表预览</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="h in eventPreviewHeaders" :key="h">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in eventPreviewData" :key="index">
|
||||
<td v-for="h in eventPreviewHeaders" :key="h">{{ item[h] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="playerPreviewData.length > 0" class="preview-table">
|
||||
<h4>选手报名表预览</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="h in playerPreviewHeaders" :key="h">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in playerPreviewData" :key="index">
|
||||
<td v-for="h in playerPreviewHeaders" :key="h">{{ item[h] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button class="confirm-btn" @click="confirmImport" :disabled="eventPreviewData.length === 0">确认导入</button>
|
||||
<button class="cancel-btn" @click="closeExcelDialog">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <!– Excel导入弹窗 –>-->
|
||||
<!-- <div v-if="showExcelDialog" class="excel-dialog-overlay">-->
|
||||
<!-- <div class="excel-dialog">-->
|
||||
<!-- <h3>通过Excel导入赛事信息</h3>-->
|
||||
<!-- <div class="excel-upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleFileDrop"-->
|
||||
<!-- :class="{ 'is-dragover': isDragover }">-->
|
||||
<!-- <input type="file" ref="fileInput" accept=".xlsx" @change="handleFileSelect" style="display: none">-->
|
||||
<!-- <div class="upload-content">-->
|
||||
<!-- <i class="fas fa-file-excel"></i>-->
|
||||
<!-- <p>点击或拖拽Excel文件到此处</p>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-if="eventPreviewData.length > 0" class="preview-table">-->
|
||||
<!-- <h4>赛事信息表预览</h4>-->
|
||||
<!-- <table>-->
|
||||
<!-- <thead>-->
|
||||
<!-- <tr>-->
|
||||
<!-- <th v-for="h in eventPreviewHeaders" :key="h">{{ h }}</th>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </thead>-->
|
||||
<!-- <tbody>-->
|
||||
<!-- <tr v-for="(item, index) in eventPreviewData" :key="index">-->
|
||||
<!-- <td v-for="h in eventPreviewHeaders" :key="h">{{ item[h] }}</td>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </tbody>-->
|
||||
<!-- </table>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-if="playerPreviewData.length > 0" class="preview-table">-->
|
||||
<!-- <h4>选手报名表预览</h4>-->
|
||||
<!-- <table>-->
|
||||
<!-- <thead>-->
|
||||
<!-- <tr>-->
|
||||
<!-- <th v-for="h in playerPreviewHeaders" :key="h">{{ h }}</th>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </thead>-->
|
||||
<!-- <tbody>-->
|
||||
<!-- <tr v-for="(item, index) in playerPreviewData" :key="index">-->
|
||||
<!-- <td v-for="h in playerPreviewHeaders" :key="h">{{ item[h] }}</td>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </tbody>-->
|
||||
<!-- </table>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="dialog-actions">-->
|
||||
<!-- <button class="confirm-btn" @click="confirmImport" :disabled="eventPreviewData.length === 0">确认导入</button>-->
|
||||
<!-- <button class="cancel-btn" @click="closeExcelDialog">取消</button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="contest-form">
|
||||
<div class="form-group">
|
||||
@ -119,7 +119,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
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 SuccessDialog from '@/components/SuccessDialog.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>
|
||||
<div class="competition-container">
|
||||
<h1 class="page-title">比赛中心</h1>
|
||||
|
||||
<!-- 比赛类型选项卡 -->
|
||||
<div class="tabs">
|
||||
<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 class="competition-page">
|
||||
<div class="page-header">
|
||||
<h1>赛程信息</h1>
|
||||
<div class="header-subtitle">
|
||||
<span class="date-range">点击即可查看和报名</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 比赛列表 -->
|
||||
<div class="tournament-list">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="filteredTournaments().length === 0" class="no-data">
|
||||
暂无{{ activeTab === 'upcoming' ? '即将开始' : activeTab === 'ongoing' ? '正在进行' : '已结束' }}的比赛
|
||||
|
||||
<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
|
||||
v-else
|
||||
v-for="tournament in filteredTournaments()"
|
||||
:key="tournament.id"
|
||||
class="tournament-card"
|
||||
:class="{ active: selectedTournamentId === tournament.id }"
|
||||
@click="selectTournament(tournament.id)"
|
||||
>
|
||||
<div class="tournament-header">
|
||||
<h3>{{ tournament.name }}</h3>
|
||||
<span class="status-badge" :class="tournament.status">
|
||||
{{ tournament.status === 'upcoming' ? '即将开始' :
|
||||
tournament.status === 'ongoing' ? '正在进行' : '已结束' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tournament-dates">
|
||||
<span>开始日期: {{ tournament.startDate }}</span>
|
||||
<span>结束日期: {{ tournament.endDate }}</span>
|
||||
<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="selectedTournamentId" class="tournament-detail">
|
||||
<TournamentBracket
|
||||
:tournamentId="selectedTournamentId"
|
||||
@refreshPlayers="refreshParticipants"
|
||||
/>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
{{ errorMessage }}
|
||||
<button class="retry-btn" @click="refreshCompetitions">
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="select-prompt">
|
||||
请从左侧选择一个比赛查看详情
|
||||
|
||||
<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.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>
|
||||
.competition-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 1200px;
|
||||
.competition-page {
|
||||
padding: 16px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
.page-header h1 {
|
||||
font-size: 22px;
|
||||
color: #1a237e;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
.header-subtitle {
|
||||
color: #666;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
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 {
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tournament-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
.left-actions,
|
||||
.right-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
.search-box {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
padding: 6px 10px 6px 28px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
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-badge.upcoming {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
.status-tag.prepare { background-color: #e6a23c; color: #fff; }
|
||||
.status-tag.starting { background-color: #67c23a; color: #fff; }
|
||||
.status-tag.finish { background-color: #909399; color: #fff; }
|
||||
|
||||
.status-badge.ongoing {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tournament-dates {
|
||||
.action-cell {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.loading, .no-data, .select-prompt {
|
||||
padding: 20px;
|
||||
.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;
|
||||
color: #666;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.tournament-detail {
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
.error-message {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
padding: 10px 14px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.competition-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
.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;
|
||||
}
|
||||
|
||||
.page-title, .tabs {
|
||||
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.left-actions, .right-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tournament-list {
|
||||
width: 30%;
|
||||
margin-right: 2%;
|
||||
|
||||
.table-container {
|
||||
margin: 0 -12px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tournament-detail, .select-prompt {
|
||||
width: 68%;
|
||||
|
||||
.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>
|
@ -24,14 +24,14 @@
|
||||
{{ statusMap[competition.status] }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="edit-controls" v-if="isOrganizer">
|
||||
<!-- <div class="edit-controls" v-if="isOrganizer">
|
||||
<button class="edit-mode-btn" @click="toggleEditMode">
|
||||
{{ isEditMode ? '退出编辑' : '编辑对阵图' }}
|
||||
</button>
|
||||
<button v-if="isEditMode" class="save-btn" @click="saveChanges" :disabled="isSaving">
|
||||
{{ isSaving ? '保存中...' : '保存修改' }}
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 报名玩家列表 -->
|
||||
@ -46,6 +46,8 @@
|
||||
<tr>
|
||||
<th>玩家名称</th>
|
||||
<th>队伍名称</th>
|
||||
<th>阵营</th>
|
||||
<th>QQ</th>
|
||||
<th>胜场</th>
|
||||
<th>负场</th>
|
||||
<th>状态</th>
|
||||
@ -55,12 +57,14 @@
|
||||
<tbody>
|
||||
<tr v-for="player in registeredPlayers" :key="player.id">
|
||||
<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.lose }}</td>
|
||||
<td>{{ player.status }}</td>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
@ -238,11 +242,13 @@ import {
|
||||
getTournamentList,
|
||||
updateTournament,
|
||||
deleteTournament,
|
||||
getSignUpList,
|
||||
getSignUpResultList,
|
||||
updateSignUpResult,
|
||||
deleteSignUpResult
|
||||
} from '@/api/tournament'
|
||||
import { getStoredUser } from '@/utils/jwt'
|
||||
deleteSignUpResult,
|
||||
deleteSignUp
|
||||
} from '@/api/tournament.js'
|
||||
import { getStoredUser } from '@/utils/jwt.js'
|
||||
import SuccessDialog from '@/components/SuccessDialog.vue'
|
||||
import ErrorDialog from '@/components/ErrorDialog.vue'
|
||||
|
||||
@ -324,10 +330,18 @@ const formatType = (type) => {
|
||||
return formatMap[type] || type
|
||||
}
|
||||
|
||||
// // 格式化阵营
|
||||
// const formatFaction = (faction) => {
|
||||
// return factionMap[faction] || faction
|
||||
// }
|
||||
// 格式化阵营
|
||||
const formatFaction = (faction) => {
|
||||
const factionMap = {
|
||||
'allied': '盟军',
|
||||
'soviet': '苏联',
|
||||
'empire': '帝国',
|
||||
'ob': 'OB',
|
||||
'voice': '解说',
|
||||
'random': '随机'
|
||||
}
|
||||
return factionMap[faction] || faction
|
||||
}
|
||||
|
||||
// 返回列表
|
||||
const handleBack = () => {
|
||||
@ -482,15 +496,40 @@ const fetchTournamentDetail = async () => {
|
||||
const fetchRegisteredPlayers = async () => {
|
||||
try {
|
||||
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)
|
||||
// 只保留 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) {
|
||||
console.error('获取报名玩家列表失败:', error)
|
||||
}
|
||||
@ -501,7 +540,33 @@ const handleRemovePlayer = async (playerId) => {
|
||||
if (!confirm('确定要移除该玩家吗?')) return
|
||||
|
||||
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() // 刷新列表
|
||||
successDialog.value = { visible: true, message: '移除成功!' }
|
||||
} catch (error) {
|
@ -142,7 +142,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
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 ErrorDialog from '@/components/ErrorDialog.vue'
|
||||
|
||||
@ -256,26 +256,32 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
const submitData = {
|
||||
id: parseInt(competitionInfo.value.id),
|
||||
tournament_name: competitionInfo.value.name,
|
||||
tournament_id: parseInt(competitionInfo.value.id),
|
||||
type: signupForm.value.type,
|
||||
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : '',
|
||||
sign_name: signName,
|
||||
teamname: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
|
||||
username: signupForm.value.username,
|
||||
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)
|
||||
console.log('报名结果:', result)
|
||||
|
||||
if (result.signup && result.result) {
|
||||
successDialog.value = { visible: true, message: '报名成功!' }
|
||||
const resultSignup = await addSignUpResult(signupResultData)
|
||||
console.log('提交的报名数据:', submitData, signupResultData)
|
||||
console.log('报名结果:', result, resultSignup)
|
||||
successDialog.value = { visible: true, message: '报名成功!' }
|
||||
setTimeout(() => {
|
||||
router.push('/competition')
|
||||
} else {
|
||||
console.error('报名结果不完整:', result)
|
||||
throw new Error('报名数据不完整,请重试')
|
||||
}
|
||||
}, 1500)
|
||||
} catch (error) {
|
||||
console.error('报名失败:', 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