feat: implement frontend-backend configuration linkage

- Create public config API for frontend consumption
- Add configuration fetching to homepage
- Implement module show/hide logic based on config
- Add support for Services items filtering
- Add support for Products featured products and pricing display
- Add support for News display count, categories, and sort order
- Fix table name from 'configs' to 'siteConfig' in API route
- Update type definitions for proper TypeScript support
This commit is contained in:
张翔
2026-03-13 13:11:20 +08:00
parent f93f802427
commit 4fdfc2d8b4
100 changed files with 894 additions and 316 deletions
+33 -3
View File
@@ -2,7 +2,7 @@
import { motion } from 'framer-motion';
import { useInView } from 'framer-motion';
import { useRef } from 'react';
import { useRef, useMemo } from 'react';
import Link from 'next/link';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@@ -10,10 +10,27 @@ import { Badge } from '@/components/ui/badge';
import { ArrowRight, Check, TrendingUp } from 'lucide-react';
import { PRODUCTS } from '@/lib/constants';
export function ProductsSection() {
interface ProductsConfig {
enabled?: boolean;
showPricing?: boolean;
featuredProducts?: string[];
}
interface ProductsSectionProps {
config?: ProductsConfig;
}
export function ProductsSection({ config }: ProductsSectionProps) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
const filteredProducts = useMemo(() => {
if (!config?.featuredProducts || config.featuredProducts.length === 0) {
return PRODUCTS;
}
return PRODUCTS.filter(product => config.featuredProducts?.includes(product.id));
}, [config]);
return (
<section id="products" role="region" aria-labelledby="products-heading" className="py-24 bg-[#F5F7FA] relative overflow-hidden" ref={ref}>
<div className="absolute top-1/2 left-0 w-[400px] h-[400px] bg-[rgba(79,70,229,0.03)] rounded-full blur-3xl" />
@@ -34,7 +51,7 @@ export function ProductsSection() {
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{PRODUCTS.map((product, idx) => (
{filteredProducts.map((product, idx) => (
<motion.div
key={product.id}
initial={{ opacity: 0, y: 20 }}
@@ -84,6 +101,19 @@ export function ProductsSection() {
</ul>
</div>
{config?.showPricing && product.pricing && (
<div className="mb-4 p-3 bg-[#F5F7FA] rounded-lg">
<p className="text-sm font-medium text-[#1C1C1C] mb-2"></p>
<div className="space-y-1">
{Object.entries(product.pricing).map(([key, value]) => (
<p key={key} className="text-xs text-[#5C5C5C]">
{value}
</p>
))}
</div>
</div>
)}
<Button variant="outline" className="w-full mt-auto group-hover:bg-[#A01830] group-hover:text-white group-hover:border-[#A01830] transition-colors">
<ArrowRight className="ml-2 w-4 h-4" />