yggdrasil/pages/admin/settings/
purge_section.rs1use dioxus::prelude::*;
7
8#[cfg(target_arch = "wasm32")]
9use crate::api::settings::{get_asset_purge_settings, update_asset_purge_settings};
10#[cfg(target_arch = "wasm32")]
11use crate::components::forms::{FormLabel, INPUT_CLASS};
12#[cfg(target_arch = "wasm32")]
13use crate::components::ui::{Checkbox, LoadingButton, ADMIN_CARD_CLASS};
14#[cfg(target_arch = "wasm32")]
15use crate::models::settings::AssetPurgeSettings;
16
17#[allow(non_snake_case)]
19#[component]
20pub fn AssetPurgeSection(toast: Callback<(String, bool)>) -> Element {
21 #[cfg(target_arch = "wasm32")]
22 {
23 let mut saved: Signal<AssetPurgeSettings> = use_signal(AssetPurgeSettings::default);
24 let mut enabled_draft: Signal<bool> = use_signal(|| true);
25 let mut days_draft: Signal<i32> = use_signal(|| 7);
26 let loading = use_signal(|| true);
27 let mut saving = use_signal(|| false);
28 let mut just_saved = use_signal(|| false);
29
30 use_effect(move || {
31 #[cfg(target_arch = "wasm32")]
32 {
33 let mut saved = saved;
34 let mut enabled_draft = enabled_draft;
35 let mut days_draft = days_draft;
36 let mut loading = loading;
37 spawn(async move {
38 match get_asset_purge_settings().await {
39 Ok(s) => {
40 saved.set(s.clone());
41 enabled_draft.set(s.auto_purge_enabled);
42 days_draft.set(s.retention_days);
43 }
44 Err(e) => toast.call((format!("加载失败:{e}"), true)),
45 }
46 loading.set(false);
47 });
48 }
49 });
50
51 let dirty = use_memo(move || {
52 enabled_draft() != saved().auto_purge_enabled || days_draft() != saved().retention_days
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 "无文章/评论引用的图片超过保留天数后由后台任务物理删除(每天执行)。"
77 }
78 }
79 }
80
81 label { class: "flex items-center gap-3 cursor-pointer max-w-xl",
82 Checkbox {
83 checked: enabled_draft(),
84 onchange: move |checked: bool| {
85 enabled_draft.set(checked);
86 just_saved.set(false);
87 },
88 }
89 div { class: "flex flex-col",
90 span { class: "text-sm font-medium text-[var(--color-paper-primary)]",
91 "启用自动清理"
92 }
93 span { class: "text-xs text-[var(--color-paper-secondary)]",
94 "关闭后未引用图片永久保留,需在素材管理页手动清理。评论区允许匿名传图,建议保持开启。"
95 }
96 }
97 }
98
99 div { class: "flex flex-col gap-2 max-w-xl",
100 FormLabel {
101 label: "保留天数",
102 html_for: Some("asset-purge-days".to_string()),
103 }
104 input {
105 id: "asset-purge-days",
106 r#type: "number",
107 min: "1",
108 max: "365",
109 class: "{INPUT_CLASS}",
110 value: "{days_draft()}",
111 oninput: move |e: Event<FormData>| {
112 let v = e.value();
113 if let Ok(n) = v.parse::<i32>() {
114 days_draft.set(n);
115 }
116 just_saved.set(false);
117 },
118 }
119 p { class: "text-xs text-[var(--color-paper-secondary)]",
120 "超出保留天数的未引用图片被物理删除(1–365 天)。保护窗防止误删尚未保存的草稿引用。"
121 }
122 }
123
124 div { class: "flex items-center justify-between gap-4 pt-1",
125 if just_saved() {
126 span { class: "inline-flex items-center gap-1.5 text-xs text-[var(--color-paper-accent)]",
127 svg {
128 class: "w-3.5 h-3.5",
129 view_box: "0 0 24 24",
130 fill: "none",
131 stroke: "currentColor",
132 stroke_width: "2.5",
133 path {
134 stroke_linecap: "round",
135 stroke_linejoin: "round",
136 d: "M5 13l4 4L19 7",
137 }
138 }
139 "已保存"
140 }
141 } else if dirty() {
142 span { class: "text-xs text-[var(--color-paper-secondary)]",
143 "有未保存的更改"
144 }
145 } else {
146 span { class: "text-xs text-transparent select-none", "·" }
147 }
148 LoadingButton {
149 label: "保存设置".to_string(),
150 loading: saving(),
151 disabled: loading() || just_saved() || !dirty(),
152 onclick: move |_| {
153 let e = enabled_draft();
154 let d = days_draft();
155 saving.set(true);
156 spawn(async move {
157 match update_asset_purge_settings(e, d).await {
158 Ok(s) => {
159 saved.set(s.clone());
160 enabled_draft.set(s.auto_purge_enabled);
161 days_draft.set(s.retention_days);
162 just_saved.set(true);
163 toast.call(("保存成功".to_string(), false));
164 }
165 Err(err) => toast.call((format!("保存失败:{err}"), true)),
166 }
167 saving.set(false);
168 });
169 },
170 }
171 }
172 }
173 }
174 }
175 }
176 #[cfg(not(target_arch = "wasm32"))]
177 {
178 rsx! {
179 div { class: "p-8 text-[var(--color-paper-secondary)]", "孤儿素材清理配置(前端渲染)" }
180 }
181 }
182}