feat(stats): add trash count to PostStats for posts page badge

为合并回收站到 /admin/posts 的 tab 设计铺路:PostStats 新增 trash 字段,
统计 deleted_at IS NOT NULL 的文章数,供文章列表页「回收站」tab 角标使用。

- stats.rs: 条件聚合查询增加 trash 列,server/非 server 构造同步补字段
- models/post.rs: PostStats 加 pub trash: i64
- cache.rs: post_stats_cache_roundtrip 测试 fixture 补 trash 字段
This commit is contained in:
xfy 2026-07-08 16:40:32 +08:00
parent ebeec2a295
commit 9c3f2242ba
3 changed files with 11 additions and 5 deletions

View File

@ -1,7 +1,7 @@
//! 文章统计接口。 //! 文章统计接口。
//! //!
//! 返回文章总数、草稿数与已发布数,供管理后台仪表盘使用,结果缓存。 //! 返回文章总数、草稿数、已发布数与回收站(软删除)数量,供管理后台仪表盘与
//! Dioxus server function注册在 `/api` 路径下。 //! 文章列表页使用,结果缓存。Dioxus server function注册在 `/api` 路径下。
//! 仅在 `feature = "server"` 启用的服务端构建中查询数据库。 //! 仅在 `feature = "server"` 启用的服务端构建中查询数据库。
use dioxus::prelude::*; use dioxus::prelude::*;
@ -19,7 +19,7 @@ use crate::models::post::PostStats;
/// 获取文章统计信息。 /// 获取文章统计信息。
/// ///
/// 需要 admin 权限;优先命中缓存,未命中时通过单次条件聚合查询同时统计 /// 需要 admin 权限;优先命中缓存,未命中时通过单次条件聚合查询同时统计
/// 未删除文章总数、草稿数与已发布数 /// 未删除文章总数、草稿数、已发布数与回收站(软删除)数量
#[server(GetPostStats, "/api")] #[server(GetPostStats, "/api")]
pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> { pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
let _user = get_current_admin_user().await?; let _user = get_current_admin_user().await?;
@ -32,13 +32,14 @@ pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
let client = get_conn().await.map_err(AppError::db_conn)?; let client = get_conn().await.map_err(AppError::db_conn)?;
// 通过单次条件聚合查询同时统计总数、草稿数与已发布数 // 通过单次条件聚合查询同时统计总数、草稿数、已发布数与回收站数量
let row = client let row = client
.query_one( .query_one(
"SELECT "SELECT
COUNT(*) FILTER (WHERE deleted_at IS NULL) AS total, COUNT(*) FILTER (WHERE deleted_at IS NULL) AS total,
COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'draft') AS drafts, COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'draft') AS drafts,
COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'published') AS published COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'published') AS published,
COUNT(*) FILTER (WHERE deleted_at IS NOT NULL) AS trash
FROM posts", FROM posts",
&[], &[],
) )
@ -49,6 +50,7 @@ pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
total: row.get("total"), total: row.get("total"),
drafts: row.get("drafts"), drafts: row.get("drafts"),
published: row.get("published"), published: row.get("published"),
trash: row.get("trash"),
}; };
crate::cache::set_post_stats(stats.clone()).await; crate::cache::set_post_stats(stats.clone()).await;
Ok(PostStatsResponse { stats }) Ok(PostStatsResponse { stats })
@ -61,6 +63,7 @@ pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
total: 0, total: 0,
drafts: 0, drafts: 0,
published: 0, published: 0,
trash: 0,
}, },
}) })
} }

View File

@ -736,6 +736,7 @@ mod tests {
total: 10, total: 10,
drafts: 3, drafts: 3,
published: 7, published: 7,
trash: 2,
}; };
set_post_stats(stats.clone()).await; set_post_stats(stats.clone()).await;

View File

@ -187,6 +187,8 @@ pub struct PostStats {
pub drafts: i64, pub drafts: i64,
/// 已发布数量。 /// 已发布数量。
pub published: i64, pub published: i64,
/// 回收站(软删除)数量。
pub trash: i64,
} }
#[cfg(test)] #[cfg(test)]