refactor: 完成静态网站转换,移除所有 CMS 和动态功能

- 删除数据库相关代码 (src/db/)
- 删除 API 路由 (src/app/api/)
- 删除认证相关代码 (src/lib/auth/, src/providers/)
- 删除监控和安全中间件 (src/lib/security/, src/lib/monitoring/)
- 删除 hooks (use-news, use-products, use-services)
- 更新组件为静态数据源
- 添加 nginx 静态配置和部署脚本
- 添加 static-link 组件
This commit is contained in:
张翔
2026-04-21 07:53:56 +08:00
parent cd1d6aa28a
commit 6403489954
197 changed files with 654 additions and 24762 deletions
+15 -81
View File
@@ -1,74 +1,35 @@
'use client';
import { useState, useMemo, useRef, useEffect, ChangeEvent } from 'react';
import { useState, useMemo, useRef, ChangeEvent } from 'react';
import { useInView } from 'framer-motion';
import { contentService } from '@/lib/api/services';
import { CASES } from '@/lib/constants';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { PageHeader } from '@/components/ui/page-header';
import { Search, ArrowLeft, Building2, Calendar, TrendingUp, ChevronLeft, ChevronRight, Filter } from 'lucide-react';
import Link from 'next/link';
import { StaticLink } from '@/components/ui/static-link';
import { motion } from 'framer-motion';
const industries = ['全部', '金融', '制造', '零售', '医疗', '教育'];
const industries = ['全部', ...Array.from(new Set(CASES.map((c) => c.industry)))];
const ITEMS_PER_PAGE = 6;
interface CaseItem {
id: string;
title: string;
description: string;
industry: string;
client: string;
slug: string;
}
export default function CasesPage() {
const [selectedIndustry, setSelectedIndustry] = useState('全部');
const [searchQuery, setSearchQuery] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [cases, setCases] = useState<CaseItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const contentRef = useRef(null);
const isContentInView = useInView(contentRef, { once: true, margin: '-100px' });
const fetchCases = async () => {
try {
setLoading(true);
setError(null);
const data = await contentService.getNews(['案例'], 100, 'desc');
const caseItems: CaseItem[] = data.map(item => ({
id: item.id,
title: item.title,
description: item.excerpt,
industry: item.category,
client: '客户企业',
slug: item.slug,
}));
setCases(caseItems);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch cases'));
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchCases();
}, []);
const filteredCases = useMemo(() => {
if (!cases || cases.length === 0) {return [];}
return cases.filter((caseItem) => {
return CASES.filter((caseItem) => {
const matchesIndustry = selectedIndustry === '全部' || caseItem.industry === selectedIndustry;
const matchesSearch =
const matchesSearch =
caseItem.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
caseItem.description.toLowerCase().includes(searchQuery.toLowerCase());
return matchesIndustry && matchesSearch;
});
}, [cases, selectedIndustry, searchQuery]);
}, [selectedIndustry, searchQuery]);
const totalPages = Math.ceil(filteredCases.length / ITEMS_PER_PAGE);
const paginatedCases = useMemo(() => {
@@ -92,28 +53,6 @@ export default function CasesPage() {
setCurrentPage(1);
};
if (loading) {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#C41E3A] mx-auto mb-4" />
<p className="text-[#5C5C5C]">...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="text-center">
<p className="text-red-600 mb-4"></p>
<Button onClick={() => window.location.reload()}></Button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
<PageHeader
@@ -123,12 +62,7 @@ export default function CasesPage() {
<div className="container-wide relative z-10 py-16" ref={contentRef}>
<div className="max-w-6xl mx-auto">
<Link href="/" className="inline-flex items-center text-[#5C5C5C] hover:text-[#C41E3A] transition-colors mb-8">
<ArrowLeft className="w-4 h-4 mr-2" />
</Link>
<motion.div
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }}
@@ -183,8 +117,8 @@ export default function CasesPage() {
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Link
href={`/cases/${caseItem.slug}`}
<StaticLink
href={`/cases/${caseItem.id}`}
className="group bg-white rounded-2xl border border-[#E5E5E5] overflow-hidden hover:shadow-xl transition-all duration-300 block"
>
<div className="relative h-48 bg-gradient-to-br from-[#F5F5F5] to-[#E5E5E5] overflow-hidden">
@@ -228,7 +162,7 @@ export default function CasesPage() {
<ArrowLeft className="w-4 h-4 ml-2 rotate-180" />
</div>
</div>
</Link>
</StaticLink>
</motion.div>
))}
</div>
@@ -293,15 +227,15 @@ export default function CasesPage() {
</p>
<div className="flex justify-center gap-4">
<Link href="/contact">
<StaticLink href="/contact">
<Button
size="lg"
variant="outline"
>
</Button>
</Link>
<Link href="/contact">
</StaticLink>
<StaticLink href="/contact">
<Button
size="lg"
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
@@ -309,7 +243,7 @@ export default function CasesPage() {
<ArrowLeft className="ml-2 w-4 h-4 rotate-180" />
</Button>
</Link>
</StaticLink>
</div>
</div>
</motion.div>