mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-09-03 06:24:24 +08:00
feat: 更新文章相关API和组件以增强搜索功能
1. 修改getArticlePagingAPI以支持按标题模糊搜索,更新相关参数名称。 2. 优化搜索组件逻辑,使用防抖机制处理关键词变化,避免过多请求。 3. 更新CommentForm组件的按钮样式,提升用户交互体验。 4. 重构useDebounce钩子,确保最新的函数和等待时间被正确引用。
This commit is contained in:
@@ -6,8 +6,8 @@ export const getArticleDataAPI = async (id: number, password?: string) => {
|
||||
return await Request<Article>('GET', `/article${!password ? `/${id}` : `/${id}?password=${password}`}`);
|
||||
}
|
||||
|
||||
// 获取文章列表
|
||||
export const getArticlePagingAPI = async (params?: Page & { key?: string }) => {
|
||||
// 获取文章列表(title 按标题模糊搜索)
|
||||
export const getArticlePagingAPI = async (params?: Page & { title?: string }) => {
|
||||
return await Request<Paginate<Article[]>>('GET', `/article`, {
|
||||
params: params ?? {}
|
||||
});
|
||||
|
||||
@@ -242,7 +242,7 @@ const CommentForm = ({ articleId }: Props) => {
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<button className="w-full h-10 mt-2 text-white rounded-md bg-primary hover:bg-primary/80 active:bg-primary/90 active:scale-95 transition-[scale] text-center cursor-pointer" type="submit">
|
||||
<button className="w-full h-10 text-white rounded-md bg-primary hover:bg-primary/80 active:bg-primary/90 active:scale-95 transition-[scale] text-center cursor-pointer" type="submit">
|
||||
发表评论
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Link from 'next/link';
|
||||
import { Modal, TextField, type DisclosureProps } from '@/ThriveUI';
|
||||
import { getArticlePagingAPI } from '@/api/article';
|
||||
import { Article } from '@/types/app/article';
|
||||
import useDebounce from '@/hooks/useDebounce';
|
||||
import Empty from '../Empty';
|
||||
|
||||
interface Props {
|
||||
@@ -18,29 +17,6 @@ export default ({ disclosure }: Props) => {
|
||||
const [data, setData] = useState<Paginate<Article[]>>();
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
const getArticleList = async (key: string) => {
|
||||
if (key.trim().length === 0) {
|
||||
setData(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await getArticlePagingAPI({
|
||||
key,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
setData(data);
|
||||
};
|
||||
|
||||
const debouncedFetchArticles = useDebounce(getArticleList, 300);
|
||||
|
||||
const onSearchArticle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const key = e.target.value;
|
||||
setSearchKey(key);
|
||||
debouncedFetchArticles(key);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setData(undefined);
|
||||
@@ -48,14 +24,49 @@ export default ({ disclosure }: Props) => {
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// 关键词变化时防抖请求;清理函数取消过期请求,避免清空后再搜被旧响应覆盖
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const key = searchKey.trim();
|
||||
if (!key) {
|
||||
setData(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const timer = window.setTimeout(async () => {
|
||||
try {
|
||||
const { data: result } = await getArticlePagingAPI({
|
||||
title: key,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
if (!cancelled) setData(result);
|
||||
} catch {
|
||||
if (!cancelled) setData(undefined);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [searchKey, isOpen]);
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose} title="搜索文章" className="max-w-2xl">
|
||||
<div className="mb-7">
|
||||
<TextField type="text" placeholder="请输入文章关键词" value={searchKey} onChange={onSearchArticle} />
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="请输入文章关键词"
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="mt-4">
|
||||
{data?.result
|
||||
? data?.result?.map((item) => (
|
||||
{data?.result?.length
|
||||
? data.result.map((item) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/article/${item.id}`}
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
import { useRef } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function useDebounce<T extends (...args: any[]) => any>(func: T, wait: number) {
|
||||
const timeoutRef = useRef<number | undefined>(null);
|
||||
/** 返回稳定的防抖函数;始终调用最新的 func,避免闭包过期 */
|
||||
export default function useDebounce<T extends (...args: never[]) => unknown>(func: T, wait: number) {
|
||||
const funcRef = useRef(func);
|
||||
funcRef.current = func;
|
||||
const waitRef = useRef(wait);
|
||||
const timeoutRef = useRef<number | undefined>(undefined);
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
funcRef.current(...args);
|
||||
}, wait);
|
||||
};
|
||||
useEffect(() => {
|
||||
funcRef.current = func;
|
||||
}, [func]);
|
||||
|
||||
useEffect(() => {
|
||||
waitRef.current = wait;
|
||||
}, [wait]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current !== undefined) clearTimeout(timeoutRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const debouncedRef = useRef<T>(null as unknown as T);
|
||||
if (!debouncedRef.current) {
|
||||
debouncedRef.current = ((...args: Parameters<T>) => {
|
||||
if (timeoutRef.current !== undefined) clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
timeoutRef.current = undefined;
|
||||
funcRef.current(...args);
|
||||
}, waitRef.current);
|
||||
}) as T;
|
||||
}
|
||||
|
||||
return debouncedRef.current;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
type ArticlePagingParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
key?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export async function getArticleCacheAPI(id: number) {
|
||||
@@ -25,19 +25,19 @@ export async function getArticlePagingCacheAPI(params: ArticlePagingParams = {})
|
||||
'use cache';
|
||||
const pageNum = params.pageNum ?? 1;
|
||||
const pageSize = params.pageSize ?? 8;
|
||||
const key = params.key ?? '';
|
||||
const title = params.title ?? '';
|
||||
|
||||
cacheLife('blog');
|
||||
cacheTag(
|
||||
CACHE_TAGS.articles,
|
||||
`${CACHE_TAGS.articlesList}-${pageNum}-${pageSize}`,
|
||||
...(key ? [`${CACHE_TAGS.articlesList}-search-${key}`] : []),
|
||||
...(title ? [`${CACHE_TAGS.articlesList}-search-${title}`] : []),
|
||||
);
|
||||
|
||||
return getArticlePagingAPI({
|
||||
pageNum,
|
||||
pageSize,
|
||||
...(key ? { key } : {}),
|
||||
...(title ? { title } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user