feat: SSR for home and tags pages
This commit is contained in:
parent
6b3e086628
commit
6cfe664085
@ -4,65 +4,66 @@ use crate::api::posts::{list_published_posts, PostListResponse};
|
||||
use crate::components::nav::use_nav_items;
|
||||
use crate::components::page_layout::PageLayout;
|
||||
use crate::components::post_card::PostCard;
|
||||
use crate::hooks::delayed_loading::use_delayed_loading;
|
||||
use crate::components::suspense_wrapper::SuspenseWrapper;
|
||||
use crate::router::Route;
|
||||
|
||||
const POSTS_PER_PAGE: i32 = 10;
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
rsx! { HomeContent { page: 1 } }
|
||||
rsx! { HomePage { page: 1 } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn HomePage(page: i32) -> Element {
|
||||
rsx! { HomeContent { page } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn HomeContent(page: i32) -> Element {
|
||||
let route = use_route::<Route>();
|
||||
let current_page = page.max(1);
|
||||
let posts_res = use_resource(move || list_published_posts(current_page, POSTS_PER_PAGE));
|
||||
let nav_items = use_nav_items(route);
|
||||
let show_skeleton = use_delayed_loading(move || posts_res.read().is_none());
|
||||
let current_page = page.max(1);
|
||||
|
||||
rsx! {
|
||||
PageLayout { nav_items,
|
||||
HomeInfo {}
|
||||
match &*posts_res.read() {
|
||||
Some(Ok(PostListResponse { posts })) => {
|
||||
rsx! {
|
||||
for post in posts.iter() {
|
||||
PostCard { post: post.clone() }
|
||||
}
|
||||
if posts.is_empty() {
|
||||
div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20",
|
||||
"暂无文章"
|
||||
}
|
||||
}
|
||||
Pagination { current_page, posts: posts.clone() }
|
||||
SuspenseWrapper {
|
||||
HomePosts { current_page }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn HomePosts(current_page: i32) -> Element {
|
||||
let posts_res = use_server_future(move || list_published_posts(current_page, POSTS_PER_PAGE))?;
|
||||
|
||||
let posts_data = posts_res.read().as_ref().map(|r| match r {
|
||||
Ok(PostListResponse { posts }) => Ok(posts.clone()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
});
|
||||
|
||||
match posts_data {
|
||||
Some(Ok(posts)) => {
|
||||
rsx! {
|
||||
for post in posts.iter() {
|
||||
PostCard { post: post.clone() }
|
||||
}
|
||||
if posts.is_empty() {
|
||||
div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20",
|
||||
"暂无文章"
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败: {e}"
|
||||
}
|
||||
}
|
||||
Pagination { current_page, posts: posts.clone() }
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败: {e}"
|
||||
}
|
||||
None => {
|
||||
rsx! {
|
||||
div { class: if show_skeleton() { "space-y-6 py-4" } else { "space-y-6 py-4 opacity-0" },
|
||||
for _ in 0..3 {
|
||||
div { class: "mb-6 p-6 bg-white dark:bg-[#2e2e33] rounded-lg border border-gray-200 dark:border-[#333] animate-pulse",
|
||||
div { class: "h-7 w-3/4 bg-gray-200 dark:bg-[#2a2a2a] rounded mb-3" }
|
||||
div { class: "h-4 w-full bg-gray-200 dark:bg-[#2a2a2a] rounded mb-2" }
|
||||
div { class: "h-4 w-2/3 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
rsx! {
|
||||
div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20",
|
||||
"加载中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,15 +4,13 @@ use crate::api::posts::{get_posts_by_tag, list_tags, PostListResponse, TagListRe
|
||||
use crate::components::nav::use_nav_items;
|
||||
use crate::components::page_layout::PageLayout;
|
||||
use crate::components::post_card::PostCard;
|
||||
use crate::hooks::delayed_loading::use_delayed_loading;
|
||||
use crate::components::suspense_wrapper::SuspenseWrapper;
|
||||
use crate::router::Route;
|
||||
|
||||
#[component]
|
||||
pub fn Tags() -> Element {
|
||||
let route = use_route::<Route>();
|
||||
let tags_res = use_resource(list_tags);
|
||||
let nav_items = use_nav_items(route);
|
||||
let show_skeleton = use_delayed_loading(move || tags_res.read().is_none());
|
||||
|
||||
rsx! {
|
||||
PageLayout { nav_items,
|
||||
@ -20,72 +18,63 @@ pub fn Tags() -> Element {
|
||||
h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]",
|
||||
"标签"
|
||||
}
|
||||
match &*tags_res.read() {
|
||||
Some(Ok(TagListResponse { tags })) => {
|
||||
let total = tags.iter().map(|t| t.post_count).sum::<i64>();
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"共 "
|
||||
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{tags.len()}" }
|
||||
" 个标签,"
|
||||
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{total}" }
|
||||
" 篇文章"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"加载失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"加载中..."
|
||||
}
|
||||
SuspenseWrapper {
|
||||
TagsContent {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TagsContent() -> Element {
|
||||
let tags_res = use_server_future(list_tags)?;
|
||||
|
||||
let tags_data = tags_res.read().as_ref().map(|r| match r {
|
||||
Ok(TagListResponse { tags }) => Ok(tags.clone()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
});
|
||||
|
||||
match tags_data {
|
||||
Some(Ok(tags)) => {
|
||||
let total = tags.iter().map(|t| t.post_count).sum::<i64>();
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"共 "
|
||||
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{tags.len()}" }
|
||||
" 个标签,"
|
||||
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{total}" }
|
||||
" 篇文章"
|
||||
}
|
||||
ul { class: "flex flex-wrap gap-4 mt-6",
|
||||
for tag in tags {
|
||||
li {
|
||||
a {
|
||||
class: "inline-flex items-center px-3 py-1.5 text-base font-medium bg-gray-100 dark:bg-[#2e2e33] text-gray-700 dark:text-[#9b9c9d] rounded-lg hover:bg-gray-200 dark:hover:bg-[#333] transition-colors",
|
||||
href: "/tags/{tag.name}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/tags/{}", tag.name).as_str());
|
||||
},
|
||||
"{tag.name}"
|
||||
sup { class: "ml-1 text-sm text-gray-500 dark:text-[#9b9c9d]", "{tag.post_count}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match &*tags_res.read() {
|
||||
Some(Ok(TagListResponse { tags })) => {
|
||||
let tags = tags.clone();
|
||||
rsx! {
|
||||
ul { class: "flex flex-wrap gap-4 mt-6",
|
||||
for tag in tags.into_iter() {
|
||||
li {
|
||||
a {
|
||||
class: "inline-flex items-center px-3 py-1.5 text-base font-medium bg-gray-100 dark:bg-[#2e2e33] text-gray-700 dark:text-[#9b9c9d] rounded-lg hover:bg-gray-200 dark:hover:bg-[#333] transition-colors",
|
||||
href: "/tags/{tag.name}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/tags/{}", tag.name).as_str());
|
||||
},
|
||||
"{tag.name}"
|
||||
sup { class: "ml-1 text-sm text-gray-500 dark:text-[#9b9c9d]", "{tag.post_count}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败"
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rsx! {
|
||||
div { class: if show_skeleton() { "flex flex-wrap gap-4 mt-6 animate-pulse" } else { "flex flex-wrap gap-4 mt-6 opacity-0" },
|
||||
for _ in 0..8 {
|
||||
div { class: "h-8 w-16 bg-gray-200 dark:bg-[#2a2a2a] rounded-lg" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
rsx! {
|
||||
div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20",
|
||||
"加载中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,10 +84,7 @@ pub fn Tags() -> Element {
|
||||
#[component]
|
||||
pub fn TagDetail(tag: String) -> Element {
|
||||
let route = use_route::<Route>();
|
||||
let tag_clone = tag.clone();
|
||||
let posts_res = use_resource(move || get_posts_by_tag(tag_clone.clone()));
|
||||
let nav_items = use_nav_items(route);
|
||||
let show_skeleton = use_delayed_loading(move || posts_res.read().is_none());
|
||||
|
||||
rsx! {
|
||||
PageLayout { nav_items,
|
||||
@ -106,59 +92,47 @@ pub fn TagDetail(tag: String) -> Element {
|
||||
h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]",
|
||||
"{tag}"
|
||||
}
|
||||
match &*posts_res.read() {
|
||||
Some(Ok(PostListResponse { posts })) => {
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"共 "
|
||||
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{posts.len()}" }
|
||||
" 篇文章"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"加载失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"加载中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SuspenseWrapper {
|
||||
TagDetailContent { tag: tag.clone() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TagDetailContent(tag: String) -> Element {
|
||||
let posts_res = use_server_future(move || get_posts_by_tag(tag.clone()))?;
|
||||
|
||||
let posts_data = posts_res.read().as_ref().map(|r| match r {
|
||||
Ok(PostListResponse { posts }) => Ok(posts.clone()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
});
|
||||
|
||||
match posts_data {
|
||||
Some(Ok(posts)) => {
|
||||
rsx! {
|
||||
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
|
||||
"共 "
|
||||
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{posts.len()}" }
|
||||
" 篇文章"
|
||||
}
|
||||
for post in posts.iter() {
|
||||
PostCard { post: post.clone() }
|
||||
}
|
||||
}
|
||||
match &*posts_res.read() {
|
||||
Some(Ok(PostListResponse { posts })) => {
|
||||
rsx! {
|
||||
for post in posts.iter() {
|
||||
PostCard { post: post.clone() }
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败"
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rsx! {
|
||||
div { class: if show_skeleton() { "space-y-6 py-4 animate-pulse" } else { "space-y-6 py-4 opacity-0" },
|
||||
for _ in 0..3 {
|
||||
div { class: "mb-6 p-6 bg-white dark:bg-[#2e2e33] rounded-lg border border-gray-200 dark:border-[#333]",
|
||||
div { class: "h-7 w-3/4 bg-gray-200 dark:bg-[#2a2a2a] rounded mb-3" }
|
||||
div { class: "h-4 w-full bg-gray-200 dark:bg-[#2a2a2a] rounded mb-2" }
|
||||
div { class: "h-4 w-2/3 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
rsx! {
|
||||
div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20",
|
||||
"加载中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user