This commit is contained in:
2025-08-01 00:55:05 +08:00
commit d96b0a511d
96 changed files with 14825 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
// 檢查資料庫結構
require('dotenv').config({ path: '.env.local' })
const mysql = require('mysql2/promise')
async function checkDatabaseStructure() {
const config = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
}
try {
const connection = await mysql.createConnection(config)
console.log('🔗 連接到 MySQL 資料庫...')
// 檢查所有表
console.log('\n📋 檢查資料庫表結構...')
const [tables] = await connection.execute(`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
`, [config.database])
console.log(`\n📊 找到 ${tables.length} 個表:`)
for (const table of tables) {
const tableName = table.table_name
console.log(`\n🔍 表: ${tableName}`)
if (!tableName) {
console.log(' ⚠️ 表名為空,跳過')
continue
}
// 檢查表結構
const [columns] = await connection.execute(`
SELECT column_name, data_type, is_nullable, column_default, column_key
FROM information_schema.columns
WHERE table_schema = ? AND table_name = ?
ORDER BY ordinal_position
`, [config.database, tableName])
console.log(' 欄位:')
columns.forEach(col => {
const key = col.column_key ? ` (${col.column_key})` : ''
const nullable = col.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'
console.log(` - ${col.column_name}: ${col.data_type} ${nullable}${key}`)
})
// 檢查外鍵
const [foreignKeys] = await connection.execute(`
SELECT
constraint_name,
column_name,
referenced_table_name,
referenced_column_name
FROM information_schema.key_column_usage
WHERE table_schema = ?
AND table_name = ?
AND referenced_table_name IS NOT NULL
`, [config.database, tableName])
if (foreignKeys.length > 0) {
console.log(' 外鍵:')
foreignKeys.forEach(fk => {
console.log(` - ${fk.constraint_name}: ${fk.column_name} -> ${fk.referenced_table_name}.${fk.referenced_column_name}`)
})
}
// 檢查索引
const [indexes] = await connection.execute(`
SELECT index_name, column_name, non_unique
FROM information_schema.statistics
WHERE table_schema = ? AND table_name = ?
ORDER BY index_name, seq_in_index
`, [config.database, tableName])
if (indexes.length > 0) {
console.log(' 索引:')
indexes.forEach(idx => {
const unique = idx.non_unique === 0 ? 'UNIQUE' : ''
console.log(` - ${idx.index_name}: ${idx.column_name} ${unique}`)
})
}
}
await connection.end()
console.log('\n✅ 資料庫結構檢查完成')
} catch (error) {
console.error('❌ 檢查資料庫結構失敗:', error)
}
}
checkDatabaseStructure()

21
scripts/check-env.js Normal file
View File

@@ -0,0 +1,21 @@
// 檢查環境變數
require('dotenv').config({ path: '.env.local' })
console.log('🔍 檢查環境變數...')
console.log('DB_HOST:', process.env.DB_HOST)
console.log('DB_PORT:', process.env.DB_PORT)
console.log('DB_DATABASE:', process.env.DB_DATABASE)
console.log('DB_USERNAME:', process.env.DB_USERNAME)
console.log('DB_PASSWORD:', process.env.DB_PASSWORD ? '***已設定***' : '未設定')
if (!process.env.DB_HOST || !process.env.DB_PORT || !process.env.DB_DATABASE || !process.env.DB_USERNAME || !process.env.DB_PASSWORD) {
console.log('❌ 環境變數未完整設定')
console.log('💡 請創建 .env.local 檔案並設定以下變數:')
console.log('DB_HOST=mysql.theaken.com')
console.log('DB_PORT=33306')
console.log('DB_DATABASE=db_A022')
console.log('DB_USERNAME=A022')
console.log('DB_PASSWORD=NNyTXRWWtP4b')
} else {
console.log('✅ 環境變數設定完整')
}

View File

@@ -0,0 +1,171 @@
-- 創建 MySQL 資料庫 Schema
-- 執行管理績效儀表板系統
-- 用戶表
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
role ENUM('executive', 'manager', 'hr') NOT NULL,
avatar_url VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_role (role)
);
-- KPI 表
CREATE TABLE IF NOT EXISTS kpi (
id VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
category ENUM('financial', 'operational', 'team', 'innovation', 'strategy') NOT NULL,
weight INT NOT NULL DEFAULT 1,
target_value DECIMAL(15,2) NOT NULL,
current_value DECIMAL(15,2) DEFAULT 0,
unit VARCHAR(50),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
approved_by VARCHAR(255),
status ENUM('pending', 'approved', 'rejected', 'active', 'completed', 'deleted') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_category (category),
INDEX idx_date_range (start_date, end_date)
);
-- KPI 進度追蹤表
CREATE TABLE IF NOT EXISTS kpi_progress (
id VARCHAR(255) PRIMARY KEY,
kpi_id VARCHAR(255) NOT NULL,
progress DECIMAL(15,2) NOT NULL,
comment TEXT,
attachment_url VARCHAR(500),
blockers TEXT,
created_by VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (kpi_id) REFERENCES kpi(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_kpi_id (kpi_id),
INDEX idx_created_by (created_by),
INDEX idx_created_at (created_at)
);
-- 評審表
CREATE TABLE IF NOT EXISTS reviews (
id VARCHAR(255) PRIMARY KEY,
reviewer_id VARCHAR(255) NOT NULL,
reviewee_id VARCHAR(255) NOT NULL,
review_type ENUM('quarterly', 'annual', 'one_on_one', 'goal_setting') NOT NULL,
scheduled_date DATETIME NOT NULL,
completed_date DATETIME,
status ENUM('scheduled', 'in_progress', 'completed', 'cancelled') DEFAULT 'scheduled',
summary TEXT,
overall_rating DECIMAL(3,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (reviewer_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (reviewee_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_reviewer_id (reviewer_id),
INDEX idx_reviewee_id (reviewee_id),
INDEX idx_scheduled_date (scheduled_date),
INDEX idx_status (status),
INDEX idx_review_type (review_type)
);
-- 評審回應表
CREATE TABLE IF NOT EXISTS review_responses (
id VARCHAR(255) PRIMARY KEY,
review_id VARCHAR(255) NOT NULL,
question_id VARCHAR(255) NOT NULL,
question_text TEXT NOT NULL,
response_text TEXT,
score INT CHECK(score >= 1 AND score <= 5),
question_type ENUM('text', 'rating', 'multiple_choice') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (review_id) REFERENCES reviews(id) ON DELETE CASCADE,
INDEX idx_review_id (review_id)
);
-- KPI 模板表(供 HR 管理)
CREATE TABLE IF NOT EXISTS kpi_templates (
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
category ENUM('financial', 'operational', 'team', 'innovation', 'strategy'),
default_weight INT,
smart_criteria JSON,
created_by VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_category (category),
INDEX idx_is_active (is_active)
);
-- 評審問題模板
CREATE TABLE IF NOT EXISTS review_question_templates (
id VARCHAR(255) PRIMARY KEY,
question_text TEXT NOT NULL,
question_type ENUM('text', 'rating', 'multiple_choice') NOT NULL,
category VARCHAR(100),
is_required BOOLEAN DEFAULT FALSE,
options JSON,
created_by VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_category (category),
INDEX idx_is_active (is_active)
);
-- 初始化示例數據
INSERT IGNORE INTO users (id, name, email, role) VALUES
('user_admin', '系統管理員', 'admin@company.com', 'executive'),
('user_hr_1', 'HR 經理', 'hr@company.com', 'hr'),
('user_mgr_1', '部門經理', 'manager@company.com', 'manager'),
('user_exec_1', '執行長', 'ceo@company.com', 'executive'),
('user_exec_2', '營運長', 'coo@company.com', 'executive');
-- 插入示例 KPI 模板
INSERT IGNORE INTO kpi_templates (id, name, description, category, default_weight) VALUES
('template_1', '營收成長', '年對年營收成長百分比', 'financial', 30),
('template_2', '團隊滿意度', '員工滿意度調查分數', 'team', 25),
('template_3', '市場佔有率', '公司市場佔有率百分比', 'operational', 20),
('template_4', '創新指數', '推出的新計畫數量', 'innovation', 25);
-- 插入示例評審問題模板
INSERT IGNORE INTO review_question_templates (id, question_text, question_type, category, is_required) VALUES
('q1', '您如何評價這個季度的整體表現?', 'rating', 'performance', TRUE),
('q2', '這個期間您的主要成就是什麼?', 'text', 'performance', TRUE),
('q3', '您面臨哪些挑戰,如何克服?', 'text', 'challenges', FALSE),
('q4', '您領導團隊的效率如何?', 'rating', 'leadership', TRUE),
('q5', '下個季度您的目標是什麼?', 'text', 'goals', TRUE);
-- 創建視圖以便更容易查詢
CREATE OR REPLACE VIEW kpi_with_user AS
SELECT
k.*,
u.name as user_name,
u.email as user_email,
u.role as user_role,
approver.name as approver_name
FROM kpi k
LEFT JOIN users u ON k.user_id = u.id
LEFT JOIN users approver ON k.approved_by = approver.id;
CREATE OR REPLACE VIEW reviews_with_users AS
SELECT
r.*,
reviewer.name as reviewer_name,
reviewer.email as reviewer_email,
reviewee.name as reviewee_name,
reviewee.email as reviewee_email
FROM reviews r
LEFT JOIN users reviewer ON r.reviewer_id = reviewer.id
LEFT JOIN users reviewee ON r.reviewee_id = reviewee.id;

275
scripts/init-database.js Normal file
View File

@@ -0,0 +1,275 @@
// 資料庫初始化腳本
require('dotenv').config({ path: '.env.local' })
const mysql = require('mysql2/promise')
const fs = require('fs')
const path = require('path')
async function initDatabase() {
const config = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
}
console.log('🔗 連接到 MySQL 資料庫...')
console.log(`主機: ${config.host}:${config.port}`)
console.log(`資料庫: ${config.database}`)
console.log(`用戶: ${config.user}`)
try {
const connection = await mysql.createConnection(config)
console.log('✅ 資料庫連接成功!')
// 讀取 SQL schema 檔案
const schemaPath = path.join(__dirname, 'create-database-schema.sql')
const schemaSQL = fs.readFileSync(schemaPath, 'utf8')
// 執行 schema
console.log('📋 執行資料庫 schema...')
const statements = schemaSQL.split(';').filter(stmt => stmt.trim())
for (const statement of statements) {
if (statement.trim()) {
await connection.execute(statement)
}
}
console.log('✅ Schema 執行完成')
// 插入測試數據
console.log('📊 插入測試數據...')
// 插入用戶數據
const users = [
{
id: 'user_admin',
name: '系統管理員',
email: 'admin@company.com',
role: 'executive',
avatar_url: null
},
{
id: 'user_001',
name: '陳雅雯',
email: 'chen@company.com',
role: 'manager',
avatar_url: null
},
{
id: 'user_002',
name: '王志明',
email: 'wang@company.com',
role: 'manager',
avatar_url: null
},
{
id: 'user_003',
name: '李美玲',
email: 'li@company.com',
role: 'manager',
avatar_url: null
},
{
id: 'user_004',
name: '張建國',
email: 'zhang@company.com',
role: 'manager',
avatar_url: null
}
]
for (const user of users) {
await connection.execute(
'INSERT IGNORE INTO users (id, name, email, role, avatar_url) VALUES (?, ?, ?, ?, ?)',
[user.id, user.name, user.email, user.role, user.avatar_url]
)
}
console.log('✅ 用戶數據插入完成')
// 插入 KPI 數據
const kpis = [
{
id: 'kpi_001',
user_id: 'user_001',
title: '營收成長率',
description: '年度營收成長百分比目標',
category: 'financial',
weight: 30,
target_value: 100,
current_value: 85,
unit: '%',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'active'
},
{
id: 'kpi_002',
user_id: 'user_002',
title: '團隊滿意度',
description: '員工滿意度調查分數',
category: 'team',
weight: 25,
target_value: 90,
current_value: 92,
unit: '分',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'active'
},
{
id: 'kpi_003',
user_id: 'user_003',
title: '市場佔有率',
description: '公司在目標市場的佔有率',
category: 'operational',
weight: 20,
target_value: 75,
current_value: 68,
unit: '%',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'active'
},
{
id: 'kpi_004',
user_id: 'user_004',
title: '創新指數',
description: '新產品開發和創新項目數量',
category: 'innovation',
weight: 25,
target_value: 80,
current_value: 45,
unit: '項',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'at_risk'
}
]
for (const kpi of kpis) {
await connection.execute(
`INSERT IGNORE INTO kpi (
id, user_id, title, description, category, weight,
target_value, current_value, unit, start_date, end_date,
approved_by, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
kpi.id, kpi.user_id, kpi.title, kpi.description, kpi.category,
kpi.weight, kpi.target_value, kpi.current_value, kpi.unit,
kpi.start_date, kpi.end_date, kpi.approved_by, kpi.status
]
)
}
console.log('✅ KPI 數據插入完成')
// 插入評審數據
const reviews = [
{
id: 'review_001',
reviewer_id: 'user_admin',
reviewee_id: 'user_001',
review_type: 'quarterly',
scheduled_date: '2024-03-15 14:00:00',
status: 'scheduled',
notes: 'Q1 績效評審'
},
{
id: 'review_002',
reviewer_id: 'user_admin',
reviewee_id: 'user_002',
review_type: 'quarterly',
scheduled_date: '2024-03-20 10:00:00',
status: 'scheduled',
notes: 'Q1 績效評審'
}
]
for (const review of reviews) {
await connection.execute(
`INSERT IGNORE INTO reviews (
id, reviewer_id, reviewee_id, review_type,
scheduled_date, status, notes
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
review.id, review.reviewer_id, review.reviewee_id,
review.review_type, review.scheduled_date, review.status, review.notes
]
)
}
console.log('✅ 評審數據插入完成')
// 插入 KPI 進度數據
const progressData = [
{
kpi_id: 'kpi_001',
progress: 85,
comment: 'Q1 表現良好,預計 Q2 可達標',
blockers: null,
created_by: 'user_001'
},
{
kpi_id: 'kpi_002',
progress: 92,
comment: '團隊滿意度超出預期',
blockers: null,
created_by: 'user_002'
},
{
kpi_id: 'kpi_003',
progress: 68,
comment: '市場競爭激烈,需要調整策略',
blockers: '競爭對手價格戰',
created_by: 'user_003'
},
{
kpi_id: 'kpi_004',
progress: 45,
comment: '創新項目進度落後,需要加速',
blockers: '研發資源不足',
created_by: 'user_004'
}
]
for (const progress of progressData) {
await connection.execute(
`INSERT IGNORE INTO kpi_progress (
kpi_id, progress, comment, blockers, created_by
) VALUES (?, ?, ?, ?, ?)`,
[
progress.kpi_id, progress.progress, progress.comment,
progress.blockers, progress.created_by
]
)
}
console.log('✅ KPI 進度數據插入完成')
// 驗證數據
console.log('🔍 驗證數據...')
const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users')
const [kpiCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi')
const [reviewCount] = await connection.execute('SELECT COUNT(*) as count FROM reviews')
const [progressCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi_progress')
console.log(`📊 數據統計:`)
console.log(` - 用戶: ${userCount[0].count}`)
console.log(` - KPI: ${kpiCount[0].count}`)
console.log(` - 評審: ${reviewCount[0].count}`)
console.log(` - 進度記錄: ${progressCount[0].count}`)
await connection.end()
console.log('🎉 資料庫初始化完成!')
console.log('💡 您現在可以啟動應用程式並測試功能')
} catch (error) {
console.error('❌ 資料庫初始化失敗:', error)
process.exit(1)
}
}
// 執行初始化
initDatabase()

296
scripts/reset-database.js Normal file
View File

@@ -0,0 +1,296 @@
// 重置資料庫腳本
require('dotenv').config({ path: '.env.local' })
const mysql = require('mysql2/promise')
const fs = require('fs')
const path = require('path')
async function resetDatabase() {
const config = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
}
console.log('🔗 連接到 MySQL 資料庫...')
console.log(`主機: ${config.host}:${config.port}`)
console.log(`資料庫: ${config.database}`)
console.log(`用戶: ${config.user}`)
try {
const connection = await mysql.createConnection(config)
console.log('✅ 資料庫連接成功!')
// 刪除現有的表(如果存在)
console.log('\n🗑 清理現有表...')
const dropTables = [
'DROP TABLE IF EXISTS review_responses',
'DROP TABLE IF EXISTS kpi_progress',
'DROP TABLE IF EXISTS kpi',
'DROP TABLE IF EXISTS reviews',
'DROP TABLE IF EXISTS users'
]
for (const dropSQL of dropTables) {
try {
await connection.execute(dropSQL)
console.log(` ✅ 刪除表: ${dropSQL.split(' ')[4]}`)
} catch (error) {
console.log(` ⚠️ 刪除表失敗: ${error.message}`)
}
}
// 讀取並執行 schema
console.log('\n📋 創建新的資料庫結構...')
const schemaPath = path.join(__dirname, 'create-database-schema.sql')
const schemaSQL = fs.readFileSync(schemaPath, 'utf8')
const statements = schemaSQL.split(';').filter(stmt => stmt.trim())
for (const statement of statements) {
if (statement.trim()) {
try {
await connection.execute(statement)
console.log(' ✅ 執行 SQL 語句成功')
} catch (error) {
console.log(` ❌ SQL 執行失敗: ${error.message}`)
}
}
}
// 插入測試數據
console.log('\n📊 插入測試數據...')
// 插入用戶數據
const users = [
{
id: 'user_admin',
name: '系統管理員',
email: 'admin@company.com',
role: 'executive',
avatar_url: null
},
{
id: 'user_001',
name: '陳雅雯',
email: 'chen@company.com',
role: 'manager',
avatar_url: null
},
{
id: 'user_002',
name: '王志明',
email: 'wang@company.com',
role: 'manager',
avatar_url: null
},
{
id: 'user_003',
name: '李美玲',
email: 'li@company.com',
role: 'manager',
avatar_url: null
},
{
id: 'user_004',
name: '張建國',
email: 'zhang@company.com',
role: 'manager',
avatar_url: null
}
]
for (const user of users) {
await connection.execute(
'INSERT IGNORE INTO users (id, name, email, role, avatar_url) VALUES (?, ?, ?, ?, ?)',
[user.id, user.name, user.email, user.role, user.avatar_url]
)
}
console.log('✅ 用戶數據插入完成')
// 插入 KPI 數據
const kpis = [
{
id: 'kpi_001',
user_id: 'user_001',
title: '營收成長率',
description: '年度營收成長百分比目標',
category: 'financial',
weight: 30,
target_value: 100,
current_value: 85,
unit: '%',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'active'
},
{
id: 'kpi_002',
user_id: 'user_002',
title: '團隊滿意度',
description: '員工滿意度調查分數',
category: 'team',
weight: 25,
target_value: 90,
current_value: 92,
unit: '分',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'active'
},
{
id: 'kpi_003',
user_id: 'user_003',
title: '市場佔有率',
description: '公司在目標市場的佔有率',
category: 'operational',
weight: 20,
target_value: 75,
current_value: 68,
unit: '%',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'active'
},
{
id: 'kpi_004',
user_id: 'user_004',
title: '創新指數',
description: '新產品開發和創新項目數量',
category: 'innovation',
weight: 25,
target_value: 80,
current_value: 45,
unit: '項',
start_date: '2024-01-01',
end_date: '2024-12-31',
approved_by: 'user_admin',
status: 'at_risk'
}
]
for (const kpi of kpis) {
await connection.execute(
`INSERT IGNORE INTO kpi (
id, user_id, title, description, category, weight,
target_value, current_value, unit, start_date, end_date,
approved_by, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
kpi.id, kpi.user_id, kpi.title, kpi.description, kpi.category,
kpi.weight, kpi.target_value, kpi.current_value, kpi.unit,
kpi.start_date, kpi.end_date, kpi.approved_by, kpi.status
]
)
}
console.log('✅ KPI 數據插入完成')
// 插入評審數據
const reviews = [
{
id: 'review_001',
reviewer_id: 'user_admin',
reviewee_id: 'user_001',
review_type: 'quarterly',
scheduled_date: '2024-03-15 14:00:00',
status: 'scheduled',
notes: 'Q1 績效評審'
},
{
id: 'review_002',
reviewer_id: 'user_admin',
reviewee_id: 'user_002',
review_type: 'quarterly',
scheduled_date: '2024-03-20 10:00:00',
status: 'scheduled',
notes: 'Q1 績效評審'
}
]
for (const review of reviews) {
await connection.execute(
`INSERT IGNORE INTO reviews (
id, reviewer_id, reviewee_id, review_type,
scheduled_date, status, summary
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
review.id, review.reviewer_id, review.reviewee_id,
review.review_type, review.scheduled_date, review.status, review.notes
]
)
}
console.log('✅ 評審數據插入完成')
// 插入 KPI 進度數據
const progressData = [
{
kpi_id: 'kpi_001',
progress: 85,
comment: 'Q1 表現良好,預計 Q2 可達標',
blockers: null,
created_by: 'user_001'
},
{
kpi_id: 'kpi_002',
progress: 92,
comment: '團隊滿意度超出預期',
blockers: null,
created_by: 'user_002'
},
{
kpi_id: 'kpi_003',
progress: 68,
comment: '市場競爭激烈,需要調整策略',
blockers: '競爭對手價格戰',
created_by: 'user_003'
},
{
kpi_id: 'kpi_004',
progress: 45,
comment: '創新項目進度落後,需要加速',
blockers: '研發資源不足',
created_by: 'user_004'
}
]
for (const progress of progressData) {
await connection.execute(
`INSERT IGNORE INTO kpi_progress (
kpi_id, progress, comment, blockers, created_by
) VALUES (?, ?, ?, ?, ?)`,
[
progress.kpi_id, progress.progress, progress.comment,
progress.blockers, progress.created_by
]
)
}
console.log('✅ KPI 進度數據插入完成')
// 驗證數據
console.log('\n🔍 驗證數據...')
const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users')
const [kpiCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi')
const [reviewCount] = await connection.execute('SELECT COUNT(*) as count FROM reviews')
const [progressCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi_progress')
console.log(`📊 數據統計:`)
console.log(` - 用戶: ${userCount[0].count}`)
console.log(` - KPI: ${kpiCount[0].count}`)
console.log(` - 評審: ${reviewCount[0].count}`)
console.log(` - 進度記錄: ${progressCount[0].count}`)
await connection.end()
console.log('\n🎉 資料庫重置完成!')
console.log('💡 您現在可以啟動應用程式並測試功能')
} catch (error) {
console.error('❌ 資料庫重置失敗:', error)
process.exit(1)
}
}
resetDatabase()

27
scripts/setup-env.js Normal file
View File

@@ -0,0 +1,27 @@
// 環境變數設定腳本
const fs = require('fs')
const path = require('path')
const envContent = `# MySQL 資料庫設定
DB_HOST=mysql.theaken.com
DB_PORT=33306
DB_DATABASE=db_A022
DB_USERNAME=A022
DB_PASSWORD=NNyTXRWWtP4b
# Next.js 設定
NEXT_PUBLIC_APP_URL=http://localhost:3006
`
const envPath = path.join(__dirname, '..', '.env.local')
try {
fs.writeFileSync(envPath, envContent)
console.log('✅ .env.local 檔案已創建')
console.log('📁 檔案位置:', envPath)
console.log('🔐 請確認資料庫連接資訊正確')
} catch (error) {
console.error('❌ 創建 .env.local 檔案失敗:', error.message)
console.log('💡 請手動創建 .env.local 檔案並添加以下內容:')
console.log(envContent)
}

View File

@@ -0,0 +1,52 @@
// 簡單的資料庫檢查
require('dotenv').config({ path: '.env.local' })
const mysql = require('mysql2/promise')
async function simpleCheck() {
const config = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
}
try {
const connection = await mysql.createConnection(config)
console.log('🔗 連接到 MySQL 資料庫...')
// 直接查詢表
console.log('\n📋 檢查資料庫表...')
const [tables] = await connection.execute('SHOW TABLES')
console.log(`\n📊 找到 ${tables.length} 個表:`)
tables.forEach((table, index) => {
const tableName = Object.values(table)[0]
console.log(` ${index + 1}. ${tableName}`)
})
// 檢查每個表的結構
for (const table of tables) {
const tableName = Object.values(table)[0]
console.log(`\n🔍 表結構: ${tableName}`)
try {
const [columns] = await connection.execute(`DESCRIBE ${tableName}`)
console.log(' 欄位:')
columns.forEach(col => {
console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'YES' ? 'NULL' : 'NOT NULL'} ${col.Key || ''}`)
})
} catch (error) {
console.log(` ❌ 無法讀取表結構: ${error.message}`)
}
}
await connection.end()
console.log('\n✅ 資料庫檢查完成')
} catch (error) {
console.error('❌ 資料庫檢查失敗:', error)
}
}
simpleCheck()

101
scripts/test-api.js Normal file
View File

@@ -0,0 +1,101 @@
// API 測試腳本
const http = require('http')
async function testAPI() {
console.log('🧪 開始測試 API 端點...\n')
// 測試資料庫連接
console.log('1⃣ 測試資料庫連接...')
try {
const dbResponse = await makeRequest('/api/database/test')
console.log(' ✅ 資料庫連接正常')
console.log(` 📊 回應: ${JSON.stringify(dbResponse, null, 2)}`)
} catch (error) {
console.log(' ❌ 資料庫連接失敗:', error.message)
}
// 測試 KPI API
console.log('\n2⃣ 測試 KPI API...')
try {
const kpiResponse = await makeRequest('/api/kpi?userId=user_001')
console.log(' ✅ KPI API 正常')
console.log(` 📊 找到 ${kpiResponse.length} 個 KPI`)
kpiResponse.forEach(kpi => {
console.log(` - ${kpi.title}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit}`)
})
} catch (error) {
console.log(' ❌ KPI API 失敗:', error.message)
}
// 測試用戶 API
console.log('\n3⃣ 測試用戶 API...')
try {
const usersResponse = await makeRequest('/api/users')
console.log(' ✅ 用戶 API 正常')
console.log(` 📊 找到 ${usersResponse.length} 個用戶`)
usersResponse.slice(0, 3).forEach(user => {
console.log(` - ${user.name} (${user.role})`)
})
} catch (error) {
console.log(' ❌ 用戶 API 失敗:', error.message)
}
// 測試評審 API
console.log('\n4⃣ 測試評審 API...')
try {
const reviewsResponse = await makeRequest('/api/reviews')
console.log(' ✅ 評審 API 正常')
console.log(` 📊 找到 ${reviewsResponse.length} 個評審`)
reviewsResponse.forEach(review => {
console.log(` - ${review.review_type} 評審 (${review.status})`)
})
} catch (error) {
console.log(' ❌ 評審 API 失敗:', error.message)
}
console.log('\n🎉 API 測試完成!')
}
function makeRequest(path) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'localhost',
port: 3006,
path: path,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
const req = http.request(options, (res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
try {
const jsonData = JSON.parse(data)
resolve(jsonData)
} catch (error) {
reject(new Error(`解析回應失敗: ${error.message}`))
}
})
})
req.on('error', (error) => {
reject(new Error(`請求失敗: ${error.message}`))
})
req.setTimeout(5000, () => {
req.destroy()
reject(new Error('請求超時'))
})
req.end()
})
}
testAPI().catch(console.error)

View File

@@ -0,0 +1,51 @@
// MySQL 資料庫連接測試腳本
require('dotenv').config({ path: '.env.local' })
const mysql = require('mysql2/promise')
async function testConnection() {
const config = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
}
console.log('🔗 嘗試連接到 MySQL 資料庫...')
console.log(`主機: ${config.host}:${config.port}`)
console.log(`資料庫: ${config.database}`)
console.log(`用戶: ${config.user}`)
try {
const connection = await mysql.createConnection(config)
console.log('✅ MySQL 資料庫連接成功!')
// 測試基本查詢
const [rows] = await connection.execute('SELECT VERSION() as version')
console.log(`📊 MySQL 版本: ${rows[0].version}`)
// 檢查資料庫表
const [tables] = await connection.execute(`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
`, [config.database])
console.log(`📋 資料庫表數量: ${tables.length}`)
if (tables.length > 0) {
console.log('現有表:')
tables.forEach(table => {
console.log(` - ${table.table_name}`)
})
} else {
console.log('⚠️ 沒有找到表,您可能需要執行 schema 創建腳本')
}
await connection.end()
} catch (error) {
console.error('❌ 資料庫連接失敗:', error.message)
process.exit(1)
}
}
testConnection()

64
scripts/test-kpi-data.js Normal file
View File

@@ -0,0 +1,64 @@
// 測試 KPI 數據
require('dotenv').config({ path: '.env.local' })
const mysql = require('mysql2/promise')
async function testKPIData() {
const config = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
}
try {
const connection = await mysql.createConnection(config)
console.log('🔗 連接到 MySQL 資料庫...')
// 檢查用戶數據
console.log('\n👥 檢查用戶數據...')
const [users] = await connection.execute('SELECT * FROM users')
console.log(`找到 ${users.length} 個用戶:`)
users.forEach(user => {
console.log(` - ${user.id}: ${user.name} (${user.role})`)
})
// 檢查 KPI 數據
console.log('\n📊 檢查 KPI 數據...')
const [kpis] = await connection.execute('SELECT * FROM kpi')
console.log(`找到 ${kpis.length} 個 KPI:`)
kpis.forEach(kpi => {
console.log(` - ${kpi.id}: ${kpi.title} (${kpi.status}) - 用戶: ${kpi.user_id}`)
})
// 測試特定用戶的 KPI
console.log('\n🔍 測試 user_001 的 KPI...')
const [userKPIs] = await connection.execute(
'SELECT * FROM kpi WHERE user_id = ?',
['user_001']
)
console.log(`user_001 有 ${userKPIs.length} 個 KPI:`)
userKPIs.forEach(kpi => {
console.log(` - ${kpi.title}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit} (${kpi.status})`)
})
// 測試活躍狀態的 KPI
console.log('\n🔍 測試活躍狀態的 KPI...')
const [activeKPIs] = await connection.execute(
'SELECT * FROM kpi WHERE status = ?',
['active']
)
console.log(`${activeKPIs.length} 個活躍的 KPI:`)
activeKPIs.forEach(kpi => {
console.log(` - ${kpi.title} (${kpi.user_id}): ${kpi.status}`)
})
await connection.end()
console.log('\n✅ KPI 數據測試完成')
} catch (error) {
console.error('❌ KPI 數據測試失敗:', error)
}
}
testKPIData()