diff --git a/src/components/OOBE/OOBE.tsx b/src/components/OOBE/OOBE.tsx index eb9c36f..dcf34d7 100644 --- a/src/components/OOBE/OOBE.tsx +++ b/src/components/OOBE/OOBE.tsx @@ -13,7 +13,15 @@ interface oobeProps { onComplete: () => void } -type oobeStep = "language" | "theme" | "password" | "students" | "reasons" | "start" +type oobeStep = + | "entry" + | "postgresql" + | "language" + | "theme" + | "password" + | "students" + | "reasons" + | "start" interface studentItem { name: string @@ -75,8 +83,9 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { const { currentTheme, setTheme, themes, applyTheme } = useTheme() const [messageApi, contextHolder] = message.useMessage() - const [currentStep, setCurrentStep] = useState("language") + const [currentStep, setCurrentStep] = useState("entry") const [loading, setLoading] = useState(false) + const [pgAutoLoading, setPgAutoLoading] = useState(false) const [selectedLanguage, setSelectedLanguage] = useState("zh-CN") const [workingTheme, setWorkingTheme] = useState(null) @@ -88,10 +97,11 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { const [newReasonDelta, setNewReasonDelta] = useState(1) const [adminPassword, setAdminPassword] = useState("") const [pointsPassword, setPointsPassword] = useState("") + const [pgConnectionString, setPgConnectionString] = useState("") const fileInputRef = useRef(null) - const steps: oobeStep[] = ["language", "theme", "password", "students", "reasons", "start"] + const steps: oobeStep[] = ["entry", "language", "theme", "password", "students", "reasons", "start"] const stepIndex = steps.indexOf(currentStep) + 1 const totalSteps = steps.length @@ -325,6 +335,63 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { handleNext() } + const markWizardCompleted = async () => { + if (!(window as any).api) throw new Error("api not ready") + const res = await (window as any).api.setSetting("is_wizard_completed", true) + if (!res?.success) throw new Error("failed") + } + + const handleSkipToApp = async () => { + setLoading(true) + try { + await markWizardCompleted() + showOobeMessage("success", t("common.success")) + onComplete() + } catch (e: any) { + showOobeMessage("error", e?.message || t("common.error")) + } finally { + setLoading(false) + } + } + + const handleConnectPgAndSync = async () => { + const connectionString = pgConnectionString.trim() + if (!connectionString) { + showOobeMessage("warning", t("settings.database.enterConnectionString")) + return + } + setPgAutoLoading(true) + try { + if (!(window as any).api) throw new Error("api not ready") + const switchRes = await (window as any).api.dbSwitchConnection(connectionString) + if (!switchRes?.success || switchRes?.data?.type !== "postgresql") { + throw new Error(switchRes?.message || t("settings.database.switchFailed")) + } + + const previewRes = await (window as any).api.dbSyncPreview() + if (!previewRes?.success || !previewRes?.data?.can_sync) { + throw new Error(previewRes?.message || previewRes?.data?.message || t("settings.database.uploadFailed")) + } + + if (previewRes?.data?.need_sync) { + const syncApplyRes = await (window as any).api.dbSyncApply("keep_local") + if (!syncApplyRes?.success || !syncApplyRes?.data?.success) { + throw new Error( + syncApplyRes?.data?.message || syncApplyRes?.message || t("settings.database.uploadFailed") + ) + } + } + + await markWizardCompleted() + showOobeMessage("success", t("settings.database.uploadSuccess")) + onComplete() + } catch (e: any) { + showOobeMessage("error", e?.message || t("settings.database.uploadFailed")) + } finally { + setPgAutoLoading(false) + } + } + const handleFinish = async () => { setLoading(true) try { @@ -374,8 +441,7 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { ensureSuccess(authRes, t("common.error")) } - const res = await (window as any).api.setSetting("is_wizard_completed", true) - ensureSuccess(res, "failed") + await markWizardCompleted() showOobeMessage("success", t("common.success")) onComplete() @@ -392,6 +458,45 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { const renderStepContent = () => { switch (currentStep) { + case "entry": + return ( +
+ {t("oobe.steps.entry.description")} + + + +
+ ) + + case "postgresql": + return ( +
+ + {t("oobe.steps.postgresql.description")} + + setPgConnectionString(e.target.value)} + placeholder={t("oobe.steps.postgresql.connectionPlaceholder")} + /> + + {t("settings.database.connectionExample")} + +
+ + +
+
+ ) + case "language": return (
@@ -802,78 +907,80 @@ export const OOBE: React.FC = ({ visible, onComplete }) => {
{renderStepContent()}
-
-
- {currentStep !== "language" && } - {currentStep !== "start" && } -
- + {currentStep !== "entry" && currentStep !== "postgresql" && (
+
+ {currentStep !== "language" && } + {currentStep !== "start" && } +
+
-
- {t("oobe.step", { current: stepIndex, total: totalSteps })} -
- -
- {currentStep !== "start" ? ( - - ) : ( - - )} +
+ {t("oobe.step", { current: stepIndex, total: totalSteps })} +
+ +
+ {currentStep !== "start" ? ( + + ) : ( + + )} +
-
+ )} ) diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index ea95a8e..392e1eb 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -42,6 +42,19 @@ "step": "Step {{current}}/{{total}}", "skip": "Skip", "steps": { + "entry": { + "title": "Get Started", + "description": "Choose how you want to proceed", + "enterOobe": "Enter OOBE", + "connectPostgresAutoSync": "Connect PostgreSQL with Auto Sync", + "skipDirect": "Skip OOBE and Enter" + }, + "postgresql": { + "title": "Connect PostgreSQL", + "description": "Enter a PostgreSQL connection string. The app will connect and auto-sync before entering.", + "connectionPlaceholder": "postgresql://user:password@host:port/database?sslmode=require", + "autoSyncAndEnter": "Connect, Auto Sync, and Enter" + }, "language": { "title": "Select Language", "description": "Choose your preferred interface language" diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 05025e7..c596fa9 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -42,6 +42,19 @@ "step": "第 {{current}}/{{total}} 步", "skip": "跳过", "steps": { + "entry": { + "title": "开始配置", + "description": "请选择接下来要进行的操作", + "enterOobe": "进入OOBE", + "connectPostgresAutoSync": "连接PostgreSQL自动同步", + "skipDirect": "跳过OOBE,直接进入" + }, + "postgresql": { + "title": "连接 PostgreSQL", + "description": "输入 PostgreSQL 连接字符串,系统将自动连接并同步后进入应用", + "connectionPlaceholder": "postgresql://user:password@host:port/database?sslmode=require", + "autoSyncAndEnter": "连接并自动同步后进入" + }, "language": { "title": "选择语言", "description": "请选择您偏好的界面语言"