refactor(post-cover): use raw blur-img structure as single lightbox image

drop ImageViewer; render .blur-img with a lightbox-single class (the
class, not a data attribute, because Dioxus' typed span elements reject
arbitrary data-* attrs). lightbox.js now keys single-mode off the
lightbox-single class instead of data-single. cover uses ?w=20
placeholder + ?w=1200 display; SSR writes --ar from real dimensions.
This commit is contained in:
xfy 2026-06-23 15:48:02 +08:00
parent f88d83b0ad
commit 75b9080df8
3 changed files with 54 additions and 13 deletions

View File

@ -92,7 +92,7 @@
// ============ 图像收集 ============ // ============ 图像收集 ============
// 收集所有 selectors命中的 .blur-img 节点。 // 收集所有 selectors命中的 .blur-img 节点。
// gallery: 正文图组成图集singles: 带 data-single 的单张图(如封面)。 // gallery: 正文图组成图集singles: 带 lightbox-single class 的单张图(如封面)。
function collectImages(roots) { function collectImages(roots) {
var gallery = []; var gallery = [];
var singles = []; var singles = [];
@ -100,7 +100,7 @@
var nodes = roots[i].querySelectorAll(".blur-img"); var nodes = roots[i].querySelectorAll(".blur-img");
for (var j = 0; j < nodes.length; j++) { for (var j = 0; j < nodes.length; j++) {
var n = nodes[j]; var n = nodes[j];
if (n.getAttribute("data-single")) { if (n.classList.contains("lightbox-single")) {
singles.push(n); singles.push(n);
} else { } else {
gallery.push(n); gallery.push(n);
@ -124,7 +124,8 @@
var origSrc = originalUrl(dataSrc); var origSrc = originalUrl(dataSrc);
var altText = fullImg.getAttribute("alt") || ""; var altText = fullImg.getAttribute("alt") || "";
var isSingle = var isSingle =
originNode.getAttribute("data-single") === "true" || gallery.length === 0; originNode.classList.contains("lightbox-single") ||
gallery.length === 0;
var vw = window.innerWidth; var vw = window.innerWidth;
var vh = window.innerHeight; var vh = window.innerHeight;

View File

@ -19,7 +19,7 @@ pub fn PostContent(content_html: String) -> Element {
let _ = js_sys::eval(include_str!("../../../public/js/post-content.js")); let _ = js_sys::eval(include_str!("../../../public/js/post-content.js"));
let _ = js_sys::eval(include_str!("../../../public/js/lightbox.js")); let _ = js_sys::eval(include_str!("../../../public/js/lightbox.js"));
let _ = js_sys::eval("window.__initPostContent('.post-content')"); let _ = js_sys::eval("window.__initPostContent('.post-content')");
// 正文图组成图集;封面(.entry-cover)单张模式,由 PostCover 标记 data-single // 正文图组成图集;封面(.entry-cover,带 lightbox-single class单张模式
let _ = js_sys::eval("window.__initLightbox(['.post-content', '.entry-cover'])"); let _ = js_sys::eval("window.__initLightbox(['.post-content', '.entry-cover'])");
}); });

View File

@ -1,26 +1,66 @@
//! 文章封面组件 //! 文章封面组件
//! //!
//! 在文章详情页渲染封面大图,使用图片查看器支持点击放大。 //! 在文章详情页渲染封面大图,使用 blur-up 双层结构与 lightbox.js 灯箱。
//! 封面为单张data-single不参与正文图集切换。
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::components::image_viewer::ImageViewer;
/// 文章封面组件。 /// 文章封面组件。
/// ///
/// Props /// Props
/// - `src`:封面原图 URL /// - `src`:封面原图 URL
/// ///
/// 使用 1200px 宽度的缩略图作为默认封面展示,点击可放大查看。 /// 渲染 `.blur-img` 结构,与正文图片一致;`data-single="true"` 标记为单张,
/// 由 `lightbox.js` 接管点击放大(原地缩放飞出 + 原图展示)。
/// 服务端读取真实尺寸写入 `--ar`,确保占位期间维持正确宽高比。
#[component] #[component]
pub fn PostCover(src: String) -> Element { pub fn PostCover(src: String) -> Element {
// SSR 时读真实尺寸算 --arWASM 端不读HTML 已在 SSR 写入)。
let ar_style = {
#[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
};
// 占位图 ?w=20展示图 ?w=1200灯箱原图由 lightbox.js 去 query 得到。
let placeholder_src = if src.contains('?') {
format!("{}&w=20", src.split('?').next().unwrap_or(&src))
} else {
format!("{}?w=20", src)
};
let full_src = if src.contains('?') {
format!("{}&w=1200", src.split('?').next().unwrap_or(&src))
} else {
format!("{}?w=1200", src)
};
rsx! { rsx! {
figure { class: "entry-cover", figure { class: "entry-cover",
ImageViewer { span {
src: src.clone(), class: "blur-img entry-cover-blur lightbox-single",
thumb_params: "?w=1200", style: "{ar_style}",
alt: "封面图片".to_string(), img {
lazy_load: false, class: "blur-img-placeholder",
src: "{placeholder_src}",
alt: "封面图片",
}
img {
class: "blur-img-full",
"data-src": "{full_src}",
alt: "封面图片",
}
} }
} }
} }