From d2c250a1b365c4157b1ad172dd4424a71596c138 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Sat, 4 Jul 2026 10:23:29 +0800 Subject: [PATCH] feat(shared): update db schema, i18n messages, rate-limit, auth-session-provider - Update src/shared/db/schema.ts - Update i18n messages for en and zh-CN (error-book, exam-homework, leave, lesson-preparation, notifications, rbac, student, textbooks, diagnostic) - Update src/shared/lib/rate-limit/index.ts and redis-limiter.ts - Update src/shared/components/auth-session-provider.tsx --- .../components/auth-session-provider.tsx | 11 +- src/shared/db/schema.ts | 45 ++- src/shared/i18n/messages/en/error-book.json | 2 +- .../i18n/messages/en/exam-homework.json | 86 +++--- src/shared/i18n/messages/en/leave.json | 20 +- .../i18n/messages/en/lesson-preparation.json | 262 ++++++++++++++++-- .../i18n/messages/en/notifications.json | 2 + src/shared/i18n/messages/en/rbac.json | 158 +++++------ src/shared/i18n/messages/en/student.json | 6 + src/shared/i18n/messages/en/textbooks.json | 10 + .../i18n/messages/zh-CN/diagnostic.json | 8 +- .../i18n/messages/zh-CN/error-book.json | 2 +- .../i18n/messages/zh-CN/exam-homework.json | 86 +++--- src/shared/i18n/messages/zh-CN/leave.json | 20 +- .../messages/zh-CN/lesson-preparation.json | 258 +++++++++++++++-- .../i18n/messages/zh-CN/notifications.json | 2 + src/shared/i18n/messages/zh-CN/rbac.json | 158 +++++------ src/shared/i18n/messages/zh-CN/student.json | 6 + src/shared/i18n/messages/zh-CN/textbooks.json | 10 + src/shared/lib/rate-limit/index.ts | 21 +- src/shared/lib/rate-limit/redis-limiter.ts | 6 +- 21 files changed, 848 insertions(+), 331 deletions(-) diff --git a/src/shared/components/auth-session-provider.tsx b/src/shared/components/auth-session-provider.tsx index f7227f2..af72118 100644 --- a/src/shared/components/auth-session-provider.tsx +++ b/src/shared/components/auth-session-provider.tsx @@ -2,10 +2,17 @@ import * as React from "react" import { SessionProvider } from "next-auth/react" +import type { Session } from "next-auth" -export function AuthSessionProvider({ children }: { children: React.ReactNode }) { +interface AuthSessionProviderProps { + children: React.ReactNode + /** SSR 期间获取的 session,避免 client 端异步获取导致的 hydration mismatch */ + session?: Session | null +} + +export function AuthSessionProvider({ children, session }: AuthSessionProviderProps) { return ( - + {children} ) diff --git a/src/shared/db/schema.ts b/src/shared/db/schema.ts index 0442af4..001bdd3 100644 --- a/src/shared/db/schema.ts +++ b/src/shared/db/schema.ts @@ -1587,8 +1587,9 @@ export const examProctoringEvents = mysqlTable("exam_proctoring_events", { export const knowledgePointMastery = mysqlTable("knowledge_point_mastery", { id: id("id").primaryKey(), - studentId: varchar("student_id", { length: 128 }).notNull().references(() => users.id, { onDelete: "cascade" }), - knowledgePointId: varchar("knowledge_point_id", { length: 128 }).notNull().references(() => knowledgePoints.id, { onDelete: "cascade" }), + // 显式 foreignKey 见表参数(避免自动生成 FK 名过长 > 64 字符) + studentId: varchar("student_id", { length: 128 }).notNull(), + knowledgePointId: varchar("knowledge_point_id", { length: 128 }).notNull(), masteryLevel: decimal("mastery_level", { precision: 5, scale: 2 }).default("0").notNull(), totalQuestions: int("total_questions").default(0).notNull(), correctQuestions: int("correct_questions").default(0).notNull(), @@ -1599,6 +1600,16 @@ export const knowledgePointMastery = mysqlTable("knowledge_point_mastery", { studentKpPk: primaryKey({ columns: [table.studentId, table.knowledgePointId] }), studentIdx: index("mastery_student_idx").on(table.studentId), kpIdx: index("mastery_kp_idx").on(table.knowledgePointId), + studentFk: foreignKey({ + columns: [table.studentId], + foreignColumns: [users.id], + name: "kpm_student_fk", + }).onDelete("cascade"), + kpFk: foreignKey({ + columns: [table.knowledgePointId], + foreignColumns: [knowledgePoints.id], + name: "kpm_kp_fk", + }).onDelete("cascade"), })); export const diagnosticReportStatusEnum = mysqlEnum("report_status", ["draft", "published", "archived"]); @@ -1750,6 +1761,28 @@ export const lessonPlanSubstitutes = mysqlTable("lesson_plan_substitutes", { dateRangeIdx: index("lps_date_range_idx").on(table.startDate, table.endDate), })); +// V5-7:课案-课时绑定表(将课案绑定到具体班级的某个日期/节次) +export const lessonPlanSchedules = mysqlTable("lesson_plan_schedules", { + id: id("id").primaryKey(), + planId: varchar("plan_id", { length: 128 }).notNull().references(() => lessonPlans.id, { onDelete: "cascade" }), + classId: varchar("class_id", { length: 128 }).notNull().references(() => classes.id, { onDelete: "cascade" }), + /** 排课日期 YYYY-MM-DD */ + scheduledDate: date("scheduled_date").notNull(), + /** 节次序号(1-12,对应学校节次安排) */ + period: int("period").notNull(), + /** 关联 class_schedule.id(如使用课表)*/ + classScheduleId: varchar("class_schedule_id", { length: 128 }), + /** 教学时长(分钟),默认 40 */ + durationMin: int("duration_min").default(40).notNull(), + createdBy: varchar("created_by", { length: 128 }).notNull().references(() => users.id), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), +}, (table) => ({ + planIdx: index("lpsc_plan_idx").on(table.planId), + classDateIdx: index("lpsc_class_date_idx").on(table.classId, table.scheduledDate), + teacherDateIdx: index("lpsc_plan_date_idx").on(table.planId, table.scheduledDate), +})); + // --- 25. System Settings (系统设置 - 键值对存储) --- export const systemSettings = mysqlTable("system_settings", { @@ -2115,7 +2148,8 @@ export const lessonPlanFormativeItems = mysqlTable("lesson_plan_formative_items" /** M5 学生作答记录 */ export const lessonPlanFormativeResponses = mysqlTable("lesson_plan_formative_responses", { id: id("id").primaryKey(), - itemId: varchar("item_id", { length: 128 }).notNull().references(() => lessonPlanFormativeItems.id, { onDelete: "cascade" }), + // 显式 foreignKey 见表参数(避免自动生成 FK 名过长 > 64 字符) + itemId: varchar("item_id", { length: 128 }).notNull(), studentId: varchar("student_id", { length: 128 }).notNull().references(() => users.id, { onDelete: "cascade" }), classId: varchar("class_id", { length: 128 }), /** 学生作答(JSON) */ @@ -2129,6 +2163,11 @@ export const lessonPlanFormativeResponses = mysqlTable("lesson_plan_formative_re itemIdx: index("lpfr_item_idx").on(table.itemId), studentIdx: index("lpfr_student_idx").on(table.studentId), classIdx: index("lpfr_class_idx").on(table.classId), + itemFk: foreignKey({ + columns: [table.itemId], + foreignColumns: [lessonPlanFormativeItems.id], + name: "lpfr_item_fk", + }).onDelete("cascade"), })); // --- 31. Lesson Plan Analytics (M10 备课分析仪表盘) --- diff --git a/src/shared/i18n/messages/en/error-book.json b/src/shared/i18n/messages/en/error-book.json index 8964320..60c1798 100644 --- a/src/shared/i18n/messages/en/error-book.json +++ b/src/shared/i18n/messages/en/error-book.json @@ -283,7 +283,7 @@ "reviewRecorded": "Review recorded", "archived": "Error archived", "deleted": "Error deleted", - "collected": "Collected {{count}} errors", + "collected": "Collected {count} errors", "noNewErrors": "No new errors to collect", "addFailed": "Failed to add error", "saveFailed": "Save failed", diff --git a/src/shared/i18n/messages/en/exam-homework.json b/src/shared/i18n/messages/en/exam-homework.json index 3b219ad..e4bde1a 100644 --- a/src/shared/i18n/messages/en/exam-homework.json +++ b/src/shared/i18n/messages/en/exam-homework.json @@ -70,7 +70,7 @@ "3": "Medium", "4": "Med-Hard", "5": "Hard", - "ariaLabel": "Difficulty level {{level}}: {{label}}" + "ariaLabel": "Difficulty level {level}: {label}" }, "actions": { "preview": "Preview Exam", @@ -83,7 +83,7 @@ "archive": "Archive", "delete": "Delete", "deleteConfirmTitle": "Are you absolutely sure?", - "deleteConfirmDescription": "This action cannot be undone. This will permanently delete the exam \"{{title}}\" and remove all associated data.", + "deleteConfirmDescription": "This action cannot be undone. This will permanently delete the exam \"{title}\" and remove all associated data.", "cancel": "Cancel", "deleteSuccess": "Exam deleted successfully", "deleteFailed": "Failed to delete exam", @@ -165,7 +165,7 @@ "richEditorHint": "Switch to rich text editor", "loaded": "loaded", "startHint": "Start by adding questions from the right panel", - "itemsInStructure": "{{count}} items in structure", + "itemsInStructure": "{count} items in structure", "saveSuccess": "Exam draft saved", "saveFailed": "Save failed", "publishSuccess": "Published exam", @@ -280,7 +280,7 @@ "sectionLabel": "Section", "partLabel": "Part", "groupLabel": "Group", - "questionCountSummary": "({{count}} questions, {{score}} pts)", + "questionCountSummary": "({count} questions, {score} pts)", "typeSingleChoice": "Single Choice", "typeMultipleChoice": "Multiple Choice", "typeJudgment": "Judgment", @@ -310,8 +310,8 @@ "taskInterrupted": "Task was interrupted after page refresh, please regenerate", "untitledExam": "Untitled Exam", "queuedSuccess": "Added to background queue, you can continue editing", - "backgroundComplete": "Background generation complete: {{title}}", - "backgroundFailed": "Background generation failed: {{title}}", + "backgroundComplete": "Background generation complete: {title}", + "backgroundFailed": "Background generation failed: {title}", "selectQuestionFirst": "Please select a question first", "questionNotFound": "Selected question not found", "enterRewriteInstruction": "Please enter rewrite instruction", @@ -321,7 +321,7 @@ "pasteSourceFirst": "Please paste the full exam text first" }, "paperPreview": { - "scoreWithUnit": "({{score}} pts)" + "scoreWithUnit": "({score} pts)" }, "actionMessages": { "enterRewriteInstruction": "Please enter rewrite instruction", @@ -361,16 +361,16 @@ "noQuestionText": "(no question text)" }, "card": { - "level": "Lvl {{level}}", - "minutes": "{{count}} min", - "points": "{{count}} pts", - "questions": "{{count}} Questions" + "level": "Lvl {level}", + "minutes": "{count} min", + "points": "{count} pts", + "questions": "{count} Questions" }, "viewer": { "section": "Section", "group": "Group", "score": "Score", - "scoreLabel": "Score: {{score}}", + "scoreLabel": "Score: {score}", "noQuestions": "No questions available.", "unknown": "unknown" }, @@ -379,7 +379,7 @@ "title": "Exam Preview", "generating": "Generating preview...", "fullPreview": "Full exam preview", - "summary": "{{count}} questions · {{subject}} · {{grade}} · {{minutes}} min · {{total}} pts", + "summary": "{count} questions · {subject} · {grade} · {minutes} min · {total} pts", "noPreview": "No preview available", "confirmCreate": "Confirm & Create", "untitledQuestion": "Untitled question", @@ -390,7 +390,7 @@ "label": "Options", "addOption": "Add option", "correct": "Correct", - "markCorrectAria": "Mark option {{id}} as correct answer", + "markCorrectAria": "Mark option {id} as correct answer", "deleteOptionAria": "Delete option" }, "editorExtensions": { @@ -400,7 +400,7 @@ "group": { "titlePlaceholder": "Group title (e.g. I. Multiple Choice)", "instructionPlaceholder": "Instruction (e.g. 3 pts each, 24 pts total) — optional, total auto-calculated", - "statsSummary": "{{count}} questions · {{score}} pts" + "statsSummary": "{count} questions · {score} pts" }, "question": { "typeSingleChoice": "Single Choice", @@ -416,7 +416,7 @@ "levelVolume": "Volume", "levelPart": "Part", "levelSubVolume": "Sub-volume", - "statsSummary": "{{count}} questions · {{score}} pts" + "statsSummary": "{count} questions · {score} pts" } } }, @@ -430,7 +430,7 @@ "emptyDescription": "You haven't created any assignments yet.", "emptyFilteredDescription": "Try clearing filters or adjusting keywords.", "clearFilters": "Clear filters", - "filterByClass": "Filter by class: {{className}}", + "filterByClass": "Filter by class: {className}", "columns": { "title": "Title", "status": "Status", @@ -488,7 +488,7 @@ }, "take": { "questions": "Questions", - "question": "Question {{index}}", + "question": "Question {index}", "points": "points", "startAssignment": "Start Assignment", "submitAssignment": "Submit Assignment", @@ -505,12 +505,12 @@ "readyDescription": "Click the \"Start Assignment\" button above to begin. Your answers will be saved when you click \"Save Answer\".", "startNow": "Start Now", "back": "Back", - "timedExam": "Timed exam: {{minutes}} minutes", + "timedExam": "Timed exam: {minutes} minutes", "timeRemaining": "Time remaining", "timeUpAutoSubmit": "Time is up, auto-submitting...", "confirmSubmit": "Confirm Submission", "confirmSubmitDescription": "All questions have been answered. Submitted answers cannot be changed. Are you sure you want to submit?", - "unansweredWarning": "You have {{count}} unanswered question(s). Submitted answers cannot be changed. Are you sure you want to submit?", + "unansweredWarning": "You have {count} unanswered question(s). Submitted answers cannot be changed. Are you sure you want to submit?", "cancel": "Cancel", "confirmSubmitAction": "Confirm Submit", "submitSuccess": "Submitted", @@ -521,15 +521,15 @@ "status": "Status", "dueDate": "Due Date", "overdue": "Overdue", - "hoursLeft": "{{hours}} hour(s) left", + "hoursLeft": "{hours} hour(s) left", "lessThanOneHour": "Less than 1 hour left", "attempts": "Attempts", - "attemptsUsed": "{{used}} / {{max}} used", - "attemptsRemaining": "· {{remaining}} remaining", + "attemptsUsed": "{used} / {max} used", + "attemptsRemaining": "· {remaining} remaining", "description": "Description", "noDescription": "No description provided.", "progress": "Progress", - "jumpToQuestion": "Jump to question {{index}}", + "jumpToQuestion": "Jump to question {index}", "answered": "Answered", "unanswered": "Not answered", "yourAnswer": "Your answer", @@ -550,12 +550,12 @@ "scanDescription": "After answering on paper, photograph and upload the full paper", "selectImageFiles": "Please select image files", "uploadFailed": "Upload failed", - "uploadSuccess": "Uploaded {{count}} images", + "uploadSuccess": "Uploaded {count} images", "deleteScan": "Delete", "moveUp": "Move Up", "moveDown": "Move Down", "dragDropHint": "Drag images here, or click to select files", - "pageLabel": "Page {{page}}", + "pageLabel": "Page {page}", "scanDisabled": "Submitted, scan images cannot be modified" }, "grade": { @@ -589,7 +589,7 @@ "scoreLabel": "Score", "addFeedback": "Add Feedback", "hideFeedback": "Hide Feedback", - "feedbackPlaceholder": "Provide feedback for {{name}}...", + "feedbackPlaceholder": "Provide feedback for {name}...", "submitGrades": "Submit Grades", "saving": "Saving...", "gradesSaved": "Grading saved successfully", @@ -600,7 +600,7 @@ "next": "Next", "gradesAutoSaveNote": "Grades are saved automatically when you click Submit. Students will see their grades and feedback immediately after you submit.", "batchAutoGrade": "Batch Auto-Grade", - "batchSelected": "{{count}} submissions selected", + "batchSelected": "{count} submissions selected", "batchSelectAtLeastOne": "Please select at least one submission", "batchFailed": "Batch grading failed", "selectAll": "Select All", @@ -616,9 +616,9 @@ "noScans": "No scan images", "saveFailed": "Save failed", "scanFeedbackPlaceholder": "Grading feedback (optional)...", - "scoreOutOf": "/ {{max}} pts", - "questionsCount": "Questions & Grading ({{count}} items)", - "scanPagesCount": "Student Scan Images ({{count}} pages)" + "scoreOutOf": "/ {max} pts", + "questionsCount": "Questions & Grading ({count} items)", + "scanPagesCount": "Student Scan Images ({count} pages)" }, "review": { "title": "Review", @@ -700,15 +700,15 @@ "questionPreview": "Question Preview", "errorAnalysis": "Error Analysis", "errorRateOverview": "Error Rate Overview", - "errorRateAriaLabel": "Error rate {{rate}}%", + "errorRateAriaLabel": "Error rate {rate}%", "question": "Question", "errors": "Errors", "errorRateLabel": "Error Rate", - "wrongAnswersWithCount": "Wrong Answers ({{count}})", + "wrongAnswersWithCount": "Wrong Answers ({count})", "wrongAnswers": "Wrong Answers", "noWrongAnswers": "No wrong answers recorded.", "studentAnswer": "Student Answer", - "studentCount": "{{count}} student(s)", + "studentCount": "{count} student(s)", "notAnswered": "Not answered", "selectQuestionHint": "Select a question from the left", "selectQuestionHintDesc": "to view error analysis", @@ -721,9 +721,9 @@ "zoomIn": "Zoom in", "rotate": "Rotate", "fullscreen": "Fullscreen", - "pageIndicator": "Page {{current}} / {{total}}", - "answerImageAlt": "Answer image page {{page}}", - "thumbnailAlt": "Thumbnail {{page}}" + "pageIndicator": "Page {current} / {total}", + "answerImageAlt": "Answer image page {page}", + "thumbnailAlt": "Thumbnail {page}" }, "submissions": { "title": "Submissions", @@ -744,26 +744,26 @@ }, "excellent": { "title": "Excellent Submissions", - "description": "Top submissions scoring {{minPercentage}}% or above in this assignment.", + "description": "Top submissions scoring {minPercentage}% or above in this assignment.", "empty": "No excellent submissions yet.", "emptyHint": "They will appear here after grading is complete.", "loading": "Loading excellent submissions...", "loadFailed": "Failed to load", "retry": "Retry", - "rank": "Rank {{rank}}", + "rank": "Rank {rank}", "scoreLabel": "Score", - "scoreValue": "{{score}} / {{max}}", - "percentage": "{{value}}%", + "scoreValue": "{score} / {max}", + "percentage": "{value}%", "lateTag": "Late", "viewDetail": "View Details", - "submittedAt": "Submitted on {{date}}", + "submittedAt": "Submitted on {date}", "studentAnon": "Student" }, "parentExam": { "examsTaken": "Exams Taken", "averageScore": "Average Score", "bestScore": "Best Score", - "examResults": "{{name}}'s Exam Results", + "examResults": "{name}'s Exam Results", "examResultsDescription": "Recent exam scores and performance trends", "noResults": "No exam results", "noResultsHint": "Exam results will appear here once available.", diff --git a/src/shared/i18n/messages/en/leave.json b/src/shared/i18n/messages/en/leave.json index c520973..079d007 100644 --- a/src/shared/i18n/messages/en/leave.json +++ b/src/shared/i18n/messages/en/leave.json @@ -1,12 +1,16 @@ { - "title": "Leave Request", - "title.parent": "Online Leave", - "title.teacher": "Leave Approval", - "title.student": "My Leave Requests", - "description": "Submit a leave request for your child.", - "description.parent": "Submit a leave request for your child. Attendance is auto-synced after homeroom teacher approval.", - "description.teacher": "View and approve leave requests from students in your classes. Approved leaves automatically mark attendance as 'excused'.", - "description.student": "Submit your own leave request. It takes effect after homeroom teacher approval.", + "title": { + "default": "Leave Request", + "parent": "Online Leave", + "teacher": "Leave Approval", + "student": "My Leave Requests" + }, + "description": { + "default": "Submit a leave request for your child.", + "parent": "Submit a leave request for your child. Attendance is auto-synced after homeroom teacher approval.", + "teacher": "View and approve leave requests from students in your classes. Approved leaves automatically mark attendance as 'excused'.", + "student": "Submit your own leave request. It takes effect after homeroom teacher approval." + }, "backToDashboard": "Back to Dashboard", "onlineLeave": "Online Leave Request", "comingSoon": "Coming soon", diff --git a/src/shared/i18n/messages/en/lesson-preparation.json b/src/shared/i18n/messages/en/lesson-preparation.json index 8559866..04bb164 100644 --- a/src/shared/i18n/messages/en/lesson-preparation.json +++ b/src/shared/i18n/messages/en/lesson-preparation.json @@ -27,7 +27,12 @@ "unpublishPlanConfirm": "Students and parents will no longer be able to view this lesson plan after unpublishing. Confirm?", "publishPlanSuccess": "Lesson plan published", "unpublishPlanSuccess": "Lesson plan unpublished", - "viewHomework": "View" + "viewHomework": "View", + "undo": "Undo", + "undoShortcut": "Undo (Ctrl+Z)", + "redo": "Redo", + "redoShortcut": "Redo (Ctrl+Shift+Z)", + "scheduleLesson": "Schedule" }, "status": { "saving": "Saving...", @@ -39,7 +44,15 @@ "published": "Published", "rejected": "Rejected", "archived": "Archived", - "publishedAsHomework": "Published as Homework" + "publishedAsHomework": "Published as Homework", + "retrySave": "Retry Save", + "saveFailed": "Save Failed", + "saveFailedHint": "Please check your network and retry. Edits are preserved.", + "recovered": "Save recovered", + "offline": "Network offline", + "offlineHint": "Edits are kept locally and will auto-save when network restores.", + "offlineBadge": "Offline", + "backOnline": "Back online" }, "blockType": { "objective": "Objective", @@ -130,7 +143,46 @@ "textbookLabel": "Textbook", "chapterLabel": "Chapter", "selectNodeForAnchor": "Select a node to link", - "createNewNode": "Create and link a new node" + "createNewNode": "Create and link a new node", + "autoLayout": "Auto Layout", + "autoLayoutHint": "Auto-arrange nodes based on flow relations", + "stageLabel": "Stage", + "stageNone": "Uncategorized", + "stage": { + "import": "Introduction", + "new_teaching": "New Teaching", + "consolidation": "Consolidation", + "summary": "Summary" + }, + "differentiationLabel": "Differentiation", + "differentiationNone": "None", + "differentiation": { + "basic": "Basic", + "intermediate": "Intermediate", + "advanced": "Advanced" + }, + "consistencyTitle": "Alignment", + "consistencyScore": "Alignment score: {score}", + "consistencyCoverage": "Objective coverage: {covered}/{total}", + "consistencyOpen": "Alignment Check", + "consistencyClose": "Close", + "consistencyNoIssues": "No alignment issues found", + "consistencyObjectiveCount": "Objectives {count}", + "consistencyExerciseCount": "Assessments {count}" + }, + "consistency": { + "title": "Teaching-Assessment Alignment Check", + "score": "Alignment score: {score}", + "coverage": "Objective coverage: {covered}/{total}", + "noIssues": "No alignment issues found", + "code": { + "noObjective": "Missing objective node (consider adding an objective node)", + "noExercise": "Missing assessment node (consider adding an exercise node)", + "objectiveNotAssessed": "Objective \"{title}\" is not covered by any assessment", + "exerciseNoKnowledgePoint": "Assessment \"{title}\" has no linked knowledge points", + "exerciseWithoutObjective": "Assessment \"{title}\" is not linked to any objective", + "objectiveNoKnowledgePoint": "Objective \"{title}\" has no tagged knowledge points" + } }, "readonly": { "title": "View Lesson Plan", @@ -160,6 +212,65 @@ "title": "Grade Lesson Plans", "description": "View lesson plans from teachers in your grade" }, + "library": { + "title": "School Lesson Plan Library", + "description": "Browse outstanding lesson plans shared by teachers in your school", + "empty": "No reference lesson plans available", + "fork": "Copy to my plans", + "forkSuccess": "Copied to your lesson plans", + "byCreator": "By: {creator}", + "noCreator": "Unknown author" + }, + "feedback": { + "title": "AI Teaching Feedback", + "open": "AI Feedback", + "loading": "AI is analyzing the lesson plan...", + "loadFailed": "AI feedback generation failed, please retry later", + "empty": "No feedback suggestions generated", + "summary": "Overall Assessment", + "score": "Score", + "category": { + "strengths": "Strengths", + "improvements": "Improvements", + "alignment": "Alignment", + "differentiation": "Differentiation" + } + }, + "aiDifferentiation": { + "title": "AI Differentiation & Curriculum Check", + "open": "AI Differentiation", + "loading": "AI is analyzing...", + "loadFailed": "AI analysis failed, please try again later", + "empty": "No results generated", + "noKnowledgePoints": "No knowledge points provided, curriculum check unavailable", + "covered": "{count} covered", + "missed": "{count} missed", + "tabs": { + "differentiation": "Differentiation", + "curriculum": "Curriculum Check", + "assessment": "Explainable Assessment" + }, + "level": { + "basic": "Basic", + "intermediate": "Intermediate", + "advanced": "Advanced" + } + }, + "heatmap": { + "title": "Curriculum Coverage Heatmap", + "description": "View how your lesson plans cover textbook knowledge points", + "totalKps": "Total KPs", + "coveredKps": "Covered", + "coverageRate": "Coverage Rate", + "blindSpotTitle": "{count} blind spots", + "andMore": " and more", + "unknownChapter": "Unknown Chapter", + "chapterCoverage": "{covered}/{total} ({rate}%)", + "notCovered": "Not covered", + "planCount": "{count} plans", + "selectTextbook": "Select a textbook to view heatmap", + "noData": "No knowledge points for this textbook" + }, "picker": { "textbookLabel": "Select Textbook", "chapterLabel": "Select Chapter", @@ -172,7 +283,11 @@ "noChapters": "No chapters in this textbook", "selectedChapter": "Selected: {chapter}", "skeletonHint": "A lesson skeleton centered on the textbook content will be generated (10 teaching nodes)", - "errorTextbookChapterRequired": "Please select a textbook and chapter" + "errorTextbookChapterRequired": "Please select a textbook and chapter", + "searchTextbookPlaceholder": "Search textbooks (title/subject/grade)...", + "searchTextbookLabel": "Search textbooks", + "recentSection": "Recently Used", + "searchEmpty": "No matching textbooks" }, "filters": { "searchPlaceholder": "Search title...", @@ -203,6 +318,25 @@ "autoLabel": "Auto version", "revertLabel": "Revert to v{versionNo}" }, + "diff": { + "summary": "Added {added} · Removed {removed} · Modified {modified}", + "comparing": "Comparing v{version} with current", + "noChanges": "No changes between the two versions", + "unchangedCount": "{count} nodes unchanged", + "added": "Added", + "removed": "Removed", + "modified": "Modified", + "unchanged": "Unchanged", + "changedFields": "Changed fields", + "compareWithCurrent": "Compare with current", + "back": "Back", + "field": { + "title": "Title", + "stage": "Stage", + "differentiation": "Differentiation", + "data": "Content" + } + }, "knowledgePoint": { "title": "Select Knowledge Points", "empty": "No knowledge points found. Please create them in the textbook module first.", @@ -232,6 +366,10 @@ "questionId": "Question {id}", "type": { "single_choice": "Single Choice", + "multiple_choice": "Multiple Choice", + "true_false": "True/False", + "short_answer": "Short Answer", + "essay": "Essay", "text": "Fill in Blank", "judgment": "True/False" }, @@ -246,7 +384,8 @@ "difficultyLabel": "Difficulty", "knowledgePointLabel": "Knowledge Points", "stemRequired": "Please enter the question stem", - "addBtn": "Add" + "addBtn": "Add", + "noDetail": "(no detail available)" }, "exercise": { "purposeLabel": "Purpose", @@ -271,7 +410,27 @@ "planNotFound": "Lesson plan not found", "noPermission": "No permission to publish", "homeworkTitle": "{title} - Homework", - "homeworkDescription": "From lesson plan" + "homeworkDescription": "From lesson plan", + "step1": "Select Classes", + "step2": "Preview Homework", + "step3": "Confirm Publish", + "stepIndicator": "Step {current}/{total}: {label}", + "next": "Next", + "back": "Back", + "previewClassCount": "Target Classes", + "previewQuestionCount": "Questions", + "previewTotalScore": "Total Score", + "previewQuestionList": "Question List", + "previewSource": "Source: {source}", + "previewNoStem": "(no stem preview)", + "previewScore": "{score} pts", + "confirmTitle": "Please confirm publish details", + "confirmClassCount": "Target classes: {count}", + "confirmQuestionCount": "Questions: {count}", + "confirmTotalScore": "Total score: {score}", + "confirmAvailableAt": "Available at: {time}", + "confirmDueAt": "Due at: {time}", + "confirmWarning": "Students and parents will receive the homework immediately upon publishing. Please verify carefully." }, "textStudy": { "sourceTextLabel": "Source Text", @@ -336,7 +495,12 @@ "text": "Text-based" }, "contentLabel": "Board Content", - "contentPlaceholder": "Enter board content..." + "contentPlaceholder": "Enter board content...", + "editMode": "Edit", + "previewMode": "Preview", + "editHint": "Tip: Use 2 spaces or 1 Tab to indent levels", + "previewEmpty": "No content yet, switch to edit mode to enter", + "untitled": "Central Topic" }, "import": { "hint": "Design lesson introduction", @@ -365,6 +529,45 @@ "richText": { "placeholder": "Enter content..." }, + "export": { + "title": "Export / Print", + "button": "Export", + "print": "Print", + "variantDetailed": "Detailed", + "variantConcise": "Concise", + "teacher": "Teacher", + "class": "Class", + "totalDuration": "Duration {count} min", + "lastSavedAt": "Last saved", + "empty": "Lesson plan is empty", + "emptySection": "(empty)", + "footerHint": "Lesson plan export - {variant}" + }, + "attachment": { + "title": "Attachment Library", + "add": "Upload File", + "insert": "Insert Attachment", + "libraryLabel": "Attachments in this lesson plan", + "empty": "No attachments yet. Upload one.", + "delete": "Delete", + "remove": "Remove", + "embeddedCount": "{count} attachments embedded", + "createSuccess": "Attachment added", + "deleteSuccess": "Attachment deleted", + "uploadFailed": "Upload failed", + "uploadSuccess": "Upload succeeded", + "type": { + "reference": "Reference", + "material": "Material", + "supplementary": "Supplementary" + }, + "kind": { + "image": "Image", + "audio": "Audio", + "video": "Video", + "file": "File" + } + }, "error": { "getList": "Failed to get lesson plan list", "getOne": "Failed to get lesson plan", @@ -394,7 +597,12 @@ "titleTooLong": "Title cannot exceed 255 characters", "templateRequired": "Please select a template", "invalidDate": "Invalid date format", - "classRequired": "At least one class is required" + "classRequired": "At least one class is required", + "invalidInput": "Invalid input data", + "unauthorized": "Unauthorized operation", + "planIdRequired": "Plan ID is required", + "classIdRequired": "Class ID is required", + "dateRequired": "Date is required" }, "confirm": { "archive": "Archive this lesson plan?", @@ -496,26 +704,6 @@ "recordSuccess": "Response recorded", "recordFailed": "Failed to record response" }, - "attachment": { - "title": "Attachments", - "add": "Add Attachment", - "edit": "Edit", - "delete": "Delete", - "noAttachments": "No attachments", - "name": "Name", - "type": "Type", - "size": "Size", - "url": "URL", - "upload": "Upload", - "download": "Download", - "deleteConfirm": "Are you sure you want to delete this attachment?", - "deleteSuccess": "Attachment deleted", - "deleteFailed": "Failed to delete attachment", - "createSuccess": "Attachment added", - "createFailed": "Failed to add attachment", - "updateSuccess": "Attachment updated", - "updateFailed": "Failed to update attachment" - }, "substitute": { "title": "Substitute Teacher", "add": "Add Assignment", @@ -536,6 +724,24 @@ "updateSuccess": "Substitute assignment updated", "updateFailed": "Failed to update substitute assignment" }, + "schedule": { + "title": "Schedule Lesson", + "boundList": "Bound Lessons", + "empty": "No bound lessons", + "addNew": "Add New Binding", + "classLabel": "Class", + "dateLabel": "Date", + "periodLabel": "Period", + "durationLabel": "Duration (min)", + "period": "Period {n}", + "duration": "{n} min", + "add": "Add", + "delete": "Delete", + "selectClass": "Please select a class", + "selectDate": "Please select a date", + "addSuccess": "Schedule binding added", + "deleteSuccess": "Schedule binding deleted" + }, "evaluation": { "title": "AI Evaluation", "generate": "Generate Evaluation", diff --git a/src/shared/i18n/messages/en/notifications.json b/src/shared/i18n/messages/en/notifications.json index e709770..cdcd603 100644 --- a/src/shared/i18n/messages/en/notifications.json +++ b/src/shared/i18n/messages/en/notifications.json @@ -49,6 +49,8 @@ "error": { "loadFailed": "Failed to load notifications", "loadFailedDesc": "Sorry, an unexpected error occurred while loading notifications. Please try again later.", + "boundaryTitle": "Notifications section failed to load", + "boundaryDescription": "An error occurred while loading notifications. Please retry.", "retry": "Retry" } } diff --git a/src/shared/i18n/messages/en/rbac.json b/src/shared/i18n/messages/en/rbac.json index b2a7e54..787232e 100644 --- a/src/shared/i18n/messages/en/rbac.json +++ b/src/shared/i18n/messages/en/rbac.json @@ -87,211 +87,211 @@ }, "exam": { "create": "Create Exam", - "create.desc": "Allows creating new exams", + "createDesc": "Allows creating new exams", "read": "Read Exam", - "read.desc": "Allows viewing exam list and details", + "readDesc": "Allows viewing exam list and details", "update": "Update Exam", - "update.desc": "Allows modifying exam information", + "updateDesc": "Allows modifying exam information", "delete": "Delete Exam", - "delete.desc": "Allows deleting exams", + "deleteDesc": "Allows deleting exams", "duplicate": "Duplicate Exam", - "duplicate.desc": "Allows duplicating existing exams", + "duplicateDesc": "Allows duplicating existing exams", "publish": "Publish Exam", - "publish.desc": "Allows publishing exams for students to take", + "publishDesc": "Allows publishing exams for students to take", "ai_generate": "AI Generate Exam", - "ai_generate.desc": "Allows using AI to generate exam content", + "ai_generateDesc": "Allows using AI to generate exam content", "submit": "Submit Exam", - "submit.desc": "Allows submitting exam responses", + "submitDesc": "Allows submitting exam responses", "proctor": "Proctor Exam", - "proctor.desc": "Allows performing proctoring operations", + "proctorDesc": "Allows performing proctoring operations", "proctor_read": "Read Proctoring", - "proctor_read.desc": "Allows viewing proctoring information" + "proctor_readDesc": "Allows viewing proctoring information" }, "homework": { "create": "Create Homework", - "create.desc": "Allows creating new homework", + "createDesc": "Allows creating new homework", "grade": "Grade Homework", - "grade.desc": "Allows grading student homework", + "gradeDesc": "Allows grading student homework", "submit": "Submit Homework", - "submit.desc": "Allows submitting homework" + "submitDesc": "Allows submitting homework" }, "question": { "create": "Create Question", - "create.desc": "Allows creating new questions", + "createDesc": "Allows creating new questions", "read": "Read Question", - "read.desc": "Allows viewing question list and details", + "readDesc": "Allows viewing question list and details", "update": "Update Question", - "update.desc": "Allows modifying question information", + "updateDesc": "Allows modifying question information", "delete": "Delete Question", - "delete.desc": "Allows deleting questions" + "deleteDesc": "Allows deleting questions" }, "textbook": { "create": "Create Textbook", - "create.desc": "Allows creating new textbooks", + "createDesc": "Allows creating new textbooks", "read": "Read Textbook", - "read.desc": "Allows viewing textbook list and details", + "readDesc": "Allows viewing textbook list and details", "update": "Update Textbook", - "update.desc": "Allows modifying textbook information", + "updateDesc": "Allows modifying textbook information", "delete": "Delete Textbook", - "delete.desc": "Allows deleting textbooks" + "deleteDesc": "Allows deleting textbooks" }, "class": { "create": "Create Class", - "create.desc": "Allows creating new classes", + "createDesc": "Allows creating new classes", "read": "Read Class", - "read.desc": "Allows viewing class list and details", + "readDesc": "Allows viewing class list and details", "update": "Update Class", - "update.desc": "Allows modifying class information", + "updateDesc": "Allows modifying class information", "delete": "Delete Class", - "delete.desc": "Allows deleting classes", + "deleteDesc": "Allows deleting classes", "enroll": "Enroll Students", - "enroll.desc": "Allows managing student enrollment in classes", + "enrollDesc": "Allows managing student enrollment in classes", "schedule": "Class Scheduling", - "schedule.desc": "Allows managing class schedules" + "scheduleDesc": "Allows managing class schedules" }, "school": { "manage": "Manage School", - "manage.desc": "Allows managing school information", + "manageDesc": "Allows managing school information", "grade_manage": "Manage Grades", - "grade_manage.desc": "Allows managing grade information", + "grade_manageDesc": "Allows managing grade information", "user_manage": "Manage Users", - "user_manage.desc": "Allows managing system users" + "user_manageDesc": "Allows managing system users" }, "user": { "profile_update": "Update Profile", - "profile_update.desc": "Allows updating user profile" + "profile_updateDesc": "Allows updating user profile" }, "ai": { "chat": "AI Chat", - "chat.desc": "Allows using AI chat feature", + "chatDesc": "Allows using AI chat feature", "configure": "AI Configure", - "configure.desc": "Allows configuring AI parameters" + "configureDesc": "Allows configuring AI parameters" }, "settings": { "admin": "Admin Settings", - "admin.desc": "Allows managing system settings" + "adminDesc": "Allows managing system settings" }, "audit": { "read": "Read Audit Logs", - "read.desc": "Allows viewing system audit logs" + "readDesc": "Allows viewing system audit logs" }, "announcement": { "manage": "Manage Announcements", - "manage.desc": "Allows creating, modifying, and deleting announcements", + "manageDesc": "Allows creating, modifying, and deleting announcements", "read": "Read Announcements", - "read.desc": "Allows viewing announcement content" + "readDesc": "Allows viewing announcement content" }, "grade_record": { "manage": "Manage Grade Records", - "manage.desc": "Allows entering and modifying grade records", + "manageDesc": "Allows entering and modifying grade records", "read": "Read Grade Records", - "read.desc": "Allows viewing grade records" + "readDesc": "Allows viewing grade records" }, "file": { "upload": "Upload File", - "upload.desc": "Allows uploading files", + "uploadDesc": "Allows uploading files", "read": "Read File", - "read.desc": "Allows viewing files", + "readDesc": "Allows viewing files", "delete": "Delete File", - "delete.desc": "Allows deleting files" + "deleteDesc": "Allows deleting files" }, "course_plan": { "manage": "Manage Course Plans", - "manage.desc": "Allows creating, modifying, and deleting course plans", + "manageDesc": "Allows creating, modifying, and deleting course plans", "read": "Read Course Plans", - "read.desc": "Allows viewing course plans" + "readDesc": "Allows viewing course plans" }, "attendance": { "manage": "Manage Attendance", - "manage.desc": "Allows entering and modifying attendance records", + "manageDesc": "Allows entering and modifying attendance records", "read": "Read Attendance", - "read.desc": "Allows viewing attendance records" + "readDesc": "Allows viewing attendance records" }, "leave_request": { "create": "Submit Leave Request", - "create.desc": "Allows parents/students to submit online leave requests", + "createDesc": "Allows parents/students to submit online leave requests", "read": "Read Leave Requests", - "read.desc": "Allows viewing own/children/managed-class leave requests", + "readDesc": "Allows viewing own/children/managed-class leave requests", "review": "Review Leave Requests", - "review.desc": "Allows homeroom teachers/admins to approve leave requests and auto-sync attendance" + "reviewDesc": "Allows homeroom teachers/admins to approve leave requests and auto-sync attendance" }, "message": { "send": "Send Message", - "send.desc": "Allows sending messages", + "sendDesc": "Allows sending messages", "read": "Read Message", - "read.desc": "Allows viewing messages", + "readDesc": "Allows viewing messages", "delete": "Delete Message", - "delete.desc": "Allows deleting messages" + "deleteDesc": "Allows deleting messages" }, "scheduling": { "auto": "Auto Scheduling", - "auto.desc": "Allows running automatic scheduling", + "autoDesc": "Allows running automatic scheduling", "adjust": "Adjust Scheduling", - "adjust.desc": "Allows manually adjusting schedules" + "adjustDesc": "Allows manually adjusting schedules" }, "elective": { "manage": "Manage Electives", - "manage.desc": "Allows managing elective settings", + "manageDesc": "Allows managing elective settings", "read": "Read Electives", - "read.desc": "Allows viewing elective information", + "readDesc": "Allows viewing elective information", "select": "Select Elective", - "select.desc": "Allows students to select electives" + "selectDesc": "Allows students to select electives" }, "diagnostic": { "manage": "Manage Diagnostics", - "manage.desc": "Allows managing diagnostic tests", + "manageDesc": "Allows managing diagnostic tests", "read": "Read Diagnostics", - "read.desc": "Allows viewing diagnostic results" + "readDesc": "Allows viewing diagnostic results" }, "lesson_plan": { "create": "Create Lesson Plan", - "create.desc": "Allows creating new lesson plans", + "createDesc": "Allows creating new lesson plans", "read": "Read Lesson Plan", - "read.desc": "Allows viewing lesson plan list and details", + "readDesc": "Allows viewing lesson plan list and details", "update": "Update Lesson Plan", - "update.desc": "Allows modifying lesson plan information", + "updateDesc": "Allows modifying lesson plan information", "delete": "Delete Lesson Plan", - "delete.desc": "Allows deleting lesson plans", + "deleteDesc": "Allows deleting lesson plans", "publish": "Publish Lesson Plan", - "publish.desc": "Allows publishing lesson plans" + "publishDesc": "Allows publishing lesson plans" }, "dashboard": { "admin_read": "Read Admin Dashboard", - "admin_read.desc": "Allows viewing admin dashboard", + "admin_readDesc": "Allows viewing admin dashboard", "teacher_read": "Read Teacher Dashboard", - "teacher_read.desc": "Allows viewing teacher dashboard", + "teacher_readDesc": "Allows viewing teacher dashboard", "student_read": "Read Student Dashboard", - "student_read.desc": "Allows viewing student dashboard", + "student_readDesc": "Allows viewing student dashboard", "parent_read": "Read Parent Dashboard", - "parent_read.desc": "Allows viewing parent dashboard" + "parent_readDesc": "Allows viewing parent dashboard" }, "error_book": { "read": "Read Error Book", - "read.desc": "Allows viewing error book", + "readDesc": "Allows viewing error book", "manage": "Manage Error Book", - "manage.desc": "Allows managing error records", + "manageDesc": "Allows managing error records", "analytics_read": "Read Error Analytics", - "analytics_read.desc": "Allows viewing error book analytics" + "analytics_readDesc": "Allows viewing error book analytics" }, "adaptive_practice": { "read": "Read Adaptive Practice", - "read.desc": "Allows viewing adaptive practice", + "readDesc": "Allows viewing adaptive practice", "manage": "Manage Adaptive Practice", - "manage.desc": "Allows managing adaptive practice" + "manageDesc": "Allows managing adaptive practice" }, "rbac": { "role_create": "Create Role", - "role_create.desc": "Allows creating new roles", + "role_createDesc": "Allows creating new roles", "role_read": "Read Role", - "role_read.desc": "Allows viewing role list and details", + "role_readDesc": "Allows viewing role list and details", "role_update": "Update Role", - "role_update.desc": "Allows modifying role information", + "role_updateDesc": "Allows modifying role information", "role_delete": "Delete Role", - "role_delete.desc": "Allows deleting roles", + "role_deleteDesc": "Allows deleting roles", "role_assign": "Assign Role", - "role_assign.desc": "Allows assigning roles to users", + "role_assignDesc": "Allows assigning roles to users", "permission_read": "Read Permission", - "permission_read.desc": "Allows viewing permission catalog" + "permission_readDesc": "Allows viewing permission catalog" } }, "actions": { diff --git a/src/shared/i18n/messages/en/student.json b/src/shared/i18n/messages/en/student.json index 6d0c9b8..dcb5d38 100644 --- a/src/shared/i18n/messages/en/student.json +++ b/src/shared/i18n/messages/en/student.json @@ -77,6 +77,12 @@ "description": "An unexpected error occurred. Please try again.", "retry": "Try again" }, + "errors": { + "unexpected": "An unexpected error occurred" + }, + "actions": { + "retry": "Try again" + }, "classDetail": { "backToCourses": "Back to Courses", "grade": "Grade {grade}", diff --git a/src/shared/i18n/messages/en/textbooks.json b/src/shared/i18n/messages/en/textbooks.json index 8f39ff4..bfec8de 100644 --- a/src/shared/i18n/messages/en/textbooks.json +++ b/src/shared/i18n/messages/en/textbooks.json @@ -180,6 +180,12 @@ "loadFailedDesc": "An error occurred while loading the textbook content. Please try again.", "retry": "Retry" }, + "errors": { + "unexpected": "An unexpected error occurred" + }, + "actions": { + "retry": "Retry" + }, "action": { "createSuccess": "Textbook created successfully.", "createFailed": "Failed to create textbook.", @@ -224,6 +230,10 @@ "noClassMasteryPermission": "No permission to view class mastery data" }, "graph": { + "layout": { + "hierarchical": "Hierarchical", + "force": "Force Graph" + }, "viewMode": { "structure": "Structure", "studentMastery": "My Mastery", diff --git a/src/shared/i18n/messages/zh-CN/diagnostic.json b/src/shared/i18n/messages/zh-CN/diagnostic.json index 2f01530..181517d 100644 --- a/src/shared/i18n/messages/zh-CN/diagnostic.json +++ b/src/shared/i18n/messages/zh-CN/diagnostic.json @@ -3,7 +3,13 @@ "student": "学生学情诊断", "class": "班级学情诊断", "reportList": "诊断报告", - "myDiagnostic": "我的学情诊断" + "myDiagnostic": "我的学情诊断", + "teacherReportList": "学情诊断", + "teacherReportListDesc": "查看并管理基于知识点掌握度的诊断报告。", + "teacherStudent": "学生诊断", + "teacherStudentDesc": "知识点掌握度分析与诊断报告。", + "teacherClass": "班级诊断", + "teacherClassDesc": "班级知识点掌握度概览与重点关注学生名单。" }, "type": { "individual": "个人", diff --git a/src/shared/i18n/messages/zh-CN/error-book.json b/src/shared/i18n/messages/zh-CN/error-book.json index c060982..d50191a 100644 --- a/src/shared/i18n/messages/zh-CN/error-book.json +++ b/src/shared/i18n/messages/zh-CN/error-book.json @@ -283,7 +283,7 @@ "reviewRecorded": "复习结果已记录", "archived": "错题已归档", "deleted": "错题已删除", - "collected": "已采集 {{count}} 道错题", + "collected": "已采集 {count} 道错题", "noNewErrors": "没有新的错题需要采集", "addFailed": "添加错题失败", "saveFailed": "保存失败", diff --git a/src/shared/i18n/messages/zh-CN/exam-homework.json b/src/shared/i18n/messages/zh-CN/exam-homework.json index 43ce22f..dae4c86 100644 --- a/src/shared/i18n/messages/zh-CN/exam-homework.json +++ b/src/shared/i18n/messages/zh-CN/exam-homework.json @@ -70,7 +70,7 @@ "3": "中等", "4": "偏难", "5": "困难", - "ariaLabel": "难度等级 {{level}}:{{label}}" + "ariaLabel": "难度等级 {level}:{label}" }, "actions": { "preview": "预览考试", @@ -83,7 +83,7 @@ "archive": "归档", "delete": "删除", "deleteConfirmTitle": "确定要删除吗?", - "deleteConfirmDescription": "此操作不可撤销。将永久删除考试\"{{title}}\"及所有关联数据。", + "deleteConfirmDescription": "此操作不可撤销。将永久删除考试\"{title}\"及所有关联数据。", "cancel": "取消", "deleteSuccess": "考试已删除", "deleteFailed": "删除考试失败", @@ -165,7 +165,7 @@ "richEditorHint": "切换到富文本编辑器", "loaded": "已加载", "startHint": "从右侧面板添加题目开始", - "itemsInStructure": "结构中有 {{count}} 项", + "itemsInStructure": "结构中有 {count} 项", "saveSuccess": "考试草稿已保存", "saveFailed": "保存失败", "publishSuccess": "考试已发布", @@ -280,7 +280,7 @@ "sectionLabel": "分卷", "partLabel": "部分", "groupLabel": "大题", - "questionCountSummary": "(共 {{count}} 题,{{score}} 分)", + "questionCountSummary": "(共 {count} 题,{score} 分)", "typeSingleChoice": "单选", "typeMultipleChoice": "多选", "typeJudgment": "判断", @@ -310,8 +310,8 @@ "taskInterrupted": "页面刷新后任务已中断,请重新生成", "untitledExam": "未命名试卷", "queuedSuccess": "已加入后台队列,可继续编辑页面", - "backgroundComplete": "后台生成完成:{{title}}", - "backgroundFailed": "后台生成失败:{{title}}", + "backgroundComplete": "后台生成完成:{title}", + "backgroundFailed": "后台生成失败:{title}", "selectQuestionFirst": "请先选择一个题目", "questionNotFound": "未找到选中的题目", "enterRewriteInstruction": "请输入重写指令", @@ -321,7 +321,7 @@ "pasteSourceFirst": "请先粘贴试卷文本" }, "paperPreview": { - "scoreWithUnit": "({{score}}分)" + "scoreWithUnit": "({score}分)" }, "actionMessages": { "enterRewriteInstruction": "请输入重写指令", @@ -361,16 +361,16 @@ "noQuestionText": "(无题目文本)" }, "card": { - "level": "难度 {{level}}", - "minutes": "{{count}} 分钟", - "points": "{{count}} 分", - "questions": "{{count}} 题" + "level": "难度 {level}", + "minutes": "{count} 分钟", + "points": "{count} 分", + "questions": "{count} 题" }, "viewer": { "section": "分卷", "group": "大题", "score": "分值", - "scoreLabel": "分值:{{score}}", + "scoreLabel": "分值:{score}", "noQuestions": "暂无题目", "unknown": "未知" }, @@ -379,7 +379,7 @@ "title": "试卷预览", "generating": "生成预览中...", "fullPreview": "完整试卷预览", - "summary": "{{count}} 题 · {{subject}} · {{grade}} · {{minutes}} 分钟 · {{total}} 分", + "summary": "{count} 题 · {subject} · {grade} · {minutes} 分钟 · {total} 分", "noPreview": "暂无预览内容", "confirmCreate": "确认并创建", "untitledQuestion": "未命名题目", @@ -390,7 +390,7 @@ "label": "选项", "addOption": "新增选项", "correct": "正确", - "markCorrectAria": "标记选项 {{id}} 为正确答案", + "markCorrectAria": "标记选项 {id} 为正确答案", "deleteOptionAria": "删除选项" }, "editorExtensions": { @@ -400,7 +400,7 @@ "group": { "titlePlaceholder": "大题标题(如:一、选择题)", "instructionPlaceholder": "说明(如:每小题3分,共24分)—— 可留空,总分自动统计", - "statsSummary": "共 {{count}} 题 · {{score}} 分" + "statsSummary": "共 {count} 题 · {score} 分" }, "question": { "typeSingleChoice": "单选", @@ -416,7 +416,7 @@ "levelVolume": "卷", "levelPart": "部分", "levelSubVolume": "分卷", - "statsSummary": "共 {{count}} 题 · {{score}} 分" + "statsSummary": "共 {count} 题 · {score} 分" } } }, @@ -430,7 +430,7 @@ "emptyDescription": "您还没有创建任何作业。", "emptyFilteredDescription": "尝试清除筛选条件或调整关键词。", "clearFilters": "清除筛选", - "filterByClass": "按班级筛选:{{className}}", + "filterByClass": "按班级筛选:{className}", "columns": { "title": "标题", "status": "状态", @@ -488,7 +488,7 @@ }, "take": { "questions": "题目", - "question": "第 {{index}} 题", + "question": "第 {index} 题", "points": "分", "startAssignment": "开始作答", "submitAssignment": "提交作业", @@ -505,12 +505,12 @@ "readyDescription": "点击上方\"开始作答\"按钮。点击\"保存答案\"将保存您的答案。", "startNow": "立即开始", "back": "返回", - "timedExam": "限时考试:{{minutes}} 分钟", + "timedExam": "限时考试:{minutes} 分钟", "timeRemaining": "剩余时间", "timeUpAutoSubmit": "时间到,正在自动提交...", "confirmSubmit": "确认提交", "confirmSubmitDescription": "所有题目已作答。提交后答案不可修改,确定要提交吗?", - "unansweredWarning": "您有 {{count}} 道题未作答。提交后答案不可修改,确定要提交吗?", + "unansweredWarning": "您有 {count} 道题未作答。提交后答案不可修改,确定要提交吗?", "cancel": "取消", "confirmSubmitAction": "确认提交", "submitSuccess": "已提交", @@ -521,15 +521,15 @@ "status": "状态", "dueDate": "截止时间", "overdue": "已逾期", - "hoursLeft": "还剩 {{hours}} 小时", + "hoursLeft": "还剩 {hours} 小时", "lessThanOneHour": "不足 1 小时", "attempts": "尝试次数", - "attemptsUsed": "已用 {{used}} / {{max}}", - "attemptsRemaining": "· 剩余 {{remaining}} 次", + "attemptsUsed": "已用 {used} / {max}", + "attemptsRemaining": "· 剩余 {remaining} 次", "description": "描述", "noDescription": "无描述。", "progress": "进度", - "jumpToQuestion": "跳转到第 {{index}} 题", + "jumpToQuestion": "跳转到第 {index} 题", "answered": "已作答", "unanswered": "未作答", "yourAnswer": "你的答案", @@ -550,12 +550,12 @@ "scanDescription": "在纸上作答后,拍照上传整卷答题图片", "selectImageFiles": "请选择图片文件", "uploadFailed": "上传失败", - "uploadSuccess": "已上传 {{count}} 张图片", + "uploadSuccess": "已上传 {count} 张图片", "deleteScan": "删除", "moveUp": "上移", "moveDown": "下移", "dragDropHint": "拖拽图片到此处,或点击选择文件", - "pageLabel": "第 {{page}} 页", + "pageLabel": "第 {page} 页", "scanDisabled": "已提交,无法修改答题图片" }, "grade": { @@ -589,7 +589,7 @@ "scoreLabel": "分数", "addFeedback": "添加反馈", "hideFeedback": "隐藏反馈", - "feedbackPlaceholder": "为 {{name}} 添加反馈...", + "feedbackPlaceholder": "为 {name} 添加反馈...", "submitGrades": "提交成绩", "saving": "保存中...", "gradesSaved": "批改已保存", @@ -600,7 +600,7 @@ "next": "下一页", "gradesAutoSaveNote": "点击提交后成绩将自动保存。学生将在您提交后立即看到成绩和反馈。", "batchAutoGrade": "批量自动批改", - "batchSelected": "已选 {{count}} 份提交", + "batchSelected": "已选 {count} 份提交", "batchSelectAtLeastOne": "请至少选择一份提交", "batchFailed": "批量批改失败", "selectAll": "全选", @@ -616,9 +616,9 @@ "noScans": "暂无答题图片", "saveFailed": "保存失败", "scanFeedbackPlaceholder": "批改评语(可选)...", - "scoreOutOf": "/ {{max}} 分", - "questionsCount": "题目与批改({{count}} 题)", - "scanPagesCount": "学生答题图片({{count}} 页)" + "scoreOutOf": "/ {max} 分", + "questionsCount": "题目与批改({count} 题)", + "scanPagesCount": "学生答题图片({count} 页)" }, "review": { "title": "复习", @@ -700,15 +700,15 @@ "questionPreview": "题目预览", "errorAnalysis": "错误分析", "errorRateOverview": "错误率概览", - "errorRateAriaLabel": "错误率 {{rate}}%", + "errorRateAriaLabel": "错误率 {rate}%", "question": "题目", "errors": "错误数", "errorRateLabel": "错误率", - "wrongAnswersWithCount": "错答 ({{count}})", + "wrongAnswersWithCount": "错答 ({count})", "wrongAnswers": "错答", "noWrongAnswers": "暂无错答记录。", "studentAnswer": "学生答案", - "studentCount": "{{count}} 名学生", + "studentCount": "{count} 名学生", "notAnswered": "未作答", "selectQuestionHint": "请从左侧选择题目", "selectQuestionHintDesc": "查看错误分析", @@ -721,9 +721,9 @@ "zoomIn": "放大", "rotate": "旋转", "fullscreen": "全屏", - "pageIndicator": "第 {{current}} / {{total}} 页", - "answerImageAlt": "答题图 第{{page}}页", - "thumbnailAlt": "缩略图 {{page}}" + "pageIndicator": "第 {current} / {total} 页", + "answerImageAlt": "答题图 第{page}页", + "thumbnailAlt": "缩略图 {page}" }, "submissions": { "title": "作业提交", @@ -744,26 +744,26 @@ }, "excellent": { "title": "优秀作业展示", - "description": "本作业中得分率达到 {{minPercentage}}% 及以上的优秀样例。", + "description": "本作业中得分率达到 {minPercentage}% 及以上的优秀样例。", "empty": "暂无符合条件的优秀作业。", "emptyHint": "完成批改后将自动汇总展示。", "loading": "加载优秀作业...", "loadFailed": "加载失败", "retry": "重试", - "rank": "第 {{rank}} 名", + "rank": "第 {rank} 名", "scoreLabel": "得分", - "scoreValue": "{{score}} / {{max}}", - "percentage": "{{value}}%", + "scoreValue": "{score} / {max}", + "percentage": "{value}%", "lateTag": "迟交", "viewDetail": "查看详情", - "submittedAt": "提交于 {{date}}", + "submittedAt": "提交于 {date}", "studentAnon": "同学" }, "parentExam": { "examsTaken": "已参加考试", "averageScore": "平均分", "bestScore": "最高分", - "examResults": "{{name}} 的考试成绩", + "examResults": "{name} 的考试成绩", "examResultsDescription": "近期考试分数与表现趋势", "noResults": "暂无考试成绩", "noResultsHint": "考试成绩将在批改完成后显示。", diff --git a/src/shared/i18n/messages/zh-CN/leave.json b/src/shared/i18n/messages/zh-CN/leave.json index af1d5b8..b32c376 100644 --- a/src/shared/i18n/messages/zh-CN/leave.json +++ b/src/shared/i18n/messages/zh-CN/leave.json @@ -1,12 +1,16 @@ { - "title": "请假申请", - "title.parent": "在线请假", - "title.teacher": "请假审批", - "title.student": "我的请假", - "description": "为您的孩子提交请假申请。", - "description.parent": "为子女提交请假申请,班主任审批后自动同步考勤。", - "description.teacher": "查看并审批本班学生提交的请假申请,审批通过后自动将请假期间考勤标记为「请假」。", - "description.student": "提交本人请假申请,班主任审批后生效。", + "title": { + "default": "请假申请", + "parent": "在线请假", + "teacher": "请假审批", + "student": "我的请假" + }, + "description": { + "default": "为您的孩子提交请假申请。", + "parent": "为子女提交请假申请,班主任审批后自动同步考勤。", + "teacher": "查看并审批本班学生提交的请假申请,审批通过后自动将请假期间考勤标记为「请假」。", + "student": "提交本人请假申请,班主任审批后生效。" + }, "backToDashboard": "返回仪表盘", "onlineLeave": "在线请假申请", "comingSoon": "即将上线", diff --git a/src/shared/i18n/messages/zh-CN/lesson-preparation.json b/src/shared/i18n/messages/zh-CN/lesson-preparation.json index bc054a1..1254919 100644 --- a/src/shared/i18n/messages/zh-CN/lesson-preparation.json +++ b/src/shared/i18n/messages/zh-CN/lesson-preparation.json @@ -27,7 +27,12 @@ "unpublishPlanConfirm": "撤回后学生和家长将无法查看此课案,确认撤回?", "publishPlanSuccess": "课案已发布", "unpublishPlanSuccess": "课案已撤回", - "viewHomework": "查看" + "viewHomework": "查看", + "undo": "撤销", + "undoShortcut": "撤销 (Ctrl+Z)", + "redo": "重做", + "redoShortcut": "重做 (Ctrl+Shift+Z)", + "scheduleLesson": "安排课时" }, "status": { "saving": "保存中...", @@ -39,7 +44,15 @@ "published": "已发布", "rejected": "已驳回", "archived": "已归档", - "publishedAsHomework": "已发布为作业" + "publishedAsHomework": "已发布为作业", + "retrySave": "重试保存", + "saveFailed": "保存失败", + "saveFailedHint": "请检查网络后重试,编辑内容已保留", + "recovered": "已恢复保存", + "offline": "网络已断开", + "offlineHint": "编辑内容会保留在本地,恢复网络后自动保存", + "offlineBadge": "离线", + "backOnline": "网络已恢复" }, "blockType": { "objective": "教学目标", @@ -130,7 +143,46 @@ "textbookLabel": "教材", "chapterLabel": "课文", "selectNodeForAnchor": "选择要关联的节点", - "createNewNode": "创建新节点并关联" + "createNewNode": "创建新节点并关联", + "autoLayout": "自动布局", + "autoLayoutHint": "按流程关系自动排列节点", + "stageLabel": "阶段", + "stageNone": "未归类", + "stage": { + "import": "导入", + "new_teaching": "新授", + "consolidation": "巩固", + "summary": "总结" + }, + "differentiationLabel": "差异化", + "differentiationNone": "无", + "differentiation": { + "basic": "基础", + "intermediate": "提高", + "advanced": "拓展" + }, + "consistencyTitle": "教学评一致性", + "consistencyScore": "一致性分数:{score}", + "consistencyCoverage": "目标覆盖:{covered}/{total}", + "consistencyOpen": "一致性校验", + "consistencyClose": "关闭", + "consistencyNoIssues": "未发现一致性问题", + "consistencyObjectiveCount": "目标 {count}", + "consistencyExerciseCount": "评价 {count}" + }, + "consistency": { + "title": "教学评一致性校验", + "score": "一致性分数:{score}", + "coverage": "目标覆盖:{covered}/{total}", + "noIssues": "未发现一致性问题", + "code": { + "noObjective": "缺少教学目标节点(建议添加 objective 节点)", + "noExercise": "缺少评价节点(建议添加 exercise 节点)", + "objectiveNotAssessed": "目标「{title}」未被任何评价覆盖", + "exerciseNoKnowledgePoint": "评价「{title}」未关联任何知识点", + "exerciseWithoutObjective": "评价「{title}」未关联任何教学目标", + "objectiveNoKnowledgePoint": "目标「{title}」未标注知识点" + } }, "readonly": { "title": "查看课案", @@ -160,6 +212,65 @@ "title": "年级备课", "description": "查看本年级教师的备课" }, + "library": { + "title": "校内课案库", + "description": "浏览本校教师分享的优秀课案", + "empty": "暂无可参考的课案", + "fork": "复制为我的课案", + "forkSuccess": "已复制到我的课案", + "byCreator": "作者:{creator}", + "noCreator": "未知作者" + }, + "feedback": { + "title": "AI 教学反馈", + "open": "AI 反馈", + "loading": "AI 正在分析课案...", + "loadFailed": "AI 反馈生成失败,请稍后重试", + "empty": "未生成反馈建议", + "summary": "整体评估", + "score": "评分", + "category": { + "strengths": "优点", + "improvements": "改进建议", + "alignment": "教学评一致性", + "differentiation": "差异化建议" + } + }, + "aiDifferentiation": { + "title": "AI 差异化与课标核对", + "open": "AI 差异化", + "loading": "AI 正在分析...", + "loadFailed": "AI 分析失败,请稍后重试", + "empty": "未生成结果", + "noKnowledgePoints": "未提供知识点列表,无法进行课标核对", + "covered": "已覆盖 {count} 项", + "missed": "未覆盖 {count} 项", + "tabs": { + "differentiation": "差异化建议", + "curriculum": "课标核对", + "assessment": "可解释评估" + }, + "level": { + "basic": "基础层", + "intermediate": "提高层", + "advanced": "拓展层" + } + }, + "heatmap": { + "title": "课标覆盖热力图", + "description": "查看您所有课案对教材知识点的覆盖情况", + "totalKps": "知识点总数", + "coveredKps": "已覆盖", + "coverageRate": "覆盖率", + "blindSpotTitle": "{count} 个教学盲点", + "andMore": " 等", + "unknownChapter": "未知章节", + "chapterCoverage": "{covered}/{total}({rate}%)", + "notCovered": "未覆盖", + "planCount": "{count} 个课案", + "selectTextbook": "请选择教材查看热力图", + "noData": "该教材暂无知识点数据" + }, "picker": { "textbookLabel": "选择教材", "chapterLabel": "选择课文", @@ -172,7 +283,11 @@ "noChapters": "该教材暂无章节", "selectedChapter": "已选课文:{chapter}", "skeletonHint": "将自动生成以课文正文为核心的备课骨架(含 10 个教学节点)", - "errorTextbookChapterRequired": "请选择教材和课文" + "errorTextbookChapterRequired": "请选择教材和课文", + "searchTextbookPlaceholder": "搜索教材(标题/学科/年级)...", + "searchTextbookLabel": "搜索教材", + "recentSection": "最近使用", + "searchEmpty": "无匹配教材" }, "filters": { "searchPlaceholder": "搜索标题...", @@ -203,6 +318,25 @@ "autoLabel": "自动版本", "revertLabel": "回退到 v{versionNo}" }, + "diff": { + "summary": "新增 {added} · 删除 {removed} · 修改 {modified}", + "comparing": "对比 v{version} 与当前", + "noChanges": "两个版本内容一致,无差异", + "unchangedCount": "{count} 个节点未变更", + "added": "新增", + "removed": "删除", + "modified": "修改", + "unchanged": "未变", + "changedFields": "变更字段", + "compareWithCurrent": "对比当前", + "back": "返回", + "field": { + "title": "标题", + "stage": "阶段", + "differentiation": "差异化", + "data": "内容" + } + }, "knowledgePoint": { "title": "选择知识点", "empty": "未找到知识点,请先在教材模块创建", @@ -232,6 +366,10 @@ "questionId": "题目 {id}", "type": { "single_choice": "单选题", + "multiple_choice": "多选题", + "true_false": "判断题", + "short_answer": "简答题", + "essay": "论述题", "text": "填空题", "judgment": "判断题" }, @@ -246,7 +384,8 @@ "difficultyLabel": "难度", "knowledgePointLabel": "知识点", "stemRequired": "请输入题干", - "addBtn": "添加" + "addBtn": "添加", + "noDetail": "(无详细内容)" }, "exercise": { "purposeLabel": "用途", @@ -271,7 +410,27 @@ "planNotFound": "课案不存在", "noPermission": "无权发布", "homeworkTitle": "{title} - 作业", - "homeworkDescription": "来自课案" + "homeworkDescription": "来自课案", + "step1": "选择班级", + "step2": "预览作业", + "step3": "确认发布", + "stepIndicator": "第 {current}/{total} 步:{label}", + "next": "下一步", + "back": "上一步", + "previewClassCount": "下发班级数", + "previewQuestionCount": "题目数量", + "previewTotalScore": "总分", + "previewQuestionList": "题目列表", + "previewSource": "来源:{source}", + "previewNoStem": "(无题干预览)", + "previewScore": "{score} 分", + "confirmTitle": "请确认发布信息", + "confirmClassCount": "下发班级数:{count}", + "confirmQuestionCount": "题目数量:{count}", + "confirmTotalScore": "总分:{score}", + "confirmAvailableAt": "开始时间:{time}", + "confirmDueAt": "截止时间:{time}", + "confirmWarning": "发布后学生和家长将立即收到作业,请仔细核对。" }, "textStudy": { "sourceTextLabel": "课文原文", @@ -336,7 +495,12 @@ "text": "文字式" }, "contentLabel": "板书内容", - "contentPlaceholder": "输入板书内容..." + "contentPlaceholder": "输入板书内容...", + "editMode": "编辑", + "previewMode": "预览", + "editHint": "提示:使用 2 个空格或 1 个 Tab 缩进表示层级", + "previewEmpty": "暂无内容,请切换到编辑模式输入", + "untitled": "中心主题" }, "import": { "hint": "设计课堂导入环节", @@ -365,6 +529,45 @@ "richText": { "placeholder": "输入内容..." }, + "export": { + "title": "导出/打印", + "button": "导出", + "print": "打印", + "variantDetailed": "详细版", + "variantConcise": "简洁版", + "teacher": "教师", + "class": "班级", + "totalDuration": "教学时长 {count} 分钟", + "lastSavedAt": "最后保存", + "empty": "课案无内容", + "emptySection": "(无内容)", + "footerHint": "教案导出 - {variant}" + }, + "attachment": { + "title": "素材库", + "add": "上传文件", + "insert": "插入附件", + "libraryLabel": "本课案已上传附件", + "empty": "暂无附件,请先上传", + "delete": "删除", + "remove": "移除", + "embeddedCount": "已嵌入 {count} 个附件", + "createSuccess": "附件已添加", + "deleteSuccess": "附件已删除", + "uploadFailed": "上传失败", + "uploadSuccess": "上传成功", + "type": { + "reference": "参考资料", + "material": "教学素材", + "supplementary": "补充资源" + }, + "kind": { + "image": "图片", + "audio": "音频", + "video": "视频", + "file": "文件" + } + }, "error": { "getList": "获取课案列表失败", "getOne": "获取课案失败", @@ -394,7 +597,12 @@ "titleTooLong": "标题不能超过 255 个字符", "templateRequired": "请选择模板", "invalidDate": "日期格式无效", - "classRequired": "至少选择一个班级" + "classRequired": "至少选择一个班级", + "invalidInput": "输入数据无效", + "unauthorized": "未授权操作", + "planIdRequired": "课案 ID 必填", + "classIdRequired": "班级 ID 必填", + "dateRequired": "日期必填" }, "confirm": { "archive": "确认归档此课案?", @@ -506,22 +714,6 @@ "updateSuccess": "已更新", "deleteSuccess": "已删除" }, - "attachment": { - "title": "资源附件", - "add": "添加附件", - "empty": "暂无附件", - "type": { - "reference": "参考资料", - "material": "教学素材", - "supplementary": "补充材料" - }, - "nameLabel": "名称", - "urlLabel": "链接", - "typeLabel": "类型", - "createSuccess": "附件已添加", - "deleteSuccess": "附件已删除", - "updateSuccess": "附件已更新" - }, "substitute": { "title": "代课教师", "add": "添加代课", @@ -540,6 +732,24 @@ "deleteSuccess": "代课已删除", "cancelConfirm": "确认取消此代课安排?" }, + "schedule": { + "title": "安排课时", + "boundList": "已绑定课时", + "empty": "暂无绑定课时", + "addNew": "添加新绑定", + "classLabel": "班级", + "dateLabel": "日期", + "periodLabel": "节次", + "durationLabel": "时长(分钟)", + "period": "第{n}节", + "duration": "{n}分钟", + "add": "添加", + "delete": "删除", + "selectClass": "请选择班级", + "selectDate": "请选择日期", + "addSuccess": "已添加课时绑定", + "deleteSuccess": "已删除课时绑定" + }, "evaluation": { "title": "AI 课案评估", "run": "开始评估", diff --git a/src/shared/i18n/messages/zh-CN/notifications.json b/src/shared/i18n/messages/zh-CN/notifications.json index 5eba0c5..0fa389b 100644 --- a/src/shared/i18n/messages/zh-CN/notifications.json +++ b/src/shared/i18n/messages/zh-CN/notifications.json @@ -49,6 +49,8 @@ "error": { "loadFailed": "通知加载失败", "loadFailedDesc": "抱歉,加载通知时发生了意外错误。请稍后重试。", + "boundaryTitle": "通知区块加载失败", + "boundaryDescription": "加载通知数据时发生错误,请重试。", "retry": "重试" } } diff --git a/src/shared/i18n/messages/zh-CN/rbac.json b/src/shared/i18n/messages/zh-CN/rbac.json index 0db4368..6baed8a 100644 --- a/src/shared/i18n/messages/zh-CN/rbac.json +++ b/src/shared/i18n/messages/zh-CN/rbac.json @@ -87,211 +87,211 @@ }, "exam": { "create": "创建考试", - "create.desc": "允许创建新考试", + "createDesc": "允许创建新考试", "read": "查看考试", - "read.desc": "允许查看考试列表和详情", + "readDesc": "允许查看考试列表和详情", "update": "更新考试", - "update.desc": "允许修改考试信息", + "updateDesc": "允许修改考试信息", "delete": "删除考试", - "delete.desc": "允许删除考试", + "deleteDesc": "允许删除考试", "duplicate": "复制考试", - "duplicate.desc": "允许复制现有考试", + "duplicateDesc": "允许复制现有考试", "publish": "发布考试", - "publish.desc": "允许发布考试供学生作答", + "publishDesc": "允许发布考试供学生作答", "ai_generate": "AI 生成考试", - "ai_generate.desc": "允许使用 AI 生成考试内容", + "ai_generateDesc": "允许使用 AI 生成考试内容", "submit": "提交考试", - "submit.desc": "允许提交考试答卷", + "submitDesc": "允许提交考试答卷", "proctor": "监考", - "proctor.desc": "允许执行监考操作", + "proctorDesc": "允许执行监考操作", "proctor_read": "查看监考", - "proctor_read.desc": "允许查看监考信息" + "proctor_readDesc": "允许查看监考信息" }, "homework": { "create": "创建作业", - "create.desc": "允许创建新作业", + "createDesc": "允许创建新作业", "grade": "批改作业", - "grade.desc": "允许批改学生作业", + "gradeDesc": "允许批改学生作业", "submit": "提交作业", - "submit.desc": "允许提交作业" + "submitDesc": "允许提交作业" }, "question": { "create": "创建题目", - "create.desc": "允许创建新题目", + "createDesc": "允许创建新题目", "read": "查看题目", - "read.desc": "允许查看题目列表和详情", + "readDesc": "允许查看题目列表和详情", "update": "更新题目", - "update.desc": "允许修改题目信息", + "updateDesc": "允许修改题目信息", "delete": "删除题目", - "delete.desc": "允许删除题目" + "deleteDesc": "允许删除题目" }, "textbook": { "create": "创建教材", - "create.desc": "允许创建新教材", + "createDesc": "允许创建新教材", "read": "查看教材", - "read.desc": "允许查看教材列表和详情", + "readDesc": "允许查看教材列表和详情", "update": "更新教材", - "update.desc": "允许修改教材信息", + "updateDesc": "允许修改教材信息", "delete": "删除教材", - "delete.desc": "允许删除教材" + "deleteDesc": "允许删除教材" }, "class": { "create": "创建班级", - "create.desc": "允许创建新班级", + "createDesc": "允许创建新班级", "read": "查看班级", - "read.desc": "允许查看班级列表和详情", + "readDesc": "允许查看班级列表和详情", "update": "更新班级", - "update.desc": "允许修改班级信息", + "updateDesc": "允许修改班级信息", "delete": "删除班级", - "delete.desc": "允许删除班级", + "deleteDesc": "允许删除班级", "enroll": "学生入班", - "enroll.desc": "允许管理班级学生入班", + "enrollDesc": "允许管理班级学生入班", "schedule": "班级排课", - "schedule.desc": "允许管理班级课程表" + "scheduleDesc": "允许管理班级课程表" }, "school": { "manage": "学校管理", - "manage.desc": "允许管理学校信息", + "manageDesc": "允许管理学校信息", "grade_manage": "年级管理", - "grade_manage.desc": "允许管理年级信息", + "grade_manageDesc": "允许管理年级信息", "user_manage": "用户管理", - "user_manage.desc": "允许管理系统用户" + "user_manageDesc": "允许管理系统用户" }, "user": { "profile_update": "更新个人资料", - "profile_update.desc": "允许更新用户个人资料" + "profile_updateDesc": "允许更新用户个人资料" }, "ai": { "chat": "AI 对话", - "chat.desc": "允许使用 AI 对话功能", + "chatDesc": "允许使用 AI 对话功能", "configure": "AI 配置", - "configure.desc": "允许配置 AI 参数" + "configureDesc": "允许配置 AI 参数" }, "settings": { "admin": "系统设置", - "admin.desc": "允许管理系统设置" + "adminDesc": "允许管理系统设置" }, "audit": { "read": "查看审计日志", - "read.desc": "允许查看系统审计日志" + "readDesc": "允许查看系统审计日志" }, "announcement": { "manage": "管理公告", - "manage.desc": "允许创建、修改和删除公告", + "manageDesc": "允许创建、修改和删除公告", "read": "查看公告", - "read.desc": "允许查看公告内容" + "readDesc": "允许查看公告内容" }, "grade_record": { "manage": "管理成绩", - "manage.desc": "允许录入和修改成绩", + "manageDesc": "允许录入和修改成绩", "read": "查看成绩", - "read.desc": "允许查看成绩记录" + "readDesc": "允许查看成绩记录" }, "file": { "upload": "上传文件", - "upload.desc": "允许上传文件", + "uploadDesc": "允许上传文件", "read": "查看文件", - "read.desc": "允许查看文件", + "readDesc": "允许查看文件", "delete": "删除文件", - "delete.desc": "允许删除文件" + "deleteDesc": "允许删除文件" }, "course_plan": { "manage": "管理课程计划", - "manage.desc": "允许创建、修改和删除课程计划", + "manageDesc": "允许创建、修改和删除课程计划", "read": "查看课程计划", - "read.desc": "允许查看课程计划" + "readDesc": "允许查看课程计划" }, "attendance": { "manage": "管理考勤", - "manage.desc": "允许录入和修改考勤记录", + "manageDesc": "允许录入和修改考勤记录", "read": "查看考勤", - "read.desc": "允许查看考勤记录" + "readDesc": "允许查看考勤记录" }, "leave_request": { "create": "提交请假申请", - "create.desc": "允许家长/学生提交在线请假申请", + "createDesc": "允许家长/学生提交在线请假申请", "read": "查看请假申请", - "read.desc": "允许查看本人/子女/所辖班级的请假申请", + "readDesc": "允许查看本人/子女/所辖班级的请假申请", "review": "审批请假申请", - "review.desc": "允许班主任/管理员审批请假申请并自动同步考勤" + "reviewDesc": "允许班主任/管理员审批请假申请并自动同步考勤" }, "message": { "send": "发送消息", - "send.desc": "允许发送消息", + "sendDesc": "允许发送消息", "read": "查看消息", - "read.desc": "允许查看消息", + "readDesc": "允许查看消息", "delete": "删除消息", - "delete.desc": "允许删除消息" + "deleteDesc": "允许删除消息" }, "scheduling": { "auto": "自动排课", - "auto.desc": "允许执行自动排课", + "autoDesc": "允许执行自动排课", "adjust": "调整排课", - "adjust.desc": "允许手动调整课程表" + "adjustDesc": "允许手动调整课程表" }, "elective": { "manage": "管理选课", - "manage.desc": "允许管理选课设置", + "manageDesc": "允许管理选课设置", "read": "查看选课", - "read.desc": "允许查看选课信息", + "readDesc": "允许查看选课信息", "select": "选课", - "select.desc": "允许学生进行选课" + "selectDesc": "允许学生进行选课" }, "diagnostic": { "manage": "管理诊断", - "manage.desc": "允许管理诊断测试", + "manageDesc": "允许管理诊断测试", "read": "查看诊断", - "read.desc": "允许查看诊断结果" + "readDesc": "允许查看诊断结果" }, "lesson_plan": { "create": "创建教案", - "create.desc": "允许创建新教案", + "createDesc": "允许创建新教案", "read": "查看教案", - "read.desc": "允许查看教案列表和详情", + "readDesc": "允许查看教案列表和详情", "update": "更新教案", - "update.desc": "允许修改教案信息", + "updateDesc": "允许修改教案信息", "delete": "删除教案", - "delete.desc": "允许删除教案", + "deleteDesc": "允许删除教案", "publish": "发布教案", - "publish.desc": "允许发布教案" + "publishDesc": "允许发布教案" }, "dashboard": { "admin_read": "查看管理员仪表盘", - "admin_read.desc": "允许查看管理员仪表盘", + "admin_readDesc": "允许查看管理员仪表盘", "teacher_read": "查看教师仪表盘", - "teacher_read.desc": "允许查看教师仪表盘", + "teacher_readDesc": "允许查看教师仪表盘", "student_read": "查看学生仪表盘", - "student_read.desc": "允许查看学生仪表盘", + "student_readDesc": "允许查看学生仪表盘", "parent_read": "查看家长仪表盘", - "parent_read.desc": "允许查看家长仪表盘" + "parent_readDesc": "允许查看家长仪表盘" }, "error_book": { "read": "查看错题本", - "read.desc": "允许查看错题本", + "readDesc": "允许查看错题本", "manage": "管理错题本", - "manage.desc": "允许管理错题记录", + "manageDesc": "允许管理错题记录", "analytics_read": "查看错题分析", - "analytics_read.desc": "允许查看错题本分析数据" + "analytics_readDesc": "允许查看错题本分析数据" }, "adaptive_practice": { "read": "查看专项练习", - "read.desc": "允许查看专项练习", + "readDesc": "允许查看专项练习", "manage": "管理专项练习", - "manage.desc": "允许管理专项练习" + "manageDesc": "允许管理专项练习" }, "rbac": { "role_create": "创建角色", - "role_create.desc": "允许创建新角色", + "role_createDesc": "允许创建新角色", "role_read": "查看角色", - "role_read.desc": "允许查看角色列表和详情", + "role_readDesc": "允许查看角色列表和详情", "role_update": "更新角色", - "role_update.desc": "允许修改角色信息", + "role_updateDesc": "允许修改角色信息", "role_delete": "删除角色", - "role_delete.desc": "允许删除角色", + "role_deleteDesc": "允许删除角色", "role_assign": "分配角色", - "role_assign.desc": "允许为用户分配角色", + "role_assignDesc": "允许为用户分配角色", "permission_read": "查看权限", - "permission_read.desc": "允许查看权限目录" + "permission_readDesc": "允许查看权限目录" } }, "actions": { diff --git a/src/shared/i18n/messages/zh-CN/student.json b/src/shared/i18n/messages/zh-CN/student.json index d0afa0b..b987a38 100644 --- a/src/shared/i18n/messages/zh-CN/student.json +++ b/src/shared/i18n/messages/zh-CN/student.json @@ -77,6 +77,12 @@ "description": "发生了意外错误,请稍后重试。", "retry": "重试" }, + "errors": { + "unexpected": "发生未知错误" + }, + "actions": { + "retry": "重试" + }, "classDetail": { "backToCourses": "返回课程", "grade": "年级 {grade}", diff --git a/src/shared/i18n/messages/zh-CN/textbooks.json b/src/shared/i18n/messages/zh-CN/textbooks.json index 9837a11..cd985d6 100644 --- a/src/shared/i18n/messages/zh-CN/textbooks.json +++ b/src/shared/i18n/messages/zh-CN/textbooks.json @@ -180,6 +180,12 @@ "loadFailedDesc": "加载教材内容时发生错误,请重试。", "retry": "重试" }, + "errors": { + "unexpected": "发生未知错误" + }, + "actions": { + "retry": "重试" + }, "action": { "createSuccess": "教材创建成功。", "createFailed": "创建教材失败。", @@ -224,6 +230,10 @@ "noClassMasteryPermission": "无权查看班级掌握度数据" }, "graph": { + "layout": { + "hierarchical": "分层布局", + "force": "力导向图" + }, "viewMode": { "structure": "结构图", "studentMastery": "个人掌握度", diff --git a/src/shared/lib/rate-limit/index.ts b/src/shared/lib/rate-limit/index.ts index 55f3004..6e85583 100644 --- a/src/shared/lib/rate-limit/index.ts +++ b/src/shared/lib/rate-limit/index.ts @@ -19,14 +19,14 @@ export type { RateLimiter, RateLimitParams, RateLimitResult } from "./types" export { RATE_LIMIT_RULES, rateLimitHeaders, rateLimitKey } from "./rules" import { MemoryRateLimiter } from "./memory-limiter" -import { RedisRateLimiter } from "./redis-limiter" /** * 单例限流器实例(按 env 选择实现,进程级复用)。 * - * RedisRateLimiter 类本身在 import 时不会加载 @upstash/* 依赖—— - * 重依赖通过类方法内的 `await import(...)` 懒加载, - * 仅当 `RATE_LIMIT_DRIVER=redis` 且实际调用 `limit()` 时才加载。 + * Redis 实现通过动态 `import("./redis-limiter")` 按需加载, + * 仅当 `RATE_LIMIT_DRIVER=redis` 时才会拉入 `redis-limiter.ts` 模块及其 + * `@upstash/ratelimit` / `@upstash/redis` 依赖。默认内存模式下该模块不进入 + * 模块图,避免可选依赖缺失导致编译失败。 */ let singleton: RateLimiter | null = null @@ -34,13 +34,16 @@ let singleton: RateLimiter | null = null * 获取当前进程的限流器实例。 * * - 默认返回内存实现 - * - 当 `RATE_LIMIT_DRIVER=redis` 时返回 Redis 实现 - * - Redis 实现懒加载所需依赖,未安装 @upstash/ratelimit/redis 时首次调用抛错 + * - 当 `RATE_LIMIT_DRIVER=redis` 时动态加载 Redis 实现 + * - Redis 实现懒加载 @upstash/* 依赖,未安装时首次调用抛错 + * + * 返回 `Promise`:Redis 实现需动态 import 模块,故为异步。 */ -export function getRateLimiter(): RateLimiter { +export async function getRateLimiter(): Promise { if (singleton) return singleton if (env.RATE_LIMIT_DRIVER === "redis") { + const { RedisRateLimiter } = await import("./redis-limiter") singleton = new RedisRateLimiter() } else { singleton = new MemoryRateLimiter() @@ -56,7 +59,7 @@ export function getRateLimiter(): RateLimiter { * **变更**:返回 `Promise`,调用方需 `await`。 */ export function rateLimit(params: RateLimitParams): Promise { - return getRateLimiter().limit(params) + return getRateLimiter().then((limiter) => limiter.limit(params)) } /** @@ -67,5 +70,5 @@ export function rateLimit(params: RateLimitParams): Promise { * **变更**:返回 `Promise`,调用方需 `await`。 */ export function resetRateLimit(key: string): Promise { - return getRateLimiter().reset(key) + return getRateLimiter().then((limiter) => limiter.reset(key)) } diff --git a/src/shared/lib/rate-limit/redis-limiter.ts b/src/shared/lib/rate-limit/redis-limiter.ts index 4ead8aa..3019e96 100644 --- a/src/shared/lib/rate-limit/redis-limiter.ts +++ b/src/shared/lib/rate-limit/redis-limiter.ts @@ -94,7 +94,8 @@ export class RedisRateLimiter implements RateLimiter { /** 懒加载 @upstash/redis 的 Redis 客户端 */ private async getRedisClient(): Promise { if (this.redisClient) return this.redisClient - const { Redis } = await import("@upstash/redis") + // webpackIgnore: 让 @upstash/redis 成为运行时可选依赖,未启用 redis 驱动时不要求安装 + const { Redis } = await import(/* webpackIgnore: true */ "@upstash/redis") this.redisClient = new Redis({ url: env.UPSTASH_REDIS_REST_URL!, token: env.UPSTASH_REDIS_REST_TOKEN!, @@ -106,7 +107,8 @@ export class RedisRateLimiter implements RateLimiter { private async getRatelimitCtor(): Promise { if (this.ratelimitCtorPromise) return this.ratelimitCtorPromise this.ratelimitCtorPromise = (async () => { - const mod = await import("@upstash/ratelimit") + // webpackIgnore: 让 @upstash/ratelimit 成为运行时可选依赖,未启用 redis 驱动时不要求安装 + const mod = await import(/* webpackIgnore: true */ "@upstash/ratelimit") return mod.Ratelimit as unknown as UpstashRatelimitCtor })() return this.ratelimitCtorPromise