Compare commits

..

No commits in common. "a59a58b41d975b8c5b5174032d81d8ed0bbf6f07" and "44eba241211ff48b3fd4ea650958b0a791468788" have entirely different histories.

15 changed files with 23 additions and 1264 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::{clean_tags, get_current_admin_user, sync_tags};
use super::types::CreatePostResponse;
#[cfg(feature = "server")]

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::get_current_admin_user;
use super::types::CreatePostResponse;
#[cfg(feature = "server")]

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::{get_current_admin_user, row_to_post_list};
use super::types::PostListResponse;
#[cfg(feature = "server")]
@ -21,21 +20,15 @@ pub async fn list_published_posts(
let client = get_conn().await.map_err(AppError::db_conn)?;
// Get total count from cache or query
let total = if let Some(cached_total) = crate::cache::get_total_published_posts().await {
cached_total
} else {
let count_row = client
.query_one(
"SELECT COUNT(*) FROM posts WHERE status = 'published' AND deleted_at IS NULL",
&[],
)
.await
.map_err(AppError::query)?;
let total: i64 = count_row.get(0);
crate::cache::set_total_published_posts(total).await;
total
};
// Get total count
let count_row = client
.query_one(
"SELECT COUNT(*) FROM posts WHERE status = 'published' AND deleted_at IS NULL",
&[],
)
.await
.map_err(AppError::query)?;
let total: i64 = count_row.get(0);
let offset = ((page - 1).max(0) as i64) * (per_page as i64);
let limit = per_page as i64;
@ -146,9 +139,6 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, Serv
posts.push(row_to_post_list(&client, row).await);
}
// NOTE: total = posts.len() is correct because get_posts_by_tag
// currently fetches ALL matching posts (no LIMIT/OFFSET).
// If pagination is added later, switch to a proper COUNT(*) query.
let total = posts.len() as i64;
crate::cache::set_posts_by_tag(&tag_name, posts.clone(), total).await;
Ok(PostListResponse { posts, total })

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::{get_current_admin_user, row_to_post_full, row_to_post_list};
use super::types::SinglePostResponse;
#[cfg(feature = "server")]

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::row_to_post_list;
use super::types::PostListResponse;
#[cfg(feature = "server")]

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::get_current_admin_user;
use super::types::PostStatsResponse;
#[cfg(feature = "server")]

View File

@ -1,6 +1,5 @@
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::{clean_tags, get_current_admin_user, sync_tags};
use super::types::CreatePostResponse;
#[cfg(feature = "server")]

View File

@ -31,7 +31,6 @@ const TTL_TAG_POSTS: Duration = Duration::from_secs(120);
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum CacheKey {
PublishedPosts { page: i32, per_page: i32 },
TotalPublishedPosts,
AllTags,
PostBySlug(String),
PostsByTag(String),
@ -110,16 +109,6 @@ pub async fn set_post_list(key: &CacheKey, posts: Vec<Post>, total: i64) {
let _ = POST_LIST_CACHE.insert(key.clone(), (posts, total)).await;
}
#[cfg(feature = "server")]
pub async fn get_total_published_posts() -> Option<i64> {
POST_LIST_CACHE.get(&CacheKey::TotalPublishedPosts).await.map(|(_, total)| total)
}
#[cfg(feature = "server")]
pub async fn set_total_published_posts(total: i64) {
let _ = POST_LIST_CACHE.insert(CacheKey::TotalPublishedPosts, (vec![], total)).await;
}
#[cfg(feature = "server")]
pub async fn get_tag_list() -> Option<Vec<Tag>> {
TAG_LIST_CACHE.get(&CacheKey::AllTags).await

View File

@ -55,7 +55,7 @@ pub fn Admin() -> Element {
"最近文章"
}
match &*posts_res.read() {
Some(Ok(PostListResponse { posts, total: _ })) => {
Some(Ok(PostListResponse { posts })) => {
rsx! {
div { class: "space-y-0",
for post in posts.iter().take(5) {
@ -64,14 +64,7 @@ pub fn Admin() -> Element {
}
}
}
Some(Err(_e)) => {
rsx! {
div { class: "text-center text-red-500 dark:text-red-400 py-20",
"加载失败"
}
}
}
None => {
_ => {
rsx! {
div { class: if show_posts_skeleton() { "space-y-4 animate-pulse" } else { "space-y-4 opacity-0" },
for _ in 0..5 {

View File

@ -27,7 +27,7 @@ pub fn Posts() -> Element {
}
match &*posts_res.read() {
Some(Ok(PostListResponse { posts, total: _ })) => {
Some(Ok(PostListResponse { posts })) => {
if posts.is_empty() {
rsx! {
div { class: "text-center py-20 text-gray-500 dark:text-[#9b9c9d]",

View File

@ -95,12 +95,12 @@ fn ArchivesContent() -> Element {
let posts_data = posts_res.read();
match &*posts_data {
Some(Ok(PostListResponse { posts, total })) => {
Some(Ok(PostListResponse { posts })) => {
let grouped = group_posts(posts);
rsx! {
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
""
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{total}" }
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{posts.len()}" }
" 篇文章"
}
for year_group in grouped.iter() {

View File

@ -29,12 +29,12 @@ 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, total }) => Ok((posts.clone(), *total)),
Ok(PostListResponse { posts }) => Ok(posts.clone()),
Err(e) => Err(e.to_string()),
});
match posts_data {
Some(Ok((posts, total))) => {
Some(Ok(posts)) => {
rsx! {
for post in posts.iter() {
PostCard { post: post.clone() }
@ -44,7 +44,7 @@ fn HomePosts(current_page: i32) -> Element {
"暂无文章"
}
}
Pagination { current_page, total }
Pagination { current_page, posts: posts.clone() }
}
}
Some(Err(e)) => {
@ -77,10 +77,9 @@ fn HomeInfo() -> Element {
}
#[component]
fn Pagination(current_page: i32, total: i64) -> Element {
fn Pagination(current_page: i32, posts: Vec<crate::models::post::Post>) -> Element {
let has_prev = current_page > 1;
let total_pages = ((total + POSTS_PER_PAGE as i64 - 1) / POSTS_PER_PAGE as i64).max(1) as i32;
let has_next = current_page < total_pages;
let has_next = posts.len() >= POSTS_PER_PAGE as usize;
let prev = current_page - 1;
let prev_route = if prev <= 1 {
Route::Home {}

View File

@ -49,7 +49,7 @@ pub fn Search() -> Element {
}
if is_searching() {
DelayedSkeleton { SearchSkeleton {} }
} else if let Some(Ok(PostListResponse { posts, total: _ })) = search_res() {
} else if let Some(Ok(PostListResponse { posts })) = search_res() {
if posts.is_empty() {
div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20",
"未找到相关文章"

View File

@ -85,16 +85,16 @@ 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, total }) => Ok((posts.clone(), *total)),
Ok(PostListResponse { posts }) => Ok(posts.clone()),
Err(e) => Err(e.to_string()),
});
match posts_data {
Some(Ok((posts, total))) => {
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]", "{total}" }
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{posts.len()}" }
" 篇文章"
}
for post in posts.iter() {