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:
@@ -263,7 +263,9 @@ export class AdminUserController {
|
||||
continue;
|
||||
}
|
||||
|
||||
await UserModel.create({ name, phone, password });
|
||||
const user = await UserModel.create({ name, phone, password });
|
||||
// 统一规则:新用户默认加入“全体用户”系统组
|
||||
await UserGroupModel.updateUserGroups(user.id, []);
|
||||
imported++;
|
||||
} catch (error: any) {
|
||||
if (error.message === '手机号已存在') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { UserModel, QuestionModel, QuizModel } from '../models';
|
||||
import { UserModel, QuestionModel, QuizModel, UserGroupModel } from '../models';
|
||||
|
||||
export class BackupController {
|
||||
// 导出用户数据
|
||||
@@ -129,10 +129,12 @@ export class BackupController {
|
||||
if (users && users.length > 0) {
|
||||
for (const user of users) {
|
||||
try {
|
||||
await UserModel.create({
|
||||
const createdUser = await UserModel.create({
|
||||
name: user.姓名 || user.name,
|
||||
phone: user.手机号 || user.phone
|
||||
});
|
||||
// 统一规则:恢复数据创建的用户也必须加入“全体用户”系统组
|
||||
await UserGroupModel.updateUserGroups(createdUser.id, []);
|
||||
restoredCount.users++;
|
||||
} catch (error) {
|
||||
console.log('用户已存在,跳过:', user.手机号 || user.phone);
|
||||
|
||||
@@ -155,7 +155,19 @@ export class QuizController {
|
||||
answer.isCorrect = false;
|
||||
}
|
||||
} else if (question.type === 'single' || question.type === 'judgment') {
|
||||
const isCorrect = answer.userAnswer === question.answer;
|
||||
const normalizeJudgment = (raw: unknown) => {
|
||||
const v = String(raw ?? '').trim();
|
||||
const yes = new Set(['A', 'T', 'TRUE', 'True', 'true', '1', '正确', '对', '是', 'Y', 'y', 'YES', 'yes']);
|
||||
const no = new Set(['B', 'F', 'FALSE', 'False', 'false', '0', '错误', '错', '否', '不是', 'N', 'n', 'NO', 'no']);
|
||||
if (yes.has(v)) return '正确';
|
||||
if (no.has(v)) return '错误';
|
||||
return v;
|
||||
};
|
||||
|
||||
const isCorrect =
|
||||
question.type === 'judgment'
|
||||
? normalizeJudgment(answer.userAnswer) === normalizeJudgment(question.answer)
|
||||
: answer.userAnswer === question.answer;
|
||||
answer.score = isCorrect ? question.score : 0;
|
||||
answer.isCorrect = isCorrect;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export class UserController {
|
||||
|
||||
const existingUser = await UserModel.findByPhone(phone);
|
||||
|
||||
if (existingUser) {
|
||||
if (existingUser) {
|
||||
if (existingUser.password && existingUser.password !== password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -118,6 +118,11 @@ export class UserController {
|
||||
});
|
||||
} else {
|
||||
const newUser = await UserModel.create({ name, phone, password });
|
||||
// 自动加入"全体用户"组
|
||||
const allUsersGroup = await UserGroupModel.getSystemGroup();
|
||||
if (allUsersGroup) {
|
||||
await UserGroupModel.addMember(allUsersGroup.id, newUser.id);
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
data: newUser
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -34,11 +34,28 @@ export interface ExcelQuestionData {
|
||||
}
|
||||
|
||||
export class QuestionModel {
|
||||
private static normalizeJudgmentAnswer(raw: unknown): string {
|
||||
const v = String(raw ?? '').trim();
|
||||
if (!v) return v;
|
||||
|
||||
// 兼容历史存储与导入:A/B、T/F、true/false、1/0 等
|
||||
const yes = new Set(['A', 'T', 'TRUE', 'True', 'true', '1', '正确', '对', '是', 'Y', 'y', 'YES', 'yes']);
|
||||
const no = new Set(['B', 'F', 'FALSE', 'False', 'false', '0', '错误', '错', '否', '不是', 'N', 'n', 'NO', 'no']);
|
||||
|
||||
if (yes.has(v)) return '正确';
|
||||
if (no.has(v)) return '错误';
|
||||
return v;
|
||||
}
|
||||
|
||||
// 创建题目
|
||||
static async create(data: CreateQuestionData): Promise<Question> {
|
||||
const id = uuidv4();
|
||||
const optionsStr = data.options ? JSON.stringify(data.options) : null;
|
||||
const answerStr = Array.isArray(data.answer) ? JSON.stringify(data.answer) : data.answer;
|
||||
const normalizedAnswer =
|
||||
data.type === 'judgment' && !Array.isArray(data.answer)
|
||||
? this.normalizeJudgmentAnswer(data.answer)
|
||||
: data.answer;
|
||||
const answerStr = Array.isArray(normalizedAnswer) ? JSON.stringify(normalizedAnswer) : normalizedAnswer;
|
||||
const category = data.category && data.category.trim() ? data.category.trim() : '通用';
|
||||
const analysis = String(data.analysis ?? '').trim().slice(0, 255);
|
||||
|
||||
@@ -71,7 +88,11 @@ export class QuestionModel {
|
||||
const question = questions[i];
|
||||
const id = uuidv4();
|
||||
const optionsStr = question.options ? JSON.stringify(question.options) : null;
|
||||
const answerStr = Array.isArray(question.answer) ? JSON.stringify(question.answer) : question.answer;
|
||||
const normalizedAnswer =
|
||||
question.type === 'judgment' && !Array.isArray(question.answer)
|
||||
? this.normalizeJudgmentAnswer(question.answer)
|
||||
: question.answer;
|
||||
const answerStr = Array.isArray(normalizedAnswer) ? JSON.stringify(normalizedAnswer) : normalizedAnswer;
|
||||
const category = question.category && question.category.trim() ? question.category.trim() : '通用';
|
||||
const analysis = String(question.analysis ?? '').trim().slice(0, 255);
|
||||
|
||||
@@ -132,7 +153,11 @@ export class QuestionModel {
|
||||
const question = questions[i];
|
||||
try {
|
||||
const optionsStr = question.options ? JSON.stringify(question.options) : null;
|
||||
const answerStr = Array.isArray(question.answer) ? JSON.stringify(question.answer) : question.answer;
|
||||
const normalizedAnswer =
|
||||
question.type === 'judgment' && !Array.isArray(question.answer)
|
||||
? this.normalizeJudgmentAnswer(question.answer)
|
||||
: question.answer;
|
||||
const answerStr = Array.isArray(normalizedAnswer) ? JSON.stringify(normalizedAnswer) : normalizedAnswer;
|
||||
const category = question.category && question.category.trim() ? question.category.trim() : '通用';
|
||||
const analysis = String(question.analysis ?? '').trim().slice(0, 255);
|
||||
|
||||
@@ -291,7 +316,11 @@ export class QuestionModel {
|
||||
}
|
||||
|
||||
if (data.answer !== undefined) {
|
||||
const answerStr = Array.isArray(data.answer) ? JSON.stringify(data.answer) : data.answer;
|
||||
const normalizedAnswer =
|
||||
data.type === 'judgment' && !Array.isArray(data.answer)
|
||||
? this.normalizeJudgmentAnswer(data.answer)
|
||||
: data.answer;
|
||||
const answerStr = Array.isArray(normalizedAnswer) ? JSON.stringify(normalizedAnswer) : normalizedAnswer;
|
||||
fields.push('answer = ?');
|
||||
values.push(answerStr);
|
||||
}
|
||||
@@ -353,6 +382,9 @@ export class QuestionModel {
|
||||
return answerStr;
|
||||
}
|
||||
}
|
||||
if (type === 'judgment') {
|
||||
return this.normalizeJudgmentAnswer(answerStr);
|
||||
}
|
||||
return answerStr;
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,14 @@ export class QuizModel {
|
||||
return answer;
|
||||
}
|
||||
}
|
||||
if (type === 'judgment') {
|
||||
const v = String(answer ?? '').trim();
|
||||
const yes = new Set(['A', 'T', 'TRUE', 'True', 'true', '1', '正确', '对', '是', 'Y', 'y', 'YES', 'yes']);
|
||||
const no = new Set(['B', 'F', 'FALSE', 'False', 'false', '0', '错误', '错', '否', '不是', 'N', 'n', 'NO', 'no']);
|
||||
if (yes.has(v)) return '正确';
|
||||
if (no.has(v)) return '错误';
|
||||
return v;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
|
||||
BIN
data/survey.db
BIN
data/survey.db
Binary file not shown.
20
deploy_bundle/server/ecosystem.config.cjs
Normal file
20
deploy_bundle/server/ecosystem.config.cjs
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'blv-oa-exam-backend',
|
||||
script: 'dist/api/server.js',
|
||||
cwd: 'R:/nodejsROOT/oa_exam/server',
|
||||
interpreter: 'node',
|
||||
node_args: '--enable-source-maps',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: '10012',
|
||||
DB_PATH: 'R:/nodejsROOT/oa_exam/db/survey.db',
|
||||
},
|
||||
time: true,
|
||||
autorestart: true,
|
||||
max_restarts: 10,
|
||||
restart_delay: 3000,
|
||||
},
|
||||
],
|
||||
};
|
||||
8370
deploy_bundle/server/package-lock.json
generated
Normal file
8370
deploy_bundle/server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
56
deploy_bundle/server/package.json
Normal file
56
deploy_bundle/server/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "survey-system",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run dev:api\" \"npm run dev:frontend\"",
|
||||
"dev:api": "nodemon --exec tsx api/server.ts",
|
||||
"dev:frontend": "vite",
|
||||
"build": "vite build && node scripts/build-api.mjs",
|
||||
"postbuild": "node scripts/copy-init-sql.mjs",
|
||||
"preview": "vite preview",
|
||||
"start": "node --enable-source-maps dist/api/server.js",
|
||||
"test": "node --import tsx --test test/admin-task-stats.test.ts test/question-text-import.test.ts test/swipe-detect.test.ts test/user-tasks.test.ts test/user-records-subjectname.test.ts test/score-percentage.test.ts",
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/axios": "^0.9.36",
|
||||
"antd": "^5.12.1",
|
||||
"axios": "^1.13.2",
|
||||
"concurrently": "^7.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.18.2",
|
||||
"joi": "^17.11.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"recharts": "^3.6.0",
|
||||
"sqlite3": "^5.1.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"uuid": "^9.0.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/multer": "^1.4.11",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@vitejs/plugin-react": "^4.1.1",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"crypto-js": "^4.2.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tsx": "^4.21.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0"
|
||||
}
|
||||
}
|
||||
1
deploy_bundle/web/assets/index-46911e80.css
Normal file
1
deploy_bundle/web/assets/index-46911e80.css
Normal file
File diff suppressed because one or more lines are too long
681
deploy_bundle/web/assets/index-509f66ca.js
Normal file
681
deploy_bundle/web/assets/index-509f66ca.js
Normal file
File diff suppressed because one or more lines are too long
64
deploy_bundle/web/assets/主要LOGO-b9927500.svg
Normal file
64
deploy_bundle/web/assets/主要LOGO-b9927500.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 28 KiB |
21
deploy_bundle/web/assets/正方形LOGO-c56db41d.svg
Normal file
21
deploy_bundle/web/assets/正方形LOGO-c56db41d.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2020 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="477px" height="621px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 80.92 105.37"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil0 {fill:#008C8C}
|
||||
.fil1 {fill:#008C8C;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="图层_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0" d="M72.67 62.65c2.16,-4 3.39,-8.58 3.39,-13.45 0,-15.66 -12.69,-28.35 -28.35,-28.35 -4.92,0 -9.54,1.26 -13.57,3.46l0 11.7c3.44,-3.54 8.25,-5.74 13.57,-5.74 10.46,0 18.93,8.47 18.93,18.93 0,10.45 -8.47,18.93 -18.93,18.93 -5.21,0 -9.93,-2.11 -13.35,-5.51 -3.4,-3.35 -4.19,-8.41 -4.36,-13.47l0 -31.15 0 -15.23 0 -0.35 0 -0.01 0 -1.04c3.33,-0.89 6.84,-1.37 10.46,-1.37 22.35,0 40.46,18.12 40.46,40.46 0,22.35 -18.11,40.46 -40.46,40.46 -22.34,0 -40.46,-18.11 -40.46,-40.46 0,-14.88 8.04,-27.89 20.01,-34.92l0 1.18 0 0 0 0.4 0 10.88 0 29.2c0,4.65 0.18,6.98 1.17,10.53 2.24,8.04 7.74,14.13 15.26,17.49 3.45,1.49 7.27,2.33 11.27,2.33 3.02,0 5.93,-0.48 8.66,-1.35 6.6,-2.95 12.23,-7.66 16.3,-13.55z"/>
|
||||
<polygon class="fil1" points="15.53,105.37 15.53,97.52 65.39,97.52 65.39,105.37 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
4
deploy_bundle/web/favicon.svg
Normal file
4
deploy_bundle/web/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" fill="#0A0B0D"/>
|
||||
<path d="M26.6677 23.7149H8.38057V20.6496H5.33301V8.38159H26.6677V23.7149ZM8.38057 20.6496H23.6201V11.4482H8.38057V20.6496ZM16.0011 16.0021L13.8461 18.1705L11.6913 16.0021L13.8461 13.8337L16.0011 16.0021ZM22.0963 16.0008L19.9414 18.1691L17.7865 16.0008L19.9414 13.8324L22.0963 16.0008Z" fill="#32F08C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
16
deploy_bundle/web/index.html
Normal file
16
deploy_bundle/web/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/正方形LOGO-c56db41d.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>宝来威考试平台</title>
|
||||
<meta name="description" content="功能完善的在线问卷调查系统,支持多种题型、随机抽题、免注册答题等特性" />
|
||||
<script type="module" crossorigin src="/assets/index-509f66ca.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-46911e80.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
20
ecosystem.config.cjs
Normal file
20
ecosystem.config.cjs
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'blv-oa-exam-backend',
|
||||
script: 'dist/api/server.js',
|
||||
cwd: 'R:/nodejsROOT/oa_exam/server',
|
||||
interpreter: 'node',
|
||||
node_args: '--enable-source-maps',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: '10012',
|
||||
DB_PATH: 'R:/nodejsROOT/oa_exam/db/survey.db',
|
||||
},
|
||||
time: true,
|
||||
autorestart: true,
|
||||
max_restarts: 10,
|
||||
restart_delay: 3000,
|
||||
},
|
||||
],
|
||||
};
|
||||
699
package-lock.json
generated
699
package-lock.json
generated
@@ -39,6 +39,7 @@
|
||||
"@vitejs/plugin-react": "^4.1.1",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"crypto-js": "^4.2.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tsx": "^4.21.0",
|
||||
@@ -481,9 +482,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -498,9 +499,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -515,9 +516,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -532,9 +533,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -549,9 +550,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -566,9 +567,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -583,9 +584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -600,9 +601,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -617,9 +618,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -634,9 +635,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -651,9 +652,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -668,9 +669,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
||||
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -685,9 +686,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
||||
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -702,9 +703,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
||||
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -719,9 +720,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
||||
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -736,9 +737,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
||||
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -753,9 +754,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -770,9 +771,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -787,9 +788,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -804,9 +805,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -821,9 +822,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -838,9 +839,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -855,9 +856,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -872,9 +873,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -889,9 +890,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -906,9 +907,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3079,9 +3080,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz",
|
||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -3092,32 +3093,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
"@esbuild/aix-ppc64": "0.25.12",
|
||||
"@esbuild/android-arm": "0.25.12",
|
||||
"@esbuild/android-arm64": "0.25.12",
|
||||
"@esbuild/android-x64": "0.25.12",
|
||||
"@esbuild/darwin-arm64": "0.25.12",
|
||||
"@esbuild/darwin-x64": "0.25.12",
|
||||
"@esbuild/freebsd-arm64": "0.25.12",
|
||||
"@esbuild/freebsd-x64": "0.25.12",
|
||||
"@esbuild/linux-arm": "0.25.12",
|
||||
"@esbuild/linux-arm64": "0.25.12",
|
||||
"@esbuild/linux-ia32": "0.25.12",
|
||||
"@esbuild/linux-loong64": "0.25.12",
|
||||
"@esbuild/linux-mips64el": "0.25.12",
|
||||
"@esbuild/linux-ppc64": "0.25.12",
|
||||
"@esbuild/linux-riscv64": "0.25.12",
|
||||
"@esbuild/linux-s390x": "0.25.12",
|
||||
"@esbuild/linux-x64": "0.25.12",
|
||||
"@esbuild/netbsd-arm64": "0.25.12",
|
||||
"@esbuild/netbsd-x64": "0.25.12",
|
||||
"@esbuild/openbsd-arm64": "0.25.12",
|
||||
"@esbuild/openbsd-x64": "0.25.12",
|
||||
"@esbuild/openharmony-arm64": "0.25.12",
|
||||
"@esbuild/sunos-x64": "0.25.12",
|
||||
"@esbuild/win32-arm64": "0.25.12",
|
||||
"@esbuild/win32-ia32": "0.25.12",
|
||||
"@esbuild/win32-x64": "0.25.12"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@@ -7040,6 +7041,490 @@
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/esbuild": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
"dev": "concurrently \"npm run dev:api\" \"npm run dev:frontend\"",
|
||||
"dev:api": "nodemon --exec tsx api/server.ts",
|
||||
"dev:frontend": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build": "vite build && node scripts/build-api.mjs",
|
||||
"postbuild": "node scripts/copy-init-sql.mjs",
|
||||
"preview": "vite preview",
|
||||
"start": "node dist/api/server.js",
|
||||
"test": "node --import tsx --test test/admin-task-stats.test.ts test/question-text-import.test.ts test/swipe-detect.test.ts test/user-tasks.test.ts test/user-records-subjectname.test.ts test/score-percentage.test.ts",
|
||||
"start": "node --enable-source-maps dist/api/server.js",
|
||||
"test": "node --import tsx --test test/admin-task-stats.test.ts test/question-text-import.test.ts test/swipe-detect.test.ts test/user-tasks.test.ts test/user-records-subjectname.test.ts test/score-percentage.test.ts test/user-default-group.test.ts",
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -48,6 +49,7 @@
|
||||
"nodemon": "^3.0.2",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tsx": "^4.21.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0"
|
||||
}
|
||||
|
||||
35
scripts/build-api.mjs
Normal file
35
scripts/build-api.mjs
Normal file
@@ -0,0 +1,35 @@
|
||||
import { build } from 'esbuild';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const projectRoot = process.cwd();
|
||||
const pkgPath = path.join(projectRoot, 'package.json');
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
|
||||
const dependencies = Object.keys(pkg.dependencies ?? {});
|
||||
const optionalDependencies = Object.keys(pkg.optionalDependencies ?? {});
|
||||
const peerDependencies = Object.keys(pkg.peerDependencies ?? {});
|
||||
|
||||
const externals = Array.from(
|
||||
new Set([
|
||||
...dependencies,
|
||||
...optionalDependencies,
|
||||
...peerDependencies,
|
||||
// also keep these as runtime externals
|
||||
'sqlite3',
|
||||
]),
|
||||
);
|
||||
|
||||
await build({
|
||||
entryPoints: ['api/server.ts'],
|
||||
outfile: 'dist/api/server.js',
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: ['node20'],
|
||||
sourcemap: true,
|
||||
logLevel: 'info',
|
||||
external: externals,
|
||||
});
|
||||
|
||||
console.log('Built backend -> dist/api/server.js');
|
||||
13
scripts/copy-init-sql.mjs
Normal file
13
scripts/copy-init-sql.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
const src = path.join(projectRoot, 'api', 'database', 'init.sql');
|
||||
const destDir = path.join(projectRoot, 'dist', 'api', 'database');
|
||||
const dest = path.join(destDir, 'init.sql');
|
||||
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
await fs.copyFile(src, dest);
|
||||
|
||||
console.log(`Copied init.sql -> ${dest}`);
|
||||
@@ -29,6 +29,7 @@ interface ExamTask {
|
||||
usedAttempts?: number;
|
||||
maxAttempts?: number;
|
||||
bestScore?: number;
|
||||
totalScore?: number;
|
||||
}
|
||||
|
||||
export const SubjectSelectionPage: React.FC = () => {
|
||||
@@ -88,6 +89,35 @@ export const SubjectSelectionPage: React.FC = () => {
|
||||
return tasks.filter(task => getTaskStatus(task) === status);
|
||||
};
|
||||
|
||||
const getScoreStatus = (pct: number): '优秀' | '合格' | '不及格' => {
|
||||
if (!Number.isFinite(pct)) return '不及格';
|
||||
if (pct >= 80) return '优秀';
|
||||
if (pct >= 60) return '合格';
|
||||
return '不及格';
|
||||
};
|
||||
|
||||
const getScoreStatusTagColor = (status: '优秀' | '合格' | '不及格') => {
|
||||
if (status === '优秀') return 'green';
|
||||
if (status === '合格') return 'blue';
|
||||
return 'red';
|
||||
};
|
||||
|
||||
const renderBestHistoryTag = (task: ExamTask, subject?: ExamSubject) => {
|
||||
const usedAttempts = Number(task.usedAttempts) || 0;
|
||||
if (usedAttempts <= 0) return null;
|
||||
|
||||
if (typeof task.bestScore !== 'number') return null;
|
||||
const totalScore = Number(subject?.totalScore ?? task.totalScore) || 0;
|
||||
const pct = totalScore > 0 ? (task.bestScore / totalScore) * 100 : NaN;
|
||||
const status = getScoreStatus(pct);
|
||||
|
||||
return (
|
||||
<Tag color={getScoreStatusTagColor(status)} className="text-xs">
|
||||
历史最高:{status}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
const startQuiz = async (taskId: string) => {
|
||||
if (!taskId) {
|
||||
message.warning('请选择考试任务');
|
||||
@@ -224,9 +254,7 @@ export const SubjectSelectionPage: React.FC = () => {
|
||||
<Tag color="green" className="text-xs">
|
||||
{usedAttempts}/{maxAttempts}
|
||||
</Tag>
|
||||
{typeof task.bestScore === 'number' ? (
|
||||
<Tag color="green" className="text-xs">最高分 {task.bestScore} 分</Tag>
|
||||
) : null}
|
||||
{renderBestHistoryTag(task, subject)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
@@ -300,9 +328,7 @@ export const SubjectSelectionPage: React.FC = () => {
|
||||
<Tag color={attemptsExhausted ? 'red' : 'default'} className="text-xs">
|
||||
{usedAttempts}/{maxAttempts}
|
||||
</Tag>
|
||||
{typeof task.bestScore === 'number' ? (
|
||||
<Tag color="green" className="text-xs">最高分 {task.bestScore} 分</Tag>
|
||||
) : null}
|
||||
{renderBestHistoryTag(task, subject)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
@@ -377,9 +403,7 @@ export const SubjectSelectionPage: React.FC = () => {
|
||||
<Tag color="blue" className="text-xs">
|
||||
{usedAttempts}/{maxAttempts}
|
||||
</Tag>
|
||||
{typeof task.bestScore === 'number' ? (
|
||||
<Tag color="green" className="text-xs">最高分 {task.bestScore} 分</Tag>
|
||||
) : null}
|
||||
{renderBestHistoryTag(task, subject)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
@@ -407,11 +431,7 @@ export const SubjectSelectionPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tasks.length === 0 && (
|
||||
<div className="text-center py-8 bg-gray-50 border-dashed border-2 border-gray-200 rounded">
|
||||
<Text type="secondary" className="text-sm">暂无可用考试任务</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -730,9 +730,12 @@ const ExamSubjectPage = () => {
|
||||
{Array.isArray(question.answer) ?
|
||||
// 多选题:直接拼接答案,不需要转换
|
||||
question.answer.join(', ') :
|
||||
question.type === 'judgment' ?
|
||||
// 判断题:A=正确,B=错误
|
||||
(question.answer === 'A' ? '正确' : '错误') :
|
||||
question.type === 'judgment' ? (() => {
|
||||
const v = String(question.answer ?? '').trim();
|
||||
if (['A', 'T', 'true', 'True', 'TRUE', '1', '正确', '对', '是'].includes(v)) return '正确';
|
||||
if (['B', 'F', 'false', 'False', 'FALSE', '0', '错误', '错', '否', '不是'].includes(v)) return '错误';
|
||||
return v;
|
||||
})() :
|
||||
// 单选题:直接显示答案,不需要转换
|
||||
question.answer}
|
||||
</span>
|
||||
|
||||
@@ -116,11 +116,19 @@ const QuestionManagePage = () => {
|
||||
};
|
||||
|
||||
const handleEdit = (question: Question) => {
|
||||
const normalizeJudgmentAnswer = (raw: unknown) => {
|
||||
const v = String(raw ?? '').trim();
|
||||
if (['A', 'T', 'true', 'True', 'TRUE', '1', '正确', '对', '是'].includes(v)) return '正确';
|
||||
if (['B', 'F', 'false', 'False', 'FALSE', '0', '错误', '错', '否', '不是'].includes(v)) return '错误';
|
||||
return v;
|
||||
};
|
||||
|
||||
setEditingQuestion(question);
|
||||
form.setFieldsValue({
|
||||
...question,
|
||||
options: question.options?.join('\n'),
|
||||
analysis: question.analysis || ''
|
||||
analysis: question.analysis || '',
|
||||
answer: question.type === 'judgment' ? normalizeJudgmentAnswer(question.answer) : question.answer,
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
@@ -137,9 +145,17 @@ const QuestionManagePage = () => {
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
const normalizeJudgmentAnswer = (raw: unknown) => {
|
||||
const v = String(raw ?? '').trim();
|
||||
if (['A', 'T', 'true', 'True', 'TRUE', '1', '正确', '对', '是'].includes(v)) return '正确';
|
||||
if (['B', 'F', 'false', 'False', 'FALSE', '0', '错误', '错', '否', '不是'].includes(v)) return '错误';
|
||||
return v;
|
||||
};
|
||||
|
||||
const formData = {
|
||||
...values,
|
||||
options: values.options ? values.options.split('\n').filter((opt: string) => opt.trim()) : undefined
|
||||
options: values.options ? values.options.split('\n').filter((opt: string) => opt.trim()) : undefined,
|
||||
answer: values.type === 'judgment' ? normalizeJudgmentAnswer(values.answer) : values.answer,
|
||||
};
|
||||
|
||||
if (editingQuestion) {
|
||||
|
||||
@@ -78,7 +78,7 @@ export const OptionList = ({ type, options, value, onChange, disabled }: OptionL
|
||||
? 'bg-[#00897B] border-[#00897B] text-white'
|
||||
: 'bg-white border-gray-300 text-gray-500'}
|
||||
`}>
|
||||
{type === 'judgment' ? (index === 0 ? 'T' : 'F') : getOptionLabel(index)}
|
||||
{type === 'judgment' ? (index === 0 ? '对' : '错') : getOptionLabel(index)}
|
||||
</div>
|
||||
|
||||
{/* 选项内容 */}
|
||||
|
||||
@@ -24,24 +24,24 @@ export const QuizFooter = ({
|
||||
const isLast = current === total - 1;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 px-2 py-1.5 safe-area-bottom z-30 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]">
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 px-2 py-2 safe-area-bottom z-30 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]">
|
||||
<div className="max-w-md mx-auto flex items-center justify-between">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<LeftOutlined />}
|
||||
onClick={onPrev}
|
||||
disabled={isFirst}
|
||||
className={`flex items-center text-gray-600 hover:text-[#00897B] text-xs ${isFirst ? 'opacity-30' : ''}`}
|
||||
className={`flex items-center text-gray-600 hover:text-[#00897B] text-sm ${isFirst ? 'opacity-30' : ''}`}
|
||||
>
|
||||
上一题
|
||||
</Button>
|
||||
|
||||
<div
|
||||
onClick={onOpenSheet}
|
||||
className="flex flex-col items-center justify-center -mt-4 bg-white rounded-full h-11 w-11 shadow-lg border border-gray-100 cursor-pointer active:scale-95 transition-transform"
|
||||
className="flex flex-col items-center justify-center -mt-5 bg-white rounded-full h-14 w-14 shadow-lg border border-gray-100 cursor-pointer active:scale-95 transition-transform"
|
||||
>
|
||||
<AppstoreOutlined className="text-sm text-[#00897B] mb-0.5" />
|
||||
<span className="text-[10px] text-gray-500 scale-90">
|
||||
<AppstoreOutlined className="text-base text-[#00897B] mb-0.5" />
|
||||
<span className="text-[12px] text-gray-500">
|
||||
{answeredCount}/{total}
|
||||
</span>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ export const QuizFooter = ({
|
||||
<Button
|
||||
type="text"
|
||||
onClick={onSubmit}
|
||||
className="flex items-center text-[#00897B] font-medium hover:bg-teal-50 text-xs"
|
||||
className="flex items-center text-[#00897B] font-medium hover:bg-teal-50 text-sm"
|
||||
>
|
||||
提交 <RightOutlined />
|
||||
</Button>
|
||||
@@ -58,7 +58,7 @@ export const QuizFooter = ({
|
||||
<Button
|
||||
type="text"
|
||||
onClick={onNext}
|
||||
className="flex items-center text-gray-600 hover:text-[#00897B] text-xs"
|
||||
className="flex items-center text-gray-600 hover:text-[#00897B] text-sm"
|
||||
>
|
||||
下一题 <RightOutlined />
|
||||
</Button>
|
||||
|
||||
78
test/user-default-group.test.ts
Normal file
78
test/user-default-group.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.DB_PATH = ':memory:';
|
||||
|
||||
const jsonFetch = async (
|
||||
baseUrl: string,
|
||||
path: string,
|
||||
options?: { method?: string; body?: unknown },
|
||||
) => {
|
||||
const res = await fetch(`${baseUrl}${path}`, {
|
||||
method: options?.method ?? 'GET',
|
||||
headers: options?.body ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: options?.body ? JSON.stringify(options.body) : undefined,
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
let json: any = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return { status: res.status, json, text };
|
||||
};
|
||||
|
||||
test('新用户创建后默认加入“全体用户”系统组(含 validate 自动创建与管理员导入)', async () => {
|
||||
const { initDatabase } = await import('../api/database');
|
||||
await initDatabase();
|
||||
|
||||
const { app } = await import('../api/server');
|
||||
const server = app.listen(0);
|
||||
|
||||
try {
|
||||
const addr = server.address();
|
||||
assert.ok(addr && typeof addr === 'object');
|
||||
const baseUrl = `http://127.0.0.1:${addr.port}`;
|
||||
|
||||
// 1) validate 自动创建用户
|
||||
const created = await jsonFetch(baseUrl, '/api/users/validate', {
|
||||
method: 'POST',
|
||||
body: { name: '默认组用户', phone: '13800139001', password: '' },
|
||||
});
|
||||
assert.equal(created.status, 200);
|
||||
assert.equal(created.json?.success, true);
|
||||
const userId = created.json?.data?.id as string;
|
||||
assert.ok(userId);
|
||||
|
||||
const list1 = await jsonFetch(baseUrl, '/api/admin/users?page=1&limit=20');
|
||||
assert.equal(list1.status, 200);
|
||||
assert.equal(list1.json?.success, true);
|
||||
const row1 = (list1.json?.data as any[]).find((u) => u.id === userId);
|
||||
assert.ok(row1);
|
||||
assert.ok(Array.isArray(row1.groups));
|
||||
assert.ok(row1.groups.some((g: any) => g.isSystem === 1 || g.isSystem === true));
|
||||
|
||||
// 2) 管理员导入用户(模拟 Excel:走 importUsers 的解析逻辑不方便,这里直接调用 createUser 接口覆盖管理端创建路径)
|
||||
const createdAdmin = await jsonFetch(baseUrl, '/api/admin/users', {
|
||||
method: 'POST',
|
||||
body: { name: '管理员创建用户', phone: '13800139002', password: '', groupIds: [] },
|
||||
});
|
||||
assert.equal(createdAdmin.status, 200);
|
||||
assert.equal(createdAdmin.json?.success, true);
|
||||
const userId2 = createdAdmin.json?.data?.id as string;
|
||||
assert.ok(userId2);
|
||||
|
||||
const list2 = await jsonFetch(baseUrl, '/api/admin/users?page=1&limit=50');
|
||||
assert.equal(list2.status, 200);
|
||||
assert.equal(list2.json?.success, true);
|
||||
const row2 = (list2.json?.data as any[]).find((u) => u.id === userId2);
|
||||
assert.ok(row2);
|
||||
assert.ok(Array.isArray(row2.groups));
|
||||
assert.ok(row2.groups.some((g: any) => g.isSystem === 1 || g.isSystem === true));
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
10
tsconfig.api.json
Normal file
10
tsconfig.api.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./api",
|
||||
"outDir": "./dist/api",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["api/**/*"],
|
||||
"exclude": ["node_modules", "dist", "src", "test", "deploy_bundle"]
|
||||
}
|
||||
@@ -23,7 +23,4 @@ export default defineConfig({
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
define: {
|
||||
global: 'globalThis',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user