173 lines
5.2 KiB
TypeScript
173 lines
5.2 KiB
TypeScript
|
|
import { useTRPC } from '@fenghuo/client';
|
||
|
|
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
||
|
|
import { useState, useMemo, useEffect } from 'react';
|
||
|
|
import type { FilterState, PaginationState } from '../lib/articles/types.js';
|
||
|
|
import { DEFAULT_PAGE_SIZE } from '../lib/articles/constants.js';
|
||
|
|
import { Prisma } from '@fenghuo/db';
|
||
|
|
|
||
|
|
export function useArticles() {
|
||
|
|
const trpc = useTRPC();
|
||
|
|
// 状态管理
|
||
|
|
const [filters, setFilters] = useState<FilterState>({
|
||
|
|
searchTerm: '',
|
||
|
|
statusFilter: 'all',
|
||
|
|
categoryFilter: 'all',
|
||
|
|
sortBy: 'created-desc',
|
||
|
|
});
|
||
|
|
|
||
|
|
const [pagination, setPagination] = useState<PaginationState>({
|
||
|
|
currentPage: 1,
|
||
|
|
pageSize: DEFAULT_PAGE_SIZE,
|
||
|
|
totalPages: 1,
|
||
|
|
totalCount: 0
|
||
|
|
});
|
||
|
|
const [selectedArticles, setSelectedArticles] = useState<string[]>([]);
|
||
|
|
const [quickEditId, setQuickEditId] = useState<string | null>(null);
|
||
|
|
const [batchAction, setBatchAction] = useState<string>('');
|
||
|
|
|
||
|
|
// 将前端筛选状态映射到 Prisma 查询条件
|
||
|
|
const { where, orderBy } = useMemo(() => {
|
||
|
|
const where: Prisma.PostWhereInput = {};
|
||
|
|
const orderBy: Prisma.PostOrderByWithRelationInput = {};
|
||
|
|
|
||
|
|
// 组合查询条件
|
||
|
|
const andConditions: Prisma.PostWhereInput[] = [];
|
||
|
|
|
||
|
|
if (filters.statusFilter !== 'all') {
|
||
|
|
andConditions.push({ status: filters.statusFilter });
|
||
|
|
} else {
|
||
|
|
// 默认不包含草稿和垃圾箱
|
||
|
|
andConditions.push({ status: { notIn: ['DRAFT', 'TRASH'] } });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (filters.searchTerm) {
|
||
|
|
andConditions.push({
|
||
|
|
OR: [
|
||
|
|
{ title: { contains: filters.searchTerm, mode: 'insensitive' } },
|
||
|
|
{ content: { contains: filters.searchTerm, mode: 'insensitive' } },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (filters.categoryFilter !== 'all') {
|
||
|
|
andConditions.push({ terms: { some: { id: filters.categoryFilter } } });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (andConditions.length > 0) {
|
||
|
|
where.AND = andConditions;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 排序
|
||
|
|
switch (filters.sortBy) {
|
||
|
|
case 'created-asc':
|
||
|
|
orderBy.createdAt = 'asc';
|
||
|
|
break;
|
||
|
|
case 'created-desc':
|
||
|
|
orderBy.createdAt = 'desc';
|
||
|
|
break;
|
||
|
|
case 'published-asc':
|
||
|
|
orderBy.publishedAt = 'asc';
|
||
|
|
break;
|
||
|
|
case 'published-desc':
|
||
|
|
orderBy.publishedAt = 'desc';
|
||
|
|
break;
|
||
|
|
case 'title-asc':
|
||
|
|
orderBy.title = 'asc';
|
||
|
|
break;
|
||
|
|
case 'title-desc':
|
||
|
|
orderBy.title = 'desc';
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return { where, orderBy };
|
||
|
|
}, [filters]);
|
||
|
|
|
||
|
|
|
||
|
|
const { data: articlesData, isLoading: isLoadingArticles, refetch: refetchArticles } = useQuery(
|
||
|
|
trpc.post.findManyWithPagination.queryOptions(
|
||
|
|
{
|
||
|
|
page: pagination.currentPage,
|
||
|
|
pageSize: pagination.pageSize,
|
||
|
|
where,
|
||
|
|
orderBy
|
||
|
|
},
|
||
|
|
{
|
||
|
|
placeholderData: keepPreviousData,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (articlesData) {
|
||
|
|
setPagination(prev => ({
|
||
|
|
...prev,
|
||
|
|
totalPages: articlesData.totalPages,
|
||
|
|
totalCount: articlesData.totalCount
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}, [articlesData]);
|
||
|
|
|
||
|
|
const { data: statsData, isLoading: isLoadingStats } = useQuery(
|
||
|
|
trpc.post.getStats.queryOptions({ where })
|
||
|
|
);
|
||
|
|
|
||
|
|
// 更新筛选器
|
||
|
|
const updateFilters = (updates: Partial<FilterState>) => {
|
||
|
|
setFilters(prev => ({ ...prev, ...updates }));
|
||
|
|
setPagination(prev => ({ ...prev, currentPage: 1 })); // 重置页码
|
||
|
|
};
|
||
|
|
|
||
|
|
const setCurrentPage = (page: number) => {
|
||
|
|
setPagination(prev => ({ ...prev, currentPage: page }));
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
// 选择操作
|
||
|
|
const handleSelectAll = (checked: boolean) => {
|
||
|
|
if (checked) {
|
||
|
|
setSelectedArticles(articlesData?.items.map(a => a.id) ?? []);
|
||
|
|
} else {
|
||
|
|
setSelectedArticles([]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSelectArticle = (articleId: string, checked: boolean) => {
|
||
|
|
setSelectedArticles(prev =>
|
||
|
|
checked ? [...prev, articleId] : prev.filter(id => id !== articleId)
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
const resetSelection = () => {
|
||
|
|
setSelectedArticles([]);
|
||
|
|
setBatchAction('');
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
// 数据
|
||
|
|
articles: articlesData?.items ?? [],
|
||
|
|
paginatedArticles: articlesData?.items ?? [], // The api already paginates
|
||
|
|
stats: statsData,
|
||
|
|
// terms: termsData ?? [], // TODO: fetch terms separately if needed
|
||
|
|
|
||
|
|
// 状态
|
||
|
|
filters,
|
||
|
|
pagination,
|
||
|
|
selectedArticles,
|
||
|
|
quickEditId,
|
||
|
|
batchAction,
|
||
|
|
isLoading: isLoadingArticles || isLoadingStats,
|
||
|
|
|
||
|
|
// 更新函数
|
||
|
|
updateFilters,
|
||
|
|
setCurrentPage,
|
||
|
|
setSelectedArticles,
|
||
|
|
setQuickEditId,
|
||
|
|
setBatchAction,
|
||
|
|
|
||
|
|
// 操作函数
|
||
|
|
handleSelectAll,
|
||
|
|
handleSelectArticle,
|
||
|
|
resetSelection,
|
||
|
|
refetchArticles
|
||
|
|
};
|
||
|
|
}
|