网站与公众号的区别,无锡做装修网站,wap静态模板,网站建设公司网站源码项目目标
主要的目的是学习tauri。
流程
1、搭建项目
2、简单的在项目使用leaflet
3、打包
准备项目
环境准备
废话不多说#xff0c;直接开始
需要有准备能运行Rust的环境和Node#xff0c;对于Rust可以参考下面这位大佬的文章#xff0c;Node不必细说。
Rust 和…项目目标
主要的目的是学习tauri。
流程
1、搭建项目
2、简单的在项目使用leaflet
3、打包
准备项目
环境准备
废话不多说直接开始
需要有准备能运行Rust的环境和Node对于Rust可以参考下面这位大佬的文章Node不必细说。
Rust 和 Cargo 安装指南-CSDN博客https://blog.csdn.net/qq_44154915/article/details/139365116
建立项目
进入tauri2
官网https://tauri.app/start/ 笔者将使用pnpm建立项目项目名称为ttvvl
如下 使用VSCode或者WebStorm打开项目
安装依赖
pnpm install 运行
pnpm tauri dev然后就在compiling 这笔者感到疑惑可能是第一次运行搞了许久 运行结果 没有问题。
看一下文件夹的属性 6个G有点大。
如果以
pnpm run dev
点击按钮会出现错误 观察上面的代码我们可以发现这个是invoke好像是个方法。第一个参数是个字符串greet
第二个参数是个对象属性是name。
同时找到src-tauri/src/lib.rs中的代码如下 fn 是Rust的关键字相当与定义了一个函数函数名叫greet参数name
format是个格式化字符串的函数
其他不是很懂但我们可以把这段代码给deepseek问一问
解答如下 从这里我们可以得到关键信息——被这个宏标记的函数能被前端调用。
因为Rust函数的函数名叫greet而invoke的第一个参数是字符串greet我们可以很容易猜测
二者必然有关系我们可以检验一下
修改函数名为greets运行
发现报错了 我们可以看到下面还有一个函数
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {tauri::Builder::default().plugin(tauri_plugin_opener::init()).invoke_handler(tauri::generate_handler![greet]).run(tauri::generate_context!()).expect(error while running tauri application);
}可以问一问deepseek 原来下面那个函数是入口函数invoke_handler是注册器因此把中括号里面的greet改成greets
应该可以运行了每次都要编译有点慢。
如果点击按钮下面应该不会出现字 事实确实如此。
那么推断很有可能是正确的那么我们把字符串greet改成greets就可以出现字了 事实确实如此笔者明白了。
再次观察Rust函数greets返回值是String 而返回格式化后的字符串对于App.vue中的TypeScript代码打印出greetMsg.value的值 在运行后的的tauri中可以打开开发者工具打印结果如下。 既然如此感觉明白了invoke作为Rust与TypeScript交互的关键函数。
安装leaflet有关依赖
pnpm install leaflet leaflet-geoman-free
因为使用typescipt还需要安装类型定义包 pnpm install --save types/leaflet.pm types/leaflet
显示地图
这也是很麻烦的事情。
安装vue-router
pnpm install vue-router
新建一些目录和文件如下 在router目录的index.ts中 import {createRouter, createWebHashHistory, Router, RouteRecordRaw, RouterOptions} from vue-router;
const routes:RouteRecordRaw[][{path:/,redirect:/map,children:[{path:map,component:()import(../views/Map.vue)}]}
]
const options:RouterOptions{history:createWebHashHistory(),routes
}
const router:RoutercreateRouter(options)
export default router
暂时先这么写 main.ts的内容 import { createApp } from vue;
import App from /App.vue;
import router from /router;
import * as L from leaflet;
import leaflet/dist/leaflet.css;
import geoman-io/leaflet-geoman-free;
import geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css;
const appcreateApp(App);
app.use(router);
app.config.globalProperties.$L L;app.mount(#app);
将L作为Vue的全局属性并且引入插件leaflet-geoman-free及相关样式
关于符号不多解释。 在constant/data.ts中 export const MAP_URL https://tile-a.openstreetmap.fr/hot/{z}/{x}/{y}.png;这个是地图瓦片的URL。通过它就能访问地图。 在utils/Map.ts中 import {MAP_URL} from /constant/data.ts;
import L from leaflet;
const GetMap (control: anynull, //控件language:any zh //语言
): L.Map {const options: L.MapOptions {center: [30.6667, 104.0667], //中心点minZoom: 0,maxZoom: 30,zoom: 10,zoomControl: true,doubleClickZoom: true,attributionControl: true,dragging: true,boxZoom: true,scrollWheelZoom: true,zoomSnap: 0.5,};const map L.map(map, options);L.tileLayer(MAP_URL, {attribution: ,}).addTo(map);map.pm.addControls(control); map.pm.setLang(language);return map;
};
export default GetMap;
如果不写
import L from leaflet Vue: L refers to a UMD global, but the current file is a module. Consider adding an import instead. 在views/Map.vue中 script setup langts
import GetMap from /utils/Map.ts;
import { onMounted } from vue;
let map: any null
onMounted(() {map GetMap();map.on(click, (e: any) {console.log(e);});
})
/scripttemplate
div idmap/div
/templatestyle scoped
#map {height: 100vh;width: 200vh;
}
/style
运行结果如下 地图定位到成都市。
显示控件
可以查看leaflet-geoman-free文档
Introduction | Documentation for Leaflet-Geomanhttps://geoman.io/docs/leaflet在constant/data.ts中
export const MAP_CONTROL{
}
可以写一些设置但有默认选项就不改了。
在views/Map.vue中导入MAP_CONTROL作为GetMap的参数
map GetMap(MAP_CONTROL);使用控件运行结果如下 搞点小操作1——计算两点之间的距离
操作过程
点两个点通过invoke函数发送经纬度到rust函数返回结果
计算距离函数
使用Haversine 公式
haversine公式计算两经纬度点距离-CSDN博客https://blog.csdn.net/spatial_coder/article/details/116605509使用Rust实现,src-tauri/src/lib.rs的代码如下
const EARTH_RADIUS_KM: f64 6371.0; // 地球半径单位公里
#[tauri::command]
// 计算两个经纬度点之间的距离单位公里
fn haversine_distance(start:[f64;2],end:[f64;2]) - f64 {// 将经纬度从度数转换为弧度let lat1_rad start[0].to_radians();let lon1_rad start[1].to_radians();let lat2_rad end[0].to_radians();let lon2_rad end[1].to_radians();// 经纬度差值let dlat lat2_rad - lat1_rad;let dlon lon2_rad - lon1_rad;// Haversine 公式let a (dlat / 2.0).sin().powi(2) lat1_rad.cos() * lat2_rad.cos() * (dlon / 2.0).sin().powi(2);let c 2.0 * a.sqrt().atan2((1.0 - a).sqrt());// 计算距离EARTH_RADIUS_KM * c
}#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {tauri::Builder::default().plugin(tauri_plugin_opener::init()).invoke_handler(tauri::generate_handler![haversine_distance]).run(tauri::generate_context!()).expect(error while running tauri application);
}以公里作为单位
对两个点marker的思考
点了两个点marker我们通过leaflet和有关插件的某些事件可以获得这两个点的属性
我们可以修改这些属性。
那应该然后修改
可以尝试使用tauri的多窗口。
marker位于父窗口发送信号到子窗口在子窗口进行修改返回父窗口。
父子窗口以及发送信号 关于窗口的配置 src-tauri/capabilities/default.json {$schema: ../gen/schemas/desktop-schema.json,identifier: default,description: Capability for the main window,windows: [main,marker-name],permissions: [core:default,opener:default,core:window:allow-hide,core:window:allow-show]
}在子窗口添加一个路由 src/router/index.ts {path:/name,component:()import(../views/name.vue)} src-tauri/tauri.conf.json windows: [{title: ttvvl,label: main,width: 800,height: 600},{title: 点位窗口,width: 400,height: 300,label: marker-name,resizable: true,parent: main,visible: false,url:#/name,decorations: false,center: true}],
参数的具体含义可以参考官网或者deekssek
{windows: [{label: main, // 窗口的唯一标识符title: My Tauri App, // 窗口标题width: 800, // 窗口宽度height: 600, // 窗口高度x: null, // 窗口初始水平位置null 表示由系统决定y: null, // 窗口初始垂直位置null 表示由系统决定minWidth: null, // 窗口最小宽度minHeight: null, // 窗口最小高度maxWidth: null, // 窗口最大宽度maxHeight: null, // 窗口最大高度resizable: true, // 是否允许调整窗口大小fullscreen: false, // 是否全屏focus: true, // 窗口是否在创建时获得焦点visible: true, // 窗口是否可见decorations: true, // 是否显示窗口装饰标题栏、边框等alwaysOnTop: false, // 窗口是否始终置顶maximized: false, // 窗口是否最大化transparent: false, // 窗口背景是否透明center: true, // 窗口是否居中theme: system, // 窗口主题system、light、darkurl: index.html, // 窗口加载的页面路径fileDropEnabled: true, // 是否启用文件拖放功能skipTaskbar: false, // 是否在任务栏中显示窗口shadow: true, // 是否显示窗口阴影acceptFirstMouse: false, // 是否接受首次鼠标点击事件tabbingIdentifier: null, // macOS 标签页标识符titleBarStyle: visible, // macOS 标题栏样式visible、transparent、overlayhiddenTitle: false // 是否隐藏标题栏标题}]
}
父窗口 src/views/Map.vue script setup langts
//ts-nocheck
import GetMap from /utils/Map.ts;
import {MAP_CONTROL} from /constant/data.ts;
import {invoke} from tauri-apps/api/core;
import {emit,listen} from tauri-apps/api/event;
import {onMounted, ref} from vue;
import WindowManager from /utils/window;const windowManager new WindowManager();
let startNamerefString(起点);
let endNamerefString(终点);
let markerWindow,mainWindow,markerIndexnull;
let map: any null
let markerList: L.Marker[] [];
let distancerefnumber(0);listen(message-to-main-window, (event){ //监听消息let payload:anyevent.payloadif(payload.close){windowManager.closeWindow(marker-name)mainWindow.show()}if(markerIndexnull){return}if(markerIndex0){startName.value起点payload.tooltipContent}else{endName.value终点payload.tooltipContent}markerList[markerIndex].bindTooltip(payload.tooltipContent)
});
async function sendMsg(tooltipContent:String,nowClickMarkerId:Number) { //发送消息markerWindowawait windowManager.getWindowByLabel(marker-name) //子窗口mainWindowawait windowManager.getWindowByLabel(main) //父窗口if(markerWindow) {mainWindow.hide()markerWindow.show();markerIndexmarkerList.findIndex((marker:L.Marker)marker._leaflet_idnowClickMarkerId)emit(message-to-second-window, { //发送消息 参数lat: markerList[0].getLatLng().lat,lng: markerList[0].getLatLng().lng,markerIndex:markerIndex,tooltipContent:tooltipContent})}
}async function get_distance(){let startmarkerList[0].getLatLng(); //开始点let endmarkerList[1].getLatLng(); //终点let startArray[start.lat,start.lng];let endArray[end.lat,end.lng];distance.value await invoke(haversine_distance, { //发送给rust tauristart: startArray,end: endArray});
}
onMounted(() {// openWindow()map GetMap(MAP_CONTROL);map.on(pm:create, (e: any) { // map create事件let marker:L.Marker e.marker;marker.on(click,(e){ // marker的点击事件let nowClickMarkerIde.target._leaflet_id // 获取marker的idsendMsg(e.target.getTooltip(),nowClickMarkerId) // 发送消息})markerList.push(marker);let length markerList.length;if(length2){ // 起始点 和终点get_distance() //获取距离}else if(length2){markerList[0].remove(); //移除第一个点markerList.shift();get_distance() // 获取距离}})})
/scripttemplate
div idmapdiv iddistance-text v-ifmarkerList.length2从{{startName}}到{{endName}}的距离为/divdivbutton typebutton idshow {{distance}}/button/divdiv idunit公里/div/div
/templatestyle scoped
#map {margin: 0 auto;width: 100%;height: 900px;
}
#show {max-width: 200px;max-height: 50px;padding: 10px;background-color: blue;color: white;text-align: center;position: absolute;top: 100px;right: 10px;z-index: 999;border-radius: 20px;
}
#distance-text {max-width: 200px;max-height: 50px;padding: 10px;background-color: #af8433;color: white;text-align: center;position: absolute;top: 10px;right: 10px;z-index: 999;border-radius: 20px;
}
#unit {max-width: 200px;max-height: 50px;padding: 10px;background-color: #af8433;color: white;text-align: center;position: absolute;top: 150px;right: 10px;z-index: 999;border-radius: 20px;
}/style
子窗口 src/views/Name.vue script setup
import {listen,emit} from tauri-apps/api/event;
import {onMounted, ref} from vue;
let lat ref(0);
let lng ref(0);
let markerIndex ref(0); // 0 替换成起点 1 替换成终点
let inputValue ref();
let oldTooltipnullconst handleClicked () {emit(message-to-main-window, {tooltipContent:inputValue.value,close: true,});
}
const handleCancel () {emit(message-to-main-window, {tooltipContent:oldTooltip,close: true,});
}
onMounted(() {listen(message-to-second-window, (event) {let payload event.payload;lat.value payload.lat;lng.value payload.lng;markerIndex.value payload.markerIndex;inputValue.valuepayload.tooltipContent;oldTooltippayload.tooltipContent});}
)/script
templatediv data-tauri-drag-region classtitlebar h1{{markerIndex0?起点:终点}}/h1div v-iflat0纬度{{lat }}/divdiv v-iflng0经度{{lng }}/divdiv名称br/input v-modelinputValue/input/divbr/button typebutton clickhandleClicked classsure确定/buttonbutton typebutton clickhandleCancel classcancel取消/button/div
/template
style
.sure{padding: 10px ;margin-right: 10vw;background-color: #5174ff;
}
.cancel{padding: 10px ;margin-right: 10vw;background-color: #ffb43e;}/style
窗口管理 src/utils/window.ts import { getAllWebviewWindows } from tauri-apps/api/webviewWindow;export class WindowManager {async getWindowByLabel(label: string) {const windows await getAllWebviewWindows();return windows.find((win) win.label label);}async closeWindow(label: string) {const window await this.getWindowByLabel(label);if (window) {window.hide();}}
}export default WindowManager;
运行结果 运行结果倒是没问题代码写得不行算了。不管那些。
打包
本地打包
打包成exe命令
pnpm tauri build
因为笔者不是第一次打包如果是第一次打包需要下载一些东西笔者就不展示了可参考下面这位大佬的过程。
从零开始的 Tauri 开发 打包成 exe 【Windows 平台】_tauri 打包-CSDN博客https://blog.csdn.net/u010263423/article/details/136006546对于leaflet打包会出现一个bug解决过程可看这篇文章
解决VueVite打包后Leaflet的marker图标不显示的问题_leaflet l.marker 没有-CSDN博客https://blog.csdn.net/qq_63401240/article/details/139972362?spm1001.2014.3001.5502总之。打包结果 打包运行结果
可以。 上传到github并打包
在github上通过工作流打包
工作流代码参考下面这位大佬
tauri使用github的action自动发布release让别人也可以看到下载链接_tauri github action-CSDN博客https://blog.csdn.net/weixin_44786530/article/details/140904091在大佬的基础上做出小的修改 修改了工件的版本其他改动不大
运行结果如下 发现ubuntu出错了。笔者没有深究。
笔者再次打包只选择了window系统结果如下 项目地址
qe-present/ttvvlhttps://github.com/qe-present/ttvvl
总结
简单地使用了tauri使用了信号通信多窗口打包。