From 7d64f333288376b153e86a28aa51287b4149f58c Mon Sep 17 00:00:00 2001 From: JSR Date: Thu, 19 Mar 2026 17:36:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=BF=9E=E6=8E=A5=E5=90=8E?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=9C=AC=E5=9C=B0=E6=95=B0=E6=8D=AE=E5=88=B0?= =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E7=9A=84=E6=89=8B=E5=8A=A8=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Settings.tsx | 66 +++++++++++++++++++++++++++++++++++++ src/i18n/locales/en-US.json | 10 +++++- src/i18n/locales/zh-CN.json | 10 +++++- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 73a18d4..281cd3b 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -79,6 +79,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }>({ connected: true, type: "sqlite" }) const [pgTestLoading, setPgTestLoading] = useState(false) const [pgSwitchLoading, setPgSwitchLoading] = useState(false) + const [pgUploadLoading, setPgUploadLoading] = useState(false) const permissionTag = useMemo(() => { return ( @@ -360,6 +361,61 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission } } + const uploadLocalToRemote = async () => { + if (!(window as any).api) return + try { + const statusRes = await (window as any).api.dbGetStatus() + if (!statusRes.success || !statusRes.data?.connected || statusRes.data?.type !== "postgresql") { + messageApi.warning(t("settings.database.uploadNeedRemote")) + return + } + + const previewRes = await (window as any).api.dbSyncPreview() + if (!previewRes.success || !previewRes.data?.can_sync) { + messageApi.error(previewRes.message || previewRes.data?.message || t("settings.database.uploadFailed")) + return + } + if (!previewRes.data.need_sync) { + messageApi.info(t("settings.database.noNeedUpload")) + return + } + + Modal.confirm({ + title: t("settings.database.uploadConfirmTitle"), + content: t("settings.database.uploadConfirmContent", { + localOnly: previewRes.data.local_only || 0, + remoteOnly: previewRes.data.remote_only || 0, + conflicts: previewRes.data.conflicts?.length || 0, + }), + okText: t("settings.database.uploadButton"), + cancelText: t("common.cancel"), + onOk: async () => { + setPgUploadLoading(true) + try { + const applyRes = await (window as any).api.dbSyncApply("keep_local") + if (applyRes.success && applyRes.data?.success) { + messageApi.success( + applyRes.data?.message || t("settings.database.uploadSuccess") + ) + emitDataUpdated("all") + await loadAll() + } else { + messageApi.error( + applyRes.data?.message || applyRes.message || t("settings.database.uploadFailed") + ) + } + } catch (e: any) { + messageApi.error(e?.message || t("settings.database.uploadFailed")) + } finally { + setPgUploadLoading(false) + } + }, + }) + } catch (e: any) { + messageApi.error(e?.message || t("settings.database.uploadFailed")) + } + } + const currentYear = new Date().getFullYear() const tabItems = [ @@ -678,7 +734,17 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission > {t("settings.database.switchToSQLite")} + +
+ {t("settings.database.uploadHint")} +
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 34ed95c..e139f55 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -205,7 +205,15 @@ "syncPoint3": "Application restart required after switching database.", "syncPoint4": "Recommended cloud database services: Neon, Supabase, AWS RDS, etc.", "enterConnectionString": "Please enter PostgreSQL connection string", - "connectionExample": "Example: postgresql://xxxxxx_xxxxx:xxxxxxxx@xx-xxx.xxx.neon.xxxx/xxxxdxx?sslmode=require" + "connectionExample": "Example: postgresql://xxxxxx_xxxxx:xxxxxxxx@xx-xxx.xxx.neon.xxxx/xxxxdxx?sslmode=require", + "uploadButton": "Upload Local Data to Remote", + "uploadHint": "After connecting remote database, upload and merge local SQLite history into remote.", + "uploadNeedRemote": "Please connect to PostgreSQL remote database first", + "noNeedUpload": "Local and remote data are already consistent", + "uploadConfirmTitle": "Upload local data to remote?", + "uploadConfirmContent": "Sync will prefer local data. Detected local-only {{localOnly}}, remote-only {{remoteOnly}}, conflicts {{conflicts}}.", + "uploadSuccess": "Local data uploaded to remote", + "uploadFailed": "Upload failed" }, "data": { "title": "Data Management", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 6e3e139..f44f086 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -205,7 +205,15 @@ "syncPoint3": "切换数据库后需要重启应用以生效。", "syncPoint4": "建议使用云数据库服务(如 Neon、Supabase、AWS RDS 等)。", "enterConnectionString": "请输入 PostgreSQL 连接字符串", - "connectionExample": "示例:postgresql://xxxxxx_xxxxx:xxxxxxxx@xx-xxx.xxx.neon.xxxx/xxxxdxx?sslmode=require" + "connectionExample": "示例:postgresql://xxxxxx_xxxxx:xxxxxxxx@xx-xxx.xxx.neon.xxxx/xxxxdxx?sslmode=require", + "uploadButton": "上传本地数据到远程", + "uploadHint": "连接远程数据库后,可将本地 SQLite 历史数据上传并合并到远程。", + "uploadNeedRemote": "请先切换并连接到 PostgreSQL 远程数据库", + "noNeedUpload": "本地与远程数据已一致,无需上传", + "uploadConfirmTitle": "确认上传本地数据到远程?", + "uploadConfirmContent": "将以本地数据为优先进行同步。检测到本地新增 {{localOnly}} 条、远程新增 {{remoteOnly}} 条、冲突 {{conflicts}} 条。", + "uploadSuccess": "本地数据已上传到远程", + "uploadFailed": "上传失败" }, "data": { "title": "数据管理",