refactor: remove obsolete ImageViewer component
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.
This commit is contained in:
parent
8b1d55613e
commit
cb8c42e833
@ -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::<web_sys::HtmlElement>().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<dyn FnMut(_)>);
|
|
||||||
|
|
||||||
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),
|
|
||||||
"✕"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,7 +3,7 @@
|
|||||||
//! 提供 Dioxus UI 组件,供前端页面(`src/pages/`)使用。
|
//! 提供 Dioxus UI 组件,供前端页面(`src/pages/`)使用。
|
||||||
//! 包括布局(`frontend_layout`、`admin_layout`)、导航(`header`、`nav`、`footer`)、
|
//! 包括布局(`frontend_layout`、`admin_layout`)、导航(`header`、`nav`、`footer`)、
|
||||||
//! 文章展示(`post`、`post_card`)、评论(`comments`)、骨架屏(`skeletons`)、
|
//! 文章展示(`post`、`post_card`)、评论(`comments`)、骨架屏(`skeletons`)、
|
||||||
//! 表单控件(`forms`)以及图片查看器(`image_viewer`)等共享组件。
|
//! 表单控件(`forms`)等共享组件。
|
||||||
|
|
||||||
/// 后台布局组件。
|
/// 后台布局组件。
|
||||||
pub mod admin_layout;
|
pub mod admin_layout;
|
||||||
@ -19,8 +19,6 @@ pub mod forms;
|
|||||||
pub mod frontend_layout;
|
pub mod frontend_layout;
|
||||||
/// 顶部导航栏组件。
|
/// 顶部导航栏组件。
|
||||||
pub mod header;
|
pub mod header;
|
||||||
/// 图片查看器组件。
|
|
||||||
pub mod image_viewer;
|
|
||||||
/// 导航组件。
|
/// 导航组件。
|
||||||
pub mod nav;
|
pub mod nav;
|
||||||
/// 文章详情组件。
|
/// 文章详情组件。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user