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}