1use dioxus::prelude::*;
15use dioxus::router::components::Link;
16
17use crate::api::posts::{get_posts_by_tag, list_tags, PostListResponse, TagListResponse};
18use crate::components::empty_state::EmptyState;
19use crate::components::post_card::PostCard;
20use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
21use crate::components::skeletons::tags_skeleton::{TagDetailSkeleton, TagsSkeleton};
22use crate::router::Route;
23
24#[component]
28pub fn Tags() -> Element {
29 rsx! {
30 div { class: "animate-page-enter",
31 header { class: "page-header mb-6",
32 h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
33 "标签"
34 }
35 }
36 TagsContent {}
37 }
38 }
39}
40
41#[component]
46fn TagsContent() -> Element {
47 let tags_res = use_server_future(list_tags)?;
48
49 let tags_data = tags_res.read().as_ref().map(|r| match r {
51 Ok(TagListResponse { tags }) => Ok(tags.clone()),
52 Err(e) => Err(e.to_string()),
53 });
54
55 match tags_data {
56 Some(Ok(tags)) => {
57 if tags.is_empty() {
58 rsx! {
59 EmptyState {
60 title: "暂无标签",
61 description: "当前还没有任何标签。",
62 }
63 }
64 } else {
65 let total = tags.iter().map(|t| t.post_count).sum::<i64>();
66 rsx! {
67 div { class: "mt-2 text-base text-paper-secondary",
68 "共 "
69 span { class: "font-medium text-paper-primary", "{tags.len()}" }
70 " 个标签,"
71 span { class: "font-medium text-paper-primary", "{total}" }
72 " 篇文章"
73 }
74 ul { class: "flex flex-wrap gap-4 mt-6",
75 for tag in tags {
76 li { key: "{tag.name}",
77 Link {
78 class: "inline-flex items-center px-3 py-1.5 text-base font-medium bg-paper-accent-soft text-paper-accent rounded-lg hover:bg-paper-accent hover:text-white transition-all duration-200",
79 to: Route::TagDetail {
80 tag: tag.name.clone(),
81 },
82 "{tag.name}"
83 sup { class: "ml-1 text-sm text-paper-secondary",
84 "{tag.post_count}"
85 }
86 }
87 }
88 }
89 }
90 }
91 }
92 }
93 Some(Err(_)) => {
94 rsx! {
95 div { class: "text-center text-red-500 dark:text-red-400 py-20", "加载失败" }
96 }
97 }
98 _ => {
99 rsx! {
100 DelayedSkeleton { TagsSkeleton {} }
101 }
102 }
103 }
104}
105
106#[component]
110pub fn TagDetail(tag: String) -> Element {
111 rsx! {
112 div { class: "animate-page-enter",
113 header { class: "page-header mb-6",
114 h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
115 "{tag}"
116 }
117 }
118 TagDetailContent { tag: tag.clone() }
119 }
120 }
121}
122
123#[component]
135fn TagDetailContent(tag: String) -> Element {
136 let router = dioxus::router::router();
137
138 let posts_res = use_server_future(move || {
139 let current_tag = match router.current::<Route>() {
140 Route::TagDetail { tag } => tag,
141 _ => tag.clone(),
142 };
143 get_posts_by_tag(current_tag)
144 })?;
145
146 let posts_data = posts_res.read().as_ref().map(|r| match r {
148 Ok(PostListResponse { posts, total }) => Ok((posts.clone(), *total)),
149 Err(e) => Err(e.to_string()),
150 });
151
152 match posts_data {
153 Some(Ok((posts, total))) => {
154 if posts.is_empty() {
155 rsx! {
156 EmptyState {
157 title: "暂无文章",
158 description: "该标签下还没有发布任何文章。",
159 }
160 }
161 } else {
162 rsx! {
163 div { class: "mt-2 mb-8 text-base text-paper-secondary",
164 "共 "
165 span { class: "font-medium text-paper-primary", "{total}" }
166 " 篇文章"
167 }
168 div { class: "mt-8",
169 for post in posts.iter() {
170 PostCard { key: "{post.id}", post: post.clone() }
171 }
172 }
173 }
174 }
175 }
176 Some(Err(_)) => {
177 rsx! {
178 div { class: "text-center text-red-500 dark:text-red-400 py-20", "加载失败" }
179 }
180 }
181 _ => {
182 rsx! {
183 DelayedSkeleton { TagDetailSkeleton {} }
184 }
185 }
186 }
187}