feat: 更新构建流程,添加 API 构建脚本和 SQL 文件复制脚本

- 修改 package.json,更新构建命令,添加 postbuild 脚本以复制 init.sql 文件。
- 新增 scripts/build-api.mjs,使用 esbuild 构建 API 代码。
- 新增 scripts/copy-init-sql.mjs,复制数据库初始化 SQL 文件到构建输出目录。
- 在 SubjectSelectionPage 组件中添加 totalScore 属性,增加历史最高分状态显示功能。
- 在 ExamSubjectPage 和 QuestionManagePage 中优化判断题答案处理逻辑。
- 在 OptionList 组件中将判断题选项文本从 'T' 和 'F' 改为 '对' 和 '错'。
- 在 QuizFooter 组件中调整样式,增加按钮和文本的可读性。
- 新增用户默认组测试用例,验证新用户创建后自动加入“全体用户”系统组。
- 新增 tsconfig.api.json,配置 API 相关 TypeScript 编译选项。
- 移除 vite.config.ts 中的 global 定义。
This commit is contained in:
2025-12-30 20:33:14 +08:00
parent 1822d8b4da
commit eb4504960e
31 changed files with 10221 additions and 150 deletions

View File

@@ -2,6 +2,7 @@ import path from 'path';
import fs from 'fs';
import { v4 as uuidv4 } from 'uuid';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
// 在ES模块中创建require函数用于兼容CommonJS模块
const require = createRequire(import.meta.url);
@@ -98,6 +99,71 @@ const ensureIndex = async (createIndexSql: string) => {
await exec(createIndexSql);
};
const ensureUserGroupSchemaAndAllUsersMembership = async () => {
// 1) Ensure tables
await ensureTable(`
CREATE TABLE IF NOT EXISTS user_groups (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
description TEXT NOT NULL DEFAULT '',
is_system BOOLEAN NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
await ensureTable(`
CREATE TABLE IF NOT EXISTS user_group_members (
group_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (group_id, user_id),
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`);
// 2) Ensure indexes
await ensureIndex(
`CREATE INDEX IF NOT EXISTS idx_user_group_members_group_id ON user_group_members(group_id);`,
);
await ensureIndex(
`CREATE INDEX IF NOT EXISTS idx_user_group_members_user_id ON user_group_members(user_id);`,
);
// 3) Ensure system group exists
const existingSystemGroup = await get(
`SELECT id FROM user_groups WHERE is_system = 1 ORDER BY created_at ASC LIMIT 1`,
);
let systemGroupId = existingSystemGroup?.id as string | undefined;
if (!systemGroupId) {
const preferredId = 'all-users';
try {
await run(
`INSERT INTO user_groups (id, name, description, is_system) VALUES (?, ?, ?, 1)`,
[preferredId, '全体用户', '系统内置:新用户自动加入'],
);
systemGroupId = preferredId;
} catch {
const fallbackId = uuidv4();
await run(
`INSERT INTO user_groups (id, name, description, is_system) VALUES (?, ?, ?, 1)`,
[fallbackId, '全体用户', '系统内置:新用户自动加入'],
);
systemGroupId = fallbackId;
}
}
// 4) Backfill membership: ensure all existing users are in the system group
if (systemGroupId) {
await run(
`INSERT OR IGNORE INTO user_group_members (group_id, user_id)
SELECT ?, u.id FROM users u`,
[systemGroupId],
);
}
};
const migrateDatabase = async () => {
// 跳过迁移,因为数据库连接可能未初始化
console.log('跳过数据库迁移');
@@ -114,16 +180,22 @@ export const initDatabase = async () => {
if (!usersTableExists) {
// 读取并执行初始化SQL文件
const initSqlPath = path.join(path.dirname(import.meta.url.replace('file:///', '')), 'init.sql');
const initSqlPath = fileURLToPath(new URL('./init.sql', import.meta.url));
const initSql = fs.readFileSync(initSqlPath, 'utf8');
await exec(initSql);
console.log('数据库初始化成功');
// 用户组(含“全体用户”系统组)
await ensureUserGroupSchemaAndAllUsersMembership();
} else {
console.log('数据库表已存在,跳过初始化');
await ensureColumn('questions', "analysis TEXT NOT NULL DEFAULT ''", 'analysis');
await ensureColumn('quiz_records', "score_percentage REAL", 'score_percentage');
await ensureColumn('quiz_records', "status TEXT", 'status');
// 用户组(含“全体用户”系统组)
await ensureUserGroupSchemaAndAllUsersMembership();
}
} catch (error) {
console.error('数据库初始化失败:', error);

View File

@@ -11,6 +11,32 @@ CREATE TABLE users (
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_users_created_at ON users(created_at);
-- 用户组表
CREATE TABLE user_groups (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
description TEXT NOT NULL DEFAULT '',
is_system BOOLEAN NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 用户组成员表
CREATE TABLE user_group_members (
group_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (group_id, user_id),
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_user_group_members_group_id ON user_group_members(group_id);
CREATE INDEX idx_user_group_members_user_id ON user_group_members(user_id);
-- 内置系统用户组:全体用户
INSERT OR IGNORE INTO user_groups (id, name, description, is_system)
VALUES ('all-users', '全体用户', '系统内置:新用户自动加入', 1);
-- 题目表
CREATE TABLE questions (
id TEXT PRIMARY KEY,