+
+
+
-
-
+
+
-
-
+
- {{ countdown }}s后重新获取
+ {{ countdownText }}
-
-
- {{ props.error }}
-
-
+
+
+ {{ error }}
+
diff --git a/src/components/basic/MyButton.vue b/src/components/basic/MyButton.vue
index 55be0f2..27235de 100644
--- a/src/components/basic/MyButton.vue
+++ b/src/components/basic/MyButton.vue
@@ -5,18 +5,18 @@ const props = defineProps({
type: {
type: String,
default: 'default',
- validator: (val) => {
- return ['default', 'primary', 'success', 'warning', 'danger'].includes(
- val
- )
- }
+ validator: (value) =>
+ ['default', 'primary', 'success', 'warning', 'danger'].includes(value)
},
size: {
type: String,
default: 'medium',
- validator: (val) => {
- return ['small', 'medium', 'large'].includes(val)
- }
+ validator: (value) => ['small', 'medium', 'large'].includes(value)
+ },
+ nativeType: {
+ type: String,
+ default: 'button',
+ validator: (value) => ['button', 'submit', 'reset'].includes(value)
},
disabled: {
type: Boolean,
@@ -25,69 +25,164 @@ const props = defineProps({
loading: {
type: Boolean,
default: false
+ },
+ ariaLabel: {
+ type: String,
+ default: ''
}
})
-const buttonClass = computed(() => {
- const baseClass = 'mx-1 rounded-full transition-all duration-300 ease-in-out'
- const typeClass = {
- default:
- 'bg-gray-500 text-white hover:ring hover:ring-gray-800 hover:ring-opacity-50',
- primary:
- 'bg-blue-200 text-white hover:ring hover:ring-blue-800 hover:ring-opacity-50',
- success:
- 'bg-green-500 text-white hover:ring hover:ring-green-800 hover:ring-opacity-50',
- warning:
- 'bg-yellow-500 text-white hover:ring hover:ring-yellow-800 hover:ring-opacity-50',
- danger:
- 'bg-red-300 text-white hover:ring hover:ring-red-800 hover:ring-opacity-50'
- }[props.type]
-
- const sizeClass = {
- small: 'w-12 h-6',
- medium: 'text-base',
- large: 'w-16 h-8'
- }[props.size]
-
- return `${baseClass} ${typeClass} ${sizeClass} ${
- props.disabled ? 'cursor-not-allowed opacity-50' : ''
- }`
-})
-
-//给组件定义一个click事件
const emit = defineEmits(['click'])
+const buttonClass = computed(() => [
+ 'app-button',
+ `app-button--${props.type}`,
+ `app-button--${props.size}`
+])
+
const handleClick = (event) => {
- if (props.disabled || props.loading) {
- return
- }
+ if (props.disabled || props.loading) return
emit('click', event)
}
+
-
diff --git a/src/components/didi/DidiClassBox.vue b/src/components/didi/DidiClassBox.vue
index 5ba381d..bf9d876 100644
--- a/src/components/didi/DidiClassBox.vue
+++ b/src/components/didi/DidiClassBox.vue
@@ -2,7 +2,7 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStudentPersonStore } from '@/stores/modules/studentPerson'
-import studentAvatar from '@/assets/student/avatar.png'
+import studentAvatar from '@/assets/student/avatar-default.svg'
import teacherAvatar from '@/assets/icon/teacher.png'
const studentPersonStore = useStudentPersonStore()
diff --git a/src/components/service/LanguageToggle.vue b/src/components/service/LanguageToggle.vue
index edd6cfb..02b4c16 100644
--- a/src/components/service/LanguageToggle.vue
+++ b/src/components/service/LanguageToggle.vue
@@ -1,10 +1,12 @@
-
-
-
-
-
+
+
+ 中文
+
+
+ EN
+
+
-
+
diff --git a/src/components/service/logoComponent.vue b/src/components/service/logoComponent.vue
index e78efe8..6581567 100644
--- a/src/components/service/logoComponent.vue
+++ b/src/components/service/logoComponent.vue
@@ -1,12 +1,124 @@
-
+
-
-

-

-
+
+
+
+ 国际中文教育
+ International Chinese
+
+
+
+
diff --git a/src/i18n/index.js b/src/i18n/index.js
index 8287fa7..14f53d1 100644
--- a/src/i18n/index.js
+++ b/src/i18n/index.js
@@ -2,16 +2,39 @@ import { createI18n } from 'vue-i18n'
import zh from '@/i18n/locales/zh.json'
import en from '@/i18n/locales/en.json'
-const messages = {
- zh,
- en
+export const supportedLocales = ['zh', 'en']
+
+export function normalizeLocale(value) {
+ const locale = String(value || '').toLowerCase()
+ return locale.startsWith('zh') ? 'zh' : locale.startsWith('en') ? 'en' : null
+}
+
+export function detectInitialLocale() {
+ if (typeof window === 'undefined') return 'en'
+
+ try {
+ const stored = normalizeLocale(window.localStorage.getItem('lang'))
+ if (stored) return stored
+ } catch {
+ // Storage may be unavailable in privacy mode; browser detection still works.
+ }
+
+ const candidates = Array.isArray(window.navigator.languages)
+ ? window.navigator.languages
+ : [window.navigator.language]
+ return candidates.some((locale) => normalizeLocale(locale) === 'zh')
+ ? 'zh'
+ : 'en'
}
+const initialLocale = detectInitialLocale()
+
const i18n = createI18n({
legacy: false,
globalInjection: true,
- locale: 'zh', // 默认语言
- messages
+ locale: initialLocale,
+ fallbackLocale: 'en',
+ messages: { zh, en }
})
export default i18n
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 17ef7bd..bc1c5ef 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -7,6 +7,708 @@
"submit": "Submit",
"cancel": "Cancel"
},
- "login": "Login",
- "register": "Register"
+ "login": "Sign in",
+ "register": "Register",
+ "common": {
+ "skipToContent": "Skip to main content",
+ "requestFailed": "The request could not be completed. Please try again."
+ },
+ "a11y": {
+ "skipToContent": "Skip to main content"
+ },
+ "system": {
+ "errorBoundary": {
+ "eyebrow": "Something went wrong",
+ "title": "This page could not be displayed",
+ "description": "Your data is safe. Reload the page to try again, or return later if the problem continues.",
+ "reload": "Reload page"
+ }
+ },
+ "shell": {
+ "brandHome": "Go to your workspace home",
+ "menu": {
+ "open": "Open navigation menu",
+ "close": "Close navigation menu",
+ "drawer": "Workspace navigation"
+ },
+ "account": "Account",
+ "notifications": "Notifications",
+ "profile": "Profile",
+ "logout": "Sign out",
+ "loggingOut": "Signing out…"
+ },
+ "roles": {
+ "student": {
+ "label": "Student"
+ },
+ "teacher": {
+ "label": "Teacher"
+ },
+ "administrator": {
+ "label": "Administrator"
+ }
+ },
+ "nav": {
+ "student": {
+ "home": "Home",
+ "teachers": "Teachers",
+ "courses": "Courses",
+ "conversation": "Chinese practice"
+ },
+ "teacher": {
+ "home": "Home",
+ "requests": "Teaching requests",
+ "courses": "Courses"
+ },
+ "administrator": {
+ "operations": "Operations",
+ "reviews": "Reviews",
+ "data": "Data"
+ }
+ },
+ "teacher": {
+ "account": {
+ "profile": "Profile",
+ "security": "Security",
+ "notices": "Notifications",
+ "navigationLabel": "Teacher account settings"
+ }
+ },
+ "auth": {
+ "brandShort": "International Chinese",
+ "brandEnglish": "Learning Platform",
+ "brandHomeLabel": "Go to the public homepage",
+ "modeNavigation": "Sign-in and registration",
+ "legalNavigation": "Legal information",
+ "eyebrow": "Learn beyond the textbook",
+ "heroTitle": "Make Chinese part of your real life.",
+ "heroDescription": "Meet verified teachers, choose a learning path that fits your goals, and practise Chinese in situations that matter.",
+ "quoteCaption": "A thoughtful learning path begins with the right teacher.",
+ "loginEyebrow": "Welcome back",
+ "loginTitle": "Continue your learning",
+ "loginDescription": "Sign in to return to your courses, bookings and conversations.",
+ "roleLegend": "Choose your role",
+ "recoveryLink": "Need help signing in?",
+ "noAccount": "New to the platform?",
+ "haveAccount": "Already have an account?",
+ "betaLabel": "Public Beta",
+ "terms": "Terms of Service",
+ "privacy": "Privacy Notice",
+ "trust": {
+ "verified": "Verified teacher profiles",
+ "firstParty": "Secure first-party sessions",
+ "realLearning": "Learning built around real situations"
+ },
+ "roles": {
+ "student": "Student",
+ "teacher": "Teacher",
+ "administrator": "Administrator"
+ },
+ "fields": {
+ "email": "Email address",
+ "password": "Password",
+ "verificationCode": "Verification code",
+ "confirmPassword": "Confirm password",
+ "name": "Full name",
+ "nationality": "Nationality",
+ "region": "Country or region",
+ "age": "Age"
+ },
+ "placeholders": {
+ "email": "you{'@'}example.com",
+ "password": "Enter your password",
+ "fullEmail": "Enter your complete email address",
+ "verificationCode": "Enter the 6-digit code",
+ "newPassword": "Create a strong password",
+ "confirmPassword": "Enter the password again",
+ "name": "How should we address you?",
+ "nationality": "e.g. Canadian",
+ "region": "Where do you currently live?",
+ "age": "Your age"
+ },
+ "actions": {
+ "login": "Sign in",
+ "createAccount": "Create account",
+ "sendCode": "Send code",
+ "sendingCode": "Sending…",
+ "next": "Continue",
+ "back": "Back"
+ },
+ "messages": {
+ "loginSuccess": "Welcome back.",
+ "registerSuccess": "Your account has been created.",
+ "codeSent": "Verification code sent. Check your inbox.",
+ "codeSentDevelopment": "Verification code generated for this development environment.",
+ "roleRedirected": "This action is for student accounts. We have taken you to your workspace instead."
+ },
+ "errors": {
+ "emailRequired": "Enter your email address.",
+ "emailInvalid": "Enter a valid email address.",
+ "passwordRequired": "Enter your password.",
+ "passwordStrength": "Use at least 8 characters with uppercase, lowercase and a number.",
+ "passwordConfirmationRequired": "Enter your password again.",
+ "passwordMismatch": "The passwords do not match.",
+ "invalidProfile": "We could not load your account profile.",
+ "loginFailed": "Sign-in failed. Check your details and try again.",
+ "registerFailed": "We could not create your account. Please try again.",
+ "roleRequired": "Choose whether you are registering as a student or teacher.",
+ "codeInvalid": "Enter the complete verification code.",
+ "codeSendFailed": "The code could not be sent. You can retry now.",
+ "termsRequired": "Please agree to the Terms of Service and Privacy Notice.",
+ "nameRequired": "Enter your name.",
+ "nationalityRequired": "Enter your nationality.",
+ "regionRequired": "Enter your country or region.",
+ "ageInvalid": "Enter an age between 6 and 120.",
+ "levelRequired": "Choose your current Chinese level.",
+ "teacherReviewRequired": "Confirm that you understand the teacher review process."
+ },
+ "register": {
+ "eyebrow": "Create your account",
+ "title": "Start with a learning profile that fits you",
+ "description": "Three short steps help us prepare the right workspace. You can go back at any time.",
+ "progressLabel": "Registration progress",
+ "accountTitle": "Secure your account",
+ "accountDescription": "Choose a role and verify the email you will use to sign in.",
+ "profileTitle": "Tell us who you are",
+ "profileDescription": "We use these details to personalise your experience and support your account.",
+ "studentStepTitle": "Shape your learning path",
+ "studentStepDescription": "Choose your current level and tell teachers what you hope to achieve.",
+ "teacherStepTitle": "Prepare your teacher application",
+ "teacherStepDescription": "Tell us about relevant qualifications. An administrator will review your profile before it becomes public.",
+ "passwordHint": "Use at least 8 characters, including uppercase and lowercase letters and a number.",
+ "agreePrefix": "I agree to the",
+ "and": "and",
+ "codeCountdown": "Send again in {seconds}s",
+ "codeSentTo": "Code sent to {email}",
+ "codeValidity": "The code is valid for 10 minutes.",
+ "privacyNoteTitle": "Why we ask",
+ "privacyNote": "These details support account safety, teacher matching and platform operations. They are handled as described in our Privacy Notice.",
+ "levelLegend": "Current Chinese level",
+ "learningGoalLabel": "What would you like to do in Chinese?",
+ "learningGoalPlaceholder": "For example: speak confidently at work, prepare for HSK 4, or talk with family.",
+ "certificateLegend": "Relevant qualifications",
+ "certificateHelp": "Select qualifications you hold. This declaration is not a document upload and does not mean your profile is verified.",
+ "teacherReviewTitle": "Teacher profiles are reviewed",
+ "teacherReviewDescription": "Your profile will remain private until an administrator reviews the information you submit. We may contact you for supporting documents.",
+ "teacherReviewAcknowledgement": "I understand that declaring a qualification does not complete verification.",
+ "steps": {
+ "account": "Account",
+ "profile": "Profile",
+ "learning": "Learning"
+ },
+ "roleDescriptions": {
+ "student": "Find teachers, book lessons and manage your learning.",
+ "teacher": "Apply to teach and manage courses after verification."
+ },
+ "levels": {
+ "beginner": "I am completely new to Chinese.",
+ "hsk1": "I understand a few familiar words and simple phrases.",
+ "hsk2": "I can handle basic everyday exchanges.",
+ "hsk3": "I can communicate in many familiar situations.",
+ "hsk4": "I can discuss a broad range of everyday and study topics.",
+ "hsk5": "I can read, listen and communicate with good fluency.",
+ "hsk6": "I can understand and express complex ideas with confidence."
+ },
+ "certificates": {
+ "international": "Certificate for Teachers of Chinese to Speakers of Other Languages",
+ "tcsl": "Teaching Chinese as a Second Language qualification",
+ "ability": "Certificate of Ability in Teaching Chinese as a Foreign Language",
+ "mandarin": "Putonghua Proficiency Test certificate"
+ }
+ },
+ "session": {
+ "offline": "We could not reach the service. Check your connection and try signing in again.",
+ "expired": "Your session has expired. Sign in again to continue where you left off.",
+ "authenticationRequired": "Sign in to continue. We will return you to the page you requested."
+ },
+ "quote": "Across the world, a true friend still feels close."
+ },
+ "public": {
+ "a11y": {
+ "skipToContent": "Skip to main content"
+ },
+ "brand": {
+ "name": "International Chinese",
+ "tagline": "Learn for real life"
+ },
+ "language": {
+ "label": "Choose language"
+ },
+ "nav": {
+ "home": "Home",
+ "teachers": "Teachers",
+ "courses": "Courses",
+ "dashboard": "My workspace",
+ "signIn": "Sign in",
+ "createAccount": "Create account",
+ "primary": "Primary navigation",
+ "mobile": "Mobile navigation",
+ "toggleMenu": "Toggle navigation menu"
+ },
+ "footer": {
+ "mission": "A public Beta platform connecting Chinese learners with verified teachers and practical learning experiences.",
+ "explore": "Explore",
+ "legal": "Legal",
+ "support": "Account support",
+ "terms": "Terms of Service",
+ "privacy": "Privacy Notice",
+ "beta": "Public Beta · Features may change"
+ },
+ "common": {
+ "applyFilters": "Apply filters",
+ "clearFilters": "Clear filters",
+ "teacherLoadErrorTitle": "Teachers could not be loaded",
+ "courseLoadErrorTitle": "Courses could not be loaded",
+ "loadErrorBody": "Check your connection and try again. Other parts of the platform may still be available.",
+ "loadingTeachers": "Loading verified teachers…",
+ "loadingCourses": "Loading published courses…",
+ "retry": "Try again",
+ "pagination": "Pagination",
+ "previous": "Previous",
+ "next": "Next",
+ "pageOf": "Page {page} of {total}",
+ "breadcrumb": "Breadcrumb"
+ },
+ "teacher": {
+ "unnamed": "Teacher profile",
+ "verified": "Verified teacher",
+ "profilePending": "Professional profile coming soon",
+ "bioPending": "This teacher is still preparing their introduction.",
+ "experience": "Experience",
+ "years": "{count} years",
+ "courses": "Courses",
+ "languages": "Teaching languages",
+ "referenceRate": "Reference rate",
+ "priceOnRequest": "Ask the teacher",
+ "viewProfile": "View profile",
+ "viewNamed": "View {name}'s profile"
+ },
+ "course": {
+ "untitled": "Untitled course",
+ "summaryPending": "Course introduction coming soon.",
+ "teacher": "Teacher",
+ "duration": "Lesson length",
+ "minutes": "{count} min",
+ "referencePrice": "Reference price",
+ "free": "No fee listed",
+ "viewDetails": "View course",
+ "levels": {
+ "beginner": "Beginner",
+ "elementary": "Elementary",
+ "intermediate": "Intermediate",
+ "advanced": "Advanced",
+ "all": "All levels"
+ }
+ },
+ "home": {
+ "metaTitle": "Learn Chinese for real life",
+ "metaDescription": "Discover verified Chinese teachers and published courses, then continue from browsing to booking in one secure platform.",
+ "eyebrow": "International Chinese · Public Beta",
+ "title": "Learn Chinese for real life, with teachers you can trust.",
+ "lead": "Explore verified teacher profiles and practical courses before you create an account. When you are ready, continue straight to a booking request.",
+ "findTeacher": "Find a teacher",
+ "exploreCourses": "Explore courses",
+ "trustNote": "Browse first. Registration is only needed when you want to book or join a learning space.",
+ "stepsEyebrow": "A clear learning path",
+ "stepsTitle": "From curiosity to conversation in three steps",
+ "stepsLead": "Choose with real information, agree on a lesson with your teacher, and keep your learning work together.",
+ "stepOneTitle": "Discover",
+ "stepOneBody": "Compare verified profiles, teaching languages, specialities and published courses.",
+ "stepTwoTitle": "Request",
+ "stepTwoBody": "Send a booking request in your own time zone. The teacher confirms before a lesson is scheduled.",
+ "stepThreeTitle": "Learn",
+ "stepThreeBody": "Meet in class, complete assignments and continue practising Chinese between lessons.",
+ "teachersEyebrow": "People who teach",
+ "teachersTitle": "Meet verified teachers",
+ "teachersLead": "Every public profile belongs to an enabled teacher who has passed the platform review.",
+ "viewAllTeachers": "View all teachers",
+ "noTeachersTitle": "Our first teachers are preparing",
+ "noTeachersBody": "The production catalogue is intentionally honest: verified profiles will appear here as teachers complete review.",
+ "joinAsTeacher": "Apply as a teacher",
+ "coursesEyebrow": "Ways to learn",
+ "coursesTitle": "Explore published courses",
+ "coursesLead": "See lesson focus, level, suggested duration and reference price before deciding what fits.",
+ "viewAllCourses": "View all courses",
+ "noCoursesTitle": "Published courses are coming",
+ "noCoursesBody": "Verified teachers are preparing the first public course descriptions. No sample listings are shown as real inventory.",
+ "valuesEyebrow": "Designed for both sides",
+ "valuesTitle": "A calmer way to begin",
+ "valuesLead": "Learners get clarity before committing; teachers get a professional space built around genuine teaching work.",
+ "studentValueTitle": "For learners",
+ "studentValueBody": "Browse openly, keep bookings and assignments organised, and practise Chinese with context.",
+ "startLearning": "Create a learner account",
+ "teacherValueTitle": "For teachers",
+ "teacherValueBody": "Present your expertise, publish courses after review and manage requests in one workspace.",
+ "finalEyebrow": "Your next conversation",
+ "finalTitle": "Start by finding the person you want to learn with.",
+ "finalBody": "Explore public profiles now. Creating an account takes only a few minutes when you are ready to request a lesson."
+ },
+ "teachers": {
+ "metaTitle": "Verified Chinese teachers",
+ "metaDescription": "Search verified Chinese teachers by speciality and rating, with transparent profiles and reference rates.",
+ "eyebrow": "Teacher directory",
+ "title": "Find a teacher you can trust",
+ "lead": "Browse enabled, verified profiles and choose by expertise, language and teaching approach.",
+ "availableCount": "Verified profiles",
+ "searchLabel": "Search teachers",
+ "searchPlaceholder": "Name, language or teaching focus",
+ "specialtyLabel": "Speciality",
+ "specialtyPlaceholder": "e.g. conversation or HSK",
+ "ratingLabel": "Minimum rating",
+ "anyRating": "Any rating",
+ "ratingFour": "4.0 and above",
+ "ratingFourFive": "4.5 and above",
+ "ratingFourEight": "4.8 and above",
+ "emptyTitle": "No teachers match these filters",
+ "emptyBody": "Try a broader search or clear the filters. New verified teachers will appear as they complete review.",
+ "results": "{count} teachers",
+ "verifiedOnly": "Verified profiles only"
+ },
+ "courses": {
+ "metaTitle": "Published Chinese courses",
+ "metaDescription": "Browse published Chinese courses by topic and level before creating an account.",
+ "eyebrow": "Course directory",
+ "title": "Choose a course for the Chinese you need",
+ "lead": "Explore published courses from verified teachers, with clear focus, level and reference pricing.",
+ "publishedCount": "Published courses",
+ "searchLabel": "Search courses",
+ "searchPlaceholder": "Course title, skill or topic",
+ "categoryLabel": "Category",
+ "categoryPlaceholder": "e.g. conversation or HSK",
+ "emptyTitle": "No courses match these filters",
+ "emptyBody": "Try a broader search or clear the filters. Newly approved courses will appear here.",
+ "results": "{count} courses",
+ "publishedOnly": "Published courses only"
+ },
+ "teacherDetail": {
+ "metaTitle": "Teacher profile",
+ "metaDescription": "Review this verified Chinese teacher's experience, teaching approach, courses and reference rate.",
+ "loading": "Loading teacher profile…",
+ "notFoundTitle": "Teacher profile not found",
+ "notFoundBody": "This profile may no longer be public, or the address may be incorrect.",
+ "backToTeachers": "Back to teachers",
+ "signInToBook": "Sign in to request a lesson",
+ "requestBooking": "Request a lesson",
+ "returnToWorkspace": "Return to your workspace",
+ "confirmationRequired": "The teacher must confirm every booking request.",
+ "noCharge": "No payment is taken during the public Beta.",
+ "timezone": "Times are shown in your device time zone: {timezone}.",
+ "aboutEyebrow": "Teacher profile",
+ "aboutTitle": "About this teacher",
+ "location": "Location",
+ "methodEyebrow": "Teaching approach",
+ "methodTitle": "How lessons are taught",
+ "methodPending": "This teacher is still preparing a detailed teaching statement.",
+ "coursesEyebrow": "Published learning",
+ "coursesTitle": "Courses from this teacher",
+ "publishedCourses": "{count} published courses",
+ "noCoursesTitle": "No published courses yet",
+ "noCoursesBody": "You can still review the teacher's profile. Their courses will appear after platform review."
+ },
+ "courseDetail": {
+ "metaTitle": "Course details",
+ "metaDescription": "Review this published Chinese course's focus, level, teacher, duration and reference price.",
+ "loading": "Loading course details…",
+ "notFoundTitle": "Course not found",
+ "notFoundBody": "This course may no longer be published, or the address may be incorrect.",
+ "backToCourses": "Back to courses",
+ "signInToContinue": "Sign in to continue",
+ "openLearningSpace": "Open learning space",
+ "returnToWorkspace": "Return to your workspace",
+ "suggestedCapacity": "Suggested group size",
+ "learners": "{count} learners",
+ "meetTeacher": "View teacher profile",
+ "noPurchaseNotice": "This public Beta does not process course purchases. Prices are shown for reference only.",
+ "overviewEyebrow": "Course overview",
+ "overviewTitle": "What you will work on",
+ "descriptionPending": "The teacher is still preparing the full course description.",
+ "teacherEyebrow": "Your teacher",
+ "verifiedTeacherNote": "This course is published by a verified, enabled teacher.",
+ "viewTeacherProfile": "View full teacher profile",
+ "nextEyebrow": "Ready to continue?",
+ "nextTitle": "Choose the teacher before the timetable",
+ "nextBody": "Review the teacher's profile and request a lesson when you are signed in."
+ },
+ "recovery": {
+ "metaTitle": "Account support",
+ "metaDescription": "Contact real account support for sign-in and recovery help during the public Beta.",
+ "emailSubject": "International Chinese account support",
+ "eyebrow": "Account support",
+ "title": "Get back to your learning safely",
+ "lead": "Automated password reset is not available in this Beta. Our support contact can help verify your request.",
+ "stepOneTitle": "Use your registered email",
+ "stepOneBody": "Contact us from the email address connected to your account whenever possible.",
+ "stepTwoTitle": "Describe the issue",
+ "stepTwoBody": "Tell us whether you cannot sign in, changed email access, or noticed unusual activity. Never send your password.",
+ "stepThreeTitle": "Wait for verification",
+ "stepThreeBody": "Support may ask for non-sensitive account details before making any change.",
+ "securityTitle": "Protect your credentials",
+ "securityBody": "We will never ask for your password, verification code or Gmail app password by email.",
+ "emailSupport": "Email account support",
+ "backToSignIn": "Back to sign in",
+ "supportAddress": "Support address:"
+ },
+ "system": {
+ "403": {
+ "eyebrow": "Access restricted",
+ "title": "This area belongs to another role",
+ "body": "Your account is signed in, but it does not have permission to open this page. Return to your workspace or go back."
+ },
+ "404": {
+ "eyebrow": "Page not found",
+ "title": "We could not find that page",
+ "body": "The address may be outdated or incomplete. Explore the public teacher and course directories instead."
+ },
+ "backHome": "Back to homepage",
+ "goBack": "Go back"
+ },
+ "legal": {
+ "eyebrow": "Public Beta information",
+ "versionLabel": "Version",
+ "version": "Beta 1.0",
+ "updatedLabel": "Last updated",
+ "updatedDate": "9 August 2026",
+ "onThisPage": "On this page",
+ "betaNoticeTitle": "Please review before public promotion",
+ "betaNoticeBody": "This Beta text describes the platform's current behaviour and is provided for product transparency. It is not a substitute for professional legal review.",
+ "contactTitle": "Questions or account requests",
+ "contactBody": "Use the support page to reach the current platform contact. Do not include passwords or verification codes.",
+ "contactAction": "Contact support",
+ "terms": {
+ "title": "Terms of Service",
+ "lead": "These terms explain the practical rules for using the International Chinese public Beta.",
+ "sections": {
+ "service": {
+ "title": "The Beta service",
+ "body": "The platform supports account registration, public teacher and course discovery, booking requests, classrooms, assignments, notifications and Chinese practice. Beta features may change, pause or be withdrawn as the service develops."
+ },
+ "accounts": {
+ "title": "Accounts and roles",
+ "body": "You must provide accurate information, protect your credentials and use only the role assigned to you. Teacher profiles and courses remain non-public until the relevant platform review is complete."
+ },
+ "publicContent": {
+ "title": "Public profiles and courses",
+ "body": "Only enabled, verified teachers and published courses are intended to appear publicly. Reference prices, suggested capacity and descriptions are informational and do not create a purchase or guaranteed place."
+ },
+ "bookings": {
+ "title": "Booking requests",
+ "body": "A learner's request is not confirmed until the teacher accepts it. The current Beta does not take payment. Times use the learner's device time zone and should be checked by both parties."
+ },
+ "conduct": {
+ "title": "Respectful use",
+ "body": "Do not misuse another person's account, disrupt classes, upload unlawful material, harass users or attempt to bypass access controls. We may restrict an account to protect users and the service."
+ },
+ "availability": {
+ "title": "Availability and limitations",
+ "body": "The Beta is provided on an evolving basis without guaranteed uninterrupted availability. Real-time audio and video may not connect on strict networks because TURN relay service is not yet included."
+ }
+ }
+ },
+ "privacy": {
+ "title": "Privacy Notice",
+ "lead": "This notice summarises what the public Beta currently stores and why.",
+ "sections": {
+ "data": {
+ "title": "Information we collect",
+ "body": "We store registration details, role and profile information, session records, learning activity, bookings, classes, assignments, messages, notifications and files you intentionally provide."
+ },
+ "use": {
+ "title": "How information is used",
+ "body": "Information is used to operate accounts, verify teachers, publish approved content, coordinate learning, protect the service and provide account support. The Beta does not use a paid external AI service."
+ },
+ "sessions": {
+ "title": "Sessions and security",
+ "body": "Authentication uses database-backed sessions and secure HttpOnly cookies in production. Verification codes expire, and sensitive service credentials are not stored in browser code."
+ },
+ "files": {
+ "title": "Files and object storage",
+ "body": "Private uploads are stored in access-controlled object storage. Downloads require an authorisation check before a short-lived signed link is issued. File type, size and quota rules apply."
+ },
+ "retention": {
+ "title": "Retention and Beta operations",
+ "body": "Data is retained while needed to operate the Beta, maintain security and meet reasonable support obligations. Backups may remain for their scheduled retention period after an operational change."
+ },
+ "rights": {
+ "title": "Questions and requests",
+ "body": "To ask about your data or request an account change, use the published support contact. We may need to verify that the request belongs to the account holder before acting."
+ }
+ }
+ }
+ },
+ "booking": {
+ "noChargeNotice": "No payment is taken during the public Beta.",
+ "submitting": "Sending…",
+ "dialogTitle": "Request a lesson",
+ "successBody": "Your request has been sent to {teacher}. It is not confirmed until the teacher accepts it.",
+ "courseLabel": "Course",
+ "durationOption": "{minutes} minutes",
+ "intro": "Send {teacher} a lesson request. You can review the details before submitting.",
+ "defaultTopic": "Practical Chinese lesson",
+ "durationLegend": "Lesson duration",
+ "cancel": "Cancel",
+ "messageLabel": "Message to the teacher (optional)",
+ "pendingStatus": "Waiting for teacher confirmation",
+ "messagePlaceholder": "Share your goals, questions or anything the teacher should know.",
+ "topicPlaceholder": "For example: workplace introductions or HSK 4 speaking",
+ "errors": {
+ "startInvalid": "Choose a valid start time.",
+ "startTooLate": "That date is too far ahead. Choose an earlier time.",
+ "topicRequired": "Tell the teacher what you would like to work on.",
+ "conflict": "That time is no longer available. Choose another time and try again.",
+ "startPast": "Choose a time in the future.",
+ "submitFailed": "Your request could not be sent. Check your connection and try again."
+ },
+ "startLabel": "Preferred start time",
+ "successTime": "Requested time: {time}",
+ "successTitle": "Lesson request sent",
+ "submit": "Send request",
+ "confirmationNotice": "The teacher must confirm this request before it becomes a scheduled lesson.",
+ "timezoneHelp": "Times are shown in your device time zone: {timezone}.",
+ "customCourseOption": "A custom lesson with this teacher",
+ "topicLabel": "Learning topic",
+ "goToStudentHome": "Go to student home",
+ "stayHere": "Stay on this page",
+ "statusLabel": "Request status"
+ }
+ },
+ "account": {
+ "password": {
+ "mustResetDescription": "For your security, create a new password before continuing to your workspace.",
+ "description": "Choose a strong password that you do not use for another service.",
+ "new": "New password",
+ "mustResetTitle": "Change your temporary password",
+ "confirm": "Confirm new password",
+ "eyebrow": "Account security",
+ "rules": {
+ "number": "At least one number",
+ "letter": "Uppercase and lowercase letters",
+ "length": "At least 8 characters"
+ },
+ "errors": {
+ "strength": "Your new password does not meet all security requirements.",
+ "failed": "The password could not be changed. Check your current password and try again.",
+ "mismatch": "The new passwords do not match."
+ },
+ "success": "Password updated successfully.",
+ "submit": "Update password",
+ "title": "Change password",
+ "current": "Current password"
+ }
+ },
+ "studentHome": {
+ "learner": "Learner",
+ "common": {
+ "notAvailable": "Not available",
+ "priceOnRequest": "Ask the teacher",
+ "teacherFallback": "Chinese teacher"
+ },
+ "header": {
+ "title": "Good to see you, {name}.",
+ "lead": "Your workspace brings the next useful step forward, without turning learning into a wall of numbers.",
+ "timezone": "Times use your device time zone: {timezone}"
+ },
+ "focus": {
+ "seal": "NOW",
+ "lessonEyebrow": "Today's focus · Live lesson",
+ "lessonTitle": "Your next conversation: {topic}",
+ "lessonDescription": "Meet {teacher} at {time}. Your confirmed classroom is ready.",
+ "assignmentEyebrow": "Today's focus · Assignment",
+ "assignmentTitle": "Continue “{title}”",
+ "assignmentDescription": "From {course} · due {due}. Your saved progress stays with the assignment.",
+ "requestEyebrow": "Today's focus · Booking",
+ "requestTitle": "Your lesson request is with the teacher",
+ "requestDescription": "{teacher} is reviewing your request for {time}. You can check the teacher profile while you wait.",
+ "discoverEyebrow": "Today's focus · Discover",
+ "discoverTitle": "Find the teacher who fits your Chinese",
+ "discoverDescription": "Browse verified profiles and choose by teaching focus, language and reference rate."
+ },
+ "actions": {
+ "enterClassroom": "Enter classroom",
+ "continueAssignment": "Continue assignment",
+ "viewTeacher": "View teacher",
+ "findTeacher": "Find a teacher",
+ "retry": "Try again",
+ "confirmCancel": "Cancel booking",
+ "keepBooking": "Keep booking",
+ "cancelling": "Cancelling…",
+ "cancelBooking": "Cancel",
+ "openAssignments": "Open assignments",
+ "viewAllTeachers": "View all teachers",
+ "viewAllCourses": "View all courses",
+ "viewCourse": "View course"
+ },
+ "appointments": {
+ "eyebrow": "Next confirmed lesson",
+ "title": "Your next live class",
+ "minutes": "{count} min",
+ "defaultTopic": "Chinese conversation practice",
+ "cancelTitle": "Cancel this booking?",
+ "cancelConfirm": "Cancel “{topic}”? The teacher will be notified.",
+ "emptyMark": "OPEN",
+ "emptyTitle": "No confirmed lesson yet",
+ "emptyBody": "Choose a verified teacher when you are ready to schedule your next conversation.",
+ "pendingTitle": "Waiting for teacher confirmation",
+ "status": {
+ "pending": "Awaiting confirmation",
+ "accepted": "Confirmed",
+ "rejected": "Not accepted",
+ "cancelled": "Cancelled",
+ "completed": "Completed",
+ "unknown": "Status unavailable"
+ }
+ },
+ "assignments": {
+ "eyebrow": "Learning work",
+ "title": "Assignments to finish",
+ "noDeadline": "No deadline",
+ "defaultTitle": "Learning assignment",
+ "courseFallback": "Published course",
+ "due": "Due {date}",
+ "draft": "Saved draft",
+ "notStarted": "Not started",
+ "draftMark": "D",
+ "taskMark": "A",
+ "emptyMark": "CLEAR",
+ "emptyTitle": "You are clear for now",
+ "emptyBody": "Published assignments that still need your work will appear here."
+ },
+ "teachers": {
+ "eyebrow": "Verified guidance",
+ "title": "Teachers worth meeting",
+ "lead": "These are real, enabled teacher profiles that have passed platform review.",
+ "avatarAlt": "Portrait of {name}",
+ "seal": "T",
+ "verified": "Verified",
+ "profilePending": "Professional profile in progress",
+ "experience": "Experience",
+ "years": "{count} years",
+ "referenceRate": "Reference rate",
+ "emptyTitle": "Verified teachers are preparing",
+ "emptyBody": "No sample profiles are shown as real teachers. Approved profiles will appear here."
+ },
+ "courses": {
+ "eyebrow": "Published learning",
+ "title": "Courses to explore",
+ "lead": "Review the focus, teacher, lesson length and reference price before choosing.",
+ "coverAlt": "Cover for {title}",
+ "coverMark": "CHINESE · IN CONTEXT",
+ "defaultTitle": "Chinese course",
+ "categoryFallback": "General Chinese",
+ "summaryPending": "The teacher is preparing the course introduction.",
+ "minutes": "{count} min",
+ "emptyMark": "C",
+ "emptyTitle": "Published courses are coming",
+ "emptyBody": "Courses will appear after a verified teacher publishes them and platform review is complete."
+ },
+ "messages": {
+ "cancelled": "The booking has been cancelled."
+ },
+ "errors": {
+ "schedule": "Your schedule could not be loaded. Other learning tools are still available.",
+ "assignments": "Assignments could not be loaded. Try this section again.",
+ "teachers": "Teacher recommendations could not be loaded. Course discovery still works.",
+ "courses": "Course recommendations could not be loaded. Teacher discovery still works.",
+ "cancel": "The booking could not be cancelled. Please try again."
+ }
+ }
}
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index 4b826f6..f780979 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -8,5 +8,707 @@
"cancel": "取消"
},
"login": "登录",
- "register": "注册"
+ "register": "注册",
+ "common": {
+ "skipToContent": "跳到主要内容",
+ "requestFailed": "请求未能完成,请稍后重试。"
+ },
+ "a11y": {
+ "skipToContent": "跳到主要内容"
+ },
+ "system": {
+ "errorBoundary": {
+ "eyebrow": "页面出现问题",
+ "title": "暂时无法显示此页面",
+ "description": "你的数据不会丢失。请重新加载页面;如果问题仍然存在,可以稍后再试。",
+ "reload": "重新加载"
+ }
+ },
+ "shell": {
+ "brandHome": "返回工作台首页",
+ "menu": {
+ "open": "打开导航菜单",
+ "close": "关闭导航菜单",
+ "drawer": "工作台导航"
+ },
+ "account": "账户",
+ "notifications": "通知",
+ "profile": "个人资料",
+ "logout": "退出登录",
+ "loggingOut": "正在退出…"
+ },
+ "roles": {
+ "student": {
+ "label": "学生"
+ },
+ "teacher": {
+ "label": "教师"
+ },
+ "administrator": {
+ "label": "管理员"
+ }
+ },
+ "nav": {
+ "student": {
+ "home": "首页",
+ "teachers": "找教师",
+ "courses": "选课程",
+ "conversation": "中文练习"
+ },
+ "teacher": {
+ "home": "首页",
+ "requests": "教学对接",
+ "courses": "课程管理"
+ },
+ "administrator": {
+ "operations": "平台运营",
+ "reviews": "审核中心",
+ "data": "数据中心"
+ }
+ },
+ "teacher": {
+ "account": {
+ "profile": "个人资料",
+ "security": "安全设置",
+ "notices": "消息通知",
+ "navigationLabel": "教师账户设置"
+ }
+ },
+ "auth": {
+ "brandShort": "国际中文教育",
+ "brandEnglish": "International Chinese",
+ "brandHomeLabel": "返回公开首页",
+ "modeNavigation": "登录与注册",
+ "legalNavigation": "法律信息",
+ "eyebrow": "走出课本,走进真实生活",
+ "heroTitle": "让中文真正走进你的生活。",
+ "heroDescription": "认识经过认证的教师,选择适合目标的学习路径,在真正重要的场景里使用中文。",
+ "quoteCaption": "合适的教师,是一段好学习旅程的起点。",
+ "loginEyebrow": "欢迎回来",
+ "loginTitle": "继续你的中文学习",
+ "loginDescription": "登录后回到课程、预约与中文练习。",
+ "roleLegend": "选择登录身份",
+ "recoveryLink": "登录遇到问题?",
+ "noAccount": "第一次使用平台?",
+ "haveAccount": "已经有账户?",
+ "betaLabel": "公开测试版",
+ "terms": "服务条款",
+ "privacy": "隐私说明",
+ "trust": {
+ "verified": "教师资料经过平台认证",
+ "firstParty": "安全的第一方会话",
+ "realLearning": "围绕真实场景学习"
+ },
+ "roles": {
+ "student": "学生",
+ "teacher": "教师",
+ "administrator": "管理员"
+ },
+ "fields": {
+ "email": "邮箱",
+ "password": "密码",
+ "verificationCode": "验证码",
+ "confirmPassword": "确认密码",
+ "name": "姓名",
+ "nationality": "国籍",
+ "region": "国家或地区",
+ "age": "年龄"
+ },
+ "placeholders": {
+ "email": "you{'@'}example.com",
+ "password": "请输入密码",
+ "fullEmail": "请输入完整邮箱地址",
+ "verificationCode": "请输入 6 位验证码",
+ "newPassword": "设置一个强密码",
+ "confirmPassword": "请再次输入密码",
+ "name": "我们应该如何称呼你?",
+ "nationality": "例如:加拿大",
+ "region": "你目前居住在哪里?",
+ "age": "请输入年龄"
+ },
+ "actions": {
+ "login": "登录",
+ "createAccount": "创建账户",
+ "sendCode": "发送验证码",
+ "sendingCode": "正在发送…",
+ "next": "继续",
+ "back": "返回"
+ },
+ "messages": {
+ "loginSuccess": "欢迎回来。",
+ "registerSuccess": "账户创建成功。",
+ "codeSent": "验证码已发送,请查看邮箱。",
+ "codeSentDevelopment": "开发环境验证码已生成。",
+ "roleRedirected": "该操作仅面向学生账户,已为你返回对应工作台。"
+ },
+ "errors": {
+ "emailRequired": "请输入邮箱。",
+ "emailInvalid": "请输入有效的邮箱地址。",
+ "passwordRequired": "请输入密码。",
+ "passwordStrength": "密码至少 8 位,并包含大小写字母和数字。",
+ "passwordConfirmationRequired": "请再次输入密码。",
+ "passwordMismatch": "两次输入的密码不一致。",
+ "invalidProfile": "暂时无法读取账户资料。",
+ "loginFailed": "登录失败,请检查信息后重试。",
+ "registerFailed": "账户创建失败,请稍后重试。",
+ "roleRequired": "请选择学生或教师身份。",
+ "codeInvalid": "请输入完整验证码。",
+ "codeSendFailed": "验证码发送失败,可以立即重试。",
+ "termsRequired": "请先同意服务条款和隐私说明。",
+ "nameRequired": "请输入姓名。",
+ "nationalityRequired": "请输入国籍。",
+ "regionRequired": "请输入国家或地区。",
+ "ageInvalid": "请输入 6 至 120 之间的年龄。",
+ "levelRequired": "请选择当前中文水平。",
+ "teacherReviewRequired": "请确认你已了解教师审核流程。"
+ },
+ "register": {
+ "eyebrow": "创建账户",
+ "title": "从一份适合你的学习档案开始",
+ "description": "三个简短步骤即可建立工作台,过程中可以随时返回修改。",
+ "progressLabel": "注册进度",
+ "accountTitle": "保护你的账户",
+ "accountDescription": "选择身份,并验证今后用于登录的邮箱。",
+ "profileTitle": "介绍一下你自己",
+ "profileDescription": "这些信息用于个性化体验、教师匹配和账户支持。",
+ "studentStepTitle": "规划你的学习路径",
+ "studentStepDescription": "选择当前水平,并告诉教师你希望实现的目标。",
+ "teacherStepTitle": "准备教师申请",
+ "teacherStepDescription": "声明相关资质。管理员审核通过后,资料才会公开。",
+ "passwordHint": "至少 8 位,并包含大小写字母和数字。",
+ "agreePrefix": "我已阅读并同意",
+ "and": "和",
+ "codeCountdown": "{seconds} 秒后可重新发送",
+ "codeSentTo": "验证码已发送至 {email}",
+ "codeValidity": "验证码 10 分钟内有效。",
+ "privacyNoteTitle": "为什么需要这些信息",
+ "privacyNote": "这些资料用于账户安全、教师匹配与平台运营,具体处理方式见隐私说明。",
+ "levelLegend": "当前中文水平",
+ "learningGoalLabel": "你希望用中文完成什么?",
+ "learningGoalPlaceholder": "例如:在工作中自信交流、准备 HSK 4,或与家人沟通。",
+ "certificateLegend": "相关资质",
+ "certificateHelp": "请选择你已取得的资质。这里仅作声明,不是证书上传,也不代表已经通过认证。",
+ "teacherReviewTitle": "教师资料需要审核",
+ "teacherReviewDescription": "管理员审核前,你的资料不会公开。平台可能联系你补充证明材料。",
+ "teacherReviewAcknowledgement": "我明白,声明资质不等于完成教师认证。",
+ "steps": {
+ "account": "账户",
+ "profile": "资料",
+ "learning": "学习"
+ },
+ "roleDescriptions": {
+ "student": "寻找教师、预约课程并管理学习进度。",
+ "teacher": "申请成为教师,认证后管理课程与教学。"
+ },
+ "levels": {
+ "beginner": "我还没有学过中文。",
+ "hsk1": "我能理解少量常用词和简单短语。",
+ "hsk2": "我能完成基础日常交流。",
+ "hsk3": "我能应对许多熟悉的生活场景。",
+ "hsk4": "我能讨论较广泛的生活和学习话题。",
+ "hsk5": "我的听读和表达较为流畅。",
+ "hsk6": "我能理解并自信表达复杂观点。"
+ },
+ "certificates": {
+ "international": "国际中文教师证书",
+ "tcsl": "对外汉语教师资格证",
+ "ability": "汉语作为外语教学能力证书",
+ "mandarin": "普通话水平测试证书"
+ }
+ },
+ "session": {
+ "offline": "暂时无法连接服务,请检查网络后重新登录。",
+ "expired": "登录会话已过期,请重新登录后继续刚才的操作。",
+ "authenticationRequired": "请先登录,完成后会返回你刚才访问的页面。"
+ },
+ "quote": "海内存知己,天涯若比邻。"
+ },
+ "public": {
+ "a11y": {
+ "skipToContent": "跳到主要内容"
+ },
+ "brand": {
+ "name": "国际中文教育",
+ "tagline": "学以致用,走进生活"
+ },
+ "language": {
+ "label": "选择语言"
+ },
+ "nav": {
+ "home": "首页",
+ "teachers": "找教师",
+ "courses": "选课程",
+ "dashboard": "我的工作台",
+ "signIn": "登录",
+ "createAccount": "创建账户",
+ "primary": "主导航",
+ "mobile": "移动端导航",
+ "toggleMenu": "展开或收起导航菜单"
+ },
+ "footer": {
+ "mission": "连接中文学习者、认证教师与真实学习场景的公开测试平台。",
+ "explore": "探索",
+ "legal": "法律信息",
+ "support": "账户支持",
+ "terms": "服务条款",
+ "privacy": "隐私说明",
+ "beta": "公开测试版 · 功能可能调整"
+ },
+ "common": {
+ "applyFilters": "应用筛选",
+ "clearFilters": "清除筛选",
+ "teacherLoadErrorTitle": "暂时无法加载教师",
+ "courseLoadErrorTitle": "暂时无法加载课程",
+ "loadErrorBody": "请检查网络后重试,平台其他功能可能仍可使用。",
+ "loadingTeachers": "正在加载认证教师…",
+ "loadingCourses": "正在加载已发布课程…",
+ "retry": "重试",
+ "pagination": "分页",
+ "previous": "上一页",
+ "next": "下一页",
+ "pageOf": "第 {page} 页,共 {total} 页",
+ "breadcrumb": "面包屑导航"
+ },
+ "teacher": {
+ "unnamed": "教师资料",
+ "verified": "认证教师",
+ "profilePending": "专业资料正在完善",
+ "bioPending": "教师正在准备个人介绍。",
+ "experience": "教学经验",
+ "years": "{count} 年",
+ "courses": "课程",
+ "languages": "授课语言",
+ "referenceRate": "参考价格",
+ "priceOnRequest": "请咨询教师",
+ "viewProfile": "查看资料",
+ "viewNamed": "查看 {name} 的资料"
+ },
+ "course": {
+ "untitled": "未命名课程",
+ "summaryPending": "课程简介正在完善。",
+ "teacher": "授课教师",
+ "duration": "课时长度",
+ "minutes": "{count} 分钟",
+ "referencePrice": "参考价格",
+ "free": "暂未标价",
+ "viewDetails": "查看课程",
+ "levels": {
+ "beginner": "入门",
+ "elementary": "初级",
+ "intermediate": "中级",
+ "advanced": "高级",
+ "all": "不限水平"
+ }
+ },
+ "home": {
+ "metaTitle": "把中文学到真实场景里",
+ "metaDescription": "浏览认证中文教师与已发布课程,从公开了解一路顺畅进入注册和预约。",
+ "eyebrow": "国际中文教育 · 公开测试版",
+ "title": "和可信赖的老师一起,把中文学到真实场景里。",
+ "lead": "注册前即可查看认证教师与实用课程;决定继续时,可以直接发起预约请求。",
+ "findTeacher": "寻找教师",
+ "exploreCourses": "浏览课程",
+ "trustNote": "先浏览,再决定。只有预约或进入学习空间时才需要注册。",
+ "stepsEyebrow": "清晰的学习路径",
+ "stepsTitle": "从好奇到开口,只需三步",
+ "stepsLead": "用真实信息做选择,与教师确认课堂,并把后续学习集中管理。",
+ "stepOneTitle": "发现",
+ "stepOneBody": "比较认证资料、授课语言、教学专长与已发布课程。",
+ "stepTwoTitle": "预约",
+ "stepTwoBody": "按你的时区提交预约,由教师确认后才会正式排课。",
+ "stepThreeTitle": "学习",
+ "stepThreeBody": "进入课堂、完成作业,并在课间持续练习中文。",
+ "teachersEyebrow": "认识教师",
+ "teachersTitle": "找到值得信赖的教师",
+ "teachersLead": "所有公开资料都来自已启用并通过平台审核的教师。",
+ "viewAllTeachers": "查看全部教师",
+ "noTeachersTitle": "首批教师正在准备",
+ "noTeachersBody": "生产环境不会用演示资料充数;教师通过审核后,真实资料会显示在这里。",
+ "joinAsTeacher": "申请成为教师",
+ "coursesEyebrow": "选择学习方式",
+ "coursesTitle": "探索已发布课程",
+ "coursesLead": "先了解课程重点、水平、建议时长和参考价格,再判断是否适合。",
+ "viewAllCourses": "查看全部课程",
+ "noCoursesTitle": "课程正在准备",
+ "noCoursesBody": "认证教师正在完善首批公开课程。平台不会把演示内容伪装成真实课程。",
+ "valuesEyebrow": "兼顾学习与教学",
+ "valuesTitle": "从容地开始一段学习",
+ "valuesLead": "学生在行动前看清信息,教师则拥有围绕真实教学工作的专业空间。",
+ "studentValueTitle": "面向学习者",
+ "studentValueBody": "开放浏览,集中管理预约与作业,在具体语境中练习中文。",
+ "startLearning": "创建学生账户",
+ "teacherValueTitle": "面向教师",
+ "teacherValueBody": "展示专业能力,审核后发布课程,并在同一工作台处理教学请求。",
+ "finalEyebrow": "下一次中文交流",
+ "finalTitle": "先找到那个你愿意跟随学习的人。",
+ "finalBody": "现在就浏览公开资料;准备预约时,几分钟即可完成账户创建。"
+ },
+ "teachers": {
+ "metaTitle": "认证中文教师",
+ "metaDescription": "按专长与评分寻找认证中文教师,查看透明资料和参考价格。",
+ "eyebrow": "教师目录",
+ "title": "寻找值得信赖的教师",
+ "lead": "浏览已启用、已认证的教师资料,按专长、语言与教学方式选择。",
+ "availableCount": "认证教师",
+ "searchLabel": "搜索教师",
+ "searchPlaceholder": "姓名、语言或教学方向",
+ "specialtyLabel": "教学专长",
+ "specialtyPlaceholder": "例如:口语或 HSK",
+ "ratingLabel": "最低评分",
+ "anyRating": "不限评分",
+ "ratingFour": "4.0 分及以上",
+ "ratingFourFive": "4.5 分及以上",
+ "ratingFourEight": "4.8 分及以上",
+ "emptyTitle": "没有符合条件的教师",
+ "emptyBody": "请扩大搜索范围或清除筛选。新教师通过认证后会出现在这里。",
+ "results": "共 {count} 位教师",
+ "verifiedOnly": "仅显示认证资料"
+ },
+ "courses": {
+ "metaTitle": "已发布中文课程",
+ "metaDescription": "注册前即可按主题与水平浏览认证教师发布的中文课程。",
+ "eyebrow": "课程目录",
+ "title": "选择真正用得上的中文课程",
+ "lead": "查看认证教师发布的课程,清楚了解重点、水平与参考价格。",
+ "publishedCount": "已发布课程",
+ "searchLabel": "搜索课程",
+ "searchPlaceholder": "课程名称、技能或主题",
+ "categoryLabel": "课程分类",
+ "categoryPlaceholder": "例如:口语或 HSK",
+ "emptyTitle": "没有符合条件的课程",
+ "emptyBody": "请扩大搜索范围或清除筛选。新课程审核通过后会显示在这里。",
+ "results": "共 {count} 门课程",
+ "publishedOnly": "仅显示已发布课程"
+ },
+ "teacherDetail": {
+ "metaTitle": "教师资料",
+ "metaDescription": "查看认证中文教师的经验、教学方式、课程与参考价格。",
+ "loading": "正在加载教师资料…",
+ "notFoundTitle": "没有找到教师资料",
+ "notFoundBody": "该资料可能已不再公开,或访问地址有误。",
+ "backToTeachers": "返回教师目录",
+ "signInToBook": "登录后预约",
+ "requestBooking": "发起预约",
+ "returnToWorkspace": "返回工作台",
+ "confirmationRequired": "所有预约都需要教师确认。",
+ "noCharge": "公开测试期间不会收取费用。",
+ "timezone": "时间按你的设备时区显示:{timezone}。",
+ "aboutEyebrow": "教师资料",
+ "aboutTitle": "关于这位教师",
+ "location": "所在地区",
+ "methodEyebrow": "教学方式",
+ "methodTitle": "课堂如何进行",
+ "methodPending": "教师正在完善详细的教学说明。",
+ "coursesEyebrow": "已发布内容",
+ "coursesTitle": "这位教师的课程",
+ "publishedCourses": "共 {count} 门已发布课程",
+ "noCoursesTitle": "暂时没有已发布课程",
+ "noCoursesBody": "你仍可先了解教师资料;课程通过平台审核后会显示在这里。"
+ },
+ "courseDetail": {
+ "metaTitle": "课程详情",
+ "metaDescription": "查看已发布中文课程的重点、水平、教师、时长与参考价格。",
+ "loading": "正在加载课程详情…",
+ "notFoundTitle": "没有找到课程",
+ "notFoundBody": "该课程可能已不再发布,或访问地址有误。",
+ "backToCourses": "返回课程目录",
+ "signInToContinue": "登录后继续",
+ "openLearningSpace": "进入学习空间",
+ "returnToWorkspace": "返回工作台",
+ "suggestedCapacity": "建议人数",
+ "learners": "{count} 人",
+ "meetTeacher": "查看教师资料",
+ "noPurchaseNotice": "公开测试版暂不处理课程购买,页面价格仅供参考。",
+ "overviewEyebrow": "课程概览",
+ "overviewTitle": "你会学习什么",
+ "descriptionPending": "教师正在完善完整课程说明。",
+ "teacherEyebrow": "授课教师",
+ "verifiedTeacherNote": "该课程由已启用的认证教师发布。",
+ "viewTeacherProfile": "查看完整教师资料",
+ "nextEyebrow": "准备继续?",
+ "nextTitle": "先选择教师,再确认时间",
+ "nextBody": "了解教师资料,登录后即可发起课堂预约。"
+ },
+ "recovery": {
+ "metaTitle": "账户支持",
+ "metaDescription": "公开测试期间,通过真实支持邮箱获取登录与账户恢复帮助。",
+ "emailSubject": "国际中文教育平台账户支持",
+ "eyebrow": "账户支持",
+ "title": "安全地回到你的学习",
+ "lead": "测试版暂未提供自动重置密码。你可以联系真实支持人员核验请求。",
+ "stepOneTitle": "使用注册邮箱",
+ "stepOneBody": "请尽量使用账户绑定的邮箱联系我们。",
+ "stepTwoTitle": "说明问题",
+ "stepTwoBody": "请说明无法登录、邮箱失效或异常活动等情况,但不要发送密码。",
+ "stepThreeTitle": "等待身份核验",
+ "stepThreeBody": "修改账户前,支持人员可能要求提供非敏感的账户信息。",
+ "securityTitle": "保护你的凭据",
+ "securityBody": "我们不会通过邮件索要密码、验证码或 Gmail 应用专用密码。",
+ "emailSupport": "发送邮件给账户支持",
+ "backToSignIn": "返回登录",
+ "supportAddress": "支持邮箱:"
+ },
+ "system": {
+ "403": {
+ "eyebrow": "访问受限",
+ "title": "这个页面属于其他角色",
+ "body": "你的账户已登录,但没有权限打开此页面。请返回自己的工作台或上一页。"
+ },
+ "404": {
+ "eyebrow": "页面不存在",
+ "title": "没有找到这个页面",
+ "body": "地址可能已失效或不完整。你可以继续浏览公开教师和课程目录。"
+ },
+ "backHome": "返回首页",
+ "goBack": "返回上一页"
+ },
+ "legal": {
+ "eyebrow": "公开测试版信息",
+ "versionLabel": "版本",
+ "version": "Beta 1.0",
+ "updatedLabel": "更新日期",
+ "updatedDate": "2026 年 8 月 9 日",
+ "onThisPage": "本页内容",
+ "betaNoticeTitle": "正式推广前请完成专业复核",
+ "betaNoticeBody": "本文案按平台当前真实行为说明测试版能力,用于产品透明度,不替代专业法律意见。",
+ "contactTitle": "问题与账户请求",
+ "contactBody": "请通过支持页面联系当前平台负责人,不要提交密码或验证码。",
+ "contactAction": "联系支持",
+ "terms": {
+ "title": "服务条款",
+ "lead": "本条款说明国际中文教育平台公开测试版的基本使用规则。",
+ "sections": {
+ "service": {
+ "title": "测试版服务",
+ "body": "平台目前支持账户注册、公开浏览教师与课程、提交预约、在线课堂、作业、通知和中文练习。随着产品完善,测试功能可能调整、暂停或下线。"
+ },
+ "accounts": {
+ "title": "账户与角色",
+ "body": "你应提供准确信息、妥善保管登录凭据,并仅使用账户对应的角色权限。教师资料和课程只有在完成相应平台审核后才会公开。"
+ },
+ "publicContent": {
+ "title": "公开资料与课程",
+ "body": "平台仅应公开已启用、已认证的教师和已发布课程。参考价格、建议人数与课程描述仅作信息展示,不构成购买、录取或席位保证。"
+ },
+ "bookings": {
+ "title": "预约请求",
+ "body": "学生提交请求后,只有教师接受才算确认。当前测试版不收取费用。时间按学生设备时区显示,双方都应在上课前再次核对。"
+ },
+ "conduct": {
+ "title": "合理使用",
+ "body": "请勿冒用账户、干扰课堂、上传违法内容、骚扰用户或绕过权限控制。为保护用户与服务,平台可以限制存在风险的账户。"
+ },
+ "availability": {
+ "title": "可用性与限制",
+ "body": "测试版持续迭代,不保证服务始终不中断。由于尚未部署 TURN 中继,严格网络环境下可能无法建立实时音视频连接。"
+ }
+ }
+ },
+ "privacy": {
+ "title": "隐私说明",
+ "lead": "本说明概括公开测试版当前保存的数据及其用途。",
+ "sections": {
+ "data": {
+ "title": "我们收集的信息",
+ "body": "平台会保存注册信息、角色与资料、会话记录、学习活动、预约、课堂、作业、消息、通知,以及你主动提供的文件。"
+ },
+ "use": {
+ "title": "信息用途",
+ "body": "这些信息用于运营账户、认证教师、发布已审核内容、协作学习、保护服务和提供账户支持。测试版目前不使用付费外部 AI 服务。"
+ },
+ "sessions": {
+ "title": "会话与安全",
+ "body": "生产环境使用数据库会话和安全的 HttpOnly Cookie 完成认证。验证码会过期,敏感服务凭据不会写入浏览器代码。"
+ },
+ "files": {
+ "title": "文件与对象存储",
+ "body": "私有文件保存在受权限控制的对象存储中。下载前会先核验权限,再签发短时有效链接;文件类型、大小和配额均有限制。"
+ },
+ "retention": {
+ "title": "保留与测试运营",
+ "body": "数据会在运营测试版、维护安全和处理合理支持请求所需期间保留。运维变更后,备份仍可能保留至既定周期结束。"
+ },
+ "rights": {
+ "title": "查询与请求",
+ "body": "如需查询数据或变更账户,请使用公开支持渠道。执行操作前,平台可能需要核验请求人确为账户持有人。"
+ }
+ }
+ }
+ },
+ "booking": {
+ "noChargeNotice": "公开测试期间不会收取费用。",
+ "submitting": "正在发送…",
+ "dialogTitle": "发起课堂预约",
+ "successBody": "预约已发送给 {teacher};教师接受前,该课堂尚未确认。",
+ "courseLabel": "关联课程",
+ "durationOption": "{minutes} 分钟",
+ "intro": "向 {teacher} 提交课堂请求,发送前可以核对全部信息。",
+ "defaultTopic": "实用中文课堂",
+ "durationLegend": "课堂时长",
+ "cancel": "取消",
+ "messageLabel": "给教师留言(选填)",
+ "pendingStatus": "等待教师确认",
+ "messagePlaceholder": "可以补充学习目标、问题或希望教师提前了解的信息。",
+ "topicPlaceholder": "例如:职场自我介绍或 HSK 4 口语",
+ "errors": {
+ "startInvalid": "请选择有效的开始时间。",
+ "startTooLate": "预约日期距离现在太远,请选择更早的时间。",
+ "topicRequired": "请告诉教师你希望学习的内容。",
+ "conflict": "该时段已不可用,请重新选择时间后再试。",
+ "startPast": "请选择未来的时间。",
+ "submitFailed": "预约发送失败,请检查网络后重试。"
+ },
+ "startLabel": "期望开始时间",
+ "successTime": "预约时间:{time}",
+ "successTitle": "预约请求已发送",
+ "submit": "发送预约",
+ "confirmationNotice": "教师接受后,预约才会成为正式课堂。",
+ "timezoneHelp": "时间按你的设备时区显示:{timezone}。",
+ "customCourseOption": "与这位教师定制一节课",
+ "topicLabel": "学习主题",
+ "goToStudentHome": "前往学生首页",
+ "stayHere": "留在当前页面",
+ "statusLabel": "预约状态"
+ }
+ },
+ "account": {
+ "password": {
+ "mustResetDescription": "为保护账户安全,请设置新密码后再进入工作台。",
+ "description": "请设置一个未在其他服务中使用的强密码。",
+ "new": "新密码",
+ "mustResetTitle": "请先修改临时密码",
+ "confirm": "确认新密码",
+ "eyebrow": "账户安全",
+ "rules": {
+ "number": "至少包含一个数字",
+ "letter": "同时包含大写和小写字母",
+ "length": "至少 8 个字符"
+ },
+ "errors": {
+ "strength": "新密码未满足全部安全要求。",
+ "failed": "密码修改失败,请检查当前密码后重试。",
+ "mismatch": "两次输入的新密码不一致。"
+ },
+ "success": "密码修改成功。",
+ "submit": "更新密码",
+ "title": "修改密码",
+ "current": "当前密码"
+ }
+ },
+ "studentHome": {
+ "learner": "学习者",
+ "common": {
+ "notAvailable": "暂无信息",
+ "priceOnRequest": "请咨询教师",
+ "teacherFallback": "中文教师"
+ },
+ "header": {
+ "title": "{name},今天继续把中文用起来。",
+ "lead": "首页只把最值得处理的下一步放到最前面,让学习不被一排数字淹没。",
+ "timezone": "时间按你的设备时区显示:{timezone}"
+ },
+ "focus": {
+ "seal": "今",
+ "lessonEyebrow": "今日重点 · 实时课堂",
+ "lessonTitle": "下一次对话:{topic}",
+ "lessonDescription": "{time} 与 {teacher} 见面,已确认的课堂可以直接进入。",
+ "assignmentEyebrow": "今日重点 · 学习作业",
+ "assignmentTitle": "继续完成《{title}》",
+ "assignmentDescription": "来自《{course}》· 截止 {due},已保存的进度会留在作业中。",
+ "requestEyebrow": "今日重点 · 预约确认",
+ "requestTitle": "预约请求正在等待教师确认",
+ "requestDescription": "{teacher} 正在查看你提交的 {time} 预约;等待期间可以继续了解教师资料。",
+ "discoverEyebrow": "今日重点 · 寻找教师",
+ "discoverTitle": "找到适合你的中文教师",
+ "discoverDescription": "浏览已认证资料,按教学方向、授课语言和参考价格做出选择。"
+ },
+ "actions": {
+ "enterClassroom": "进入课堂",
+ "continueAssignment": "继续作业",
+ "viewTeacher": "查看教师",
+ "findTeacher": "寻找教师",
+ "retry": "重试",
+ "confirmCancel": "确认取消",
+ "keepBooking": "保留预约",
+ "cancelling": "正在取消…",
+ "cancelBooking": "取消预约",
+ "openAssignments": "打开作业",
+ "viewAllTeachers": "查看全部教师",
+ "viewAllCourses": "查看全部课程",
+ "viewCourse": "查看课程"
+ },
+ "appointments": {
+ "eyebrow": "下一堂已确认课",
+ "title": "你的下一次实时课堂",
+ "minutes": "{count} 分钟",
+ "defaultTopic": "中文情景对话",
+ "cancelTitle": "取消这次预约?",
+ "cancelConfirm": "确定取消“{topic}”吗?教师会收到通知。",
+ "emptyMark": "待",
+ "emptyTitle": "还没有已确认的课堂",
+ "emptyBody": "准备好安排下一次对话时,可以从已认证教师中选择。",
+ "pendingTitle": "等待教师确认",
+ "status": {
+ "pending": "等待确认",
+ "accepted": "已确认",
+ "rejected": "未被接受",
+ "cancelled": "已取消",
+ "completed": "已完成",
+ "unknown": "状态未知"
+ }
+ },
+ "assignments": {
+ "eyebrow": "学习任务",
+ "title": "待完成作业",
+ "noDeadline": "长期有效",
+ "defaultTitle": "学习作业",
+ "courseFallback": "已发布课程",
+ "due": "截止 {date}",
+ "draft": "已保存草稿",
+ "notStarted": "尚未开始",
+ "draftMark": "稿",
+ "taskMark": "作",
+ "emptyMark": "清",
+ "emptyTitle": "当前没有待完成作业",
+ "emptyBody": "教师发布且仍需完成的作业会显示在这里。"
+ },
+ "teachers": {
+ "eyebrow": "认证指导",
+ "title": "值得认识的教师",
+ "lead": "这里展示的都是真实启用、已经通过平台审核的教师资料。",
+ "avatarAlt": "{name} 的头像",
+ "seal": "师",
+ "verified": "已认证",
+ "profilePending": "专业资料正在完善",
+ "experience": "教学经验",
+ "years": "{count} 年",
+ "referenceRate": "参考价格",
+ "emptyTitle": "认证教师正在准备",
+ "emptyBody": "平台不会用演示账户充数;真实教师通过审核后会显示在这里。"
+ },
+ "courses": {
+ "eyebrow": "已发布学习内容",
+ "title": "可以探索的课程",
+ "lead": "先了解课程重点、教师、时长和参考价格,再决定是否适合。",
+ "coverAlt": "《{title}》课程封面",
+ "coverMark": "中文 · 在场景中",
+ "defaultTitle": "中文课程",
+ "categoryFallback": "通用中文",
+ "summaryPending": "教师正在完善课程简介。",
+ "minutes": "{count} 分钟",
+ "emptyMark": "课",
+ "emptyTitle": "已发布课程正在准备",
+ "emptyBody": "认证教师发布并通过平台审核后,真实课程会显示在这里。"
+ },
+ "messages": {
+ "cancelled": "预约已取消。"
+ },
+ "errors": {
+ "schedule": "暂时无法加载学习安排,其他学习功能仍可继续使用。",
+ "assignments": "暂时无法加载作业,请单独重试这一部分。",
+ "teachers": "暂时无法加载教师推荐,课程发现仍可使用。",
+ "courses": "暂时无法加载课程推荐,教师发现仍可使用。",
+ "cancel": "取消预约失败,请稍后重试。"
+ }
+ }
}
diff --git a/src/main.js b/src/main.js
index f7fc5da..b0e4787 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,17 +1,15 @@
import { createApp } from 'vue'
import pinia from '@/stores/index'
-import '@/assets/style/tailwind.css' // 引入 Tailwind CSS
-import ElementPlus from 'element-plus'
-import 'element-plus/dist/index.css'
-import i18n from '@/i18n' // 引入 i18n
+import '@/assets/style/tailwind.css'
+import '@/assets/main.scss'
+import i18n from '@/i18n'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(pinia)
-app.use(ElementPlus)
app.use(router)
-app.use(i18n) // 使用 i18n
+app.use(i18n)
app.mount('#app')
diff --git a/src/stores/modules/localeStore.js b/src/stores/modules/localeStore.js
index 002cb1b..1a33b31 100644
--- a/src/stores/modules/localeStore.js
+++ b/src/stores/modules/localeStore.js
@@ -1,15 +1,32 @@
import { defineStore } from 'pinia'
-import i18n from '@/i18n'
+import i18n, { detectInitialLocale, normalizeLocale } from '@/i18n'
+
+function applyDocumentLanguage(locale) {
+ if (typeof document === 'undefined') return
+ document.documentElement.lang = locale === 'zh' ? 'zh-CN' : 'en'
+}
export const useLocaleStore = defineStore('locale', {
state: () => ({
- locale: 'zh' // 默认语言
+ locale: detectInitialLocale()
}),
actions: {
- setLocale(locale) {
- this.locale = locale
- localStorage.setItem('lang', locale)
- i18n.global.locale.value = locale // 同步到 vue-i18n
+ initialize() {
+ this.setLocale(this.locale, { persist: false })
+ },
+ setLocale(locale, { persist = true } = {}) {
+ const nextLocale = normalizeLocale(locale) || 'en'
+ this.locale = nextLocale
+ i18n.global.locale.value = nextLocale
+ applyDocumentLanguage(nextLocale)
+
+ if (persist && typeof window !== 'undefined') {
+ try {
+ window.localStorage.setItem('lang', nextLocale)
+ } catch {
+ // The active locale still works when persistent storage is blocked.
+ }
+ }
}
}
})
diff --git a/src/styles/base.scss b/src/styles/base.scss
new file mode 100644
index 0000000..0088935
--- /dev/null
+++ b/src/styles/base.scss
@@ -0,0 +1,77 @@
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ min-width: 320px;
+ min-height: 100%;
+ scroll-behavior: smooth;
+ text-size-adjust: 100%;
+}
+
+body {
+ min-width: 320px;
+ min-height: 100vh;
+ margin: 0;
+ color: var(--color-ink);
+ background: var(--color-paper);
+ font-family: var(--font-body);
+ line-height: 1.6;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+}
+
+button,
+input,
+select,
+textarea {
+ color: inherit;
+ font: inherit;
+}
+
+button,
+a {
+ -webkit-tap-highlight-color: transparent;
+}
+
+button:not(:disabled),
+[role='button']:not([aria-disabled='true']) {
+ cursor: pointer;
+}
+
+img,
+svg {
+ display: block;
+ max-width: 100%;
+}
+
+a {
+ color: inherit;
+}
+
+:focus-visible {
+ outline: 3px solid var(--color-focus);
+ outline-offset: 3px;
+}
+
+::selection {
+ color: var(--color-surface);
+ background: var(--color-cinnabar);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
diff --git a/src/styles/tokens.scss b/src/styles/tokens.scss
new file mode 100644
index 0000000..8fd76aa
--- /dev/null
+++ b/src/styles/tokens.scss
@@ -0,0 +1,75 @@
+:root {
+ color-scheme: light;
+
+ --color-paper: #f5f1e8;
+ --color-surface: #fffdf7;
+ --color-ink: #102a2f;
+ --color-ink-soft: #173a40;
+ --color-jade: #2f746b;
+ --color-cinnabar: #b64738;
+ --color-gold: #b88a3b;
+ --color-muted: #62757a;
+ --color-focus: #1769d2;
+ --color-line: rgba(16, 42, 47, 0.14);
+ --color-line-strong: rgba(16, 42, 47, 0.24);
+ --color-success-soft: #e4f0ea;
+ --color-danger-soft: #f8e9e5;
+
+ /* Public discovery components are also embedded in authenticated shells. */
+ --public-paper: var(--color-paper);
+ --public-surface: var(--color-surface);
+ --public-ink: var(--color-ink);
+ --public-ink-soft: var(--color-ink-soft);
+ --public-jade: var(--color-jade);
+ --public-vermilion: var(--color-cinnabar);
+ --public-gold: var(--color-gold);
+ --public-muted: var(--color-muted);
+ --public-line: #d8d3c7;
+ --public-focus: var(--color-focus);
+ --public-radius-sm: var(--radius-sm);
+ --public-radius-md: var(--radius-md);
+ --public-radius-lg: var(--radius-lg);
+
+ --font-display: 'Songti SC', 'STSong', 'Noto Serif CJK SC', Georgia, serif;
+ --font-body:
+ 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Segoe UI', sans-serif;
+ --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
+
+ --space-1: 0.25rem;
+ --space-2: 0.5rem;
+ --space-3: 0.75rem;
+ --space-4: 1rem;
+ --space-5: 1.25rem;
+ --space-6: 1.5rem;
+ --space-8: 2rem;
+ --space-10: 2.5rem;
+ --space-12: 3rem;
+
+ --radius-sm: 10px;
+ --radius-md: 16px;
+ --radius-lg: 24px;
+ --radius-pill: 999px;
+
+ --shadow-sm:
+ 0 1px 2px rgba(16, 42, 47, 0.04), 0 7px 20px rgba(16, 42, 47, 0.06);
+ --shadow-md: 0 16px 42px rgba(16, 42, 47, 0.11);
+ --shadow-lg: 0 28px 80px rgba(16, 42, 47, 0.18);
+
+ --content-max: 1280px;
+ --header-height: 72px;
+ --transition-fast: 160ms ease;
+ --transition-base: 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
+
+ --el-color-primary: var(--color-jade);
+ --el-color-primary-light-3: #659990;
+ --el-color-primary-light-5: #8fb7af;
+ --el-color-primary-light-7: #bdd3cd;
+ --el-color-primary-light-8: #d4e3df;
+ --el-color-primary-light-9: #eaf1ef;
+ --el-color-primary-dark-2: #255d56;
+ --el-border-radius-base: var(--radius-sm);
+ --el-border-color: var(--color-line);
+ --el-text-color-primary: var(--color-ink);
+ --el-text-color-regular: var(--color-muted);
+ --el-bg-color: var(--color-surface);
+}
diff --git a/src/views/administrator/administratorLayout/administratorLayout.vue b/src/views/administrator/administratorLayout/administratorLayout.vue
index e5200c5..c877523 100644
--- a/src/views/administrator/administratorLayout/administratorLayout.vue
+++ b/src/views/administrator/administratorLayout/administratorLayout.vue
@@ -1,37 +1,82 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/views/teacher/UserPage/UserPage.vue b/src/views/teacher/UserPage/UserPage.vue
index 23ac10a..1b16611 100644
--- a/src/views/teacher/UserPage/UserPage.vue
+++ b/src/views/teacher/UserPage/UserPage.vue
@@ -1,26 +1,44 @@
-
+
+ !
+
+
+ {{ reconnectExhausted ? '课堂连接暂时中断' : '正在恢复课堂连接' }}
+
+
{{ connectionError }}
+
+ 建议切换稳定网络、关闭可能拦截 WebSocket 的代理,再尝试重新连接。
+
+
+
+ 手动重连
+
+
+
@@ -633,6 +698,71 @@ onBeforeUnmount(() => {
animation: blink 0.8s ease-in-out infinite alternate;
}
+.connection-pill.closed i {
+ background: #e77d6d;
+}
+
+.connection-alert {
+ display: grid;
+ align-items: center;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ gap: 12px;
+ margin: 0 0 16px;
+ border: 1px solid rgba(234, 183, 104, 0.45);
+ padding: 12px 14px;
+ color: #f5dfbd;
+ background: rgba(113, 76, 28, 0.2);
+}
+
+.connection-alert--exhausted {
+ border-color: rgba(231, 125, 109, 0.55);
+ color: #ffd7d0;
+ background: rgba(121, 45, 37, 0.24);
+}
+
+.connection-alert__mark {
+ display: grid;
+ width: 30px;
+ height: 30px;
+ place-items: center;
+ border: 1px solid currentColor;
+ border-radius: 50%;
+ font-weight: 800;
+}
+
+.connection-alert strong {
+ display: block;
+ font-size: 0.9rem;
+}
+
+.connection-alert p {
+ margin: 3px 0 0;
+ color: #c7d6d5;
+ font-size: 0.78rem;
+ line-height: 1.5;
+}
+
+.connection-alert .connection-alert__advice {
+ color: #efc9c2;
+}
+
+.connection-alert button {
+ min-width: 96px;
+ min-height: 44px;
+ border: 1px solid currentColor;
+ padding: 0 15px;
+ color: inherit;
+ background: transparent;
+ font: inherit;
+ font-weight: 700;
+ cursor: pointer;
+}
+
+.connection-alert button:focus-visible {
+ outline: 3px solid #71d8d4;
+ outline-offset: 3px;
+}
+
.classroom-grid {
display: grid;
min-height: calc(100vh - 120px);
@@ -901,6 +1031,16 @@ onBeforeUnmount(() => {
}
@media (max-width: 620px) {
+ .connection-alert {
+ align-items: flex-start;
+ grid-template-columns: auto 1fr;
+ }
+
+ .connection-alert button {
+ grid-column: 1 / -1;
+ width: 100%;
+ }
+
.classroom-header > div:first-child {
align-items: flex-start;
flex-direction: column;
diff --git a/src/views/login/components/registerComponent.vue b/src/views/login/components/registerComponent.vue
index b116577..1ac07a8 100644
--- a/src/views/login/components/registerComponent.vue
+++ b/src/views/login/components/registerComponent.vue
@@ -1,432 +1,443 @@
+
-
-
-
-
+
+
{{ t('auth.register.eyebrow') }}
+
{{ t('auth.register.title') }}
+
{{ t('auth.register.description') }}
+
+
+
+ -
-
-
-
-
-
-
-
-
-