使用Tauri开发一个公司的内部模板仓库管理工具

2024-10-04 21:48:55 浏览数 (2)

背景

公司内部希望有一个可以快速开发项目的工具,不用每次都使用基础的脚手架进行初始化再根据项目需求进行定制改动,开源仓库又与公司业务有不少差距,所以决定在公司内部几个典型项目的基础上维护几个通用的示例项目方便开发同学快速进入开发。

解决方案

之前使用electron开发过一些小工具但是安装包体积太大了,所以这次选用tauri来开发。主要功能就是将公司内几个典型仓库内置在工具中,并支持开发者对项目进行快速克隆,完成新项目的初始化操作。

这里我们主要实现如下几个功能:

  • 展示模板仓库列表
  • 克隆模板仓库
  • 记住本机工作目录
  • 预览模板仓库 UI 界面
  • 单独清除仓库的.git文件夹
  • 删除本地仓库

开发准备

本次开发环境为 win10 系统,需要安装RustMicrosoft Visual Studio C 生成工具。

实现方法

为了方便开发,这里使用element-plus帮助构建页面。

项目初始化

首先使用create-tauri-app创建新 Tauri 项目,按照初始化之后的提示执行pnpm install安装依赖,然后再启动项目。

rust后端代码修改

配置部分

tauri应用配置(这部分比较重要,一定要准确配置好应用所需接口权限)修改。

修改src-tauritauri.conf.json文件,在tauri->allowlist增加如下接口权限配置:

代码语言:json复制
"os": {
    "all": true
},
"http":{
    "all": true,
    "request": true,
    "scope":[
      "http://**",
      "https://**"
    ]
},
"shell": {
    "all": false,
    "open": true,
    "execute": true,
    "scope": [
      {
        "name": "run-git",
        "cmd": "git",
        "args": true
      }
    ]
},
"dialog": {
    "all": false,
    "open": true
},
"path": {
    "all": true
}

其中shellrust调用shell克隆仓库使用,osdialogpath为需要使用到的系统APIhttp是我们在页面上发送请求所用。

rust部分

然后修改 src-taurisrcmain.rs 文件,追加如下核心方法:

1.检查文件夹是否存在:

代码语言:rust复制
#[tauri::command]
fn check_folder_exists(folder: &str) -> String {
    println!("folder: {}", folder);
    let path = Path::new(folder);
    if path.exists() && path.is_dir() {
        return '1'.to_string();
    }
    return '0'.to_string();
}

2.打开文件夹:

代码语言:rust复制
#[tauri::command]
fn open_directory(path: &str) {
    let output = match std::env::consts::OS {
        "windows" => Command::new("cmd")
            .args(&["/C", &format!("start {}", path)])
            .output()
            .expect("Failed to start"),
        "linux" | "macos" => Command::new("open")
            .arg(path)
            .output()
            .expect("Failed to start"),
        _ => panic!("Unsupported OS"),
    };

    if output.status.success() {
        println!("Directory opened successfully.");
    } else {
        println!("Failed to open directory.");
    }
}

3.删除文件夹:

代码语言:rust复制
#[tauri::command]
fn del_directory(path: &str) -> String {
    let dir_path = Path::new(path);
    if dir_path.exists() && dir_path.is_dir() {
        fs::remove_dir_all(dir_path).expect("无法删除目录");
        println!("目录已删除");
        return '1'.to_string();
    } else {
        println!("目录不存在");
        return '0'.to_string();
    }
}

以上命令需要如下依赖:

代码语言:rust复制
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;

最后在main函数中注册tauri命令:

代码语言:rust复制
tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![
        greet,
        check_folder_exists,
        open_directory,
        del_directory
    ])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

到此,rust后端部分就完成了。

前端代码修改

1.集成element-plus

首先安装如下几个依赖:element-pluslessless-loader

修改src目录下的main.js,引入element-plus

代码语言:js复制
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";

const app = createApp(App);
app.use(ElementPlus);

2.定义外部接口。 仓库列表我们使用一段json,为了方便直接把json放在外部存储上,然后在接口中请求返回。 在srcrequestindex.js中追加如下代码(没有request文件夹可自己新建):

代码语言:js复制
import { http } from "@tauri-apps/api";
async function getTemplates() {
  return await http.fetch(
    "https://存储服务/templates.json",
    {
      method: "get",
      responseType: 2,
    }
  );
}

export { getTemplates };

参考返回数据示例:

代码语言:json复制
{
    name: "xxx管理系统",
    thumbs: [
      {
        picUrl: 'https://xxx/1.png',
        pageName: '登录'
      },
      {
        picUrl: 'https://xxx/2.png',
        pageName: '首页'
      },
      {
        picUrl: 'https://xxx/3.png',
        pageName: '系统管理'
      }
    ],
    git: 'http://xxx.git',
},

这里主要返回了项目名、缩略图数组(用于展示项目的几个典型页面UI)、仓库地址。

3.修改页面。

修改创建项目时自带的 srccomponentsGreet.vue 页面。

引入一些必要依赖:

代码语言:js复制
import { invoke } from "@tauri-apps/api/tauri";
import { Command } from '@tauri-apps/api/shell';
import { open as openDialog } from '@tauri-apps/api/dialog';
import { appDataDir } from '@tauri-apps/api/path';
import { type } from '@tauri-apps/api/os';

追加如下函数(介绍几个关键方法):

代码语言:js复制
// 选择本地工作目录
async function chooseDir() {
  const dir_path = await openDialog({
    directory: true,
    multiple: false,
    defaultPath: await appDataDir(),
  });
  dir.value = dir_path;
  localStorage.setItem('local_dir', dir_path);
}

// 克隆仓库
async function do_clone(params) {
  const command = new Command('run-git', params);
  command.on('close', data => {
    console.log(`command finished with code ${data.code} and signal ${data.signal}`)
    cloneLoading.value = false;
    ElMessage({
      message: '仓库克隆成功',
      type: 'success',
    })
    let clonedProjects = JSON.parse(localStorage.getItem('clonedProjects') || '{}');
    clonedProjects[params[1]] = params[2];
    localStorage.setItem('clonedProjects', JSON.stringify(clonedProjects));
  });
  command.on('error', error => {
    console.error(`command error: "${error}"`)
    cloneLoading.value = false;
  });
  command.stdout.on('data', line => console.log(`command stdout: "${line}"`));
  command.stderr.on('data', line => console.log(`command stderr: "${line}"`));
  cloneLoading.value = true;
  const child = await command.spawn();
  console.log('pid:', child.pid);
}

// 克隆仓库或者打开本地仓库目录
async function open_clone_repo(doc, repoIndex) {
  if (isCloned(doc)) {
    openCloned(doc)
    return
  }
  if (dir.value == '') {
    ElMessage({
      message: '请选择工作目录',
      type: 'error',
    })
    return
  }
  let repo_name = ''
  if (projectName.value == '') {
    repo_name = doc.substring(doc.lastIndexOf('/')   1).split('.')[0];
  }
  curRepoIndex.value = repoIndex;
  let spliter = '\'
  // mac系统
  const osType = await type();
  if (osType.indexOf('Darwin') != -1) {
    spliter = '/'
  }
  const clone_dir = projectName.value == '' ? dir.value   spliter   repo_name : dir.value   spliter   projectName.value;
  console.log('local_dir:', clone_dir)
  const exists = await invoke("check_folder_exists", { folder: clone_dir });
  if ( exists) {
    ElMessage({
      message: '本地已存在该目录,请重新输入',
      type: 'error',
    })
    return
  }
  do_clone(['clone', doc, clone_dir])
}

// 删除本地目录
async function del_local(url) {
  let clonedProjects = JSON.parse(localStorage.getItem('clonedProjects') || '{}');
  if (clonedProjects[url]) {
    console.log('del dir:', clonedProjects[url])
    ElMessageBox.alert('确定删除吗', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      closeOnClickModal: true,
      showCancelButton: true,
      showClose: false,
      callback: async (action) => {
        if (action === 'confirm') {
          const status = await invoke("del_directory", { path: clonedProjects[url] })
          if ( status) {
            ElMessage({
              type: 'success',
              message: `操作成功`,
            })
            delete clonedProjects[url];
            localStorage.setItem('clonedProjects', JSON.stringify(clonedProjects));
            const projectsTemp = projects.value
            projects.value = []
            projects.value = projectsTemp
          } else {
            ElMessage({
              type: 'error',
              message: `操作失败`,
            })
          }
        }
      },
    })

  }
}

// 判断仓库是不是在本地被克隆过
function isCloned(url) {
  let clonedProjects = JSON.parse(localStorage.getItem('clonedProjects') || '{}');
  if (clonedProjects[url]) {
    return true
  }
  return false
}

4.关键方法的调用按钮:

代码语言:html复制
<div>
  <el-button :loading="cloneLoading && proIndex == curRepoIndex" type="primary"
    @click="open_clone_repo(project.git, proIndex)">{{ isCloned(project.git) ? '打开目录' : '克隆仓库' }}</el-button>
</div>
<el-dropdown class="del-dir" v-if="isCloned(project.git)" split-button type="primary"
  @click="del_local(project.git)" @command="handleCommand($event, project.git)">
  删除目录
  <template #dropdown>
    <el-dropdown-menu>
      <el-dropdown-item command="clear_git">清除.git文件夹</el-dropdown-item>
    </el-dropdown-menu>
  </template>
</el-dropdown>

然后一个简单的仓库管理工具就初具雏形了,执行pnpm tauri dev来测试下。

下次开发项目就可以直接使用这个工具,浏览项目的UI截图,挑选模板仓库中适合需求的项目进行克隆来进行开发了。

参考资料

  • tauri 预先准备
  • Visual Studio 2022 生成工具
  • demo source

0 人点赞