Skip to main content

yggdrasil/pages/admin/settings/
trash_section.rs

1//! 回收站自动清理分区(启用开关 + 保留天数)。
2//!
3//! 调度任务每小时读取 DB 值执行清理。
4
5use dioxus::prelude::*;
6
7#[cfg(target_arch = "wasm32")]
8use crate::api::settings::{get_trash_settings, update_trash_settings};
9#[cfg(target_arch = "wasm32")]
10use crate::components::forms::{FormLabel, INPUT_CLASS};
11#[cfg(target_arch = "wasm32")]
12use crate::components::ui::{Checkbox, LoadingButton, ADMIN_CARD_CLASS};
13#[cfg(target_arch = "wasm32")]
14use crate::models::settings::TrashSettings;
15
16/// 回收站自动清理分区组件。
17#[allow(non_snake_case)]
18#[component]
19pub fn TrashSection(toast: Callback<(String, bool)>) -> Element {
20    #[cfg(target_arch = "wasm32")]
21    {
22        let mut saved: Signal<TrashSettings> = use_signal(TrashSettings::default);
23        let mut enabled_draft: Signal<bool> = use_signal(|| false);
24        let mut days_draft: Signal<i32> = use_signal(|| 30);
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 enabled_draft = enabled_draft;
34                let mut days_draft = days_draft;
35                let mut loading = loading;
36                spawn(async move {
37                    match get_trash_settings().await {
38                        Ok(s) => {
39                            saved.set(s.clone());
40                            enabled_draft.set(s.auto_purge_enabled);
41                            days_draft.set(s.retention_days);
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            enabled_draft() != saved().auto_purge_enabled || days_draft() != saved().retention_days
52        });
53
54        rsx! {
55            div { class: "space-y-6",
56                div { class: "{ADMIN_CARD_CLASS} p-6 md:p-8 flex flex-col gap-6",
57                    div { class: "flex items-center gap-3",
58                        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)]",
59                            svg {
60                                class: "w-5 h-5",
61                                view_box: "0 0 24 24",
62                                fill: "none",
63                                stroke: "currentColor",
64                                stroke_width: "1.8",
65                                stroke_linecap: "round",
66                                stroke_linejoin: "round",
67                                path { d: "M3 6h18 M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2 M10 11v6 M14 11v6" }
68                            }
69                        }
70                        div {
71                            h2 { class: "text-xl font-bold text-[var(--color-paper-primary)]",
72                                "回收站自动清理"
73                            }
74                            p { class: "text-sm text-[var(--color-paper-secondary)] mt-0.5",
75                                "已删除文章超过保留天数后由后台任务物理删除。"
76                            }
77                        }
78                    }
79
80                    label { class: "flex items-center gap-3 cursor-pointer max-w-xl",
81                        Checkbox {
82                            checked: enabled_draft(),
83                            onchange: move |checked: bool| {
84                                enabled_draft.set(checked);
85                                just_saved.set(false);
86                            },
87                        }
88                        div { class: "flex flex-col",
89                            span { class: "text-sm font-medium text-[var(--color-paper-primary)]",
90                                "启用自动清理"
91                            }
92                            span { class: "text-xs text-[var(--color-paper-secondary)]",
93                                "关闭后已删除文章永久保留在回收站,需手动彻底删除。"
94                            }
95                        }
96                    }
97
98                    div { class: "flex flex-col gap-2 max-w-xl",
99                        FormLabel {
100                            label: "保留天数",
101                            html_for: Some("trash-days".to_string()),
102                        }
103                        input {
104                            id: "trash-days",
105                            r#type: "number",
106                            min: "1",
107                            max: "365",
108                            class: "{INPUT_CLASS}",
109                            value: "{days_draft()}",
110                            oninput: move |e: Event<FormData>| {
111                                let v = e.value();
112                                if let Ok(n) = v.parse::<i32>() {
113                                    days_draft.set(n);
114                                }
115                                just_saved.set(false);
116                            },
117                        }
118                        p { class: "text-xs text-[var(--color-paper-secondary)]",
119                            "超出保留天数的已删除文章被物理删除(1–365 天)。"
120                        }
121                    }
122
123                    div { class: "flex items-center justify-between gap-4 pt-1",
124                        if just_saved() {
125                            span { class: "inline-flex items-center gap-1.5 text-xs text-[var(--color-paper-accent)]",
126                                svg {
127                                    class: "w-3.5 h-3.5",
128                                    view_box: "0 0 24 24",
129                                    fill: "none",
130                                    stroke: "currentColor",
131                                    stroke_width: "2.5",
132                                    path {
133                                        stroke_linecap: "round",
134                                        stroke_linejoin: "round",
135                                        d: "M5 13l4 4L19 7",
136                                    }
137                                }
138                                "已保存"
139                            }
140                        } else if dirty() {
141                            span { class: "text-xs text-[var(--color-paper-secondary)]",
142                                "有未保存的更改"
143                            }
144                        } else {
145                            span { class: "text-xs text-transparent select-none", "·" }
146                        }
147                        LoadingButton {
148                            label: "保存设置".to_string(),
149                            loading: saving(),
150                            disabled: loading() || just_saved() || !dirty(),
151                            onclick: move |_| {
152                                let e = enabled_draft();
153                                let d = days_draft();
154                                saving.set(true);
155                                spawn(async move {
156                                    match update_trash_settings(e, d).await {
157                                        Ok(s) => {
158                                            saved.set(s.clone());
159                                            enabled_draft.set(s.auto_purge_enabled);
160                                            days_draft.set(s.retention_days);
161                                            just_saved.set(true);
162                                            toast.call(("保存成功".to_string(), false));
163                                        }
164                                        Err(err) => toast.call((format!("保存失败:{err}"), true)),
165                                    }
166                                    saving.set(false);
167                                });
168                            },
169                        }
170                    }
171                }
172            }
173        }
174    }
175    #[cfg(not(target_arch = "wasm32"))]
176    {
177        rsx! {
178            div { class: "p-8 text-[var(--color-paper-secondary)]", "回收站配置(前端渲染)" }
179        }
180    }
181}