当然, 我们可以使用 Next. Js 框架来重写这个 Tauri 应用。以下是使用 Next. Js 和 Bun 的步骤:
- 首先, 创建一个新的 Next. Js 项目:
bun create next-app calculator-app
cd calculator-app- 安装必要的依赖:
bun add @tauri-apps/api @tauri-apps/cli unocss
bun add -d @types/node @types/react @types/react-dom typescript- 在项目根目录创建
src-tauri文件夹, 并在其中创建src/main.rs:
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use tauri::Manager;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct CalculationResult {
result: f64,
}
#[tauri::command]
fn calculate(operation: String, values: Vec<f64>) -> CalculationResult {
let result = match operation.as_str() {
"sin" => values[0].sin(),
"cos" => values[0].cos(),
"tan" => values[0].tan(),
"mean" => values.iter().sum::<f64>() / values.len() as f64,
"median" => {
let mut sorted = values.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mid = sorted.len() / 2;
if sorted.len() % 2 == 0 {
(sorted[mid - 1] + sorted[mid]) / 2.0
} else {
sorted[mid]
}
},
_ => 0.0,
};
CalculationResult { result }
}
fn main() {
tauri::Builder::default()
.setup(|app| {
#[cfg(debug_assertions)]
{
let window = app.get_window("main").unwrap();
window.open_devtools();
}
Ok(())
})
.invoke_handler(tauri::generate_handler![calculate])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}- 修改
src/pages/index.tsx:
import React, { useState } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
const themes = {
light: 'bg-white text-black',
dark: 'bg-gray-800 text-white',
colorful: 'bg-gradient-to-r from-purple-400 via-pink-500 to-red-500 text-white',
};
export default function Home() {
const [operation, setOperation] = useState('');
const [values, setValues] = useState('');
const [result, setResult] = useState<number | null>(null);
const [theme, setTheme] = useState('light');
const [layout, setLayout] = useState('vertical');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const res: { result: number } = await invoke('calculate', {
operation,
values: values.split(',').map(Number),
});
setResult(res.result);
} catch (error) {
console.error('Calculation error:', error);
}
};
const handleNumberClick = (num: number) => {
setValues(prev => prev + (prev ? ',' : '') + num);
};
const handleClear = () => {
setValues('');
setResult(null);
};
return (
<div className={`p-4 ${themes[theme as keyof typeof themes]} ${layout === 'vertical' ? 'flex flex-col' : 'flex flex-row'}`}>
<div className="flex-1">
<h1 className="text-2xl font-bold mb-4">Complex Calculator</h1>
<form onSubmit={handleSubmit} className="mb-4">
<select
value={operation}
onChange={(e) => setOperation (e.target. Value)}
ClassName="w-full p-2 mb-2 border rounded"
>
<option value="">Select operation</option>
<option value="sin">Sin</option>
<option value="cos">Cos</option>
<option value="tan">Tan</option>
<option value="mean">Mean</option>
<option value="median">Median</option>
</select>
<input
type="text"
value={values}
onChange={(e) => setValues(e.target.value)}
placeholder="Enter values (comma-separated)"
className="w-full p-2 mb-2 border rounded"
/>
<button type="submit" className="w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600">Calculate</button>
</form>
{result !== null && <p className="text-xl">Result: {result}</p>}
<div className="grid grid-cols-3 gap-2 mt-4">
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 0].map((num) => (
<button
key={num}
onClick={() => handleNumberClick(num)}
className="p-2 bg-gray-200 rounded hover:bg-gray-300"
>
{num}
</button>
))}
<button
onClick={handleClear}
className="p-2 bg-red-500 text-white rounded hover:bg-red-600 col-span-3"
>
Clear
</button>
</div>
</div>
<div className={`${layout === 'vertical' ? 'mt-4' : 'ml-4'}`}>
<h2 className="text-xl font-bold mb-2">Customize</h2>
<select
value={theme}
onChange={(e) => setTheme(e.target.value)}
className="w-full p-2 mb-2 border rounded"
>
<option value="light">Light Theme</option>
<option value="dark">Dark Theme</option>
<option value="colorful">Colorful Theme</option>
</select>
<select
value={layout}
onChange={(e) => setLayout(e.target.value)}
className="w-full p-2 mb-2 border rounded"
>
<option value="vertical">Vertical Layout</option>
<option value="horizontal">Horizontal Layout</option>
</select>
</div>
</div>
);
}- 配置 UnoCSS:
创建 uno.config.ts:
import { defineConfig } from 'unocss'
import presetUno from '@unocss/preset-uno'
export default defineConfig({
presets: [presetUno()],
})- 修改
next.config.js:
const UnoCSS = require('@unocss/webpack').default
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config) => {
config.plugins.push(UnoCSS())
return config
},
}
module.exports = nextConfig- 在
src/pages/_app.tsx中导入 UnoCSS:
import '@unocss/reset/tailwind.css'
import 'uno.css'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp- 更新
package.json:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^1.0.0",
"next": "12.0.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"@tauri-apps/cli": "^1.0.0",
"@types/node": "^14.14.31",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"typescript": "^4.3.2",
"unocss": "^0.53.0"
}
}- 运行应用:
Bun run tauri dev- 构建应用:
Bun run tauri build这个版本将计算器应用改写成了一个使用 Next. Js 框架的 Tauri 桌面应用。主要变化包括:
- 使用 Next. Js 替代了 Vite 作为前端框架。
- 配置了 UnoCSS 以在 Next. Js 中使用。
- 调整了项目结构以适应 Next. Js 的约定。
注意, 虽然我们使用了 Next. Js, 但在 Tauri 应用中, 我们实际上并不会使用 Next. Js 的服务器端渲染功能。这里使用 Next. Js 主要是为了利用其提供的开发体验和工具链。