feat: add skeleton screen components and optimize image config

This commit is contained in:
张翔
2026-02-27 20:30:36 +08:00
parent aa47a45a95
commit 5924a7d493
3 changed files with 132 additions and 59 deletions
+7 -1
View File
@@ -4,10 +4,16 @@ const nextConfig: NextConfig = {
output: 'export', output: 'export',
distDir: 'dist', distDir: 'dist',
images: { images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
unoptimized: true, unoptimized: true,
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/webp'],
}, },
compress: true, compress: true,
poweredByHeader: false, poweredByHeader: false,
+17 -2
View File
@@ -3,10 +3,11 @@
import Link from 'next/link'; import Link from 'next/link';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useInView } from 'framer-motion'; import { useInView } from 'framer-motion';
import { useRef } from 'react'; import { useRef, useState, useEffect } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { PageHeader } from '@/components/ui/page-header'; import { PageHeader } from '@/components/ui/page-header';
import { ServiceCardSkeleton } from '@/components/ui/loading-skeleton';
import { ArrowRight, Code, Cloud, BarChart3, Shield } from 'lucide-react'; import { ArrowRight, Code, Cloud, BarChart3, Shield } from 'lucide-react';
import { SERVICES } from '@/lib/constants'; import { SERVICES } from '@/lib/constants';
@@ -20,6 +21,12 @@ const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
export default function ServicesPage() { export default function ServicesPage() {
const contentRef = useRef(null); const contentRef = useRef(null);
const isContentInView = useInView(contentRef, { once: true, margin: '-100px' }); const isContentInView = useInView(contentRef, { once: true, margin: '-100px' });
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const timer = setTimeout(() => setIsLoading(false), 1000);
return () => clearTimeout(timer);
}, []);
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-white">
@@ -30,6 +37,13 @@ export default function ServicesPage() {
<div className="container-wide relative z-10 py-16" ref={contentRef}> <div className="container-wide relative z-10 py-16" ref={contentRef}>
<div className="max-w-6xl mx-auto"> <div className="max-w-6xl mx-auto">
{isLoading ? (
<div className="grid md:grid-cols-2 gap-8">
{Array.from({ length: 4 }).map((_, index) => (
<ServiceCardSkeleton key={index} />
))}
</div>
) : (
<div className="grid md:grid-cols-2 gap-8"> <div className="grid md:grid-cols-2 gap-8">
{SERVICES.map((service, index) => { {SERVICES.map((service, index) => {
const Icon = iconMap[service.icon]; const Icon = iconMap[service.icon];
@@ -37,7 +51,7 @@ export default function ServicesPage() {
<motion.div <motion.div
key={service.id} key={service.id}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}} animate={isContentInView ? { opacity:1, y: 0 } : {}}
transition={{ duration: 0.5, delay: index * 0.1 }} transition={{ duration: 0.5, delay: index * 0.1 }}
> >
<Link <Link
@@ -78,6 +92,7 @@ export default function ServicesPage() {
); );
})} })}
</div> </div>
)}
</div> </div>
</div> </div>
+66 -14
View File
@@ -1,27 +1,79 @@
export function LoadingSkeleton({ className = '' }: { className?: string }) { 'use client';
import { cn } from '@/lib/utils';
interface SkeletonProps {
className?: string;
}
export function Skeleton({ className }: SkeletonProps) {
return ( return (
<div className={`animate-pulse bg-gray-200 rounded ${className}`} /> <div
className={cn(
'animate-pulse rounded-md bg-[#F5F5F5]',
className
)}
/>
); );
} }
export function FormSkeleton() { export function CardSkeleton() {
return ( return (
<div className="space-y-4"> <div className="bg-[#FAFAFA] rounded-xl border border-[#E5E5E5] p-6">
<LoadingSkeleton className="h-12 w-full" /> <Skeleton className="h-12 w-12 rounded-xl mb-4" />
<LoadingSkeleton className="h-12 w-full" /> <Skeleton className="h-6 w-3/4 mb-3" />
<LoadingSkeleton className="h-12 w-full" /> <Skeleton className="h-4 w-full mb-2" />
<LoadingSkeleton className="h-32 w-full" /> <Skeleton className="h-4 w-2/3" />
</div> </div>
); );
} }
export function SectionSkeleton() { export function ServiceCardSkeleton() {
return ( return (
<div className="py-24"> <div className="bg-white rounded-xl border border-[#E5E5E5] p-6 h-full">
<div className="container-wide"> <Skeleton className="h-14 w-14 rounded-xl mb-4" />
<LoadingSkeleton className="h-12 w-1/3 mb-8" /> <Skeleton className="h-6 w-3/4 mb-3" />
<LoadingSkeleton className="h-6 w-2/3 mb-4" /> <Skeleton className="h-4 w-full mb-2" />
<LoadingSkeleton className="h-6 w-1/2" /> <Skeleton className="h-4 w-2/3" />
</div>
);
}
export function CaseCardSkeleton() {
return (
<div className="bg-white rounded-xl border border-[#E5E5E5] overflow-hidden">
<Skeleton className="h-48 w-full" />
<div className="p-6">
<Skeleton className="h-5 w-1/2 mb-3" />
<Skeleton className="h-6 w-3/4 mb-3" />
<Skeleton className="h-4 w-full mb-2" />
<Skeleton className="h-4 w-2/3" />
</div>
</div>
);
}
export function ProductCardSkeleton() {
return (
<div className="bg-[#FAFAFA] rounded-xl border border-[#E5E5E5] p-6 h-full flex flex-col">
<Skeleton className="h-6 w-1/3 mb-3" />
<Skeleton className="h-7 w-3/4 mb-4" />
<Skeleton className="h-4 w-full mb-2" />
<Skeleton className="h-4 w-2/3 mb-4" />
<Skeleton className="h-10 w-full mt-auto" />
</div>
);
}
export function NewsCardSkeleton() {
return (
<div className="bg-white rounded-xl border border-[#E5E5E5] overflow-hidden">
<Skeleton className="h-48 w-full" />
<div className="p-6">
<Skeleton className="h-5 w-1/4 mb-3" />
<Skeleton className="h-6 w-3/4 mb-3" />
<Skeleton className="h-4 w-full mb-2" />
<Skeleton className="h-4 w-2/3" />
</div> </div>
</div> </div>
); );