feat: 添加预览效果页面并优化交互效果

refactor: 优化代码健壮性和类型安全

style: 更新字体样式和全局CSS

fix: 修复IntersectionObserver潜在空引用问题

chore: 更新依赖和ESLint配置

build: 更新构建ID和路由配置
This commit is contained in:
张翔
2026-02-24 10:24:05 +08:00
parent 64165c4499
commit fecbfd1990
239 changed files with 3403 additions and 5181 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ export default function AboutPage() {
<div className="font-semibold text-black text-sm md:text-base">{milestone.date}</div>
</div>
<div className="flex-1 pb-6 border-l-2 border-gray-200 pl-6 relative">
<div className="absolute -left-[9px] top-1 w-4 h-4 bg-black rounded-full"></div>
<div className="absolute -left-[9px] top-1 w-4 h-4 bg-black rounded-full" />
<h3 className="font-semibold text-black mb-1">{milestone.title}</h3>
<p className="text-gray-600 text-sm leading-relaxed">{milestone.description}</p>
</div>
+3 -4
View File
@@ -1,12 +1,11 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { ArrowLeft, Building2, CheckCircle2, TrendingUp, Users, Target } from 'lucide-react';
import { CASES, COMPANY_INFO } from '@/lib/constants';
import { CASES } from '@/lib/constants';
import type { StaticImageData } from 'next/image';
interface CaseResult {
@@ -36,7 +35,7 @@ export function CaseDetailClient({ caseItem }: CaseDetailClientProps) {
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
if (entry?.isIntersecting) {
setIsVisible(true);
}
},
@@ -116,7 +115,7 @@ export function CaseDetailClient({ caseItem }: CaseDetailClientProps) {
<section>
<h2 className="text-2xl font-semibold text-[#171717] mb-4"></h2>
<div className="space-y-4">
{caseItem.tags.map((tag, index) => (
{caseItem.tags.map((tag) => (
<div key={tag} className="flex items-start gap-3">
<div className="w-6 h-6 bg-[#C41E3A] rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle2 className="w-4 h-4 text-white" />
+1 -2
View File
@@ -13,10 +13,9 @@ export default function ContactPage() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
async function handleSubmit(formData: FormData) {
async function handleSubmit(_formData: FormData) {
setIsSubmitting(true);
// Simulate form submission
await new Promise(resolve => setTimeout(resolve, 1500));
setIsSubmitting(false);
+21 -1
View File
@@ -1,9 +1,20 @@
@import "tailwindcss";
@font-face {
font-family: 'Aoyagi Reisho';
src: url('/fonts/AoyagiReisho.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
font-stretch: normal;
unicode-range: U+4E00-9FFF, U+3400-4DBF, U+20000-2A6DF, U+2A700-2B73F, U+2B740-2B81F, U+2B820-2CEAF, U+F900-FAFF, U+2F800-2FA1F;
}
@theme inline {
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--font-chinese: var(--font-noto-sans-sc);
--font-calligraphy: 'Aoyagi Reisho', var(--font-long-cang), 'Long Cang', var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif;
}
:root {
@@ -315,7 +326,16 @@
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* 青柳隶书体 - 与 Logo 保持一致 */
.font-calligraphy {
font-family: 'Aoyagi Reisho', var(--font-long-cang), 'Long Cang', var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
font-weight: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* 发光效果 */
.bg-glow-red {
background: radial-gradient(circle at center, var(--color-accent-red-glow) 0%, transparent 70%);
+26 -2
View File
@@ -1,5 +1,5 @@
import type { Metadata, Viewport } from "next";
import { Geist, Geist_Mono, Noto_Sans_SC } from "next/font/google";
import { Geist, Geist_Mono, Noto_Sans_SC, Ma_Shan_Zheng, Long_Cang } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/contexts/theme-context";
import { WebVitals } from "@/components/analytics/web-vitals";
@@ -27,6 +27,22 @@ const notoSansSC = Noto_Sans_SC({
preload: true,
});
const maShanZheng = Ma_Shan_Zheng({
weight: "400",
variable: "--font-ma-shan-zheng",
subsets: ["latin"],
display: "swap",
preload: true,
});
const longCang = Long_Cang({
weight: "400",
variable: "--font-long-cang",
subsets: ["latin"],
display: "swap",
preload: true,
});
export const metadata: Metadata = {
title: {
default: "四川睿新致远科技有限公司 - 企业数字化转型服务商",
@@ -98,6 +114,14 @@ export default function RootLayout({
<head>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/favicon.svg" />
{/* 预加载龙藏体,确保与 Logo 一致 */}
<link
rel="preload"
href="https://fonts.gstatic.com/s/longcang/v21/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.0.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<OrganizationSchema />
<WebsiteSchema />
<script
@@ -114,7 +138,7 @@ export default function RootLayout({
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} ${notoSansSC.variable} font-sans antialiased`}
className={`${geistSans.variable} ${geistMono.variable} ${notoSansSC.variable} ${maShanZheng.variable} ${longCang.variable} font-sans antialiased`}
style={{ fontFamily: "'Noto Sans SC', 'Geist', -apple-system, BlinkMacSystemFont, sans-serif" }}
>
<WebVitals />
+390
View File
@@ -0,0 +1,390 @@
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import { MeshGradient } from '@/components/effects/mesh-gradient';
import { TechGridFlow } from '@/components/effects/tech-grid-flow';
import { DataParticleFlow } from '@/components/effects/data-particle-flow';
import { GeometricAbstract } from '@/components/effects/geometric-abstract';
import { InkTechFusion } from '@/components/effects/ink-tech-fusion';
type EffectType = 'mesh' | 'tech-grid' | 'data-particle' | 'geometric' | 'ink-tech' | 'combined';
type ParticleShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'star' | 'mixed';
type ParticleEffect = 'default' | 'pulse' | 'glow' | 'trail';
interface EffectConfig {
id: EffectType;
name: string;
description: string;
features: string[];
recommended: boolean;
}
const effects: EffectConfig[] = [
{
id: 'mesh',
name: 'MeshGradient',
description: '多层渐变叠加,优雅专业',
features: ['GPU 加速', '4 种主题', '完全可访问'],
recommended: false,
},
{
id: 'tech-grid',
name: 'TechGridFlow',
description: '科技网格流,数字化连接',
features: ['网格线条', '发光效果', '3 种密度'],
recommended: true,
},
{
id: 'data-particle',
name: 'DataParticleFlow',
description: '数据粒子流,信息流动',
features: ['粒子动画', '点阵背景', '可自定义数量'],
recommended: true,
},
{
id: 'geometric',
name: 'GeometricAbstract',
description: '几何抽象,现代美学',
features: ['多种形状', '旋转动画', '3 种复杂度'],
recommended: false,
},
{
id: 'ink-tech',
name: 'InkTechFusion',
description: '水墨科技融合,传承创新',
features: ['水墨效果', '科技线条', '双色渐变'],
recommended: true,
},
{
id: 'combined',
name: '组合方案',
description: 'TechGridFlow + InkTechFusion',
features: ['科技感', '文化底蕴', '完美契合'],
recommended: true,
},
];
const particleShapes: { id: ParticleShape; name: string; icon: string }[] = [
{ id: 'circle', name: '圆形', icon: '●' },
{ id: 'square', name: '方形', icon: '■' },
{ id: 'triangle', name: '三角形', icon: '▲' },
{ id: 'diamond', name: '菱形', icon: '◆' },
{ id: 'star', name: '星形', icon: '★' },
{ id: 'mixed', name: '混合', icon: '✦' },
];
const particleEffects: { id: ParticleEffect; name: string; description: string }[] = [
{ id: 'default', name: '默认', description: '标准动画效果' },
{ id: 'pulse', name: '脉冲', description: '呼吸式缩放' },
{ id: 'glow', name: '发光', description: '增强发光效果' },
{ id: 'trail', name: '轨迹', description: '长距离移动轨迹' },
];
export default function EffectsPreviewPage() {
const [selectedEffect, setSelectedEffect] = useState<EffectType>('data-particle');
const [particleShape, setParticleShape] = useState<ParticleShape>('circle');
const [particleEffect, setParticleEffect] = useState<ParticleEffect>('default');
const [particleIntensity, setParticleIntensity] = useState<'subtle' | 'normal' | 'prominent'>('normal');
const renderEffect = () => {
switch (selectedEffect) {
case 'mesh':
return <MeshGradient variant="elegant" />;
case 'tech-grid':
return <TechGridFlow variant="default" color="#C41E3A" />;
case 'data-particle':
return (
<DataParticleFlow
particleCount={60}
color="#C41E3A"
intensity={particleIntensity}
shape={particleShape}
effect={particleEffect}
/>
);
case 'geometric':
return <GeometricAbstract variant="minimal" color="#C41E3A" />;
case 'ink-tech':
return <InkTechFusion variant="subtle" primaryColor="#C41E3A" secondaryColor="#1C1C1C" />;
case 'combined':
return (
<>
<InkTechFusion variant="subtle" primaryColor="#C41E3A" secondaryColor="#1C1C1C" />
<TechGridFlow variant="sparse" color="#C41E3A" />
</>
);
default:
return null;
}
};
return (
<div className="min-h-screen bg-[#FAFAFA]">
<div className="fixed top-0 left-0 right-0 z-50 bg-white/95 backdrop-blur-sm border-b border-[#E5E5E5] shadow-sm">
<div className="container-wide py-4">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-2xl font-bold text-[#1C1C1C]">Hero Section </h1>
<p className="text-sm text-[#718096]"></p>
</div>
<a
href="/"
className="px-4 py-2 rounded-lg bg-[#F5F5F5] text-[#1C1C1C] hover:bg-[#E5E5E5] transition-colors text-sm font-medium"
>
</a>
</div>
<div className="flex gap-2 overflow-x-auto pb-2">
{effects.map((effect) => (
<motion.button
key={effect.id}
onClick={() => setSelectedEffect(effect.id)}
className={`relative px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap transition-all ${
selectedEffect === effect.id
? 'bg-[#C41E3A] text-white'
: 'bg-[#F5F5F5] text-[#1C1C1C] hover:bg-[#E5E5E5]'
}`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{effect.name}
{effect.recommended && (
<span className="absolute -top-1 -right-1 px-1.5 py-0.5 text-[10px] bg-[#16A34A] text-white rounded-full">
</span>
)}
</motion.button>
))}
</div>
{selectedEffect === 'data-particle' && (
<div className="mt-4 space-y-3 border-t border-[#E5E5E5] pt-3">
<div>
<label className="text-xs font-medium text-[#718096] mb-2 block"></label>
<div className="flex gap-2">
{particleShapes.map((shape) => (
<button
key={shape.id}
onClick={() => setParticleShape(shape.id)}
className={`px-3 py-1.5 rounded text-sm font-medium transition-all ${
particleShape === shape.id
? 'bg-[#C41E3A] text-white'
: 'bg-[#F5F5F5] text-[#1C1C1C] hover:bg-[#E5E5E5]'
}`}
>
<span className="mr-1">{shape.icon}</span>
{shape.name}
</button>
))}
</div>
</div>
<div>
<label className="text-xs font-medium text-[#718096] mb-2 block"></label>
<div className="flex gap-2">
{particleEffects.map((effect) => (
<button
key={effect.id}
onClick={() => setParticleEffect(effect.id)}
className={`px-3 py-1.5 rounded text-sm font-medium transition-all ${
particleEffect === effect.id
? 'bg-[#C41E3A] text-white'
: 'bg-[#F5F5F5] text-[#1C1C1C] hover:bg-[#E5E5E5]'
}`}
>
{effect.name}
</button>
))}
</div>
</div>
<div>
<label className="text-xs font-medium text-[#718096] mb-2 block"></label>
<div className="flex gap-2">
{(['subtle', 'normal', 'prominent'] as const).map((intensity) => (
<button
key={intensity}
onClick={() => setParticleIntensity(intensity)}
className={`px-3 py-1.5 rounded text-sm font-medium transition-all ${
particleIntensity === intensity
? 'bg-[#C41E3A] text-white'
: 'bg-[#F5F5F5] text-[#1C1C1C] hover:bg-[#E5E5E5]'
}`}
>
{intensity === 'subtle' ? '柔和' : intensity === 'normal' ? '正常' : '突出'}
</button>
))}
</div>
</div>
</div>
)}
</div>
</div>
<div className="pt-32">
<div className="relative h-screen">
{renderEffect()}
<div className="relative z-10 h-full flex items-center justify-center">
<div className="text-center px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="mb-6"
>
<span className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border border-[#1C1C1C]/20 bg-white/80 backdrop-blur-sm text-[#1C1C1C] text-sm font-medium">
·
</span>
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-5xl sm:text-6xl lg:text-7xl font-bold tracking-tight mb-6 text-[#1A1A2E]"
>
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-xl sm:text-2xl text-[#C41E3A] mb-4 font-medium"
>
</motion.p>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="text-lg text-[#718096] mb-10 max-w-2xl mx-auto"
>
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="flex gap-4 justify-center"
>
<button className="px-8 py-3 rounded-lg bg-[#C41E3A] text-white font-medium hover:bg-[#A01830] transition-colors">
</button>
<button className="px-8 py-3 rounded-lg border border-[#1C1C1C] text-[#1C1C1C] font-medium hover:bg-[#F5F5F5] transition-colors">
</button>
</motion.div>
</div>
</div>
</div>
<div className="container-wide py-16">
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-8"></h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{effects.map((effect) => (
<motion.div
key={effect.id}
className={`p-6 rounded-xl border-2 transition-all cursor-pointer ${
selectedEffect === effect.id
? 'border-[#C41E3A] bg-[#FEF2F4]'
: 'border-[#E5E5E5] bg-white hover:border-[#D4D4D4]'
}`}
onClick={() => setSelectedEffect(effect.id)}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<div className="flex items-start justify-between mb-3">
<h3 className="text-lg font-semibold text-[#1C1C1C]">{effect.name}</h3>
{effect.recommended && (
<span className="px-2 py-1 text-xs bg-[#16A34A] text-white rounded-full">
</span>
)}
</div>
<p className="text-sm text-[#718096] mb-4">{effect.description}</p>
<div className="flex flex-wrap gap-2">
{effect.features.map((feature, index) => (
<span
key={index}
className="px-2 py-1 text-xs bg-[#F5F5F5] text-[#5C5C5C] rounded"
>
{feature}
</span>
))}
</div>
</motion.div>
))}
</div>
{selectedEffect === 'data-particle' && (
<div className="mt-8 p-6 rounded-xl bg-gradient-to-r from-[#FEF2F4] to-[#FFF0F3] border border-[#FFE8EC]">
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-4">🎨 DataParticleFlow </h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<h4 className="font-medium text-[#1C1C1C] mb-2"></h4>
<ul className="space-y-1 text-sm text-[#5C5C5C]">
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
</ul>
</div>
<div>
<h4 className="font-medium text-[#1C1C1C] mb-2"></h4>
<ul className="space-y-1 text-sm text-[#5C5C5C]">
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
</ul>
</div>
<div>
<h4 className="font-medium text-[#1C1C1C] mb-2"></h4>
<ul className="space-y-1 text-sm text-[#5C5C5C]">
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
<li> <strong></strong> - </li>
</ul>
</div>
</div>
</div>
)}
<div className="mt-8 p-6 rounded-xl bg-[#F5F5F5] border border-[#E5E5E5]">
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-3">📊 </h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center">
<div className="text-3xl font-bold text-[#C41E3A]">60fps</div>
<div className="text-sm text-[#718096]"></div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-[#C41E3A]">GPU</div>
<div className="text-sm text-[#718096]"></div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-[#C41E3A]">WCAG</div>
<div className="text-sm text-[#718096]">访</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-[#C41E3A]">100%</div>
<div className="text-sm text-[#718096]"></div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}