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;