新增競賽前台呈現、刪除競賽、修改競賽狀態

This commit is contained in:
2025-09-16 14:57:40 +08:00
parent 1f2fb14bd0
commit b4386dc481
21 changed files with 1714 additions and 127 deletions

View File

@@ -616,7 +616,7 @@ export class UserService {
const competitionStatsSql = `
SELECT
COUNT(*) as total_competitions,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_competitions
COUNT(CASE WHEN status = 'active' OR status = 'ongoing' THEN 1 END) as active_competitions
FROM competitions
`;
const competitionStats = await this.queryOne(competitionStatsSql);
@@ -1282,13 +1282,135 @@ export class CompetitionService {
// 更新競賽
static async updateCompetition(id: string, updates: Partial<Competition>): Promise<boolean> {
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
const setClause = fields.map(field => `${field} = ?`).join(', ');
const values = fields.map(field => (updates as any)[field]);
const sql = `UPDATE competitions SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
const result = await db.update(sql, [...values, id]);
return result.affectedRows > 0;
try {
const dbSyncFixed = new DatabaseSyncFixed();
// 使用雙寫功能更新競賽
const result = await dbSyncFixed.smartDualUpdate('competitions', id, updates);
if (!result.success) {
console.error('競賽更新失敗:', result.masterError || result.slaveError);
return false;
}
return true;
} catch (error) {
console.error('競賽更新失敗:', error);
return false;
}
}
// 獲取當前競賽
static async getCurrentCompetition(): Promise<any | null> {
try {
const sql = 'SELECT * FROM competitions WHERE is_current = TRUE AND is_active = TRUE LIMIT 1';
const competitions = await db.query<any>(sql);
if (competitions.length > 0) {
const competition = competitions[0];
return await this.getCompetitionWithDetails(competition.id);
}
return null;
} catch (error) {
console.error('獲取當前競賽失敗:', error);
return null;
}
}
// 設置當前競賽
static async setCurrentCompetition(competitionId: string): Promise<boolean> {
try {
const dbSyncFixed = new DatabaseSyncFixed();
// 先清除所有競賽的當前狀態 - 使用直接 SQL 更新
try {
await dbSyncFixed.clearAllCurrentCompetitions();
} catch (error) {
console.error('清除當前競賽狀態失敗:', error);
}
// 設置指定競賽為當前競賽
const setResult = await dbSyncFixed.smartDualUpdate('competitions', competitionId, { is_current: true });
if (!setResult.success) {
console.error('設置當前競賽失敗:', setResult.masterError || setResult.slaveError);
return false;
}
return true;
} catch (error) {
console.error('設置當前競賽失敗:', error);
return false;
}
}
// 清除當前競賽
static async clearCurrentCompetition(): Promise<boolean> {
try {
await db.update('UPDATE competitions SET is_current = FALSE');
return true;
} catch (error) {
console.error('清除當前競賽失敗:', error);
return false;
}
}
// 刪除競賽 - 支援雙寫
static async deleteCompetition(id: string): Promise<boolean> {
try {
const dbSyncFixed = new DatabaseSyncFixed();
// 先獲取競賽信息
const competition = await this.getCompetitionById(id);
if (!competition) {
console.error('競賽不存在');
return false;
}
// 刪除關聯數據
await this.deleteCompetitionRelations(id);
// 使用雙寫功能刪除競賽
const result = await dbSyncFixed.smartDualDelete('competitions', id);
if (!result.success) {
console.error('競賽刪除失敗:', result.masterError || result.slaveError);
return false;
}
return true;
} catch (error) {
console.error('競賽刪除失敗:', error);
return false;
}
}
// 刪除競賽關聯數據
private static async deleteCompetitionRelations(competitionId: string): Promise<void> {
try {
const dbSyncFixed = new DatabaseSyncFixed();
// 刪除所有關聯表數據
const relations = [
'competition_judges',
'competition_teams',
'competition_apps',
'competition_award_types',
'competition_rules'
];
for (const relationTable of relations) {
try {
await dbSyncFixed.smartDualDelete(relationTable, competitionId, 'competition_id');
} catch (error) {
console.error(`刪除關聯表 ${relationTable} 失敗:`, error);
}
}
} catch (error) {
console.error('刪除競賽關聯數據失敗:', error);
}
}
// =====================================================
@@ -1369,16 +1491,50 @@ export class CompetitionService {
}
// 獲取競賽的應用列表
static async getCompetitionApps(competitionId: string): Promise<any[]> {
const sql = `
SELECT a.*, ca.submitted_at, u.name as creator_name, u.department as creator_department
FROM competition_apps ca
JOIN apps a ON ca.app_id = a.id
LEFT JOIN users u ON a.creator_id = u.id
WHERE ca.competition_id = ? AND a.is_active = TRUE
ORDER BY ca.submitted_at ASC
`;
return await db.query(sql, [competitionId]);
static async getCompetitionApps(competitionId: string, competitionType?: string): Promise<any[]> {
// 先獲取競賽信息
const competition = await this.getCompetitionById(competitionId);
if (!competition) return [];
let apps: any[] = [];
if (competition.type === 'team') {
// 對於團隊競賽,獲取所有參賽團隊的應用
const teams = await this.getCompetitionTeams(competitionId);
if (teams.length > 0) {
// 過濾掉 undefined 或 null 的 team_id 值
const teamIds = teams
.map(t => t.team_id)
.filter(id => id !== undefined && id !== null);
if (teamIds.length > 0) {
const placeholders = teamIds.map(() => '?').join(',');
const sql = `
SELECT a.*, u.name as creator_name, u.department as creator_department, t.name as team_name
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
LEFT JOIN teams t ON a.team_id = t.id
WHERE a.team_id IN (${placeholders}) AND a.is_active = TRUE
ORDER BY a.created_at ASC
`;
apps = await db.query(sql, teamIds);
}
}
} else {
// 對於個人競賽,從 competition_apps 表獲取
const sql = `
SELECT a.*, ca.submitted_at, u.name as creator_name, u.department as creator_department
FROM competition_apps ca
JOIN apps a ON ca.app_id = a.id
LEFT JOIN users u ON a.creator_id = u.id
WHERE ca.competition_id = ? AND a.is_active = TRUE
ORDER BY ca.submitted_at ASC
`;
apps = await db.query(sql, [competitionId]);
}
return apps;
}
// 為競賽添加團隊
@@ -1621,9 +1777,42 @@ export class CompetitionService {
this.getCompetitionRules(competitionId)
]);
// 根據日期動態計算競賽狀態
const now = new Date();
const startDate = new Date(competition.start_date);
const endDate = new Date(competition.end_date);
let calculatedStatus = competition.status;
// 確保日期比較的準確性,使用 UTC 時間避免時區問題
const nowUTC = new Date(now.getTime() + now.getTimezoneOffset() * 60000);
const startDateUTC = new Date(startDate.getTime() + startDate.getTimezoneOffset() * 60000);
const endDateUTC = new Date(endDate.getTime() + endDate.getTimezoneOffset() * 60000);
console.log('🔍 競賽狀態計算:', {
competitionId,
name: competition.name,
now: nowUTC.toISOString(),
startDate: startDateUTC.toISOString(),
endDate: endDateUTC.toISOString(),
originalStatus: competition.status
});
// 根據實際日期計算狀態
if (nowUTC < startDateUTC) {
calculatedStatus = 'upcoming'; // 即將開始
} else if (nowUTC >= startDateUTC && nowUTC <= endDateUTC) {
calculatedStatus = 'active'; // 進行中
} else if (nowUTC > endDateUTC) {
calculatedStatus = 'completed'; // 已完成
}
console.log('🔍 計算後的狀態:', calculatedStatus);
// 轉換字段名稱以匹配前端期望的格式
return {
...competition,
status: calculatedStatus, // 使用計算後的狀態
startDate: competition.start_date,
endDate: competition.end_date,
evaluationFocus: competition.evaluation_focus,
@@ -2625,7 +2814,7 @@ export class AppService {
const competitionStatsSql = `
SELECT
COUNT(*) as total_competitions,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_competitions
COUNT(CASE WHEN status = 'active' OR status = 'ongoing' THEN 1 END) as active_competitions
FROM competitions
`;
const competitionStats = await this.queryOne(competitionStatsSql);