修正分頁、Too many connection 問題
This commit is contained in:
@@ -4,43 +4,34 @@ import { db } from "@/lib/database"
|
|||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const appService = new AppService()
|
// 使用批次查詢減少連線使用
|
||||||
const userService = new UserService()
|
|
||||||
// 獲取總用戶數
|
|
||||||
const totalUsersResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(*) as total FROM users
|
|
||||||
`)
|
|
||||||
const totalUsers = totalUsersResult?.total || 0
|
|
||||||
|
|
||||||
// 獲取今日活躍用戶數(今日有登入記錄的用戶)
|
|
||||||
const today = new Date().toISOString().split('T')[0]
|
const today = new Date().toISOString().split('T')[0]
|
||||||
const todayActiveUsersResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(DISTINCT user_id) as count
|
|
||||||
FROM activity_logs
|
|
||||||
WHERE DATE(created_at) = ? AND action = 'login'
|
|
||||||
`, [today])
|
|
||||||
const todayActiveUsers = todayActiveUsersResult?.count || 0
|
|
||||||
|
|
||||||
// 獲取昨日活躍用戶數(用於比較)
|
|
||||||
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]
|
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]
|
||||||
const yesterdayActiveUsersResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(DISTINCT user_id) as count
|
// 批次查詢基本統計數據
|
||||||
FROM activity_logs
|
const basicStats = await db.query(`
|
||||||
WHERE DATE(created_at) = ? AND action = 'login'
|
SELECT
|
||||||
`, [yesterday])
|
(SELECT COUNT(*) FROM users) as total_users,
|
||||||
const yesterdayActiveUsers = yesterdayActiveUsersResult?.count || 0
|
(SELECT COUNT(DISTINCT user_id) FROM activity_logs WHERE DATE(created_at) = ? AND action = 'login') as today_active_users,
|
||||||
|
(SELECT COUNT(DISTINCT user_id) FROM activity_logs WHERE DATE(created_at) = ? AND action = 'login') as yesterday_active_users,
|
||||||
|
(SELECT AVG(rating) FROM apps WHERE rating > 0) as avg_rating,
|
||||||
|
(SELECT COUNT(*) FROM apps) as total_apps,
|
||||||
|
(SELECT COUNT(*) FROM apps LIMIT 5) as new_this_week
|
||||||
|
`, [today, yesterday])
|
||||||
|
|
||||||
|
const stats = basicStats[0]
|
||||||
|
const totalUsers = stats?.total_users || 0
|
||||||
|
const todayActiveUsers = stats?.today_active_users || 0
|
||||||
|
const yesterdayActiveUsers = stats?.yesterday_active_users || 0
|
||||||
|
const avgRating = stats?.avg_rating || 0
|
||||||
|
const totalApps = stats?.total_apps || 0
|
||||||
|
const newThisWeek = stats?.new_this_week || 0
|
||||||
|
|
||||||
// 計算今日活躍用戶增長率
|
// 計算今日活躍用戶增長率
|
||||||
const todayActiveGrowth = yesterdayActiveUsers > 0
|
const todayActiveGrowth = yesterdayActiveUsers > 0
|
||||||
? ((todayActiveUsers - yesterdayActiveUsers) / yesterdayActiveUsers * 100).toFixed(1)
|
? ((todayActiveUsers - yesterdayActiveUsers) / yesterdayActiveUsers * 100).toFixed(1)
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
// 獲取平均評分
|
|
||||||
const avgRatingResult = await db.queryOne(`
|
|
||||||
SELECT AVG(rating) as avg_rating FROM apps WHERE rating > 0
|
|
||||||
`)
|
|
||||||
const avgRating = avgRatingResult?.avg_rating || 0
|
|
||||||
|
|
||||||
// 獲取上週平均評分(簡化版本,使用當前評分減去0.1)
|
// 獲取上週平均評分(簡化版本,使用當前評分減去0.1)
|
||||||
const lastWeekRating = Math.max(0, avgRating - 0.1)
|
const lastWeekRating = Math.max(0, avgRating - 0.1)
|
||||||
|
|
||||||
@@ -49,21 +40,6 @@ export async function GET(request: NextRequest) {
|
|||||||
? (avgRating - lastWeekRating).toFixed(1)
|
? (avgRating - lastWeekRating).toFixed(1)
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
// 獲取應用總數
|
|
||||||
const totalAppsResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(*) as total FROM apps
|
|
||||||
`)
|
|
||||||
const totalApps = totalAppsResult?.total || 0
|
|
||||||
|
|
||||||
// 獲取本週新增應用數(簡化版本,不依賴日期)
|
|
||||||
const newThisWeekResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(*) as count
|
|
||||||
FROM apps
|
|
||||||
WHERE id LIKE '%'
|
|
||||||
LIMIT 5
|
|
||||||
`)
|
|
||||||
const newThisWeek = newThisWeekResult?.count || 0
|
|
||||||
|
|
||||||
// 計算用戶增長率(考慮平台剛上線的情況)
|
// 計算用戶增長率(考慮平台剛上線的情況)
|
||||||
let userGrowth = 0
|
let userGrowth = 0
|
||||||
let userGrowthText = "較上月"
|
let userGrowthText = "較上月"
|
||||||
@@ -78,36 +54,56 @@ export async function GET(request: NextRequest) {
|
|||||||
userGrowthText = "較上月"
|
userGrowthText = "較上月"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取近7天的使用趨勢數據(真實數據)
|
// 批次查詢近7天的使用趨勢數據
|
||||||
|
const dateRange = []
|
||||||
|
for (let i = 6; i >= 0; i--) {
|
||||||
|
const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000)
|
||||||
|
dateRange.push(date.toISOString().split('T')[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
const dailyStats = await db.query(`
|
||||||
|
SELECT
|
||||||
|
DATE(viewed_at) as date,
|
||||||
|
COUNT(DISTINCT user_id) as daily_users,
|
||||||
|
COUNT(*) as daily_sessions
|
||||||
|
FROM user_views
|
||||||
|
WHERE DATE(viewed_at) IN (${dateRange.map(() => '?').join(',')})
|
||||||
|
GROUP BY DATE(viewed_at)
|
||||||
|
`, dateRange)
|
||||||
|
|
||||||
|
const dailyActivityStats = await db.query(`
|
||||||
|
SELECT
|
||||||
|
DATE(created_at) as date,
|
||||||
|
COUNT(*) as daily_activity
|
||||||
|
FROM activity_logs
|
||||||
|
WHERE DATE(created_at) IN (${dateRange.map(() => '?').join(',')})
|
||||||
|
GROUP BY DATE(created_at)
|
||||||
|
`, dateRange)
|
||||||
|
|
||||||
|
// 建立查詢結果的映射
|
||||||
|
const dailyStatsMap = new Map()
|
||||||
|
dailyStats.forEach(stat => {
|
||||||
|
dailyStatsMap.set(stat.date, stat)
|
||||||
|
})
|
||||||
|
|
||||||
|
const dailyActivityMap = new Map()
|
||||||
|
dailyActivityStats.forEach(stat => {
|
||||||
|
dailyActivityMap.set(stat.date, stat)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 構建每日使用數據
|
||||||
const dailyUsageData = []
|
const dailyUsageData = []
|
||||||
for (let i = 6; i >= 0; i--) {
|
for (let i = 6; i >= 0; i--) {
|
||||||
const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000)
|
const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000)
|
||||||
const dateStr = date.toISOString().split('T')[0]
|
const dateStr = date.toISOString().split('T')[0]
|
||||||
const dayName = ["日", "一", "二", "三", "四", "五", "六"][date.getDay()]
|
const dayName = ["日", "一", "二", "三", "四", "五", "六"][date.getDay()]
|
||||||
|
|
||||||
// 查詢當日活躍用戶數(基於瀏覽記錄)
|
const dailyStat = dailyStatsMap.get(dateStr) || { daily_users: 0, daily_sessions: 0 }
|
||||||
const dailyUsersResult = await db.queryOne(`
|
const activityStat = dailyActivityMap.get(dateStr) || { daily_activity: 0 }
|
||||||
SELECT COUNT(DISTINCT user_id) as count
|
|
||||||
FROM user_views
|
|
||||||
WHERE DATE(viewed_at) = ?
|
|
||||||
`, [dateStr])
|
|
||||||
const dailyUsers = dailyUsersResult?.count || 0
|
|
||||||
|
|
||||||
// 查詢當日總瀏覽次數
|
const dailyUsers = dailyStat.daily_users || 0
|
||||||
const dailySessionsResult = await db.queryOne(`
|
const dailySessions = dailyStat.daily_sessions || 0
|
||||||
SELECT COUNT(*) as count
|
const dailyActivity = activityStat.daily_activity || 0
|
||||||
FROM user_views
|
|
||||||
WHERE DATE(viewed_at) = ?
|
|
||||||
`, [dateStr])
|
|
||||||
const dailySessions = dailySessionsResult?.count || 0
|
|
||||||
|
|
||||||
// 查詢當日活動記錄數
|
|
||||||
const dailyActivityResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(*) as count
|
|
||||||
FROM activity_logs
|
|
||||||
WHERE DATE(created_at) = ?
|
|
||||||
`, [dateStr])
|
|
||||||
const dailyActivity = dailyActivityResult?.count || 0
|
|
||||||
|
|
||||||
// 基於真實數據計算系統負載
|
// 基於真實數據計算系統負載
|
||||||
const cpuPeak = Math.min(90, 20 + dailyUsers * 0.8 + dailySessions * 0.05)
|
const cpuPeak = Math.min(90, 20 + dailyUsers * 0.8 + dailySessions * 0.05)
|
||||||
@@ -170,26 +166,32 @@ export async function GET(request: NextRequest) {
|
|||||||
category: app.category
|
category: app.category
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 獲取24小時使用數據
|
// 批次查詢24小時使用數據
|
||||||
|
const hourlyStats = await db.query(`
|
||||||
|
SELECT
|
||||||
|
HOUR(created_at) as hour,
|
||||||
|
COUNT(DISTINCT CASE WHEN action IN ('login', 'view') THEN user_id END) as hourly_users,
|
||||||
|
COUNT(*) as hourly_activity
|
||||||
|
FROM activity_logs
|
||||||
|
WHERE DATE(created_at) = CURDATE()
|
||||||
|
GROUP BY HOUR(created_at)
|
||||||
|
ORDER BY hour
|
||||||
|
`)
|
||||||
|
|
||||||
|
// 建立小時統計的映射
|
||||||
|
const hourlyStatsMap = new Map()
|
||||||
|
hourlyStats.forEach(stat => {
|
||||||
|
hourlyStatsMap.set(stat.hour, stat)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 構建24小時數據
|
||||||
const hourlyData = []
|
const hourlyData = []
|
||||||
for (let hour = 0; hour < 24; hour++) {
|
for (let hour = 0; hour < 24; hour++) {
|
||||||
const hourStr = hour.toString().padStart(2, '0')
|
const hourStr = hour.toString().padStart(2, '0')
|
||||||
|
|
||||||
// 查詢該小時的活躍用戶數
|
const hourlyStat = hourlyStatsMap.get(hour) || { hourly_users: 0, hourly_activity: 0 }
|
||||||
const hourlyUsersResult = await db.queryOne(`
|
const hourlyUsers = hourlyStat.hourly_users || 0
|
||||||
SELECT COUNT(DISTINCT user_id) as count
|
const hourlyActivity = hourlyStat.hourly_activity || 0
|
||||||
FROM activity_logs
|
|
||||||
WHERE HOUR(created_at) = ? AND action IN ('login', 'view')
|
|
||||||
`, [hour])
|
|
||||||
const hourlyUsers = hourlyUsersResult?.count || 0
|
|
||||||
|
|
||||||
// 查詢該小時的總活動數
|
|
||||||
const hourlyActivityResult = await db.queryOne(`
|
|
||||||
SELECT COUNT(*) as count
|
|
||||||
FROM activity_logs
|
|
||||||
WHERE HOUR(created_at) = ?
|
|
||||||
`, [hour])
|
|
||||||
const hourlyActivity = hourlyActivityResult?.count || 0
|
|
||||||
|
|
||||||
// 根據時間段和用戶數確定強度等級
|
// 根據時間段和用戶數確定強度等級
|
||||||
let intensity = "low"
|
let intensity = "low"
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { JudgeService } from '@/lib/services/database-service';
|
import { JudgeService } from '@/lib/services/database-service';
|
||||||
|
|
||||||
|
const judgeService = new JudgeService();
|
||||||
|
|
||||||
// 獲取單一評審
|
// 獲取單一評審
|
||||||
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
|
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
|
||||||
try {
|
try {
|
||||||
@@ -105,7 +107,7 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
|
|||||||
if (body.is_active !== undefined) updateData.is_active = body.is_active;
|
if (body.is_active !== undefined) updateData.is_active = body.is_active;
|
||||||
|
|
||||||
// 執行更新
|
// 執行更新
|
||||||
const success = await JudgeService.updateJudge(id, updateData);
|
const success = await judgeservice.updateJudge(id, updateData);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@@ -155,10 +157,10 @@ export async function DELETE(request: NextRequest, { params }: { params: { id: s
|
|||||||
|
|
||||||
if (hardDelete) {
|
if (hardDelete) {
|
||||||
// 硬刪除:從資料庫中完全移除
|
// 硬刪除:從資料庫中完全移除
|
||||||
success = await JudgeService.deleteJudge(id);
|
success = await judgeservice.deleteJudge(id);
|
||||||
} else {
|
} else {
|
||||||
// 軟刪除:將 is_active 設為 false
|
// 軟刪除:將 is_active 設為 false
|
||||||
success = await JudgeService.updateJudge(id, { is_active: false });
|
success = await judgeservice.updateJudge(id, { is_active: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@@ -118,7 +118,8 @@ export async function POST(request: NextRequest) {
|
|||||||
is_active: isActive
|
is_active: isActive
|
||||||
};
|
};
|
||||||
|
|
||||||
const newJudge = await JudgeService.createJudge(judgeData);
|
const judgeService = new JudgeService();
|
||||||
|
const newJudge = await judgeService.createJudge(judgeData);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
@@ -74,7 +74,7 @@ export async function GET(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const page = parseInt(searchParams.get('page') || '1')
|
const page = parseInt(searchParams.get('page') || '1')
|
||||||
const limit = parseInt(searchParams.get('limit') || '10')
|
const limit = parseInt(searchParams.get('limit') || '5')
|
||||||
const search = searchParams.get('search') || ''
|
const search = searchParams.get('search') || ''
|
||||||
const department = searchParams.get('department') || ''
|
const department = searchParams.get('department') || ''
|
||||||
const role = searchParams.get('role') || ''
|
const role = searchParams.get('role') || ''
|
||||||
|
@@ -973,16 +973,11 @@ export default function AIShowcasePlatform() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -320,16 +320,11 @@ export default function RegisterPage() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -985,16 +985,11 @@ export function AppManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -1156,16 +1151,11 @@ export function AppManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -2659,16 +2659,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -2944,16 +2939,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -5140,16 +5130,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -5236,16 +5221,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -5363,16 +5343,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -6281,16 +6256,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -6385,16 +6355,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -6809,7 +6774,7 @@ export function CompetitionManagement() {
|
|||||||
部門 <span className="text-red-500">*</span>
|
部門 <span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department) ? "custom" : newJudge.department}
|
value={newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ITBU", "MBU1", "PBU", "SBG", "SBU", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department) ? "custom" : newJudge.department}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
if (value === "custom") {
|
if (value === "custom") {
|
||||||
setNewJudge({ ...newJudge, department: "" })
|
setNewJudge({ ...newJudge, department: "" })
|
||||||
@@ -6829,16 +6794,11 @@ export function CompetitionManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -6848,7 +6808,7 @@ export function CompetitionManagement() {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{/* 自定義部門輸入框 */}
|
{/* 自定義部門輸入框 */}
|
||||||
{(newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department)) && (
|
{(newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ITBU", "MBU1", "PBU", "SBG", "SBU", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department)) && (
|
||||||
<Input
|
<Input
|
||||||
value={newJudge.department}
|
value={newJudge.department}
|
||||||
onChange={(e) => setNewJudge({ ...newJudge, department: e.target.value })}
|
onChange={(e) => setNewJudge({ ...newJudge, department: e.target.value })}
|
||||||
|
@@ -315,16 +315,11 @@ export function TeamManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -88,7 +88,7 @@ export function UserManagement() {
|
|||||||
})
|
})
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 10,
|
limit: 5,
|
||||||
total: 0,
|
total: 0,
|
||||||
totalPages: 0
|
totalPages: 0
|
||||||
})
|
})
|
||||||
@@ -813,16 +813,11 @@ export function UserManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
@@ -963,6 +958,36 @@ export function UserManagement() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
{/* 用戶列表分頁控件 */}
|
||||||
|
{pagination.totalPages > 1 && (
|
||||||
|
<div className="flex items-center justify-between pt-4 border-t">
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
顯示第 {((pagination.page - 1) * pagination.limit) + 1} - {Math.min(pagination.page * pagination.limit, pagination.total)} 項,共 {pagination.total} 項
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPagination(prev => ({ ...prev, page: prev.page - 1 }))}
|
||||||
|
disabled={pagination.page <= 1}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<span className="text-sm">
|
||||||
|
第 {pagination.page} 頁,共 {pagination.totalPages} 頁
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPagination(prev => ({ ...prev, page: prev.page + 1 }))}
|
||||||
|
disabled={pagination.page >= pagination.totalPages}
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -1207,16 +1232,11 @@ export function UserManagement() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -32,7 +32,7 @@ export function RegisterDialog({ open, onOpenChange }: RegisterDialogProps) {
|
|||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||||
const [success, setSuccess] = useState(false)
|
const [success, setSuccess] = useState(false)
|
||||||
|
|
||||||
const departments = ["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"]
|
const departments = ["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ITBU", "MBU1", "PBU", "SBG", "SBU", "法務室", "關係企業發展", "稽核室", "總經理室"]
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@@ -755,16 +755,11 @@ export function PopularityRankings() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -326,16 +326,11 @@ export function RegistrationDialog({ open, onOpenChange }: RegistrationDialogPro
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -202,16 +202,11 @@ export function FavoritesPage() {
|
|||||||
<SelectItem value="HQBU">HQBU</SelectItem>
|
<SelectItem value="HQBU">HQBU</SelectItem>
|
||||||
<SelectItem value="HRBU">HRBU</SelectItem>
|
<SelectItem value="HRBU">HRBU</SelectItem>
|
||||||
<SelectItem value="IBU">IBU</SelectItem>
|
<SelectItem value="IBU">IBU</SelectItem>
|
||||||
<SelectItem value="ICDU">ICDU</SelectItem>
|
|
||||||
<SelectItem value="ICBU">ICBU</SelectItem>
|
|
||||||
<SelectItem value="ITBU">ITBU</SelectItem>
|
<SelectItem value="ITBU">ITBU</SelectItem>
|
||||||
<SelectItem value="MBU1">MBU1</SelectItem>
|
<SelectItem value="MBU1">MBU1</SelectItem>
|
||||||
<SelectItem value="MBU5">MBU5</SelectItem>
|
|
||||||
<SelectItem value="PJA">PJA</SelectItem>
|
|
||||||
<SelectItem value="PBU">PBU</SelectItem>
|
<SelectItem value="PBU">PBU</SelectItem>
|
||||||
<SelectItem value="SBG">SBG</SelectItem>
|
<SelectItem value="SBG">SBG</SelectItem>
|
||||||
<SelectItem value="SBU">SBU</SelectItem>
|
<SelectItem value="SBU">SBU</SelectItem>
|
||||||
<SelectItem value="董事會">董事會</SelectItem>
|
|
||||||
<SelectItem value="法務室">法務室</SelectItem>
|
<SelectItem value="法務室">法務室</SelectItem>
|
||||||
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
<SelectItem value="關係企業發展">關係企業發展</SelectItem>
|
||||||
<SelectItem value="稽核室">稽核室</SelectItem>
|
<SelectItem value="稽核室">稽核室</SelectItem>
|
||||||
|
@@ -148,9 +148,9 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
console.log('ℹ️ 暫無競賽數據')
|
console.log('ℹ️ 暫無競賽數據')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 競賽數據載入失敗:', competitionsData.message)
|
// 沒有競賽數據是正常情況,不報錯
|
||||||
// 設置空數組以避免undefined錯誤
|
|
||||||
setCompetitions([])
|
setCompetitions([])
|
||||||
|
console.log('ℹ️ 暫無競賽數據')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入當前競賽
|
// 載入當前競賽
|
||||||
@@ -173,7 +173,9 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
console.log('ℹ️ 暫無當前競賽')
|
console.log('ℹ️ 暫無當前競賽')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 當前競賽載入失敗:', currentData.message)
|
// 沒有當前競賽是正常情況,不報錯
|
||||||
|
setCurrentCompetition(null)
|
||||||
|
console.log('ℹ️ 暫無當前競賽')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入評審數據
|
// 載入評審數據
|
||||||
@@ -191,8 +193,9 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
console.log('ℹ️ 暫無評審數據')
|
console.log('ℹ️ 暫無評審數據')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 評審數據載入失敗:', judgesData.message)
|
// 沒有評審數據是正常情況,不報錯
|
||||||
setJudges([])
|
setJudges([])
|
||||||
|
console.log('ℹ️ 暫無評審數據')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 載入競賽數據失敗:', error)
|
console.error('❌ 載入競賽數據失敗:', error)
|
||||||
|
156
lib/database-middleware.ts
Normal file
156
lib/database-middleware.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 資料庫連線管理中間件
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { db } from './database';
|
||||||
|
import { dbMonitor } from './database-monitor';
|
||||||
|
|
||||||
|
// 連線池狀態追蹤
|
||||||
|
let connectionCount = 0;
|
||||||
|
const maxConnections = 3; // 與資料庫配置保持一致
|
||||||
|
|
||||||
|
export function withDatabaseConnection<T extends any[]>(
|
||||||
|
handler: (...args: T) => Promise<NextResponse>
|
||||||
|
) {
|
||||||
|
return async (...args: T): Promise<NextResponse> => {
|
||||||
|
// 檢查連線池狀態
|
||||||
|
if (connectionCount >= maxConnections) {
|
||||||
|
console.warn(`⚠️ 連線池已滿 (${connectionCount}/${maxConnections}),等待可用連線...`);
|
||||||
|
|
||||||
|
// 等待一段時間後重試
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
if (connectionCount >= maxConnections) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: '資料庫連線池已滿,請稍後再試',
|
||||||
|
retryAfter: 5 // 建議5秒後重試
|
||||||
|
},
|
||||||
|
{ status: 503 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionCount++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await handler(...args);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('資料庫操作錯誤:', error);
|
||||||
|
|
||||||
|
// 根據錯誤類型返回適當的響應
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message.includes('ER_CON_COUNT_ERROR')) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: '資料庫連線數已達上限,請稍後再試',
|
||||||
|
retryAfter: 10
|
||||||
|
},
|
||||||
|
{ status: 503 }
|
||||||
|
);
|
||||||
|
} else if (error.message.includes('ECONNRESET') || error.message.includes('PROTOCOL_CONNECTION_LOST')) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: '資料庫連線中斷,請重試',
|
||||||
|
retryAfter: 3
|
||||||
|
},
|
||||||
|
{ status: 503 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: '資料庫操作失敗',
|
||||||
|
details: error instanceof Error ? error.message : '未知錯誤'
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
connectionCount--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 連線池健康檢查
|
||||||
|
export async function checkConnectionHealth(): Promise<{
|
||||||
|
isHealthy: boolean;
|
||||||
|
connectionCount: number;
|
||||||
|
maxConnections: number;
|
||||||
|
usagePercentage: number;
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
const stats = dbMonitor.getConnectionStats();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isHealthy: stats.usagePercentage < 80,
|
||||||
|
connectionCount: stats.currentConnections,
|
||||||
|
maxConnections: stats.maxConnections,
|
||||||
|
usagePercentage: stats.usagePercentage
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('連線健康檢查失敗:', error);
|
||||||
|
return {
|
||||||
|
isHealthy: false,
|
||||||
|
connectionCount: 0,
|
||||||
|
maxConnections: 0,
|
||||||
|
usagePercentage: 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自動釋放空閒連線
|
||||||
|
export async function autoReleaseIdleConnections(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const health = await checkConnectionHealth();
|
||||||
|
|
||||||
|
if (health.usagePercentage > 70) {
|
||||||
|
console.log('🔄 連線使用率過高,嘗試釋放空閒連線...');
|
||||||
|
return await dbMonitor.releaseIdleConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自動釋放連線失敗:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 連線池監控
|
||||||
|
export function startConnectionMonitoring() {
|
||||||
|
// 每30秒檢查一次連線狀態
|
||||||
|
dbMonitor.startMonitoring(30000);
|
||||||
|
|
||||||
|
// 每5分鐘嘗試釋放空閒連線
|
||||||
|
setInterval(async () => {
|
||||||
|
await autoReleaseIdleConnections();
|
||||||
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
console.log('🔍 資料庫連線監控已啟動');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 優雅的關閉
|
||||||
|
export async function gracefulShutdown() {
|
||||||
|
console.log('🔄 正在優雅關閉資料庫連線...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
dbMonitor.stopMonitoring();
|
||||||
|
await db.close();
|
||||||
|
console.log('✅ 資料庫連線已關閉');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 關閉資料庫連線時發生錯誤:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 處理程序退出事件
|
||||||
|
if (typeof process !== 'undefined') {
|
||||||
|
process.on('SIGINT', gracefulShutdown);
|
||||||
|
process.on('SIGTERM', gracefulShutdown);
|
||||||
|
process.on('exit', gracefulShutdown);
|
||||||
|
}
|
166
lib/database-monitor.ts
Normal file
166
lib/database-monitor.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 資料庫連線監控工具
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { db } from './database';
|
||||||
|
|
||||||
|
export class DatabaseMonitor {
|
||||||
|
private static instance: DatabaseMonitor;
|
||||||
|
private connectionCount = 0;
|
||||||
|
private maxConnections = 0;
|
||||||
|
private isMonitoring = false;
|
||||||
|
private monitorInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): DatabaseMonitor {
|
||||||
|
if (!DatabaseMonitor.instance) {
|
||||||
|
DatabaseMonitor.instance = new DatabaseMonitor();
|
||||||
|
}
|
||||||
|
return DatabaseMonitor.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 開始監控
|
||||||
|
public startMonitoring(intervalMs: number = 30000) {
|
||||||
|
if (this.isMonitoring) {
|
||||||
|
console.log('⚠️ 資料庫監控已在運行中');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isMonitoring = true;
|
||||||
|
console.log('🔍 開始監控資料庫連線狀態...');
|
||||||
|
|
||||||
|
this.monitorInterval = setInterval(async () => {
|
||||||
|
await this.checkConnectionStatus();
|
||||||
|
}, intervalMs);
|
||||||
|
|
||||||
|
// 立即執行一次檢查
|
||||||
|
this.checkConnectionStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止監控
|
||||||
|
public stopMonitoring() {
|
||||||
|
if (this.monitorInterval) {
|
||||||
|
clearInterval(this.monitorInterval);
|
||||||
|
this.monitorInterval = null;
|
||||||
|
}
|
||||||
|
this.isMonitoring = false;
|
||||||
|
console.log('⏹️ 資料庫監控已停止');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查連線狀態
|
||||||
|
private async checkConnectionStatus() {
|
||||||
|
try {
|
||||||
|
// 檢查當前連線數
|
||||||
|
const statusResult = await db.queryOne(`
|
||||||
|
SHOW STATUS LIKE 'Threads_connected'
|
||||||
|
`);
|
||||||
|
|
||||||
|
const currentConnections = statusResult?.Value || 0;
|
||||||
|
this.connectionCount = parseInt(currentConnections);
|
||||||
|
|
||||||
|
// 檢查最大連線數
|
||||||
|
const maxConnResult = await db.queryOne(`
|
||||||
|
SHOW VARIABLES LIKE 'max_connections'
|
||||||
|
`);
|
||||||
|
|
||||||
|
this.maxConnections = parseInt(maxConnResult?.Value || '100');
|
||||||
|
|
||||||
|
// 檢查連線使用率
|
||||||
|
const usagePercentage = (this.connectionCount / this.maxConnections) * 100;
|
||||||
|
|
||||||
|
// 記錄狀態
|
||||||
|
console.log(`📊 資料庫連線狀態: ${this.connectionCount}/${this.maxConnections} (${usagePercentage.toFixed(1)}%)`);
|
||||||
|
|
||||||
|
// 警告檢查
|
||||||
|
if (usagePercentage > 80) {
|
||||||
|
console.warn(`⚠️ 資料庫連線使用率過高: ${usagePercentage.toFixed(1)}%`);
|
||||||
|
} else if (usagePercentage > 60) {
|
||||||
|
console.log(`⚠️ 資料庫連線使用率較高: ${usagePercentage.toFixed(1)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查長時間運行的查詢
|
||||||
|
await this.checkLongRunningQueries();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 資料庫監控錯誤:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查長時間運行的查詢
|
||||||
|
private async checkLongRunningQueries() {
|
||||||
|
try {
|
||||||
|
const longQueries = await db.query(`
|
||||||
|
SELECT
|
||||||
|
ID,
|
||||||
|
USER,
|
||||||
|
HOST,
|
||||||
|
DB,
|
||||||
|
COMMAND,
|
||||||
|
TIME,
|
||||||
|
STATE,
|
||||||
|
INFO
|
||||||
|
FROM information_schema.PROCESSLIST
|
||||||
|
WHERE TIME > 30
|
||||||
|
AND COMMAND != 'Sleep'
|
||||||
|
ORDER BY TIME DESC
|
||||||
|
LIMIT 5
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (longQueries.length > 0) {
|
||||||
|
console.warn(`⚠️ 發現 ${longQueries.length} 個長時間運行的查詢:`);
|
||||||
|
longQueries.forEach((query, index) => {
|
||||||
|
console.warn(` ${index + 1}. 用戶: ${query.USER}, 時間: ${query.TIME}s, 狀態: ${query.STATE}`);
|
||||||
|
if (query.INFO && query.INFO.length > 100) {
|
||||||
|
console.warn(` 查詢: ${query.INFO.substring(0, 100)}...`);
|
||||||
|
} else if (query.INFO) {
|
||||||
|
console.warn(` 查詢: ${query.INFO}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 檢查長時間查詢時發生錯誤:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取連線統計
|
||||||
|
public getConnectionStats() {
|
||||||
|
return {
|
||||||
|
currentConnections: this.connectionCount,
|
||||||
|
maxConnections: this.maxConnections,
|
||||||
|
usagePercentage: (this.connectionCount / this.maxConnections) * 100,
|
||||||
|
isMonitoring: this.isMonitoring
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 強制釋放空閒連線
|
||||||
|
public async releaseIdleConnections() {
|
||||||
|
try {
|
||||||
|
const result = await db.query(`
|
||||||
|
KILL
|
||||||
|
(SELECT GROUP_CONCAT(ID)
|
||||||
|
FROM information_schema.PROCESSLIST
|
||||||
|
WHERE COMMAND = 'Sleep'
|
||||||
|
AND TIME > 300)
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log('🔄 已嘗試釋放空閒連線');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 釋放空閒連線失敗:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取連線池狀態
|
||||||
|
public getPoolStatus() {
|
||||||
|
// 這裡可以添加更多連線池狀態信息
|
||||||
|
return {
|
||||||
|
isMonitoring: this.isMonitoring,
|
||||||
|
lastCheck: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 導出單例實例
|
||||||
|
export const dbMonitor = DatabaseMonitor.getInstance();
|
308
lib/database-service-base.ts
Normal file
308
lib/database-service-base.ts
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 資料庫服務基類 - 確保連線正確關閉
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { db } from './database';
|
||||||
|
import { PoolConnection } from 'mysql2/promise';
|
||||||
|
|
||||||
|
export abstract class DatabaseServiceBase {
|
||||||
|
// 靜態安全查詢方法 - 確保連線關閉
|
||||||
|
static async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 獲取連線
|
||||||
|
connection = await db.getConnection();
|
||||||
|
|
||||||
|
// 執行查詢
|
||||||
|
const [rows] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return rows as T[];
|
||||||
|
} catch (error) {
|
||||||
|
// 確保連線被釋放
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 實例安全查詢方法 - 確保連線關閉
|
||||||
|
protected async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 獲取連線
|
||||||
|
connection = await db.getConnection();
|
||||||
|
|
||||||
|
// 執行查詢
|
||||||
|
const [rows] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return rows as T[];
|
||||||
|
} catch (error) {
|
||||||
|
// 確保連線被釋放
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 靜態安全單一查詢方法
|
||||||
|
static async safeQueryOne<T = any>(sql: string, params?: any[]): Promise<T | null> {
|
||||||
|
const results = await DatabaseServiceBase.safeQuery<T>(sql, params);
|
||||||
|
return results.length > 0 ? results[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 實例安全單一查詢方法
|
||||||
|
protected async safeQueryOne<T = any>(sql: string, params?: any[]): Promise<T | null> {
|
||||||
|
const results = await this.safeQuery<T>(sql, params);
|
||||||
|
return results.length > 0 ? results[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 靜態安全插入方法
|
||||||
|
static async safeInsert(sql: string, params?: any[]): Promise<any> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 實例安全插入方法
|
||||||
|
protected async safeInsert(sql: string, params?: any[]): Promise<any> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 靜態安全更新方法
|
||||||
|
static async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 實例安全更新方法
|
||||||
|
protected async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 靜態安全刪除方法
|
||||||
|
static async safeDelete(sql: string, params?: any[]): Promise<any> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 實例安全刪除方法
|
||||||
|
protected async safeDelete(sql: string, params?: any[]): Promise<any> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事務處理方法
|
||||||
|
protected async withTransaction<T>(
|
||||||
|
callback: (connection: PoolConnection) => Promise<T>
|
||||||
|
): Promise<T> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
await connection.beginTransaction();
|
||||||
|
|
||||||
|
const result = await callback(connection);
|
||||||
|
|
||||||
|
await connection.commit();
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
await connection.rollback();
|
||||||
|
} catch (rollbackError) {
|
||||||
|
console.error('回滾事務時發生錯誤:', rollbackError);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批次查詢方法 - 減少連線使用
|
||||||
|
protected async batchQuery<T = any>(
|
||||||
|
queries: Array<{ sql: string; params?: any[] }>
|
||||||
|
): Promise<T[][]> {
|
||||||
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = await db.getConnection();
|
||||||
|
const results: T[][] = [];
|
||||||
|
|
||||||
|
for (const query of queries) {
|
||||||
|
const [rows] = await connection.execute(query.sql, query.params);
|
||||||
|
results.push(rows as T[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即釋放連線
|
||||||
|
connection.release();
|
||||||
|
connection = null;
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (error) {
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 連線健康檢查
|
||||||
|
protected async checkConnectionHealth(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await this.safeQueryOne('SELECT 1 as health_check');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('資料庫連線健康檢查失敗:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -15,17 +15,20 @@ const dbConfig = {
|
|||||||
database: process.env.DB_NAME || 'db_AI_Platform',
|
database: process.env.DB_NAME || 'db_AI_Platform',
|
||||||
charset: 'utf8mb4',
|
charset: 'utf8mb4',
|
||||||
timezone: '+08:00',
|
timezone: '+08:00',
|
||||||
acquireTimeout: 10000, // 10秒獲取連接超時
|
acquireTimeout: 5000, // 5秒獲取連接超時
|
||||||
timeout: 10000, // 10秒查詢超時
|
timeout: 8000, // 8秒查詢超時
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
connectionLimit: 5, // 減少連接數,避免 Too many connections
|
connectionLimit: 3, // 進一步減少連接數,避免 Too many connections
|
||||||
queueLimit: 10, // 允許排隊,避免立即失敗
|
queueLimit: 5, // 減少排隊數量
|
||||||
// 添加連接重試和錯誤處理配置
|
// 添加連接重試和錯誤處理配置
|
||||||
retryDelay: 2000,
|
retryDelay: 1000,
|
||||||
maxRetries: 3,
|
maxRetries: 2,
|
||||||
// 添加連接池配置
|
// 添加連接池配置
|
||||||
idleTimeout: 60000, // 1分鐘空閒超時,快速釋放連接
|
idleTimeout: 30000, // 30秒空閒超時,快速釋放連接
|
||||||
maxIdle: 5, // 最大空閒連接數
|
maxIdle: 2, // 最大空閒連接數
|
||||||
|
// 添加連接生命週期管理
|
||||||
|
maxReconnects: 3,
|
||||||
|
reconnectDelay: 2000,
|
||||||
// 添加 SSL 配置(如果需要)
|
// 添加 SSL 配置(如果需要)
|
||||||
ssl: false as any,
|
ssl: false as any,
|
||||||
};
|
};
|
||||||
@@ -67,9 +70,9 @@ export class Database {
|
|||||||
return await dbFailover.query<T>(sql, params);
|
return await dbFailover.query<T>(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection;
|
let connection: mysql.PoolConnection | null = null;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
const maxRetries = 3;
|
const maxRetries = 2;
|
||||||
|
|
||||||
while (retries < maxRetries) {
|
while (retries < maxRetries) {
|
||||||
try {
|
try {
|
||||||
@@ -80,14 +83,19 @@ export class Database {
|
|||||||
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
||||||
|
|
||||||
if (connection) {
|
if (connection) {
|
||||||
connection.release();
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') {
|
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ER_CON_COUNT_ERROR') {
|
||||||
retries++;
|
retries++;
|
||||||
if (retries < maxRetries) {
|
if (retries < maxRetries) {
|
||||||
console.log(`等待 ${2000 * retries}ms 後重試...`);
|
console.log(`等待 ${1000 * retries}ms 後重試...`);
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000 * retries));
|
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +103,11 @@ export class Database {
|
|||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
connection.release();
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,12 +150,22 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = await this.getConnection();
|
let connection: mysql.PoolConnection | null = null;
|
||||||
try {
|
try {
|
||||||
|
connection = await this.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
return result as mysql.ResultSetHeader;
|
return result as mysql.ResultSetHeader;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('資料庫插入錯誤:', error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,12 +189,22 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = await this.getConnection();
|
let connection: mysql.PoolConnection | null = null;
|
||||||
try {
|
try {
|
||||||
|
connection = await this.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
return result as mysql.ResultSetHeader;
|
return result as mysql.ResultSetHeader;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('資料庫更新錯誤:', error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,12 +228,22 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = await this.getConnection();
|
let connection: mysql.PoolConnection | null = null;
|
||||||
try {
|
try {
|
||||||
|
connection = await this.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
return result as mysql.ResultSetHeader;
|
return result as mysql.ResultSetHeader;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('資料庫刪除錯誤:', error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
if (connection) {
|
||||||
|
try {
|
||||||
|
connection.release();
|
||||||
|
} catch (releaseError) {
|
||||||
|
console.error('釋放連線時發生錯誤:', releaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { db } from '../database';
|
import { db } from '../database';
|
||||||
import { dbSync } from '../database-sync';
|
import { dbSync } from '../database-sync';
|
||||||
|
import { DatabaseServiceBase } from '../database-service-base';
|
||||||
const { DatabaseSyncFixed } = require('../database-sync-fixed.js');
|
const { DatabaseSyncFixed } = require('../database-sync-fixed.js');
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
@@ -37,7 +38,7 @@ import type {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 用戶服務
|
// 用戶服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class UserService {
|
export class UserService extends DatabaseServiceBase {
|
||||||
// 創建用戶
|
// 創建用戶
|
||||||
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
|
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
|
||||||
const sql = `
|
const sql = `
|
||||||
@@ -59,20 +60,20 @@ export class UserService {
|
|||||||
userData.last_login || null
|
userData.last_login || null
|
||||||
];
|
];
|
||||||
|
|
||||||
await db.insert(sql, params);
|
await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
return await this.findByEmail(userData.email) as User;
|
return await this.findByEmail(userData.email) as User;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根據郵箱獲取用戶
|
// 根據郵箱獲取用戶
|
||||||
async findByEmail(email: string): Promise<User | null> {
|
async findByEmail(email: string): Promise<User | null> {
|
||||||
const sql = 'SELECT * FROM users WHERE email = ? AND status = "active"';
|
const sql = 'SELECT * FROM users WHERE email = ? AND status = "active"';
|
||||||
return await db.queryOne<User>(sql, [email]);
|
return await this.safeQueryOne<User>(sql, [email]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根據ID獲取用戶
|
// 根據ID獲取用戶
|
||||||
async findById(id: string): Promise<User | null> {
|
async findById(id: string): Promise<User | null> {
|
||||||
const sql = 'SELECT * FROM users WHERE id = ? AND status = "active"';
|
const sql = 'SELECT * FROM users WHERE id = ? AND status = "active"';
|
||||||
return await db.queryOne<User>(sql, [id]);
|
return await this.safeQueryOne<User>(sql, [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新用戶
|
// 更新用戶
|
||||||
@@ -82,7 +83,7 @@ export class UserService {
|
|||||||
const values = fields.map(field => (updates as any)[field]);
|
const values = fields.map(field => (updates as any)[field]);
|
||||||
|
|
||||||
const sql = `UPDATE users SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
const sql = `UPDATE users SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||||
const result = await db.update(sql, [...values, id]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [...values, id]);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return await this.findById(id);
|
return await this.findById(id);
|
||||||
@@ -93,7 +94,7 @@ export class UserService {
|
|||||||
// 更新最後登入時間
|
// 更新最後登入時間
|
||||||
async updateLastLogin(id: string): Promise<boolean> {
|
async updateLastLogin(id: string): Promise<boolean> {
|
||||||
const sql = 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?';
|
const sql = 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?';
|
||||||
const result = await db.update(sql, [id]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [id]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ export class UserService {
|
|||||||
page?: number;
|
page?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
} = {}): Promise<{ users: User[]; total: number }> {
|
} = {}): Promise<{ users: User[]; total: number }> {
|
||||||
const { search, department, role, status, page = 1, limit = 10 } = filters;
|
const { search, department, role, status, page = 1, limit = 5 } = filters;
|
||||||
|
|
||||||
// 構建查詢條件
|
// 構建查詢條件
|
||||||
let whereConditions: string[] = [];
|
let whereConditions: string[] = [];
|
||||||
@@ -150,7 +151,7 @@ export class UserService {
|
|||||||
|
|
||||||
// 獲取總數
|
// 獲取總數
|
||||||
const countSql = `SELECT COUNT(*) as total FROM users ${whereClause}`;
|
const countSql = `SELECT COUNT(*) as total FROM users ${whereClause}`;
|
||||||
const countResult = await this.query(countSql, params);
|
const countResult = await DatabaseServiceBase.safeQuery(countSql, params);
|
||||||
const total = countResult[0]?.total || 0;
|
const total = countResult[0]?.total || 0;
|
||||||
|
|
||||||
// 獲取用戶列表
|
// 獲取用戶列表
|
||||||
@@ -164,7 +165,7 @@ export class UserService {
|
|||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT ${offset}, ${limit}
|
LIMIT ${offset}, ${limit}
|
||||||
`;
|
`;
|
||||||
const users = await this.query<User>(usersSql, params);
|
const users = await this.safeQuery<User>(usersSql, params);
|
||||||
|
|
||||||
return { users, total };
|
return { users, total };
|
||||||
}
|
}
|
||||||
@@ -190,7 +191,7 @@ export class UserService {
|
|||||||
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_this_month
|
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_this_month
|
||||||
FROM users
|
FROM users
|
||||||
`;
|
`;
|
||||||
const result = await this.query(sql);
|
const result = await DatabaseServiceBase.safeQuery(sql);
|
||||||
const stats = result[0] || {};
|
const stats = result[0] || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -823,7 +824,7 @@ export class UserService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 評審服務
|
// 評審服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class JudgeService {
|
export class JudgeService extends DatabaseServiceBase {
|
||||||
// 安全解析 expertise 字段
|
// 安全解析 expertise 字段
|
||||||
private static parseExpertise(expertise: any): string[] {
|
private static parseExpertise(expertise: any): string[] {
|
||||||
if (!expertise) return [];
|
if (!expertise) return [];
|
||||||
@@ -847,7 +848,7 @@ export class JudgeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 創建評審
|
// 創建評審
|
||||||
static async createJudge(judgeData: Omit<Judge, 'id' | 'created_at' | 'updated_at'>): Promise<Judge> {
|
async createJudge(judgeData: Omit<Judge, 'id' | 'created_at' | 'updated_at'>): Promise<Judge> {
|
||||||
const sql = `
|
const sql = `
|
||||||
INSERT INTO judges (id, name, title, department, expertise, avatar, is_active)
|
INSERT INTO judges (id, name, title, department, expertise, avatar, is_active)
|
||||||
VALUES (UUID(), ?, ?, ?, ?, ?, ?)
|
VALUES (UUID(), ?, ?, ?, ?, ?, ?)
|
||||||
@@ -861,8 +862,8 @@ export class JudgeService {
|
|||||||
judgeData.is_active
|
judgeData.is_active
|
||||||
];
|
];
|
||||||
|
|
||||||
await db.insert(sql, params);
|
await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
return await this.getJudgeByName(judgeData.name) as Judge;
|
return await JudgeService.getJudgeByName(judgeData.name) as Judge;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根據姓名獲取評審
|
// 根據姓名獲取評審
|
||||||
@@ -916,7 +917,7 @@ export class JudgeService {
|
|||||||
console.log('執行 SQL:', sql);
|
console.log('執行 SQL:', sql);
|
||||||
console.log('參數:', [...values, id]);
|
console.log('參數:', [...values, id]);
|
||||||
|
|
||||||
const result = await db.update(sql, [...values, id]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [...values, id]);
|
||||||
console.log('更新結果:', result);
|
console.log('更新結果:', result);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
@@ -925,7 +926,7 @@ export class JudgeService {
|
|||||||
static async deleteJudge(id: string): Promise<boolean> {
|
static async deleteJudge(id: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'DELETE FROM judges WHERE id = ?';
|
const sql = 'DELETE FROM judges WHERE id = ?';
|
||||||
const result = await db.delete(sql, [id]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [id]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('刪除評審錯誤:', error);
|
console.error('刪除評審錯誤:', error);
|
||||||
@@ -967,7 +968,7 @@ export class JudgeService {
|
|||||||
|
|
||||||
sql += ' ORDER BY a.name';
|
sql += ' ORDER BY a.name';
|
||||||
|
|
||||||
const results = await db.query(sql, params);
|
const results = await DatabaseServiceBase.safeQuery(sql, params);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -975,7 +976,7 @@ export class JudgeService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 團隊服務
|
// 團隊服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class TeamService {
|
export class TeamService extends DatabaseServiceBase {
|
||||||
// 創建團隊
|
// 創建團隊
|
||||||
static async createTeam(teamData: {
|
static async createTeam(teamData: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -998,7 +999,7 @@ export class TeamService {
|
|||||||
teamData.description || null
|
teamData.description || null
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await db.insert(sql, params);
|
const result = await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
console.log('團隊創建結果:', result);
|
console.log('團隊創建結果:', result);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -1017,7 +1018,7 @@ export class TeamService {
|
|||||||
GROUP BY t.id
|
GROUP BY t.id
|
||||||
ORDER BY t.created_at DESC
|
ORDER BY t.created_at DESC
|
||||||
`;
|
`;
|
||||||
const results = await db.query(sql);
|
const results = await DatabaseServiceBase.safeQuery(sql);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1031,7 +1032,7 @@ export class TeamService {
|
|||||||
LEFT JOIN users u ON t.leader_id = u.id
|
LEFT JOIN users u ON t.leader_id = u.id
|
||||||
WHERE t.id = ? AND t.is_active = TRUE
|
WHERE t.id = ? AND t.is_active = TRUE
|
||||||
`;
|
`;
|
||||||
const results = await db.query(sql, [id]);
|
const results = await DatabaseServiceBase.safeQuery(sql, [id]);
|
||||||
return results.length > 0 ? results[0] : null;
|
return results.length > 0 ? results[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1045,7 +1046,7 @@ export class TeamService {
|
|||||||
LEFT JOIN users u ON t.leader_id = u.id
|
LEFT JOIN users u ON t.leader_id = u.id
|
||||||
WHERE t.name = ? AND t.is_active = TRUE
|
WHERE t.name = ? AND t.is_active = TRUE
|
||||||
`;
|
`;
|
||||||
const results = await db.query(sql, [name]);
|
const results = await DatabaseServiceBase.safeQuery(sql, [name]);
|
||||||
return results.length > 0 ? results[0] : null;
|
return results.length > 0 ? results[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1072,7 +1073,7 @@ export class TeamService {
|
|||||||
const sql = `UPDATE teams SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
const sql = `UPDATE teams SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await db.update(sql, values);
|
const result = await DatabaseServiceBase.safeUpdate(sql, values);
|
||||||
console.log('團隊更新結果:', result);
|
console.log('團隊更新結果:', result);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1085,7 +1086,7 @@ export class TeamService {
|
|||||||
static async deleteTeam(id: string): Promise<boolean> {
|
static async deleteTeam(id: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'UPDATE teams SET is_active = FALSE, updated_at = CURRENT_TIMESTAMP WHERE id = ?';
|
const sql = 'UPDATE teams SET is_active = FALSE, updated_at = CURRENT_TIMESTAMP WHERE id = ?';
|
||||||
const result = await db.update(sql, [id]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [id]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('刪除團隊錯誤:', error);
|
console.error('刪除團隊錯誤:', error);
|
||||||
@@ -1097,7 +1098,7 @@ export class TeamService {
|
|||||||
static async hardDeleteTeam(id: string): Promise<boolean> {
|
static async hardDeleteTeam(id: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'DELETE FROM teams WHERE id = ?';
|
const sql = 'DELETE FROM teams WHERE id = ?';
|
||||||
const result = await db.delete(sql, [id]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [id]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('硬刪除團隊錯誤:', error);
|
console.error('硬刪除團隊錯誤:', error);
|
||||||
@@ -1115,7 +1116,7 @@ export class TeamService {
|
|||||||
const params = [id, teamId, userId, role];
|
const params = [id, teamId, userId, role];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await db.insert(sql, params);
|
const result = await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
console.log('團隊成員添加結果:', result);
|
console.log('團隊成員添加結果:', result);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1133,7 +1134,7 @@ export class TeamService {
|
|||||||
WHERE tm.team_id = ? AND u.status = 'active'
|
WHERE tm.team_id = ? AND u.status = 'active'
|
||||||
ORDER BY tm.joined_at ASC
|
ORDER BY tm.joined_at ASC
|
||||||
`;
|
`;
|
||||||
const results = await db.query(sql, [teamId]);
|
const results = await DatabaseServiceBase.safeQuery(sql, [teamId]);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1141,7 +1142,7 @@ export class TeamService {
|
|||||||
static async removeTeamMember(teamId: string, userId: string): Promise<boolean> {
|
static async removeTeamMember(teamId: string, userId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'DELETE FROM team_members WHERE team_id = ? AND user_id = ?';
|
const sql = 'DELETE FROM team_members WHERE team_id = ? AND user_id = ?';
|
||||||
const result = await db.delete(sql, [teamId, userId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [teamId, userId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('移除團隊成員錯誤:', error);
|
console.error('移除團隊成員錯誤:', error);
|
||||||
@@ -1153,7 +1154,7 @@ export class TeamService {
|
|||||||
static async updateTeamMemberRole(teamId: string, userId: string, role: string): Promise<boolean> {
|
static async updateTeamMemberRole(teamId: string, userId: string, role: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'UPDATE team_members SET role = ? WHERE team_id = ? AND user_id = ?';
|
const sql = 'UPDATE team_members SET role = ? WHERE team_id = ? AND user_id = ?';
|
||||||
const result = await db.update(sql, [role, teamId, userId]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [role, teamId, userId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新團隊成員角色錯誤:', error);
|
console.error('更新團隊成員角色錯誤:', error);
|
||||||
@@ -1165,7 +1166,7 @@ export class TeamService {
|
|||||||
static async bindAppToTeam(teamId: string, appId: string): Promise<boolean> {
|
static async bindAppToTeam(teamId: string, appId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'UPDATE apps SET team_id = ? WHERE id = ? AND is_active = TRUE';
|
const sql = 'UPDATE apps SET team_id = ? WHERE id = ? AND is_active = TRUE';
|
||||||
const result = await db.update(sql, [teamId, appId]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [teamId, appId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('綁定應用到團隊錯誤:', error);
|
console.error('綁定應用到團隊錯誤:', error);
|
||||||
@@ -1177,7 +1178,7 @@ export class TeamService {
|
|||||||
static async unbindAppFromTeam(appId: string): Promise<boolean> {
|
static async unbindAppFromTeam(appId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const sql = 'UPDATE apps SET team_id = NULL WHERE id = ?';
|
const sql = 'UPDATE apps SET team_id = NULL WHERE id = ?';
|
||||||
const result = await db.update(sql, [appId]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [appId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解除應用與團隊綁定錯誤:', error);
|
console.error('解除應用與團隊綁定錯誤:', error);
|
||||||
@@ -1200,7 +1201,7 @@ export class TeamService {
|
|||||||
`;
|
`;
|
||||||
console.log('📝 getTeamApps SQL:', sql);
|
console.log('📝 getTeamApps SQL:', sql);
|
||||||
console.log('📝 getTeamApps 參數:', [teamId]);
|
console.log('📝 getTeamApps 參數:', [teamId]);
|
||||||
const results = await db.query(sql, [teamId]);
|
const results = await DatabaseServiceBase.safeQuery(sql, [teamId]);
|
||||||
console.log('📊 getTeamApps 結果:', results.length, '個應用');
|
console.log('📊 getTeamApps 結果:', results.length, '個應用');
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1221,7 @@ export class TeamService {
|
|||||||
GROUP BY t.id
|
GROUP BY t.id
|
||||||
) as team_stats
|
) as team_stats
|
||||||
`;
|
`;
|
||||||
const results = await db.query(sql);
|
const results = await DatabaseServiceBase.safeQuery(sql);
|
||||||
return results[0] || { totalTeams: 0, activeTeams: 0, inactiveTeams: 0, avgMembersPerTeam: 0 };
|
return results[0] || { totalTeams: 0, activeTeams: 0, inactiveTeams: 0, avgMembersPerTeam: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1228,7 +1229,7 @@ export class TeamService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 競賽服務
|
// 競賽服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class CompetitionService {
|
export class CompetitionService extends DatabaseServiceBase {
|
||||||
// 創建競賽
|
// 創建競賽
|
||||||
static async createCompetition(competitionData: Omit<Competition, 'id' | 'created_at' | 'updated_at'>): Promise<Competition> {
|
static async createCompetition(competitionData: Omit<Competition, 'id' | 'created_at' | 'updated_at'>): Promise<Competition> {
|
||||||
// 使用智能雙寫,每個資料庫生成自己的 ID
|
// 使用智能雙寫,每個資料庫生成自己的 ID
|
||||||
@@ -1409,7 +1410,7 @@ export class CompetitionService {
|
|||||||
// 清除當前競賽
|
// 清除當前競賽
|
||||||
static async clearCurrentCompetition(): Promise<boolean> {
|
static async clearCurrentCompetition(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await db.update('UPDATE competitions SET is_current = FALSE');
|
await DatabaseServiceBase.safeUpdate('UPDATE competitions SET is_current = FALSE');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1487,7 +1488,7 @@ export class CompetitionService {
|
|||||||
WHERE cj.competition_id = ? AND j.is_active = TRUE
|
WHERE cj.competition_id = ? AND j.is_active = TRUE
|
||||||
ORDER BY cj.assigned_at ASC
|
ORDER BY cj.assigned_at ASC
|
||||||
`;
|
`;
|
||||||
return await db.query(sql, [competitionId]);
|
return await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 為競賽添加評審
|
// 為競賽添加評審
|
||||||
@@ -1496,7 +1497,7 @@ export class CompetitionService {
|
|||||||
const dbSyncFixed = new DatabaseSyncFixed();
|
const dbSyncFixed = new DatabaseSyncFixed();
|
||||||
|
|
||||||
// 先刪除現有的評審關聯
|
// 先刪除現有的評審關聯
|
||||||
await db.delete('DELETE FROM competition_judges WHERE competition_id = ?', [competitionId]);
|
await DatabaseServiceBase.safeDelete('DELETE FROM competition_judges WHERE competition_id = ?', [competitionId]);
|
||||||
|
|
||||||
// 添加新的評審關聯
|
// 添加新的評審關聯
|
||||||
if (judgeIds.length > 0) {
|
if (judgeIds.length > 0) {
|
||||||
@@ -1534,7 +1535,7 @@ export class CompetitionService {
|
|||||||
// 從競賽中移除評審
|
// 從競賽中移除評審
|
||||||
static async removeCompetitionJudge(competitionId: string, judgeId: string): Promise<boolean> {
|
static async removeCompetitionJudge(competitionId: string, judgeId: string): Promise<boolean> {
|
||||||
const sql = 'DELETE FROM competition_judges WHERE competition_id = ? AND judge_id = ?';
|
const sql = 'DELETE FROM competition_judges WHERE competition_id = ? AND judge_id = ?';
|
||||||
const result = await db.delete(sql, [competitionId, judgeId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, judgeId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1548,7 +1549,7 @@ export class CompetitionService {
|
|||||||
WHERE ct.competition_id = ? AND t.is_active = TRUE
|
WHERE ct.competition_id = ? AND t.is_active = TRUE
|
||||||
ORDER BY ct.registered_at ASC
|
ORDER BY ct.registered_at ASC
|
||||||
`;
|
`;
|
||||||
return await db.query(sql, [competitionId]);
|
return await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取競賽的應用列表
|
// 獲取競賽的應用列表
|
||||||
@@ -1579,7 +1580,7 @@ export class CompetitionService {
|
|||||||
WHERE a.team_id IN (${placeholders}) AND a.is_active = TRUE
|
WHERE a.team_id IN (${placeholders}) AND a.is_active = TRUE
|
||||||
ORDER BY a.created_at ASC
|
ORDER BY a.created_at ASC
|
||||||
`;
|
`;
|
||||||
apps = await db.query(sql, teamIds);
|
apps = await DatabaseServiceBase.safeQuery(sql, teamIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1592,7 +1593,7 @@ export class CompetitionService {
|
|||||||
WHERE ca.competition_id = ? AND a.is_active = TRUE
|
WHERE ca.competition_id = ? AND a.is_active = TRUE
|
||||||
ORDER BY ca.submitted_at ASC
|
ORDER BY ca.submitted_at ASC
|
||||||
`;
|
`;
|
||||||
apps = await db.query(sql, [competitionId]);
|
apps = await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
@@ -1604,7 +1605,7 @@ export class CompetitionService {
|
|||||||
const dbSyncFixed = new DatabaseSyncFixed();
|
const dbSyncFixed = new DatabaseSyncFixed();
|
||||||
|
|
||||||
// 先刪除現有的團隊關聯
|
// 先刪除現有的團隊關聯
|
||||||
await db.delete('DELETE FROM competition_teams WHERE competition_id = ?', [competitionId]);
|
await DatabaseServiceBase.safeDelete('DELETE FROM competition_teams WHERE competition_id = ?', [competitionId]);
|
||||||
|
|
||||||
// 添加新的團隊關聯
|
// 添加新的團隊關聯
|
||||||
if (teamIds.length > 0) {
|
if (teamIds.length > 0) {
|
||||||
@@ -1642,7 +1643,7 @@ export class CompetitionService {
|
|||||||
// 從競賽中移除團隊
|
// 從競賽中移除團隊
|
||||||
static async removeCompetitionTeam(competitionId: string, teamId: string): Promise<boolean> {
|
static async removeCompetitionTeam(competitionId: string, teamId: string): Promise<boolean> {
|
||||||
const sql = 'DELETE FROM competition_teams WHERE competition_id = ? AND team_id = ?';
|
const sql = 'DELETE FROM competition_teams WHERE competition_id = ? AND team_id = ?';
|
||||||
const result = await db.delete(sql, [competitionId, teamId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, teamId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,7 +1653,7 @@ export class CompetitionService {
|
|||||||
const dbSyncFixed = new DatabaseSyncFixed();
|
const dbSyncFixed = new DatabaseSyncFixed();
|
||||||
|
|
||||||
// 先刪除現有的應用關聯
|
// 先刪除現有的應用關聯
|
||||||
await db.delete('DELETE FROM competition_apps WHERE competition_id = ?', [competitionId]);
|
await DatabaseServiceBase.safeDelete('DELETE FROM competition_apps WHERE competition_id = ?', [competitionId]);
|
||||||
|
|
||||||
// 添加新的應用關聯
|
// 添加新的應用關聯
|
||||||
if (appIds.length > 0) {
|
if (appIds.length > 0) {
|
||||||
@@ -1690,7 +1691,7 @@ export class CompetitionService {
|
|||||||
// 從競賽中移除應用
|
// 從競賽中移除應用
|
||||||
static async removeCompetitionApp(competitionId: string, appId: string): Promise<boolean> {
|
static async removeCompetitionApp(competitionId: string, appId: string): Promise<boolean> {
|
||||||
const sql = 'DELETE FROM competition_apps WHERE competition_id = ? AND app_id = ?';
|
const sql = 'DELETE FROM competition_apps WHERE competition_id = ? AND app_id = ?';
|
||||||
const result = await db.delete(sql, [competitionId, appId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, appId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1702,7 +1703,7 @@ export class CompetitionService {
|
|||||||
WHERE cat.competition_id = ?
|
WHERE cat.competition_id = ?
|
||||||
ORDER BY cat.order_index ASC, cat.created_at ASC
|
ORDER BY cat.order_index ASC, cat.created_at ASC
|
||||||
`;
|
`;
|
||||||
return await db.query(sql, [competitionId]);
|
return await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 為競賽添加獎項類型
|
// 為競賽添加獎項類型
|
||||||
@@ -1711,7 +1712,7 @@ export class CompetitionService {
|
|||||||
const dbSyncFixed = new DatabaseSyncFixed();
|
const dbSyncFixed = new DatabaseSyncFixed();
|
||||||
|
|
||||||
// 先刪除現有的獎項類型
|
// 先刪除現有的獎項類型
|
||||||
await db.delete('DELETE FROM competition_award_types WHERE competition_id = ?', [competitionId]);
|
await DatabaseServiceBase.safeDelete('DELETE FROM competition_award_types WHERE competition_id = ?', [competitionId]);
|
||||||
|
|
||||||
// 添加新的獎項類型
|
// 添加新的獎項類型
|
||||||
if (awardTypes.length > 0) {
|
if (awardTypes.length > 0) {
|
||||||
@@ -1756,7 +1757,7 @@ export class CompetitionService {
|
|||||||
// 從競賽中移除獎項類型
|
// 從競賽中移除獎項類型
|
||||||
static async removeCompetitionAwardType(competitionId: string, awardTypeId: string): Promise<boolean> {
|
static async removeCompetitionAwardType(competitionId: string, awardTypeId: string): Promise<boolean> {
|
||||||
const sql = 'DELETE FROM competition_award_types WHERE competition_id = ? AND id = ?';
|
const sql = 'DELETE FROM competition_award_types WHERE competition_id = ? AND id = ?';
|
||||||
const result = await db.delete(sql, [competitionId, awardTypeId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, awardTypeId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1767,7 +1768,7 @@ export class CompetitionService {
|
|||||||
const dbSyncFixed = new DatabaseSyncFixed();
|
const dbSyncFixed = new DatabaseSyncFixed();
|
||||||
|
|
||||||
// 先刪除現有的評分規則
|
// 先刪除現有的評分規則
|
||||||
await db.delete('DELETE FROM competition_rules WHERE competition_id = ?', [competitionId]);
|
await DatabaseServiceBase.safeDelete('DELETE FROM competition_rules WHERE competition_id = ?', [competitionId]);
|
||||||
|
|
||||||
// 添加新的評分規則
|
// 添加新的評分規則
|
||||||
if (rules.length > 0) {
|
if (rules.length > 0) {
|
||||||
@@ -1811,7 +1812,7 @@ export class CompetitionService {
|
|||||||
// 從競賽中移除評分規則
|
// 從競賽中移除評分規則
|
||||||
static async removeCompetitionRule(competitionId: string, ruleId: string): Promise<boolean> {
|
static async removeCompetitionRule(competitionId: string, ruleId: string): Promise<boolean> {
|
||||||
const sql = 'DELETE FROM competition_rules WHERE competition_id = ? AND id = ?';
|
const sql = 'DELETE FROM competition_rules WHERE competition_id = ? AND id = ?';
|
||||||
const result = await db.delete(sql, [competitionId, ruleId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, ruleId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1883,7 +1884,7 @@ export class CompetitionService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 應用服務
|
// 應用服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class AppService {
|
export class AppService extends DatabaseServiceBase {
|
||||||
// 創建應用
|
// 創建應用
|
||||||
async createApp(appData: {
|
async createApp(appData: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -3449,7 +3450,7 @@ export class AppService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 評分服務
|
// 評分服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class ScoringService {
|
export class ScoringService extends DatabaseServiceBase {
|
||||||
// 獲取競賽規則
|
// 獲取競賽規則
|
||||||
static async getCompetitionRules(competitionId: string): Promise<any[]> {
|
static async getCompetitionRules(competitionId: string): Promise<any[]> {
|
||||||
console.log('🔍 獲取競賽規則,competitionId:', competitionId);
|
console.log('🔍 獲取競賽規則,competitionId:', competitionId);
|
||||||
@@ -3462,7 +3463,7 @@ export class ScoringService {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await db.query(sql, [competitionId]);
|
const result = await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||||
console.log('🔍 競賽規則查詢結果:', result);
|
console.log('🔍 競賽規則查詢結果:', result);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -3478,7 +3479,7 @@ export class ScoringService {
|
|||||||
FROM competition_apps ca
|
FROM competition_apps ca
|
||||||
WHERE ca.app_id = ?
|
WHERE ca.app_id = ?
|
||||||
`;
|
`;
|
||||||
const result = await db.query(sql, [appId]);
|
const result = await DatabaseServiceBase.safeQuery(sql, [appId]);
|
||||||
return result.length > 0 ? result[0].competition_id : null;
|
return result.length > 0 ? result[0].competition_id : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3513,7 +3514,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 編輯模式,使用記錄ID:', finalJudgeScoreId);
|
console.log('🔍 編輯模式,使用記錄ID:', finalJudgeScoreId);
|
||||||
} else {
|
} else {
|
||||||
// 新增模式:檢查是否已存在評分記錄
|
// 新增模式:檢查是否已存在評分記錄
|
||||||
const existingScore = await db.query(
|
const existingScore = await DatabaseServiceBase.safeQuery(
|
||||||
'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ?',
|
'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ?',
|
||||||
[judge_id, app_id]
|
[judge_id, app_id]
|
||||||
);
|
);
|
||||||
@@ -3530,14 +3531,14 @@ export class ScoringService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 檢查記錄是否存在,決定是更新還是插入
|
// 檢查記錄是否存在,決定是更新還是插入
|
||||||
const existingRecord = await db.query(
|
const existingRecord = await DatabaseServiceBase.safeQuery(
|
||||||
'SELECT id FROM judge_scores WHERE id = ?',
|
'SELECT id FROM judge_scores WHERE id = ?',
|
||||||
[finalJudgeScoreId]
|
[finalJudgeScoreId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingRecord.length > 0) {
|
if (existingRecord.length > 0) {
|
||||||
// 更新現有記錄
|
// 更新現有記錄
|
||||||
await db.update(`
|
await DatabaseServiceBase.safeUpdate(`
|
||||||
UPDATE judge_scores
|
UPDATE judge_scores
|
||||||
SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP
|
SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@@ -3545,7 +3546,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 更新現有評分記錄');
|
console.log('🔍 更新現有評分記錄');
|
||||||
} else {
|
} else {
|
||||||
// 檢查是否已有相同 judge_id + app_id + competition_id 的記錄
|
// 檢查是否已有相同 judge_id + app_id + competition_id 的記錄
|
||||||
const duplicateRecord = await db.query(
|
const duplicateRecord = await DatabaseServiceBase.safeQuery(
|
||||||
'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ? AND competition_id = ?',
|
'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ? AND competition_id = ?',
|
||||||
[judge_id, app_id, competition_id]
|
[judge_id, app_id, competition_id]
|
||||||
);
|
);
|
||||||
@@ -3553,7 +3554,7 @@ export class ScoringService {
|
|||||||
if (duplicateRecord.length > 0) {
|
if (duplicateRecord.length > 0) {
|
||||||
// 更新現有記錄
|
// 更新現有記錄
|
||||||
finalJudgeScoreId = duplicateRecord[0].id;
|
finalJudgeScoreId = duplicateRecord[0].id;
|
||||||
await db.update(`
|
await DatabaseServiceBase.safeUpdate(`
|
||||||
UPDATE judge_scores
|
UPDATE judge_scores
|
||||||
SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP
|
SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@@ -3561,7 +3562,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 更新重複的評分記錄');
|
console.log('🔍 更新重複的評分記錄');
|
||||||
} else {
|
} else {
|
||||||
// 創建新記錄
|
// 創建新記錄
|
||||||
await db.insert(`
|
await DatabaseServiceBase.safeInsert(`
|
||||||
INSERT INTO judge_scores (id, judge_id, app_id, competition_id, total_score, comments)
|
INSERT INTO judge_scores (id, judge_id, app_id, competition_id, total_score, comments)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`, [
|
`, [
|
||||||
@@ -3581,7 +3582,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 競賽規則:', rules);
|
console.log('🔍 競賽規則:', rules);
|
||||||
|
|
||||||
// 3. 刪除現有的評分詳情
|
// 3. 刪除現有的評分詳情
|
||||||
await db.delete(
|
await DatabaseServiceBase.safeDelete(
|
||||||
'DELETE FROM judge_score_details WHERE judge_score_id = ?',
|
'DELETE FROM judge_score_details WHERE judge_score_id = ?',
|
||||||
[finalJudgeScoreId]
|
[finalJudgeScoreId]
|
||||||
);
|
);
|
||||||
@@ -3605,7 +3606,7 @@ export class ScoringService {
|
|||||||
weight: rule.weight
|
weight: rule.weight
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.insert(`
|
await DatabaseServiceBase.safeInsert(`
|
||||||
INSERT INTO judge_score_details (id, judge_score_id, rule_id, rule_name, score, weight)
|
INSERT INTO judge_score_details (id, judge_score_id, rule_id, rule_name, score, weight)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`, [
|
`, [
|
||||||
@@ -3646,7 +3647,7 @@ export class ScoringService {
|
|||||||
GROUP BY js.id
|
GROUP BY js.id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await db.query(sql, [judgeScoreId]);
|
const result = await DatabaseServiceBase.safeQuery(sql, [judgeScoreId]);
|
||||||
return result.length > 0 ? result[0] : null;
|
return result.length > 0 ? result[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3660,7 +3661,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 獲取競賽評分進度,competitionId:', competitionId);
|
console.log('🔍 獲取競賽評分進度,competitionId:', competitionId);
|
||||||
|
|
||||||
// 獲取競賽的評審數量
|
// 獲取競賽的評審數量
|
||||||
const judgesResult = await db.query(`
|
const judgesResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT COUNT(DISTINCT cj.judge_id) as judge_count
|
SELECT COUNT(DISTINCT cj.judge_id) as judge_count
|
||||||
FROM competition_judges cj
|
FROM competition_judges cj
|
||||||
WHERE cj.competition_id = ?
|
WHERE cj.competition_id = ?
|
||||||
@@ -3670,7 +3671,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 評審數量:', judgeCount);
|
console.log('🔍 評審數量:', judgeCount);
|
||||||
|
|
||||||
// 獲取競賽的參賽APP數量
|
// 獲取競賽的參賽APP數量
|
||||||
const appsResult = await db.query(`
|
const appsResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT COUNT(DISTINCT ca.app_id) as app_count
|
SELECT COUNT(DISTINCT ca.app_id) as app_count
|
||||||
FROM competition_apps ca
|
FROM competition_apps ca
|
||||||
WHERE ca.competition_id = ?
|
WHERE ca.competition_id = ?
|
||||||
@@ -3685,20 +3686,20 @@ export class ScoringService {
|
|||||||
|
|
||||||
if (judgeCount === 0) {
|
if (judgeCount === 0) {
|
||||||
// 嘗試從 judges 表獲取所有評審
|
// 嘗試從 judges 表獲取所有評審
|
||||||
const allJudgesResult = await db.query('SELECT COUNT(*) as judge_count FROM judges');
|
const allJudgesResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as judge_count FROM judges');
|
||||||
finalJudgeCount = allJudgesResult[0]?.judge_count || 0;
|
finalJudgeCount = allJudgesResult[0]?.judge_count || 0;
|
||||||
console.log('🔍 使用所有評審數量:', finalJudgeCount);
|
console.log('🔍 使用所有評審數量:', finalJudgeCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appCount === 0) {
|
if (appCount === 0) {
|
||||||
// 嘗試從 apps 表獲取所有APP
|
// 嘗試從 apps 表獲取所有APP
|
||||||
const allAppsResult = await db.query('SELECT COUNT(*) as app_count FROM apps');
|
const allAppsResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as app_count FROM apps');
|
||||||
finalAppCount = allAppsResult[0]?.app_count || 0;
|
finalAppCount = allAppsResult[0]?.app_count || 0;
|
||||||
console.log('🔍 使用所有APP數量:', finalAppCount);
|
console.log('🔍 使用所有APP數量:', finalAppCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取已完成的評分數量
|
// 獲取已完成的評分數量
|
||||||
const completedResult = await db.query(`
|
const completedResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT COUNT(*) as completed_count
|
SELECT COUNT(*) as completed_count
|
||||||
FROM judge_scores js
|
FROM judge_scores js
|
||||||
WHERE js.competition_id = ?
|
WHERE js.competition_id = ?
|
||||||
@@ -3755,7 +3756,7 @@ export class ScoringService {
|
|||||||
console.log('🔍 獲取評分完成度匯總,competitionId:', competitionId);
|
console.log('🔍 獲取評分完成度匯總,competitionId:', competitionId);
|
||||||
|
|
||||||
// 獲取競賽的評審列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有評審
|
// 獲取競賽的評審列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有評審
|
||||||
let judgesResult = await db.query(`
|
let judgesResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT j.id, j.name
|
SELECT j.id, j.name
|
||||||
FROM judges j
|
FROM judges j
|
||||||
LEFT JOIN competition_judges cj ON j.id = cj.judge_id
|
LEFT JOIN competition_judges cj ON j.id = cj.judge_id
|
||||||
@@ -3765,13 +3766,13 @@ export class ScoringService {
|
|||||||
|
|
||||||
// 如果沒有關聯的評審,獲取所有評審
|
// 如果沒有關聯的評審,獲取所有評審
|
||||||
if (judgesResult.length === 0) {
|
if (judgesResult.length === 0) {
|
||||||
judgesResult = await db.query(`
|
judgesResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT id, name FROM judges ORDER BY name
|
SELECT id, name FROM judges ORDER BY name
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取競賽的APP列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有APP
|
// 獲取競賽的APP列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有APP
|
||||||
let appsResult = await db.query(`
|
let appsResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT a.id, a.name, t.name as team_name
|
SELECT a.id, a.name, t.name as team_name
|
||||||
FROM apps a
|
FROM apps a
|
||||||
LEFT JOIN competition_apps ca ON a.id = ca.app_id
|
LEFT JOIN competition_apps ca ON a.id = ca.app_id
|
||||||
@@ -3782,7 +3783,7 @@ export class ScoringService {
|
|||||||
|
|
||||||
// 如果沒有關聯的APP,獲取所有APP
|
// 如果沒有關聯的APP,獲取所有APP
|
||||||
if (appsResult.length === 0) {
|
if (appsResult.length === 0) {
|
||||||
appsResult = await db.query(`
|
appsResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT a.id, a.name, t.name as team_name
|
SELECT a.id, a.name, t.name as team_name
|
||||||
FROM apps a
|
FROM apps a
|
||||||
LEFT JOIN teams t ON a.team_id = t.id
|
LEFT JOIN teams t ON a.team_id = t.id
|
||||||
@@ -3791,7 +3792,7 @@ export class ScoringService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 獲取已完成的評分記錄
|
// 獲取已完成的評分記錄
|
||||||
const scoresResult = await db.query(`
|
const scoresResult = await DatabaseServiceBase.safeQuery(`
|
||||||
SELECT js.judge_id, js.app_id, js.total_score, js.submitted_at,
|
SELECT js.judge_id, js.app_id, js.total_score, js.submitted_at,
|
||||||
j.name as judge_name, a.name as app_name
|
j.name as judge_name, a.name as app_name
|
||||||
FROM judge_scores js
|
FROM judge_scores js
|
||||||
@@ -3928,7 +3929,7 @@ export class ScoringService {
|
|||||||
scoreData.comments || null
|
scoreData.comments || null
|
||||||
];
|
];
|
||||||
|
|
||||||
await db.insert(sql, params);
|
await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
return await this.getProposalScore(scoreData.judge_id, scoreData.proposal_id) as ProposalJudgeScore;
|
return await this.getProposalScore(scoreData.judge_id, scoreData.proposal_id) as ProposalJudgeScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4028,7 +4029,7 @@ export class ScoringService {
|
|||||||
|
|
||||||
ORDER BY submitted_at DESC
|
ORDER BY submitted_at DESC
|
||||||
`;
|
`;
|
||||||
return await db.query(sql, [competitionId, competitionId, competitionId]);
|
return await DatabaseServiceBase.safeQuery(sql, [competitionId, competitionId, competitionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交團隊評分(使用應用評分表,但標記為團隊類型)
|
// 提交團隊評分(使用應用評分表,但標記為團隊類型)
|
||||||
@@ -4062,7 +4063,7 @@ export class ScoringService {
|
|||||||
scoreData.comments || null
|
scoreData.comments || null
|
||||||
];
|
];
|
||||||
|
|
||||||
await db.insert(sql, params);
|
await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
return await this.getAppScore(scoreData.judge_id, virtualAppId) as AppJudgeScore;
|
return await this.getAppScore(scoreData.judge_id, virtualAppId) as AppJudgeScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4180,14 +4181,14 @@ export class ScoringService {
|
|||||||
|
|
||||||
sql += ' ORDER BY submitted_at DESC';
|
sql += ' ORDER BY submitted_at DESC';
|
||||||
|
|
||||||
return await db.query(sql, params);
|
return await DatabaseServiceBase.safeQuery(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刪除評分記錄
|
// 刪除評分記錄
|
||||||
static async deleteScore(scoreId: string, scoreType: 'app' | 'proposal'): Promise<boolean> {
|
static async deleteScore(scoreId: string, scoreType: 'app' | 'proposal'): Promise<boolean> {
|
||||||
const tableName = scoreType === 'app' ? 'app_judge_scores' : 'proposal_judge_scores';
|
const tableName = scoreType === 'app' ? 'app_judge_scores' : 'proposal_judge_scores';
|
||||||
const sql = `DELETE FROM ${tableName} WHERE id = ?`;
|
const sql = `DELETE FROM ${tableName} WHERE id = ?`;
|
||||||
const result = await db.delete(sql, [scoreId]);
|
const result = await DatabaseServiceBase.safeDelete(sql, [scoreId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4206,7 +4207,7 @@ export class ScoringService {
|
|||||||
const values = fields.map(field => (updates as any)[field]);
|
const values = fields.map(field => (updates as any)[field]);
|
||||||
|
|
||||||
const sql = `UPDATE app_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
const sql = `UPDATE app_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||||
const result = await db.update(sql, [...values, scoreId]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [...values, scoreId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4225,7 +4226,7 @@ export class ScoringService {
|
|||||||
const values = fields.map(field => (updates as any)[field]);
|
const values = fields.map(field => (updates as any)[field]);
|
||||||
|
|
||||||
const sql = `UPDATE proposal_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
const sql = `UPDATE proposal_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||||
const result = await db.update(sql, [...values, scoreId]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [...values, scoreId]);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4233,7 +4234,7 @@ export class ScoringService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 獎項服務
|
// 獎項服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class AwardService {
|
export class AwardService extends DatabaseServiceBase {
|
||||||
// 創建獎項
|
// 創建獎項
|
||||||
static async createAward(awardData: Omit<Award, 'id' | 'created_at'>): Promise<Award> {
|
static async createAward(awardData: Omit<Award, 'id' | 'created_at'>): Promise<Award> {
|
||||||
const sql = `
|
const sql = `
|
||||||
@@ -4261,7 +4262,7 @@ export class AwardService {
|
|||||||
awardData.category
|
awardData.category
|
||||||
];
|
];
|
||||||
|
|
||||||
await db.insert(sql, params);
|
await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
return await this.getAwardByCompetitionAndCreator(awardData.competition_id, awardData.creator) as Award;
|
return await this.getAwardByCompetitionAndCreator(awardData.competition_id, awardData.creator) as Award;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4287,7 +4288,7 @@ export class AwardService {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// 系統設定服務
|
// 系統設定服務
|
||||||
// =====================================================
|
// =====================================================
|
||||||
export class SystemSettingService {
|
export class SystemSettingService extends DatabaseServiceBase {
|
||||||
// 獲取設定值
|
// 獲取設定值
|
||||||
static async getSetting(key: string): Promise<string | null> {
|
static async getSetting(key: string): Promise<string | null> {
|
||||||
const sql = 'SELECT value FROM system_settings WHERE `key` = ?';
|
const sql = 'SELECT value FROM system_settings WHERE `key` = ?';
|
||||||
@@ -4309,7 +4310,7 @@ export class SystemSettingService {
|
|||||||
`;
|
`;
|
||||||
const params = [key, value, description || null, category, isPublic];
|
const params = [key, value, description || null, category, isPublic];
|
||||||
|
|
||||||
const result = await db.insert(sql, params);
|
const result = await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user