Skip to main content

yggdrasil/pages/admin/settings/
cache_section.rs

1//! 图片磁盘缓存配置分区。
2//!
3//! 即时生效层:清理任务每小时 tick 时从 runtime accessor 读取最新值。
4
5use dioxus::prelude::*;
6
7#[cfg(target_arch = "wasm32")]
8use crate::api::settings::{get_image_cache_settings, update_image_cache_settings};
9#[cfg(target_arch = "wasm32")]
10use crate::components::forms::{FormLabel, INPUT_CLASS};
11#[cfg(target_arch = "wasm32")]
12use crate::components::ui::{LoadingButton, ADMIN_CARD_CLASS};
13#[cfg(target_arch = "wasm32")]
14use crate::models::settings::ImageCacheSettings;
15
16/// 图片磁盘缓存配置分区组件。
17#[allow(non_snake_case)]
18#[component]
19pub fn CacheSection(toast: Callback<(String, bool)>) -> Element {
20    #[cfg(target_arch = "wasm32")]
21    {
22        let mut saved: Signal<ImageCacheSettings> = use_signal(ImageCacheSettings::default);
23        let mut mb_draft: Signal<u32> = use_signal(|| 1024);
24        let mut hours_draft: Signal<u32> = use_signal(|| 168);
25        let loading = use_signal(|| true);
26        let mut saving = use_signal(|| false);
27        let mut just_saved = use_signal(|| false);
28
29        use_effect(move || {
30            #[cfg(target_arch = "wasm32")]
31            {
32                let mut saved = saved;
33                let mut mb_draft = mb_draft;
34                let mut hours_draft = hours_draft;
35                let mut loading = loading;
36                spawn(async move {
37                    match get_image_cache_settings().await {
38                        Ok(s) => {
39                            saved.set(s.clone());
40                            mb_draft.set(s.disk_cache_max_mb);
41                            hours_draft.set(s.disk_cache_max_age_hours);
42                        }
43                        Err(e) => toast.call((format!("加载失败:{e}"), true)),
44                    }
45                    loading.set(false);
46                });
47            }
48        });
49
50        let dirty = use_memo(move || {
51            mb_draft() != saved().disk_cache_max_mb
52                || hours_draft() != saved().disk_cache_max_age_hours
53        });
54
55        rsx! {
56            div { class: "space-y-6",
57                div { class: "{ADMIN_CARD_CLASS} p-6 md:p-8 flex flex-col gap-6",
58                    div { class: "flex items-center gap-3",
59                        span { class: "inline-flex items-center justify-center w-10 h-10 rounded-full bg-[var(--color-paper-theme)] text-[var(--color-paper-primary)] border border-[var(--color-paper-border)]",
60                            svg {
61                                class: "w-5 h-5",
62                                view_box: "0 0 24 24",
63                                fill: "none",
64                                stroke: "currentColor",
65                                stroke_width: "1.8",
66                                stroke_linecap: "round",
67                                stroke_linejoin: "round",
68                                path { d: "M21 8v13H3V8 M1 3h22v5H1z M10 12h4" }
69                            }
70                        }
71                        div {
72                            h2 { class: "text-xl font-bold text-[var(--color-paper-primary)]",
73                                "图片磁盘缓存"
74                            }
75                            p { class: "text-sm text-[var(--color-paper-secondary)] mt-0.5",
76                                "uploads/.cache/ 的容量与保留策略,清理任务每小时扫描一次。"
77                            }
78                        }
79                    }
80
81                    div { class: "flex flex-col gap-2 max-w-xl",
82                        FormLabel {
83                            label: "最大容量(MB)",
84                            html_for: Some("cache-max-mb".to_string()),
85                        }
86                        input {
87                            id: "cache-max-mb",
88                            r#type: "number",
89                            min: "1",
90                            class: "{INPUT_CLASS}",
91                            value: "{mb_draft()}",
92                            oninput: move |e: Event<FormData>| {
93                                let v = e.value();
94                                if let Ok(n) = v.parse::<u32>() {
95                                    mb_draft.set(n);
96                                }
97                                just_saved.set(false);
98                            },
99                        }
100                        p { class: "text-xs text-[var(--color-paper-secondary)]",
101                            "超限时按修改时间删除最旧的缓存文件。默认 1024 MB。"
102                        }
103                    }
104
105                    div { class: "flex flex-col gap-2 max-w-xl",
106                        FormLabel {
107                            label: "最大保留时长(小时)",
108                            html_for: Some("cache-max-age".to_string()),
109                        }
110                        input {
111                            id: "cache-max-age",
112                            r#type: "number",
113                            min: "1",
114                            class: "{INPUT_CLASS}",
115                            value: "{hours_draft()}",
116                            oninput: move |e: Event<FormData>| {
117                                let v = e.value();
118                                if let Ok(n) = v.parse::<u32>() {
119                                    hours_draft.set(n);
120                                }
121                                just_saved.set(false);
122                            },
123                        }
124                        p { class: "text-xs text-[var(--color-paper-secondary)]",
125                            "超期的缓存文件优先删除。默认 168 小时(7 天)。"
126                        }
127                    }
128
129                    div { class: "flex items-center justify-between gap-4 pt-1",
130                        if just_saved() {
131                            span { class: "inline-flex items-center gap-1.5 text-xs text-[var(--color-paper-accent)]",
132                                svg {
133                                    class: "w-3.5 h-3.5",
134                                    view_box: "0 0 24 24",
135                                    fill: "none",
136                                    stroke: "currentColor",
137                                    stroke_width: "2.5",
138                                    path {
139                                        stroke_linecap: "round",
140                                        stroke_linejoin: "round",
141                                        d: "M5 13l4 4L19 7",
142                                    }
143                                }
144                                "已保存"
145                            }
146                        } else if dirty() {
147                            span { class: "text-xs text-[var(--color-paper-secondary)]",
148                                "有未保存的更改"
149                            }
150                        } else {
151                            span { class: "text-xs text-transparent select-none", "·" }
152                        }
153                        LoadingButton {
154                            label: "保存设置".to_string(),
155                            loading: saving(),
156                            disabled: loading() || just_saved() || !dirty(),
157                            onclick: move |_| {
158                                let mb = mb_draft();
159                                let hours = hours_draft();
160                                saving.set(true);
161                                spawn(async move {
162                                    match update_image_cache_settings(mb, hours).await {
163                                        Ok(s) => {
164                                            saved.set(s.clone());
165                                            mb_draft.set(s.disk_cache_max_mb);
166                                            hours_draft.set(s.disk_cache_max_age_hours);
167                                            just_saved.set(true);
168                                            toast.call(("保存成功".to_string(), false));
169                                        }
170                                        Err(e) => toast.call((format!("保存失败:{e}"), true)),
171                                    }
172                                    saving.set(false);
173                                });
174                            },
175                        }
176                    }
177                }
178            }
179        }
180    }
181    #[cfg(not(target_arch = "wasm32"))]
182    {
183        rsx! {
184            div { class: "p-8 text-[var(--color-paper-secondary)]", "缓存配置(前端渲染)" }
185        }
186    }
187}