Skip to main content

yggdrasil/hooks/
query.rs

1//! 通用客户端数据加载 hook。
2//!
3//! 封装 `use_effect + spawn + 多个 signal` 的三件套,统一 loading / data / error
4//! 三态与非 wasm cfg 处理。后台 CSR 页面用这套;公共 SSR 页面仍用
5//! `use_server_future`(带 suspend 的真 SSR 预取),二者职责区分明确。
6//!
7//! 当前只提供 `use_paginated`(分页列表)。system tab 的"load_once + use_future
8//! 轮询"形态、dashboard 的多源聚合形态保持手写——强行套 hook 反而更复杂。
9
10use dioxus::prelude::*;
11
12/// 分页列表加载状态。
13///
14/// 各字段是独立 `Signal`,调用方可按名读取、传给子组件,或对 `items` 做
15/// `with_mut` 乐观更新。
16pub struct PaginatedState<T> {
17    /// 是否正在加载。
18    pub loading: Signal<bool>,
19    /// 当前页数据。
20    pub items: Signal<Vec<T>>,
21    /// 总条数(用于分页器)。
22    pub total: Signal<i64>,
23    /// 最近一次加载错误(成功时为 None)。
24    pub error: Signal<Option<String>>,
25}
26
27/// 分页列表加载 hook。
28///
29/// - `page`:返回当前页码的信号读取闭包(返回值变化时自动重新加载)。
30/// - `per_page`:每页条数。
31/// - `fetch`:接收 `(page, per_page)`,返回 `Future<Output = Result<(Vec<T>, i64), E>>`。
32///   `E: Display` 用于把错误写入 `error` signal。
33///
34/// 非 wasm32 下 `loading` 立即置 false(SSR 不发起请求)。
35///
36/// # 例
37///
38/// ```ignore
39/// let state = use_paginated(
40///     || current_page,
41///     POSTS_PER_PAGE,
42///     |p, pp| async move {
43///         list_posts(p, pp, None).await
44///             .map(|r| (r.posts, r.total))
45///             .map_err(|e| e.to_string())
46///     },
47/// );
48/// ```
49#[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut))]
50#[allow(unused_variables)]
51pub fn use_paginated<T, P, F, Fut, E>(page: P, per_page: i32, fetch: F) -> PaginatedState<T>
52where
53    T: PartialEq + Clone + 'static,
54    P: Fn() -> i32 + Copy + 'static,
55    F: Fn(i32, i32) -> Fut + Copy + 'static,
56    Fut: std::future::Future<Output = Result<(Vec<T>, i64), E>> + 'static,
57    E: std::fmt::Display + 'static,
58{
59    let mut loading = use_signal(|| true);
60    let mut items = use_signal(Vec::new);
61    let mut total = use_signal(|| 0_i64);
62    let mut error: Signal<Option<String>> = use_signal(|| None);
63
64    use_effect(move || {
65        let p = page();
66        loading.set(true);
67        error.set(None);
68        #[cfg(target_arch = "wasm32")]
69        {
70            let f = fetch;
71            spawn(async move {
72                match f(p, per_page).await {
73                    Ok((list, t)) => {
74                        items.set(list);
75                        total.set(t);
76                    }
77                    Err(e) => error.set(Some(e.to_string())),
78                }
79                loading.set(false);
80            });
81        }
82        #[cfg(not(target_arch = "wasm32"))]
83        {
84            loading.set(false);
85            // 抚平非 wasm 下未使用的变量(p / per_page / fetch)。
86            let _ = (p, per_page, fetch);
87        }
88    });
89
90    PaginatedState {
91        loading,
92        items,
93        total,
94        error,
95    }
96}