diff --git a/app/api/contract-template/templates.ts b/app/api/contract-template/templates.ts
index a8ab031..de3ab55 100644
--- a/app/api/contract-template/templates.ts
+++ b/app/api/contract-template/templates.ts
@@ -177,7 +177,7 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
// 构建查询参数
const params: PostgrestParams = {
- select: 'id,template_code,title,category_id,description,file_format,is_featured,created_at,updated_at,contract_categories(id,name,icon)',
+ select: 'id,template_code,title,category_id,description,file_path,file_format,is_featured,created_at,updated_at,pdf_file_path,contract_categories(id,name,icon,description)',
limit: pageSize,
offset: (page - 1) * pageSize,
order: `${sortBy}.${sortOrder}`
@@ -303,7 +303,7 @@ export async function getContractTemplate(id: string | number) {
export async function getFeaturedTemplates(limit: number = 6) {
try {
const params: PostgrestParams = {
- select: 'id,template_code,title,category_id,description,file_format,is_featured,created_at,updated_at,contract_categories(id,name,icon)',
+ select: 'id,template_code,title,category_id,description,file_path,file_format,is_featured,created_at,updated_at,pdf_file_path,contract_categories(id,name,icon,description)',
filter: { 'is_featured': 'eq.true' },
order: 'updated_at.desc',
limit
diff --git a/app/components/contract-template/SearchResultHeader.tsx b/app/components/contract-template/SearchResultHeader.tsx
index c1066d5..f117d6b 100644
--- a/app/components/contract-template/SearchResultHeader.tsx
+++ b/app/components/contract-template/SearchResultHeader.tsx
@@ -40,10 +40,10 @@ export function SearchResultHeader({
value={sortBy}
onChange={(e) => onSortChange(e.target.value)}
>
-
+
-
-
+ {/*
+ */}
diff --git a/app/components/contract-template/TemplateCard.tsx b/app/components/contract-template/TemplateCard.tsx
index 36d836a..745de50 100644
--- a/app/components/contract-template/TemplateCard.tsx
+++ b/app/components/contract-template/TemplateCard.tsx
@@ -1,15 +1,17 @@
-import { useState } from 'react';
+// import { useState } from 'react';
import { useNavigate } from '@remix-run/react';
interface Template {
id: string;
title: string;
- type: string;
+ // type: string;
description: string;
updateTime: string;
- useCount: number;
- rating: number;
+ // useCount: number;
+ // rating: number;
category: string;
+ file_path?: string;
+ file_format?: string;
}
interface TemplateCardProps {
@@ -18,21 +20,71 @@ interface TemplateCardProps {
}
export function TemplateCard({ template, onClick }: TemplateCardProps) {
- const [isFavorited, setIsFavorited] = useState(false);
+ // 注释掉收藏功能,后续版本再开发
+ // const [isFavorited, setIsFavorited] = useState(false);
const navigate = useNavigate();
- const handleFavoriteClick = (e: React.MouseEvent) => {
+ /* const handleFavoriteClick = (e: React.MouseEvent) => {
e.stopPropagation();
setIsFavorited(!isFavorited);
+ }; */
+
+ // MinIO下载URL构建函数
+ const buildDownloadUrl = (filePath: string): string => {
+ // 使用实际的MinIO配置
+ const minioHost = 'http://nas.7bm.co:9000';
+ const bucketName = 'docauditai';
+
+ // 确保文件路径不以/开头
+ const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
+
+ return `${minioHost}/${bucketName}/${cleanPath}`;
+ };
+
+ // 下载文件函数
+ const downloadFile = async (filePath: string, fileName: string) => {
+ try {
+ const downloadUrl = buildDownloadUrl(filePath);
+
+ // 清理文件名,移除可能导致问题的字符
+ const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_');
+
+ // 创建临时下载链接
+ const link = document.createElement('a');
+ link.href = downloadUrl;
+ link.download = cleanFileName;
+ link.target = '_blank';
+
+ // 触发下载
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ console.log('开始下载文件:', cleanFileName);
+ } catch (error) {
+ console.error('下载文件失败:', error);
+ alert('下载失败,请稍后重试');
+ }
};
const handleActionClick = (e: React.MouseEvent, action: string) => {
e.stopPropagation();
switch (action) {
- case '立即使用':
- console.log('下载并使用模板:', template.id);
- // 这里应该触发下载逻辑
+ case '立即下载':
+ // 添加调试信息
+ console.log('模板数据:', template);
+ console.log('文件路径:', template.file_path);
+ console.log('文件格式:', template.file_format);
+
+ if (template.file_path) {
+ // 构建文件名,使用模板标题和文件格式
+ const fileExtension = template.file_format || 'docx';
+ const fileName = `${template.title}.${fileExtension}`;
+ downloadFile(template.file_path, fileName);
+ } else {
+ alert('文件路径不存在,无法下载');
+ }
break;
case '预览':
// 导航到模板详情页面
@@ -43,7 +95,7 @@ export function TemplateCard({ template, onClick }: TemplateCardProps) {
}
};
- const renderStars = (rating: number) => {
+ /* const renderStars = (rating: number) => {
const stars = [];
const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 !== 0;
@@ -58,7 +110,7 @@ export function TemplateCard({ template, onClick }: TemplateCardProps) {
}
}
return stars;
- };
+ }; */
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -76,28 +128,43 @@ export function TemplateCard({ template, onClick }: TemplateCardProps) {
tabIndex={0}
aria-label={`查看${template.title}详情`}
>
-
+ {/* 注释掉头部的type和rating显示 */}
+ {/*
{template.type}
{renderStars(template.rating)}
{template.rating}
-
+
*/}
{template.title}
- {template.description}
+
+ {template.description}
+
更新时间:{template.updateTime}
- 使用次数:{template.useCount.toLocaleString()}
+ {/* 注释掉使用次数显示 */}
+ {/* 使用次数:{template.useCount.toLocaleString()} */}
-
+ */}
);
diff --git a/app/components/contract-template/TemplateGrid.tsx b/app/components/contract-template/TemplateGrid.tsx
index 47a1046..2d916e2 100644
--- a/app/components/contract-template/TemplateGrid.tsx
+++ b/app/components/contract-template/TemplateGrid.tsx
@@ -3,12 +3,14 @@ import { TemplateCard } from './TemplateCard';
interface Template {
id: string;
title: string;
- type: string;
+ // type: string;
description: string;
updateTime: string;
- useCount: number;
- rating: number;
+ // useCount: number;
+ // rating: number;
category: string;
+ file_path?: string;
+ file_format?: string;
}
interface TemplateGridProps {
diff --git a/app/routes/contract-template.list._index.tsx b/app/routes/contract-template.list._index.tsx
index a7df43d..743487c 100644
--- a/app/routes/contract-template.list._index.tsx
+++ b/app/routes/contract-template.list._index.tsx
@@ -25,7 +25,7 @@ export const meta: MetaFunction = () => {
// 将数据库模板转换为前端显示格式
function transformTemplate(template: ContractTemplate) {
// 模拟使用次数和评分(实际项目中可以从其他表获取)
- const mockUsageCount = Math.floor(Math.random() * 2000) + 100;
+ /* const mockUsageCount = Math.floor(Math.random() * 2000) + 100;
const mockRating = (Math.random() * 1.5 + 3.5).toFixed(1);
// 根据模板属性确定类型
@@ -36,17 +36,24 @@ function transformTemplate(template: ContractTemplate) {
templateType = '简化版';
} else if (template.description && (template.description.includes('专业') || template.description.includes('大客户'))) {
templateType = '专业版';
- }
+ } */
+
+ // 添加调试信息
+ console.log('原始模板数据:', template);
+ console.log('file_path:', template.file_path);
+ console.log('file_format:', template.file_format);
return {
id: template.id.toString(),
title: template.title,
- type: templateType,
+ // type: templateType,
description: template.description || '',
updateTime: new Date(template.updated_at).toLocaleDateString('zh-CN'),
- useCount: mockUsageCount,
- rating: parseFloat(mockRating),
- category: template.category?.name || '其他'
+ // useCount: mockUsageCount,
+ // rating: parseFloat(mockRating),
+ category: template.category?.name || '其他',
+ file_path: template.file_path,
+ file_format: template.file_format
};
}
@@ -55,16 +62,45 @@ export async function loader({ request }: LoaderFunctionArgs) {
const category = url.searchParams.get('category') || '';
const category_id = url.searchParams.get('category_id') || '';
const type = url.searchParams.get('type') || '';
+ const sortBy = url.searchParams.get('sortBy') || 'relevance';
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = 6;
try {
+ // 根据sortBy值设置数据库排序参数
+ let dbSortBy = 'id';
+ let dbSortOrder: 'asc' | 'desc' = 'asc';
+
+ switch (sortBy) {
+ case 'relevance':
+ dbSortBy = 'id';
+ dbSortOrder = 'asc';
+ break;
+ case 'newest':
+ dbSortBy = 'updated_at';
+ dbSortOrder = 'desc';
+ break;
+ /* case 'popular':
+ // 暂时按创建时间排序,后续可以加入使用频率字段
+ dbSortBy = 'created_at';
+ dbSortOrder = 'desc';
+ break;
+ case 'rating':
+ // 暂时按特色推荐排序,后续可以加入评分字段
+ dbSortBy = 'is_featured';
+ dbSortOrder = 'desc';
+ break; */
+ default:
+ dbSortBy = 'id';
+ dbSortOrder = 'asc';
+ }
+
// 构建搜索参数
const searchParams: TemplateSearchParams = {
page,
pageSize,
- sortBy: 'updated_at',
- sortOrder: 'desc'
+ sortBy: dbSortBy,
+ sortOrder: dbSortOrder
};
// 优先使用category_id,其次使用category名称
@@ -91,6 +127,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
category,
category_id,
type,
+ sortBy,
categories: []
};
}
@@ -99,12 +136,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
const categories: ContractCategoryWithCount[] = categoriesResponse.error ? [] : categoriesResponse.data || [];
// 转换模板数据格式
- let transformedTemplates = templatesResponse.data?.templates.map(transformTemplate) || [];
+ const transformedTemplates = templatesResponse.data?.templates.map(transformTemplate) || [];
- // 如果有类型筛选,在前端进行筛选(因为数据库中没有type字段)
- if (type) {
+ // 注释掉类型筛选,因为数据库中没有type字段且已隐藏该功能
+ /* if (type) {
transformedTemplates = transformedTemplates.filter(t => t.type === type);
- }
+ } */
// 获取当前分类信息(用于显示)
let currentCategory = '全部';
@@ -123,6 +160,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
category: currentCategory,
category_id,
type,
+ sortBy,
categories
};
} catch (error) {
@@ -135,18 +173,18 @@ export async function loader({ request }: LoaderFunctionArgs) {
category,
category_id,
type,
+ sortBy,
categories: []
};
}
}
export default function ContractTemplateList() {
- const { templates, total, page, pageSize, category, category_id, categories } = useLoaderData();
+ const { templates, total, page, pageSize, category, category_id, categories, sortBy } = useLoaderData();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [activeFilter, setActiveFilter] = useState(category || '全部');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
- const [sortBy, setSortBy] = useState('newest');
// 监听category变化,同步更新activeFilter状态
useEffect(() => {
@@ -186,6 +224,13 @@ export default function ContractTemplateList() {
navigate(`/contract-template/list?${params.toString()}`);
};
+ const handleSortChange = (newSort: string) => {
+ const params = new URLSearchParams(searchParams);
+ params.set('sortBy', newSort);
+ params.delete('page'); // 重置页码
+ navigate(`/contract-template/list?${params.toString()}`);
+ };
+
// 动态生成筛选选项
// 计算所有分类的总模板数量
const totalAllTemplates = categories.reduce((sum, cat) => sum + (cat.template_count || 0), 0);
@@ -235,11 +280,12 @@ export default function ContractTemplateList() {
diff --git a/app/routes/contract-template.search.results.tsx b/app/routes/contract-template.search.results.tsx
index 76fab16..4b37c2e 100644
--- a/app/routes/contract-template.search.results.tsx
+++ b/app/routes/contract-template.search.results.tsx
@@ -38,29 +38,33 @@ interface CategoryWithSearchCount extends ContractCategory {
interface DisplayTemplate {
id: string;
title: string;
- type: string;
+ // type: string;
description: string;
updateTime: string;
- useCount: number;
- rating: number;
+ // useCount: number;
+ // rating: number;
category: string;
+ file_path?: string;
+ file_format?: string;
}
// 将数据库模板转换为前端显示格式
function transformTemplate(template: ContractTemplate) {
// 模拟使用次数和评分(实际项目中可以从其他表获取)
- const mockUsageCount = Math.floor(Math.random() * 2000) + 100;
- const mockRating = (Math.random() * 1.5 + 3.5).toFixed(1);
+ /* const mockUsageCount = Math.floor(Math.random() * 2000) + 100;
+ const mockRating = (Math.random() * 1.5 + 3.5).toFixed(1); */
return {
id: template.id.toString(),
title: template.title,
- type: template.is_featured ? '推荐版' : '标准版',
+ // type: template.is_featured ? '推荐版' : '标准版',
description: template.description || '',
updateTime: new Date(template.updated_at).toLocaleDateString('zh-CN'),
- useCount: mockUsageCount,
- rating: parseFloat(mockRating),
- category: template.category?.name || '其他'
+ // useCount: mockUsageCount,
+ // rating: parseFloat(mockRating),
+ category: template.category?.name || '其他',
+ file_path: template.file_path,
+ file_format: template.file_format
};
}
@@ -68,11 +72,38 @@ export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const query = url.searchParams.get('q') || '';
const category = url.searchParams.get('category') || '';
+ const sortBy = url.searchParams.get('sortBy') || 'relevance';
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = 6;
// 记录搜索开始时间
const startTime = Date.now();
+
+ // 根据sortBy值设置数据库排序参数
+ let dbSortBy = 'id';
+ let dbSortOrder: 'asc' | 'desc' = 'asc';
+
+ switch (sortBy) {
+ case 'relevance':
+ dbSortBy = 'id';
+ dbSortOrder = 'asc';
+ break;
+ case 'newest':
+ dbSortBy = 'updated_at';
+ dbSortOrder = 'desc';
+ break;
+ /* case 'popular':
+ dbSortBy = 'created_at';
+ dbSortOrder = 'desc';
+ break;
+ case 'rating':
+ dbSortBy = 'is_featured';
+ dbSortOrder = 'desc';
+ break; */
+ default:
+ dbSortBy = 'id';
+ dbSortOrder = 'asc';
+ }
try {
// 并行获取搜索结果和分类数据
@@ -81,8 +112,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
category,
page,
pageSize,
- sortBy: 'updated_at',
- sortOrder: 'desc'
+ sortBy: dbSortBy,
+ sortOrder: dbSortOrder
}),
getContractCategories()
]);
@@ -97,6 +128,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
total: 0,
page,
pageSize,
+ sortBy,
searchTime: '搜索失败',
categories: []
};
@@ -155,6 +187,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
total: searchResponse.data?.total || 0,
page,
pageSize,
+ sortBy,
searchTime: searchTimeText,
categories: categoriesWithSearchCount
};
@@ -167,6 +200,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
total: 0,
page,
pageSize,
+ sortBy,
searchTime: '搜索失败',
categories: []
};
@@ -174,12 +208,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
}
export default function ContractTemplateSearchResults() {
- const { results, query, total, page, pageSize, searchTime, categories }: {
+ const { results, query, total, page, pageSize, sortBy, searchTime, categories }: {
results: DisplayTemplate[];
query: string;
total: number;
page: number;
pageSize: number;
+ sortBy: string;
searchTime: string;
categories: CategoryWithSearchCount[];
} = useLoaderData();
@@ -188,7 +223,6 @@ export default function ContractTemplateSearchResults() {
const navigate = useNavigate();
const [activeFilter, setActiveFilter] = useState('全部');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
- const [sortBy, setSortBy] = useState('relevance');
const handleSearch = (newQuery: string) => {
if (newQuery.trim()) {
@@ -220,6 +254,13 @@ export default function ContractTemplateSearchResults() {
navigate(`/contract-template/search/results?${params.toString()}`);
};
+ const handleSortChange = (newSort: string) => {
+ const params = new URLSearchParams(searchParams);
+ params.set('sortBy', newSort);
+ params.delete('page'); // 重置页码
+ navigate(`/contract-template/search/results?${params.toString()}`);
+ };
+
// 动态生成筛选选项
const filters = [
{ label: '全部', count: total },
@@ -246,7 +287,7 @@ export default function ContractTemplateSearchResults() {
viewMode={viewMode}
onViewModeChange={setViewMode}
sortBy={sortBy}
- onSortChange={setSortBy}
+ onSortChange={handleSortChange}
/>
{/* 筛选标签 */}