docs(components): 补充中文注释
This commit is contained in:
parent
c5d1eb117c
commit
c43da3676f
@ -1,3 +1,8 @@
|
||||
//! 后台管理布局组件
|
||||
//!
|
||||
//! 包裹所有后台路由,提供管理员专属导航、登录校验、主题切换与登出入口。
|
||||
//! 在未完成身份校验前显示与真实布局结构一致的骨架屏,避免切换闪烁。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::api::auth::{get_current_user, logout};
|
||||
@ -10,6 +15,13 @@ use crate::hooks::delayed_loading::use_delayed_loading;
|
||||
use crate::router::Route;
|
||||
use crate::theme::ThemeToggle;
|
||||
|
||||
/// 后台管理整体布局组件。
|
||||
///
|
||||
/// 负责:
|
||||
/// - 通过 `get_current_user` 校验登录状态,未登录时跳转登录页
|
||||
/// - 渲染顶部导航(仪表盘、写文章、管理文章)与主题切换/登出按钮
|
||||
/// - 根据当前路由切换主区域样式(Write 路由固定高度,其他路由可滚动)
|
||||
/// - 校验完成前使用骨架屏保持布局稳定
|
||||
#[component]
|
||||
pub fn AdminLayout() -> Element {
|
||||
let mut ctx: UserContext = use_context();
|
||||
@ -17,6 +29,7 @@ pub fn AdminLayout() -> Element {
|
||||
let route = use_route::<Route>();
|
||||
let show_skeleton = use_delayed_loading(move || !(ctx.checked)());
|
||||
|
||||
// 仅在首次挂载时执行一次登录校验
|
||||
use_effect(move || {
|
||||
if !(ctx.checked)() {
|
||||
(ctx.checked).set(true);
|
||||
@ -37,6 +50,7 @@ pub fn AdminLayout() -> Element {
|
||||
}
|
||||
});
|
||||
|
||||
// 后台导航项,当前路由高亮
|
||||
let admin_nav_items = vec![
|
||||
NavItemConfig {
|
||||
route: Route::Admin {},
|
||||
@ -55,6 +69,7 @@ pub fn AdminLayout() -> Element {
|
||||
},
|
||||
];
|
||||
|
||||
// 右侧操作区:主题切换 + 登出按钮
|
||||
let right_content = rsx! {
|
||||
div { class: "flex items-center gap-3",
|
||||
ThemeToggle {}
|
||||
@ -88,6 +103,7 @@ pub fn AdminLayout() -> Element {
|
||||
"min-h-screen flex flex-col bg-white dark:bg-[#1d1e20]"
|
||||
};
|
||||
|
||||
// 根据校验状态与用户状态渲染真实布局、跳转提示或骨架屏
|
||||
match ((ctx.checked)(), (ctx.user)()) {
|
||||
(true, Some(_)) => {
|
||||
rsx! {
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
//! 后台仪表盘骨架屏
|
||||
//!
|
||||
//! 仅在 AdminLayout 的内容区展示,不包含 Header 与 Footer,
|
||||
//! 用于校验登录状态期间保持视觉稳定。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::skeletons::atoms::SkeletonBox;
|
||||
|
||||
/// 仅仪表盘内容区骨架(不含 header/footer)
|
||||
/// 仪表盘内容区骨架屏组件(不含 header/footer)。
|
||||
///
|
||||
/// 包含统计卡片、快捷操作按钮与最近文章列表三组占位块。
|
||||
#[component]
|
||||
pub fn AdminDashboardSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,8 +1,21 @@
|
||||
//! 评论管理操作组件
|
||||
//!
|
||||
//! 在后台管理文章评论时,提供通过、标记垃圾、删除(移入回收站)三种操作按钮。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::api::comments::{approve_comment, spam_comment, trash_comment};
|
||||
use crate::components::comments::section::CommentContext;
|
||||
|
||||
/// 评论管理操作按钮组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `comment_id`:目标评论 ID
|
||||
/// - `post_id`:所属文章 ID(当前未使用,保留用于未来扩展)
|
||||
///
|
||||
/// 关键事件:
|
||||
/// - 点击"通过"/"垃圾"/"删除"按钮后调用对应 API,操作完成后触发评论列表刷新
|
||||
/// - 操作期间禁用按钮,防止重复提交
|
||||
#[component]
|
||||
pub fn CommentActions(comment_id: i64, post_id: i32) -> Element {
|
||||
let ctx: CommentContext = use_context();
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
//! 评论表单组件
|
||||
//!
|
||||
//! 提供发表评论与回复评论的表单,包含昵称、邮箱、网站、内容与反垃圾蜜罐字段。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::api::comments::create_comment;
|
||||
@ -5,6 +9,16 @@ use crate::components::comments::section::CommentContext;
|
||||
use crate::components::forms::{AlertBox, BUTTON_PRIMARY_CLASS, INPUT_CLASS};
|
||||
use crate::hooks::comment_storage::{self, PendingComment};
|
||||
|
||||
/// 评论表单组件,用于顶层评论或回复评论。
|
||||
///
|
||||
/// Props:
|
||||
/// - `post_id`:所属文章 ID
|
||||
/// - `parent_id`:回复目标评论 ID,`None` 表示顶层评论
|
||||
///
|
||||
/// 关键事件:
|
||||
/// - 挂载时从本地存储恢复上次填写的作者信息
|
||||
/// - 提交时校验必填项与蜜罐字段
|
||||
/// - 提交成功后清空内容、保存作者信息、添加待审核评论并触发列表刷新
|
||||
#[component]
|
||||
pub fn CommentForm(post_id: i32, parent_id: Option<i64>) -> Element {
|
||||
let ctx: CommentContext = use_context();
|
||||
@ -21,6 +35,7 @@ pub fn CommentForm(post_id: i32, parent_id: Option<i64>) -> Element {
|
||||
let mut message = use_signal(|| Option::<(String, &'static str)>::None);
|
||||
let mut loaded = use_signal(|| false);
|
||||
|
||||
// 首次挂载时从本地存储加载作者信息
|
||||
use_effect(move || {
|
||||
if loaded() {
|
||||
return;
|
||||
@ -33,6 +48,7 @@ pub fn CommentForm(post_id: i32, parent_id: Option<i64>) -> Element {
|
||||
}
|
||||
});
|
||||
|
||||
// 回复表单:当前未激活回复时隐藏
|
||||
if let Some(pid) = parent_id {
|
||||
if active_reply() != Some(pid) {
|
||||
return rsx! {};
|
||||
@ -107,6 +123,7 @@ pub fn CommentForm(post_id: i32, parent_id: Option<i64>) -> Element {
|
||||
"支持 Markdown 语法"
|
||||
}
|
||||
|
||||
// 蜜罐字段:对普通用户隐藏,用于拦截简单机器人
|
||||
textarea {
|
||||
class: "hidden",
|
||||
aria_hidden: "true",
|
||||
@ -131,6 +148,7 @@ pub fn CommentForm(post_id: i32, parent_id: Option<i64>) -> Element {
|
||||
let content = content_md();
|
||||
let hp = honeypot();
|
||||
|
||||
// 蜜罐被填充则直接丢弃
|
||||
if !hp.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,15 +1,29 @@
|
||||
//! 单条评论项组件
|
||||
//!
|
||||
//! 展示已审核通过的评论,支持展开/收起回复表单。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::comments::form::CommentForm;
|
||||
use crate::components::comments::section::CommentContext;
|
||||
use crate::models::comment::PublicComment;
|
||||
|
||||
/// 单条已审核评论组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `comment`:已审核评论数据
|
||||
/// - `post_id`:所属文章 ID
|
||||
///
|
||||
/// 关键行为:
|
||||
/// - 点击"回复"按钮切换该评论下方的回复表单
|
||||
/// - 最大递归深度限制为 20,超过后隐藏回复按钮
|
||||
#[component]
|
||||
pub fn CommentItem(comment: PublicComment, post_id: i32) -> Element {
|
||||
let ctx: CommentContext = use_context();
|
||||
let mut active_reply = ctx.active_reply;
|
||||
let refresh_trigger = ctx.refresh_trigger;
|
||||
|
||||
// 孤儿评论按顶层展示
|
||||
let depth = if comment.parent_id.is_none() && comment.depth > 0 {
|
||||
0
|
||||
} else {
|
||||
@ -23,6 +37,7 @@ pub fn CommentItem(comment: PublicComment, post_id: i32) -> Element {
|
||||
|
||||
let _ = refresh_trigger;
|
||||
|
||||
// 作者名展示为链接或普通文本
|
||||
let author_element = match &comment.author_url {
|
||||
Some(url) if !url.is_empty() => rsx! {
|
||||
a {
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
//! 评论列表组件
|
||||
//!
|
||||
//! 将已审核评论与待审核评论合并成一棵树并按时间排序渲染。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::comments::item::CommentItem;
|
||||
@ -5,12 +9,20 @@ use crate::components::comments::pending_item::PendingCommentItem;
|
||||
use crate::hooks::comment_storage::PendingComment;
|
||||
use crate::models::comment::PublicComment;
|
||||
|
||||
/// 合并后的评论节点,可能是已审核或待审核评论。
|
||||
#[derive(Clone)]
|
||||
enum MergedComment {
|
||||
Approved(PublicComment),
|
||||
Pending(PendingComment),
|
||||
}
|
||||
|
||||
/// 合并两类评论并构建成树形结构。
|
||||
///
|
||||
/// 处理逻辑:
|
||||
/// - 将已审核与待审核评论统一为 `MergedComment`
|
||||
/// - 若某条评论的 parent_id 不存在于当前集合中,则视为顶层评论
|
||||
/// - 同一父节点下的子评论按时间排序
|
||||
/// - 使用 DFS 前序遍历生成最终展示顺序
|
||||
fn merge_and_treeify(
|
||||
approved: Vec<PublicComment>,
|
||||
pending: Vec<PendingComment>,
|
||||
@ -31,6 +43,7 @@ fn merge_and_treeify(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 按 parent_id 分组,处理指向不存在父节点的 parent_id
|
||||
let mut children_map: HashMap<Option<i64>, Vec<MergedComment>> = HashMap::new();
|
||||
for comment in all {
|
||||
let parent_id = match &comment {
|
||||
@ -47,6 +60,7 @@ fn merge_and_treeify(
|
||||
.push(comment);
|
||||
}
|
||||
|
||||
// 每个父节点下的子评论按创建时间排序
|
||||
for children in children_map.values_mut() {
|
||||
children.sort_by(|a, b| {
|
||||
let time_a = match a {
|
||||
@ -61,6 +75,7 @@ fn merge_and_treeify(
|
||||
});
|
||||
}
|
||||
|
||||
// 深度优先遍历生成树形顺序
|
||||
fn dfs(
|
||||
parent_id: Option<i64>,
|
||||
children_map: &HashMap<Option<i64>, Vec<MergedComment>>,
|
||||
@ -83,6 +98,14 @@ fn merge_and_treeify(
|
||||
result
|
||||
}
|
||||
|
||||
/// 评论列表组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `comments`:已审核评论列表
|
||||
/// - `pending`:待审核评论列表
|
||||
/// - `post_id`:所属文章 ID
|
||||
///
|
||||
/// 根据两类评论构建合并树,依次渲染为 `CommentItem` 或 `PendingCommentItem`。
|
||||
#[component]
|
||||
pub fn CommentList(
|
||||
comments: Vec<PublicComment>,
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
//! 评论组件模块
|
||||
//!
|
||||
//! 提供评论相关组件:评论列表、单条评论、待审核评论、评论表单、评论区段与管理操作。
|
||||
|
||||
/// 评论操作按钮组件。
|
||||
pub mod actions;
|
||||
/// 评论表单组件。
|
||||
pub mod form;
|
||||
/// 单条评论组件。
|
||||
pub mod item;
|
||||
/// 评论列表组件。
|
||||
pub mod list;
|
||||
/// 待审核评论组件。
|
||||
pub mod pending_item;
|
||||
/// 评论区段组件。
|
||||
pub mod section;
|
||||
|
||||
@ -1,10 +1,24 @@
|
||||
//! 待审核评论项组件
|
||||
//!
|
||||
//! 展示用户刚提交、尚未通过审核的评论占位项,
|
||||
//! 视觉上使用较低的透明度并标注"审核中"状态。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::hooks::comment_storage::{render_pending_content, PendingComment};
|
||||
|
||||
/// 待审核评论项组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `comment`:待审核评论数据
|
||||
/// - `post_id`:所属文章 ID(当前未使用,保留用于未来扩展)
|
||||
///
|
||||
/// 展示内容包括:作者头像/链接、"刚刚"时间占位、审核中徽章、Markdown 渲染内容。
|
||||
/// 深度最大展示 6 层缩进,孤儿评论深度会被修正为 0。
|
||||
#[component]
|
||||
#[allow(unused_variables)]
|
||||
pub fn PendingCommentItem(comment: PendingComment, post_id: i32) -> Element {
|
||||
// 孤儿评论(parent_id 为 None 但 depth > 0)按顶层展示
|
||||
let depth = if comment.parent_id.is_none() && comment.depth > 0 {
|
||||
0
|
||||
} else {
|
||||
@ -14,6 +28,7 @@ pub fn PendingCommentItem(comment: PendingComment, post_id: i32) -> Element {
|
||||
let indent = depth.min(6) * 24;
|
||||
let content_html = render_pending_content(&comment.content_md);
|
||||
|
||||
// 作者名展示为链接或普通文本
|
||||
let author_element = match &comment.author_url {
|
||||
Some(url) if !url.is_empty() => rsx! {
|
||||
a {
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
//! 评论区段组件
|
||||
//!
|
||||
//! 管理单篇文章的评论上下文(回复目标、刷新触发器、待审核评论),
|
||||
//! 负责加载评论列表、轮询待审核评论状态并渲染表单与列表。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::api::comments::{check_pending_status, get_comments, CommentTreeResponse};
|
||||
@ -6,13 +11,32 @@ use crate::components::comments::list::CommentList;
|
||||
use crate::components::skeletons::comment_skeleton::CommentListSkeleton;
|
||||
use crate::hooks::comment_storage::{self, PendingComment};
|
||||
|
||||
/// 评论上下文,供评论相关组件共享状态。
|
||||
///
|
||||
/// 字段:
|
||||
/// - `active_reply`:当前正在回复的评论 ID
|
||||
/// - `refresh_trigger`:刷新触发信号,切换时触发评论列表重新加载
|
||||
/// - `pending_comments`:本地存储的待审核评论
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CommentContext {
|
||||
/// 当前正在回复的评论 ID。
|
||||
pub active_reply: Signal<Option<i64>>,
|
||||
/// 刷新触发信号,切换时触发评论列表重新加载。
|
||||
pub refresh_trigger: Signal<bool>,
|
||||
/// 本地存储的待审核评论。
|
||||
pub pending_comments: Signal<Vec<PendingComment>>,
|
||||
}
|
||||
|
||||
/// 评论区段组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `post_id`:所属文章 ID
|
||||
///
|
||||
/// 负责:
|
||||
/// - 提供 `CommentContext` 上下文
|
||||
/// - 加载本地待审核评论并定期轮询其审核状态
|
||||
/// - 加载已审核评论列表并合并展示
|
||||
/// - 空评论时展示提示文案
|
||||
#[component]
|
||||
pub fn CommentSection(post_id: i32) -> Element {
|
||||
let ctx = use_context_provider(|| {
|
||||
@ -26,6 +50,7 @@ pub fn CommentSection(post_id: i32) -> Element {
|
||||
}
|
||||
});
|
||||
|
||||
// 轮询待审核评论状态,已处理(非 pending)的评论从本地移除
|
||||
use_future(move || {
|
||||
let mut pending = ctx.pending_comments;
|
||||
async move {
|
||||
@ -46,12 +71,13 @@ pub fn CommentSection(post_id: i32) -> Element {
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
// Silently ignore on WASM; server-only logging not available
|
||||
// 在 WASM 环境下静默忽略,服务器端日志不可用
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 评论数据资源,refresh_trigger 变化时自动重新加载
|
||||
let comments_resource = use_resource(move || async move {
|
||||
let _ = (ctx.refresh_trigger)();
|
||||
get_comments(post_id).await
|
||||
@ -59,6 +85,7 @@ pub fn CommentSection(post_id: i32) -> Element {
|
||||
|
||||
let data = comments_resource.read();
|
||||
|
||||
// 根据加载结果渲染评论区、错误提示或骨架屏
|
||||
match &*data {
|
||||
Some(Ok(CommentTreeResponse { comments, count })) => {
|
||||
let approved_count = *count;
|
||||
|
||||
@ -1,12 +1,25 @@
|
||||
//! 页脚组件
|
||||
//!
|
||||
//! 提供站点版权信息,并在用户向下滚动超过一屏后显示"回到顶部"悬浮按钮。
|
||||
//! 回到顶部的滚动监听与平滑滚动逻辑仅在 WASM 前端生效。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 页脚与回到顶部按钮组件。
|
||||
///
|
||||
/// Props:无。
|
||||
/// 关键行为:
|
||||
/// - 监听窗口滚动,超过一屏时显示回到顶部按钮
|
||||
/// - 点击按钮平滑滚动到顶部,并清理 URL 中的 `#`
|
||||
/// - 滚动监听与平滑滚动仅在 `target_arch = "wasm32"` 下执行
|
||||
#[component]
|
||||
#[allow(unused_mut)]
|
||||
pub fn Footer() -> Element {
|
||||
let mut visible = use_signal(|| false);
|
||||
|
||||
// WASM 下保存 scroll 事件闭包与 window,用于后续清理
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let listener_state = use_hook(|| {
|
||||
Rc::new(RefCell::new(
|
||||
@ -14,12 +27,14 @@ pub fn Footer() -> Element {
|
||||
))
|
||||
});
|
||||
|
||||
// 非 WASM 下保持类型一致,避免编译错误
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let _listener_state = use_hook(|| Rc::new(RefCell::new(None::<()>)));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let listener_state_for_effect = listener_state.clone();
|
||||
|
||||
// 挂载时注册 scroll 监听器,并根据当前滚动位置初始化按钮可见性
|
||||
use_effect(move || {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
@ -56,6 +71,7 @@ pub fn Footer() -> Element {
|
||||
}
|
||||
});
|
||||
|
||||
// 卸载时移除 scroll 监听器,防止内存泄漏
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use_drop(move || {
|
||||
if let Some((closure, window)) = listener_state.borrow_mut().take() {
|
||||
@ -66,6 +82,7 @@ pub fn Footer() -> Element {
|
||||
}
|
||||
});
|
||||
|
||||
// 根据 visible 动态切换按钮显示/隐藏样式
|
||||
let btn_class = use_memo(move || {
|
||||
let base = "fixed bottom-16 right-8 z-50 w-10 h-10 rounded-full bg-paper-entry border border-paper-border shadow-sm flex items-center justify-center cursor-pointer transition-all duration-300 text-paper-secondary hover:text-paper-accent";
|
||||
if visible() {
|
||||
@ -105,6 +122,9 @@ pub fn Footer() -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
/// 平滑滚动到页面顶部,并清理 history 中的 `#` 哈希。
|
||||
///
|
||||
/// 仅在 `target_arch = "wasm32"` 下执行实际滚动,SSR 环境中为空操作。
|
||||
fn scroll_to_top() {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
|
||||
@ -1,9 +1,24 @@
|
||||
//! 表单控件组件
|
||||
//!
|
||||
//! 提供登录、注册、评论等页面共享的输入框、按钮与提示框样式常量与组件。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 输入框基础 CSS 类,统一文本框、邮箱框、URL 框等样式。
|
||||
pub const INPUT_CLASS: &str = "w-full px-4 py-2 border border-paper-border rounded-lg bg-paper-entry text-paper-primary placeholder:text-paper-tertiary focus:outline-none focus:border-paper-accent focus:ring-1 focus:ring-paper-accent/30 transition-colors duration-200";
|
||||
|
||||
/// 主按钮 CSS 类,用于表单提交等主操作按钮。
|
||||
pub const BUTTON_PRIMARY_CLASS: &str = "w-full py-2.5 px-4 bg-paper-accent text-white font-medium rounded-full hover:brightness-110 active:scale-[0.98] transition-all duration-200 cursor-pointer";
|
||||
|
||||
/// 表单输入框组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `r#type`:input 类型(如 `"text"`、`"email"`、`"password"`)
|
||||
/// - `placeholder`:占位提示文本
|
||||
/// - `value`:当前值
|
||||
/// - `disabled`:是否禁用
|
||||
/// - `oninput`:输入事件回调,返回新的字符串值
|
||||
/// - `onkeydown`:可选的键盘事件回调
|
||||
#[component]
|
||||
pub fn FormInput(
|
||||
r#type: &'static str,
|
||||
@ -35,6 +50,10 @@ pub fn FormInput(
|
||||
}
|
||||
}
|
||||
|
||||
/// 表单标签组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `label`:标签文本
|
||||
#[component]
|
||||
pub fn FormLabel(label: &'static str) -> Element {
|
||||
rsx! {
|
||||
@ -44,6 +63,11 @@ pub fn FormLabel(label: &'static str) -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
/// 提示框组件,用于显示成功、错误等状态消息。
|
||||
///
|
||||
/// Props:
|
||||
/// - `message`:提示文本
|
||||
/// - `variant`:风格类型,支持 `"error"`、`"success"` 与其他默认类型
|
||||
#[component]
|
||||
pub fn AlertBox(message: String, variant: &'static str) -> Element {
|
||||
let (bg_class, text_class) = match variant {
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
//! 前台布局组件
|
||||
//!
|
||||
//! 包裹所有前台路由,提供统一的 Header、Footer 与主内容区容器,
|
||||
//! 并为不同路由在 SuspenseBoundary 中展示对应的骨架屏。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::footer::Footer;
|
||||
@ -12,6 +17,7 @@ use crate::components::skeletons::tags_skeleton::TagsSkeleton;
|
||||
use crate::router::Route;
|
||||
use crate::theme::ThemeToggle;
|
||||
|
||||
/// 根据当前前台路由选择对应的骨架屏组件。
|
||||
fn route_skeleton(route: &Route) -> Element {
|
||||
match route {
|
||||
Route::Archives {} => rsx! { DelayedSkeleton { ArchiveSkeleton {} } },
|
||||
@ -23,6 +29,10 @@ fn route_skeleton(route: &Route) -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
/// 前台整体布局组件。
|
||||
///
|
||||
/// 负责渲染 Header(含前台导航与主题切换)、主内容区与 Footer,
|
||||
/// 并在路由内容加载过程中显示与路由匹配的骨架屏。
|
||||
#[component]
|
||||
pub fn FrontendLayout() -> Element {
|
||||
let route = use_route::<Route>();
|
||||
|
||||
@ -1,15 +1,34 @@
|
||||
//! 顶部导航栏组件
|
||||
//!
|
||||
//! 提供站点 Logo、响应式导航菜单项与右侧自定义内容区,
|
||||
//! 支持前台布局与后台布局复用。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
use crate::router::Route;
|
||||
|
||||
/// 导航项配置,用于描述 Header 中的一个链接。
|
||||
///
|
||||
/// 字段:
|
||||
/// - `route`:目标路由
|
||||
/// - `label`:显示文本
|
||||
/// - `is_active`:当前是否处于激活状态
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct NavItemConfig {
|
||||
/// 目标路由。
|
||||
pub route: Route,
|
||||
/// 显示文本。
|
||||
pub label: &'static str,
|
||||
/// 当前是否处于激活状态。
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
/// 顶部导航栏组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `nav_items`:导航项列表
|
||||
/// - `right_content`:右侧自定义内容(如主题切换、登出按钮)
|
||||
#[component]
|
||||
pub fn Header(nav_items: Vec<NavItemConfig>, right_content: Element) -> Element {
|
||||
rsx! {
|
||||
@ -37,6 +56,12 @@ pub fn Header(nav_items: Vec<NavItemConfig>, right_content: Element) -> Element
|
||||
}
|
||||
}
|
||||
|
||||
/// 单个导航项组件,根据 `is_active` 切换高亮样式。
|
||||
///
|
||||
/// Props:
|
||||
/// - `route`:目标路由
|
||||
/// - `label`:显示文本
|
||||
/// - `is_active`:是否高亮
|
||||
#[component]
|
||||
fn NavItem(route: Route, label: &'static str, is_active: bool) -> Element {
|
||||
let base_class = "px-3 py-1 text-base rounded-lg transition-all duration-200";
|
||||
|
||||
@ -1,5 +1,22 @@
|
||||
//! 图片查看器组件
|
||||
//!
|
||||
//! 提供缩略图展示与点击放大后的全屏灯箱(lightbox)查看,
|
||||
//! 支持自定义缩略图参数、alt 文本与懒加载。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 图片查看器组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `src`:原图 URL
|
||||
/// - `thumb_params`:缩略图 URL 参数,默认 `"?w=800"`
|
||||
/// - `alt`:图片替代文本,默认 `"图片"`
|
||||
/// - `lazy_load`:是否启用懒加载,默认 `false`
|
||||
///
|
||||
/// 关键事件:
|
||||
/// - 点击缩略图:打开全屏灯箱
|
||||
/// - 点击遮罩或关闭按钮:关闭灯箱
|
||||
/// - 点击灯箱内容区:阻止事件冒泡,避免误关闭
|
||||
#[component]
|
||||
pub fn ImageViewer(
|
||||
src: String,
|
||||
@ -9,6 +26,7 @@ pub fn ImageViewer(
|
||||
) -> Element {
|
||||
let mut is_open = use_signal(|| false);
|
||||
|
||||
// 拼接缩略图 URL:保留原 URL 的 query 参数并追加 thumb_params
|
||||
let thumb_src = if src.contains('?') {
|
||||
format!(
|
||||
"{}&{}",
|
||||
@ -20,7 +38,7 @@ pub fn ImageViewer(
|
||||
};
|
||||
|
||||
rsx! {
|
||||
// Thumbnail
|
||||
// 缩略图
|
||||
img {
|
||||
class: "cursor-pointer transition-opacity hover:opacity-90",
|
||||
src: "{thumb_src}",
|
||||
@ -29,7 +47,7 @@ pub fn ImageViewer(
|
||||
onclick: move |_| is_open.set(true),
|
||||
}
|
||||
|
||||
// Full-screen lightbox
|
||||
// 全屏灯箱
|
||||
if is_open() {
|
||||
div {
|
||||
class: "image-viewer-overlay",
|
||||
|
||||
@ -1,13 +1,33 @@
|
||||
//! 组件模块
|
||||
//!
|
||||
//! 提供 Dioxus UI 组件,供前端页面(`src/pages/`)使用。
|
||||
//! 包括布局(`frontend_layout`、`admin_layout`)、导航(`header`、`nav`、`footer`)、
|
||||
//! 文章展示(`post`、`post_card`)、评论(`comments`)、骨架屏(`skeletons`)、
|
||||
//! 表单控件(`forms`)以及图片查看器(`image_viewer`)等共享组件。
|
||||
|
||||
/// 后台布局组件。
|
||||
pub mod admin_layout;
|
||||
/// 后台页面骨架屏组件。
|
||||
pub mod admin_skeleton;
|
||||
/// 评论相关组件。
|
||||
pub mod comments;
|
||||
/// 页脚组件。
|
||||
pub mod footer;
|
||||
/// 表单控件组件。
|
||||
pub mod forms;
|
||||
/// 前台布局组件。
|
||||
pub mod frontend_layout;
|
||||
/// 顶部导航栏组件。
|
||||
pub mod header;
|
||||
/// 图片查看器组件。
|
||||
pub mod image_viewer;
|
||||
/// 导航组件。
|
||||
pub mod nav;
|
||||
/// 文章详情组件。
|
||||
pub mod post;
|
||||
/// 文章卡片组件。
|
||||
pub mod post_card;
|
||||
/// 骨架屏组件集合。
|
||||
pub mod skeletons;
|
||||
/// 编辑器页面骨架屏组件。
|
||||
pub mod write_skeleton;
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
//! 前台导航项配置
|
||||
//!
|
||||
//! 根据当前路由生成前台 Header 所需的导航项列表。
|
||||
|
||||
use crate::components::header::NavItemConfig;
|
||||
use crate::router::Route;
|
||||
|
||||
/// 生成前台导航项列表,当前访问的路由会被标记为激活。
|
||||
///
|
||||
/// 参数:
|
||||
/// - `route`:当前路由
|
||||
///
|
||||
/// 返回:包含首页、归档、标签、搜索、关于的导航配置数组。
|
||||
pub fn use_nav_items(route: Route) -> Vec<NavItemConfig> {
|
||||
vec![
|
||||
NavItemConfig {
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
//! 面包屑组件
|
||||
//!
|
||||
//! 在文章详情页展示从首页到当前文章标题的导航路径。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
use crate::router::Route;
|
||||
|
||||
/// 面包屑导航组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `title`:当前文章标题
|
||||
///
|
||||
/// 渲染 `Home > 当前标题` 的面包屑路径。
|
||||
#[component]
|
||||
pub fn Breadcrumbs(title: String) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,8 +1,21 @@
|
||||
//! 文章详情组件模块
|
||||
//!
|
||||
//! 提供文章详情页所需的各个子组件:封面、内容、页眉、元信息、页脚、面包屑、
|
||||
//! 上一篇/下一篇导航与目录。
|
||||
|
||||
/// 面包屑导航组件。
|
||||
pub mod breadcrumbs;
|
||||
/// 文章内容组件。
|
||||
pub mod post_content;
|
||||
/// 文章封面组件。
|
||||
pub mod post_cover;
|
||||
/// 文章页脚组件。
|
||||
pub mod post_footer;
|
||||
/// 文章页眉组件。
|
||||
pub mod post_header;
|
||||
/// 文章元信息组件。
|
||||
pub mod post_meta;
|
||||
/// 上一篇/下一篇导航组件。
|
||||
pub mod post_nav_links;
|
||||
/// 文章目录组件。
|
||||
pub mod post_toc;
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
//! 文章内容组件
|
||||
//!
|
||||
//! 渲染由服务端生成的文章 HTML 内容,并在 WASM 前端初始化交互脚本。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 文章内容组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `content_html`:服务端渲染的文章 HTML 字符串
|
||||
///
|
||||
/// 关键行为:
|
||||
/// - 在 `target_arch = "wasm32"` 环境下执行 `post-content.js` 并调用初始化函数,
|
||||
/// 用于处理代码块、图片点击等前端交互
|
||||
#[component]
|
||||
pub fn PostContent(content_html: String) -> Element {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
//! 文章封面组件
|
||||
//!
|
||||
//! 在文章详情页渲染封面大图,使用图片查看器支持点击放大。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::image_viewer::ImageViewer;
|
||||
|
||||
/// 文章封面组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `src`:封面原图 URL
|
||||
///
|
||||
/// 使用 1200px 宽度的缩略图作为默认封面展示,点击可放大查看。
|
||||
#[component]
|
||||
pub fn PostCover(src: String) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
//! 文章页脚组件
|
||||
//!
|
||||
//! 展示文章标签、上一篇/下一篇导航与返回首页链接。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
@ -5,6 +9,15 @@ use crate::components::post::post_nav_links::PostNavLinks;
|
||||
use crate::models::post::Post;
|
||||
use crate::router::Route;
|
||||
|
||||
/// 文章页脚组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `post`:文章数据模型
|
||||
///
|
||||
/// 展示内容包括:
|
||||
/// - 文章标签云,链接到对应标签详情页
|
||||
/// - 相邻文章导航(如有)
|
||||
/// - 返回首页链接
|
||||
#[component]
|
||||
pub fn PostFooter(post: Post) -> Element {
|
||||
let tags = post.tags.clone();
|
||||
|
||||
@ -1,9 +1,23 @@
|
||||
//! 文章页眉组件
|
||||
//!
|
||||
//! 组合面包屑、标题、摘要与元信息,草稿状态会在标题旁显示草稿图标。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::post::breadcrumbs::Breadcrumbs;
|
||||
use crate::components::post::post_meta::PostMeta;
|
||||
use crate::models::post::{Post, PostStatus};
|
||||
|
||||
/// 文章页眉组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `post`:文章数据模型
|
||||
///
|
||||
/// 展示内容包括:
|
||||
/// - 面包屑导航(Home → 文章标题)
|
||||
/// - 文章标题,草稿状态附加草稿图标
|
||||
/// - 文章摘要(如有)
|
||||
/// - 文章元信息
|
||||
#[component]
|
||||
pub fn PostHeader(post: Post) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
//! 文章元信息组件
|
||||
//!
|
||||
//! 展示文章发布日期、阅读时长与字数统计。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::models::post::Post;
|
||||
|
||||
/// 文章元信息组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `post`:文章数据模型
|
||||
///
|
||||
/// 渲染格式:`日期 · min read · words`
|
||||
#[component]
|
||||
pub fn PostMeta(post: Post) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,9 +1,20 @@
|
||||
//! 文章上一篇/下一篇导航组件
|
||||
//!
|
||||
//! 在文章详情页底部提供相邻文章的快速跳转。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
use crate::models::post::PostNav;
|
||||
use crate::router::Route;
|
||||
|
||||
/// 文章相邻导航组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `prev`:上一篇文章摘要
|
||||
/// - `next`:下一篇文章摘要
|
||||
///
|
||||
/// 左右两侧分别渲染 Prev/Next 链接,若无相邻文章则占位空白。
|
||||
#[component]
|
||||
pub fn PostNavLinks(prev: Option<PostNav>, next: Option<PostNav>) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,5 +1,15 @@
|
||||
//! 文章目录组件
|
||||
//!
|
||||
//! 在文章详情页渲染由服务端生成的目录 HTML,支持折叠展开。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 文章目录(Table of Contents)组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `toc_html`:服务端生成的目录 HTML 字符串
|
||||
///
|
||||
/// 通过 `dangerous_inner_html` 注入目录结构,快捷键 `Alt + C` 可聚焦。
|
||||
#[component]
|
||||
pub fn PostToc(toc_html: String) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
//! 文章卡片组件
|
||||
//!
|
||||
//! 在首页、标签详情等列表中展示单篇文章的标题、摘要、封面、日期与标签。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
@ -5,6 +9,19 @@ use crate::components::image_viewer::ImageViewer;
|
||||
use crate::models::post::Post;
|
||||
use crate::router::Route;
|
||||
|
||||
/// 文章卡片组件。
|
||||
///
|
||||
/// Props:
|
||||
/// - `post`:文章数据模型
|
||||
///
|
||||
/// 展示内容包括:
|
||||
/// - 封面图(如有,使用 400x300 缩略图)
|
||||
/// - 文章标题
|
||||
/// - 摘要(最多两行)
|
||||
/// - 发布日期与标签
|
||||
///
|
||||
/// 关键事件:
|
||||
/// - 点击标签时阻止事件冒泡,避免触发整卡跳转
|
||||
#[component]
|
||||
pub fn PostCard(post: Post) -> Element {
|
||||
let post_slug = post.slug.clone();
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
//! 归档页骨架屏
|
||||
//!
|
||||
//! 在归档数据加载期间展示按年份/月份分组的文章列表占位。
|
||||
|
||||
use crate::components::skeletons::atoms::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 归档页骨架屏
|
||||
/// 结构:统计行("共 N 篇文章") + 年份标题 + 月份标题 + 文章条目列表
|
||||
/// 模拟 2 个年份,每个年份 2 个月,每个月 3 篇文章
|
||||
/// 归档页骨架屏组件。
|
||||
///
|
||||
/// 结构:统计行("共 N 篇文章")+ 年份标题 + 月份标题 + 文章条目列表。
|
||||
/// 模拟 2 个年份,每个年份 2 个月,每个月 3 篇文章。
|
||||
#[component]
|
||||
pub fn ArchiveSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
//! 骨架屏原子组件
|
||||
//!
|
||||
//! 提供通用的脉冲动画占位块,供各页面骨架屏组合使用。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 通用骨架占位块。
|
||||
///
|
||||
/// Props:
|
||||
/// - `class`:Tailwind CSS 类,控制尺寸与形状
|
||||
/// - `style`:可选的内联样式字符串
|
||||
///
|
||||
/// 默认带有 `animate-pulse` 动画与半透明的占位背景。
|
||||
#[component]
|
||||
pub fn SkeletonBox(class: &'static str, style: Option<&'static str>) -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
//! 评论列表骨架屏
|
||||
//!
|
||||
//! 在评论数据加载期间展示评论条目占位。
|
||||
|
||||
use crate::components::skeletons::atoms::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 评论列表骨架屏组件。
|
||||
///
|
||||
/// 渲染一个标题占位与若干条评论条目占位,包含头像、作者名与内容行。
|
||||
#[component]
|
||||
pub fn CommentListSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,21 +1,28 @@
|
||||
//! 延迟骨架屏包装组件
|
||||
//!
|
||||
//! 让骨架屏先以静态灰色块立即显示,延迟一段时间后再启动 pulse 动画,
|
||||
//! 避免快速加载时动画一闪而过带来的视觉抖动。
|
||||
|
||||
use crate::utils::time::sleep_ms;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 骨架屏 pulse 动画延迟(毫秒)
|
||||
/// 加载时间低于此值时骨架屏只显示静态灰色块,避免 pulse 动画一闪而过
|
||||
/// 骨架屏 pulse 动画延迟(毫秒)。
|
||||
///
|
||||
/// 加载时间低于此值时骨架屏只显示静态灰色块,避免 pulse 动画一闪而过。
|
||||
const SKELETON_PULSE_DELAY_MS: u32 = 200;
|
||||
|
||||
/// 延迟 pulse 动画的骨架屏包装组件
|
||||
/// 延迟 pulse 动画的骨架屏包装组件。
|
||||
///
|
||||
/// 骨架屏区域**立即显示**(灰色静态占位块),避免空白闪烁。
|
||||
/// 延迟 SKELETON_PULSE_DELAY_MS 毫秒后,如果仍在加载,则启动 pulse 动画。
|
||||
/// 延迟 `SKELETON_PULSE_DELAY_MS` 毫秒后,如果仍在加载,则启动 pulse 动画。
|
||||
///
|
||||
/// 快加载(< 200ms):用户只看到静态灰色块一闪而过,无动画感知
|
||||
/// 慢加载:灰色块正常 pulse,提示正在加载
|
||||
/// 快加载(< 200ms):用户只看到静态灰色块一闪而过,无动画感知。
|
||||
/// 慢加载:灰色块正常 pulse,提示正在加载。
|
||||
#[component]
|
||||
pub fn DelayedSkeleton(children: Element) -> Element {
|
||||
let mut pulsing = use_signal(|| false);
|
||||
|
||||
// 延迟启动 pulse 动画
|
||||
use_effect(move || {
|
||||
spawn(async move {
|
||||
sleep_ms(SKELETON_PULSE_DELAY_MS).await;
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
//! 首页骨架屏
|
||||
//!
|
||||
//! 模拟首页文章卡片列表与分页区域。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::skeletons::atoms::SkeletonBox;
|
||||
use crate::components::skeletons::post_card_skeleton::PostCardSkeleton;
|
||||
|
||||
/// 首页骨架屏 - 模拟文章卡片列表 + 分页区域
|
||||
/// 显示 5 个文章卡片骨架 + 分页按钮占位
|
||||
/// 首页骨架屏组件。
|
||||
///
|
||||
/// 显示 5 个文章卡片骨架与分页按钮占位。
|
||||
#[component]
|
||||
pub fn HomeSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,10 +1,24 @@
|
||||
//! 骨架屏组件模块
|
||||
//!
|
||||
//! 提供各页面在数据加载期间的占位组件,以及通用骨架原子组件。
|
||||
|
||||
/// 归档页面骨架屏组件。
|
||||
pub mod archive_skeleton;
|
||||
/// 通用骨架原子组件。
|
||||
pub mod atoms;
|
||||
/// 评论区骨架屏组件。
|
||||
pub mod comment_skeleton;
|
||||
/// 延迟显示骨架屏组件。
|
||||
pub mod delayed_skeleton;
|
||||
/// 首页骨架屏组件。
|
||||
pub mod home_skeleton;
|
||||
/// 文章卡片骨架屏组件。
|
||||
pub mod post_card_skeleton;
|
||||
/// 文章详情页骨架屏组件。
|
||||
pub mod post_detail_skeleton;
|
||||
/// 文章列表页骨架屏组件。
|
||||
pub mod posts_skeleton;
|
||||
/// 搜索页骨架屏组件。
|
||||
pub mod search_skeleton;
|
||||
/// 标签页骨架屏组件。
|
||||
pub mod tags_skeleton;
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
//! 文章卡片骨架屏
|
||||
//!
|
||||
//! 模拟 PostCard 组件的视觉占位,用于列表页加载。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::skeletons::atoms::SkeletonBox;
|
||||
|
||||
/// 文章卡片骨架屏 - 模拟 PostCard 的视觉结构
|
||||
/// 包含:标题行(24px bold) + 摘要2行 + 元信息行(日期+标签)
|
||||
/// 文章卡片骨架屏组件。
|
||||
///
|
||||
/// 包含:标题行(24px bold) + 摘要两行 + 元信息行(日期+标签)。
|
||||
#[component]
|
||||
pub fn PostCardSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
//! 文章详情页骨架屏
|
||||
//!
|
||||
//! 在文章详情数据加载期间展示面包屑、标题、摘要、元信息、封面与正文占位。
|
||||
|
||||
use crate::components::skeletons::atoms::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 文章详情页骨架屏
|
||||
/// 结构:面包屑 + 标题(h1) + 摘要 + 元信息 + 封面图 + 正文(多段) + Footer
|
||||
/// 文章详情页骨架屏组件。
|
||||
///
|
||||
/// 结构:面包屑 + 标题(h1) + 摘要 + 元信息 + 封面图 + 正文(多段) + 页脚占位。
|
||||
#[component]
|
||||
pub fn PostDetailSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
//! 后台文章管理列表骨架屏
|
||||
//!
|
||||
//! 模拟后台 Posts 页面的表格结构。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::skeletons::atoms::SkeletonBox;
|
||||
|
||||
/// 后台文章管理列表骨架屏组件。
|
||||
///
|
||||
/// 渲染带表头的表格占位,包含 10 行数据行。
|
||||
#[component]
|
||||
pub fn PostsSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
//! 搜索页骨架屏
|
||||
//!
|
||||
//! 在搜索结果加载期间展示搜索卡片列表占位。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::skeletons::post_card_skeleton::PostCardSkeleton;
|
||||
|
||||
/// 搜索页骨架屏 - 搜索结果卡片列表
|
||||
/// 模拟 3 个搜索结果卡片(与搜索页现有内联骨架结构一致)
|
||||
/// 搜索页骨架屏组件。
|
||||
///
|
||||
/// 模拟 3 个搜索结果卡片,与搜索页现有内联骨架结构一致。
|
||||
#[component]
|
||||
pub fn SearchSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
//! 标签相关骨架屏
|
||||
//!
|
||||
//! 提供标签列表页与标签详情页的加载占位组件。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::skeletons::atoms::SkeletonBox;
|
||||
use crate::components::skeletons::post_card_skeleton::PostCardSkeleton;
|
||||
|
||||
/// 标签列表页骨架屏
|
||||
/// 结构:统计行("共 N 个标签,M 篇文章") + 标签云(flex wrap 的 pill 列表)
|
||||
/// 标签列表页骨架屏组件。
|
||||
///
|
||||
/// 结构:统计行("共 N 个标签,M 篇文章")+ 标签云(flex wrap 的 pill 列表)。
|
||||
#[component]
|
||||
pub fn TagsSkeleton() -> Element {
|
||||
rsx! {
|
||||
@ -35,7 +40,9 @@ pub fn TagsSkeleton() -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
/// 标签详情页骨架屏 - 与首页文章列表结构相同
|
||||
/// 标签详情页骨架屏组件。
|
||||
///
|
||||
/// 结构与首页文章列表相同,包含统计行与文章卡片骨架。
|
||||
#[component]
|
||||
pub fn TagDetailSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
//! 文章编辑器页骨架屏
|
||||
//!
|
||||
//! 在写文章/编辑文章页面加载时展示,模拟标题、元信息、编辑器工具栏与正文区域。
|
||||
|
||||
use crate::components::skeletons::atoms::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 文章编辑器页骨架屏组件。
|
||||
///
|
||||
/// 模拟 Write 页面的整体结构:顶部标题与元信息、中间编辑器区域、底部操作按钮。
|
||||
#[component]
|
||||
pub fn WriteSkeleton() -> Element {
|
||||
rsx! {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user