0.0.3.0
This commit is contained in:
+131
-36
@@ -4,19 +4,41 @@ import type React from "react"
|
||||
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { TreeLayout } from "@/components/tree/tree-layout"
|
||||
import { D3OrgChartFlow, type D3OrgChartRef } from "@/components/tree/d3-org-chart-flow"
|
||||
import { useFamily } from "@/context/family-context"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ZoomIn, ZoomOut, Move, Filter, Download } from "lucide-react"
|
||||
import { useState, useRef } from "react"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { ZoomIn, ZoomOut, Move, Download, LayoutGrid, ChevronDown } from "lucide-react"
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
|
||||
type ViewMode = "traditional" | "d3-org-chart"
|
||||
|
||||
export default function TreePage() {
|
||||
const { treeData } = useFamily()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("traditional")
|
||||
|
||||
// 从 URL 参数读取视图模式
|
||||
useEffect(() => {
|
||||
const view = searchParams.get('view')
|
||||
if (view === 'd3-org-chart' || view === 'traditional') {
|
||||
setViewMode(view)
|
||||
}
|
||||
}, [searchParams])
|
||||
const [scale, setScale] = useState(1)
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [startPos, setStartPos] = useState({ x: 0, y: 0 })
|
||||
const [dragStartPos, setDragStartPos] = useState({ x: 0, y: 0 })
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const d3ChartRef = useRef<D3OrgChartRef>(null)
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
// 如果点击的是节点卡片,不启动拖拽
|
||||
@@ -173,43 +195,116 @@ export default function TreePage() {
|
||||
<div className="h-screen flex flex-col bg-muted/30 overflow-hidden">
|
||||
<SiteHeader />
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="absolute top-20 left-4 z-20 flex flex-col gap-2 bg-background/80 backdrop-blur p-2 rounded-lg border shadow-sm">
|
||||
<Button variant="outline" size="icon" onClick={() => setScale((s) => Math.min(s + 0.1, 2))}>
|
||||
<ZoomIn className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" onClick={() => setScale((s) => Math.max(s - 0.1, 0.1))}>
|
||||
<ZoomOut className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" onClick={() => setPosition({ x: 0, y: 0 })}>
|
||||
<Move className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="h-px bg-border my-1" />
|
||||
<Button variant="outline" size="icon" onClick={handleExportImage} title="导出为图片">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
{/* 视图切换按钮 */}
|
||||
<div className="absolute top-20 right-4 z-30">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="gap-2 shadow-lg">
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
{viewMode === 'traditional' ? '传统视图' : 'D3家谱图'}
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
params.set('view', 'traditional')
|
||||
router.push(`/tree?${params.toString()}`)
|
||||
}}
|
||||
className={viewMode === 'traditional' ? 'bg-accent' : ''}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span>传统家谱视图</span>
|
||||
{viewMode === 'traditional' && <span className="text-primary">✓</span>}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
params.set('view', 'd3-org-chart')
|
||||
router.push(`/tree?${params.toString()}`)
|
||||
}}
|
||||
className={viewMode === 'd3-org-chart' ? 'bg-accent' : ''}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span>D3 家谱图</span>
|
||||
{viewMode === 'd3-org-chart' && <span className="text-primary">✓</span>}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{/* Tree Canvas */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex-1 overflow-hidden cursor-move relative touch-none bg-[url('https://www.transparenttextures.com/patterns/rice-paper.png')] bg-repeat select-none"
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
|
||||
transformOrigin: "top center",
|
||||
transition: isDragging ? "none" : "transform 0.1s ease-out",
|
||||
}}
|
||||
className="absolute min-w-full min-h-full flex justify-center pt-20 pb-20"
|
||||
>
|
||||
<TreeLayout rootId={treeData.rootId} />
|
||||
{viewMode === 'traditional' ? (
|
||||
<div className="flex-1 relative">
|
||||
{/* Toolbar */}
|
||||
<div className="absolute top-4 left-4 z-20 flex flex-col gap-2 bg-background/80 backdrop-blur p-2 rounded-lg border shadow-sm">
|
||||
<Button variant="outline" size="icon" onClick={() => setScale((s) => Math.min(s + 0.1, 2))}>
|
||||
<ZoomIn className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" onClick={() => setScale((s) => Math.max(s - 0.1, 0.1))}>
|
||||
<ZoomOut className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" onClick={() => setPosition({ x: 0, y: 0 })}>
|
||||
<Move className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="h-px bg-border my-1" />
|
||||
<Button variant="outline" size="icon" onClick={handleExportImage} title="导出为图片">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tree Canvas */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full h-full overflow-hidden cursor-move relative touch-none bg-[url('https://www.transparenttextures.com/patterns/rice-paper.png')] bg-repeat select-none"
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
|
||||
transformOrigin: "top center",
|
||||
transition: isDragging ? "none" : "transform 0.1s ease-out",
|
||||
}}
|
||||
className="absolute min-w-full min-h-full flex justify-center pt-20 pb-20"
|
||||
>
|
||||
<TreeLayout rootId={treeData.rootId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 relative">
|
||||
{/* 统一的控制按钮 */}
|
||||
<div className="absolute top-4 left-4 z-20 flex flex-col gap-2 bg-background/80 backdrop-blur p-2 rounded-lg border shadow-sm">
|
||||
<Button variant="outline" size="icon" onClick={() => d3ChartRef.current?.expandAll()} title="展开全部">
|
||||
<ZoomIn className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" onClick={() => d3ChartRef.current?.collapseAll()} title="折叠全部">
|
||||
<ZoomOut className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" onClick={() => d3ChartRef.current?.fitView()} title="适应视图">
|
||||
<Move className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="h-px bg-border my-1" />
|
||||
<Button variant="outline" size="icon" onClick={() => d3ChartRef.current?.exportPNG()} title="导出PNG">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-full p-4">
|
||||
<D3OrgChartFlow
|
||||
ref={d3ChartRef}
|
||||
members={treeData.members}
|
||||
rootId={treeData.rootId}
|
||||
onMemberClick={(id: string) => router.push(`/members/${id}`)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user