tauri v2 开源项目学习(一)
前言:
tauri2编程,前端部分和electron差不多,框架部分差别大,资料少,官网乱,AI又骗我
所以在gitee上,寻找tauri v2开源项目,
通过记录框架部分与rust部分的写法,对照确定编程方式
提示:不要在VSCode里自动运行Cargo,在powershell里运行Cargo build,不会卡住
1. tauri-desktop
https://gitee.com/MapleKing/tauri-desktop
展示了自制标题栏的做法
1.1 tauri.conf.json
{..."app": {"windows": [{"title": "tauri-desktop","minWidth": 800,"minHeight": 600,"decorations": false}],"security": {"csp": null}},"bundle": {..."resources": {"config/*": "config/"}}
}
- decorations 的作用:
true(默认值):
窗口会显示操作系统原生的标题栏、边框、最小化/最大化/关闭按钮等装饰。
例如:Windows 上的标准标题栏,macOS 顶部的红黄绿按钮等。
false:
隐藏原生装饰,窗口将变成一个无边框的“纯内容”区域。
通常用于自定义标题栏(用 HTML/CSS 实现)。
需要自行处理窗口拖动、最小化、关闭等功能(可通过 Tauri API 实现)。
1.2 TopPlace.vue
提供了最小,最大,关闭的3个按钮的处理函数
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
appWindow.listen("tauri://resize", async () => {appWindow.isMaximized().then((res)=>{isMax.value = res;})
});
function toggleMaximize (){appWindow.toggleMaximize();
}
function close(){appWindow.close();
}
function minimize(){appWindow.minimize();
}
1.3 frame.ts
前端操作文件的一个案例
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile,writeTextFile} from '@tauri-apps/plugin-fs';
import { useGlobal } from '@/stores/global';
import CONFIG from '@/frame/config';
const initMenus = async function (){const resourcePath = await resolveResource(CONFIG.CONFIG_PATH);useGlobal().menus = JSON.parse(await readTextFile(resourcePath));
}
const initUserConfig = async function (){const resourcePath = await resolveResource(CONFIG.USER_CONFIG_PATH);//获取用户配置//获取用户本地配置的主题useGlobal().userConfig = JSON.parse(await readTextFile(resourcePath)); useGlobal().theme = useGlobal().userConfig.theme;
}const initFrame = async function (){return new Promise(async (resolve) => {await initMenus();await initUserConfig();resolve(true);})
}
const updateUserConfig = async function (){const userConfig = useGlobal().userConfig; // 获取用户配置const resourcePath = await resolveResource(CONFIG.USER_CONFIG_PATH);await writeTextFile(resourcePath, JSON.stringify(userConfig)); // 将用户配置写入文件
}export { initFrame,updateUserConfig
};
1.4 Cargo.toml
Cargo.toml加加入tauri-plugin-fs,关于tauri-plugin-shell并未发现作用
...
[lib]
name = "tauri_app_win_lib"
crate-type = ["staticlib", "cdylib", "rlib"][build-dependencies]
tauri-build = { version = "2.0.0", features = [] }[dependencies]
tauri = { version = "2.0.0", features = [] }
tauri-plugin-shell = "2.0.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-plugin-fs = "2"
1.5 lib.rs
初始化tauri_plugin_fs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {tauri::Builder::default().plugin(tauri_plugin_fs::init()).plugin(tauri_plugin_shell::init()).invoke_handler(tauri::generate_handler![greet]).run(tauri::generate_context!()).expect("error while running tauri application");
}
1.6 package.json
{"name": "tauri-desktop","private": true,"version": "0.1.0","type": "module","scripts": {"dev": "vite","build": "vue-tsc --noEmit && vite build","preview": "vite preview","tauri": "tauri"},"dependencies": {"@tauri-apps/api": ">=2.0.0","@tauri-apps/plugin-fs": "~2.0.0","@tauri-apps/plugin-shell": ">=2.0.0","pinia": "^2.2.4","vue": "^3.3.4","vue-router": "^4.4.5"},"devDependencies": {"@tauri-apps/cli": ">=2.0.0","@types/node": "^22.7.4","@vitejs/plugin-vue": "^5.0.5","naive-ui": "^2.40.1","typescript": "^5.2.2","vfonts": "^0.0.3","vite": "^5.3.1","vue-tsc": "^2.0.22"}
}
1.7 capabilities/default.json
{"$schema": "../gen/schemas/desktop-schema.json","identifier": "default","description": "Capability for the main window","windows": ["main"],"permissions": ["core:default","shell:allow-open","core:window:default","core:window:allow-close","core:window:allow-toggle-maximize","core:window:allow-minimize","core:window:allow-is-maximized","core:window:allow-start-dragging","fs:allow-read-text-file","fs:allow-resource-read-recursive","fs:allow-resource-write-recursive","fs:allow-resource-write","fs:allow-resource-read","fs:default","fs:allow-create"]
}
2. tauri-latest-stables-study
https://gitee.com/smartxh/tauri-latest-stables-study
简单登录界面测试,记录一些框架的写法
2.1 tauri.conf.json
- withGlobalTauri的作用:
类型: boolean
默认值: false
自动在 window.TAURI 上挂载 Tauri 的 JavaScript API,前端可以直接通过 window.TAURI.tauri 或 window.TAURI.window 等调用 Tauri 功能。
true:
适用于传统网页开发或需要全局访问 Tauri API 的场景。
false(默认):
不自动注入全局 TAURI 对象,前端需通过 @tauri-apps/api 的 ES 模块导入方式调用 API(推荐)。
更符合现代模块化开发规范,避免全局变量污染。
此处并未这样使用
{"app": {"withGlobalTauri": true,"windows": [],"security": {"csp": null,"capabilities": ["main-capability"]}},"bundle": {"copyright": "Huan","licenseFile": "./copyright/License.txt",...}
}
2.2 \capabilities\default.json
提供了main-capability的内容,和permissions的复杂的写法
{"$schema": "../gen/schemas/desktop-schema.json","identifier": "main-capability","description": "Capability for the main window","platforms": ["macOS", "windows", "linux"],"windows": ["*"],"permissions": ["core:default","opener:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-set-title",{"identifier": "fs:write-files","allow": [{"path": "**"}]},{"identifier": "fs:allow-mkdir","allow": [{"path": "**"}]},{"identifier": "fs:read-dirs","allow": [{"path": "**"}]},{"identifier": "fs:read-files","allow": [{"path": "**"}]},{"identifier": "fs:allow-copy-file","allow": [{"path": "**"}]},{"identifier": "fs:allow-read-text-file","allow": [{"path": "**"}]}]
}
2.3 tauri.windows.conf.json
- skipTaskbar的作用:
类型: boolean
默认值: false
作用:
true: 窗口不会在操作系统的任务栏(Windows/Linux)或 Dock(macOS)中显示图标。
false: 窗口会正常出现在任务栏/Dock 中(默认行为)。
适用场景:
登录窗口、悬浮小工具、通知窗口等辅助性窗口,不希望占用任务栏空间时使用。
通常与 resizable: false 和 decorations: false 结合,实现简洁的弹出式界面。
示例效果:
用户按主窗口时,登录窗口不会在任务栏生成额外图标,避免任务栏拥挤。
- transparent的作用:
类型: boolean
默认值: false
作用:
true: 窗口背景完全透明,仅显示内容(需前端 CSS 配合,如设置 background: transparent)。
false: 窗口背景为不透明(默认行为)。
这里使用了多窗口,通过url与路由绑定一起,这样是否能前期缓存,没有设定url是否还会有作用,需要进一步测试
"app": {"withGlobalTauri": true,"windows": [{"title": "登录","label": "login","url": "/login","fullscreen": false,"resizable": false,"center": true,"width": 320,"height": 448,"skipTaskbar": true,"transparent": true,"decorations": false},{"title": "注册","label": "register","url": "/register","fullscreen": false,"resizable": false,"center": true,"width": 320,"height": 448,"skipTaskbar": true,"transparent": true,"decorations": false},{"title": "变量生成","label": "variableGenerate","url": "/","fullscreen": false,"resizable": false,"center": true,"width": 320,"height": 448,"skipTaskbar": false,"transparent": true,"decorations": false}],"security": {"csp": null}}
2.4 lib.rs的写法
// 导入模块
mod commands;// 如果是移动平台,将此函数标记应用入口
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {// 创建构建器实例tauri::Builder::default()// 添加插件.plugin(tauri_plugin_opener::init()).plugin(tauri_plugin_fs::init())// 添加webview可访问的函数.invoke_handler(tauri::generate_handler![commands::login_api,commands::register_api])// 运行应用.run(tauri::generate_context!())// 捕获错误.expect("error while running tauri application");
}
2.5 command.rs的写法
通过这样的写法,分离业务逻辑,简单明了
#[tauri::command]
pub fn login_api(account: String, password: String) -> String {if account == "huan" && password == "123456" {"login_success".to_string()} else {"login_fail".to_string()}
}#[tauri::command]
pub fn register_api(username: String, email: String, password: String) -> String {if username.len() > 3 && email.contains('@') && password.len() > 6 {"register_success".to_string()} else {"register_fail".to_string()}
}
3. tauri 串口工具
https://gitee.com/loock/tauri-serial-tool
UDP的应用案例
3.1 tauri.conf.json
没有重要内容
3.2 cargo.toml
安装了tauri-plugin-udp与tauri-plugin-network
tauri-plugin官网上有发布一些,如果去github搜素tauri-plugin,会有更多的插件支持
[build-dependencies]
tauri-build = { version = "2", features = [] }[dependencies]
tauri = { version = "2", features = [ "devtools"] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
socket2 = "0.4"
pnet = "0.28"
tauri-plugin-udp = "0.1.1"
tauri-plugin-network = "2.0.4"
https://github.com/kuyoonjo/tauri-plugin-udp
看到upd是如何应用的
3.2 default.json
{"windows": ["main"],"permissions": ["core:default","udp:default","shell:allow-open","network:allow-all", "network:allow-get-interfaces"]
}
3.3 App.tsx
提供了udp的操作
// 引入React Hooks和其他依赖
import { useState, useEffect, useRef } from "react";
import { Input, Button, Card, Modal, Checkbox, Select } from "antd"; // Ant Design 组件
import { bind, unbind, send } from "@kuyoonjo/tauri-plugin-udp"; // UDP 插件方法
import { listen } from "@tauri-apps/api/event"; // 事件监听
import HexForm, { defaultAutoData } from "./AutoRepeat"; // 自定义表单组件与默认自动回复数据
import HexInput from "./HexInput"; // 十六进制输入组件
import dayjs from "dayjs";
import { getInterfaces } from "tauri-plugin-network-api";
import "./App.css";function getLocalIP() {return getInterfaces().then((ifaces) => {const ips: string[] = [];ifaces.forEach((item) => {item.v4_addrs?.forEach?.((ip4) => {ips.push(ip4.ip);});});return ips;});
}// 将字节数组转换为十六进制字符串
function bytesToHex(bytes: number[]) {return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join(" ");
}// 将十六进制字符串转换为字节数组
function hexToBytes(hexString: string) {const bytes = hexString.replace(/\s+/g, "").match(/.{1,2}/g); // 清理非十六进制字符,并按每两位分组return bytes ? bytes.map((byte) => parseInt(byte, 16)) : []; // 转换为整数数组
}interface IMsg {type: "in" | "out";time: string;value: string;
}function saveArrayAsTxtFile(array: IMsg[], filename = "output.txt") {// 将数组转换为字符串,每个元素占一行const content = array?.map((item) => {return `${item.time} ${item.type} : ${item.value}`;}).join("\n");// 创建一个 Blob 对象const blob = new Blob([content], { type: "text/plain" });// 创建一个下载链接const link = document.createElement("a");link.href = URL.createObjectURL(blob);link.download = filename;// 触发点击事件来下载文件document.body.appendChild(link);link.click();// 清理document.body.removeChild(link);URL.revokeObjectURL(link.href);
}// 发送十六进制数据
const sendHex = (destIP: string,destPort: number,hexStr: string,id: string
) => {const bytes = hexToBytes(hexStr); // 将十六进制字符串转换为字节数组return send(id, `${destIP}:${destPort}`, bytes).catch((err) => {console.error("%c Line:63 🍐", "color:#7f2b82", err);alert(`消息发送失败: ${err}`); // 显示发送失败信息}); // 发送数据
};// 获取自动回复配置
const getAutoReport = (msg: string) => {return (window as any).list?.find?.((item: any) => item.hex1 === msg); // 查找匹配的消息
};function customRandomString(length = 8,charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
) {let result = "";for (let i = 0; i < length; i++) {result += charset.charAt(Math.floor(Math.random() * charset.length));}return result;
}// 主应用组件
const App = () => {const idRef = useRef(customRandomString());const [ipOptions, setIps] = useState<{ label: string; value: string }[]>([{ label: "127.0.0.1", value: "127.0.0.1" },]);const [_, setLocalIP] = useState("127.0.0.1"); // 本地IP地址const [type, setType] = useState(0); // 当前状态,0:未启动, 1:已启动const [localPort, setLocalPort] = useState(8080); // 本地端口号const [destIP, setDestIP] = useState("127.0.0.1"); // 目标IP地址const [destPort, setDestPort] = useState(8080); // 目标端口号const [msg, setMsg] = useState<IMsg[]>([]); //消息列表const [message, setMessages] = useState<string>(""); // 待发送的消息const listenRef = useRef<any>(); // 保存监听函数引用const [isModalOpen, setIsModalOpen] = useState(false); // 控制模态框显示状态const timteIntervalFlag = useRef<{ flag: any; time: number }>({flag: null,time: 1000,}); // 定时const addMsg = (type: "in" | "out", hex: string) => {setMsg((m) => [{ type, time: dayjs().format("HH:mm:ss.SSS"), value: hex },...m,]);};// 组件挂载或卸载时执行useEffect(() => {(window as any).list = defaultAutoData(); // 初始化自动回复列表getLocalIP().then((ips) => {setIps(ips?.map((v) => ({ label: v, value: v })));});// 监听UDP消息listen("plugin://udp", (x: any) => {const hex = bytesToHex(x.payload.data); // 将接收到的数据转换为十六进制字符串const auto = getAutoReport(hex); // 查找自动回复配置if (auto) {setTimeout(() => {addMsg("out", auto.hex2);sendHex(destIP, destPort, auto.hex2, idRef.current); // 如果有匹配的自动回复,则发送回复}, 100);}addMsg("in", hex);}).then((fn) => {listenRef.current = fn; // 保存监听函数引用以便后续移除});// 组件卸载时清理监听return () => {listenRef.current?.();};}, [destIP, destPort]);// 启动UDP服务器const handleStartServer = async () => {try {await bind(idRef.current, `0.0.0.0:${localPort}`); // 绑定UDP服务器到指定端口setType(1); // 更新状态为已启动} catch (error) {console.error(error);alert(`UDP 服务启动失败: ${error}`); // 显示错误信息}};// 关闭UDP服务器const handleCloseServer = async () => {unbind(idRef.current); // 解绑UDP服务器setType(0); // 更新状态为未启动};// 发送消息const handleSendMessage = async () => {if (!message) return; // 如果没有消息则不发送try {addMsg("out", message);sendHex(destIP, destPort, message, idRef.current); // 发送十六进制消息} catch (error) {console.error(error);alert(`消息发送失败: ${error}`); // 显示发送失败信息}};// 返回React元素return (<div>{/* UDP连接信息卡片 */}<Card title="UDP连接信息"><table><tbody><tr><td>本机IP端口:</td><td><Selectdisabled={!!type}options={ipOptions}onChange={(v) => {setLocalIP(v);}}style={{ minWidth: "200px" }}></Select>:<Inputstyle={{ display: "inline-block", width: 80 }}type="number"disabled={!!type}value={localPort}onChange={(e) => setLocalPort(Number(e.target.value))}/></td></tr><tr><td>远端IP端口:</td><td><Inputstyle={{ display: "inline-block", width: 150 }}type="ip"value={destIP}onChange={(e) => setDestIP(e.target.value)}/>:<Inputstyle={{ display: "inline-block", width: 80 }}type="number"value={destPort}onChange={(e) => setDestPort(Number(e.target.value))}/></td></tr></tbody></table>{type === 0 ? (<Button type="primary" onClick={handleStartServer}>打开</Button>) : (<Button type="primary" onClick={handleCloseServer}>关闭</Button>)}</Card>{/* 数据卡片 */}<Cardtitle="数据"style={{ marginTop: "10px" }}extra={<><ButtononClick={() => {setIsModalOpen(true);}}>自动回复</Button><ButtononClick={() => {saveArrayAsTxtFile(msg, `${new Date().getTime()}.txt`);}}style={{ marginLeft: "8px" }}>保存数据</Button><ButtononClick={() => {setMsg([]); // 清空接收消息}}style={{ marginLeft: "8px" }}>清空数据</Button></>}><div><divstyle={{padding: "20px",height: "300px",overflowY: "auto",border: "#ccc solid 1px",borderRadius: "8px",}}>{msg?.map?.((item) => (<div key={`${item.time}${item.value}`}><span>{item.time}</span>{" "}<span className={item.type}>{item.value}</span></div>))}{" "}{/* 显示接收消息 */}</div><div style={{ display: "flex", marginTop: "20px" }}><HexInput value={message} onChange={(v: any) => setMessages(v)} />{" "}{/* 十六进制输入框 */}<Buttonstyle={{ marginLeft: "8px" }}type="primary"onClick={handleSendMessage}>发送</Button>{" "}{/* 发送按钮 */}</div><divstyle={{display: "flex",justifyContent: "end",alignItems: "center",}}><CheckboxonChange={(e) => {console.log(e, timteIntervalFlag.current);window.clearInterval(timteIntervalFlag.current?.flag);if (e.target.checked) {timteIntervalFlag.current.flag = setInterval(() => {handleSendMessage();}, timteIntervalFlag.current.time);}}}>定时发送</Checkbox><Inputstyle={{ width: "150px" }}defaultValue={1000}type="number"suffix={"ms/次"}onChange={(v) => {timteIntervalFlag.current.time = Number(v.target.value);}}></Input></div></div></Card>{/* 自动回复设置模态框 */}<Modaltitle="自动回复设置"open={isModalOpen}onOk={() => setIsModalOpen(false)}onCancel={() => setIsModalOpen(false)}width={"80vw"}destroyOnClosefooter={null}><HexForm /></Modal></div>);
};export default App;
4. tauri-template
https://gitee.com/ZeroOpens/tauri-template/tree/master
一个带有更新等功能的框架
4.1 tauri.conf.json
...
"plugins": {"updater": {"pubkey": "myapp.key.pub","endpoints": ["https://github.com/user/repo/releases/latest/download/latest.json"]}}
4.2 cargo.toml
[dependencies]
# 启用 devtools 功能
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-plugin-http = "2"[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"[profile.dev]
incremental = true # 以较小的步骤编译您的二进制文件。[profile.release]
codegen-units = 1 # 允许 LLVM 执行更好的优化。
lto = true # 启用链接时优化。
opt-level = "s" # 优先考虑小的二进制文件大小。如果您更喜欢速度,请使用 `3`。
panic = "abort" # 通过禁用 panic 处理程序来提高性能。
strip = true # 确保移除调试符号。
4.3 useTauri.ts
获得Tauri的版本号等
import { ref, onMounted } from 'vue';
import { getTauriVersion, getName, getVersion } from '@tauri-apps/api/app';export default function () {let tauriVersion = ref('')let appName = ref('')let appVersion = ref('')onMounted( async () => {tauriVersion.value = await getTauriVersion(); // 获取tauri版本appName.value = await getName(); // 获取应用程序名称appVersion.value = await getVersion(); // 获取应用程序版本})// 导出return {tauriVersion, appName, appVersion}
}
4.4 update.vue
更新代码,通过运行check,获得update对象,
通过判断是否需要更新,一路下来,完成程序更新
最后通过relaunch重启程序
<template><div class="update-container"><div class="update-section"><button @click="checkUpdate" :disabled="isUpdating">检查更新</button><div v-if="updateMessage" class="update-message">{{ updateMessage }}</div><div v-if="showProgress" class="progress-container"><div class="progress-bar" :style="{ width: progressPercentage + '%' }"></div><div class="progress-text">{{ progressPercentage }}%</div></div></div></div>
</template><script setup lang="ts">defineOptions({name: 'Update'})import { ref } from 'vue';import { check } from '@tauri-apps/plugin-updater';import { relaunch } from '@tauri-apps/plugin-process';const updateMessage = ref('');const isUpdating = ref(false);const showProgress = ref(false);const progressPercentage = ref(0);// 更新功能const checkUpdate = async () => {try {isUpdating.value = true;updateMessage.value = '正在检查更新...';const update = await check();if (!update) {updateMessage.value = '您已经在使用最新版本';setTimeout(() => {updateMessage.value = '';isUpdating.value = false;}, 3000);return;}updateMessage.value = `发现新版本 ${update.version},更新说明: ${update.body}`;showProgress.value = true;let downloaded = 0;let contentLength = 0;await update.downloadAndInstall((event) => {switch (event.event) {case 'Started':contentLength = event.data.contentLength!;updateMessage.value = `开始下载更新,总大小: ${(contentLength / 1024 / 1024).toFixed(2)}MB`;break;case 'Progress':downloaded += event.data.chunkLength;progressPercentage.value = Math.floor((downloaded / contentLength) * 100);break;case 'Finished':updateMessage.value = '下载完成,准备安装...';break;}});updateMessage.value = '更新已安装,即将重启应用...';setTimeout(async () => {await relaunch();}, 2000);} catch (error) {console.error('Update error:', error);updateMessage.value = `更新失败: ${error}`;isUpdating.value = false;showProgress.value = false;}}
</script>
5. tauri-vue3
https://gitee.com/funtry/tauri-vue3
一个托盘图标的演示
5.1 tauri.conf.json
security的设置方式有所不同
- dangerousDisableAssetCspModification 的作用:
false(默认值):
Tauri 会自动修改 CSP(内容安全策略),添加必要的安全规则(如允许加载本地资源、Tauri API 调用等),确保应用正常运行。
例如,自动添加 asset: 协议、ws:(WebSocket)等白名单规则。
推荐保持默认值,除非你有特殊需求。
true:
禁用 Tauri 对 CSP 的自动修改,完全使用开发者配置的 csp 规则。
这是一个 危险选项(前缀 dangerous 已标明),可能导致应用功能异常(如资源加载失败、Tauri API 不可用)。
仅适用于需要 完全自定义 CSP 的高级场景(如严格安全策略需求)。
..."app": {"windows": [{"title": "tauri-app-vue3","width": 1330,"height": 730}],"security": {"csp": "default-src 'self'","dangerousDisableAssetCspModification": false}},
5.2 tauriApi.rs
调用函数、关闭、获得窗口基本操作
import { getCurrentWindow } from '@tauri-apps/api/window';
import { exit } from '@tauri-apps/plugin-process';
import { invoke } from '@tauri-apps/api/core';
/*** * @param name * @returns */
export const greet = async (name: string): Promise<string> => {return await invoke("greet", { name });
}export const getCurrentWindowInstance = async () => {return await getCurrentWindow();
}
/*** 退出应用*/
export const exitApp = async () => {await exit();
}
5.3 tray.rs
托盘图标操作
/*** @description: 托盘菜单* @return {*}*/
// 获取当前窗口
import { getCurrentWindow } from '@tauri-apps/api/window';
// 导入系统托盘
import { TrayIcon, TrayIconOptions, TrayIconEvent } from '@tauri-apps/api/tray';
// 托盘菜单
import { Menu } from '@tauri-apps/api/menu';import { exitApp } from './tools/tauriApi';/*** 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等*/
const options: TrayIconOptions = {// icon 的相对路径基于:项目根目录/src-tauri/,其他 tauri api 相对路径大抵都是这个套路icon: "icons/32x32.png",// 托盘提示,悬浮在托盘图标上可以显示 tauri-apptooltip: 'tauri-app',// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。menuOnLeftClick: false,// 托盘图标上事件的处理程序。action: (event: TrayIconEvent) => {// 左键点击事件if (event.type === 'Click' && event.button === "Left" && event.buttonState === 'Down') {console.log('单击事件');// 显示窗口winShowFocus();}}
}/*** 窗口置顶显示*/
async function winShowFocus() {// 获取窗体实例const win = getCurrentWindow();// 检查窗口是否见,如果不可见则显示出来if (!(await win.isVisible())) {win.show();} else {// 检查是否处于最小化状态,如果处于最小化状态则解除最小化if (await win.isMinimized()) {await win.unminimize();}// 窗口置顶await win.setFocus();}
}/*** 创建托盘菜单*/
async function createMenu() {return await Menu.new({// items 的显示顺序是倒过来的items: [{icon: 'icons/32x32.png',id: 'show',text: '显示窗口',action: () => {winShowFocus();}},{// 菜单 idid: 'quit',// 菜单文本text: '退出',// 菜单项点击事件action: async() => {try {// 退出程序exitApp();} catch (error) {console.log(error);}}}]})
}/*** 创建系统托盘*/
export async function createTray() {// 获取 menuoptions.menu = await createMenu();await TrayIcon.new(options);
}
6. tauri-win-learn
https://gitee.com/mingjianyeying/tauri-win-learn
功能比较多框架
6.1 tauri.conf.json
无特别
6.2 cargo.toml
加入比较多的插件
[dependencies]
tauri = { version = "2.0.0-beta.3", features = ["wry", "tray-icon", "image-ico"] }
tauri-plugin-opener = "2.0.0-beta.3"
tauri-plugin-sql = { version = "2.0.0-beta.3", features = ["mysql"], default-features = false }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "mysql", "macros"], default-features = false }
tokio = { version = "1", features = ["full"] }
tauri-plugin-os = "2.0.0-beta.3"
tauri-plugin-dialog = "2.0.0-beta.3"
tauri-plugin-http = "2.0.0-beta.3"
tauri-plugin-notification = "2.0.0-beta.3"
tauri-plugin-log = "2"
tauri-plugin-system-info = "2.0.9"
6.3 default.json
"permissions": ["core:default","opener:default","sql:default","sql:allow-execute","os:default","dialog:default",{"identifier": "http:default","allow": [{"url": "https://api.github.com/users/tauri-apps"}]},"notification:default","log:default"]
6.4 main.rs
可以通过mod sysInfo嵌入,也可以通过use tauri_app_lib::{database, tray};嵌入
use serde::Serialize;
use tauri_app_lib::{database, tray};
// 导入sysInfo模块
mod sysInfo;
...
// 注册命令
.invoke_handler(tauri::generate_handler![get_os_info,database::get_users,database::add_user,database::update_user,database::delete_user,sysInfo::get_detailed_system_info,sysInfo::get_simplified_system_info
])
6.5 lib.rs
// 引入并导出子模块
pub mod database;
pub mod tray;// 导出常用结构体,使消费者更容易使用
pub use database::{Config, DatabaseConfig};// 注意:系统信息命令直接使用tauri_plugin_system_info插件提供的命令
6.6 rust其他功能
- tray.rs :托盘
- sysInfo.rs :获取系统信息
- database.rs :数据库操作
6.7 src\pages\FileOperationPage.jsx
对话框操作
import { open, save, message, ask, confirm } from '@tauri-apps/plugin-dialog';
...const file = await open({multiple: false,directory: false,});
6.8 src\pages\HttpCasePage.jsx
http请求
import { fetch } from '@tauri-apps/plugin-http';
...
const response = await fetch(url, {method: 'GET',headers: {'Accept': 'application/json'}});
6.9 src\pages\LogCasePage.jsx
log日志
import { trace, info, error, debug, warn, attachConsole } from '@tauri-apps/plugin-log';
...
import { appLogDir } from '@tauri-apps/api/path';
...
6.10 src\pages\NotificationCasePage.jsx
通知
import { isPermissionGranted,requestPermission,sendNotification,createChannel,Importance,Visibility
} from '@tauri-apps/plugin-notification';
7. tffmpeg
https://gitee.com/zwssd1980/tffmpeg
tauri 运行ffmpeg
7.1 lib.rs
这个项目有关于Command一些操作细节可参考
let mut ffmpeg_cmd = Command::new("ffmpeg/bin/ffmpeg.exe").arg("-loglevel").arg("debug").arg("-y").arg("-hide_banner").arg("-i").arg(&input_path_clone).args(options_clone.split_whitespace()).arg(&output_file_clone).stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()).creation_flags(0x08000000).spawn().map_err(|e| e.to_string())?;
7.2 App.jsx
前端有些ffmpeg信息归纳
const options2 = [{ value: "1", label: "音视频质量不变" },{ value: "2", label: "WEB视频流媒体" },{ value: "3", label: "H264压缩" },{ value: "4", label: "H264压缩-Intel加速" },{ value: "5", label: "H264压缩-AMD加速" },{ value: "6", label: "H264压缩-NV加速" },{ value: "7", label: "H265压缩" },{ value: "8", label: "H265压缩-AMD加速" },{ value: "9", label: "H265压缩-AMD加速" },{ value: "10", label: "H265压缩-NV加速" },{ value: "11", label: "快速时间录制" },{ value: "12", label: "快速时间放大" },{ value: "13", label: "设置高质量比例" },{ value: "14", label: "视频0.5倍速 + 光流法补帧到60帧" },{ value: "15", label: "裁切视频画面" },{ value: "16", label: "视频旋转度数" },{ value: "17", label: "水平翻转画面" },{ value: "18", label: "垂直翻转画面" },{ value: "19", label: "设定至指定分辨率并且自动填充黑边" },{ value: "20", label: "转码到mp3" },{ value: "21", label: "音频两倍速" },{ value: "22", label: "音频倒放" },{ value: "23", label: "声音响度标准化" },{ value: "24", label: "音量大小调节" },{ value: "25", label: "静音第一个声道" },{ value: "26", label: "静音所有声道" },{ value: "27", label: "交换左右声道" },{ value: "28", label: "gif(15fps,480p)" },{ value: "29", label: "从视频区间每秒提取n张照片" },{ value: "30", label: "截取指定数量的帧保存为图片" },{ value: "31", label: "视频或音乐添加封面图片" },
];const audioOptions2 = [{ value: "32", label: "转码到mp3" },{ value: "33", label: "音频两倍速" },{ value: "34", label: "音频倒放" },{ value: "35", label: "声音响度标准化" },{ value: "36", label: "音量大小调节" },{ value: "37", label: "静音第一个声道" },{ value: "38", label: "静音所有声道" },{ value: "39", label: "交换左右声道" },
];const picOptions2 = [{ value: "40", label: "JPEG压缩质量(1-31,越大压缩率越高)" },{ value: "41", label: "PNG无损压缩(zlib 1-9)" },{ value: "42", label: "WebP压缩(质量0-100)" },
];
8. tts-tauri
https://gitee.com/lieranhuasha/tts-tauri
一个tts的demo
8.1 cargo.toml
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = "0.4.40"
sha2 = "0.10.8"
uuid = {version = "1.16.0", features = ["v4"] }
url = "2.5.4"
regex = "1.11.1"
hex = "0.4.3"
tokio = {version = "1.44.2", features = ["full"] }
tokio-tungstenite = {version = "0.26.2", features = ["native-tls"] }
futures-util = "0.3.31"
reqwest = "0.12.15"
base64 = "0.22.1"
tauri-plugin-dialog = "2"
tauri-plugin-shell = "2"
8.2 lib.rs
通过utils里的代码,提供了请求接口的rust的写法,值得参考
pub mod utils;
use utils::api::{get_exe_path, get_voices_list, start_tts};
8.3 api.rs
关于接口请求的代码写法
let response = get(url).await.map_err(|e| CustomResult::error(Some(e.to_string()), None))?;if response.status().is_success() {let body = response.text().await.map_err(|e| CustomResult::error(Some(e.to_string()), None))?;let json: serde_json::Value =from_str(body.as_str()).map_err(|e| CustomResult::error(Some(e.to_string()), None))?;return Ok(CustomResult::success(None, Some(json)));}return Err(CustomResult::error(Some(response.status().to_string()),None,));
}
8.4 src\pages\SetPage.vue
这里会打开默认浏览器
import { open } from '@tauri-apps/plugin-dialog';import { open as openShell } from '@tauri-apps/plugin-shell';...const openBrowser = async (url)=>{await openShell(url);}
8.5 src\utils\sqlite.js
数据库也可以在前端操作,sqlite操作
import Database from '@tauri-apps/plugin-sql';
...
function connect(){return new Promise(async (resolve, reject) => {if(isConnect){resolve();}else{try {db = await Database.load('sqlite:database.db');// 初始化数据库for (let i = 0; i < databseTable.length; i++) {// 初始化数据库,如果出错,则立即中止,并退出程序await init(databseTable[i].name, databseTable[i].sql).catch((err) => {reject(err);return;});}// 初始化成功,连接成功isConnect = true;resolve();} catch (error) {reject(error);}}})
}
tauri2开始,通过tauri-plugin的方式,把更多的编程,通过js语言来编写