25 KiB
ThriveX 博客系统 - API 和组件文档
项目简介
ThriveX 是一个基于 Next.js 15 + React 19 的现代化博客管理系统,采用 TypeScript 开发,具有高颜值的界面设计和丰富的功能特性。
📖 目录
🏗️ 项目架构
src/
├── api/ # API 接口
├── app/ # Next.js App Router 页面
├── components/ # 可复用组件
├── hooks/ # 自定义 Hooks
├── lib/ # 第三方库配置
├── stores/ # 状态管理
├── types/ # TypeScript 类型定义
├── utils/ # 工具函数
├── assets/ # 静态资源
└── styles/ # 样式文件
🔌 API 接口文档
文章相关 API
getArticleDataAPI(id: number, password?: string)
获取指定文章的详细数据
参数:
id(number): 文章IDpassword(string, 可选): 加密文章的密码
返回值: Promise<ResponseData<Article>>
示例:
import { getArticleDataAPI } from '@/api/article';
// 获取普通文章
const article = await getArticleDataAPI(123);
// 获取加密文章
const encryptedArticle = await getArticleDataAPI(123, 'password123');
getArticleListAPI()
获取所有文章列表
返回值: Promise<ResponseData<Article[]>>
示例:
import { getArticleListAPI } from '@/api/article';
const articles = await getArticleListAPI();
console.log(articles.data); // 文章数组
getArticlePagingAPI(data: QueryData)
分页获取文章数据
参数:
data(QueryData): 查询参数对象pagination.page(number): 页码pagination.size(number): 每页数量query(FilterData, 可选): 过滤条件
返回值: Promise<ResponseData<Paginate<Article[]>>>
示例:
import { getArticlePagingAPI } from '@/api/article';
const result = await getArticlePagingAPI({
pagination: { page: 1, size: 10 },
query: { key: '搜索关键词' }
});
getRandomArticleListAPI()
获取随机文章列表
返回值: Promise<ResponseData<Article[]>>
getRecommendedArticleListAPI()
获取推荐文章列表
返回值: Promise<ResponseData<Article[]>>
recordViewAPI(id: number)
增加文章浏览量
参数:
id(number): 文章ID
返回值: Promise<ResponseData<void>>
分类相关 API
getCateListAPI()
获取所有分类列表
返回值: Promise<ResponseData<Cate[]>>
示例:
import { getCateListAPI } from '@/api/cate';
const categories = await getCateListAPI();
getCateArticleListAPI(id: number, page: number)
获取指定分类下的文章列表
参数:
id(number): 分类IDpage(number): 页码
返回值: Promise<ResponseData<Article[]>>
getCateArticleCountAPI()
获取各分类的文章数量统计
返回值: Promise<ResponseData<any>>
标签相关 API
getTagListAPI()
获取所有标签列表
返回值: Promise<ResponseData<Tag[]>>
getTagListWithArticleCountAPI()
获取标签列表(包含文章数量)
返回值: Promise<ResponseData<TagWithCount[]>>
getTagArticleListAPI(id: number, page: number)
获取指定标签下的文章列表
参数:
id(number): 标签IDpage(number): 页码
返回值: Promise<ResponseData<Article[]>>
评论相关 API
addCommentDataAPI(data: Comment)
添加评论
参数:
data(Comment): 评论数据对象
返回值: Promise<ResponseData<Comment>>
示例:
import { addCommentDataAPI } from '@/api/comment';
const comment = await addCommentDataAPI({
articleId: 123,
content: '这是一条评论',
nickname: '访客',
email: 'user@example.com'
});
getCommentListAPI()
获取所有评论列表
返回值: Promise<ResponseData<Comment[]>>
getArticleCommentListAPI(articleId: number, paginate: Page)
获取指定文章的评论列表
参数:
articleId(number): 文章IDpaginate(Page): 分页参数
返回值: Promise<ResponseData<Comment[]>>
配置相关 API
getWebConfigDataAPI<T>(name: string)
获取网站配置数据
参数:
name(string): 配置名称
返回值: Promise<ResponseData<T>>
示例:
import { getWebConfigDataAPI } from '@/api/config';
import { Web } from '@/types/app/config';
const webConfig = await getWebConfigDataAPI<{ value: Web }>('web');
🧩 组件库文档
布局组件
Container
页面容器组件,提供响应式布局
Props:
children(React.ReactNode): 子组件
用法:
import Container from '@/components/Container';
<Container>
<div>页面内容</div>
</Container>
特性:
- 自动居中布局
- 响应式宽度:lg: 950px, xl: 1200px
- 内置 padding 和 margin
Header
网站头部导航组件
特性:
- 暗黑模式切换
- 响应式导航菜单
- 滚动时样式变化
- 分类导航下拉菜单
用法:
import Header from '@/components/Header';
<Header />
Footer
网站底部组件
特性:
- 版权信息展示
- 友情链接
- 社交媒体链接
交互组件
Loading
加载动画组件
用法:
import Loading from '@/components/Loading';
<Loading />
特性:
- SVG 动画效果
- 全屏遮罩
- 支持暗黑模式
Pagination
分页组件
Props:
total(number): 总页数page(number): 当前页码size(number, 可选): 每页大小path(string, 可选): 路径前缀className(string, 可选): 自定义样式类
用法:
import Pagination from '@/components/Pagination';
<Pagination
total={10}
page={1}
path="/articles"
className="mt-4"
/>
Search
搜索组件
特性:
- 实时搜索
- 防抖处理
- 搜索历史
- 搜索建议
功能组件
Show
条件渲染组件
Props:
when(boolean): 显示条件children(React.ReactNode): 子组件
用法:
import Show from '@/components/Show';
<Show when={isVisible}>
<div>只在条件为真时显示</div>
</Show>
Skeleton
骨架屏组件
Props:
loading(boolean): 是否显示骨架屏children(React.ReactNode): 实际内容
用法:
import Skeleton from '@/components/Skeleton';
<Skeleton loading={isLoading}>
<div>实际内容</div>
</Skeleton>
Empty
空状态组件
Props:
description(string, 可选): 描述文本image(string, 可选): 自定义图片
用法:
import Empty from '@/components/Empty';
<Empty description="暂无数据" />
特效组件
Confetti
彩带动画组件
用法:
import Confetti from '@/components/Confetti';
<Confetti />
Ripple
水波纹效果组件
Props:
children(React.ReactNode): 子组件
用法:
import Ripple from '@/components/Ripple';
<Ripple>
<button>点击有水波纹效果</button>
</Ripple>
Starry
星空背景组件
用法:
import Starry from '@/components/Starry';
<Starry />
Lantern
灯笼装饰组件
用法:
import Lantern from '@/components/Lantern';
<Lantern />
内容组件
ArticleLayout
文章布局组件
Props:
article(Article): 文章数据layout(ArticleLayout): 布局类型
用法:
import ArticleLayout from '@/components/ArticleLayout';
<ArticleLayout
article={articleData}
layout="card"
/>
Slide
轮播图组件
Props:
images(string[]): 图片URL数组autoPlay(boolean, 可选): 自动播放interval(number, 可选): 播放间隔
用法:
import Slide from '@/components/Slide';
<Slide
images={['/img1.jpg', '/img2.jpg']}
autoPlay={true}
interval={3000}
/>
Swiper
滑动组件
Props:
children(React.ReactNode): 子组件spaceBetween(number, 可选): 间距slidesPerView(number, 可选): 每页显示数量
用法:
import Swiper from '@/components/Swiper';
<Swiper spaceBetween={20} slidesPerView={3}>
<div>Slide 1</div>
<div>Slide 2</div>
<div>Slide 3</div>
</Swiper>
工具组件
Typed
打字机效果组件
Props:
strings(string[]): 要显示的字符串数组typeSpeed(number, 可选): 打字速度backSpeed(number, 可选): 删除速度
用法:
import Typed from '@/components/Typed';
<Typed
strings={['Hello World!', 'Welcome to ThriveX!']}
typeSpeed={50}
backSpeed={30}
/>
Tools
工具栏组件
特性:
- 回到顶部
- 暗黑模式切换
- 分享功能
- 全屏功能
用法:
import Tools from '@/components/Tools';
<Tools />
RandomAvatar
随机头像生成组件
Props:
seed(string): 生成种子size(number, 可选): 头像大小
用法:
import RandomAvatar from '@/components/RandomAvatar';
<RandomAvatar seed="user123" size={64} />
加密组件
Encrypt
内容加密组件
Props:
children(React.ReactNode): 需要加密的内容password(string): 密码onUnlock(Function, 可选): 解锁回调
用法:
import Encrypt from '@/components/Encrypt';
<Encrypt password="123456" onUnlock={handleUnlock}>
<div>加密内容</div>
</Encrypt>
🛠️ 工具函数库
日期格式化
dayFormat(timestamp: number | string)
将时间戳格式化为相对时间
参数:
timestamp(number | string): 时间戳
返回值: string
示例:
import { dayFormat } from '@/utils';
console.log(dayFormat(Date.now())); // "今天"
console.log(dayFormat(Date.now() - 86400000)); // "昨天"
console.log(dayFormat(Date.now() - 172800000)); // "前天"
console.log(dayFormat(Date.now() - 604800000)); // "7 天前"
HTML 解析工具
HTMLParser.extractText(htmlString: string)
从 HTML 字符串中提取纯文本
参数:
htmlString(string): HTML 字符串
返回值: string
示例:
import { HTMLParser } from '@/utils/htmlParser';
const html = '<p>Hello <strong>World</strong>!</p>';
const text = HTMLParser.extractText(html);
console.log(text); // "Hello World!"
HTMLParser.sanitize(htmlString: string, options?)
安全地清理 HTML 内容
参数:
htmlString(string): HTML 字符串options(object, 可选): 清理选项
返回值: string
示例:
import { HTMLParser } from '@/utils/htmlParser';
const dirtyHTML = '<script>alert("xss")</script><p>Safe content</p>';
const cleanHTML = HTMLParser.sanitize(dirtyHTML);
console.log(cleanHTML); // "<p>Safe content</p>"
异步处理工具
to<T, U>(promise: Promise<T>)
Promise 错误处理包装器
参数:
promise(Promise): 需要处理的 Promise
返回值: Promise<[U, undefined] | [null, T]>
示例:
import { to } from '@/utils';
const [err, data] = await to(fetch('/api/data'));
if (err) {
console.error('请求失败:', err);
} else {
console.log('请求成功:', data);
}
工具函数
getRandom(min: number, max: number)
生成指定范围内的随机数
参数:
min(number): 最小值max(number): 最大值
返回值: number
示例:
import { getRandom } from '@/utils';
const randomNum = getRandom(1, 100);
console.log(randomNum); // 1-100 之间的随机数
cn(...inputs: ClassValue[])
CSS 类名合并工具
参数:
inputs(ClassValue[]): 类名数组
返回值: string
示例:
import { cn } from '@/lib/utils';
const className = cn(
'base-class',
isActive && 'active',
'another-class'
);
网络请求工具
Request<T>(method: string, api: string, data?: any, caching?: boolean)
统一的 HTTP 请求函数
参数:
method(string): 请求方法 (GET, POST, PUT, DELETE)api(string): API 端点data(any, 可选): 请求数据caching(boolean, 可选): 是否缓存
返回值: Promise<ResponseData<T>>
示例:
import Request from '@/utils/request';
// GET 请求
const response = await Request<User>('GET', '/user/123');
// POST 请求
const newUser = await Request<User>('POST', '/user', {
name: 'John',
email: 'john@example.com'
});
🎣 自定义 Hooks
useDebounce(func: Function, wait: number)
防抖 Hook
参数:
func(Function): 需要防抖的函数wait(number): 防抖延迟时间(毫秒)
返回值: Function
示例:
import useDebounce from '@/hooks/useDebounce';
function SearchComponent() {
const [query, setQuery] = useState('');
const debouncedSearch = useDebounce((searchTerm: string) => {
// 执行搜索操作
console.log('搜索:', searchTerm);
}, 300);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="搜索..."
/>
);
}
📦 状态管理
Config Store
全局配置状态管理
状态:
isDark(boolean): 是否暗黑模式web(Web): 网站配置theme(Theme): 主题配置
方法:
setIsDark(status: boolean): 设置暗黑模式setWeb(data: Web): 设置网站配置setTheme(data: Theme): 设置主题配置
示例:
import { useConfigStore } from '@/stores';
function ThemeToggle() {
const { isDark, setIsDark } = useConfigStore();
const toggleTheme = () => {
setIsDark(!isDark);
};
return (
<button onClick={toggleTheme}>
{isDark ? '🌞' : '🌙'}
</button>
);
}
📋 类型定义
响应数据类型
ResponseData<T>
API 响应数据结构
interface ResponseData<T> {
code: number; // 状态码
message: string; // 响应消息
data: T; // 响应数据
}
Paginate<T>
分页数据结构
interface Paginate<T> {
next: boolean; // 是否有下一页
prev: boolean; // 是否有上一页
page: number; // 当前页码
size: number; // 每页大小
pages: number; // 总页数
total: number; // 总记录数
result: T; // 数据结果
}
文章相关类型
Article
文章数据结构
interface Article {
id?: number;
title: string; // 标题
description: string; // 描述
content: string; // 内容
cover: string; // 封面图
cateIds: string; // 分类ID(逗号分隔)
cateList: Cate[]; // 分类列表
tagIds: string; // 标签ID(逗号分隔)
tagList: Tag[]; // 标签列表
view?: number; // 浏览量
comment?: number; // 评论数
config: Config; // 配置信息
prev: { id: number; title: string }; // 上一篇
next: { id: number; title: string }; // 下一篇
createTime?: string; // 创建时间
}
Config
文章配置结构
interface Config {
id?: number;
articleId?: number;
top: number; // 置顶级别
status: Status; // 状态:show | no_home | hide
isEncrypt: number; // 是否加密
isDel: number; // 是否删除
password: string; // 加密密码
}
分类和标签类型
Cate
分类数据结构
interface Cate {
id?: number;
name: string; // 分类名称
description?: string; // 分类描述
cover?: string; // 分类封面
sort?: number; // 排序
createTime?: string; // 创建时间
}
Tag
标签数据结构
interface Tag {
id?: number;
name: string; // 标签名称
color?: string; // 标签颜色
createTime?: string; // 创建时间
}
评论类型
Comment
评论数据结构
interface Comment {
id?: number;
articleId: number; // 文章ID
parentId?: number; // 父评论ID
content: string; // 评论内容
nickname: string; // 昵称
email: string; // 邮箱
website?: string; // 网站
avatar?: string; // 头像
ip?: string; // IP地址
address?: string; // 地址
isTop: number; // 是否置顶
createTime?: string; // 创建时间
children?: Comment[]; // 子评论
}
配置类型
Web
网站配置类型
interface Web {
name: string; // 网站名称
title: string; // 网站标题
subhead: string; // 副标题
description: string; // 网站描述
keyword: string; // 关键词
author: string; // 作者
email: string; // 邮箱
domain: string; // 域名
favicon: string; // 网站图标
logo: string; // 网站Logo
cover: string; // 封面图
}
Theme
主题配置类型
interface Theme {
primaryColor: string; // 主色调
layout: ArticleLayout; // 文章布局
showTools: boolean; // 显示工具栏
showLantern: boolean; // 显示灯笼
showSakura: boolean; // 显示樱花
}
💡 使用示例
1. 创建文章列表页面
import { useState, useEffect } from 'react';
import { getArticlePagingAPI } from '@/api/article';
import Container from '@/components/Container';
import Loading from '@/components/Loading';
import Pagination from '@/components/Pagination';
import { Article } from '@/types/app/article';
export default function ArticleList() {
const [articles, setArticles] = useState<Article[]>([]);
const [loading, setLoading] = useState(true);
const [pagination, setPagination] = useState({
page: 1,
total: 0,
size: 10
});
useEffect(() => {
fetchArticles();
}, [pagination.page]);
const fetchArticles = async () => {
setLoading(true);
try {
const response = await getArticlePagingAPI({
pagination: {
page: pagination.page,
size: pagination.size
}
});
if (response?.data) {
setArticles(response.data.result);
setPagination(prev => ({
...prev,
total: response.data.pages
}));
}
} catch (error) {
console.error('获取文章失败:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return <Loading />;
}
return (
<Container>
<div className="article-list">
{articles.map(article => (
<div key={article.id} className="article-item">
<h2>{article.title}</h2>
<p>{article.description}</p>
<div className="article-meta">
<span>浏览量: {article.view}</span>
<span>评论: {article.comment}</span>
</div>
</div>
))}
</div>
<Pagination
total={pagination.total}
page={pagination.page}
className="mt-8"
/>
</Container>
);
}
2. 创建搜索组件
import { useState } from 'react';
import { getArticlePagingAPI } from '@/api/article';
import useDebounce from '@/hooks/useDebounce';
import { Article } from '@/types/app/article';
export default function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Article[]>([]);
const [loading, setLoading] = useState(false);
const debouncedSearch = useDebounce(async (searchTerm: string) => {
if (!searchTerm.trim()) {
setResults([]);
return;
}
setLoading(true);
try {
const response = await getArticlePagingAPI({
pagination: { page: 1, size: 10 },
query: { key: searchTerm }
});
if (response?.data) {
setResults(response.data.result);
}
} catch (error) {
console.error('搜索失败:', error);
} finally {
setLoading(false);
}
}, 300);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<div className="search-component">
<input
type="text"
value={query}
onChange={handleSearch}
placeholder="搜索文章..."
className="search-input"
/>
{loading && <div>搜索中...</div>}
<div className="search-results">
{results.map(article => (
<div key={article.id} className="search-result-item">
<h3>{article.title}</h3>
<p>{article.description}</p>
</div>
))}
</div>
</div>
);
}
3. 使用主题配置
import { useConfigStore } from '@/stores';
import { Switch } from '@heroui/react';
export default function ThemeSettings() {
const { isDark, setIsDark, theme, setTheme } = useConfigStore();
const toggleDarkMode = () => {
setIsDark(!isDark);
// 应用到 document
document.documentElement.classList.toggle('dark', !isDark);
};
const updateThemeColor = (color: string) => {
setTheme({
...theme,
primaryColor: color
});
// 应用到 CSS 变量
document.documentElement.style.setProperty('--primary-color', color);
};
return (
<div className="theme-settings">
<div className="setting-item">
<label>暗黑模式</label>
<Switch
isSelected={isDark}
onValueChange={toggleDarkMode}
/>
</div>
<div className="setting-item">
<label>主题色</label>
<input
type="color"
value={theme.primaryColor || '#3b82f6'}
onChange={(e) => updateThemeColor(e.target.value)}
/>
</div>
</div>
);
}
4. HTML 内容处理
import { HTMLParser } from '@/utils/htmlParser';
import { useEffect, useState } from 'react';
export default function ArticleContent({ htmlContent }: { htmlContent: string }) {
const [processedContent, setProcessedContent] = useState('');
useEffect(() => {
// 清理和安全化 HTML 内容
const sanitized = HTMLParser.sanitize(htmlContent, {
allowedTags: ['p', 'br', 'strong', 'em', 'a', 'img', 'h1', 'h2', 'h3', 'code', 'pre'],
allowedAttributes: ['href', 'src', 'alt', 'title'],
maxLength: 10000
});
setProcessedContent(sanitized);
}, [htmlContent]);
// 提取纯文本用于摘要
const textContent = HTMLParser.extractText(htmlContent);
const summary = textContent.slice(0, 200) + '...';
return (
<article>
<div className="article-summary">
{summary}
</div>
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: processedContent }}
/>
</article>
);
}
5. 错误处理示例
import { to } from '@/utils';
import { getArticleDataAPI } from '@/api/article';
export default function ArticlePage({ id }: { id: number }) {
const [article, setArticle] = useState(null);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
useEffect(() => {
loadArticle();
}, [id]);
const loadArticle = async () => {
setLoading(true);
setError('');
const [err, response] = await to(getArticleDataAPI(id));
if (err) {
setError('文章加载失败');
console.error('Error loading article:', err);
} else if (response?.data) {
setArticle(response.data);
} else {
setError('文章不存在');
}
setLoading(false);
};
if (loading) return <Loading />;
if (error) return <div className="error-message">{error}</div>;
if (!article) return <div>文章不存在</div>;
return (
<div className="article-page">
<h1>{article.title}</h1>
<div dangerouslySetInnerHTML={{ __html: article.content }} />
</div>
);
}
📝 环境配置
在使用 API 前,请确保正确配置环境变量:
# .env.local
NEXT_PUBLIC_PROJECT_API=http://localhost:8080/api
NEXT_PUBLIC_CACHING_TIME=3600
🚀 快速开始
-
安装依赖
npm install # 或 pnpm install -
配置环境变量
cp .env.example .env.local # 编辑 .env.local 文件 -
启动开发服务器
npm run dev # 或 pnpm dev -
访问应用 打开浏览器访问 http://localhost:3000
📞 技术支持
如果您在使用过程中遇到问题,可以通过以下方式获取帮助:
- 📧 邮箱:liuyuyang1024@yeah.net
- 🌐 项目官网:https://thrivex.liuyuyang.net/
- 📖 在线文档:https://docs.liuyuyang.net/
- 🔗 项目预览:https://liuyuyang.net/
© 2024 ThriveX Blog System. All rights reserved.