tauri2编程,前端部分和electron差不多,框架部分差别大,资料少,官网乱,AI又骗我
所以在gitee上,寻找tauri v2开源项目,
通过记录框架部分与rust部分的写法,对照确定编程方式
提示:不要在VSCode里自动运行Cargo,在powershell里运行Cargo build,不会卡住
https://gitee.com/MapleKing/tauri-desktop
展示了自制标题栏的做法
{
...
"app": {
"windows": [
{
"title": "tauri-desktop",
"minWidth": 800,
"minHeight": 600,
"decorations": false
}
],
"security": {
"csp": null
}
},
"bundle": {
...
"resources": {
"config/*": "config/"
}
}
}
true(默认值):
窗口会显示操作系统原生的标题栏、边框、最小化/最大化/关闭按钮等装饰。
例如:Windows 上的标准标题栏,macOS 顶部的红黄绿按钮等。
false:
隐藏原生装饰,窗口将变成一个无边框的“纯内容”区域。
通常用于自定义标题栏(用 HTML/CSS 实现)。
需要自行处理窗口拖动、最小化、关闭等功能(可通过 Tauri API 实现)。
提供了最小,最大,关闭的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();
}
前端操作文件的一个案例
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
};
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"
初始化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");
}
{
"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"
}
}
{
"$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"
]
}
https://gitee.com/smartxh/tauri-latest-stables-study
简单登录界面测试,记录一些框架的写法
类型: 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",
...
}
}
提供了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": "**"
}]
}
]
}
类型: boolean
默认值: false
作用:
true: 窗口不会在操作系统的任务栏(Windows/Linux)或 Dock(macOS)中显示图标。
false: 窗口会正常出现在任务栏/Dock 中(默认行为)。
适用场景:
登录窗口、悬浮小工具、通知窗口等辅助性窗口,不希望占用任务栏空间时使用。
通常与 resizable: false 和 decorations: false 结合,实现简洁的弹出式界面。
示例效果:
用户按主窗口时,登录窗口不会在任务栏生成额外图标,避免任务栏拥挤。
类型: 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
}
}
// 导入模块
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");
}
通过这样的写法,分离业务逻辑,简单明了
#[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()
}
}
https://gitee.com/loock/tauri-serial-tool
UDP的应用案例
没有重要内容
安装了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是如何应用的
{
"windows": ["main"],
"permissions": [
"core:default",
"udp:default",
"shell:allow-open",
"network:allow-all",
"network:allow-get-interfaces"
]
}
提供了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>
<Select
disabled={!!type}
options={ipOptions}
onChange={(v) => {
setLocalIP(v);
}}
style={{ minWidth: "200px" }}
></Select>
:
<Input
style={{ 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>
<Input
style={{ display: "inline-block", width: 150 }}
type="ip"
value={destIP}
onChange={(e) => setDestIP(e.target.value)}
/>
:
<Input
style={{ 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>
{/* 数据卡片 */}
<Card
title="数据"
style={{ marginTop: "10px" }}
extra={
<>
<Button
onClick={() => {
setIsModalOpen(true);
}}
>
自动回复
</Button>
<Button
onClick={() => {
saveArrayAsTxtFile(msg, `${new Date().getTime()}.txt`);
}}
style={{ marginLeft: "8px" }}
>
保存数据
</Button>
<Button
onClick={() => {
setMsg([]); // 清空接收消息
}}
style={{ marginLeft: "8px" }}
>
清空数据
</Button>
</>
}
>
<div>
<div
style={{
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)} />{" "}
{/* 十六进制输入框 */}
<Button
style={{ marginLeft: "8px" }}
type="primary"
onClick={handleSendMessage}
>
发送
</Button>{" "}
{/* 发送按钮 */}
</div>
<div
style={{
display: "flex",
justifyContent: "end",
alignItems: "center",
}}
>
<Checkbox
onChange={(e) => {
console.log(e, timteIntervalFlag.current);
window.clearInterval(timteIntervalFlag.current?.flag);
if (e.target.checked) {
timteIntervalFlag.current.flag = setInterval(() => {
handleSendMessage();
}, timteIntervalFlag.current.time);
}
}}
>
定时发送
</Checkbox>
<Input
style={{ width: "150px" }}
defaultValue={1000}
type="number"
suffix={"ms/次"}
onChange={(v) => {
timteIntervalFlag.current.time = Number(v.target.value);
}}
></Input>
</div>
</div>
</Card>
{/* 自动回复设置模态框 */}
<Modal
title="自动回复设置"
open={isModalOpen}
onOk={() => setIsModalOpen(false)}
onCancel={() => setIsModalOpen(false)}
width={"80vw"}
destroyOnClose
footer={null}
>
<HexForm />
</Modal>
</div>
);
};
export default App;
https://gitee.com/ZeroOpens/tauri-template/tree/master
一个带有更新等功能的框架
...
"plugins": {
"updater": {
"pubkey": "myapp.key.pub",
"endpoints": [
"https://github.com/user/repo/releases/latest/download/latest.json"
]
}
}
[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 # 确保移除调试符号。
获得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}
}
更新代码,通过运行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>
https://gitee.com/funtry/tauri-vue3
一个托盘图标的演示
security的设置方式有所不同
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
}
},
调用函数、关闭、获得窗口基本操作
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();
}
托盘图标操作
/**
* @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-app
tooltip: '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();
}
},
{
// 菜单 id
id: 'quit',
// 菜单文本
text: '退出',
// 菜单项点击事件
action: async() => {
try {
// 退出程序
exitApp();
} catch (error) {
console.log(error);
}
}
}
]
})
}
/**
* 创建系统托盘
*/
export async function createTray() {
// 获取 menu
options.menu = await createMenu();
await TrayIcon.new(options);
}
无特别
加入比较多的插件
[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"
"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"
]
可以通过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
])
// 引入并导出子模块
pub mod database;
pub mod tray;
// 导出常用结构体,使消费者更容易使用
pub use database::{Config, DatabaseConfig};
// 注意:系统信息命令直接使用tauri_plugin_system_info插件提供的命令
对话框操作
import { open, save, message, ask, confirm } from '@tauri-apps/plugin-dialog';
...
const file = await open({
multiple: false,
directory: false,
});
http请求
import { fetch } from '@tauri-apps/plugin-http';
...
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
log日志
import { trace, info, error, debug, warn, attachConsole } from '@tauri-apps/plugin-log';
...
import { appLogDir } from '@tauri-apps/api/path';
...
通知
import {
isPermissionGranted,
requestPermission,
sendNotification,
createChannel,
Importance,
Visibility
} from '@tauri-apps/plugin-notification';
https://gitee.com/zwssd1980/tffmpeg
tauri 运行ffmpeg
这个项目有关于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())?;
前端有些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)" },
];
https://gitee.com/lieranhuasha/tts-tauri
一个tts的demo
[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"
通过utils里的代码,提供了请求接口的rust的写法,值得参考
pub mod utils;
use utils::api::{get_exe_path, get_voices_list, start_tts};
关于接口请求的代码写法
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,
));
}
这里会打开默认浏览器
import { open } from '@tauri-apps/plugin-dialog';
import { open as openShell } from '@tauri-apps/plugin-shell';
...
const openBrowser = async (url)=>{
await openShell(url);
}
数据库也可以在前端操作,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语言来编写