{"id":14989478,"url":"https://github.com/kestrela/kestrel-task","last_synced_at":"2025-04-12T01:30:44.521Z","repository":{"id":253205567,"uuid":"833479544","full_name":"kestrela/kestrel-task","owner":"kestrela","description":"微芒计划:一款由tauri构建的待办效率管理工具","archived":false,"fork":false,"pushed_at":"2024-10-24T06:26:47.000Z","size":1290,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T21:12:32.914Z","etag":null,"topics":["koa2","mysql","rust","tauri","tauri-app","vite","vue3","windows"],"latest_commit_sha":null,"homepage":"https://kestrel-task.cn","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kestrela.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-25T06:25:49.000Z","updated_at":"2025-03-06T01:04:49.000Z","dependencies_parsed_at":"2024-09-25T00:34:26.091Z","dependency_job_id":null,"html_url":"https://github.com/kestrela/kestrel-task","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"5c31ddfd4b958fe73d83b77b6adf4f8b1016b09f"},"previous_names":["kestrela/kestrel-task"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kestrela%2Fkestrel-task","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kestrela%2Fkestrel-task/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kestrela%2Fkestrel-task/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kestrela%2Fkestrel-task/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kestrela","download_url":"https://codeload.github.com/kestrela/kestrel-task/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248504195,"owners_count":21115133,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["koa2","mysql","rust","tauri","tauri-app","vite","vue3","windows"],"created_at":"2024-09-24T14:18:25.969Z","updated_at":"2025-04-12T01:30:43.878Z","avatar_url":"https://github.com/kestrela.png","language":"Vue","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\ntheme: smartblue\n---\n## 开源仓库地址 gitee\n\n[Git仓库地址:https://gitee.com/zhanhongzhu/zhanhongzhu.git](https://gitee.com/zhanhongzhu/zhanhongzhu.git)\n\n## 应用地址\n\n[windows应用地址下载 https://kestrel-task.cn](https://kestrel-task.cn/)\n\n## 具体内容\n\n也可以看[🎉使用Tauri+vite+koa2+mysql开发了一款待办效率应用 ](https://juejin.cn/post/7383957030346326026) 这篇文章。\n\n## 💻技术栈\n\n*   **Tauri**: Tauri是一个用于构建现代桌面应用程序的工具，结合了Rust、Vue.js和Web技术，提供了强大的跨平台能力。\n*   **Vue3**: Vue3是流行的JavaScript框架Vue.js的最新版本，具有更好的性能、更好的TypeScript支持和更多的特性。\n*   **Vite5**: Vite是一个现代化的构建工具，Vite5是其最新版本，具有快速的冷启动、热模块替换和原生ES模块支持。\n*   **Koa2**: Koa2是一个基于Node.js的轻量级Web框架，使用异步函数处理中间件，提供了简洁而强大的Web开发体验。\n*   **MySQL**: MySQL是一个流行的关系型数据库管理系统，具有高性能、可靠性和广泛的应用领域，适用于各种规模的应用程序。\n\n## 我的待办\n\n快速添加待办任务，快速查看任务进度，摘要等。新增标签，分类，更好管理待办任务。通过标签、分类筛选待办任务，方便快捷。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/6c4381b677d74783af71a81af662c6c5~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=%2B%2BlwwA%2BeQ8nRVrNsbsBtfgMrMgs%3D)\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/4c5f85d31f9345d581336102ebd413a1~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=YX2vN4vtdz1oO%2FAMfhlzuivNP%2Bk%3D)\n\n## OKR目标管理\n\n我的想法是通过OKR管理系列的任务，这样每完成一个小任务，就可以关闭一个小任务，直观又方便，等到所有关键的小任务都完成了，整个任务也就完成了。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/efe7fbc1bd48491eb0091d2fa8d3374f~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=WwT5P5V9LCBQTXBuY5Eqe5KIkcs%3D)\n\n## 番茄工作法\n\n主要是一个计时的时钟，可以在专注计时的时候，专注地完成某项任务，快捷方便，使用番茄工作法，选择一个待完成的任务，将番茄时间设为25分钟，也可以选择其他的区间，专注工作，中途不允许做任何与该任务无关的事。时刻保持专注。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/5a9cc62780ca4d2d9d3695207291e0a8~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=NxLlTj53%2FzAky7%2BiE2cHKEnO0gY%3D)\n\n## 日历视图\n\n打开日历界面,通过视图的形式查看公历或农历日历下每个日期的待办提醒或任务事项。也可以在日历视图，添加任务。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/6457aa4f3580439d91f967bed1d7fea2~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=gVZhGArunPBIrUHwh76XSmteW7Q%3D)\n\n## MEMO快速记录\n\n为了更好地帮你捕捉想法与灵感，提供了快速记录的输入框。专注记录想法，无需思考标题和排版。控制记录长度，降低记录压力，快速捕捉。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/78e6a240c6d742c191ffa746a4d1fce9~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=RfY86pptYDfJndPfgKH8FRGLE9E%3D)\n\n## 统计功能\n\n展示了每天的待办数，以及每天新增的待办数量。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/57c9bb54167d41b0be4e8cd5e2798611~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=xkUm%2Ft6DO977bGlJEWhqsSQtHxA%3D)\n\n# 展示功能点\n\n## 打包发布版本\n\n脚本命令\n\n```js\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"tauri\": \"tauri\",\n    \"pub\": \"cd build \u0026\u0026 node ./updateVersion.js \u0026\u0026 pnpm tauri build \u0026\u0026 node ./publish.js\"\n  }\n```\n\n```js\nnpm run pub\n```\n\n## 更新版本号以及更新publicKey\n\n```js\n//build/publish.js\nimport fs from 'fs'\n// 读取 tauri.conf.json\nconst tauriConf = JSON.parse(fs.readFileSync('../src-tauri/tauri.conf.json', 'utf8'));\nlet newVersion = tauriConf.package.version; //更新的版本号\n\n// 读取 update.json\nlet updateJson = JSON.parse(fs.readFileSync('update.json', 'utf8'));\n// 更新 update.json version\nupdateJson.version = newVersion;\n\n//获取版本更新的内容\n//签名、版本路径、发版日期\nconst signature =  fs.readFileSync(`../src-tauri/target/release/bundle/msi/kestrel-task_${newVersion}_x64_zh-CN.msi.zip.sig`, 'utf8');\nupdateJson.platforms['windows-x86_64'].signature = signature;\nupdateJson.platforms['windows-x86_64'].url = `https://kestrel-task.cn/kestrel-task_${newVersion}_x64_zh-CN.msi.zip`\nupdateJson.pub_date = new Date();\nfs.writeFileSync('update.json', JSON.stringify(updateJson, null, 2));\n```\n\n## 版本json信息\n\n```js\n//build/update.json\n\n{\n  \"version\": \"1.0.8\",\n  \"notes\": \"kestrel-task\",\n  \"pub_date\": \"2024-03-23T04:23:39.799Z\",\n  \"platforms\": {\n    \"windows-x86_64\": {\n      \"signature\": \"dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVSNVRCUit5Zjc1Y3JLV085djl6eTMza2NqMXFIV0paNkl2ckgrTGZTRm9wcEJwcUlkaTBhM2hvN3pSVkRUZXlTZ2NSejJremg2Vklja041VkZmdGlZZ0hxTGVVM2xlL3dFPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzExMTY3ODE5CWZpbGU6a2VzdHJlbC10YXNrXzEuMC44X3g2NF96aC1DTi5tc2kuemlwCldSWVdwb0dwRU1aQUJ2ckFra2FTMjBkcnZtL0FWU3grd3MzeHZVTDhWRFFFUC9QWkpzdUNvUG9HZXBrVmhWMkoxTkpGc2pkYU5rRHYwcVdHdlk5dkFBPT0K\",\n      \"url\": \"https://kestrel-task.cn/kestrel-task_1.0.8_x64_zh-CN.msi.zip\"\n    }\n  }\n}\n\n```\n\n## 更新tauri.conf.json版本信息\n\n```js\n// build/updateVersion.js\nimport fs from 'fs' // 读取 tauri.conf.json\nconst tauriConf = JSON.parse(fs.readFileSync('../src-tauri/tauri.conf.json', 'utf8'));\nlet version = tauriConf.package.version; //更新的版本号\n\n//更新版本号\nlet versionParts = version.split('.').map(Number);\nversionParts[2] += 1;\nlet newVersion =versionParts.join('.');\n// 更新 tauri.conf.json version\ntauriConf.package.version = newVersion;\nfs.writeFileSync('../src-tauri/tauri.conf.json', JSON.stringify(tauriConf, null, 2));\n```\n\n## 使用tauri的http模块\n\n此处进行简单的封装，接口请求在控制台无法被查看到。如果觉得不方便，完全可以使用axios库。也是可以的。\n\n```js\nimport { http } from \"@tauri-apps/api\";\n\nexport function request(config) {\n  return new Promise((resolve, reject) =\u003e {\n    http\n      .fetch(\"https://kestrel-task.cn\" + config.url, {\n        method: \"POST\",\n        body: http.Body.json(config.data),\n        headers: {\n          Authorization: token,\n        },\n      })\n      .then((res) =\u003e {\n        resolve(res.data.data);\n      })\n      .catch((err) =\u003e {\n        reject(err);\n      });\n  });\n}\n\n```\n\n## http封装get请求\n\n```js\nexport function requestGet(config, d) {\n  let url = d ? config.url : \"https://kestrel-task.cn\" + config.url;\n  return new Promise((resolve, reject) =\u003e {\n    http\n      .fetch(url, {\n        method: \"get\",\n        headers: {\n          Authorization: token,\n        },\n      })\n      .then((res) =\u003e {\n        resolve(d ? res.data : res.data.data);\n      })\n      .catch((err) =\u003e {\n        reject(err);\n      });\n  });\n}\n\n```\n\n## 使用封装的request函数\n\n```js\nexport const login = (data) =\u003e {\n  return request({\n    url: \"/web/login\",\n    method: \"post\",\n    data,\n  });\n};\n```\n\n## 使用WebviewWindow封装公共的窗口\n\n```js\nimport { WebviewWindow } from '@tauri-apps/api/window'\nimport { emit } from '@tauri-apps/api/event'\n\n// 创建新窗口\nexport async function createWin(args) {\n    await emit('win-create', args)\n}\n\n// 获取窗口\nexport async function getWin(label) {\n    return await WebviewWindow.getByLabel(label)\n}\n\n/**\n * @desc 设置窗口\n * @param type {string} 'show'|'hide'|'close'|'min'|'max'|'max2min'|'exit'|'relaunch'\n */\nexport async function setWin(type) {\n    await emit('win-' + type)\n}\n\n// 登录窗口\nexport async function loginWin() {\n    await createWin({\n        label: 'Login',\n        title: '登录',\n        url: '/login',\n        width: 320,\n        height: 420,\n        resizable: false,\n        alwaysOnTop: true,\n    })\n}\n\n// ...\n```\n\n```js\n/**\n * @desc    封装新开多窗体\n */\n\nimport {\n  WebviewWindow,\n  appWindow,\n  getAll,\n  getCurrent,\n} from \"@tauri-apps/api/window\";\nimport { relaunch, exit } from \"@tauri-apps/api/process\";\nimport { emit, listen } from \"@tauri-apps/api/event\";\n\nimport { setWin } from \"./actions.js\";\n\n// 系统参数配置\nexport const windowConfig = {\n  label: null, // 窗口唯一label\n  title: \"\", // 窗口标题\n  url: \"\", // 路由地址url\n  width: 900, // 窗口宽度\n  height: 640, // 窗口高度\n  minWidth: null, // 窗口最小宽度\n  minHeight: null, // 窗口最小高度\n  x: null, // 窗口相对于屏幕左侧坐标\n  y: null, // 窗口相对于屏幕顶端坐标\n  center: true, // 窗口居中显示\n  resizable: true, // 是否支持缩放\n  maximized: false, // 最大化窗口\n  decorations: true, // 窗口是否无边框及导航条\n  alwaysOnTop: false, // 置顶窗口\n};\n\nclass Windows {\n  constructor() {\n    this.mainWin = null;\n  }\n\n  // 获取窗口\n  getWin(label) {\n    return WebviewWindow.getByLabel(label);\n  }\n\n  // 获取全部窗口\n  getAllWin() {\n    return getAll();\n  }\n\n  // 创建新窗口\n  async createWin(options) {\n    const args = Object.assign({}, windowConfig, options);\n\n    // 判断窗口是否存在\n    const existWin = getAll().find((w) =\u003e w.label == args.label);\n    if (existWin) {\n      if (existWin.label.indexOf(\"main\") == -1) {\n        await existWin?.unminimize();\n        await existWin?.setFocus();\n        return;\n      }\n      await existWin?.close();\n    }\n\n    // 创建窗口对象\n    let win = new WebviewWindow(args.label, args);\n\n    // 是否最大化\n    if (args.maximized \u0026\u0026 args.resizable) {\n      win.maximize();\n    }\n\n    // 窗口创建完毕/失败\n    win.once(\"tauri://created\", async () =\u003e {\n      console.log(\"window create success!\");\n    });\n\n    win.once(\"tauri://error\", async () =\u003e {\n      console.log(\"window create error!\");\n    });\n  }\n\n  // 开启主进程监听事件\n  async listen() {\n    // 创建新窗体\n    await listen(\"win-create\", (event) =\u003e {\n      this.createWin(JSON.parse(event.payload));\n    });\n\n    // 显示窗体\n    await listen(\"win-show\", async (event) =\u003e {\n      if (appWindow.label.indexOf(\"main\") == -1) return;\n      await appWindow.show();\n      await appWindow.unminimize();\n      await appWindow.setFocus();\n    });\n\n    // 隐藏窗体\n    await listen(\"win-hide\", async (event) =\u003e {\n      if (appWindow.label.indexOf(\"main\") == -1) return;\n      await appWindow.hide();\n    });\n\n    // 退出应用\n    await listen(\"win-exit\", async (event) =\u003e {\n      setWin(\"logout\");\n      await exit();\n    });\n\n    // 重启应用\n    await listen(\"win-relaunch\", async (event) =\u003e {\n      await relaunch();\n    });\n\n    // 主/渲染进程传参\n    await listen(\"win-setdata\", async (event) =\u003e {\n      await emit(\"win-postdata\", JSON.parse(event.payload));\n    });\n  }\n}\n\nexport default Windows;\n\n```\n\n## 封装Echart组件，便于使用\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/c58317a1cff24d27a5a4f4bfd69556bb~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=WHILRxvviLoW2uizwYre9kDcXyw%3D)\n\n```js\n\u003ctemplate\u003e\n    \u003cdiv ref=\"MyEcharts\" :style=\"{ height: height, width: width }\"\u003e\u003c/div\u003e\n  \u003c/template\u003e\n  \n  \u003cscript\u003e\n  import * as echarts from 'echarts'\n  import T from './echarts-theme-T.js'\n  echarts.registerTheme('T', T)\n  const unwarp = obj =\u003e obj \u0026\u0026 (obj.__v_raw || obj.valueOf() || obj)\n  export default {\n      ...echarts,\n      name: 'Charts',\n      props: {\n          // 高度\n          height: { type: String, default: '100%' },\n          // 宽度\n          width: { type: String, default: '100%' },\n          // 是否无数据\n          nodata: { type: Boolean, default: false },\n          // 配置项\n          option: { type: Object, default: () =\u003e {} }\n      },\n      data() {\n          return {\n              isActivat: false,\n              myChart: null,\n              MyEcharts:null\n          }\n      },\n      watch: {\n          option: {\n              deep: true,\n              handler(v) {\n                  unwarp(this.myChart).setOption(v)\n              }\n          }\n      },\n      computed: {\n          myOptions: function() {\n              return this.option || {}\n          }\n      },\n      activated() {\n          if (!this.isActivat) {\n              this.$nextTick(() =\u003e {\n                  this.myChart.resize()\n              })\n          }\n      },\n      deactivated() {\n          this.isActivat = false\n      },\n      mounted() {\n          this.isActivat = true\n          this.$nextTick(() =\u003e {\n              this.draw()\n          })\n      },\n      methods: {\n          draw() {\n              const myChart = echarts.init(this.$refs.MyEcharts, 'T')\n              myChart.setOption(this.myOptions)\n              this.myChart = myChart\n              window.addEventListener('resize', () =\u003e myChart.resize())\n          }\n      }\n  }\n  \u003c/script\u003e\n```\n\n## echart主题模块\n\n```js\n//echarts-theme-T.js\nconst T = {\n    color: ['#409EFF', '#36CE9E', '#f56e6a', '#626c91', '#edb00d', '#909399'], // 颜色数组\n    grid: { // 网格\n        left: '3%', // 左边距\n        right: '3%', // 右边距\n        bottom: '10', // 下边距\n        top: '40', // 上边距\n        containLabel: true // 包含标签\n    },\n    legend: { // 图例\n        textStyle: { // 文本样式\n            color: '#999' // 颜色\n        },\n        inactiveColor: 'rgba(128,128,128,0.4)' // 不活跃颜色\n    },\n    categoryAxis: { // 类别轴\n        axisLine: { // 轴线\n            show: true, // 显示\n            lineStyle: { // 线条样式\n                color: 'rgba(128,128,128,0.2)', // 颜色\n                width: 1 // 宽度\n            }\n        },\n        axisTick: { // 刻度线\n            show: false, // 不显示\n            lineStyle: { // 线条样式\n                color: '#000' // 颜色\n            }\n        },\n        axisLabel: { // 轴标签\n            color: '#999' // 颜色\n        },\n        splitLine: { // 分隔线\n            show: false, // 不显示\n            lineStyle: { // 线条样式\n                color: ['#eee'] // 颜色\n            }\n        },\n        splitArea: { // 分隔区域\n            show: false, // 不显示\n            areaStyle: { // 区域样式\n                color: ['rgba(255,255,255,0.01)', 'rgba(0,0,0,0.01)'] // 颜色\n            }\n        }\n    },\n    valueAxis: { // 数值轴\n        axisLine: { // 轴线\n            show: false, // 不显示\n            lineStyle: { // 线条样式\n                color: '#999' // 颜色\n            }\n        },\n        splitLine: { // 分隔线\n            show: true, // 显示\n            lineStyle: { // 线条样式\n                color: 'rgba(128,128,128,0.2)' // 颜色\n            }\n        }\n    }\n}\n\nexport default T\n```\n\n## 封装公共的弹窗组件\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/b90b6962ec454c8c83239e4ee7df1557~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=nXWLhLDbktFgdYhfpUx0isqadcI%3D)\n\n```js\n\u003ctemplate\u003e\n  \u003cel-dialog class=\"my-dialog\" draggable v-bind=\"$attrs\" v-model=\"modelValue\" :modal-append-to-body=\"modalAppendToBody\"\n    :append-to-body=\"appendToBody\" :fullscreen=\"fullscreen\" :close-on-click-modal=\"closeOnClickModal\"\n    :close-on-press-escape=\"closeOnPressEscape\" :width=\"comWidth\" :top=\"top\" @closed=\"closed\"\u003e\n    \u003ctemplate v-slot:title\u003e\n      \u003cslot name=\"title\"\u003e\n        \u003cspan class=\"my-dialog-title\"\u003e{{ dialogTitle || ''}}\u003c/span\u003e\n      \u003c/slot\u003e\n    \u003c/template\u003e\n    \u003cdiv v-loading=\"loading\" class=\"body-content\" :style=\"{'height':comHeight}\"\u003e\n      \u003cslot\u003e\u003c/slot\u003e\n    \u003c/div\u003e\n    \u003ctemplate #footer\u003e\n      \u003cdiv class=\"dialog-footer\" v-if=\"closeBtn\"\u003e\n        \u003cel-button type=\"close\" size=\"small1\" @click=\"closed\"\u003e关闭\u003c/el-button\u003e\n      \u003c/div\u003e\n    \u003c/template\u003e\n  \u003c/el-dialog\u003e\n\u003c/template\u003e\n\u003cscript setup\u003e\nimport { computed } from 'vue'\nconst props = defineProps(\n  {\n    visible: { type: Boolean, default: false }, // 是否可见\n    loading: { type: Boolean, default: false }, // 是否加载中\n    top: { type: String, default: '20vh' }, // 距离顶部的距离\n    fullscreen: { type: Boolean, default: false }, // 是否全屏\n    size: { type: String, default: 'big' }, // 大小\n    width: { type: [Number, String], default: 0 }, // 宽度\n    height: { type: [Number, String], default: '55vh' }, // 宽度\n    dialogTitle: { type: String, default: '' }, // 弹出框标题\n    modalAppendToBody: { type: Boolean, default: false }, // 是否将弹出框插入到body中\n    appendToBody: { type: Boolean, default: false }, // 是否将内容插入到body中\n    closeOnClickModal: { type: Boolean, default: false }, // 是否在点击模态框时关闭\n    closeOnPressEscape: { type: Boolean, default: false }, // 是否在按下ESC键时关闭\n    dblclickDisabled: { type: Boolean, default: false }, // 是否禁用双击放大\n    closeBtn: { type: Boolean, default: false }, // 关闭按钮\n  },\n  ['modelValue']\n)\n\nconst comWidth = computed(() =\u003e {\n  if (props.size === 'small') {\n    return props.width || '30%'\n  } else if (props.size === 'middle') {\n    return props.width || '40%'\n  } else if (props.size === 'big') {\n    return props.width || '60%'\n  }\n  return props.width || '40%'\n})\n\nconst comHeight = computed(() =\u003e {\n  return props.height || '55vh'\n})\n\nconst emit = defineEmits(['update:modelValue','closed'])\nconst closed = () =\u003e {\n  emit('update:modelValue')\n  emit('closed',false)\n}\n\u003c/script\u003e\n```\n\n## 封装ResizeObserver函数\n\n主要是监听元素的变化或者窗口的变化。有一部分用到了可以拉伸的左右布局的模块。\n\n```js\n//directive/index.js\n// 监听元素大小变化的指令\nconst map = new WeakMap();\nconst ob = new ResizeObserver((entries) =\u003e {\n  for (const entry of entries) {\n    // 获取dom元素的回调\n    const handler = map.get(entry.target);\n    //存在回调函数\n    if (handler) {\n      // 将监听的值给回调函数\n      handler({\n        width: entry.borderBoxSize[0].inlineSize,\n        height: entry.borderBoxSize[0].blockSize,\n      });\n    }\n  }\n});\n\nexport const Resize = {\n  mounted(el, binding) {\n    //将dom与回调的关系塞入map\n    map.set(el, binding.value);\n    //监听el元素的变化\n    ob.observe(el);\n  },\n  unmounted(el) {\n    //取消监听\n    ob.unobserve(el);\n  },\n};\nconst directives = { Resize };\n\nconst registerDirective = (app) =\u003e {\n  Object.keys(directives).forEach((key) =\u003e {\n    app.directive(key, directives[key]);\n  });\n};\nexport default registerDirective;\n\n```\n\n## 公共拖曳布局的函数\n\n主要是用于左右布局宽度的变化，可以使用鼠标进行拖曳，改变左右盒子的高度。\n\n```js\nexport const useCommon = ()=\u003e{\n    function setLayoutDrag(dragId) {\n        const resize = document.getElementById(dragId)\n        let previousElement = resize.previousSibling\n        let nextElement = resize.nextSibling\n        let previousTag = previousElement.tagName\n        let nextTag = nextElement.tagName\n        resize.onmousedown = (e) =\u003e {\n          const startX = e.clientX\n          const startY = e.clientY\n          let type = ''\n          if (previousTag === 'ASIDE' \u0026\u0026 nextTag === 'MAIN') {\n            type = 'ASIDE-MAIN'\n          } else if (previousTag === 'MAIN' \u0026\u0026 nextTag === 'ASIDE') {\n            type = 'MAIN-ASIDE'\n          } else if (\n            (previousTag === 'HEADER' \u0026\u0026 nextTag === 'MAIN') ||\n            (previousTag === 'FOOTER' \u0026\u0026 nextTag === 'MAIN')\n          ) {\n            type = 'HEADER-MAIN'\n          } else if (\n            (previousTag === 'MAIN' \u0026\u0026 nextTag === 'HEADER') ||\n            (previousTag === 'MAIN' \u0026\u0026 nextTag === 'FOOTER')\n          ) {\n            type = 'MAIN-HEADER'\n          }\n          let initWidth = 0,\n            initHeight = 0\n          if (type === 'ASIDE-MAIN') {\n            initWidth = previousElement.clientWidth // 初始位置\n          } else if (type === 'MAIN-ASIDE') {\n            initWidth = nextElement.clientWidth // 初始位置\n          } else if (type === 'HEADER-MAIN') {\n            initHeight = previousElement.clientHeight\n          } else if (type === 'MAIN-HEADER') {\n            initHeight = nextElement.clientHeight\n          }\n          document.onmousemove = (k) =\u003e {\n            const endX = k.clientX\n            const endY = k.clientY\n            let moveLen = endX - startX // 横向移动宽度\n            let moveHeight = endY - startY // 纵向移动高度\n            switch (type) {\n              case 'ASIDE-MAIN':\n                let asideMainWidth = initWidth + moveLen\n                if (moveLen \u003c 0) {\n                  // 向左移\n                  if (asideMainWidth \u003e 400) {\n                    // 左侧剩90\n                    previousElement.style.width = asideMainWidth + 'px'\n                  }\n                } else {\n                  // 向右移动\n                  if (nextElement.clientWidth \u003e 400) {\n                    // 右侧剩90\n                    previousElement.style.width = asideMainWidth + 'px'\n                  }\n                }\n                break\n              case 'MAIN-ASIDE':\n                let mainAsideWidth = initWidth - moveLen\n                if (moveLen \u003c 0) {\n                  // 向左移\n                  if (previousElement.clientWidth \u003e 400) {\n                    // 左侧剩90\n                    nextElement.style.width = mainAsideWidth + 'px'\n                  }\n                } else {\n                  // 向右移动\n                  if (mainAsideWidth \u003e 400) {\n                    nextElement.style.width = mainAsideWidth + 'px'\n                  }\n                }\n                break\n              case 'HEADER-MAIN': {\n                let headerMainHeight = initHeight + moveHeight\n                if (moveHeight \u003c 0) {\n                  // 向上移\n                  if (headerMainHeight \u003e 60) {\n                    // 上侧剩90\n                    previousElement.style.height = headerMainHeight + 'px'\n                  }\n                } else {\n                  // 向下移动\n                  if (nextElement.clientHeight \u003e 60) {\n                    // 下侧剩90\n                    previousElement.style.height = headerMainHeight + 'px'\n                  }\n                }\n                break\n              }\n              case 'MAIN-HEADER': {\n                let mainHeaderHeight = initHeight - moveHeight\n                if (moveHeight \u003c 0) {\n                  // 向上移\n                  if (previousElement.clientHeight \u003e 60) {\n                    // 左侧剩90\n                    nextElement.style.height = mainHeaderHeight + 'px'\n                  }\n                } else {\n                  // 向下移动\n                  if (mainHeaderHeight \u003e 60) {\n                    nextElement.style.height = mainHeaderHeight + 'px'\n                  }\n                }\n                break\n              }\n      \n              default:\n            }\n          }\n          document.onmouseup = (evt) =\u003e {\n            document.onmousemove = null\n            document.onmouseup = null\n            resize.releaseCapture \u0026\u0026 resize.releaseCapture()\n          }\n          resize.setCapture \u0026\u0026 resize.setCapture()\n          return false\n        }\n      }\n\n      return {\n        setLayoutDrag\n      }\n}\n```\n\n## 公共布局\n\n此处的可以自己查看代码。\n\n![](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/a1ed96f041ad4bbf8a4f75e9b19ab10b~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzcwMjgxMDg5NDE1MzU5MiJ9\u0026rk3s=f64ab15b\u0026x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\u0026x-orig-expires=1722473182\u0026x-orig-sign=ua2Rl2MdqQEcygu9%2FmvC3dzEeKI%3D)\n\n## invoke调用rust函数，关闭splash\n\n```js\nimport { invoke } from '@tauri-apps/api/tauri'\n\nonMounted(() =\u003e {\n  // window.addEventListener('contextmenu', (e) =\u003e e.preventDefault(), false)\n  document.addEventListener('DOMContentLoaded', () =\u003e {\n    // This will wait for the window to load, but you could\n    // run this function on whatever trigger you want\n    setTimeout(() =\u003e {\n      invoke('close_splashscreen')\n    }, 1000)\n  })\n})\n```\n\n🎉使用Tauri+vite+koa2+mysql开发了一款待办效率应用\n\n## 📝项目概述\n\n这是一个基于tauri+vite的应用，它采用了一些最新的前端技术，包括 Tauri、Vue3、Vite5、koa2 和 mysql。它提供了丰富的效率管理工具。\n\n应用地址：\u003chttps://kestrel-task.cn\u003e\n\n喜欢的可以试试哦，🙏🙏🙏\n\n## 🏆项目预览\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21aec3d72f864d88b207f678a0b04517~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=631\\\u0026s=48644\\\u0026e=png\\\u0026b=fdfdfd)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d786d19a4cdb40d5855ae54d4a904f25~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=680\\\u0026s=66330\\\u0026e=png\\\u0026b=fdf2f1)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/638ac319810547f7ad589554a3991ff1~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=680\\\u0026s=68763\\\u0026e=png\\\u0026b=fdf0ee)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ee5d7dde28f74ff08abc209ee619a9a2~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=680\\\u0026s=48007\\\u0026e=png\\\u0026b=fefefe)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b25a5c46c424b08978ae5683b5d5677~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=680\\\u0026s=77110\\\u0026e=png\\\u0026b=fdf2eb)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2e15a6cfd6b420d9024530486228804~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=680\\\u0026s=87965\\\u0026e=png\\\u0026b=8d8784)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e479faa9c1314a2e9a4430bb000cd443~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=702\\\u0026s=68312\\\u0026e=png\\\u0026b=fefefe)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fef428b0f92346b1af6091610240c586~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=702\\\u0026s=65271\\\u0026e=png\\\u0026b=fefdfd)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d9ff347653d243f08161df8e15e3cbf5~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100\\\u0026h=717\\\u0026s=65095\\\u0026e=png\\\u0026b=fdfdfd)\n\n## 💻技术栈\n\n*   **Tauri**: Tauri是一个用于构建现代桌面应用程序的工具，结合了Rust、Vue.js和Web技术，提供了强大的跨平台能力。\n*   **Vue3**: Vue3是流行的JavaScript框架Vue.js的最新版本，具有更好的性能、更好的TypeScript支持和更多的特性。\n*   **Vite5**: Vite是一个现代化的构建工具，Vite5是其最新版本，具有快速的冷启动、热模块替换和原生ES模块支持。\n*   **Koa2**: Koa2是一个基于Node.js的轻量级Web框架，使用异步函数处理中间件，提供了简洁而强大的Web开发体验。\n*   **MySQL**: MySQL是一个流行的关系型数据库管理系统，具有高性能、可靠性和广泛的应用领域，适用于各种规模的应用程序。\n\n主要包括的功能点实现\n\n## 🔔图标生成\n\n在项目根目录，放上图片为 app-icon.png的图片\n然后执行。就能看到图标已经生成了。\n\n```json\nnpm run tauri icon\n```\n\n## 📢配置应用系统托盘\n\n### 新建tray.rs,编写托盘事件。内容如下。\n\n```rust\nuse tauri::{\n    AppHandle, CustomMenuItem, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem\n};\nuse tauri::Manager;\n\n// 托盘菜单\npub fn menu() -\u003e SystemTray {\n    let quit = CustomMenuItem::new(\"quit\".to_string(), \"退出\");\n    let show = CustomMenuItem::new(\"show\".to_string(), \"显示\");\n    let hide = CustomMenuItem::new(\"hide\".to_string(), \"隐藏\");\n    let tray_menu = SystemTrayMenu::new()\n        .add_native_item(SystemTrayMenuItem::Separator)\n        .add_item(hide)\n        .add_item(show)\n        .add_native_item(SystemTrayMenuItem::Separator)\n        .add_item(quit);\n\n    SystemTray::new().with_menu(tray_menu)\n}\n\n// 托盘事件\npub fn handler(app: \u0026AppHandle, event: SystemTrayEvent) {\n    let window = app.get_window(\"main\").unwrap();\n    match event {\n        SystemTrayEvent::MenuItemClick { id, .. } =\u003e match id.as_str() {\n            \"quit\" =\u003e {\n                std::process::exit(0);\n            }\n            \"show\" =\u003e {\n                window.show().unwrap();\n            }\n            \"hide\" =\u003e {\n                window.hide().unwrap();\n            }\n            _ =\u003e {}\n        },\n        _ =\u003e {}\n    }\n}\n\n```\n\n### 在main.rs中使用\n\n```rust\nuse std::{thread,time};\nmod tray;\n\nfn main() {\n    let context = tauri::generate_context!();\n    tauri::Builder::default()\n        // .menu(tauri::Menu::os_default(\u0026context.package_info().name)) //配置界面菜单\n        .system_tray(tray::menu()) // ✅ 将 `tauri.conf.json` 上配置的图标添加到系统托盘\n        .on_system_tray_event(tray::handler) // ✅ 注册系统托盘事件处理程序\n        .invoke_handler(tauri::generate_handler![my_custom_command, init_process,close_splashscreen])\n        .run(context)\n        .expect(\"error while running tauri application\");\n}\n```\n\n## 🎛️ 接口封装请求\n\nTauri 是一个框架，它允许开发者使用 Rust 语言来构建轻量级的桌面应用程序，Tauri 提供了一套 API，其中包括了用于处理 HTTP 请求的 http 模块。\ntauri.conf.json 文件中进行配置：\n\n```json\n{\n  \"tauri\": {\n    \"allowlist\": {\n      \"http\": {\n        \"all\": true,\n        \"request\": true,\n        \"scope\":[\n          \"http://**\",\n          \"https://**\"\n        ]\n      }\n    }\n  }\n}\n```\n\n## 🎵基于API的封装\n\n```js\n// http.js\n\nimport { fetch, ResponseType, Body } from '@tauri-apps/api/http'\n\n// https://tauri.app/zh-cn/v1/api/js/http#fetch\nexport const http = (opts = {}) =\u003e {\n  return new Promise((resolve, reject) =\u003e {\n    const { url, method, query, data, headers, callback } = opts\n    fetch(url, {\n      method: method || 'GET',\n      headers: {\n        'content-type': 'application/json',\n        ...headers,\n      },\n      responseType: ResponseType.JSON,\n      timeout: 60000,\n      query: query,\n      body: Body.json({\n        ...data,\n      }),\n    })\n    .then((res) =\u003e {\n      callback \u0026\u0026 callback(res)\n      resolve(res)\n    })\n    .catch((e) =\u003e {\n      reject(e)\n    })\n  })\n}\n```\n\n## 😄获取版本号\n\n这个函数通常用于获取应用程序的版本信息\n\n```js\nimport { getVersion } from '@tauri-apps/api/app'\n```\n\n```js\nonMounted(async () =\u003e {\n  appVersion.value = await getVersion()\n  getNewVersions()\n})\n```\n\n## 🙂检查版本更新\n\n这段代码的作用是导入 Tauri 中的更新模块 @tauri-apps/api/updater 中的 checkUpdate 和 installUpdate 函数。checkUpdate 用于检查是否有可用的应用程序更新，而 installUpdate 用于安装应用程序更新。\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/756ee30143e049faabe7ce0d322b3126~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1037\\\u0026h=591\\\u0026s=98618\\\u0026e=png\\\u0026b=8c8584)\n\n```js\nimport { checkUpdate, installUpdate } from '@tauri-apps/api/updater'\n```\n\n```js\n  checkUpdate().then(async (res) =\u003e {\n    const { shouldUpdate, manifest } = res\n    if (shouldUpdate) {\n      confirm(`发现新版本：${manifest?.version}，是否升级？`, { title: '版本更新', type: 'success' }).then(async (res) =\u003e {\n        try {\n          ElMessage.success({\n            message: '正在下载更新...',\n            duration: 3000,\n          })\n          installUpdate()\n            .then(async (res) =\u003e {\n              await relaunch()\n            })\n            .catch((e) =\u003e {\n            })\n        } catch (e) {\n          ElMessage.error({\n            message: '下载更新失败',\n            description: e.toString() || '',\n          })\n        }\n      })\n    } else {\n     await confirm(`当前版本，已经是最新版本`, { title: '检查更新', type: 'success' ,okLabel: '确定',cancelLabel: '取消'})\n    }\n  })\n```\n\n## 🙃窗口操作\n\n窗口禁用最大化和最小化功能\n\n```json\nimport { appWindow } from '@tauri-apps/api/window';\n```\n\n## 🥰禁用最大化和取消禁用\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b0fea5c2cb14c34871a021e33ded335~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1101\\\u0026h=643\\\u0026s=935785\\\u0026e=png\\\u0026b=8196e8)\n\n```js\nappWindow.setMaximizable(true) //禁用\nappWindow.setMaximizable(false) //取消禁用\n```\n\n```js\nappWindow.setMinimized(true) //禁用\nappWindow.setMinimized(false) //取消禁用\n```\n\n## 🥰消息提示\n\n### Ask弹窗\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6542bed23be430c9e40d7ade9f663fd~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=428\\\u0026h=204\\\u0026s=8338\\\u0026e=png\\\u0026b=f9f9f9)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/afc34e8aa5734ffab3484f36a411062f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=499\\\u0026h=221\\\u0026s=12089\\\u0026e=png\\\u0026b=f9f9f9)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f9cec481b624506a2b43254fd94353e~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=499\\\u0026h=221\\\u0026s=12089\\\u0026e=png\\\u0026b=f9f9f9)\n\n```js\nimport { ask } from '@tauri-apps/api/dialog';\nconst yes = await ask('确定更新该版本?', '发现新版本');\nconst yes2 = await ask('确定更新该版本?', { title: '发现新版本', type: 'warning' });\n```\n\n如果想要修改按钮的文本，可以使用,okLabel: '确定',cancelLabel: '取消'。\n\n## 😊confirm弹窗\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f0c4c0575394da89365917c1361e717~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=445\\\u0026h=175\\\u0026s=9331\\\u0026e=png\\\u0026b=f8f8f8)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/31e9903db57949bcb6e4bae01a0d5ecc~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=444\\\u0026h=205\\\u0026s=11454\\\u0026e=png\\\u0026b=f8f8f8)\n\n```json\nimport { confirm } from '@tauri-apps/api/dialog';\nconst confirmed = await confirm('确定更新该版本?', '发现新版本');\nconst confirmed2 = await confirm('确定更新该版本?', { title: '发现新版本', type: 'warning',okLabel: '确定',cancelLabel: '取消' });\n```\n\n## 😝message提示框\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73e615ead53946faa2f3f9459202f2d0~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=405\\\u0026h=153\\\u0026s=8463\\\u0026e=png\\\u0026b=f6f6f6)\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32ff141227514934928c346184811397~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=417\\\u0026h=164\\\u0026s=9159\\\u0026e=png\\\u0026b=f7f7f7)\n\n```js\nimport { message } from '@tauri-apps/api/dialog';\nawait message('确定更新该版本', '发现新版本');\nawait message('确定更新该版本', { title: '发现新版本', type: 'error',okLabel: '确定',cancelLabel: '取消' });\n```\n\n## 🤗open 选择文件弹窗\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8874f30a3ad943f4b584985e984d6eba~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=794\\\u0026h=557\\\u0026s=46422\\\u0026e=png)\n\n```js\nimport { open } from '@tauri-apps/api/dialog';\n// Open a selection dialog for image files\nconst selected = await open({\n  multiple: true,\n  filters: [{\n    name: 'Image',\n    extensions: ['png', 'jpeg']\n  }]\n});\nif (Array.isArray(selected)) {\n  // user selected multiple files\n} else if (selected === null) {\n  // user cancelled the selection\n} else {\n  // user selected a single file\n}\n```\n\n## 😐save文件保存弹窗\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6048b5853f0547348337e1dcd3df73dc~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=661\\\u0026h=571\\\u0026s=34662\\\u0026e=png\\\u0026b=fbfbfb)\n\n```js\nimport { save } from '@tauri-apps/api/dialog';\nconst filePath = await save({\n  filters: [{\n    name: 'Image',\n    extensions: ['png', 'jpeg']\n  }]\n});\n\n```\n\n## 😁splashscreen页设置\n\n为什么要设置这个呢，因为`splashscreen` 页面通常用于在应用程序启动时显示一个启动画面或加载动画，以提供用户友好的界面体验。这个页面可以包含应用程序的标志、名称或其他相关信息，帮助用户确认应用程序正在加载。一旦应用程序加载完成，通常会自动关闭 `splashscreen` 页面并显示应用程序的主界面。\n\n#### 🫠tauri.conf.json配置\n\n```json\n    \"windows\": [\n      {\n        \"fullscreen\": false,\n        \"resizable\": true,\n        \"title\": \"微芒计划\",\n        \"width\": 1100,\n        \"height\": 680,\n        \"minHeight\": 600,\n        \"minWidth\": 900,\n        \"visible\": false\n      },\n      {\n        \"width\": 800,\n        \"height\": 500,\n        \"minHeight\": 500,\n        \"minWidth\": 800,\n        \"decorations\": false,\n        \"url\": \"splashscreen.html\",\n        \"label\": \"splashscreen\",\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ]\n```\n\nsplashscreen.html要放到public下面，为一个html文件，里面可以写开屏的图片动画或者界面。\n\n### 🙂main.rs编写关闭splashscreen 页面的功能\n\n```rust\n// Create the command:\n// This command must be async so that it doesn't run on the main thread.\n#[tauri::command]\nasync fn close_splashscreen(window: Window) {\n  // Close splashscreen\n  window.get_window(\"splashscreen\").expect(\"no window labeled 'splashscreen' found\").close().unwrap();\n  // Show main window\n  window.get_window(\"main\").expect(\"no window labeled 'main' found\").show().unwrap();\n}\n```\n\n### 🙃main入口提供给前端使用\n\n```rust\nfn main() {\n    let context = tauri::generate_context!();\n    tauri::Builder::default()\n        // .menu(tauri::Menu::os_default(\u0026context.package_info().name)) //配置界面菜单\n        .system_tray(tray::menu()) // ✅ 将 `tauri.conf.json` 上配置的图标添加到系统托盘\n        .on_system_tray_event(tray::handler) // ✅ 注册系统托盘事件处理程序\n        .invoke_handler(tauri::generate_handler![my_custom_command, init_process,close_splashscreen])\n        .run(context)\n        .expect(\"error while running tauri application\");\n}\n```\n\n### 🙃在前端调用splashscreen页面\n\n界面加载完成后，关掉\n\n```rust\nimport { onMounted } from 'vue'\nimport { invoke } from '@tauri-apps/api/tauri'\nonMounted(() =\u003e {\n  // window.addEventListener('contextmenu', (e) =\u003e e.preventDefault(), false)\n  document.addEventListener('DOMContentLoaded', () =\u003e {\n    // This will wait for the window to load, but you could\n    // run this function on whatever trigger you want\n    setTimeout(() =\u003e {\n      invoke('close_splashscreen')\n    }, 1000)\n  })\n})\n```\n\n🎉结语\n感兴趣的可以试试，有不清楚的问题，关于tauri开发方面的问题，也可以一起交流。欢迎加我：zhan\\_1337608148。一起成长，一起进步。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkestrela%2Fkestrel-task","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkestrela%2Fkestrel-task","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkestrela%2Fkestrel-task/lists"}