大唔透 發表於 2025-4-2 16:13:00

无需WebView,Vue也能开发跨平台桌面应用

<h1 id="前言">前言</h1>
<p>一直以来,使用Vue开发桌面应用大部分都是使用基于webview的方案,如electron,tauri等。不依赖webview的,移动端倒有不少,如Weex,NativeScript等,桌面端寥寥无几。</p>
<p>最近,Deft框架完成了Vue3的适配,支持使用Vue+Rust开发跨平台应用,不依赖Webview,目前支持Linux,Windows,MacOS和Android等系统,后续计划将支持鸿蒙和iOS。</p>
<h1 id="快速开始">快速开始</h1>
<p>开始前,系统需要先安装nodejs,Rust,clang14+,如果未安装,直接去官网下载安装即可。</p>
<p>执行下面命令,快速创建新工程,my-vue-app为新工程名称。</p>
<pre><code class="language-bash">npm create deft@latest my-vue-app
</code></pre>
<p>可以根据自己的喜好选择语言和框架,这里,以TypeScript+Vue为例。</p>
<p>命令执行完后,会在当前目录下创建一个<code>my-vue-app</code>项目,执行下面命令启动项目。</p>
<pre><code class="language-bash">cd my-vue-app
npm install
npm run dev
</code></pre>
<p>启动成功后,会得到如下输出:</p>
<pre><code>...
Rspack compiled successfully in 2.31 s

==============================================
Press r to run on this device
Press a to run on connected android device
Press q to quit
==============================================
</code></pre>
<p>按<code>r</code>,即可开始构建预览应用。首次构建,需要下载依赖,所以耗时可能会比较长,可以配置cargo镜像提高下载速度。</p>
<p>构建成功后,就能看到预览窗口了,可以直接修改<code>ui</code>目录下的JS/TS文件实时预览效果。</p>
<p><img src="https://img2024.cnblogs.com/blog/154217/202504/154217-20250402160957232-2117724387.png" alt="" loading="lazy"></p>
<h1 id="资源占用">资源占用</h1>
<p>在Win11下<strong>内存占用</strong>仅12M,<strong>应用体积包</strong>20M,压缩后仅9M。</p>
<h1 id="系统接口调用">系统接口调用</h1>
<p>开发桌面应用的过程中,有时会遇到调用系统接口的需求,特别是开发本地工具类应用的时候。JS本身不提供系统调用的接口,但是Rust可以非常方便的进行系统接口调用,我们可以使用Rust编写系统调用逻辑,然后提供接口给JS调用,从而扩展JS的能力。</p>
<p>以获取系统和内存信息为例,不同系统提供的底层接口往往不一样,为了抹平不同系统之间的差异,我们可以借助<code>sysinfo</code>库来简化我们的工作。</p>
<h3 id="增加依赖">增加依赖</h3>
<p>首先,在命令行切换到我们的工程目录,执行以下命令增加cargo依赖。</p>
<pre><code class="language-bash">cargo add sysinfo serde
</code></pre>
<h3 id="定义数据交换结构">定义数据交换结构</h3>
<p>然后,需要定义Rust和JS之间的通信数据结构,打开项目下的<code>main.rs</code>,增加如下内容:</p>
<pre><code class="language-rust">use deft::js_serialize;
use serde::Serialize;

#
#
pub struct SysInfo {
    pub mem_total: u64,
    pub mem_free: u64,
    pub mem_used: u64,
    pub sys_name: Option&lt;String&gt;,
    pub sys_kernel_version: Option&lt;String&gt;,
    pub sys_os_version: Option&lt;String&gt;,
    pub sys_hostname: Option&lt;String&gt;,
    pub cpu_count: usize,
}

js_serialize!(SysInfo);
</code></pre>
<p>js_serialize宏在这里的作用是使SysInfo结构体能序列化为js对象,而<code>#</code>可以将字段的下划线命名规则自动转换成小驼峰命名。</p>
<h3 id="使用rust编写接口">使用Rust编写接口</h3>
<p>定义好结构体后,就可以使用Rust编写接口了。继续在<code>main.rs</code>增加如下内容:</p>
<pre><code>use deft::js_func;
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};


#
pub async fn get_sys_info() -&gt; SysInfo {
    let mut sys = System::new_with_specifics(
      RefreshKind::nothing()
            .with_cpu(CpuRefreshKind::everything())
            .with_memory(MemoryRefreshKind::everything()),
    );
    sys.refresh_all();
    let mem_total = sys.total_memory();
    let mem_used = sys.used_memory();
    let mem_free = sys.free_memory();
    let sys_name = System::name();
    let sys_kernel_version = System::kernel_version();
    let sys_os_version = System::os_version();
    let sys_hostname = System::host_name();
    let cpu_count = sys.cpus().len();

    SysInfo {
      mem_total,
      mem_used,
      mem_free,
      sys_name,
      sys_kernel_version,
      sys_os_version,
      sys_hostname,
      cpu_count,
    }
}
</code></pre>
<p>这里有两个点需要关注的:</p>
<ol>
<li>函数使用<code></code>注解,表明这是一个可供js调用的函数。</li>
<li>函数前使用<code>async</code>关键字修饰,表明这是一个异步执行的函数,这样即使在函数里执行了耗时任务,也不会阻塞UI线程。如果需要在UI线程同步执行,则去掉<code>async</code>即可。</li>
</ol>
<h3 id="注册接口">注册接口</h3>
<p>万事具备,只欠东风。最后,我们只需把我们定义的<code>get_sys_info</code>方法注册到js引擎即可。找到<code>main.rs</code>里的<code>init_js_engine</code>方法,增加以下代码:</p>
<pre><code>js_engine.add_global_func(get_sys_info::new());
</code></pre>
<h1 id="修改ui">修改UI</h1>
<p>接口完成了,现在我们来修改一下我们的UI。</p>
<h3 id="声明接口">声明接口</h3>
<p>如果使用了TS,需要我们声明一下我们使用Rust导出的函数,不然TS编译器会报错。打开<code>deft-env.d.ts</code>文件(或者在项目中新建一个<code>d.ts</code>文件),增加如下内容:</p>
<pre><code>declare interface SysInfo {
    cpuCount: number;
    memTotal: number;
    memFree: number;
    memUsed: number;
    sysName: string;
    sysKernelVersion: string;
    sysOsVersion: string;
    sysHostname: string;
}

declare function get_sys_info(): Promise&lt;SysInfo&gt;
</code></pre>
<h3 id="在js调用rust接口">在JS调用Rust接口</h3>
<p>现在我们可以在JS里调用Rust编写的<code>get_sys_info</code>函数了。打开项目里的<code>App.vue</code>文件,script部分修改为:</p>
<pre><code>const sysInfo = ref({} as SysInfo);

let stopped = false;

async function updateInfoLoop() {
if (stopped) {
    return;
}
sysInfo.value = await get_sys_info();
setTimeout(updateInfoLoop, 1000);
}

updateInfoLoop();

onUnmounted(() =&gt; {
stopped = true;
})

</code></pre>
<p>这里,使用计时器,不停的刷新数据。</p>
<p>最后,在template里把这个数据渲染出来即可。</p>
<pre><code class="language-html">&lt;container :style="{
    alignItems: 'flex-start',
    justifyContent: 'center',
    height: '100%',
    width: '100%',
    gap: 20,
    padding: '0 20',
}"&gt;
    &lt;container :style="{width: '100%', gap: 30}"&gt;
      &lt;container&gt;
      &lt;container&gt;系统名称:{{sysInfo.sysName}}&lt;/container&gt;
      &lt;container&gt;系统版本:{{sysInfo.sysOsVersion}}&lt;/container&gt;
      &lt;container&gt;内核版本:{{sysInfo.sysKernelVersion}}&lt;/container&gt;
      &lt;container&gt;处理器数:{{sysInfo.cpuCount}}&lt;/container&gt;
      &lt;container&gt;总内存量:{{formatMem(sysInfo.memTotal)}}&lt;/container&gt;
      &lt;/container&gt;
   
      &lt;container :style="{width: '100%', gap: 4}"&gt;
      &lt;container :style="{
            width: '100%',
            justifyContent: 'space-between',
            flexDirection: 'row'
      }"&gt;
          &lt;container&gt;已用内存:{{ formatMem(sysInfo.memUsed) }}&lt;/container&gt;
          &lt;container&gt;可用内存:{{ formatMem(sysInfo.memTotal - sysInfo.memUsed) }}&lt;/container&gt;
      &lt;/container&gt;
      &lt;container :style="{width: '100%', height: 10, background: '#333'}"&gt;
          &lt;container :style="{
            width: Math.round(100 * sysInfo.memUsed / sysInfo.memTotal) + '%',
            height: '100%',
            background: '#375fac'
          }"&gt;&lt;/container&gt;
      &lt;/container&gt;
      &lt;/container&gt;
    &lt;/container&gt;
&lt;/container&gt;
</code></pre>
<p>这里的container相当于h5里的div。目前所有元素只支持内联样式,不支持通过class等选择器设置样式。</p>
<p><strong>2025.04.20更新:从v0.4版开始,已支持CSS选择器。</strong></p>
<p>最终运行效果:</p>
<p><img src="https://img2024.cnblogs.com/blog/154217/202504/154217-20250402161022664-831069074.png" alt="" loading="lazy"></p>
<h1 id="打包">打包</h1>
<p>执行下面命令即可打包</p>
<pre><code>npm run build
</code></pre>
<p>打包完成后,会在<code>target/你的系统架构/release/</code>目录下生成一个单独的可执行二进制文件(包含编译后的JS代码),将其拷贝出来即可。</p>
<h1 id="相关链接">相关链接</h1>
<p>Deft框架:https://github.com/deft-ui/deft</p>
<p>Deft文档:https://deft-ui.github.io/guides/what-is-deft/</p>
<p>本文代码:https://github.com/deft-ui/deft-vue-examples/tree/main/system-info</p><br><br>
来源:https://www.cnblogs.com/kason/p/18806159/deft-vue-quick-start
頁: [1]
查看完整版本: 无需WebView,Vue也能开发跨平台桌面应用