feat: 添加连锁门店系统首页及核心UI组件

新增首页布局、导航栏、页脚及多个核心UI组件(按钮、卡片、表格等)
添加图片资源、工具函数和样式配置
实现响应式设计和主题支持
包含行业解决方案展示区块
This commit is contained in:
JanYork
2026-03-18 10:50:42 +08:00
commit 6a68a96287
110 changed files with 12842 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
"use client"
import { useState } from "react"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
import {
Monitor,
SplitSquareVertical,
BarChart3,
Check,
ArrowRight,
Cpu,
Network,
Eye,
} from "lucide-react"
import homeData from "@/data/home.json"
import { cn } from "@/lib/utils"
const productIcons: Record<string, React.ReactNode> = {
pos: <Monitor className="w-7 h-7" />,
split: <SplitSquareVertical className="w-7 h-7" />,
monitor: <Eye className="w-7 h-7" />,
}
const productBgSvg: Record<string, React.ReactNode> = {
pos: (
<svg className="absolute inset-0 w-full h-full opacity-[0.04]" viewBox="0 0 400 280" fill="none" aria-hidden="true">
<rect x="60" y="40" width="280" height="160" rx="12" stroke="currentColor" strokeWidth="2" />
<rect x="80" y="60" width="240" height="120" rx="6" stroke="currentColor" strokeWidth="1.5" />
<line x1="160" y1="200" x2="240" y2="200" stroke="currentColor" strokeWidth="2" />
<rect x="140" y="200" width="120" height="30" rx="4" stroke="currentColor" strokeWidth="1.5" />
{[0,1,2,3].map(i => <rect key={i} x={90 + i * 56} y="70" width="44" height="28" rx="4" stroke="currentColor" strokeWidth="1" />)}
</svg>
),
split: (
<svg className="absolute inset-0 w-full h-full opacity-[0.04]" viewBox="0 0 400 280" fill="none" aria-hidden="true">
<circle cx="200" cy="90" r="30" stroke="currentColor" strokeWidth="2" />
<circle cx="100" cy="200" r="22" stroke="currentColor" strokeWidth="2" />
<circle cx="200" cy="200" r="22" stroke="currentColor" strokeWidth="2" />
<circle cx="300" cy="200" r="22" stroke="currentColor" strokeWidth="2" />
<line x1="200" y1="120" x2="100" y2="178" stroke="currentColor" strokeWidth="1.5" />
<line x1="200" y1="120" x2="200" y2="178" stroke="currentColor" strokeWidth="1.5" />
<line x1="200" y1="120" x2="300" y2="178" stroke="currentColor" strokeWidth="1.5" />
</svg>
),
monitor: (
<svg className="absolute inset-0 w-full h-full opacity-[0.04]" viewBox="0 0 400 280" fill="none" aria-hidden="true">
{[0,1,2,3,4].map(i => <line key={i} x1="40" y1={60 + i * 40} x2="360" y2={60 + i * 40} stroke="currentColor" strokeWidth="1" />)}
{[0,1,2,3,4,5,6,7].map(i => <line key={i} x1={40 + i * 46} y1="60" x2={40 + i * 46} y2="220" stroke="currentColor" strokeWidth="1" />)}
<polyline points="40,200 120,140 200,160 280,100 360,120" stroke="currentColor" strokeWidth="2" fill="none" />
<circle cx="280" cy="100" r="5" fill="currentColor" />
</svg>
),
}
const colorMap: Record<string, { ring: string; badge: string; icon: string }> = {
blue: { ring: "hover:border-primary/50", badge: "bg-primary/10 text-primary", icon: "bg-primary text-white" },
indigo: { ring: "hover:border-indigo-400/50", badge: "bg-indigo-50 text-indigo-600", icon: "bg-indigo-600 text-white" },
violet: { ring: "hover:border-violet-400/50", badge: "bg-violet-50 text-violet-600", icon: "bg-violet-600 text-white" },
}
export function ServicesSection() {
const { products } = homeData
const [active, setActive] = useState(0)
return (
<section id="products" className="py-20 lg:py-28 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<div className="flex flex-col items-center text-center gap-4 mb-14">
<Badge
variant="outline"
className="border-primary/30 text-primary bg-primary-light px-4 py-1 text-sm font-medium"
>
{products.sectionBadge}
</Badge>
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground text-balance">
{products.title}
</h2>
<p className="text-muted-foreground text-base lg:text-lg max-w-2xl text-pretty">
{products.subtitle}
</p>
</div>
{/* Tab triggers */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 mb-10">
{products.items.map((item, i) => {
const colors = colorMap[item.color]
return (
<button
key={item.id}
onClick={() => setActive(i)}
className={cn(
"flex items-center gap-2.5 px-5 py-3 rounded-xl border text-sm font-semibold transition-all duration-200",
active === i
? "bg-primary border-primary text-white shadow-lg shadow-primary/25"
: "bg-background border-border text-muted-foreground hover:border-primary/40 hover:text-foreground"
)}
>
<span className={cn("w-6 h-6 rounded-lg flex items-center justify-center", active === i ? "bg-white/20" : colors.icon)}>
{item.iconType === "pos" && <Monitor className="w-3.5 h-3.5" />}
{item.iconType === "split" && <Network className="w-3.5 h-3.5" />}
{item.iconType === "monitor" && <Eye className="w-3.5 h-3.5" />}
</span>
{item.tag}
</button>
)
})}
</div>
{/* Active product detail */}
{products.items.map((item, i) => {
const colors = colorMap[item.color]
if (i !== active) return null
return (
<Card
key={item.id}
className={cn(
"border-2 overflow-hidden transition-all duration-300",
active === i ? "border-primary/30 shadow-xl shadow-primary/10" : "border-border"
)}
>
<CardContent className="p-0">
<div className="grid grid-cols-1 lg:grid-cols-2">
{/* Left: text */}
<div className="p-8 lg:p-10 flex flex-col gap-6">
<div className="flex items-center gap-3">
<div className={cn("w-12 h-12 rounded-2xl flex items-center justify-center shadow-sm", colors.icon)}>
{productIcons[item.iconType]}
</div>
<div>
<span className={cn("text-xs font-semibold px-2.5 py-1 rounded-full", colors.badge)}>
{item.tag}
</span>
<h3 className="text-xl font-bold text-foreground mt-1">{item.title}</h3>
</div>
</div>
<p className="text-muted-foreground leading-relaxed text-pretty">{item.description}</p>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
{item.features.map((f) => (
<li key={f} className="flex items-center gap-2 text-sm text-foreground">
<div className="w-5 h-5 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
<Check className="w-3 h-3 text-primary" />
</div>
{f}
</li>
))}
</ul>
<a
href="#contact"
className="self-start flex items-center gap-1.5 text-sm font-semibold text-primary hover:gap-2.5 transition-all"
>
<ArrowRight className="w-4 h-4" />
</a>
</div>
{/* Right: visual mockup */}
<div className={cn(
"relative flex items-center justify-center min-h-[280px] lg:min-h-0 overflow-hidden",
item.color === "blue" && "bg-primary/5",
item.color === "indigo" && "bg-indigo-50",
item.color === "violet" && "bg-violet-50",
)}>
{productBgSvg[item.iconType]}
<div className="relative z-10 flex flex-col items-center gap-5 p-8">
{/* Central icon */}
<div className={cn("w-20 h-20 rounded-3xl flex items-center justify-center shadow-xl", colors.icon)}>
{item.iconType === "pos" && <Monitor className="w-10 h-10" />}
{item.iconType === "split" && <SplitSquareVertical className="w-10 h-10" />}
{item.iconType === "monitor" && <BarChart3 className="w-10 h-10" />}
</div>
{/* Floating stat cards */}
<div className="flex flex-wrap justify-center gap-3 max-w-xs">
{item.features.slice(0, 3).map((f) => (
<span key={f} className="bg-white border border-border text-xs text-foreground font-medium px-3 py-1.5 rounded-full shadow-sm">
{f}
</span>
))}
</div>
<div className="flex items-center gap-2 bg-white border border-border rounded-xl px-4 py-2.5 shadow-sm">
<Cpu className="w-4 h-4 text-primary" />
<span className="text-xs font-semibold text-foreground"> · </span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
)
})}
</div>
</section>
)
}