This commit is contained in:
freedakgmail
2025-11-30 19:10:30 +08:00
parent a05fb384a4
commit 86e889cc2a
16 changed files with 1555 additions and 763 deletions
+26 -26
View File
@@ -3,7 +3,7 @@
import { useEffect, useState } from "react"
import { useSession } from "next-auth/react"
import Link from "next/link"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle, ChineseCardDescription } from "@/components/ui/chinese-card"
import { Button } from "@/components/ui/button"
import { Users, Calendar, Plus, ArrowRight } from "lucide-react"
import { format } from "date-fns"
@@ -47,41 +47,41 @@ export function FamilyTreesList() {
if (!session) {
return (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ChineseCard variant="plaque" className="shadow-sm">
<ChineseCardHeader>
<ChineseCardTitle></ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<Link href="/auth/signin">
<Button></Button>
</Link>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
)
}
if (loading) {
return (
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<ChineseCard variant="plaque" className="shadow-sm">
<ChineseCardHeader>
<ChineseCardTitle></ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent>
<p className="text-muted-foreground">...</p>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
)
}
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<ChineseCard variant="plaque" className="shadow-sm">
<ChineseCardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle></CardTitle>
<CardDescription>
<ChineseCardTitle></ChineseCardTitle>
<ChineseCardDescription>
{trees.length > 0 ? `${trees.length} 个家族树` : '还没有家族树'}
</CardDescription>
</ChineseCardDescription>
</div>
<Link href="/trees/new">
<Button size="sm">
@@ -89,8 +89,8 @@ export function FamilyTreesList() {
</Button>
</Link>
</CardHeader>
<CardContent>
</ChineseCardHeader>
<ChineseCardContent>
{trees.length === 0 ? (
<div className="text-center py-8">
<p className="text-muted-foreground mb-4"></p>
@@ -106,7 +106,7 @@ export function FamilyTreesList() {
{trees.map((tree) => (
<div
key={tree.id}
className="flex items-center justify-between p-4 rounded-lg border bg-card hover:bg-accent/50 transition-colors"
className="flex items-center justify-between p-4 rounded-lg border bg-card/50 hover:bg-accent/50 transition-colors"
>
<div className="flex-1">
<h3 className="font-semibold text-lg">{tree.name}</h3>
@@ -134,7 +134,7 @@ export function FamilyTreesList() {
))}
</div>
)}
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
)
}
+33 -33
View File
@@ -13,7 +13,7 @@ import {
import { CanvasRenderer } from "echarts/renderers"
import { graphic } from "echarts/core"
import type { FamilyMember } from "@/types/family"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle, ChineseCardDescription } from "@/components/ui/chinese-card"
// 注册必要的组件
echarts.use([
@@ -330,45 +330,45 @@ export function StatisticsCharts({ members }: StatisticsChartsProps) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ChineseCard variant="default" cornerOrnament className="shadow-sm">
<ChineseCardHeader>
<ChineseCardTitle></ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<div ref={genderChartRef} className="h-[300px]" />
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ChineseCard variant="default" cornerOrnament className="shadow-sm">
<ChineseCardHeader>
<ChineseCardTitle></ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<div ref={generationChartRef} className="h-[300px]" />
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ChineseCard variant="scroll" className="shadow-sm">
<ChineseCardHeader className="pt-6">
<ChineseCardTitle></ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<div ref={ageDistributionRef} className="h-[300px]" />
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ChineseCard variant="scroll" className="shadow-sm">
<ChineseCardHeader className="pt-6">
<ChineseCardTitle></ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<div ref={birthYearChartRef} className="h-[300px]" />
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
)
}
+195 -131
View File
@@ -11,7 +11,7 @@ import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { CalendarIcon, User, Scroll, Users, Images, BookOpen } from "lucide-react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle, ChineseCardDescription } from "@/components/ui/chinese-card"
import { Switch } from "@/components/ui/switch"
import { ImageUpload } from "@/components/ui/image-upload"
import { PhotoGallery } from "./photo-gallery"
@@ -290,6 +290,8 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
spouseFatherName: formData.spouseFatherName,
spouseMotherName: formData.spouseMotherName,
phone: formData.phone,
telephone: formData.telephone,
email: formData.email,
occupation: formData.occupation,
courtesyName: formData.courtesyName,
artName: formData.artName,
@@ -298,8 +300,10 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
birthDate: formData.birthDate,
deathDate: formData.deathDate,
isLunarDate: formData.isLunarDate || false,
ancestralHome: formData.ancestralHome,
birthPlace: formData.birthPlace,
deathPlace: formData.deathPlace,
burialPlace: formData.burialPlace,
address: formData.address,
bio: formData.bio,
avatarUrl: formData.avatarUrl,
@@ -337,7 +341,7 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
{!hideTabs && (
<div className="flex-shrink-0">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="info"></TabsTrigger>
<TabsTrigger value="info"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="photos"></TabsTrigger>
<TabsTrigger value="stories"></TabsTrigger>
@@ -346,17 +350,17 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
)}
<div className="flex-1 overflow-y-auto">
{/* 详细信息Tab - 包含基本信息和家庭关系 */}
{/* 基本信息Tab - 包含基本信息和家庭关系 */}
<TabsContent value="info" className="space-y-6 mt-6 w-full">
{/* 基本信息卡片 */}
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<User className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-6">
{/* 头像和基本信息两栏布局 */}
<div className="grid grid-cols-1 lg:grid-cols-[200px_1fr] gap-8">
{/* 左侧:头像 */}
@@ -367,89 +371,88 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
/>
</div>
{/* 右侧:基本表单字段 */}
<div className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{/* 右侧:表单 */}
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="surname"> <span className="text-destructive">*</span></Label>
<Label htmlFor="surname">
<span className="text-red-500">*</span>
</Label>
<Input
id="surname"
value={formData.surname}
value={formData.surname || ""}
onChange={(e) => handleChange("surname", e.target.value)}
placeholder="例如:李"
className={errors.includes("surname") ? "border-red-500" : ""}
/>
</div>
<div className="space-y-2">
<Label htmlFor="givenName"> <span className="text-destructive">*</span></Label>
<Label htmlFor="givenName">
<span className="text-red-500">*</span>
</Label>
<Input
id="givenName"
value={formData.givenName}
value={formData.givenName || ""}
onChange={(e) => handleChange("givenName", e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="gender"> <span className="text-destructive">*</span></Label>
<Select value={formData.gender} onValueChange={(val) => handleChange("gender", val)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="MALE"></SelectItem>
<SelectItem value="FEMALE"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="generation"> <span className="text-destructive">*</span></Label>
<Input
id="generation"
type="number"
value={formData.generation}
onChange={(e) => handleChange("generation", Number.parseInt(e.target.value))}
placeholder="例如:明"
className={errors.includes("givenName") ? "border-red-500" : ""}
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="phone"></Label>
<div className="space-y-2">
<Label></Label>
<RadioGroup
value={formData.gender}
onValueChange={(val) => handleChange("gender", val)}
className="flex space-x-4"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="MALE" id="male" />
<Label htmlFor="male"></Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="FEMALE" id="female" />
<Label htmlFor="female"></Label>
</div>
</RadioGroup>
</div>
<div className="space-y-2">
<Label htmlFor="generation"> ()</Label>
<div className="flex items-center gap-4">
<Input
id="phone"
value={formData.phone || ""}
onChange={(e) => handleChange("phone", e.target.value)}
placeholder="请输入联系电话"
/>
</div>
<div className="space-y-2">
<Label htmlFor="occupation"></Label>
<Input
id="occupation"
value={formData.occupation || ""}
onChange={(e) => handleChange("occupation", e.target.value)}
placeholder="请输入职业"
id="generation"
type="number"
value={formData.generation || ""}
onChange={(e) => handleChange("generation", parseInt(e.target.value) || 0)}
className="w-32"
min={1}
/>
<span className="text-sm text-muted-foreground"></span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
{/* 家庭关系 - 左右两个独立卡片 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 左栏:父母 */}
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<Users className="h-5 w-5" />
</CardTitle>
</ChineseCardTitle>
{!isFounder && (
<CardDescription className="text-xs">
<ChineseCardDescription className="text-xs">
</CardDescription>
</ChineseCardDescription>
)}
</CardHeader>
<CardContent>
</ChineseCardHeader>
<ChineseCardContent>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="fatherId"></Label>
@@ -558,23 +561,23 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
)}
</div>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
{/* 右栏:配偶和子女 */}
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<Users className="h-5 w-5" />
</CardTitle>
</ChineseCardTitle>
{!isFounder && (
<CardDescription className="text-xs">
<ChineseCardDescription className="text-xs">
</CardDescription>
</ChineseCardDescription>
)}
</CardHeader>
<CardContent>
</ChineseCardHeader>
<ChineseCardContent>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="spouseId"></Label>
@@ -697,8 +700,8 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
)}
</div>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
</TabsContent>
@@ -706,14 +709,14 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
<TabsContent value="details" className="space-y-6 mt-6 w-full">
{/* 使用两列布局放置两个卡片 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<Scroll className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="courtesyName"></Label>
@@ -752,17 +755,17 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
/>
</div>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<CalendarIcon className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4">
<DateInputWithLunar
id="birthDate"
@@ -781,20 +784,29 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
showLunarToggle={false}
/>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
{/* 地点和简介卡片 */}
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<Scroll className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="ancestralHome"></Label>
<Input
id="ancestralHome"
value={formData.ancestralHome || ""}
onChange={(e) => handleChange("ancestralHome", e.target.value)}
placeholder="请输入籍贯"
/>
</div>
<div className="space-y-2">
<Label htmlFor="birthPlace"></Label>
<Input
@@ -804,6 +816,18 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
placeholder="请输入出生地"
/>
</div>
<div className="space-y-2">
<Label htmlFor="address"></Label>
<Input
id="address"
value={formData.address || ""}
onChange={(e) => handleChange("address", e.target.value)}
placeholder="请输入居住地"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="deathPlace"></Label>
<Input
@@ -814,12 +838,52 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
/>
</div>
<div className="space-y-2">
<Label htmlFor="address"></Label>
<Label htmlFor="burialPlace"></Label>
<Input
id="address"
value={formData.address || ""}
onChange={(e) => handleChange("address", e.target.value)}
placeholder="请输入居住地"
id="burialPlace"
value={formData.burialPlace || ""}
onChange={(e) => handleChange("burialPlace", e.target.value)}
placeholder="请输入安葬地"
/>
</div>
<div className="space-y-2">
<Label htmlFor="occupation"></Label>
<Input
id="occupation"
value={formData.occupation || ""}
onChange={(e) => handleChange("occupation", e.target.value)}
placeholder="请输入职业"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="phone"></Label>
<Input
id="phone"
value={formData.phone || ""}
onChange={(e) => handleChange("phone", e.target.value)}
placeholder="请输入手机号"
/>
</div>
<div className="space-y-2">
<Label htmlFor="telephone"></Label>
<Input
id="telephone"
value={formData.telephone || ""}
onChange={(e) => handleChange("telephone", e.target.value)}
placeholder="请输入固定电话"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email"></Label>
<Input
id="email"
type="email"
value={formData.email || ""}
onChange={(e) => handleChange("email", e.target.value)}
placeholder="请输入电子邮箱"
/>
</div>
</div>
@@ -834,57 +898,57 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
rows={4}
/>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
{/* 生平影像Tab */}
<TabsContent value="photos" className="space-y-6 mt-6 w-full">
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<Images className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
</ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<PhotoGallery
photos={formData.photos}
photoIds={formData.photoIds}
onChange={handlePhotosChange}
/>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
{/* 家族故事Tab */}
<TabsContent value="stories" className="space-y-6 mt-6 w-full">
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<BookOpen className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
</ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<StoryManager
stories={formData.stories || []}
onChange={(stories) => handleChange("stories", stories)}
/>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
<Scroll className="h-5 w-5" />
</CardTitle>
<CardDescription>便</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
</ChineseCardTitle>
<ChineseCardDescription>便</ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="space-y-3">
<div className="flex gap-2">
<Input
@@ -925,8 +989,8 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
</div>
)}
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
</div>
</Tabs>
@@ -146,7 +146,7 @@ export function MemberVersionHistory({ memberId, treeId, onVersionSelect, lastUp
{/* 其他信息变更 */}
{changedOthers.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-foreground mb-2"></h4>
<h4 className="text-xs font-semibold text-foreground mb-2"></h4>
<div className="space-y-2 text-xs">
{changedOthers.map(([field, change]) => (
<div key={field} className="flex items-start gap-2">
+25
View File
@@ -0,0 +1,25 @@
"use client"
import { ArrowLeft } from "lucide-react"
import { Button } from "@/components/ui/button"
interface BackButtonProps {
onClick: () => void
label?: string
className?: string
}
export function BackButton({ onClick, label = "返回", className = "" }: BackButtonProps) {
return (
<div className="py-4">
<Button
variant="ghost"
onClick={onClick}
className={`gap-2 pl-0 hover:bg-transparent hover:text-primary text-2xl font-bold ${className}`}
>
<ArrowLeft className="h-6 w-6" />
{label}
</Button>
</div>
)
}
+207
View File
@@ -0,0 +1,207 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
/**
* 中国风卡片组件
* 提供多种传统中国设计风格变体
*/
type ChineseCardVariant = 'default' | 'scroll' | 'plaque' | 'portrait' | 'bamboo'
interface ChineseCardProps extends React.ComponentProps<'div'> {
variant?: ChineseCardVariant
/** 是否显示角饰 */
cornerOrnament?: boolean
/** 是否显示顶部装饰条 */
headerDecoration?: boolean
}
function ChineseCard({
className,
variant = 'default',
cornerOrnament = false,
headerDecoration = false,
children,
...props
}: ChineseCardProps) {
return (
<div
data-slot="chinese-card"
data-variant={variant}
className={cn(
// 基础样式
'relative bg-card text-card-foreground flex flex-col',
// 变体样式
variant === 'default' && 'chinese-card-default',
variant === 'scroll' && 'chinese-card-scroll',
variant === 'plaque' && 'chinese-card-plaque',
variant === 'portrait' && 'chinese-card-portrait',
variant === 'bamboo' && 'chinese-card-bamboo',
// 角饰
cornerOrnament && 'chinese-card-corner-ornament',
className,
)}
{...props}
>
{/* 顶部装饰条 */}
{headerDecoration && (
<div className="chinese-card-header-decoration" />
)}
{/* 角饰元素 */}
{cornerOrnament && (
<>
<span className="chinese-corner chinese-corner-tl" />
<span className="chinese-corner chinese-corner-tr" />
<span className="chinese-corner chinese-corner-bl" />
<span className="chinese-corner chinese-corner-br" />
</>
)}
{children}
</div>
)
}
function ChineseCardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="chinese-card-header"
className={cn(
'flex flex-col gap-1.5 px-5 pt-5 pb-3',
className,
)}
{...props}
/>
)
}
function ChineseCardTitle({ className, ...props }: React.ComponentProps<'h3'>) {
return (
<h3
data-slot="chinese-card-title"
className={cn(
'font-serif text-lg font-semibold leading-tight tracking-wide',
className,
)}
{...props}
/>
)
}
function ChineseCardDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
<p
data-slot="chinese-card-description"
className={cn(
'font-serif text-sm text-muted-foreground',
className,
)}
{...props}
/>
)
}
function ChineseCardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="chinese-card-content"
className={cn('px-5 pb-5', className)}
{...props}
/>
)
}
function ChineseCardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="chinese-card-footer"
className={cn(
'flex items-center px-5 pb-5 pt-0',
className,
)}
{...props}
/>
)
}
/**
* 中国风分隔线
*/
function ChineseDivider({
className,
variant = 'wave',
...props
}: React.ComponentProps<'div'> & {
variant?: 'wave' | 'cloud' | 'meander' | 'simple'
}) {
return (
<div
data-slot="chinese-divider"
className={cn(
'chinese-divider',
variant === 'wave' && 'chinese-divider-wave',
variant === 'cloud' && 'chinese-divider-cloud',
variant === 'meander' && 'chinese-divider-meander',
variant === 'simple' && 'chinese-divider-simple',
className,
)}
{...props}
/>
)
}
/**
* 统计数字组件 - 用于展示大数字
*/
function ChineseStatNumber({
className,
children,
...props
}: React.ComponentProps<'span'>) {
return (
<span
className={cn(
'text-3xl font-serif font-bold tracking-tight text-foreground',
className,
)}
{...props}
>
{children}
</span>
)
}
/**
* 统计标签组件
*/
function ChineseStatLabel({
className,
children,
...props
}: React.ComponentProps<'span'>) {
return (
<span
className={cn(
'text-sm font-medium text-muted-foreground uppercase tracking-wider',
className,
)}
{...props}
>
{children}
</span>
)
}
export {
ChineseCard,
ChineseCardHeader,
ChineseCardTitle,
ChineseCardDescription,
ChineseCardContent,
ChineseCardFooter,
ChineseDivider,
ChineseStatNumber,
ChineseStatLabel,
type ChineseCardVariant,
}