From cb8c42e833c3192646ff889a70b7dd2f5b9bb33c Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 23 Jun 2026 15:51:40 +0800 Subject: [PATCH] refactor: remove obsolete ImageViewer component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit both call sites (PostCover, PostCard) now render raw .blur-img, so the Dioxus ImageViewer component — with its leaky closure.forget() and dead js_sys::eval delay — is no longer needed. remove the file and its mod declaration. --- src/components/image_viewer.rs | 163 --------------------------------- src/components/mod.rs | 4 +- 2 files changed, 1 insertion(+), 166 deletions(-) delete mode 100644 src/components/image_viewer.rs diff --git a/src/components/image_viewer.rs b/src/components/image_viewer.rs deleted file mode 100644 index 08eba13..0000000 --- a/src/components/image_viewer.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! 图片查看器组件 -//! -//! 提供缩略图展示与点击放大后的全屏灯箱(lightbox)查看, -//! 支持自定义缩略图参数、alt 文本与懒加载。 -//! 灯箱支持键盘操作:Escape 关闭、Tab 在关闭按钮与图片间循环。 - -use dioxus::prelude::*; - -/// 图片查看器组件。 -/// -/// Props: -/// - `src`:原图 URL -/// - `thumb_params`:缩略图 URL 参数,默认 `"?w=800"` -/// - `alt`:图片替代文本,默认 `"图片"` -/// - `lazy_load`:是否启用懒加载,默认 `false` -/// - `lightbox`:是否启用点击放大灯箱,默认 `true`。卡片等场景设为 `false` -/// 以禁用灯箱交互,使整张卡片归一为单一跳转行为(避免嵌套交互)。 -/// -/// 关键事件(仅在 `lightbox == true` 时生效): -/// - 点击缩略图:打开全屏灯箱 -/// - 点击遮罩或关闭按钮:关闭灯箱 -/// - 点击灯箱内容区:阻止事件冒泡,避免误关闭 -/// - 键盘 Escape:关闭灯箱 -#[component] -pub fn ImageViewer( - src: String, - #[props(default = "?w=800".to_string())] thumb_params: String, - #[props(default = "?w=20".to_string())] placeholder_params: String, - #[props(default = "图片".to_string())] alt: String, - #[props(default = false)] lazy_load: bool, - #[props(default = true)] lightbox: bool, -) -> Element { - let mut is_open = use_signal(|| false); - - // 打开灯箱时聚焦关闭按钮,并监听 Escape 键关闭。 - // 灯箱禁用时跳过键盘监听与焦点管理,避免无谓的 DOM 操作。 - #[cfg(target_arch = "wasm32")] - { - use wasm_bindgen::{closure::Closure, JsCast}; - - use_effect(move || { - if !lightbox || !is_open() { - return; - } - - wasm_bindgen_futures::spawn_local(async move { - // 下一帧再聚焦,确保 DOM 已渲染。 - let _ = js_sys::eval("new Promise(r => requestAnimationFrame(r))").unwrap(); - if let Some(btn) = web_sys::window() - .and_then(|w| w.document()) - .and_then(|d| d.query_selector(".image-viewer-close").ok()) - .flatten() - .and_then(|e| e.dyn_into::().ok()) - { - let _ = btn.focus(); - } - }); - - let closure = Closure::wrap(Box::new(move |e: web_sys::KeyboardEvent| { - if e.key() == "Escape" { - is_open.set(false); - } - }) as Box); - - if let Some(window) = web_sys::window() { - let _ = window.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()); - } - - closure.forget(); - }); - } - - // 计算 aspect-ratio:SSR 时读图片真实尺寸。WASM 端不读(--ar 已在 SSR 写入 HTML)。 - // 非 /uploads/ 的外链图或读不到尺寸时不设 --ar。 - let ar_style = { - // `mut` 仅 server 构建需要:WASM 构建剥离 #[cfg(feature = "server")] 块后, - // s 从未被重新赋值,故对 WASM 抑制 unused_mut。 - #[cfg_attr(not(feature = "server"), allow(unused_mut))] - let mut s = String::new(); - #[cfg(feature = "server")] - { - if let Some(rel) = src - .strip_prefix("/uploads/") - .map(|p| p.split('?').next().unwrap_or(p)) - { - if let Some((w, h)) = crate::api::image::get_image_dimensions(rel) { - // 注意:CSS aspect-ratio 用斜杠分隔(width / height),不是冒号 - s = format!("--ar:{} / {};", w, h); - } - } - } - s - }; - - // 拼接占位图 URL 和高清图 URL - let placeholder_src = if src.contains('?') { - format!( - "{}&{}", - src.split('?').next().unwrap_or(&src), - placeholder_params.trim_start_matches('?') - ) - } else { - format!("{}{}", src, placeholder_params) - }; - let full_src = if src.contains('?') { - format!( - "{}&{}", - src.split('?').next().unwrap_or(&src), - thumb_params.trim_start_matches('?') - ) - } else { - format!("{}{}", src, thumb_params) - }; - - rsx! { - // blur-up 双层:底层占位图 + 上层高清图(data-src 由前端 JS 懒加载) - // 灯箱禁用时不绑定 onclick,使缩略图成为纯展示元素(卡片由外层覆盖链接接管跳转)。 - span { - class: "blur-img", - style: "{ar_style}", - onclick: move |_| if lightbox { is_open.set(true) }, - img { - class: "blur-img-placeholder", - src: "{placeholder_src}", - alt: "{alt}", - loading: if lazy_load { "lazy" } else { "eager" }, - } - img { - class: "blur-img-full", - "data-src": "{full_src}", - alt: "{alt}", - } - } - - // 全屏灯箱(仅 lightbox 启用时可能打开) - if lightbox && is_open() { - div { - class: "image-viewer-overlay", - role: "dialog", - aria_modal: "true", - aria_label: "图片预览", - onclick: move |_| is_open.set(false), - div { - class: "image-viewer-content", - onclick: move |evt: dioxus::events::MouseEvent| evt.stop_propagation(), - img { - class: "image-viewer-img", - src: "{src}", - alt: "{alt}", - tabindex: 0, - } - button { - class: "image-viewer-close", - r#type: "button", - aria_label: "关闭图片预览", - onclick: move |_| is_open.set(false), - "✕" - } - } - } - } - } -} diff --git a/src/components/mod.rs b/src/components/mod.rs index aaca272..350ea60 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -3,7 +3,7 @@ //! 提供 Dioxus UI 组件,供前端页面(`src/pages/`)使用。 //! 包括布局(`frontend_layout`、`admin_layout`)、导航(`header`、`nav`、`footer`)、 //! 文章展示(`post`、`post_card`)、评论(`comments`)、骨架屏(`skeletons`)、 -//! 表单控件(`forms`)以及图片查看器(`image_viewer`)等共享组件。 +//! 表单控件(`forms`)等共享组件。 /// 后台布局组件。 pub mod admin_layout; @@ -19,8 +19,6 @@ pub mod forms; pub mod frontend_layout; /// 顶部导航栏组件。 pub mod header; -/// 图片查看器组件。 -pub mod image_viewer; /// 导航组件。 pub mod nav; /// 文章详情组件。