diff --git a/src-tauri/src/plugins/window/permissions/default.toml b/src-tauri/src/plugins/window/permissions/default.toml index 580efadd..70e09851 100644 --- a/src-tauri/src/plugins/window/permissions/default.toml +++ b/src-tauri/src/plugins/window/permissions/default.toml @@ -11,4 +11,6 @@ permissions = [ "allow-show-send-modal-window", "allow-hide-send-modal-window", "allow-close-send-modal-window", + "allow-show-onboarding-window", + "allow-hide-onboarding-window", ] diff --git a/src-tauri/src/plugins/window/src/commands/mod.rs b/src-tauri/src/plugins/window/src/commands/mod.rs index 85f2dd42..b8c554b7 100644 --- a/src-tauri/src/plugins/window/src/commands/mod.rs +++ b/src-tauri/src/plugins/window/src/commands/mod.rs @@ -1,6 +1,8 @@ use tauri::{async_runtime::spawn, AppHandle, Manager, Runtime, WebviewWindow}; +pub mod onboarding; pub mod send_modal; +pub use onboarding::*; pub use send_modal::*; // 主窗口的label diff --git a/src-tauri/src/plugins/window/src/commands/onboarding.rs b/src-tauri/src/plugins/window/src/commands/onboarding.rs new file mode 100644 index 00000000..07d8a916 --- /dev/null +++ b/src-tauri/src/plugins/window/src/commands/onboarding.rs @@ -0,0 +1,36 @@ +use tauri::{command, AppHandle, Manager, Runtime}; + +pub static ONBOARDING_WINDOW_LABEL: &str = "onboarding"; + +// 显示 Onboarding 窗口 +#[command] +pub async fn show_onboarding_window(app_handle: AppHandle) -> Result<(), String> { + println!("[onboarding] Attempting to show onboarding window"); + + let window = app_handle.get_webview_window(ONBOARDING_WINDOW_LABEL); + + if let Some(window) = window { + println!("[onboarding] Found onboarding window, showing"); + + // 显示窗口 + let _ = window.show(); + let _ = window.unminimize(); + let _ = window.set_focus(); + + println!("[onboarding] Window shown successfully"); + } else { + println!("[onboarding] ERROR: onboarding window not found! Make sure it's defined in tauri.conf.json"); + return Err("Onboarding window not found".to_string()); + } + + Ok(()) +} + +// 隐藏 Onboarding 窗口 +#[command] +pub async fn hide_onboarding_window(app_handle: AppHandle) -> Result<(), String> { + if let Some(window) = app_handle.get_webview_window(ONBOARDING_WINDOW_LABEL) { + let _ = window.hide(); + } + Ok(()) +} diff --git a/src-tauri/src/plugins/window/src/lib.rs b/src-tauri/src/plugins/window/src/lib.rs index 7ae82947..9ee456ee 100644 --- a/src-tauri/src/plugins/window/src/lib.rs +++ b/src-tauri/src/plugins/window/src/lib.rs @@ -19,6 +19,8 @@ pub fn init() -> TauriPlugin { commands::show_send_modal_window, commands::hide_send_modal_window, commands::close_send_modal_window, + commands::show_onboarding_window, + commands::hide_onboarding_window, ]) .build() } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index a8362df6..866629d0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -101,6 +101,24 @@ "width": 1920, "x": 0, "y": 0 + }, + { + "center": true, + "decorations": true, + "dragDropEnabled": false, + "height": 700, + "label": "onboarding", + "maxHeight": 900, + "maximizable": false, + "minHeight": 600, + "minWidth": 720, + "resizable": true, + "skipTaskbar": false, + "title": "欢迎使用 WeCut", + "transparent": false, + "url": "index.html/#/onboarding", + "visible": false, + "width": 720 } ] }, diff --git a/src/App.tsx b/src/App.tsx index e2376c4f..92aaad1e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import { openUrl } from "@tauri-apps/plugin-opener"; import { useBoolean, useEventListener, useKeyPress, useMount } from "ahooks"; import { ConfigProvider, theme } from "antd"; import { isString } from "es-toolkit"; +import { useEffect } from "react"; import { RouterProvider } from "react-router-dom"; import { useSnapshot } from "valtio"; import { LISTEN_KEY, PRESET_SHORTCUT } from "./constants"; @@ -17,6 +18,7 @@ import { getAntdLocale, i18n } from "./locales"; import { applyMainWindowLayout, hideWindow, + showOnboardingWindow, showWindow, } from "./plugins/window"; import { router } from "./router"; @@ -48,12 +50,14 @@ const App = () => { // 生成 antd 的颜色变量 generateColorVars(); + }); - // 若未完成引导,跳转到引导页 - if (!globalStore.app.hasCompletedOnboarding) { - router.navigate("/onboarding"); + // 若未完成引导,显示引导窗口(在 RouterProvider 渲染后执行) + useEffect(() => { + if (ready && !globalStore.app.hasCompletedOnboarding) { + showOnboardingWindow(); } - }); + }, [ready]); // 监听语言的变化 useImmediateKey(globalStore.appearance, "language", i18n.changeLanguage); diff --git a/src/constants/index.ts b/src/constants/index.ts index 85d2c6f4..74329459 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -10,6 +10,7 @@ export const UPDATE_MESSAGE_KEY = "app-update-message"; export const WINDOW_LABEL = { MAIN: "main", + ONBOARDING: "onboarding", PREFERENCE: "preference", SCREENSHOT: "screenshot", SEND_MODAL: "send-modal", diff --git a/src/pages/Onboarding/index.tsx b/src/pages/Onboarding/index.tsx index 1e86f8ea..f9498ade 100644 --- a/src/pages/Onboarding/index.tsx +++ b/src/pages/Onboarding/index.tsx @@ -1,10 +1,9 @@ import { emit } from "@tauri-apps/api/event"; -import { openUrl } from "@tauri-apps/plugin-opener"; +import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { useInterval } from "ahooks"; import { Button, Card, Flex, Tooltip, Typography } from "antd"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; import { checkAccessibilityPermission, checkFullDiskAccessPermission, @@ -25,7 +24,6 @@ const GITHUB_URL = "https://github.com/wecode-ai/WeCut"; const Onboarding = () => { const { t } = useTranslation(); - const navigate = useNavigate(); const [accessibilityGranted, setAccessibilityGranted] = useState(false); const [screenRecordingGranted, setScreenRecordingGranted] = useState(false); @@ -70,25 +68,31 @@ const Onboarding = () => { const handleComplete = async () => { globalStore.app.hasCompletedOnboarding = true; await saveStore(); - navigate("/"); + // 关闭引导窗口,显示主窗口 + const window = getCurrentWebviewWindow(); + await window.close(); + showWindow(WINDOW_LABEL.MAIN); }; const handleOpenPreference = async () => { globalStore.app.hasCompletedOnboarding = true; await saveStore(); - navigate("/"); + // 关闭引导窗口 + const window = getCurrentWebviewWindow(); + await window.close(); // 通知偏好设置窗口切换到 Wegent 集成 tab await showWindow(WINDOW_LABEL.PREFERENCE as any); await emit(LISTEN_KEY.PREFERENCE_NAVIGATE, "wegent"); }; - const renderPermissionStatus = ( - granted: boolean, - onRequest: () => void, - ) => { + const renderPermissionStatus = (granted: boolean, onRequest: () => void) => { if (granted) { return ( - + {t("onboarding.permission.authorized")} @@ -97,11 +101,11 @@ const Onboarding = () => { return ( @@ -110,132 +114,142 @@ const Onboarding = () => { const permissions = [ { - key: "accessibility", - title: t("onboarding.permission.accessibility.title"), description: t("onboarding.permission.accessibility.description"), granted: accessibilityGranted, + key: "accessibility", onRequest: handleRequestAccessibility, + title: t("onboarding.permission.accessibility.title"), }, { - key: "screenRecording", - title: t("onboarding.permission.screen_recording.title"), description: t("onboarding.permission.screen_recording.description"), granted: screenRecordingGranted, + key: "screenRecording", onRequest: handleRequestScreenRecording, + title: t("onboarding.permission.screen_recording.title"), }, { - key: "fullDiskAccess", - title: t("onboarding.permission.full_disk_access.title"), description: t("onboarding.permission.full_disk_access.description"), granted: fullDiskAccessGranted, + key: "fullDiskAccess", onRequest: handleRequestFullDiskAccess, + title: t("onboarding.permission.full_disk_access.title"), }, ]; return ( - - - {/* 区块 1:开源与隐私声明 */} - - WeCut -
- - WeCut - - - {t("onboarding.privacy.description")} - -
- - - -
- - {/* 区块 2:macOS 权限引导 */} -
- - {t("onboarding.permissions.title")} - - - {t("onboarding.permissions.description")} - - - {permissions.map(({ key, title, description, granted, onRequest }) => ( - - - - -
- {title} -
- {description} -
-
- {renderPermissionStatus(granted, onRequest)} -
-
- ))} -
-
- - {/* 区块 3:Wegent Key 配置(可选) */} - - - + {/* 可滚动内容区 */} +
+
+ {/* 区块 1:开源与隐私声明 */} + + WeCut -
- {t("onboarding.wegent.title")} - - {t("onboarding.wegent.description")} +
+ + WeCut + + + {t("onboarding.privacy.description")} +
+ -
+
- - - {/* 底部固定操作区 */} -
- + {/* 区块 2:macOS 权限引导 */} +
+ + {t("onboarding.permissions.title")} + + + {t("onboarding.permissions.description")} + + + {permissions.map( + ({ key, title, description, granted, onRequest }) => ( + + + + +
+ + {title} + +
+ + {description} + +
+
+ {renderPermissionStatus(granted, onRequest)} +
+
+ ), + )} +
+
+ + {/* 区块 3:Wegent Key 配置(可选) */} + + + +
+ + {t("onboarding.wegent.title")} + + + {t("onboarding.wegent.description")} + + +
+
+
+
+
+ + {/* 底部操作区 - 固定在底部 */} +
+ {!allGranted && ( {t("onboarding.complete.hint")} @@ -247,7 +261,6 @@ const Onboarding = () => {
- +
); }; diff --git a/src/plugins/window.ts b/src/plugins/window.ts index d9a78d23..ad820938 100644 --- a/src/plugins/window.ts +++ b/src/plugins/window.ts @@ -11,8 +11,10 @@ import { getCursorMonitor } from "@/utils/monitor"; import { getPreferredWindowState } from "@/utils/windowState"; const COMMAND = { + HIDE_ONBOARDING_WINDOW: "plugin:eco-window|hide_onboarding_window", HIDE_TOAST_WINDOW: "plugin:eco-window|hide_toast_window", HIDE_WINDOW: "plugin:eco-window|hide_window", + SHOW_ONBOARDING_WINDOW: "plugin:eco-window|show_onboarding_window", SHOW_TASKBAR_ICON: "plugin:eco-window|show_taskbar_icon", SHOW_TOAST_WINDOW: "plugin:eco-window|show_toast_window", SHOW_WINDOW: "plugin:eco-window|show_window", @@ -118,6 +120,20 @@ export const showToastWindow = async () => { await invoke(COMMAND.SHOW_TOAST_WINDOW); }; +/** + * 显示 Onboarding 窗口(引导页) + */ +export const showOnboardingWindow = async () => { + await invoke(COMMAND.SHOW_ONBOARDING_WINDOW); +}; + +/** + * 隐藏 Onboarding 窗口(引导页) + */ +export const hideOnboardingWindow = async () => { + await invoke(COMMAND.HIDE_ONBOARDING_WINDOW); +}; + /** * 隐藏 Toast 窗口 */ diff --git a/src/utils/store.ts b/src/utils/store.ts index 57fa8e4f..fbe19387 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -87,6 +87,11 @@ const initStore = async () => { clipboardStore.activeTagId = clipboardStore.ui.activeTagId; } + // 数据迁移:老用户默认已完成引导(从文件恢复后仍不存在 hasCompletedOnboarding 表示是老用户) + if (globalStore.app.hasCompletedOnboarding === undefined) { + globalStore.app.hasCompletedOnboarding = true; + } + await mkdir(globalStore.env.saveDataDir, { recursive: true }); };