Merge pull request #18 from PANDAJSR/tauri

修复了 Tauri 版本的亿些bug到可以正常运行的程度
This commit is contained in:
2026-03-17 22:26:17 +08:00
committed by GitHub
19 changed files with 226 additions and 64 deletions
+7
View File
@@ -6,15 +6,22 @@ node_modules
# Build outputs # Build outputs
dist dist
dist-ssr dist-ssr
out
*.local *.local
# Tauri # Tauri
src-tauri/target src-tauri/target
src-tauri/Cargo.lock src-tauri/Cargo.lock
src-tauri/gen src-tauri/gen
.cargo-local/
# Database # Database
data.sql data.sql
*.sqlite
*.sqlite3
*.db
*.db-shm
*.db-wal
# Logs # Logs
logs logs
+1
View File
@@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>SecScore</title> <title>SecScore</title>
</head> </head>
+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="12" fill="#0052D9" />
<path
d="M16 22c0-3.314 2.686-6 6-6h20c3.314 0 6 2.686 6 6v20c0 3.314-2.686 6-6 6H22c-3.314 0-6-2.686-6-6V22z"
fill="#ffffff"
/>
<path
d="M24 28h16a2 2 0 110 4H24a2 2 0 110-4zm0 8h10a2 2 0 110 4H24a2 2 0 110-4z"
fill="#0052D9"
/>
</svg>

After

Width:  |  Height:  |  Size: 379 B

-1
View File
@@ -1,5 +1,4 @@
use parking_lot::RwLock; use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use tauri::State; use tauri::State;
+1 -1
View File
@@ -1,4 +1,4 @@
use chrono::{Datelike, Duration, TimeZone, Timelike, Utc}; use chrono::{Datelike, Duration, Timelike, Utc};
use parking_lot::RwLock; use parking_lot::RwLock;
use sea_orm::{ use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set, ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
+1 -1
View File
@@ -1,5 +1,5 @@
use parking_lot::RwLock; use parking_lot::RwLock;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryOrder, Set}; use sea_orm::{ActiveModelTrait, EntityTrait, QueryOrder, Set};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use tauri::State; use tauri::State;
-2
View File
@@ -4,8 +4,6 @@ use tauri::{AppHandle, Manager};
use crate::state::AppState; use crate::state::AppState;
use super::response::IpcResponse;
#[tauri::command] #[tauri::command]
pub async fn window_minimize( pub async fn window_minimize(
app: AppHandle, app: AppHandle,
+2 -2
View File
@@ -29,7 +29,7 @@ pub fn setup_app(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
} }
fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> { fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle(); let handle = app.handle().clone();
let db_path = if cfg!(debug_assertions) { let db_path = if cfg!(debug_assertions) {
std::path::PathBuf::from("data.sql") std::path::PathBuf::from("data.sql")
} else { } else {
@@ -52,7 +52,7 @@ fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
match create_sqlite_connection(&db_path_str).await { match create_sqlite_connection(&db_path_str).await {
Ok(conn) => { Ok(conn) => {
let state = handle.state::<crate::state::SafeAppState>(); let state = handle.state::<crate::state::SafeAppState>();
let mut state_guard = state.write(); let state_guard = state.write();
let mut db_guard = state_guard.db.write(); let mut db_guard = state_guard.db.write();
*db_guard = Some(conn); *db_guard = Some(conn);
eprintln!("Database connected to: {}", db_path_str); eprintln!("Database connected to: {}", db_path_str);
+1 -1
View File
@@ -9,9 +9,9 @@ fn main() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.setup(|app| { .setup(|app| {
setup_app(app)?;
let state = AppState::new(app.handle().clone()); let state = AppState::new(app.handle().clone());
app.manage(Arc::new(RwLock::new(state))); app.manage(Arc::new(RwLock::new(state)));
setup_app(app)?;
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
+27 -9
View File
@@ -15,16 +15,34 @@ function MainContent(): React.JSX.Element {
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
useEffect(() => { useEffect(() => {
if (!(window as any).api) return const api = (window as any).api
const unlisten = (window as any).api.onNavigate((route: string) => { if (!api) return
const currentPath = location.pathname === "/" ? "/home" : location.pathname
const targetPath = route === "/" ? "/home" : route
if (currentPath !== targetPath) { let disposed = false
navigate(route) let unlisten: (() => void) | null = null
}
}) api
return () => unlisten() .onNavigate((route: string) => {
const currentPath = location.pathname === "/" ? "/home" : location.pathname
const targetPath = route === "/" ? "/home" : route
if (currentPath !== targetPath) {
navigate(route)
}
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, [navigate, location.pathname]) }, [navigate, location.pathname])
const [wizardVisible, setWizardVisible] = useState(false) const [wizardVisible, setWizardVisible] = useState(false)
+5 -1
View File
@@ -2,6 +2,7 @@ import React, { Suspense, lazy } from "react"
import { Layout, Space, Button, Tag, Spin } from "antd" import { Layout, Space, Button, Tag, Spin } from "antd"
import { Routes, Route, Navigate } from "react-router-dom" import { Routes, Route, Navigate } from "react-router-dom"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { WindowControls } from "./WindowControls"
const Home = lazy(() => import("./Home").then((m) => ({ default: m.Home }))) const Home = lazy(() => import("./Home").then((m) => ({ default: m.Home })))
const StudentManager = lazy(() => const StudentManager = lazy(() =>
@@ -67,6 +68,7 @@ export function ContentArea({
background: "var(--ss-header-bg)", background: "var(--ss-header-bg)",
borderBottom: "1px solid var(--ss-border-color)", borderBottom: "1px solid var(--ss-border-color)",
flexShrink: 0, flexShrink: 0,
WebkitAppRegion: "drag",
} as React.CSSProperties } as React.CSSProperties
} }
> >
@@ -76,7 +78,8 @@ export function ContentArea({
{ {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
paddingRight: "12px", paddingRight: "8px",
WebkitAppRegion: "no-drag",
} as React.CSSProperties } as React.CSSProperties
} }
> >
@@ -94,6 +97,7 @@ export function ContentArea({
)} )}
</Space> </Space>
</div> </div>
<WindowControls />
</div> </div>
<Content style={{ flex: 1, overflowY: "auto" }}> <Content style={{ flex: 1, overflowY: "auto" }}>
+28 -12
View File
@@ -95,20 +95,36 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
useEffect(() => { useEffect(() => {
loadAll() loadAll()
if (!(window as any).api) return const api = (window as any).api
const unsubscribe = (window as any).api.onSettingChanged((change: any) => { if (!api) return
setSettings((prev) => {
if (change?.key === "log_level") return { ...prev, log_level: change.value } let disposed = false
if (change?.key === "is_wizard_completed") let unlisten: (() => void) | null = null
return { ...prev, is_wizard_completed: change.value }
if (change?.key === "window_zoom") return { ...prev, window_zoom: change.value } api
if (change?.key === "auto_score_enabled") .onSettingChanged((change: any) => {
return { ...prev, auto_score_enabled: change.value } setSettings((prev) => {
return prev if (change?.key === "log_level") return { ...prev, log_level: change.value }
if (change?.key === "is_wizard_completed")
return { ...prev, is_wizard_completed: change.value }
if (change?.key === "window_zoom") return { ...prev, window_zoom: change.value }
if (change?.key === "auto_score_enabled")
return { ...prev, auto_score_enabled: change.value }
return prev
})
}) })
}) .then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => { return () => {
if (typeof unsubscribe === "function") unsubscribe() disposed = true
if (unlisten) unlisten()
} }
}, []) }, [])
+23 -8
View File
@@ -39,15 +39,30 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
const handleStatusChange = () => { const handleStatusChange = () => {
loadDbStatus() loadDbStatus()
} }
if ((window as any).api) { const api = (window as any).api
const unsubscribe = (window as any).api.onSettingChanged( if (!api) return
(change: { key: string; value: any }) => {
if (change.key === "pg_connection_status") { let disposed = false
handleStatusChange() let unlisten: (() => void) | null = null
}
api
.onSettingChanged((change: { key: string; value: any }) => {
if (change.key === "pg_connection_status") {
handleStatusChange()
} }
) })
return unsubscribe .then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
} }
}, []) }, [])
+27 -6
View File
@@ -16,13 +16,34 @@ export function TitleBar({ children }: TitleBarProps): React.JSX.Element {
const [isMaximized, setIsMaximized] = useState(false) const [isMaximized, setIsMaximized] = useState(false)
useEffect(() => { useEffect(() => {
if (!(window as any).api) return const api = (window as any).api
;(window as any).api.windowIsMaximized().then((v: boolean) => setIsMaximized(v)) if (!api) return
const cleanup = (window as any).api.onWindowMaximizedChanged((maximized: boolean) => { let disposed = false
setIsMaximized(maximized) let unlisten: (() => void) | null = null
})
return cleanup api
.windowIsMaximized()
.then((v: boolean) => setIsMaximized(v))
.catch(() => void 0)
api
.onWindowMaximizedChanged((maximized: boolean) => {
setIsMaximized(maximized)
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, []) }, [])
const minimize = () => { const minimize = () => {
+27 -6
View File
@@ -11,13 +11,34 @@ export function WindowControls(): React.JSX.Element {
const [isMaximized, setIsMaximized] = useState(false) const [isMaximized, setIsMaximized] = useState(false)
useEffect(() => { useEffect(() => {
if (!(window as any).api) return const api = (window as any).api
;(window as any).api.windowIsMaximized().then((v: boolean) => setIsMaximized(v)) if (!api) return
const cleanup = (window as any).api.onWindowMaximizedChanged((maximized: boolean) => { let disposed = false
setIsMaximized(maximized) let unlisten: (() => void) | null = null
})
return cleanup api
.windowIsMaximized()
.then((v: boolean) => setIsMaximized(v))
.catch(() => void 0)
api
.onWindowMaximizedChanged((maximized: boolean) => {
setIsMaximized(maximized)
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, []) }, [])
const minimize = () => { const minimize = () => {
+23 -10
View File
@@ -120,23 +120,36 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre
}, [applyThemeConfig]) }, [applyThemeConfig])
useEffect(() => { useEffect(() => {
if (!(window as any).api) return const api = (window as any).api
if (!api) return
;(async () => { ;(async () => {
await loadThemes() await loadThemes()
await loadCurrentTheme() await loadCurrentTheme()
})() })()
const unsubscribe = (window as any).api.onThemeChanged((theme: themeConfig) => { let disposed = false
setCurrentTheme(theme) let unlisten: (() => void) | null = null
currentThemeRef.current = theme
applyThemeConfig(theme) api
loadThemes() .onThemeChanged((theme: themeConfig) => {
}) setCurrentTheme(theme)
currentThemeRef.current = theme
applyThemeConfig(theme)
loadThemes()
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => { return () => {
if (typeof unsubscribe === "function") { disposed = true
unsubscribe() if (unlisten) unlisten()
}
} }
}, [applyThemeConfig, loadCurrentTheme, loadThemes]) }, [applyThemeConfig, loadCurrentTheme, loadThemes])
+6 -1
View File
@@ -7,6 +7,11 @@ import App from "./App"
import { ClientContext } from "./ClientContext" import { ClientContext } from "./ClientContext"
import { StudentService } from "./services/StudentService" import { StudentService } from "./services/StudentService"
import { ServiceProvider } from "./contexts/ServiceContext" import { ServiceProvider } from "./contexts/ServiceContext"
import { api } from "./preload/types"
if (!(window as any).api) {
;(window as any).api = api
}
const ctx = new ClientContext() const ctx = new ClientContext()
new StudentService(ctx) new StudentService(ctx)
@@ -19,7 +24,7 @@ const safeWriteLog = (payload: {
try { try {
const api = (window as any).api const api = (window as any).api
if (!api?.writeLog) return if (!api?.writeLog) return
api.writeLog(payload) Promise.resolve(api.writeLog(payload)).catch(() => void 0)
} catch { } catch {
return return
} }
+32 -3
View File
@@ -173,6 +173,9 @@ const api = {
windowMaximize: (): Promise<boolean> => invoke("window_maximize"), windowMaximize: (): Promise<boolean> => invoke("window_maximize"),
windowClose: (): Promise<void> => invoke("window_close"), windowClose: (): Promise<void> => invoke("window_close"),
windowIsMaximized: (): Promise<boolean> => invoke("window_is_maximized"), windowIsMaximized: (): Promise<boolean> => invoke("window_is_maximized"),
toggleDevTools: (): Promise<void> => invoke("toggle_devtools"),
windowResize: (width: number, height: number): Promise<void> =>
invoke("window_resize", { width, height }),
onWindowMaximizedChanged: (callback: (maximized: boolean) => void): Promise<UnlistenFn> => { onWindowMaximizedChanged: (callback: (maximized: boolean) => void): Promise<UnlistenFn> => {
return listen<boolean>("window:maximized-changed", (event) => { return listen<boolean>("window:maximized-changed", (event) => {
callback(event.payload) callback(event.payload)
@@ -185,15 +188,19 @@ const api = {
}, },
// Logger // Logger
queryLogs: (params?: { lines?: number }): Promise<{ success: boolean; data: string[] }> => queryLogs: (
invoke("log_query", { params }), input?: number | { lines?: number }
): Promise<{ success: boolean; data: string[] }> => {
const lines = typeof input === "number" ? input : input?.lines
return invoke("log_query", { lines })
},
clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"), clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"),
setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }), setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }),
writeLog: (payload: { writeLog: (payload: {
level: string level: string
message: string message: string
meta?: any meta?: any
}): Promise<{ success: boolean }> => invoke("log_write", payload), }): Promise<{ success: boolean }> => invoke("log_write", { payload }),
// Database Connection // Database Connection
dbTestConnection: ( dbTestConnection: (
@@ -291,6 +298,28 @@ const api = {
ruleIds: number[] ruleIds: number[]
): Promise<{ success: boolean; data?: boolean; message?: string }> => ): Promise<{ success: boolean; data?: boolean; message?: string }> =>
invoke("auto_score_sort_rules", { ruleIds }), invoke("auto_score_sort_rules", { ruleIds }),
// Generic invoke wrapper for backward compatibility with callers using `api.invoke`
invoke: async (channel: string, ...args: any[]): Promise<any> => {
switch (channel) {
case "auto-score:getRules":
return api.autoScoreGetRules()
case "auto-score:addRule":
return api.autoScoreAddRule(args[0])
case "auto-score:updateRule":
return api.autoScoreUpdateRule(args[0])
case "auto-score:deleteRule":
return api.autoScoreDeleteRule(args[0])
case "auto-score:toggleRule":
return api.autoScoreToggleRule(args[0])
case "auto-score:sortRules":
return api.autoScoreSortRules(args[0])
case "auto-score:getStatus":
return api.autoScoreGetStatus()
default:
throw new Error(`Unsupported legacy invoke channel: ${channel}`)
}
},
} }
export default api export default api
+4
View File
@@ -9,6 +9,10 @@ export default defineConfig({
"@": resolve(__dirname, "src"), "@": resolve(__dirname, "src"),
}, },
}, },
optimizeDeps: {
// Avoid scanning legacy `old-ss` entries under project root.
entries: ["index.html"],
},
build: { build: {
outDir: "dist", outDir: "dist",
emptyOutDir: true, emptyOutDir: true,