什么?我用日历软件跑路了?那我就自己开发一个!

2,947 阅读4分钟

最近,我的日历软件突然“跑路了”——对,有一天调休居然不显示,而且App Store 已经很久不更新了。不如自己动手,丰衣足食?于是我决定用 Tauri 来开发一个专属的 macOS 日历小助手。今天,就跟大家分享一下我的开发过程:如何用前端技术搭配 Tauri,做出一个轻量级、极速响应的日历应用。想自己开发一个日历助手?跟我一起来吧!

为什么要用 Tauri?

作为一个前端开发工程师,我虽然会些 Swift,但远没有前端技术那么熟练。而最近听说 Tauri 2.0 发布了,它的跨平台能力和极轻量的特性吸引了我。用 Tauri 开发 macOS 应用可以继续用我熟悉的前端栈(如 JavaScript、HTML、CSS),再加上 Tauri 2.0 对 Rust 和 API 的优化,我认为这是一个完美的机会尝试一下。

成品效果

先展示下成品效果

1730965622166.jpg

准备工作

在开始之前,需要确保你有以下开发工具:

  1. Node.js:用于管理前端依赖。

  2. Rust:Tauri 的后端部分是用 Rust 写的,所以需要安装 Rust。

  3. 包管理工具:这里我选择使用 pnpm 来管理项目依赖,你们可以使用Bun或者其他。

  4. Xcode:构建 macOS 应用需要的开发环境。

第一步:初始化 Tauri 项目

输入命令pnpm create tauri-app快速创建项目,前端用 Vue 或 React 都行。这里选择用 Vite + Vue,创建一个简单的前端项目,然后加上 Tauri 的后端。

pnpm create tauri-app
.../19305833e3a-fda2                     |   +3 +
.../19305833e3a-fda2                     | Progress: resolved 12, reused 0, downloaded 3, added 3, done
✔ Project name · calendar
✔ Identifier · com.example.calendar
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · pnpm
✔ Choose your UI template · Vue - (https://vuejs.org/)
✔ Choose your UI flavor · TypeScript

Template created! To get started run:
  cd calendar
  pnpm install
  pnpm tauri android init
  pnpm tauri ios init

For Desktop development, run:
  pnpm tauri dev

For Android development, run:
  pnpm tauri android dev

For iOS development, run:
  pnpm tauri ios dev

第二步:创建前端界面

接下来就是大家熟悉的 Vue 3 开发流程:配置路由、创建组件和编写页面界面。这里我们的代码还未开源,具体的界面代码暂时不详细展示。

第三步:修改应用窗体风格,实现 macOS 原生弹窗效果

为了让应用更具 macOS 原生体验,我参考了我之前使用的日历软件,发现它使用了 macOS 系统的 NSPopover 作为弹窗。这种弹窗效果不仅美观,还能提供更好的用户体验。经过一番调研,我找到了一个名为 tauri-plugin-nspopover 的插件,它能够帮助我们实现与 NSPopover 类似的原生弹窗效果。

这里使用Rust或者TS/JS都行,由于我在学习Rust,所以下面就用Rust给大家演示

安装 tauri-plugin-nspopover

首先 ,我们需要在项目中安装 tauri-plugin-nspopover 插件。进入 src-tauri 目录,在 Cargo.toml 文件中添加这个插件:

[dependencies]
tauri = { version = "2.0.0", features = [ "macos-private-api", "tray-icon", "unstable"] }
tauri-plugin-nspopover = { git = "https://github.com/freethinkel/tauri-nspopover-plugin.git", branch = "tauri-beta/v2", version = "3.3.0" }

配置插件

// lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_nspopover::init())
        .setup(|app| {
            let window = app.handle().get_webview_window("main").unwrap();
            window.to_popover();
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");

第四步:注册系统任务栏托盘

由于NSPopover需要一个托盘来触发的她的显示和隐藏,我们去注册一个托盘以及它的托盘事件

在tauri.conf.json 配置托盘图标、id 等信息

//tauri.conf.json
{
   ……
  "app": {
    "trayIcon": {
      "iconPath": "icons/tray.png",
      "iconAsTemplate": true,
      "id": "main"
    }
  },
  "bundle": {
   ……
  }
}

注册托盘事件

// lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_nspopover::init())
        .setup(|app| {
            let window = app.handle().get_webview_window("main").unwrap();
            window.to_popover();

           tray.on_tray_icon_event(move |_, event| {
                if (!handle.is_popover_shown()) {
                    handle.show_popover();
                } else {
                    handle.hide_popover();
                }
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");

第五步:隐藏Dock栏图标

由于这应用是常驻后台的,所以我不太想让它在Dock栏常驻,所以通过以下设置让他在Dock隐藏

// lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_nspopover::init())
        .setup(|app| {
            // 隐藏Dock栏
            app.set_activation_policy(ActivationPolicy::Accessory);
            let window = app.handle().get_webview_window("main").unwrap();
            window.to_popover();

           tray.on_tray_icon_event(move |_, event| {
                if (!handle.is_popover_shown()) {
                    handle.show_popover();
                } else {
                    handle.hide_popover();
                }
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");

第六步:打包上架

因为需要上架macOS,而macOS审核又比较严格,所以我需要补充一些权限的声明:

  1. tauri.conf.json同级创建Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict> 
      <key>ITSAppUsesNonExemptEncryption</key>
      <false/>
  </dict>
</plist>
  1. tauri.conf.json同级创建Entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        //必须
        <key>com.apple.security.app-sandbox</key>
        <true/>
        //必须不然会白屏
        <key>com.apple.security.network.client</key>
        <true/>
        // 你用到的权限
        ……
        //团队id+应用的id
        <key>com.apple.application-identifier</key>
        <string>${团队Id}.${identifier}</string>
        //团队id
        <key>com.apple.developer.team-identifier</key>
        <string>${团队Id}</string>
    </dict>
</plist>
  1. 修改tauri.conf.json 配置 Info.plistEntitlements.plist
{
  ……
  "bundle": {
    "macOS": {
      "entitlements": "./Entitlements.plist",
    },
     ……
  }
}
  1. 修改tauri.conf.json配置证书和签名
{
  ……
  "bundle": {
    "macOS": {
      "entitlements": "./Entitlements.plist",
      "signingIdentity":"Apple Distribution:你的公司或者团队名",
      "files": {
          "embedded.provisionprofile": "你的provisionprofile地址"
        }
    },
     ……
  }
}
  1. 执行打包命令
sudo pnpm run tauri build --bundles app --target universal-apple-darwin

8. 签名打包pkg

xcrun productbuild --sign "3rd Party Mac Developer Installer:你的公司或者团队名" --component "你的文件地址" /Applications "文件名.pkg"

踩坑

因为签名是需要联网认证你的证书,所以需要检查一下本地的证书,是否过期。

image.png

最后

附上下载地址