yggdrasil/pages/admin/settings/
trash_section.rs1use 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#[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}