前言
本文是基于rust和tauri,由于tauri是前、后端结合的GUI框架,既可以直接生成包含前端代码的文件,也可以在已有的前端项目上集成tauri框架,将前端页面化为桌面GUI。
环境配置
系统:windows 10 平台:visual studio code 语言:rust、javascript 库:tauri2.0
概述
本文基于tauri框架,创建一个图片显示器程序。要实现的功能是,如何从本地路径加载、显示、保存图片。
前提准备
由于本文涉及到文件选择,但我们不使用html的input元素,而是使用tauri的插件,所以我们需要添加一个文件对话框的库:
npm run tauri add dialog
dialog的用法如下(tauri官方手册):
比如我们在本文想要实现文件选择,那么就需要打开一个文件对话框,其官方代码如下:
open对话框示例代码
import { open } from '@tauri-apps/plugin-dialog';
// Open a dialog
const file = await open({
multiple: false,
directory: false,
});
console.log(file);
// Prints file path and name to the console
事实上dialog插件可以在javascript和rust中使用,但本文只选择在将javascript中使用,不涉及rust中的使用,详细可以参考tauri官网: v2.tauri.app/zh-cn/plugi…
1、创建前端项目
此处不再赘述,可以参看我的前一篇博文: juejin.cn/post/746867…
本文将在之前的项目的基础上进行修改。我们将修改之前的计算器布局,添加一个图片选择与显示的前端页面,可以命名为imgshow.html。 imgshow.html
<div class="imgshow">
<div id="btnzone">
<button id="openfilebtn">加载图片</button>
<button id="savefilebtn">保存图片</button>
</div>
<p id="srcfilepathp">源图片路径:</p>
<div id="img1div" class="img1div">
<img id="srcimgshow" alt="src">
</div>
</div>
将上面的文件放入public文件夹,以便调用。然后我们需要修改原来的main.js文件:
async function loadImage(path,divid) {
const response = await fetch(path);
const template = await response.text();
document.querySelector(divid).innerHTML = template;
};
loadImage('./imgshow.html','#app');
可以单独运行页面,看一下效果:
上图是初始运行效果,未加载任何图片。
2、图片显示
现在,我们希望点击加载图片按钮时,会弹出一个文件选择对话框,以便于我们可以选择本地文件夹内的图片,当我们选择完成,获取本地图片路径,同时将图片显示在img元素内。 我们在前文已经说过,将使用dialog这个插件。
import { open,message,save } from '@tauri-apps/plugin-dialog'
如上,open是打开文件对话框,message是消息对话框,save是保存文件对话框。 我们先使用open:
//加载图片按钮
openfilebtn.addEventListener('click',async ()=>{
const file=await open({
multiple:false,
directory:false,
});
if (file){
srcfilepathp.innerHTML = "源图片路径:";
srcfilepathp.innerHTML += file;
const url=convertFileSrc(file);
srcurl=url;
img1.src=url;
} else {
await message('没有选择源文件', { title: 'Tauri', kind: 'info' });
}
});
如上,我们使用open对话框获取文件路径,但是此处获取的是本地图片的绝对路径,是无法直接在前端打开的,因为浏览器是不能直接显示本地路径文件的。所以我们需要对路径进行转换,我们可以使用tauri提供的convertFileSrc这个函数:
import { invoke,convertFileSrc } from '@tauri-apps/api/core'
这个函数与invoke一样,属于tauri的核心api,可以查看convertFileSrc的源码解释,它的作用就是将文件路径转为web中可以使用的格式。但是仅仅转换格式还不够,出于安全考虑,如果你想要真正在web中显示本地图片,还需要进行设置,打开tauri.conf.json文件:
"security": {
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
"assetProtocol": {
"enable": true,
"scope": []
}
}
如上,找到security这个设置项,修改csp的值,此处的
"default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost"
是官方推荐的设置,另外还要添加assetProtocol,将enable设置为true。否则运行时,那怕路径是正确的,也无法正确显示图片,会报错。
看一下演示:
3、保存图片
事实上,对于本地文件的操作,tauri提供了一个fs插件库,但是这个库因为安全问题,对于路径的使用有限制:
也就是说fs库在对文件进行操作时,需要在特定的路径下进行,如果要自定义,是无法直接实现的,关于这个问题,事实上是有争论的,在官方的github issue中有讨论,感兴趣的可以去看看: github.com/tauri-apps/… fs这个库提供了readFile和writeFile这样的读写文件的函数,但是他们都要基于一个基本目录,而这个基本目录有哪些呢:
declare enum BaseDirectory {
Audio = 1,
Cache = 2,
Config = 3,
Data = 4,
LocalData = 5,
Document = 6,
Download = 7,
Picture = 8,
Public = 9,
Video = 10,
Resource = 11,
Temp = 12,
AppConfig = 13,
AppData = 14,
AppLocalData = 15,
AppCache = 16,
AppLog = 17,
Desktop = 18,
Executable = 19,
Font = 20,
Home = 21,
Runtime = 22,
Template = 23
}
如上,举个例子,如果你选择基本路径为Public,那么你能访问的路径就是(windows系统):C:\Users\Public
下的文件。而且还需要启用权限,打开src-tauri/capabilities/default.json文件(这是初始化tauri时自动生成的),配置permissions,添加fs的权限:
{
"identifier":"fs:allow-app-read-recursive",
"allow":[{"path":"$PUBLIC/*"}]
},
上面权限的意思是,允许对设置的文件夹进行完整的读取访问。详细的权限可以参看tauri的官方手册:
此处只是简单说明,但我们并不希望使用这种方式,因为它限制了文件路径,无法自定义任意路径。所以我们需要换一种方式,使用rust来读取本地文件,然后把读取的文件内容再传递给前端调用,同样,也可以将前端获取的数据,传递给rust来保存到本地文件。 所以,我们修改rust文件lib.rs:
#[tauri::command]
fn read_file(path:std::path::PathBuf) ->Vec<u8> {
std::fs::read(path).unwrap()
}
#[tauri::command]
fn write_file(path:std::path::PathBuf,data:Vec<u8>) {
std::fs::write(path, data).unwrap()
}
如上,我们添加了两个函数,通过使用rust的标准文件读写库fs来对文件进行操作,以读取文件为例,通过传入文件路径,使用std::fs::read读取文件内容,然后返回。然后通过invoke在前端调用,同样地,如果我们要保存文件,如图片,也是一样的逻辑。
const response=await fetch(srcurl);
const blob=await response.blob();
const buffer=await blob.arrayBuffer();
const imgdata=new Uint8Array(buffer);
await invoke('write_file',{path:file,data:imgdata})
.then(
await message('保存成功', { title: 'Tauri', kind: 'info' })
)
如上,我们在javascript中通过图片的url获取图片的原始数据,也就是字节数组,然后我们通过invoke将文件路径和字节数组传给rust的函数,rust端使用std::fs::write写入文件。