feat: complete application

This commit is contained in:
zihluwang committed 2025-07-26 09:18:56 +08:00
1 parent 21546b278a
commit 2d52b8df56
10 files changed
+236 -72

No files matched your search

+74
View File
@@ -17,6 +17,18 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.3"
@@ -910,6 +922,18 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.3.0"
@@ -1394,12 +1418,30 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown 0.14.5",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@@ -1905,6 +1947,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.9.4" version = "0.9.4"
@@ -2963,6 +3016,20 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "rusqlite"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"bitflags 2.9.1",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.25" version = "0.1.25"
@@ -4259,6 +4326,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version-compare" name = "version-compare"
version = "0.2.0" version = "0.2.0"
@@ -5163,6 +5236,7 @@ name = "zuanbot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"rusqlite",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
+1
View File
@@ -23,4 +23,5 @@ tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
anyhow = { version = "^1.0.98" } anyhow = { version = "^1.0.98" }
rusqlite = { version = "0.32", features = ["bundled"] }
BIN
View File
Binary file not shown.
+22 -3
View File
@@ -1,10 +1,29 @@
use tauri::{State};
use rusqlite::{OptionalExtension};
use crate::database::Database; // 自己定义的含互斥锁包装 SQLite 连接的结构
#[tauri::command] #[tauri::command]
pub fn greet(name: &str) -> String { pub fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name) format!("Hello, {}! You've been greeted from Rust!", name)
} }
#[tauri::command] #[tauri::command]
pub fn get_message(level: &str) -> String { pub async fn get_message(state: State<'_, Database>, level: Option<String>) -> Result<String, String> {
println!("level = {level}"); let conn = state.connection().lock().map_err(|e| e.to_string())?;
"".to_string()
let mut stmt = if let Some(ref _level) = level {
conn.prepare("SELECT `text` FROM `main` WHERE LEVEL = ?1 ORDER BY RANDOM() LIMIT 1")
.map_err(|e| e.to_string())?
} else {
conn.prepare("SELECT `text` FROM `main` ORDER BY RANDOM() LIMIT 1")
.map_err(|e| e.to_string())?
};
let message_opt: Option<String> = if let Some(_level) = &level {
stmt.query_row([_level], |row| row.get(0)).optional().map_err(|e| e.to_string())?
} else {
stmt.query_row([], |row| row.get(0)).optional().map_err(|e| e.to_string())?
};
Ok(message_opt.unwrap_or_default())
} }
-1
View File
@@ -1 +0,0 @@
pub const APP_NAME: &str = "ZuAnBot";
+37
View File
@@ -0,0 +1,37 @@
use rusqlite::Connection;
use std::path::PathBuf;
use std::sync::Mutex;
use anyhow::Result;
use tauri::Manager;
pub struct Database {
conn: Mutex<Connection>,
}
impl Database {
/// 初始化数据库连接
pub fn new(app_handle: &tauri::AppHandle) -> Result<Self> {
let db_path = get_bundled_database_path(app_handle)?;
let conn = Connection::open_with_flags(
db_path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
)?;
Ok(Database { conn: Mutex::from(conn) })
}
/// 获取数据库连接的引用
pub fn connection(&self) -> &Mutex<Connection> {
&self.conn
}
}
/// 获取打包的数据库文件路径
fn get_bundled_database_path(app_handle: &tauri::AppHandle) -> Result<PathBuf> {
let resource_path = app_handle
.path()
.resolve("data.db", tauri::path::BaseDirectory::Resource)?;
println!("Database path: {:?}", resource_path);
Ok(resource_path)
}
-16
View File
@@ -1,16 +0,0 @@
use anyhow::Result;
use std::path::PathBuf;
/// Get system-wide data dir.
pub fn get_global_data_dir() -> Result<PathBuf> {
#[cfg(target_os = "macos")]
let global_data_dir = PathBuf::from("/Library/Application Support/");
#[cfg(target_os = "windows")]
let global_data_dir = PathBuf::from("C:\\ProgramData\\");
#[cfg(target_os = "linux")]
let global_data_dir = PathBuf::from("/var/lib/");
Ok(global_data_dir)
}
+20 -4
View File
@@ -1,14 +1,30 @@
use crate::commands::{greet, get_message}; use tauri::Manager;
use crate::commands::{get_message};
use crate::database::Database;
mod commands; mod commands;
mod dirs; mod database;
mod constants;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet, get_message]) .setup(|app| {
// 初始化数据库
match Database::new(app.handle()) {
Ok(db) => {
println!("Database initialized successfully");
// 可以将数据库实例存储到应用状态中,供后续使用
app.manage(db);
},
Err(e) => {
eprintln!("Failed to initialize database: {}", e);
return Err(Box::from(e));
}
}
Ok(())
})
.invoke_handler(tauri::generate_handler![get_message])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
+3
View File
@@ -30,6 +30,9 @@
"icons/128x128@2x.png", "icons/128x128@2x.png",
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
],
"resources": [
"./data.db"
] ]
} }
} }
+79 -48
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react" import React, { useEffect, useState } from "react"
import { invoke } from "@tauri-apps/api/core" import { invoke } from "@tauri-apps/api/core"
enum Level { enum Level {
@@ -6,60 +6,91 @@ enum Level {
max = "max", max = "max",
} }
function App() { export default function App() {
const [greetMsg, setGreetMsg] = useState<string>("") // 文本内容状态
const [level, setLevel] = useState<Level>(Level.min) const [text, setText] = useState<string>("")
const [dbInfo, setDbInfo] = useState<string>("") const [isLast, setIsLast] = useState<boolean>(false)
const [last, setLast] = useState<string>("")
async function getMessage() { useEffect(() => {
setGreetMsg(await invoke("get_message", { level })) ;(async () => {
await getMessage()
})()
}, [])
const getMessage = async (level?: Level) => {
// 保存上一条信息
setLast(text)
// 获取下一条信息
const nextMessage = await invoke<string>("get_message", { level })
setText(nextMessage)
} }
async function queryDatabase() { const showLast = () => {
try { setIsLast(true)
const result = await invoke("query_database") setText(last)
setDbInfo(result as string) }
} catch (error) {
setDbInfo(`Error: ${error}`) // 复制文字到剪贴板
const copyToClipboard = () => {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(
() => {
alert("复制成功!")
},
() => {
alert("复制失败,请手动复制!")
}
)
} else {
alert("当前浏览器不支持复制功能")
} }
} }
function onChange(event: React.ChangeEvent<HTMLSelectElement>) {
const value = event.target.value as Level
setLevel(value)
}
return ( return (
<main className="container"> <div className="min-h-screen bg-gray-100 text-gray-700 flex flex-col">
<h1>Welcome to Tauri + React</h1> <header className="bg-purple-700 text-white flex flex-col md:flex-row items-center md:justify-between px-6 py-5 sticky top-0 z-40 shadow-md">
<h3 className="text-xl font-semibold mb-2 md:mb-0">骂人宝典</h3>
<nav className="flex space-x-6 text-sm md:text-base"></nav>
</header>
<div className="row"> <main className="flex-grow max-w-4xl mx-auto p-4">
<select value={level} id="greet-input" onChange={onChange}> <section className="mb-6 flex flex-wrap justify-center gap-4">
<option value={Level.min}>普通</option> <button
<option value={Level.max}>火力全开</option> onClick={() => getMessage(Level.min)}
</select> className="bg-pink-600 hover:bg-pink-700 text-white font-semibold py-3 px-6 rounded shadow-md transition">
<button 😤 火力全开
type="button" </button>
onClick={() => {
void getMessage() <button
}}> onClick={() => getMessage(Level.max)}
Get Message className="bg-teal-500 hover:bg-teal-600 text-white font-semibold py-3 px-6 rounded shadow-md transition">
</button> 🙂 口吐莲花
</div> </button>
<p>{greetMsg}</p> </section>
<div className="row"> {text}
<button
type="button" <section className="mb-6 flex flex-wrap justify-center items-center gap-4">
onClick={() => { <button
void queryDatabase() onClick={showLast}
}}> disabled={isLast}
Query Database className="bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded shadow-md transition">
</button> 上一条
</div> </button>
<p>{dbInfo}</p>
</main> <button
onClick={copyToClipboard}
className="bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded shadow-md transition">
复制
</button>
</section>
<div className="text-center pt-8 text-sm text-red-600">
⚠️ 不管怎么说骂人都是不对的,请不要主动攻击别人。
</div>
</main>
</div>
) )
} }
export default App