Tauri学习笔记:0

系列文章目录

vue-pure-admin (opens new window)是一款开源完全免费且开箱即用的中后台管理系统模版。完全采用 ECMAScript 模块(ESM)规范来编写和组织代码,使用了最新的 Vue3、Vite、Element-Plus、TypeScript、Pinia、Tailwindcss 等主流技术开发。(Pure Admin 保姆级文档

#在线预览


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

关键词

  • HTML
  • CSS
  • JavaScript
  • Vite
  • Vue
  • Tauri
  • TypeScript
  • Rust

一、环境配置

npm install -g pnpm
  • Tauri快速开始-Vite
    • pnpm
    • TypeScript
  • VSCode安装插件
    • Volar
    • Tauri
    • rust-analyzer
  • VSCode按F5生成并配置launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "request": "launch",
            "name": "pnpm",
            "command": "pnpm run tauri dev",
            "type": "node-terminal",
        }
    ]
}

二、功能实现

Base工程

  1. 克隆tauri-pure-admin到本地;
  2. VSCode打开tauri-pure-admin-main文件夹;
    a. 执行pnpm install
    b. 执行pnpm browser:dev或者pnpm dev;

以上两步是确认相关配置是ok的,后续每一个功能的实现、测试均基于此Base工程;


  • 功能1:实现读取本地数据加载到ECharts
  1. 上传文件功能组件:tauri-pure-admin-main\src\components\MyComponents\FileUploader.vue
<template>
  <el-upload ref="upload" class="upload-demo" action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
    :limit="1" :on-exceed="handleExceed" :auto-upload="false" style="margin-top: 5px;">
    <template #trigger>
      <el-button type="primary">选择文件</el-button>
    </template>
    <el-button class="ml-3" type="success" @click="submitUpload">
      上传
    </el-button>
    <template #tip>
      <div class="el-upload__tip text-red">
        限制上传一个文件,且不超过 2MB
      </div>
    </template>
  </el-upload>
</template>

<script setup lang="ts">
import { ref, defineEmits } from 'vue'
import { genFileId } from 'element-plus'
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'

const upload = ref<UploadInstance>()

const handleExceed: UploadProps['onExceed'] = (files) => {
  upload.value!.clearFiles()
  const file = files[0] as UploadRawFile
  file.uid = genFileId()
  upload.value!.handleStart(file)
}

const submitUpload = () => {
  upload.value!.submit();
  runFunction();
}

const emit = defineEmits(['function'])

function runFunction() {
  emit('function')
}

</script>
  1. 修改主页面:tauri-pure-admin-main\src\views\welcome\index.vue
<script setup lang="ts">
import { hasAuth, getAuths } from "@/router/utils";

defineOptions({
  name: "Welcome"
});

import { ECharts, EChartsOption, init } from 'echarts';
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
import FileUploader from '@/components/MyComponents/FileUploader.vue';

const count = ref(0);
var arr = ref([] as number[]);
arr.value.push(1);
arr.value.push(5);
arr.value.push(4);
arr.value.push(3);
arr.value.push(3);
arr.value.push(count.value);

const option = {
  title: {
    text: 'ECharts 入门示例'
  },
  tooltip: {},
  xAxis: {
    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
  },
  yAxis: {},
  series: [{
    name: '销量',
    type: 'bar',
    data: [50, 20, 36, 10, 10, 50]
  }]
};

// 定义props
interface Props {
  width?: string;
  height?: string;
  option?: EChartsOption;
}

const props = withDefaults(defineProps<Props>(), {
  width: '100%',
  height: '80%',
  option: () => ({
    title: {
      text: 'ECharts 入门示例'
    },
    tooltip: {},
    xAxis: {
      data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
    },
    yAxis: {},
    series: [{
      name: '销量',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 50]
    }]
  })
});

const myChartsRef = ref<HTMLDivElement>();
let myChart: ECharts;

let timer: string | number | NodeJS.Timeout | undefined;

const initChart = (): void => {
  if (myChart !== undefined) {
    myChart.dispose();
  }
  myChart = init(myChartsRef.value as HTMLDivElement);
  myChart?.setOption(props.option, true);
};

const resizeChart = (): void => {
  timer = setTimeout(() => {
    if (myChart) {
      myChart.resize();
    }
  }, 500);
};

onMounted(() => {
  initChart();
  window.addEventListener('resize', resizeChart);
});

onBeforeUnmount(() => {
  window.removeEventListener('resize', resizeChart);
  clearTimeout(timer);
  timer = 0;
});

watch(
  props.option,
  () => {
    initChart();
  },
  {
    deep: true
  }
);

const update = () => {
  count.value++;
  arr.value.pop();
  arr.value.push(count.value);
  option.series[0].data = arr.value;
  myChart.setOption(option);
};

</script>

<template>
  <div>
    <FileUploader @function="update" />
  </div>
  <div ref="myChartsRef" :style="{ height: height, width: width }" :option="option" />
</template>
  1. 效果图

在这里插入图片描述

  • 功能2:动态添加按钮
  1. 添加按钮组件:tauri-pure-admin-main\src\components\MyComponents\AddButtons.vue
<template>
    <div>
        <!-- 遍历props中的buttons数组,创建一个按钮列表 -->
        <el-button v-for="(button, index) in props.buttons" :key="index" @click="button.onClick">
            {{ button.text }}
        </el-button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

// 定义Button接口,描述按钮的结构
export interface Button {
    text: string; // 按钮显示的文本
    onClick: () => void; // 点击按钮时执行的函数
}

// 使用defineProps定义组件接收的props
const props = defineProps({
    buttons: {
        // buttons属性为一个Button类型数组,是必传属性
        type: Array as () => Button[], 
        required: true,
    },
})
</script>
  1. 页面加载:tauri-pure-admin-main\src\components\MyComponents\AddButtons.vue
<script setup lang="ts">
import { hasAuth, getAuths } from "@/router/utils"; // 引入权限控制相关工具函数

defineOptions({
  name: "Welcome" // 设置当前组件的名称
});

import { ECharts, EChartsOption, init } from 'echarts'; // 引入echarts相关模块
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'; // 引入Vue 3的响应式和生命周期函数
import FileUploader from '@/components/MyComponents/FileUploader.vue'; // 引入文件上传组件
import AddButtons from "@/components/MyComponents/AddButtons.vue"; // 引入按钮组件
import { Button } from "@/components/MyComponents/AddButtons.vue"; // 引入按钮组件中的Button类型

// 初始化echarts使用的数据和配置
const count = ref(0);
var arr = ref([] as number[]); // 初始化一个数字类型的数组
arr.value.push(1);
arr.value.push(5);
arr.value.push(4);
arr.value.push(3);
arr.value.push(3);
arr.value.push(count.value);

const option = {
  title: {
    text: 'ECharts 入门示例'
  },
  tooltip: {},
  xAxis: {
    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
  },
  yAxis: {},
  series: [{
    name: '销量',
    type: 'bar',
    data: [50, 20, 36, 10, 10, 50]
  }]
};

// 定义组件的props
interface Props {
  width?: string; // 组件宽度
  height?: string; // 组件高度
  option?: EChartsOption; // echarts配置项
}

// 使用withDefaults来设置props的默认值
const props = withDefaults(defineProps<Props>(), {
  width: '100%',
  height: '80%',
  option: () => ({
    title: {
      text: 'ECharts 入门示例'
    },
    tooltip: {},
    xAxis: {
      data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
    },
    yAxis: {},
    series: [{
      name: '销量',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 50]
    }]
  })
});

// 初始化echarts图表的引用
const myChartsRef = ref<HTMLDivElement>();
let myChart: ECharts;

let timer: string | number | NodeJS.Timeout | undefined;

// 初始化图表和监听窗口大小改变来调整图表大小
const initChart = (): void => {
  if (myChart !== undefined) {
    myChart.dispose();
  }
  myChart = init(myChartsRef.value as HTMLDivElement);
  myChart?.setOption(props.option, true);
};

const resizeChart = (): void => {
  timer = setTimeout(() => {
    if (myChart) {
      myChart.resize();
    }
  }, 500);
};

// 生命周期钩子:组件挂载时初始化图表,并添加窗口大小改变的监听
onMounted(() => {
  initChart();
  window.addEventListener('resize', resizeChart);
});

// 生命周期钩子:组件卸载前移除监听并清理资源
onBeforeUnmount(() => {
  window.removeEventListener('resize', resizeChart);
  clearTimeout(timer);
  timer = 0;
});

// 监听props中的option变化,以重新初始化图表
watch(
  props.option,
  () => {
    initChart();
  },
  {
    deep: true
  }
);

// 更新图表数据的函数
const update = () => {
  count.value++;
  arr.value.pop();
  arr.value.push(count.value);  
  option.series[0].data = arr.value;
  myChart.setOption(option); // 更新图表配置
};

// 初始化一组按钮,并定义其点击事件来更新图表
const buttons = ref<Button[]>([]);
var i;
for (i = 0; i < 10; i++) {
  const newButton: Button = {
    text: `按钮 ${i}`,
    onClick: () => {
      update();
    },
  };
  buttons.value.push(newButton);
}

</script>

<template>
  <div>
    <AddButtons :buttons="buttons" /> <!-- 显示添加的按钮 -->
    <FileUploader @function="update" /> <!-- 文件上传组件,上传后更新图表 -->
  </div>
  <div ref="myChartsRef" :style="{ height: height, width: width }" :option="option" /> <!-- echarts图表容器 -->
</template>
  1. 效果图
    在这里插入图片描述
  • 功能3:Tauri打开文件对话框
  1. tauri2.0.0-bete.19支持文件对话框,实现跟tauri1有区别,见官方说明
  2. 要点:
    a. 初始化项目时要选择--beta版本,这才是tauri2.0.0,cargo create-tauri-app --beta
    b. 与官方文档不同,这里与前端交互的rust函数参数要添加tauri::Window类型参数,从而函数体可以使用window.dialog()使用当前窗口句柄创建对话框;
  3. 完整代码:
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri_plugin_dialog::DialogExt;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(window: tauri::Window, name: &str) -> String {
    window.dialog().file().pick_file(|file_path| {
        // do something with the optional file path here
        // the file path is `None` if the user closed the dialog
        println!("{:?}", file_path);
    });
    format!("Hello, {}! You've been greeted from Rust!", name)
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

  1. 效果图
  • 功能4:Rust通过管道多线程传递变量
  1. 代码:
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri_plugin_dialog::DialogExt;
// 1. 引入包
use std::sync::mpsc;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(app: tauri::AppHandle, name: &str) -> String {
	// 2. 创建管道
    let (tx, rx) = mpsc::channel();

    app.dialog().file().pick_file(move |file_path| match file_path {
        Some(file_path) => {
            println!("{:?}", file_path.path);
            // 3. 管道发送
            let _ = tx.send(file_path.path);
        },
        None => println!("Canceled"),
    });
    // 4. 管道接受
    let res = rx.recv().unwrap();
    format!("Hello, {:?}! You've been greeted from Rust!", res)
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  1. 效果图: 在这里插入图片描述
  • 功能5:rust打开对话框读取csv到dict
  1. Cargo.toml
csv = "1.1.1"
polars = { version = "0.33.2", features = ["lazy", "ndarray"] }
  1. csv_to_dict.rs
use polars::prelude::*;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};

pub fn read_csv_to_dict(
    file_path: &str,
) -> Result<HashMap<String, Vec<Option<f64>>>, Box<dyn std::error::Error>> {
    // 创建一个文件缓冲读取器
    let file = File::open(file_path)?;
    let reader = BufReader::new(file);

    // 使用CsvReader读取CSV文件,第一行被视为列名
    let df = CsvReader::new(reader)
        .has_header(true) // 指定第一行是列名
        .finish()?;

    let mut dict = HashMap::new();
    for column in df.get_columns() {
        let column_name = column.name();
        let column_values = df
            .column(column.name())
            .unwrap()
            .as_any()
            .downcast_ref::<Float64Chunked>()
            .unwrap()
            .into_iter()
            .map(|v| v)
            .collect::<Vec<_>>();
        dict.insert(column_name.to_string(), column_values);
    }
    Ok(dict)
}
  1. main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod csv_to_dict;
use std::sync::mpsc;
use tauri_plugin_dialog::DialogExt;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(app: tauri::AppHandle, name: &str) -> String {
    let (tx, rx) = mpsc::channel();

    app.dialog()
        .file()
        .pick_file(move |file_path| match file_path {
            Some(file_path) => {
                match csv_to_dict::read_csv_to_dict(file_path.path.to_str().unwrap()) {
                    Ok(data) => {
                        let _ = tx.send(data);
                    }
                    Err(err) => {
                        eprintln!("Error: {}", err);
                    }
                }
            }
            None => println!("Canceled"),
        });
    let res = rx.recv().unwrap();
    for key in res.keys() {
        println!("{:?}", res.get(key).unwrap());
    }
    format!("Hello, {:?}! You've been greeted from Rust!", "123")
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

  • 功能6:serd
  1. csv_to_json.rs
use serde::{Deserialize, Serialize};
use serde_json::{Result, Value};

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: i32,
    city: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Vecs {
    names: Vec<String>,
    rows: Vec<Vec<String>>,
}

pub fn print_json() -> String {
    let person = Person {
        name: String::from("jack"),
        age: 10,
        city: String::from("beijing"),
    };
    let vecs = Vecs {
        names: vec![
            String::from("name"),
            String::from("age"),
            String::from("city"),
        ],
        rows: vec![
            vec![
                String::from("jack"),
                String::from("10"),
                String::from("beijing"),
            ],
            vec![
                String::from("tom"),
                String::from("20"),
                String::from("shanghai"),
            ],
        ],
    };
    let json = serde_json::to_string(&vecs).unwrap();

    println!("{:#?}", json);
    json
}

  1. main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod csv_to_json;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(name: &str) -> String {
   let json = csv_to_json::print_json();
    format!("Hello, {}! You've been greeted from Rust!", json)
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  1. 效果图:
    在这里插入图片描述
  • 功能6:前端vue按钮打开后端tauri对话框选择本地csv文件读取数据传给前端ts得到json对象使用;
  1. Cargo.toml
[package]
name = "demo"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] }

[dependencies]
tauri = { version = "2.0.0-beta", features = [] }
tauri-plugin-shell = "2.0.0-beta"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
csv = "1.1.1"
polars = { version = "0.33.2", features = ["lazy", "ndarray"] }
tauri-plugin-dialog = "2.0.0-beta.7"
  1. Greet.vue
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";

const greetMsg = ref("");
const name = ref("");

async function greet() {
  // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
  greetMsg.value = await invoke("greet", { name: name.value });
  console.log(greetMsg.value);
  let json = JSON.parse(greetMsg.value)
  console.log(json);
  console.log(json["rows"][0]);
}
</script>

<template>
  <form class="row" @submit.prevent="greet">
    <input id="greet-input" v-model="name" placeholder="Enter a name..." />
    <button type="submit">Greet</button>
  </form>

  <p>{{ greetMsg }}</p>
</template>

  1. util.rs
use polars::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{Result, Value};
use std::fs::File;

#[derive(Serialize, Deserialize, Debug)]
struct Vecs {
    names: Vec<String>,
    rows: Vec<Vec<String>>,
}

pub fn read_csv_to_json(file_path: &str) -> Result<String> {
    let df = CsvReader::new(File::open(file_path).unwrap())
        .infer_schema(None)
        .has_header(true)
        .finish()
        .unwrap(); // 读取CSV文件

    let column_names = df
        .get_column_names()
        .iter()
        .map(|name| name.to_string())
        .collect();

    let data: Vec<Vec<_>> = df
        .get_columns()
        .iter()
        .map(|c| c.iter().map(|v| v.to_string()).collect::<Vec<_>>())
        .collect();

    let vecs = Vecs {
        names: column_names,
        rows: data,
    };

    let json = serde_json::to_string(&vecs).unwrap();
    
    Ok(json)
}

  1. main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod util;
use std::sync::mpsc;

use tauri_plugin_dialog::DialogExt;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(app: tauri::AppHandle, name: &str) -> String {
    let (tx, rx) = mpsc::channel();

    app.dialog()
        .file()
        .pick_file(move |file_path| match file_path {
            Some(file_path) => {
                let _ = tx.send(file_path.path);
            }
            None => println!("Canceled"),
        });

    let path = rx.recv().unwrap();

    let json = util::read_csv_to_json(path.to_str().unwrap()).unwrap();

    json
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

  1. 效果图:
    在这里插入图片描述
  2. 常用命令:
cargo creare-tauri-app --beta
//cd src-tauri
cargo add tauri-plugin-dialog
//cd ..
$env:RUST_BACKTRACE=1; pnpm tauri dev
  • 功能7:tauri读取csv数据更新ts的Echarts;
  1. Greet.vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { invoke } from "@tauri-apps/api/core";
import { EChartsOption, ECharts, init } from "echarts";

const greetMsg = ref("");
const name = ref("");

async function greet() {
  greetMsg.value = await invoke("greet", { name: name.value });
  let json = JSON.parse(greetMsg.value);
  option.series[0].data = json["rows"][0].map(Number);
  option.series[0].type = "line";
  myChart.setOption(option);
}

interface Props {
  width?: string;
  height?: string;
  option?: EChartsOption;
}

const props = withDefaults(defineProps<Props>(), {
  width: "100%",
  height: "500%",
  option: () => ({
    title: {
      text: "ECharts 入门示例",
    },
    tooltip: {},
    xAxis: {
      data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
    },
    yAxis: {},
    series: [
      {
        name: "销量",
        type: "bar",
        data: [5, 20, 36, 10, 10, 50],
      },
    ],
  }),
});

const myChartsRef = ref<HTMLDivElement>();
let myChart: ECharts;

let timer: number | undefined;

const initChart = (): void => {
  if (myChart !== undefined) {
    myChart.dispose();
  }
  myChart = init(myChartsRef.value as HTMLDivElement);
  myChart?.setOption(props.option, true);
};

const resizeChart = (): void => {
  timer = setTimeout(() => {
    if (myChart) {
      myChart.resize();
    }
  }, 500);
};

onMounted(() => {
  initChart();
  window.addEventListener("resize", resizeChart);
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", resizeChart);
  clearTimeout(timer);
  timer = 0;
});

watch(
  props.option,
  () => {
    initChart();
  },
  {
    deep: true,
  }
);

const option = {
  title: {
    text: "ECharts 入门示例",
  },
  tooltip: {},
  xAxis: {
    data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
  },
  yAxis: {},
  series: [
    {
      name: "销量",
      type: "bar",
      data: [50, 20, 36, 10, 10, 50],
    },
  ],
};
</script>

<template>
  <form @submit.prevent="greet">
    <input id="greet-input" v-model="name" placeholder="选择csv数据..." />
    <button type="submit">打开...</button>
  </form>
  <p>{{ greetMsg }}</p>
  <p
    ref="myChartsRef"
    :style="{ height: height, width: width }"
    :option="option"
  />
</template>
  1. 效果图:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

总结

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值