Skip to main content

yggdrasil/components/
empty_state.rs

1//! 空列表、无搜索结果等状态共用的插画与行动入口。
2
3use dioxus::prelude::*;
4
5use crate::components::ui::BTN_PRIMARY;
6
7const EMPTY_STATE_IMAGES: &[&str] = &[
8    "/images/empty-state/dog-camera.webp",
9    "/images/empty-state/dog-wind-chime.webp",
10    "/images/empty-state/dog-delivery.webp",
11    "/images/empty-state/dog-pajamas.webp",
12    "/images/empty-state/dog-watermelon.webp",
13    "/images/empty-state/dog-heart.webp",
14    "/images/empty-state/dog-head-pat.webp",
15    "/images/empty-state/dog-mirror.webp",
16    "/images/empty-state/dog-chopsticks.webp",
17    "/images/empty-state/dog-grass.webp",
18    "/images/empty-state/dog-polaroid.webp",
19    "/images/empty-state/dog-autumn-leaf.webp",
20    "/images/empty-state/dog-laptop.webp",
21    "/images/empty-state/dog-heartbeat.webp",
22    "/images/empty-state/dog-travel.webp",
23    "/images/empty-state/dog-ring.webp",
24    "/images/empty-state/dog-hug.webp",
25    "/images/empty-state/dog-sparklers.webp",
26];
27
28/// 空状态行动按钮。跳转或重试由调用方负责。
29#[derive(Props, Clone, PartialEq)]
30pub struct EmptyStateAction {
31    #[props(into)]
32    pub label: String,
33    pub onclick: EventHandler<()>,
34}
35
36/// 展示标题、说明与随机小狗插画,可选行动按钮或指定配图。
37#[component]
38pub fn EmptyState(
39    #[props(into, default = "还没有文章".to_string())] title: String,
40    #[props(into, default = String::new())] description: String,
41    /// 指定配图时覆盖随机图库;空字符串使用随机图库。
42    #[props(into, default = String::new())]
43    image: String,
44    #[props(default)] action: Option<EmptyStateAction>,
45) -> Element {
46    // 首次 SSR 与 hydration 使用同一张图;挂载后随机一次,后续重绘保持不变。
47    let selected = use_signal(|| 0_usize);
48    use_effect(move || {
49        #[cfg(target_arch = "wasm32")]
50        {
51            let mut selected = selected;
52            selected.set((js_sys::Math::random() * EMPTY_STATE_IMAGES.len() as f64) as usize);
53        }
54    });
55    let src = if image.is_empty() {
56        EMPTY_STATE_IMAGES[selected()]
57    } else {
58        &image
59    };
60
61    rsx! {
62        section { class: "empty-state",
63            div { class: "empty-state-art",
64                img { src: "{src}", alt: "", width: "192", height: "192", draggable: "false" }
65            }
66            span { class: "empty-state-kicker", "YGGDRASIL / 一处留白" }
67            h2 { "{title}" }
68            if !description.is_empty() {
69                p { class: "empty-state-description", "{description}" }
70            }
71            if let Some(act) = action {
72                button {
73                    class: "{BTN_PRIMARY} empty-state-action",
74                    r#type: "button",
75                    onclick: move |_| act.onclick.call(()),
76                    "{act.label}"
77                }
78            }
79        }
80    }
81}