diff --git a/src/content/docs/learn/system-tray.mdx b/src/content/docs/learn/system-tray.mdx index 2ffe2071ad..eb9614946e 100644 --- a/src/content/docs/learn/system-tray.mdx +++ b/src/content/docs/learn/system-tray.mdx @@ -51,10 +51,10 @@ See [`TrayIconOptions`] for more information on the customization options. use tauri::tray::TrayIconBuilder; tauri::Builder::default() -.setup(|app| { -let tray = TrayIconBuilder::new().build(app)?; -Ok(()) -}) + .setup(|app| { + let tray = TrayIconBuilder::new().build(app)?; + Ok(()) + }) ``` diff --git a/src/content/docs/zh-cn/learn/Security/capabilities-for-windows-and-platforms.mdx b/src/content/docs/zh-cn/learn/Security/capabilities-for-windows-and-platforms.mdx new file mode 100644 index 0000000000..2a50390c84 --- /dev/null +++ b/src/content/docs/zh-cn/learn/Security/capabilities-for-windows-and-platforms.mdx @@ -0,0 +1,184 @@ +--- +title: 适用于不同 Windows 和平台的功能 +sidebar: + order: 11 +i18nReady: true +--- + +import { Steps } from '@astrojs/starlight/components'; +import ShowSolution from '@components/ShowSolution.astro' +import Cta from '@fragments/cta.mdx'; + +本指南将帮助你自定义 Tauri 应用程序的功能。 + +## 本指南的内容 + +- Tauri 应用中创建多个窗口 +- 对不同的窗口使用不同的功能 +- 使用平台特定的功能 + +## 先决条件 + +完成 [`使用插件权限`](/learn/security/using-plugin-permissions/) 后再阅读此练习。 + +## 指导 + + +1. ### 在 Tauri 应用程序中创建多个窗口 + + 这里我们创建一个有两个窗口的应用程序,并分别标记为 `first` 和 `second` 。 + 在 Tauri 应用程序中,有很多种方法可以创建窗口。 + + #### 使用 Tauri 配置文件创建窗口 + + 在 Tauri 配置文件中,通常名为 `tauri.conf.json` : + + + ```javascript + "productName": "multiwindow", + ... + "app": { + "windows": [ + { + "label": "first", + "title": "First", + "width": 800, + "height": 600 + }, + { + "label": "second", + "title": "Second", + "width": 800, + "height": 600 + } + ], + }, + ... + } + ``` + + + #### 以编程的方式创建窗口 + + 在 Rust 代码中创建 Tauri 应用程序: + + + ```rust + tauri::Builder::default() + .invoke_handler(tauri::generate_handler![greet]) + .setup(|app| { + let webview_url = tauri::WebviewUrl::App("index.html".into()); + // 第一个窗口 + tauri::WebviewWindowBuilder::new(app, "first", webview_url.clone()) + .title("First") + .build()?; + // 第二个窗口 + tauri::WebviewWindowBuilder::new(app, "second", webview_url) + .title("Second") + .build()?; + Ok(()) + }) + .run(context) + .expect("error while running tauri application"); + ``` + + +2. ### 将不同的功能应用于不同的窗口 + + Tauri 应用的窗口可以使用 Tauri 后端的不同功能或插件。为了提高安全性,建议只为每个窗口提供必要的功能。 + 我们模拟了一个场景,其中 `first` 窗口使用文件系统和对话框功能,`second` 只使用对话框功能。 + + #### 每个类别单独的功能文件 + + 建议根据功能文件所支持的操作类别来分离功能文件。 + + + `src-tauri/capabilities` 中的 JSON 文件将被纳入功能系统。 + 在这里,我们将与文件系统和对话框窗口相关的功能分别存储到 `filesystem.json` 和 `dialog.json` 中。 + + *Tauri 项目的文件树:* + ``` + /src + /src-tauri + /capabilities + filesystem.json + dialog.json + tauri.conf.json + package.json + README.md + ``` + + + #### 为 `first` 窗口提供文件系统功能 + + 我们赋予 `first` 窗口读取 `$HOME` 目录内容的权限。 + + + 在功能文件中使用具有一个或多个窗口标签的 `windows` 字段。 + + ```json title="filesystem.json" + { + "identifier": "fs-read-home", + "description": "Allow access file access to home directory", + "local": true, + "windows": ["first"], + "permissions": [ + "fs:allow-home-read", + ] + } + ``` + + + #### 为 `first` 和 `second` 窗口提供对话框功能 + + 我们为 `first` 和 `second` 窗口提供创建“是/否”的对话框的功能。 + + + 在功能文件中使用具有一个或多个窗口标签的 `windows` 字段。 + + ```json title="dialog.json" + { + "identifier": "dialog", + "description": "Allow to open a dialog", + "local": true, + "windows": ["first", "second"], + "permissions": ["dialog:allow-ask"] + } + ``` + + + + +3. ### 使功能依赖于平台 + + 我们现在想自定义功能,使其仅在某些平台上有效。我们使我们的文件系统功能仅在 `linux` 和 `windows` 上有效。 + + + 在功能文件中使用 `platforms` 字段使其特定于平台。 + + ```json title="filesystem.json" + { + "identifier": "fs-read-home", + "description": "Allow access file access to home directory", + "local": true, + "windows": ["first"], + "permissions": [ + "fs:allow-home-read", + ], + "platforms": ["linux", "windows"] + } + ``` + + 目前可用的平台有 `linux` 、 `windows` 、 `macos` 、 `android` 和 `ios` 。 + + + + +## 结论和资源 + +我们学习了如何在 Tauri 应用中创建多个窗口并赋予它们特定功能。此外,这些功能还可以针对特定平台进行定制。 + +在 [Tauri Github 仓库](https://github.com/tauri-apps/tauri) 的 +[`api` 示例](https://github.com/tauri-apps/tauri/tree/dev/examples/api) +中可以找到一个使用窗口功能的示例程序。 +功能文件中可以使用的字段在 [功能](/reference/acl/capability/) 参考中列出。 diff --git a/src/content/docs/zh-cn/learn/Security/using-plugin-permissions.mdx b/src/content/docs/zh-cn/learn/Security/using-plugin-permissions.mdx new file mode 100644 index 0000000000..697ba7200c --- /dev/null +++ b/src/content/docs/zh-cn/learn/Security/using-plugin-permissions.mdx @@ -0,0 +1,267 @@ +--- +title: 使用插件权限 +sidebar: + order: 10 +i18nReady: true +--- + +import { Steps } from '@astrojs/starlight/components'; +import ShowSolution from '@components/ShowSolution.astro' +import Cta from '@fragments/cta.mdx'; + +本实验的目标是更好地了解如何启用或禁用插件权限、在哪里描述它们以及如何使用插件的默认权限。 + +最后,你将能够查找和使用任意插件的权限,并了解如何自定义现有权限。 +你将获得一个 Tauri 示例应用程序,其中使用了一个插件和插件特定的权限。 + + + +1. ### 创建 Tauri 应用程序 + + 创建你的 Tauri 应用程序。 + 在我们的示例中,我们将使用 [`create-tauri-app`](https://github.com/tauri-apps/create-tauri-app) : + + + + 我们将使用 `pnpm` 进行逐步解释,但你可以选择其他包管理器并在命令中相应地替换它。 + + + + ``` + pnpm create tauri-app + ``` + + ``` + ✔ Project name · plugin-permission-demo + ✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun) + ✔ Choose your package manager · pnpm + ✔ Choose your UI template · Vanilla + ✔ Choose your UI flavor · TypeScript + + Template created! To get started run: + cd plugin-permission-demo + pnpm install + pnpm tauri dev + ``` + + +2. ### 将 `file-system` 插件添加到你的应用程序中 + + 要搜索现有的插件,你可以使用多个资源。 + + 最直接的方法是检查你的插件是否已在文档的 [插件](/plugin/) 部分中, + 此处是 Tauri 维护的插件集的一部分。 + 文件系统插件是 Tauri 插件工作区的一部分,你可以按照 [设置](/plugin/file-system/#permission-table) 将其添加到你的项目中。 + + 如果该插件是社区努力的一部分,那么你最有可能在 [crates.io](https://crates.io/search?q=tauri-plugin-) + 上搜索 `tauri-plugin-` 时找到它。 + + + 如果它是我们工作区中现有的插件,你可以使用自动化方式: + + ``` + pnpm tauri add fs + ``` + + 如果你在 [crates.io](https://crates.io/crates/tauri-plugin-fs) 上找到它 + 你需要手动将其添加为依赖项并修改 Tauri 构建器初始化插件: + + ```sh + cargo add tauri-plugin-fs + ``` + + 修改 `lib.rs` 来初始化插件: + + ```rust title="src-tauri/src/lib.rs" ins={4} + #[cfg_attr(mobile, tauri::mobile_entry_point)] + fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_fs::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + } + ``` + + +3. ### 了解 `fs` 插件的默认权限 + + 每个插件都有一组 `default` 权限集,其中包含使用插件所需的所有权限和范围, + 以及使用插件的合理的最小功能集。 + + 对于官方维护的插件,你可以在文档中找到相关描述 + (例如 [fs default](/plugin/file-system/#default-permission) )。 + + In case you are figuring this out for a community plugin you + need to check out the source code of the plugin. + This should be defined in `your-plugin/permissions/default.toml`. + 如果你要查看社区插件的默认权限集,则需要查看该插件的源代码。 + 这应该在 `your-plugin/permissions/default.toml` 中定义。 + + + ```toml + "$schema" = "schemas/schema.json" + + [default] + description = """ + # Tauri `fs` 插件的默认权限集 + + 这个配置文件定义了授予文件系统的默认权限。 + + ### 已授予的权限 + + 这个默认的权限集启用了所有与读取相关的命令, + 并允许访问 `$APP` 文件夹及其在运行时创建的子目录。 + `$APP` 文件夹的具体位置取决于操作系统以及应用程序的运行方式。 + + 一般来说,`$APP` 文件夹需要由应用程序在运行时手动创建,之后才能访问其中的文件或子目录。 + + ### 被拒绝的权限 + + 该默认权限集默认阻止对 Tauri 应用程序中关键组件的访问。 + 在 Windows 上,默认会禁止访问 WebView 数据文件夹 。 + + """ + permissions = ["read-all", "scope-app-recursive", "deny-default"] + + ``` + + +4. ### 找到正确的权限 + + 此步骤是关于找到你需要的权限,以便以最少的系统访问权限将实现你的命令并公开到前端。 + + `fs` 插件具有自动生成的权限,可以禁用或启用单个命令并允许或禁用全局范围。 + + 这些可以在 [文档](/plugin/file-system/#permission-table) 中找到, + 也可以在插件源代码中找到(`fs/permissions/autogenerated`)。 + + 假设我们想要启用写入位于用户的 `$HOME` 文件夹中的文本文件 `test.txt`。 + + 为此,我们将在自动生成的权限中搜索允许写入文本文件的权限, + 例如 `allow-write-text-file` ,然后是允许我们访问 `$HOME/test.txt` 范围内的文件。 + + 我们需要将这些添加到我们的 `capabilities` 部分,在 `src-tauri/tauri.conf.json` + 文件中或 `src-tauri/capabilities/` 文件夹中的某个文件。 + 默认情况下, `src-tauri/capabilities/default.json` 中已包含我们可以修改的权限。 + + + + ```json title="src-tauri/capabilities/default.json" del={18} ins={19} + { + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "image:default", + "resources:default", + "menu:default", + "tray:default", + "shell:allow-open", + "fs:default", + "fs:allow-write-text-file", + ] + } + ``` + + + + 由于 `fs` 插件中只有自动生成的范围可以访问完整的 `$HOME` 文件夹, + 因此我们需要配置自己的范围。此范围应仅对 `write-text-file` 命令启用, + 并且应仅包含我们的 `test.txt` 文件。 + + + ```json title="src-tauri/capabilities/default.json" del={18} ins={19-22} + { + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "image:default", + "resources:default", + "menu:default", + "tray:default", + "shell:allow-open", + "fs:allow-write-text-file", + { + "identifier": "fs:allow-write-text-file", + "allow": [{ "path": "$HOME/test.txt" }] + }, + ] + } + ``` + + +5. ### 在实践中测试权限 + + 添加必要的权限后,我们要确认我们的应用程序可以访问该文件并写入其内容。 + + + 我们可以在我们的应用程序中使用这个代码片段来写入文件: + + ```ts title="src/main.ts" + import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + + let greetInputEl: HTMLInputElement | null; + + async function write(message: string) { + await writeTextFile('test.txt', message, { baseDir: BaseDirectory.Home }); + } + + window.addEventListener("DOMContentLoaded", () => { + greetInputEl = document.querySelector("#greet-input"); + document.querySelector("#greet-form")?.addEventListener("submit", (e) => { + e.preventDefault(); + if (!greetInputEl ) + return; + + write(greetInputEl.value == "" ? "No input provided": greetInputEl.value); + + }); + }); + + ``` + + 用这段代码替换 `src/main.ts` 意味着在使用一般的 Vanilla+Typescript + 应用时,我们无需修改默认的 `index.html` 。 + 在运行中的应用中的输入框输入任何内容,提交后都会写入文件中。 + + 让我们来实际测试一下: + + ``` + pnpm run tauri dev + ``` + + 写入输入框并单击“提交”后,我们可以通过终端或手动打开主文件夹中的文件进行检查。 + + ``` + cat $HOME/test.txt + ``` + + 你应该会看到你的输入,然后就完成了有关在 Tauri 应用程序中使用插件权限的学习。🥳 + + + 如果你遇到此错误: + + ```sh + [Error] Unhandled Promise Rejection: fs.write_text_file not allowed. Permissions associated with this command: fs:allow-app-write, fs:allow-app-write-recursive, fs:allow-appcache-write, fs:allow-appcache-write-recursive, fs:allow-appconf... + (anonymous function) (main.ts:5) + ``` + 那么你很可能没有正确遵循 [先前的指示](#找到正确的权限). + + + + diff --git a/src/content/docs/zh-cn/learn/Security/writing-plugin-permissions.mdx b/src/content/docs/zh-cn/learn/Security/writing-plugin-permissions.mdx new file mode 100644 index 0000000000..6cdc075355 --- /dev/null +++ b/src/content/docs/zh-cn/learn/Security/writing-plugin-permissions.mdx @@ -0,0 +1,256 @@ +--- +title: 编写插件权限 +sidebar: + order: 11 +i18nReady: true +--- + +import { Steps } from '@astrojs/starlight/components'; +import ShowSolution from '@components/ShowSolution.astro' +import Cta from '@fragments/cta.mdx'; + +本练习的目标是更好地理解在编写自己的插件时如何创建插件权限。 + +在最后,你将能够为插件创建简单的权限。你将获得一个 +Tauri 插件示例,其中权限部分是自动生成的,部分是手动设置的。 + + + +1. ### 创建 Tauri 插件 + + 在我们的示例中,我们将使用 Tauri [`cli`](/reference/cli/) 引导生成一个 Tauri 插件源代码结构。 + 请确保已安装所有必备 [组件](/start/prerequisites/) ,并通过运行 + `cargo tauri info` 验证你的 Tauri CLI 版本是否正确。 + + 输出应表明 `tauri-cli` 版本为 `2.x` 。 + 我们将使用 `pnpm` 进行分步说明,但你可以选择其他包管理器,并在命令中相应地替换它。 + + 安装最新版本后,你可以继续使用 Tauri CLI 创建插件。 + + + ```sh + mkdir -p tauri-learning + cd tauri-learning + cargo tauri plugin new test + cd tauri-plugin-test + pnpm install + pnpm build + cargo build + ``` + + +2. ### 创建一个新命令 + + 为了展示一些实用而简单的东西,让我们假设我们的命令将用户输入的内容 + 写入临时文件夹内的文件中,同时向文件添加一些自定义标题。 + + 我们将命令命名为 `write_custom_file` ,并在 `src/commands.rs` + 中实现它 并将其添加到我们的插件构建器中以暴露给前端。 + + Tauri 的核心程序将自动生成此命令的 `allow` 和 `deny` 权限,因此我们不需要关心这一点。 + + + + 命令实现: + + ```rust title="src/commands.rs" ins={15-22} ins=", Manager" + use tauri::{AppHandle, command, Runtime}; + + use crate::models::*; + use crate::Result; + use crate::TestExt; + + #[command] + pub(crate) async fn ping( + app: AppHandle, + payload: PingRequest, + ) -> Result { + app.test1().ping(payload) + } + + #[command] + pub(crate) async fn write_custom_file( + user_input: String, + app: AppHandle, + ) -> Result { + std::fs::write(app.path().temp_dir().unwrap(), user_input)?; + Ok("success".to_string()) + } + + ``` + + 为你的新命令自动生成内置权限: + + ```rust title="src/build.rs" ins="\"write_custom_file\"" + const COMMANDS: &[&str] = &["ping", "write_custom_file"]; + ``` + + 这些内置权限将由 Tauri 构建系统自动生成,并显示在 `permissions/autogenerated/commands` + 文件夹中。默认情况下,将创建 `enable-` 和 `deny-` 权限。 + + + +3. ### 公开新命令 + + 上一步是编写实际的命令实现。接下来,我们希望将其暴露给前端,以便其可以被使用。 + + + + 配置 Tauri 构建器以生成调用处理程序,将前端 IPC 请求传递给新实现的命令: + + ```rust title="src/lib.rs" ins="commands::write_custom_file," + pub fn init() -> TauriPlugin { + Builder::new("test") + .invoke_handler(tauri::generate_handler![ + commands::ping, + commands::write_custom_file, + ]) + .setup(|app, api| { + #[cfg(mobile)] + let test = mobile::init(app, api)?; + #[cfg(desktop)] + let test = desktop::init(app, api)?; + app.manage(test); + + // 管理状态,以便命令访问 + app.manage(MyState::default()); + Ok(()) + }) + .build() + } + ``` + + 在前端模块中公开新命令。 + + 这一步对于示例应用程序成功导入前端模块至关重要。这是为了方便起见, + 不会对安全性产生影响,因为命令处理程序已经生成,该命令也可以从前端手动调用。 + + ```ts title="guest-js/index.ts" ins={11-13} + import { invoke } from '@tauri-apps/api/core' + + export async function ping(value: string): Promise { + return await invoke<{value?: string}>('plugin:test|ping', { + payload: { + value, + }, + }).then((r) => (r.value ? r.value : null)); + } + + export async function writeCustomFile(user_input: string): Promise { + return await invoke('plugin:test|write_custom_file',{userInput: user_input}); + } + ``` + + :::tip + 调用参数需要采用驼峰命名法。本例中,它是 `userInput` 而不是 `user_input` 。 + ::: + + 确保你的包已经构建: + + ``` + pnpm build + ``` + + + +4. ### 定义默认插件权限 + + 由于我们的插件应该公开 `write_custom_file` 命令, + 因此我们应该将其添加到我们的 `default.toml` 权限中。 + + + + 将其添加到我们的默认权限集以允许我们刚刚公开的新命令正常运行。 + + ```toml title="permissions/default.toml" ins=", \"allow-write-custom-file\"" + "$schema" = "schemas/schema.json" + [default] + description = "Default permissions for the plugin" + permissions = ["allow-ping", "allow-write-custom-file"] + ``` + + +5. ### 从示例应用程序调用测试命令 + + 创建的插件目录结构包含一个 `examples/tauri-app` 文件夹, + 其中有一个可供使用的 Tauri 应用程序来测试该插件。 + + 由于我们添加了新命令,因此我们需要稍微修改前端来调用我们的新命令。 + + + ```svelte title="src/App.svelte" del={11-13,42-45} ins={14-16,45-49} + + + + Welcome to Tauri! + + + + + + + + + + + + + + + Click on the Tauri, Vite, and Svelte logos to learn more. + + + + + + + + Ping + {@html response} + + + Write + {@html response} + + + + + + + ``` + + 运行此程序并按下“Write”按钮,你将看到以下内容: + + ``` + success + ``` + + 你应该在临时文件夹中找到一个 `test.txt` 文件,其中包含来自我们新实现的插件命令的消息。🥳 + + + + diff --git a/src/content/docs/zh-cn/learn/index.mdx b/src/content/docs/zh-cn/learn/index.mdx new file mode 100644 index 0000000000..9e18997bb2 --- /dev/null +++ b/src/content/docs/zh-cn/learn/index.mdx @@ -0,0 +1,79 @@ +--- +title: 学习 +sidebar: + order: 0 + label: 概述 +i18nReady: true +--- + +import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components'; +import AwesomeTauri from '@components/AwesomeTauri.astro'; +import BookItem from '@components/BookItem.astro'; +import RoseRustBook from '@assets/learn/community/HTML_CSS_JavaScript_and_Rust_for_Beginners_A_Guide_to_Application_Development_with_Tauri.png'; + +学习栏目旨在为提供与 Tauri 主题相关的端到端的学习体验。 + +这些教程将引导你完成特定主题,并帮助你将指南和参考文档中的知识应用于实际场景。 + +对于安全相关主题,你可以学习权限系统。你将获得关于如何使用它、扩展它以及编写自定义权限的实际见解。 + + + + + + + +要学习如何编写自己的启动画面或使用 Node.js sidecar ,请查看: + + + + + + +## 更多学习资源 + +本节包含由社区创建的、未托管在本网站上的学习资源。 + + + +### 书籍 + + + +### 指南 & 教程 + + + +#### 视频指南 + + diff --git a/src/content/docs/zh-cn/learn/sidecar-nodejs.mdx b/src/content/docs/zh-cn/learn/sidecar-nodejs.mdx new file mode 100644 index 0000000000..6258f02f37 --- /dev/null +++ b/src/content/docs/zh-cn/learn/sidecar-nodejs.mdx @@ -0,0 +1,187 @@ +--- +title: Node.js 作为侧车进程 +sidebar: + order: 1 +i18nReady: true +--- + +import CommandTabs from '@components/CommandTabs.astro'; +import { Tabs, TabItem, Steps } from '@astrojs/starlight/components'; +import CTA from '@fragments/cta.mdx'; + +在本指南中,我们将把一个 Node.js 应用程序打包成一个自包含的二进制文件, +用于作为 Tauri 应用程序中的侧车进程(sidecar) ,而无需最终用户安装 Node.js。 +本示例教程仅适用于桌面操作系统。 + +我们建议你先阅读通用的 [侧车指南],以更深入地了解 Tauri 侧车进程的工作原理。 + +在本示例中,我们将创建一个 Node.js 应用程序,它从命令行读取输入 [process.argv],并通过 [console.log] 将输出写入标准输出。 +你可以使用其他替代的进程间通信方式,例如本地服务器、标准输入/输出或本地套接字。 +请注意,每种方式都有其自身的优缺点和安全问题。 + +## 先决条件 + +一个已经配置好 shell 插件的 Tauri 应用,并且能够在你本地正常编译和运行。 + +:::tip[创建一个实验应用] + +如果你不是高级用户,我们**强烈建议**你使用此处提供的选项和框架来操作。这只是一个实验环境,完成后你可以随时删除该项目。 + + + +- Project name: `node-sidecar-lab` +- Choose which language to use for your frontend: `Typescript / Javascript` +- Choose your package manager: `pnpm` +- Choose your UI template: `Vanilla` +- Choose your UI flavor: `Typescript` +- Would you like to setup the project for mobile as well? `yes` + +::: + +:::note +请先阅读并按照 [shell 插件指南](/plugin/shell/) 正确地初始化和配置该插件。 +如果没有正确初始化和配置插件,本示例将无法正常工作。 +::: + +## 指引 + + + +1. ##### 初始化侧车项目 + + 让我们创建一个新的 Node.js 项目,用于编写我们的侧车实现。 + 在你的 Tauri 应用程序的根目录下新建一个文件夹(在本例中我们将其命名为 `sidecar-app`), + 然后在该目录中运行你所使用的 Node.js 包管理器的 init 命令: + + + + 我们将使用 [pkg] 将我们的 Node.js 应用程序编译为一个自包含的二进制文件。 + 现在我们先将它作为开发依赖安装: + + + +1. ##### 编写侧车逻辑 + + 现在我们可以开始编写将由我们的 Tauri 应用程序执行的 JavaScript 代码。 + + 在本例中,我们将处理来自命令行参数的指令,并将输出写入标准输出(stdout)。 + 这意味着我们的进程是短生命周期的,每次只处理一条命令。 + 如果你的应用需要长生命周期运行,建议考虑使用其他的进程间通信方式。 + + 现在在我们的 `sidecar-app` 目录中创建一个 `index.js` 文件,并编写一个简单的 Node.js 应用: + + ```js title=sidecar-app/index.js + const command = process.argv[2]; + + switch (command) { + case 'ping': + const message = process.argv[3]; + console.log(`pong, ${message}`); + break; + default: + console.error(`unknown command ${command}`); + process.exit(1); + } + ``` + +1. ##### 打包侧车应用 + + 要将我们的 Node.js 应用程序打包为一个自包含的二进制文件,我们可以运行以下 `pkg` 命令: + + + + 这将在 Linux 和 macOS 上生成 `sidecar-app/app` 可执行文件,在 Windows 上生成 `sidecar-app/app.exe` 可执行文件。 + 为了将该文件重命名为 Tauri 所期望的侧车文件名,我们可以使用以下 Node.js 脚本: + + ```js + import { execSync } from 'child_process'; + import fs from 'fs'; + + const ext = process.platform === 'win32' ? '.exe' : ''; + + const rustInfo = execSync('rustc -vV'); + const targetTriple = /host: (\S+)/g.exec(rustInfo)[1]; + if (!targetTriple) { + console.error('无法确定平台目标三元组'); + } + fs.renameSync( + `app${ext}`, + `../src-tauri/binaries/app-${targetTriple}${ext}` + ); + ``` + +1. ##### 在 Tauri 应用中配置侧车应用 + + 现在我们已经准备好了 Node.js 应用程序,接下来可以通过配置 [`bundle > externalBin`] 数组,将其连接到我们的 Tauri 应用程序: + + ```json title="src-tauri/tauri.conf.json" + { + "bundle": { + "externalBin": ["binaries/app"] + } + } + ``` + + 只要对应平台的二进制文件存在于 `src-tauri/binaries/app-` 路径下,Tauri CLI 就会自动将该侧车应用二进制文件打包进你的应用程序中。 + +1. ##### 运行侧车应用 + + 我们可以从 Rust 代码中运行这个侧车二进制文件,也可以直接从 JavaScript 中调用。 + + + + + + 下面我们直接在 Node.js 侧车进程中执行 `ping` 命令: + + + ```javascript + import { Command } from '@tauri-apps/plugin-shell'; + + const message = 'Tauri'; + + const command = Command.sidecar('binaries/app', ['ping', message]); + const output = await command.execute(); + const response = output.stdout; + ``` + + + + + + 让我们将一个 `ping` 的 Tauri 命令传递给 Node.js 侧车来执行: + + ```rust + #[tauri::command] + async fn ping(app: tauri::AppHandle, message: String) -> String { + let sidecar_command = app + .shell() + .sidecar("app") + .unwrap() + .arg("ping") + .arg(message); + let output = sidecar_command.output().unwrap(); + let response = String::from_utf8(output.stdout).unwrap(); + response + } + ``` + + + + + + + +[侧车指南]: /develop/sidecar/ +[process.argv]: https://nodejs.org/docs/latest/api/process.html#processargv +[console.log]: https://nodejs.org/api/console.html#consolelogdata-args +[pkg]: https://github.com/vercel/pkg +[`bundle > externalBin`]: /reference/config/#externalbin diff --git a/src/content/docs/zh-cn/learn/splashscreen.mdx b/src/content/docs/zh-cn/learn/splashscreen.mdx new file mode 100644 index 0000000000..b1534a6c9a --- /dev/null +++ b/src/content/docs/zh-cn/learn/splashscreen.mdx @@ -0,0 +1,269 @@ +--- +title: 启动画面 +sidebar: + order: 1 +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 5 +i18nReady: true +--- + +import { Image } from 'astro:assets'; +import step_1 from '@assets/learn/splashscreen/step_1.png'; +import step_3 from '@assets/learn/splashscreen/step_3.png'; +import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; +import ShowSolution from '@components/ShowSolution.astro'; +import CTA from '@fragments/cta.mdx'; + +在本实验中,我们将在 Tauri 应用中实现一个基本的启动画面功能。 +实现方法非常简单,启动画面实际上就是在启动应用前执行一些繁重的设置任务时 +创建一个新窗口来显示一些内容,然后在设置完成后关闭它。 + +## 先决条件 + +:::tip[创建一个实验应用] + +如果你不是高级用户,我们**强烈建议**你使用此处提供的选项和框架来操作。这只是一个实验环境,完成后你可以随时删除该项目。 + + + +- Project name: `splashscreen-lab` +- Choose which language to use for your frontend: `Typescript / Javascript` +- Choose your package manager: `pnpm` +- Choose your UI template: `Vanilla` +- Choose your UI flavor: `Typescript` +- Would you like to setup the project for mobile as well? `yes` +::: + +## 步骤 + + + +1. ##### 安装依赖项并运行项目 + + 在开始开发任何项目之前,构建和运行初始模板非常重要,以验证你的设置是否按预期工作。 + + + ```sh frame=none + # 确保你在正确的目录下 + cd splashscreen-lab + # 安装依赖 + pnpm install + # 构建并运行应用 + pnpm tauri dev + ``` + + + +1. ##### 在 `tauri.conf.json` 中注册窗口 + + 添加新窗口最简单的方法是直接将它们添加到 `tauri.conf.json` 中。你也可以在启动时动态创建它们, + 但为了简单起见,我们直接注册它们。请确保你有一个标签为 `main` 窗口(创建时设置为隐藏窗口), + 以及一个标签为 `splashscreen` 的窗口(创建时设置为直接显示窗口)。 + 你可以将所有其他选项保留为默认值,也可以根据个人喜好进行调整。 + + + ```json + // src-tauri/tauri.conf.json + { + "windows": [ + { + "label": "main", + "visible": false + }, + { + "label": "splashscreen", + "url": "/splashscreen" + } + ] + } + ``` + + +1. ##### 创建新页面来托管你的启动画面 + + Before you begin you'll need to have some content to show. How you develop new pages depend on your chosen framework, + most have the concept of a "router" that handles page navigation which should work just like normal in Tauri, in which case + you just create a new splashscreen page. Or as we're going to be doing here, create a new `splashscreen.html` file to host the contents. + 开始之前,你需要准备一些用于展示的内容。如何开发新页面取决于你选择的框架,大多数框架都包含“路由器”的概念, + 用于处理页面导航,其工作原理与 Tauri 中的常规操作相同。在这种情况下,你只需创建一个新的启动画面页面即可。 + 或者,就像我们这里要做的那样,创建一个新的 `splashscreen.html` 文件来托管内容。 + + What's important here is that you can navigate to a `/splashscreen` URL and be shown the contents you want for your splashscreen. Try running the app again after this step! + 这里重要的是,你可以导航到 `/splashscreen` 并显示你想要的启动画面内容。完成此步骤后,请尝试再次运行该应用程序! + + + ```html + // /splashscreen.html + + + + + + + Tauri App + + + + Tauri used Splash + + It was super effective! + + + + + ``` + + + +1. ##### 开始一些设置任务 + + 由于启动画面通常用于隐藏繁重的设置相关任务,因此让我们假装给应用程序一些繁重的任务去做,一些在前端,一些在后端。 + + 为了模拟前端的繁重设置,我们将使用一个简单的 `setTimeout` 函数。 + + 在后端模拟繁重操作的最简单方法是使用 Tokio crate,这是 Tauri 在后端用来提供异步运行时的 Rust crate。虽然 Tauri 提供了运行时,但 Tauri 并没有从中重新导出各种实用程序,因此我们需要将该 crate 添加到项目中才能访问它们。这在 Rust 生态系统中是一种非常正常的做法。 + + 不要在异步函数中使用 `std::thread::sleep` ,它们在并发环境中协同运行而不是并行运行,这意味着如果你让线程而不是 Tokio 任务休眠,你将锁定计划在该线程上运行的所有任务,从而导致你的应用程序冻结。 + + + ```sh frame=none + # 使用该命令到包含 `Cargo.toml` 文件的目录下 + cd src-tauri + # 添加 Tokio crate + cargo add tokio + # 选择性的返回顶层目录以继续开发 + # `tauri dev` 也可以自动识别出从哪里启动 + cd .. + ``` + + ```javascript + // src/main.ts + // 这些语句可以复制粘贴到现有的代码下面,但不要替换整个文件!! + + // 在 TypeScript 中实现的一个 sleep 函数 + function sleep(seconds: number): Promise { + return new Promise(resolve => setTimeout(resolve, seconds * 1000)); + } + + // 设置函数 + async function setup() { + // 模拟执行一个很重的前端设置任务 + console.log('Performing really heavy frontend setup task...') + await sleep(3); + console.log('Frontend setup task complete!') + // 设置前端任务为完成 + invoke('set_complete', {task: 'frontend'}) + } + + // 实际上的 JavaScript main 函数 + window.addEventListener("DOMContentLoaded", () => { + setup() + }); + ``` + + ```rust + // /src-tauri/src/lib.rs + // 导入我们需要使用的模块 + use std::sync::Mutex; + use tauri::async_runtime::spawn; + use tauri::{AppHandle, Manager, State}; + use tokio::time::{sleep, Duration}; + + // 创建一个结构,用于跟踪前端任务完成情况 + // 设置相关任务 + struct SetupState { + frontend_task: bool, + backend_task: bool, + } + + // 在 v2 移动兼容应用中我们的主要入口点 + #[cfg_attr(mobile, tauri::mobile_entry_point)] + pub fn run() { + // 不要在 Tauri 启动之前写代码,而是写在 setup 钩子中 + tauri::Builder::default() + // 注册一个由 Tauri 管理的 `State` + // 我们需要对它拥有写访问权限,因此我们将其包裹在 `Mutex` 中 + .manage(Mutex::new(SetupState { + frontend_task: false, + backend_task: false, + })) + // 添加我们用于检查的命令 + .invoke_handler(tauri::generate_handler![greet, set_complete]) + // 使用 setup 钩子来执行设置相关任务 + // 在主循环之前运行,因此尚未创建窗口 + .setup(|app| { + // Spawn 操作设置为一个非阻塞任务,以便在它执行的同时可以创建并运行窗口。 + spawn(setup(app.handle().clone())); + // 钩子期望返回一个 Ok 的结果 + Ok(()) + }) + // 启动应用 + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + } + + #[tauri::command] + fn greet(name: String) -> String { + format!("Hello {name} from Rust!") + } + + // 一个用于设置初始化任务状态的自定义任务 + #[tauri::command] + async fn set_complete( + app: AppHandle, + state: State<'_, Mutex>, + task: String, + ) -> Result<(), ()> { + // 以只读方式锁定 `State` + let mut state_lock = state.lock().unwrap(); + match task.as_str() { + "frontend" => state_lock.frontend_task = true, + "backend" => state_lock.backend_task = true, + _ => panic!("invalid task completed!"), + } + // 检查两个任务是否都已完成 + if state_lock.backend_task && state_lock.frontend_task { + // 设置都已完成,我们可以关闭启动画面并且显示 main 窗口了 + let splash_window = app.get_webview_window("splashscreen").unwrap(); + let main_window = app.get_webview_window("main").unwrap(); + splash_window.close().unwrap(); + main_window.show().unwrap(); + } + Ok(()) + } + + // 一个异步函数,用于执行一些耗时的设置任务 + async fn setup(app: AppHandle) -> Result<(), ()> { + // 模拟执行一些耗时的设置任务,3秒后完成 + println!("Performing really heavy backend setup task..."); + sleep(Duration::from_secs(3)).await; + println!("Backend setup task completed!"); + // 设置后端任务为已完成 + // 可以像普通函数一样运行命令,但需要自己处理输入参数 + set_complete( + app.clone(), + app.state::>(), + "backend".to_string(), + ) + .await?; + Ok(()) + } + ``` + + +1. ##### 启动应用 + + 你现在应该会看到一个启动画面窗口弹出,前端和后端将各自执行耗时 3 秒的初始化任务,完成后启动画面会消失,并显示主窗口! + + + +## 讨论 + +##### 你是否应该一个启动画面? + +一般来说,使用启动画面其实意味着你承认自己的应用无法在足够短的时间内完成加载,以至于不得不依赖它。 +事实上,更好的做法通常是直接打开主窗口,然后在界面的某个角落显示一个小的加载指示器(比如旋转的进度条),让用户知道后台仍在进行一些初始化任务。 + +然而,话说回来,使用启动画面也可以是一种风格上的选择,或者你可能有一些特殊需求,必须等待某些任务完成后才能启动应用。 +在这种情况下,使用启动画面当然没有*错*,只是通常来说它并不是必需的,而且可能会让用户觉得这个应用优化得不够好。 diff --git a/src/content/docs/zh-cn/learn/system-tray.mdx b/src/content/docs/zh-cn/learn/system-tray.mdx new file mode 100644 index 0000000000..2a4683ce9c --- /dev/null +++ b/src/content/docs/zh-cn/learn/system-tray.mdx @@ -0,0 +1,316 @@ +--- +title: 系统托盘 +sidebar: + order: 1 +tableOfContents: + maxHeadingLevel: 4 +i18nReady: true +--- + +import { Icon } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +Tauri 允许您为应用程序创建和自定义系统托盘。这可以通过提供对常用操作的快速访问来提升用户体验。 + +## 配置 + +首先,更新您的 `Cargo.toml` 以包含系统托盘所需的功能。 + +```toml title="src-tauri/Cargo.toml" +tauri = { version = "2.0.0", features = [ "tray-icon" ] } +``` + +## 用法 + +托盘 API 在 JavaScript 和 Rust 中均可使用。 + +### 创建托盘图标 + + + +使用 [`TrayIcon.new`] 静态函数创建一个新的托盘图标。 + +```javascript +import { TrayIcon } from '@tauri-apps/api/tray'; + +const options = { + // 你可以在这里添加一个托盘菜单、标题、任务提示、事件处理程序等等 +}; + +const tray = await TrayIcon.new(options); +``` + +有关自定义选项的更多信息,请参阅 [`TrayIconOptions`] 。 + + + + + +```rust +use tauri::tray::TrayIconBuilder; + +tauri::Builder::default() + .setup(|app| { + let tray = TrayIconBuilder::new().build(app)?; + Ok(()) + }) + +``` + +有关自定义选项的更多信息,请参阅 [`TrayIconBuilder`] 。 + + + + +### 更改托盘图标 + +创建托盘时,您可以使用应用程序图标作为托盘图标: + + + + +```javascript +import { TrayIcon } from '@tauri-apps/api/tray'; +import { defaultWindowIcon } from '@tauri-apps/api/app'; + +const options = { + icon: await defaultWindowIcon(), +}; + +const tray = await TrayIcon.new(options); +``` + + + + + +```rust +let tray = TrayIconBuilder::new() + .icon(app.default_window_icon().unwrap().clone()) + .build(app)?; +``` + + + + +### 添加菜单 + +要添加一个单击托盘时显示的菜单,您可以使用 `menu` 选项。 + +:::note +默认情况下,单击左键或右键单击都会显示菜单。 + +为了防止左键单击时弹出菜单,请在 Rust 中调用 [`menu_on_left_click(false)`][TrayIconBuilder::menu_on_left_click] 函数 +或在 JavaScript 中将 [`menuOnLeftClick`] 选项设置为 `false` 。 +::: + +{/* TODO: link to the menu plugin documentation page */} + + + + +```javascript +import { TrayIcon } from '@tauri-apps/api/tray'; +import { Menu } from '@tauri-apps/api/menu'; + +const menu = await Menu.new({ + items: [ + { + id: 'quit', + text: 'Quit', + }, + ], +}); + +const options = { + menu, + menuOnLeftClick: true, +}; + +const tray = await TrayIcon.new(options); +``` + + + + + +```rust +use tauri::{ + menu::{Menu, MenuItem}, + tray::TrayIconBuilder, +}; + +let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; +let menu = Menu::with_items(app, &[&quit_i])?; + +let tray = TrayIconBuilder::new() + .menu(&menu) + .menu_on_left_click(true) + .build(app)?; +``` + + + + +#### 监听菜单事件 + + + +在 JavaScript 中,您可以将菜单单击事件监听器直接附加到菜单项: + +- 使用共享菜单点击处理程序 + + ```javascript + import { Menu } from '@tauri-apps/api/menu'; + + function onTrayMenuClick(itemId) { + // itemId === 'quit' + } + + const menu = await Menu.new({ + items: [ + { + id: 'quit', + text: 'Quit', + action: onTrayMenuClick, + }, + ], + }); + ``` + +- 使用专用的菜单点击处理程序 + + ```javascript + import { Menu } from '@tauri-apps/api/menu'; + + const menu = await Menu.new({ + items: [ + { + id: 'quit', + text: 'Quit', + action: () => { + console.log('quit pressed'); + }, + }, + ], + }); + ``` + + + + +使用 [`TrayIconBuilder::on_menu_event`] 方法附加托盘菜单点击事件监听器: + +```rust +use tauri::tray::TrayIconBuilder; + +TrayIconBuilder::new() + .on_menu_event(|app, event| match event.id.as_ref() { + "quit" => { + println!("quit menu item was clicked"); + app.exit(0); + } + _ => { + println!("menu item {:?} not handled", event.id); + } + }) +``` + + + + +### 监听托盘事件 + +托盘图标可能发出以下鼠标事件: + +- Click: 当光标收到左键、右键或中键单击时触发,包括鼠标是否被释放的信息 +- Double click: 当光标收到双击左键、右键或中键时触发 +- Enter: 当光标进入托盘图标区域时触发 +- Move: 当光标在托盘图标区域移动时触发 +- Leave: 当光标离开托盘图标区域时触发 + + + + +```javascript +import { TrayIcon } from '@tauri-apps/api/tray'; + +const options = { + action: (event) => { + switch (event.type) { + case 'Click': + console.log( + `mouse ${event.button} button pressed, state: ${event.buttonState}` + ); + break; + case 'DoubleClick': + console.log(`mouse ${event.button} button pressed`); + break; + case 'Enter': + console.log( + `mouse hovered tray at ${event.rect.position.x}, ${event.rect.position.y}` + ); + break; + case 'Move': + console.log( + `mouse moved on tray at ${event.rect.position.x}, ${event.rect.position.y}` + ); + break; + case 'Leave': + console.log( + `mouse left tray at ${event.rect.position.x}, ${event.rect.position.y}` + ); + break; + } + }, +}; + +const tray = await TrayIcon.new(options); +``` + +有关事件负载的更多信息,请参阅 [`TrayIconEvent`][js TrayIconEvent] 。 + + + + + +```rust +use tauri::{ + Manager, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent} +}; + +TrayIconBuilder::new() + .on_tray_icon_event(|tray, event| match event { + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } => { + println!("left click pressed and released"); + // 在这个例子中,当点击托盘图标时,将展示并聚焦于主窗口 + let app = tray.app_handle(); + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + } + _ => { + println!("unhandled event {event:?}"); + } + }) +``` + +有关事件类型的更多信息,请参阅 [`TrayIconEvent`][rust TrayIconEvent] 。 + + + + +[`TrayIcon.new`]: /reference/javascript/api/namespacetray/#new +[`TrayIconOptions`]: /reference/javascript/api/namespacetray/#trayiconoptions +[`TrayIconBuilder`]: https://docs.rs/tauri/2.0.0/tauri/tray/struct.TrayIconBuilder.html +[TrayIconBuilder::menu_on_left_click]: https://docs.rs/tauri/2.0.0/tauri/tray/struct.TrayIconBuilder.html#method.menu_on_left_click +[`menuOnLeftClick`]: /reference/javascript/api/namespacetray/#properties-1 +[`TrayIconBuilder::on_menu_event`]: https://docs.rs/tauri/2.0.0/tauri/tray/struct.TrayIconBuilder.html#method.on_menu_event +[js TrayIconEvent]: /reference/javascript/api/namespacetray/#trayiconevent +[rust TrayIconEvent]: https://docs.rs/tauri/2.0.0/tauri/tray/enum.TrayIconEvent.html
+ Click on the Tauri, Vite, and Svelte logos to learn more. +