120 lines
3.7 KiB
TypeScript
120 lines
3.7 KiB
TypeScript
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
|
|
import { useNavigate, useLoaderData } from '@remix-run/react';
|
|
import { ContractSearchHero } from '~/components/contract-template/ContractSearchHero';
|
|
import { getContractCategoriesWithCount } from '~/api/contract-template/templates';
|
|
import type { ContractCategoryWithCount } from '~/api/contract-template/templates';
|
|
import styles from '~/styles/pages/contract-template.css?url';
|
|
import { getUserSession } from '~/api/login/auth.server';
|
|
|
|
export const links = () => [
|
|
{ rel: 'stylesheet', href: styles }
|
|
];
|
|
|
|
export const meta: MetaFunction = () => {
|
|
return [
|
|
{ title: 'AI智能合同模板搜索 - 智慧法务' },
|
|
{
|
|
name: 'description',
|
|
content: '使用AI智能搜索快速找到最适合的合同模板,支持自然语言描述搜索。'
|
|
}
|
|
];
|
|
};
|
|
|
|
// 面包屑导航配置
|
|
export const handle = {
|
|
breadcrumb: "智能搜索"
|
|
};
|
|
|
|
/**
|
|
* 转换分类数据为前端显示格式
|
|
* @param category 分类数据
|
|
* @returns 转换后的分类数据
|
|
*/
|
|
function transformCategory(category: ContractCategoryWithCount) {
|
|
return {
|
|
id: category.id,
|
|
name: category.name,
|
|
icon: category.icon || 'ri-file-text-line',
|
|
count: category.template_count
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 加载分类数据
|
|
* @returns 分类数据
|
|
*/
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const url = new URL(request.url);
|
|
const { handleServerAuth } = await import("~/utils/server-auth-handler");
|
|
|
|
return handleServerAuth(async () => {
|
|
// 获取 JWT
|
|
const { frontendJWT } = await getUserSession(request);
|
|
const jwt = frontendJWT || undefined;
|
|
|
|
// 使用聚合查询获取分类及其模板数量
|
|
const categoriesResponse = await getContractCategoriesWithCount(jwt);
|
|
|
|
// 处理分类数据
|
|
if (categoriesResponse.error) {
|
|
console.error('获取分类失败:', categoriesResponse.error);
|
|
return { categories: [] };
|
|
}
|
|
|
|
const categories = categoriesResponse.data || [];
|
|
|
|
// 转换分类数据格式
|
|
const categoriesWithCount = categories.map(transformCategory);
|
|
|
|
return { categories: categoriesWithCount };
|
|
}, url.pathname);
|
|
}
|
|
|
|
export default function ContractTemplateSearchIndex() {
|
|
const navigate = useNavigate();
|
|
const { categories } = useLoaderData<typeof loader>();
|
|
|
|
const handleSearch = (query: string) => {
|
|
if (query.trim()) {
|
|
navigate(`/contract-template/search/results?q=${encodeURIComponent(query)}`);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 处理分类点击事件 - 使用ID而不是名称
|
|
* @param categoryId 分类ID
|
|
*/
|
|
const handleCategoryClick = (categoryId: number) => {
|
|
navigate(`/contract-template/list?category_id=${categoryId}`);
|
|
};
|
|
|
|
return (
|
|
<div className="contract-template-search">
|
|
<ContractSearchHero onSearch={handleSearch} />
|
|
<div className="quick-categories">
|
|
{categories.map((category, index) => (
|
|
<div
|
|
key={category.id || index}
|
|
className="category-card"
|
|
onClick={() => handleCategoryClick(category.id)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
handleCategoryClick(category.id);
|
|
}
|
|
}}
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-label={`选择${category.name}分类,共${category.count}个模板`}
|
|
>
|
|
<div className="category-icon">
|
|
<i className={category.icon}></i>
|
|
</div>
|
|
<div className="category-title">{category.name}</div>
|
|
<div className="category-count">{category.count}个模板</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|