@octo-cyber/quiz-engine 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/controllers/attempt.controller.d.ts +3 -0
  2. package/dist/controllers/attempt.controller.d.ts.map +1 -0
  3. package/dist/controllers/attempt.controller.js +83 -0
  4. package/dist/controllers/attempt.controller.js.map +1 -0
  5. package/dist/controllers/category.controller.d.ts +3 -0
  6. package/dist/controllers/category.controller.d.ts.map +1 -0
  7. package/dist/controllers/category.controller.js +38 -0
  8. package/dist/controllers/category.controller.js.map +1 -0
  9. package/dist/controllers/exam.controller.d.ts +3 -0
  10. package/dist/controllers/exam.controller.d.ts.map +1 -0
  11. package/dist/controllers/exam.controller.js +48 -0
  12. package/dist/controllers/exam.controller.js.map +1 -0
  13. package/dist/controllers/index.d.ts +7 -0
  14. package/dist/controllers/index.d.ts.map +1 -0
  15. package/dist/controllers/index.js +16 -0
  16. package/dist/controllers/index.js.map +1 -0
  17. package/dist/controllers/paper.controller.d.ts +3 -0
  18. package/dist/controllers/paper.controller.d.ts.map +1 -0
  19. package/dist/controllers/paper.controller.js +70 -0
  20. package/dist/controllers/paper.controller.js.map +1 -0
  21. package/dist/controllers/question.controller.d.ts +3 -0
  22. package/dist/controllers/question.controller.d.ts.map +1 -0
  23. package/dist/controllers/question.controller.js +59 -0
  24. package/dist/controllers/question.controller.js.map +1 -0
  25. package/dist/controllers/statistics.controller.d.ts +3 -0
  26. package/dist/controllers/statistics.controller.d.ts.map +1 -0
  27. package/dist/controllers/statistics.controller.js +37 -0
  28. package/dist/controllers/statistics.controller.js.map +1 -0
  29. package/dist/entities/index.d.ts +24 -0
  30. package/dist/entities/index.d.ts.map +1 -0
  31. package/dist/entities/index.js +43 -0
  32. package/dist/entities/index.js.map +1 -0
  33. package/dist/entities/quiz-answer.entity.d.ts +19 -0
  34. package/dist/entities/quiz-answer.entity.d.ts.map +1 -0
  35. package/dist/entities/quiz-answer.entity.js +81 -0
  36. package/dist/entities/quiz-answer.entity.js.map +1 -0
  37. package/dist/entities/quiz-attempt.entity.d.ts +17 -0
  38. package/dist/entities/quiz-attempt.entity.d.ts.map +1 -0
  39. package/dist/entities/quiz-attempt.entity.js +80 -0
  40. package/dist/entities/quiz-attempt.entity.js.map +1 -0
  41. package/dist/entities/quiz-category.entity.d.ts +10 -0
  42. package/dist/entities/quiz-category.entity.d.ts.map +1 -0
  43. package/dist/entities/quiz-category.entity.js +55 -0
  44. package/dist/entities/quiz-category.entity.js.map +1 -0
  45. package/dist/entities/quiz-exam.entity.d.ts +25 -0
  46. package/dist/entities/quiz-exam.entity.d.ts.map +1 -0
  47. package/dist/entities/quiz-exam.entity.js +99 -0
  48. package/dist/entities/quiz-exam.entity.js.map +1 -0
  49. package/dist/entities/quiz-paper-question.entity.d.ts +12 -0
  50. package/dist/entities/quiz-paper-question.entity.d.ts.map +1 -0
  51. package/dist/entities/quiz-paper-question.entity.js +58 -0
  52. package/dist/entities/quiz-paper-question.entity.js.map +1 -0
  53. package/dist/entities/quiz-paper.entity.d.ts +18 -0
  54. package/dist/entities/quiz-paper.entity.d.ts.map +1 -0
  55. package/dist/entities/quiz-paper.entity.js +75 -0
  56. package/dist/entities/quiz-paper.entity.js.map +1 -0
  57. package/dist/entities/quiz-question.entity.d.ts +28 -0
  58. package/dist/entities/quiz-question.entity.d.ts.map +1 -0
  59. package/dist/entities/quiz-question.entity.js +107 -0
  60. package/dist/entities/quiz-question.entity.js.map +1 -0
  61. package/dist/index.d.ts +27 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +66 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/quiz-engine.module.d.ts +8 -0
  66. package/dist/quiz-engine.module.d.ts.map +1 -0
  67. package/dist/quiz-engine.module.js +45 -0
  68. package/dist/quiz-engine.module.js.map +1 -0
  69. package/dist/schemas/attempt.schema.d.ts +47 -0
  70. package/dist/schemas/attempt.schema.d.ts.map +1 -0
  71. package/dist/schemas/attempt.schema.js +19 -0
  72. package/dist/schemas/attempt.schema.js.map +1 -0
  73. package/dist/schemas/category.schema.d.ts +36 -0
  74. package/dist/schemas/category.schema.d.ts.map +1 -0
  75. package/dist/schemas/category.schema.js +12 -0
  76. package/dist/schemas/category.schema.js.map +1 -0
  77. package/dist/schemas/exam.schema.d.ts +70 -0
  78. package/dist/schemas/exam.schema.d.ts.map +1 -0
  79. package/dist/schemas/exam.schema.js +20 -0
  80. package/dist/schemas/exam.schema.js.map +1 -0
  81. package/dist/schemas/paper.schema.d.ts +71 -0
  82. package/dist/schemas/paper.schema.d.ts.map +1 -0
  83. package/dist/schemas/paper.schema.js +26 -0
  84. package/dist/schemas/paper.schema.js.map +1 -0
  85. package/dist/schemas/question.schema.d.ts +147 -0
  86. package/dist/schemas/question.schema.d.ts.map +1 -0
  87. package/dist/schemas/question.schema.js +32 -0
  88. package/dist/schemas/question.schema.js.map +1 -0
  89. package/dist/services/attempt.service.d.ts +33 -0
  90. package/dist/services/attempt.service.d.ts.map +1 -0
  91. package/dist/services/attempt.service.js +197 -0
  92. package/dist/services/attempt.service.js.map +1 -0
  93. package/dist/services/category.service.d.ts +14 -0
  94. package/dist/services/category.service.d.ts.map +1 -0
  95. package/dist/services/category.service.js +74 -0
  96. package/dist/services/category.service.js.map +1 -0
  97. package/dist/services/exam.service.d.ts +17 -0
  98. package/dist/services/exam.service.d.ts.map +1 -0
  99. package/dist/services/exam.service.js +92 -0
  100. package/dist/services/exam.service.js.map +1 -0
  101. package/dist/services/grade.service.d.ts +16 -0
  102. package/dist/services/grade.service.d.ts.map +1 -0
  103. package/dist/services/grade.service.js +75 -0
  104. package/dist/services/grade.service.js.map +1 -0
  105. package/dist/services/index.d.ts +8 -0
  106. package/dist/services/index.d.ts.map +1 -0
  107. package/dist/services/index.js +18 -0
  108. package/dist/services/index.js.map +1 -0
  109. package/dist/services/paper.service.d.ts +32 -0
  110. package/dist/services/paper.service.d.ts.map +1 -0
  111. package/dist/services/paper.service.js +157 -0
  112. package/dist/services/paper.service.js.map +1 -0
  113. package/dist/services/question.service.d.ts +30 -0
  114. package/dist/services/question.service.d.ts.map +1 -0
  115. package/dist/services/question.service.js +155 -0
  116. package/dist/services/question.service.js.map +1 -0
  117. package/dist/services/statistics.service.d.ts +43 -0
  118. package/dist/services/statistics.service.d.ts.map +1 -0
  119. package/dist/services/statistics.service.js +134 -0
  120. package/dist/services/statistics.service.js.map +1 -0
  121. package/package.json +85 -0
  122. package/web/index.ts +51 -0
  123. package/web/manifest.ts +36 -0
  124. package/web/messages/en-US.json +143 -0
  125. package/web/messages/zh-CN.json +143 -0
  126. package/web/pages/ExamRoomPage.tsx +289 -0
  127. package/web/pages/ExamsPage.tsx +248 -0
  128. package/web/pages/PapersPage.tsx +202 -0
  129. package/web/pages/QuestionBankPage.tsx +263 -0
  130. package/web/pages/StatisticsPage.tsx +178 -0
  131. package/web/services/attempt-service.ts +53 -0
  132. package/web/services/category-service.ts +26 -0
  133. package/web/services/exam-service.ts +31 -0
  134. package/web/services/paper-service.ts +50 -0
  135. package/web/services/question-service.ts +36 -0
  136. package/web/services/statistics-service.ts +28 -0
  137. package/web/stores/quiz-store.ts +31 -0
  138. package/web/types/quiz.ts +166 -0
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.StatisticsService = void 0;
10
+ const core_1 = require("@octo-cyber/core");
11
+ const quiz_attempt_entity_js_1 = require("../entities/quiz-attempt.entity.js");
12
+ const quiz_answer_entity_js_1 = require("../entities/quiz-answer.entity.js");
13
+ const quiz_exam_entity_js_1 = require("../entities/quiz-exam.entity.js");
14
+ const quiz_paper_entity_js_1 = require("../entities/quiz-paper.entity.js");
15
+ let StatisticsService = class StatisticsService {
16
+ attemptRepo;
17
+ answerRepo;
18
+ examRepo;
19
+ paperRepo;
20
+ initialized = false;
21
+ initialize() {
22
+ const db = core_1.Container.get(core_1.DatabaseService);
23
+ this.attemptRepo = db.getRepository(quiz_attempt_entity_js_1.QuizAttempt);
24
+ this.answerRepo = db.getRepository(quiz_answer_entity_js_1.QuizAnswer);
25
+ this.examRepo = db.getRepository(quiz_exam_entity_js_1.QuizExam);
26
+ this.paperRepo = db.getRepository(quiz_paper_entity_js_1.QuizPaper);
27
+ this.initialized = true;
28
+ }
29
+ ensureInitialized() {
30
+ if (!this.initialized) {
31
+ try {
32
+ this.initialize();
33
+ }
34
+ catch {
35
+ throw new core_1.AppError('StatisticsService not initialized', 500);
36
+ }
37
+ }
38
+ }
39
+ async getExamStats(examId) {
40
+ this.ensureInitialized();
41
+ const exam = await this.examRepo.findOne({ where: { id: examId } });
42
+ if (!exam)
43
+ throw core_1.AppError.notFound('Exam not found');
44
+ const paper = await this.paperRepo.findOne({ where: { id: exam.paperId } });
45
+ const passingScore = paper?.passingScore ?? 60;
46
+ const attempts = await this.attemptRepo.find({ where: { examId } });
47
+ const graded = attempts.filter((a) => a.status === 'GRADED');
48
+ const submitted = attempts.filter((a) => a.status === 'SUBMITTED' || a.status === 'GRADED');
49
+ const scores = graded.map((a) => a.totalScore);
50
+ const averageScore = scores.length > 0
51
+ ? scores.reduce((s, v) => s + v, 0) / scores.length
52
+ : 0;
53
+ const highestScore = scores.length > 0 ? Math.max(...scores) : 0;
54
+ const lowestScore = scores.length > 0 ? Math.min(...scores) : 0;
55
+ const passCount = graded.filter((a) => a.isPassed || a.totalScore >= passingScore).length;
56
+ const passRate = graded.length > 0 ? passCount / graded.length : 0;
57
+ const distribution = this.buildScoreDistribution(scores, paper?.totalScore ?? 100);
58
+ return {
59
+ examId,
60
+ title: exam.title,
61
+ totalAttempts: attempts.length,
62
+ submittedAttempts: submitted.length,
63
+ gradedAttempts: graded.length,
64
+ averageScore: Math.round(averageScore * 10) / 10,
65
+ highestScore,
66
+ lowestScore,
67
+ passRate: Math.round(passRate * 100) / 100,
68
+ scoreDistribution: distribution,
69
+ };
70
+ }
71
+ async getUserStats(userId) {
72
+ this.ensureInitialized();
73
+ const attempts = await this.attemptRepo.find({
74
+ where: { userId },
75
+ order: { createdAt: 'DESC' },
76
+ });
77
+ const graded = attempts.filter((a) => a.status === 'GRADED');
78
+ const scores = graded.map((a) => a.totalScore);
79
+ const averageScore = scores.length > 0
80
+ ? scores.reduce((s, v) => s + v, 0) / scores.length
81
+ : 0;
82
+ const highestScore = scores.length > 0 ? Math.max(...scores) : 0;
83
+ const passCount = graded.filter((a) => a.isPassed).length;
84
+ return {
85
+ userId,
86
+ totalAttempts: attempts.length,
87
+ averageScore: Math.round(averageScore * 10) / 10,
88
+ highestScore,
89
+ passCount,
90
+ passRate: graded.length > 0 ? Math.round((passCount / graded.length) * 100) / 100 : 0,
91
+ recentAttempts: attempts.slice(0, 10),
92
+ };
93
+ }
94
+ async getRanking(examId, limit = 20) {
95
+ this.ensureInitialized();
96
+ const attempts = await this.attemptRepo.find({
97
+ where: { examId, status: 'GRADED' },
98
+ order: { totalScore: 'DESC' },
99
+ });
100
+ const seen = new Set();
101
+ const ranked = [];
102
+ let rank = 1;
103
+ for (const a of attempts) {
104
+ if (!seen.has(a.userId)) {
105
+ seen.add(a.userId);
106
+ ranked.push({ userId: a.userId, score: a.totalScore, rank: rank++ });
107
+ }
108
+ if (ranked.length >= limit)
109
+ break;
110
+ }
111
+ return ranked;
112
+ }
113
+ buildScoreDistribution(scores, totalScore) {
114
+ const buckets = [
115
+ { label: '0-59', min: 0, max: 59 },
116
+ { label: '60-69', min: 60, max: 69 },
117
+ { label: '70-79', min: 70, max: 79 },
118
+ { label: '80-89', min: 80, max: 89 },
119
+ { label: '90-100', min: 90, max: 100 },
120
+ ];
121
+ return buckets.map(({ label, min, max }) => ({
122
+ range: label,
123
+ count: scores.filter((s) => {
124
+ const pct = totalScore > 0 ? (s / totalScore) * 100 : 0;
125
+ return pct >= min && pct <= max;
126
+ }).length,
127
+ }));
128
+ }
129
+ };
130
+ exports.StatisticsService = StatisticsService;
131
+ exports.StatisticsService = StatisticsService = __decorate([
132
+ (0, core_1.Service)()
133
+ ], StatisticsService);
134
+ //# sourceMappingURL=statistics.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statistics.service.js","sourceRoot":"","sources":["../../src/services/statistics.service.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@octo-cyber/quiz-engine",
3
+ "version": "0.5.0",
4
+ "description": "Octo quiz engine module — question bank, smart paper generation, online exam, auto grading, statistics",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./web": {
14
+ "types": "./web/index.ts",
15
+ "import": "./web/index.ts",
16
+ "default": "./web/index.ts"
17
+ },
18
+ "./web/pages/*": {
19
+ "types": "./web/pages/*.tsx",
20
+ "import": "./web/pages/*.tsx",
21
+ "default": "./web/pages/*.tsx"
22
+ }
23
+ },
24
+ "dependencies": {
25
+ "zod": "^3.24.0",
26
+ "@octo-cyber/core": "^0.5.4",
27
+ "@octo-cyber/auth": "^0.5.6"
28
+ },
29
+ "peerDependencies": {
30
+ "react": "^19.0.0",
31
+ "react-dom": "^19.0.0",
32
+ "next-intl": "^4.0.0",
33
+ "lucide-react": ">=0.400.0",
34
+ "sonner": "^2.0.0",
35
+ "zustand": "^5.0.0",
36
+ "@octo-cyber/ui": "^0.5.3",
37
+ "@octo-cyber/layout": "^0.5.2"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@octo-cyber/ui": {
41
+ "optional": true
42
+ },
43
+ "@octo-cyber/layout": {
44
+ "optional": true
45
+ },
46
+ "react": {
47
+ "optional": true
48
+ },
49
+ "react-dom": {
50
+ "optional": true
51
+ },
52
+ "next-intl": {
53
+ "optional": true
54
+ },
55
+ "lucide-react": {
56
+ "optional": true
57
+ },
58
+ "sonner": {
59
+ "optional": true
60
+ },
61
+ "zustand": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "devDependencies": {
66
+ "@types/react": "^19",
67
+ "@types/react-dom": "^19",
68
+ "typescript": "^5.8.0",
69
+ "vitest": "^3.0.0"
70
+ },
71
+ "files": [
72
+ "dist",
73
+ "web"
74
+ ],
75
+ "publishConfig": {
76
+ "registry": "https://registry.npmjs.org/"
77
+ },
78
+ "license": "MIT",
79
+ "scripts": {
80
+ "build": "tsc -p tsconfig.build.json",
81
+ "dev": "tsc -p tsconfig.build.json --watch",
82
+ "test": "vitest run",
83
+ "typecheck": "tsc --noEmit"
84
+ }
85
+ }
package/web/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @octo-cyber/quiz-engine — Frontend entry point.
3
+ */
4
+
5
+ // Manifest
6
+ export { quizEngineFrontendManifest } from './manifest'
7
+
8
+ // Pages
9
+ export { default as QuestionBankPage } from './pages/QuestionBankPage'
10
+ export { default as PapersPage } from './pages/PapersPage'
11
+ export { default as ExamsPage } from './pages/ExamsPage'
12
+ export { default as ExamRoomPage } from './pages/ExamRoomPage'
13
+ export { default as StatisticsPage } from './pages/StatisticsPage'
14
+
15
+ // Services
16
+ export { categoryApi } from './services/category-service'
17
+ export { questionApi } from './services/question-service'
18
+ export { paperApi } from './services/paper-service'
19
+ export { examApi } from './services/exam-service'
20
+ export { attemptApi } from './services/attempt-service'
21
+ export { statisticsApi } from './services/statistics-service'
22
+
23
+ // Store
24
+ export { useQuizStore } from './stores/quiz-store'
25
+
26
+ // Types
27
+ export type {
28
+ QuizCategory,
29
+ QuizQuestion,
30
+ QuestionType,
31
+ QuizOption,
32
+ QuizPaper,
33
+ PaperStatus,
34
+ QuizPaperQuestion,
35
+ PaperWithQuestions,
36
+ QuizExam,
37
+ ExamStatus,
38
+ QuizAttempt,
39
+ AttemptStatus,
40
+ QuizAnswer,
41
+ AttemptDetail,
42
+ PaginatedResponse,
43
+ ExamStats,
44
+ UserStats,
45
+ RankingEntry,
46
+ SmartGenerateRule,
47
+ } from './types/quiz'
48
+
49
+ // i18n messages
50
+ export { default as quizEngineMessagesZhCN } from './messages/zh-CN.json'
51
+ export { default as quizEngineMessagesEnUS } from './messages/en-US.json'
@@ -0,0 +1,36 @@
1
+ import type { IFrontendManifest } from '@octo-cyber/core';
2
+
3
+ export const quizEngineFrontendManifest: IFrontendManifest = {
4
+ moduleId: 'quiz-engine',
5
+ icon: 'GraduationCap',
6
+ color: 'violet',
7
+ position: 'main',
8
+ titleKey: 'quizEngine.title',
9
+ descriptionKey: 'quizEngine.description',
10
+ pages: [
11
+ {
12
+ path: '/quiz/questions',
13
+ titleKey: 'quizEngine.pages.questionBank',
14
+ icon: 'BookOpen',
15
+ component: '@octo-cyber/quiz-engine/web/pages/QuestionBankPage',
16
+ },
17
+ {
18
+ path: '/quiz/papers',
19
+ titleKey: 'quizEngine.pages.papers',
20
+ icon: 'FileText',
21
+ component: '@octo-cyber/quiz-engine/web/pages/PapersPage',
22
+ },
23
+ {
24
+ path: '/quiz/exams',
25
+ titleKey: 'quizEngine.pages.exams',
26
+ icon: 'ClipboardList',
27
+ component: '@octo-cyber/quiz-engine/web/pages/ExamsPage',
28
+ },
29
+ {
30
+ path: '/quiz/stats',
31
+ titleKey: 'quizEngine.pages.statistics',
32
+ icon: 'BarChart2',
33
+ component: '@octo-cyber/quiz-engine/web/pages/StatisticsPage',
34
+ },
35
+ ],
36
+ };
@@ -0,0 +1,143 @@
1
+ {
2
+ "quizEngine": {
3
+ "title": "Online Exam",
4
+ "description": "Question bank, paper builder, online exams and result statistics",
5
+ "pages": {
6
+ "questionBank": "Question Bank",
7
+ "papers": "Papers",
8
+ "exams": "Exams",
9
+ "statistics": "Statistics"
10
+ },
11
+ "question": {
12
+ "title": "Question Bank",
13
+ "description": "Manage questions: single choice, multiple choice, true/false, fill-blank, essay",
14
+ "create": "New Question",
15
+ "createTitle": "New Question",
16
+ "searchPlaceholder": "Search question content...",
17
+ "filterType": "Type",
18
+ "filterCategory": "Category",
19
+ "filterDifficulty": "Difficulty",
20
+ "noData": "No questions yet",
21
+ "createSuccess": "Question created",
22
+ "createFailed": "Failed to create question",
23
+ "deleteSuccess": "Question deleted",
24
+ "deleteFailed": "Failed to delete question",
25
+ "contentPlaceholder": "Enter question content (Markdown supported)...",
26
+ "types": {
27
+ "single_choice": "Single",
28
+ "multiple_choice": "Multiple",
29
+ "true_false": "T/F",
30
+ "fill_blank": "Fill",
31
+ "essay": "Essay"
32
+ },
33
+ "col": {
34
+ "type": "Type",
35
+ "content": "Content",
36
+ "difficulty": "Difficulty",
37
+ "score": "Score",
38
+ "useCount": "Used"
39
+ }
40
+ },
41
+ "paper": {
42
+ "title": "Papers",
43
+ "description": "Create and manage exam papers",
44
+ "create": "New Paper",
45
+ "createTitle": "New Paper",
46
+ "titlePlaceholder": "Enter paper title...",
47
+ "noData": "No papers yet",
48
+ "createSuccess": "Paper created",
49
+ "createFailed": "Failed to create paper",
50
+ "deleteSuccess": "Paper deleted",
51
+ "deleteFailed": "Failed to delete paper",
52
+ "publishSuccess": "Paper published",
53
+ "publishFailed": "Failed to publish paper",
54
+ "publish": "Publish",
55
+ "titleRequired": "Please enter a title",
56
+ "totalScore": "Total",
57
+ "passingScore": "Pass",
58
+ "timeLimit": "Time",
59
+ "statuses": {
60
+ "draft": "Draft",
61
+ "published": "Published",
62
+ "archived": "Archived"
63
+ },
64
+ "col": {
65
+ "title": "Title",
66
+ "description": "Description"
67
+ }
68
+ },
69
+ "exam": {
70
+ "title": "Exams",
71
+ "description": "Create exam sessions with time windows and rules",
72
+ "create": "New Exam",
73
+ "createTitle": "New Exam",
74
+ "noData": "No exams yet",
75
+ "createSuccess": "Exam created",
76
+ "createFailed": "Failed to create exam",
77
+ "deleteSuccess": "Exam deleted",
78
+ "deleteFailed": "Failed to delete exam",
79
+ "statusUpdated": "Status updated",
80
+ "updateFailed": "Update failed",
81
+ "formRequired": "Please fill required fields",
82
+ "selectPaper": "Select a paper",
83
+ "maxAttempts": "Max attempts",
84
+ "shuffleQuestions": "Shuffle questions",
85
+ "showAnswerAfterSubmit": "Show answers after submit",
86
+ "startTime": "Start time",
87
+ "endTime": "End time",
88
+ "schedule": "Schedule",
89
+ "start": "Start",
90
+ "end": "End",
91
+ "statuses": {
92
+ "draft": "Draft",
93
+ "scheduled": "Scheduled",
94
+ "ongoing": "Ongoing",
95
+ "ended": "Ended",
96
+ "cancelled": "Cancelled"
97
+ },
98
+ "col": {
99
+ "title": "Title",
100
+ "paper": "Paper",
101
+ "description": "Description"
102
+ }
103
+ },
104
+ "examRoom": {
105
+ "startFailed": "Failed to start exam, please try again",
106
+ "loadFailed": "Failed to load exam",
107
+ "submitted": "Exam submitted",
108
+ "yourScore": "Your score",
109
+ "passed": "Passed",
110
+ "failed": "Failed",
111
+ "question": "Q",
112
+ "score": "Score",
113
+ "answered": "Answered {count}/{total}",
114
+ "submit": "Submit",
115
+ "submitSuccess": "Submitted! Score: {score}",
116
+ "submitFailed": "Submit failed, please try again",
117
+ "confirmSubmitTitle": "Confirm Submit",
118
+ "confirmSubmitDesc": "You answered {answered}/{total} questions. Submit now?",
119
+ "trueLabel": "True",
120
+ "falseLabel": "False",
121
+ "fillBlankPlaceholder": "Fill in your answer...",
122
+ "essayPlaceholder": "Write your answer..."
123
+ },
124
+ "statistics": {
125
+ "title": "Statistics",
126
+ "description": "View exam statistics, score distribution and ranking",
127
+ "selectExam": "Select exam",
128
+ "selectExamHint": "Select an exam to view statistics",
129
+ "totalAttempts": "Participants",
130
+ "gradedAttempts": "Graded",
131
+ "averageScore": "Average",
132
+ "passRate": "Pass rate",
133
+ "highestScore": "Highest",
134
+ "lowestScore": "Lowest",
135
+ "scoreDistribution": "Score distribution",
136
+ "ranking": "Ranking",
137
+ "noRanking": "No ranking data",
138
+ "rank": "Rank",
139
+ "userId": "User",
140
+ "score": "Score"
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,143 @@
1
+ {
2
+ "quizEngine": {
3
+ "title": "在线考试",
4
+ "description": "题库管理、试卷组卷、在线考试与成绩统计",
5
+ "pages": {
6
+ "questionBank": "题库",
7
+ "papers": "试卷",
8
+ "exams": "考试",
9
+ "statistics": "成绩统计"
10
+ },
11
+ "question": {
12
+ "title": "题库管理",
13
+ "description": "管理题目,支持单选、多选、判断、填空、简答",
14
+ "create": "新建题目",
15
+ "createTitle": "新建题目",
16
+ "searchPlaceholder": "搜索题目内容...",
17
+ "filterType": "题型",
18
+ "filterCategory": "分类",
19
+ "filterDifficulty": "难度",
20
+ "noData": "暂无题目",
21
+ "createSuccess": "题目创建成功",
22
+ "createFailed": "题目创建失败",
23
+ "deleteSuccess": "题目已删除",
24
+ "deleteFailed": "题目删除失败",
25
+ "contentPlaceholder": "输入题目内容,支持 Markdown...",
26
+ "types": {
27
+ "single_choice": "单选",
28
+ "multiple_choice": "多选",
29
+ "true_false": "判断",
30
+ "fill_blank": "填空",
31
+ "essay": "简答"
32
+ },
33
+ "col": {
34
+ "type": "题型",
35
+ "content": "题目内容",
36
+ "difficulty": "难度",
37
+ "score": "分值",
38
+ "useCount": "使用次数"
39
+ }
40
+ },
41
+ "paper": {
42
+ "title": "试卷管理",
43
+ "description": "创建和管理试卷,支持智能组卷",
44
+ "create": "新建试卷",
45
+ "createTitle": "新建试卷",
46
+ "titlePlaceholder": "输入试卷标题...",
47
+ "noData": "暂无试卷",
48
+ "createSuccess": "试卷创建成功",
49
+ "createFailed": "试卷创建失败",
50
+ "deleteSuccess": "试卷已删除",
51
+ "deleteFailed": "试卷删除失败",
52
+ "publishSuccess": "试卷发布成功",
53
+ "publishFailed": "试卷发布失败",
54
+ "publish": "发布",
55
+ "titleRequired": "请输入试卷标题",
56
+ "totalScore": "总分",
57
+ "passingScore": "及格分",
58
+ "timeLimit": "时限",
59
+ "statuses": {
60
+ "draft": "草稿",
61
+ "published": "已发布",
62
+ "archived": "已归档"
63
+ },
64
+ "col": {
65
+ "title": "试卷名称",
66
+ "description": "描述"
67
+ }
68
+ },
69
+ "exam": {
70
+ "title": "考试管理",
71
+ "description": "创建考试活动,设置时间和规则",
72
+ "create": "新建考试",
73
+ "createTitle": "新建考试",
74
+ "noData": "暂无考试",
75
+ "createSuccess": "考试创建成功",
76
+ "createFailed": "考试创建失败",
77
+ "deleteSuccess": "考试已删除",
78
+ "deleteFailed": "考试删除失败",
79
+ "statusUpdated": "状态已更新",
80
+ "updateFailed": "更新失败",
81
+ "formRequired": "请填写必填项",
82
+ "selectPaper": "选择试卷",
83
+ "maxAttempts": "最大次数",
84
+ "shuffleQuestions": "随机题目顺序",
85
+ "showAnswerAfterSubmit": "提交后展示答案",
86
+ "startTime": "开始时间",
87
+ "endTime": "结束时间",
88
+ "schedule": "发布",
89
+ "start": "开始",
90
+ "end": "结束",
91
+ "statuses": {
92
+ "draft": "草稿",
93
+ "scheduled": "已发布",
94
+ "ongoing": "进行中",
95
+ "ended": "已结束",
96
+ "cancelled": "已取消"
97
+ },
98
+ "col": {
99
+ "title": "考试名称",
100
+ "paper": "试卷",
101
+ "description": "描述"
102
+ }
103
+ },
104
+ "examRoom": {
105
+ "startFailed": "开始考试失败,请重试",
106
+ "loadFailed": "加载考试信息失败",
107
+ "submitted": "答卷已提交",
108
+ "yourScore": "你的得分",
109
+ "passed": "通过",
110
+ "failed": "未通过",
111
+ "question": "第",
112
+ "score": "分值",
113
+ "answered": "已答 {count}/{total} 题",
114
+ "submit": "提交答卷",
115
+ "submitSuccess": "提交成功,得分: {score}",
116
+ "submitFailed": "提交失败,请重试",
117
+ "confirmSubmitTitle": "确认提交",
118
+ "confirmSubmitDesc": "你已完成 {answered}/{total} 题,确定提交吗?",
119
+ "trueLabel": "正确",
120
+ "falseLabel": "错误",
121
+ "fillBlankPlaceholder": "填写你的答案...",
122
+ "essayPlaceholder": "写下你的回答..."
123
+ },
124
+ "statistics": {
125
+ "title": "成绩统计",
126
+ "description": "查看考试统计、成绩分布和排名",
127
+ "selectExam": "选择考试",
128
+ "selectExamHint": "请选择考试查看统计",
129
+ "totalAttempts": "参与人数",
130
+ "gradedAttempts": "已阅卷",
131
+ "averageScore": "平均分",
132
+ "passRate": "通过率",
133
+ "highestScore": "最高分",
134
+ "lowestScore": "最低分",
135
+ "scoreDistribution": "成绩分布",
136
+ "ranking": "排行榜",
137
+ "noRanking": "暂无排名数据",
138
+ "rank": "排名",
139
+ "userId": "用户",
140
+ "score": "得分"
141
+ }
142
+ }
143
+ }