新增評分項目設定、資料庫整合
This commit is contained in:
78
lib/database.ts
Normal file
78
lib/database.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
// 資料庫配置
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || 'mysql.theaken.com',
|
||||
port: parseInt(process.env.DB_PORT || '33306'),
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'zh6161168',
|
||||
database: process.env.DB_NAME || 'db_AI_scoring',
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
acquireTimeout: 60000,
|
||||
timeout: 60000,
|
||||
reconnect: true,
|
||||
multipleStatements: true,
|
||||
};
|
||||
|
||||
// 建立連接池
|
||||
const pool = mysql.createPool({
|
||||
...dbConfig,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
});
|
||||
|
||||
// 資料庫連接函數
|
||||
export async function getConnection() {
|
||||
try {
|
||||
const connection = await pool.getConnection();
|
||||
return connection;
|
||||
} catch (error) {
|
||||
console.error('資料庫連接失敗:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 執行查詢函數
|
||||
export async function query(sql: string, params?: any[]) {
|
||||
try {
|
||||
const [rows] = await pool.execute(sql, params);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
console.error('查詢執行失敗:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 執行事務函數
|
||||
export async function transaction(callback: (connection: mysql.PoolConnection) => Promise<any>) {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
const result = await callback(connection);
|
||||
await connection.commit();
|
||||
return result;
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 測試資料庫連接
|
||||
export async function testConnection() {
|
||||
try {
|
||||
const connection = await getConnection();
|
||||
await connection.ping();
|
||||
connection.release();
|
||||
console.log('✅ 資料庫連接成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 資料庫連接失敗:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default pool;
|
141
lib/models/index.ts
Normal file
141
lib/models/index.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
// 資料庫模型定義
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
username: string;
|
||||
password_hash: string;
|
||||
full_name?: string;
|
||||
avatar_url?: string;
|
||||
role: 'admin' | 'user';
|
||||
is_active: boolean;
|
||||
email_verified_at?: Date;
|
||||
last_login_at?: Date;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface CriteriaTemplate {
|
||||
id: number;
|
||||
user_id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
is_default: boolean;
|
||||
is_public: boolean;
|
||||
total_weight: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface CriteriaItem {
|
||||
id: number;
|
||||
template_id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
weight: number;
|
||||
max_score: number;
|
||||
sort_order: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: number;
|
||||
user_id: number;
|
||||
template_id: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
status: 'draft' | 'uploading' | 'analyzing' | 'completed' | 'failed';
|
||||
analysis_started_at?: Date;
|
||||
analysis_completed_at?: Date;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface ProjectFile {
|
||||
id: number;
|
||||
project_id: number;
|
||||
original_name: string;
|
||||
file_name: string;
|
||||
file_path: string;
|
||||
file_size: number;
|
||||
file_type: string;
|
||||
mime_type: string;
|
||||
upload_status: 'uploading' | 'completed' | 'failed';
|
||||
upload_progress: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface ProjectWebsite {
|
||||
id: number;
|
||||
project_id: number;
|
||||
url: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
status: 'pending' | 'analyzing' | 'completed' | 'failed';
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface Evaluation {
|
||||
id: number;
|
||||
project_id: number;
|
||||
overall_score?: number;
|
||||
max_possible_score: number;
|
||||
grade?: string;
|
||||
analysis_duration?: number;
|
||||
ai_model_version?: string;
|
||||
status: 'pending' | 'analyzing' | 'completed' | 'failed';
|
||||
error_message?: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface EvaluationScore {
|
||||
id: number;
|
||||
evaluation_id: number;
|
||||
criteria_item_id: number;
|
||||
score: number;
|
||||
max_score: number;
|
||||
weight: number;
|
||||
weighted_score: number;
|
||||
percentage: number;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface EvaluationFeedback {
|
||||
id: number;
|
||||
evaluation_id: number;
|
||||
criteria_item_id?: number;
|
||||
feedback_type: 'overall' | 'criteria' | 'strength' | 'improvement';
|
||||
content: string;
|
||||
sort_order: number;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface SystemSetting {
|
||||
id: number;
|
||||
setting_key: string;
|
||||
setting_value?: string;
|
||||
description?: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
// 查詢結果類型
|
||||
export interface ProjectWithDetails extends Project {
|
||||
template: CriteriaTemplate;
|
||||
files: ProjectFile[];
|
||||
websites: ProjectWebsite[];
|
||||
evaluation?: Evaluation;
|
||||
}
|
||||
|
||||
export interface EvaluationWithDetails extends Evaluation {
|
||||
project: Project;
|
||||
scores: (EvaluationScore & { criteria_item: CriteriaItem })[];
|
||||
feedback: EvaluationFeedback[];
|
||||
}
|
||||
|
||||
export interface CriteriaTemplateWithItems extends CriteriaTemplate {
|
||||
items: CriteriaItem[];
|
||||
}
|
433
lib/services/database.ts
Normal file
433
lib/services/database.ts
Normal file
@@ -0,0 +1,433 @@
|
||||
import { query, transaction } from '../database';
|
||||
import type {
|
||||
User,
|
||||
CriteriaTemplate,
|
||||
CriteriaItem,
|
||||
Project,
|
||||
ProjectFile,
|
||||
ProjectWebsite,
|
||||
Evaluation,
|
||||
EvaluationScore,
|
||||
EvaluationFeedback,
|
||||
ProjectWithDetails,
|
||||
EvaluationWithDetails,
|
||||
CriteriaTemplateWithItems,
|
||||
} from '../models';
|
||||
|
||||
// 用戶相關操作
|
||||
export class UserService {
|
||||
static async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO users (email, username, password_hash, full_name, avatar_url, role, is_active, email_verified_at, last_login_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
userData.email,
|
||||
userData.username,
|
||||
userData.password_hash,
|
||||
userData.full_name,
|
||||
userData.avatar_url,
|
||||
userData.role,
|
||||
userData.is_active,
|
||||
userData.email_verified_at,
|
||||
userData.last_login_at,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findByEmail(email: string): Promise<User | null> {
|
||||
const sql = 'SELECT * FROM users WHERE email = ?';
|
||||
const rows = await query(sql, [email]) as User[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async findById(id: number): Promise<User | null> {
|
||||
const sql = 'SELECT * FROM users WHERE id = ?';
|
||||
const rows = await query(sql, [id]) as User[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async updateLastLogin(id: number) {
|
||||
const sql = 'UPDATE users SET last_login_at = NOW() WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 評分標準模板相關操作
|
||||
export class CriteriaTemplateService {
|
||||
static async create(templateData: Omit<CriteriaTemplate, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO criteria_templates (user_id, name, description, is_default, is_public, total_weight)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
templateData.user_id,
|
||||
templateData.name,
|
||||
templateData.description,
|
||||
templateData.is_default,
|
||||
templateData.is_public,
|
||||
templateData.total_weight,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findById(id: number): Promise<CriteriaTemplate | null> {
|
||||
const sql = 'SELECT * FROM criteria_templates WHERE id = ?';
|
||||
const rows = await query(sql, [id]) as CriteriaTemplate[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async findByUserId(userId: number): Promise<CriteriaTemplate[]> {
|
||||
const sql = 'SELECT * FROM criteria_templates WHERE user_id = ? ORDER BY created_at DESC';
|
||||
return await query(sql, [userId]) as CriteriaTemplate[];
|
||||
}
|
||||
|
||||
static async findDefault(): Promise<CriteriaTemplate | null> {
|
||||
const sql = 'SELECT * FROM criteria_templates WHERE is_default = 1 LIMIT 1';
|
||||
const rows = await query(sql) as CriteriaTemplate[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async findWithItems(id: number): Promise<CriteriaTemplateWithItems | null> {
|
||||
const template = await this.findById(id);
|
||||
if (!template) return null;
|
||||
|
||||
const itemsSql = 'SELECT * FROM criteria_items WHERE template_id = ? ORDER BY sort_order';
|
||||
const items = await query(itemsSql, [id]) as CriteriaItem[];
|
||||
|
||||
return { ...template, items };
|
||||
}
|
||||
|
||||
static async update(id: number, templateData: Partial<CriteriaTemplate>) {
|
||||
const fields = Object.keys(templateData).map(key => `${key} = ?`).join(', ');
|
||||
const values = Object.values(templateData);
|
||||
const sql = `UPDATE criteria_templates SET ${fields}, updated_at = NOW() WHERE id = ?`;
|
||||
await query(sql, [...values, id]);
|
||||
}
|
||||
|
||||
static async delete(id: number) {
|
||||
const sql = 'DELETE FROM criteria_templates WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 評分項目相關操作
|
||||
export class CriteriaItemService {
|
||||
static async create(itemData: Omit<CriteriaItem, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO criteria_items (template_id, name, description, weight, max_score, sort_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
itemData.template_id,
|
||||
itemData.name,
|
||||
itemData.description,
|
||||
itemData.weight,
|
||||
itemData.max_score,
|
||||
itemData.sort_order,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findByTemplateId(templateId: number): Promise<CriteriaItem[]> {
|
||||
const sql = 'SELECT * FROM criteria_items WHERE template_id = ? ORDER BY sort_order';
|
||||
const rows = await query(sql, [templateId]) as any[];
|
||||
|
||||
// 映射資料庫欄位到前端期望的格式
|
||||
return rows.map(row => ({
|
||||
id: row.id,
|
||||
template_id: row.template_id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
weight: Number(row.weight) || 0,
|
||||
maxScore: Number(row.max_score) || 10, // 映射 max_score 到 maxScore 並轉換為數字
|
||||
sort_order: row.sort_order,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
}));
|
||||
}
|
||||
|
||||
static async update(id: number, itemData: Partial<CriteriaItem>) {
|
||||
const fields = Object.keys(itemData).map(key => `${key} = ?`).join(', ');
|
||||
const values = Object.values(itemData);
|
||||
const sql = `UPDATE criteria_items SET ${fields}, updated_at = NOW() WHERE id = ?`;
|
||||
await query(sql, [...values, id]);
|
||||
}
|
||||
|
||||
static async delete(id: number) {
|
||||
const sql = 'DELETE FROM criteria_items WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
|
||||
static async deleteByTemplateId(templateId: number) {
|
||||
const sql = 'DELETE FROM criteria_items WHERE template_id = ?';
|
||||
await query(sql, [templateId]);
|
||||
}
|
||||
}
|
||||
|
||||
// 專案相關操作
|
||||
export class ProjectService {
|
||||
static async create(projectData: Omit<Project, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO projects (user_id, template_id, title, description, status, analysis_started_at, analysis_completed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
projectData.user_id,
|
||||
projectData.template_id,
|
||||
projectData.title,
|
||||
projectData.description,
|
||||
projectData.status,
|
||||
projectData.analysis_started_at,
|
||||
projectData.analysis_completed_at,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findById(id: number): Promise<Project | null> {
|
||||
const sql = 'SELECT * FROM projects WHERE id = ?';
|
||||
const rows = await query(sql, [id]) as Project[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async findByUserId(userId: number, limit = 20, offset = 0): Promise<Project[]> {
|
||||
const sql = `
|
||||
SELECT * FROM projects
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`;
|
||||
return await query(sql, [userId, limit, offset]) as Project[];
|
||||
}
|
||||
|
||||
static async findWithDetails(id: number): Promise<ProjectWithDetails | null> {
|
||||
const project = await this.findById(id);
|
||||
if (!project) return null;
|
||||
|
||||
const template = await CriteriaTemplateService.findById(project.template_id);
|
||||
if (!template) return null;
|
||||
|
||||
const files = await ProjectFileService.findByProjectId(id);
|
||||
const websites = await ProjectWebsiteService.findByProjectId(id);
|
||||
const evaluation = await EvaluationService.findByProjectId(id);
|
||||
|
||||
return {
|
||||
...project,
|
||||
template,
|
||||
files,
|
||||
websites,
|
||||
evaluation,
|
||||
};
|
||||
}
|
||||
|
||||
static async update(id: number, projectData: Partial<Project>) {
|
||||
const fields = Object.keys(projectData).map(key => `${key} = ?`).join(', ');
|
||||
const values = Object.values(projectData);
|
||||
const sql = `UPDATE projects SET ${fields}, updated_at = NOW() WHERE id = ?`;
|
||||
await query(sql, [...values, id]);
|
||||
}
|
||||
|
||||
static async delete(id: number) {
|
||||
const sql = 'DELETE FROM projects WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 專案文件相關操作
|
||||
export class ProjectFileService {
|
||||
static async create(fileData: Omit<ProjectFile, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO project_files (project_id, original_name, file_name, file_path, file_size, file_type, mime_type, upload_status, upload_progress)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
fileData.project_id,
|
||||
fileData.original_name,
|
||||
fileData.file_name,
|
||||
fileData.file_path,
|
||||
fileData.file_size,
|
||||
fileData.file_type,
|
||||
fileData.mime_type,
|
||||
fileData.upload_status,
|
||||
fileData.upload_progress,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findByProjectId(projectId: number): Promise<ProjectFile[]> {
|
||||
const sql = 'SELECT * FROM project_files WHERE project_id = ? ORDER BY created_at';
|
||||
return await query(sql, [projectId]) as ProjectFile[];
|
||||
}
|
||||
|
||||
static async updateStatus(id: number, status: ProjectFile['upload_status'], progress?: number) {
|
||||
const sql = 'UPDATE project_files SET upload_status = ?, upload_progress = ?, updated_at = NOW() WHERE id = ?';
|
||||
await query(sql, [status, progress || 0, id]);
|
||||
}
|
||||
|
||||
static async delete(id: number) {
|
||||
const sql = 'DELETE FROM project_files WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 專案網站相關操作
|
||||
export class ProjectWebsiteService {
|
||||
static async create(websiteData: Omit<ProjectWebsite, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO project_websites (project_id, url, title, description, status)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
websiteData.project_id,
|
||||
websiteData.url,
|
||||
websiteData.title,
|
||||
websiteData.description,
|
||||
websiteData.status,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findByProjectId(projectId: number): Promise<ProjectWebsite[]> {
|
||||
const sql = 'SELECT * FROM project_websites WHERE project_id = ? ORDER BY created_at';
|
||||
return await query(sql, [projectId]) as ProjectWebsite[];
|
||||
}
|
||||
|
||||
static async updateStatus(id: number, status: ProjectWebsite['status']) {
|
||||
const sql = 'UPDATE project_websites SET status = ?, updated_at = NOW() WHERE id = ?';
|
||||
await query(sql, [status, id]);
|
||||
}
|
||||
|
||||
static async delete(id: number) {
|
||||
const sql = 'DELETE FROM project_websites WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 評審相關操作
|
||||
export class EvaluationService {
|
||||
static async create(evaluationData: Omit<Evaluation, 'id' | 'created_at' | 'updated_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO evaluations (project_id, overall_score, max_possible_score, grade, analysis_duration, ai_model_version, status, error_message)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
evaluationData.project_id,
|
||||
evaluationData.overall_score,
|
||||
evaluationData.max_possible_score,
|
||||
evaluationData.grade,
|
||||
evaluationData.analysis_duration,
|
||||
evaluationData.ai_model_version,
|
||||
evaluationData.status,
|
||||
evaluationData.error_message,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findById(id: number): Promise<Evaluation | null> {
|
||||
const sql = 'SELECT * FROM evaluations WHERE id = ?';
|
||||
const rows = await query(sql, [id]) as Evaluation[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async findByProjectId(projectId: number): Promise<Evaluation | null> {
|
||||
const sql = 'SELECT * FROM evaluations WHERE project_id = ? ORDER BY created_at DESC LIMIT 1';
|
||||
const rows = await query(sql, [projectId]) as Evaluation[];
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async findWithDetails(id: number): Promise<EvaluationWithDetails | null> {
|
||||
const evaluation = await this.findById(id);
|
||||
if (!evaluation) return null;
|
||||
|
||||
const project = await ProjectService.findById(evaluation.project_id);
|
||||
if (!project) return null;
|
||||
|
||||
const scoresSql = `
|
||||
SELECT es.*, ci.name as criteria_item_name, ci.description as criteria_item_description
|
||||
FROM evaluation_scores es
|
||||
JOIN criteria_items ci ON es.criteria_item_id = ci.id
|
||||
WHERE es.evaluation_id = ?
|
||||
ORDER BY ci.sort_order
|
||||
`;
|
||||
const scores = await query(scoresSql, [id]) as (EvaluationScore & { criteria_item: CriteriaItem })[];
|
||||
|
||||
const feedback = await EvaluationFeedbackService.findByEvaluationId(id);
|
||||
|
||||
return {
|
||||
...evaluation,
|
||||
project,
|
||||
scores,
|
||||
feedback,
|
||||
};
|
||||
}
|
||||
|
||||
static async update(id: number, evaluationData: Partial<Evaluation>) {
|
||||
const fields = Object.keys(evaluationData).map(key => `${key} = ?`).join(', ');
|
||||
const values = Object.values(evaluationData);
|
||||
const sql = `UPDATE evaluations SET ${fields}, updated_at = NOW() WHERE id = ?`;
|
||||
await query(sql, [...values, id]);
|
||||
}
|
||||
|
||||
static async delete(id: number) {
|
||||
const sql = 'DELETE FROM evaluations WHERE id = ?';
|
||||
await query(sql, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 評分結果相關操作
|
||||
export class EvaluationScoreService {
|
||||
static async create(scoreData: Omit<EvaluationScore, 'id' | 'created_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO evaluation_scores (evaluation_id, criteria_item_id, score, max_score, weight, weighted_score, percentage)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
scoreData.evaluation_id,
|
||||
scoreData.criteria_item_id,
|
||||
scoreData.score,
|
||||
scoreData.max_score,
|
||||
scoreData.weight,
|
||||
scoreData.weighted_score,
|
||||
scoreData.percentage,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findByEvaluationId(evaluationId: number): Promise<EvaluationScore[]> {
|
||||
const sql = 'SELECT * FROM evaluation_scores WHERE evaluation_id = ?';
|
||||
return await query(sql, [evaluationId]) as EvaluationScore[];
|
||||
}
|
||||
|
||||
static async deleteByEvaluationId(evaluationId: number) {
|
||||
const sql = 'DELETE FROM evaluation_scores WHERE evaluation_id = ?';
|
||||
await query(sql, [evaluationId]);
|
||||
}
|
||||
}
|
||||
|
||||
// 評語相關操作
|
||||
export class EvaluationFeedbackService {
|
||||
static async create(feedbackData: Omit<EvaluationFeedback, 'id' | 'created_at'>) {
|
||||
const sql = `
|
||||
INSERT INTO evaluation_feedback (evaluation_id, criteria_item_id, feedback_type, content, sort_order)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`;
|
||||
const result = await query(sql, [
|
||||
feedbackData.evaluation_id,
|
||||
feedbackData.criteria_item_id,
|
||||
feedbackData.feedback_type,
|
||||
feedbackData.content,
|
||||
feedbackData.sort_order,
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static async findByEvaluationId(evaluationId: number): Promise<EvaluationFeedback[]> {
|
||||
const sql = 'SELECT * FROM evaluation_feedback WHERE evaluation_id = ? ORDER BY sort_order';
|
||||
return await query(sql, [evaluationId]) as EvaluationFeedback[];
|
||||
}
|
||||
|
||||
static async deleteByEvaluationId(evaluationId: number) {
|
||||
const sql = 'DELETE FROM evaluation_feedback WHERE evaluation_id = ?';
|
||||
await query(sql, [evaluationId]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user