From c2eb2614bb2c85e65da52a6b0f163165de9511bd Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 26 Jun 2026 09:52:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(write):=20=E6=81=A2=E5=A4=8D=20cover=5Fuplo?= =?UTF-8?q?ading=20=E4=B8=8A=E4=BC=A0=E4=B8=AD=E4=BF=9D=E5=AD=98=E6=8B=A6?= =?UTF-8?q?=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit code review 指出上一提交(835ada0)移除 on_submit 的 cover_uploading() 拦截是真实回归:uploads_in_flight 只覆盖编辑器内图片上传,不含封面上传 路径(upload_image_file 是独立 async,不更新该计数)。用户选大图后立即 点保存会把空/旧封面落库,封面被静默丢弃。 修复:cover_uploading 改由父组件 write_editor 声明并作为 prop 传入 CoverUploader(与 cover_image 同为双向绑定 signal),恢复 on_submit 的 上传中拦截。子组件仍负责 set,父组件读,接口从 1 个 signal 扩为 2 个 (cover_image + cover_uploading),均为父组件真正需要的状态。 --- src/pages/admin/write.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/pages/admin/write.rs b/src/pages/admin/write.rs index 880453c..c12a84c 100644 --- a/src/pages/admin/write.rs +++ b/src/pages/admin/write.rs @@ -76,6 +76,8 @@ fn write_editor(post_id: Option) -> Element { let mut slug = use_signal(|| "".to_string()); let mut tags = use_signal(|| "".to_string()); let mut cover_image = use_signal(|| "".to_string()); + // 封面上传中状态:由子组件 CoverUploader 写入,本组件在 on_submit 读取以拦截保存。 + let mut cover_uploading = use_signal(|| false); let mut status = use_signal(|| "draft".to_string()); let mut content = use_signal(|| "".to_string()); // 页面与编辑器加载、保存、错误、成功等状态。 @@ -248,6 +250,12 @@ fn write_editor(post_id: Option) -> Element { } drop(in_flight); + // 封面图上传中拦截:防止保存半成品(cover_uploading 由子组件 CoverUploader 写入)。 + if cover_uploading() { + error.set(Some("封面图正在上传,请等待完成后再保存".to_string())); + return; + } + if title().trim().is_empty() { error.set(Some("标题不能为空".to_string())); } @@ -441,7 +449,7 @@ fn write_editor(post_id: Option) -> Element { } // 封面图上传区(抽取为子组件 CoverUploader,见文件末尾)。 - CoverUploader { cover_image } + CoverUploader { cover_image, cover_uploading } } // 编辑器区域 - 整页滚动下用视口高度,保证充裕的编辑空间。 @@ -550,12 +558,20 @@ fn write_editor(post_id: Option) -> Element { /// 通过 `cover_image` signal 与父组件双向绑定——子组件写入最终 URL, /// 父组件读取它用于保存。其余上传中间态(uploading/error/url/drag)对本组件私有。 /// +/// 封面上传子组件。 +/// +/// 封装封面图的全部状态与交互:拖拽/粘贴/选择文件上传、URL 输入、预览、移除。 +/// 通过两个 signal 与父组件双向绑定: +/// - `cover_image`:子组件写入最终 URL,父组件读取用于保存; +/// - `cover_uploading`:子组件上传时置 true,父组件在 `on_submit` 读取以拦截 +/// 上传中的保存(避免封面 URL 尚未落定就提交导致封面丢失)。 +/// 其余上传中间态(error/url/drag)对本组件私有。 +/// /// 从 `write_editor` 抽取以降低 god component 复杂度(见 dioxus-render-purity skill)。 #[component] #[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut, unused_variables))] -fn CoverUploader(cover_image: Signal) -> Element { - // 封面图上传状态:uploading 进度态、错误消息、URL 输入框展开、拖拽高亮。 - let mut cover_uploading = use_signal(|| false); +fn CoverUploader(cover_image: Signal, cover_uploading: Signal) -> Element { + // 封面图上传状态:错误消息、URL 输入框展开、拖拽高亮(cover_uploading 由父组件传入)。 let mut cover_error = use_signal(|| None::); let mut cover_url_mode = use_signal(|| false); let mut cover_drag_active = use_signal(|| false);