diff --git a/public/npc.zip b/public/npc.zip new file mode 100644 index 0000000..5691ad2 Binary files /dev/null and b/public/npc.zip differ diff --git a/public/tauri.svg b/public/tauri.svg index 31b62c9..3ebcdf9 100644 --- a/public/tauri.svg +++ b/public/tauri.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg index e7b8dfb..3ebcdf9 100644 --- a/public/vite.svg +++ b/public/vite.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src-tauri/src/server.rs b/src-tauri/src/server.rs index 83a3b58..b623363 100644 --- a/src-tauri/src/server.rs +++ b/src-tauri/src/server.rs @@ -3,7 +3,7 @@ use crate::models::*; use crate::storage::{get_media_type, is_allowed_extension, STORAGE}; use axum::{ body::Body, - extract::{Form, Multipart, State}, + extract::{DefaultBodyLimit, Form, Multipart, State}, http::{Response, StatusCode, Uri}, response::sse::{Event, KeepAlive, Sse}, response::Json, @@ -17,6 +17,7 @@ use std::convert::Infallible; use std::path::PathBuf; use std::sync::Arc; use std::fs; +use tokio::io::AsyncWriteExt; use tokio_stream::wrappers::BroadcastStream; use tokio_stream::StreamExt; use tower_http::cors::CorsLayer; @@ -181,6 +182,7 @@ pub fn create_router(state: AppState) -> Router { .nest_service("/file", ServeDir::new(media_dir_str())) // SPA 回退 → 嵌入式文件(跨平台,编译到二进制中) .fallback_service(get(spa_fallback)) + .layer(DefaultBodyLimit::max(2048 * 1024 * 1024)) // 2GB 上传限制 .layer(CorsLayer::permissive()) .with_state(state) } @@ -290,7 +292,7 @@ async fn list_media() -> Json { async fn upload_handler( mut multipart: Multipart, ) -> Result, (StatusCode, String)> { - while let Ok(Some(field)) = multipart.next_field().await { + while let Ok(Some(mut field)) = multipart.next_field().await { let file_name = field.file_name().unwrap_or("file").to_string(); let ext = std::path::Path::new(&file_name) .extension() @@ -310,12 +312,26 @@ async fn upload_handler( let new_name = format!("{}_{}{}", stem, ts, ext); let save_path = media_dir().join(&new_name); - let data = field.bytes().await.map_err(|e| { - (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) - })?; - fs::write(&save_path, &data).map_err(|e| { - (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) + // 流式写入文件,避免大视频文件占用过多内存 + let mut file = tokio::fs::File::create(&save_path).await.map_err(|e| { + (StatusCode::INTERNAL_SERVER_ERROR, format!("文件创建失败: {}", e)) })?; + loop { + match field.chunk().await { + Ok(Some(chunk)) => { + file.write_all(&chunk).await.map_err(|e| { + (StatusCode::INTERNAL_SERVER_ERROR, format!("写入文件失败: {}", e)) + })?; + } + Ok(None) => break, + Err(e) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!("读取上传数据失败: {}", e), + )) + } + } + } } Ok(Json(serde_json::json!({"ok": true}))) } diff --git a/src/styles/screen.css b/src/styles/screen.css index f00a340..9d91a68 100644 --- a/src/styles/screen.css +++ b/src/styles/screen.css @@ -276,7 +276,7 @@ /* ============ Sound Hint ============ */ #soundHint { position: fixed; - bottom: 48px; + bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 100;