0.0.4.0
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { RelationshipCalculator } from '@/lib/relationship-calculator'
|
||||
|
||||
/**
|
||||
* 计算两个成员之间的关系
|
||||
* GET /api/trees/[treeId]/relationship?from=xxx&to=yyy
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { treeId: string } }
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const fromId = searchParams.get('from')
|
||||
const toId = searchParams.get('to')
|
||||
|
||||
if (!fromId || !toId) {
|
||||
return NextResponse.json(
|
||||
{ error: '缺少参数:from 和 to' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 获取家族树的所有成员
|
||||
const members = await prisma.familyMember.findMany({
|
||||
where: { treeId: params.treeId }
|
||||
})
|
||||
|
||||
if (members.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: '家族树不存在或没有成员' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 计算关系
|
||||
const calculator = new RelationshipCalculator(members as any)
|
||||
const result = calculator.getRelationshipDetail(fromId, toId)
|
||||
|
||||
// 获取成员信息
|
||||
const fromMember = members.find(m => m.id === fromId)
|
||||
const toMember = members.find(m => m.id === toId)
|
||||
|
||||
return NextResponse.json({
|
||||
from: {
|
||||
id: fromMember?.id,
|
||||
name: fromMember?.fullName
|
||||
},
|
||||
to: {
|
||||
id: toMember?.id,
|
||||
name: toMember?.fullName
|
||||
},
|
||||
relationship: result.term,
|
||||
path: result.path,
|
||||
description: result.description,
|
||||
success: result.success
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('计算关系失败:', error)
|
||||
return NextResponse.json(
|
||||
{ error: '计算关系失败' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { SiteHeader } from '@/components/site-header'
|
||||
import { useFamily } from '@/context/family-context'
|
||||
import { useRelationship } from '@/hooks/use-relationship'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
export default function RelationshipTestPage() {
|
||||
const { treeData } = useFamily()
|
||||
const { calculateRelationship, getDirectRelatives, getSiblings } = useRelationship()
|
||||
|
||||
const [fromId, setFromId] = useState<string>('')
|
||||
const [toId, setToId] = useState<string>('')
|
||||
const [result, setResult] = useState<any>(null)
|
||||
|
||||
const members = Object.values(treeData.members)
|
||||
|
||||
const handleCalculate = () => {
|
||||
if (!fromId || !toId) return
|
||||
|
||||
const relationship = calculateRelationship(fromId, toId)
|
||||
const fromMember = treeData.members[fromId]
|
||||
const toMember = treeData.members[toId]
|
||||
|
||||
setResult({
|
||||
from: fromMember,
|
||||
to: toMember,
|
||||
relationship
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-background">
|
||||
<SiteHeader />
|
||||
|
||||
<main className="flex-1 container mx-auto px-4 py-8">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{/* 标题 */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold font-serif mb-2">家族关系计算器</h1>
|
||||
<p className="text-muted-foreground">
|
||||
选择两个家族成员,自动计算他们之间的亲属关系
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 选择成员 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>选择成员</CardTitle>
|
||||
<CardDescription>请选择要计算关系的两个成员</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">成员A</label>
|
||||
<Select value={fromId} onValueChange={setFromId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择成员A" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{members.map(member => (
|
||||
<SelectItem key={member.id} value={member.id}>
|
||||
{member.fullName} (第{member.generation}世)
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">成员B</label>
|
||||
<Select value={toId} onValueChange={setToId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择成员B" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{members.map(member => (
|
||||
<SelectItem key={member.id} value={member.id}>
|
||||
{member.fullName} (第{member.generation}世)
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleCalculate}
|
||||
disabled={!fromId || !toId}
|
||||
className="w-full"
|
||||
>
|
||||
计算关系
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 计算结果 */}
|
||||
{result && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>计算结果</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-center gap-4 p-6 bg-muted rounded-lg">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold font-serif mb-1">
|
||||
{result.from.fullName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
第{result.from.generation}世
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Badge variant="default" className="text-lg px-4 py-2">
|
||||
{result.relationship.term}
|
||||
</Badge>
|
||||
{result.relationship.path && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
路径: {result.relationship.path}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold font-serif mb-1">
|
||||
{result.to.fullName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
第{result.to.generation}世
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{result.relationship.description && (
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">关系说明:</div>
|
||||
<div className="text-muted-foreground">
|
||||
<strong>{result.from.fullName}</strong> 是 <strong>{result.to.fullName}</strong> 的 <strong className="text-primary">{result.relationship.term}</strong>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result.relationship.success === false && (
|
||||
<div className="p-4 bg-yellow-50 dark:bg-yellow-950 rounded-lg">
|
||||
<div className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||
⚠️ {result.relationship.error || '无法计算关系'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 快速查询示例 */}
|
||||
{fromId && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{treeData.members[fromId]?.fullName} 的直系亲属
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{(() => {
|
||||
const relatives = getDirectRelatives(fromId)
|
||||
if (!relatives) return null
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{relatives.father && (
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<div className="text-sm text-muted-foreground mb-1">父亲</div>
|
||||
<div className="font-medium">{relatives.father.fullName}</div>
|
||||
</div>
|
||||
)}
|
||||
{relatives.mother && (
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<div className="text-sm text-muted-foreground mb-1">母亲</div>
|
||||
<div className="font-medium">{relatives.mother.fullName}</div>
|
||||
</div>
|
||||
)}
|
||||
{relatives.spouses.length > 0 && (
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<div className="text-sm text-muted-foreground mb-1">配偶</div>
|
||||
{relatives.spouses.map((spouse: any) => (
|
||||
<div key={spouse.id} className="font-medium">
|
||||
{spouse.fullName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{relatives.children.length > 0 && (
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<div className="text-sm text-muted-foreground mb-1">
|
||||
子女 ({relatives.children.length}人)
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{relatives.children.slice(0, 3).map((child: any) => (
|
||||
<div key={child.id} className="font-medium text-sm">
|
||||
{child.fullName}
|
||||
</div>
|
||||
))}
|
||||
{relatives.children.length > 3 && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
还有 {relatives.children.length - 3} 人...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
||||
{(() => {
|
||||
const siblings = getSiblings(fromId)
|
||||
if (siblings.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="mt-4 p-3 bg-muted rounded-lg">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
兄弟姐妹 ({siblings.length}人)
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{siblings.map((sibling: any) => (
|
||||
<Badge key={sibling.id} variant="outline">
|
||||
{sibling.fullName}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+242
-6
@@ -6,6 +6,7 @@ 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 { useRelationship } from "@/hooks/use-relationship"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -13,18 +14,36 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { ZoomIn, ZoomOut, Move, Download, LayoutGrid, ChevronDown } from "lucide-react"
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { ZoomIn, ZoomOut, Move, Download, LayoutGrid, ChevronDown, Users } from "lucide-react"
|
||||
import { useState, useRef, useEffect, useCallback } from "react"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
|
||||
type ViewMode = "traditional" | "d3-org-chart"
|
||||
|
||||
export default function TreePage() {
|
||||
const { treeData } = useFamily()
|
||||
const { calculateRelationship } = useRelationship()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("traditional")
|
||||
|
||||
// 关系查询模式
|
||||
const [relationMode, setRelationMode] = useState(false)
|
||||
const [selectedMembers, setSelectedMembers] = useState<string[]>([])
|
||||
const [relationResult, setRelationResult] = useState<any>(null)
|
||||
const [showRelationDialog, setShowRelationDialog] = useState(false)
|
||||
|
||||
// 使用ref存储最新的relationMode值,避免回调重新创建
|
||||
const relationModeRef = useRef(relationMode)
|
||||
const selectedMembersRef = useRef(selectedMembers)
|
||||
|
||||
useEffect(() => {
|
||||
relationModeRef.current = relationMode
|
||||
selectedMembersRef.current = selectedMembers
|
||||
}, [relationMode, selectedMembers])
|
||||
|
||||
// 从 URL 参数读取视图模式
|
||||
useEffect(() => {
|
||||
const view = searchParams.get('view')
|
||||
@@ -70,6 +89,146 @@ export default function TreePage() {
|
||||
setIsDragging(false)
|
||||
}
|
||||
|
||||
// 处理成员选择(关系查询模式)
|
||||
const handleMemberClick = (memberId: string) => {
|
||||
if (selectedMembers.length === 0) {
|
||||
// 选择第一个成员
|
||||
setSelectedMembers([memberId])
|
||||
} else if (selectedMembers.length === 1) {
|
||||
// 选择第二个成员,计算关系
|
||||
const [firstId] = selectedMembers
|
||||
if (firstId === memberId) {
|
||||
// 点击同一个人,取消选择
|
||||
setSelectedMembers([])
|
||||
return
|
||||
}
|
||||
|
||||
const result = calculateRelationship(firstId, memberId)
|
||||
const firstMember = treeData.members[firstId]
|
||||
const secondMember = treeData.members[memberId]
|
||||
|
||||
setRelationResult({
|
||||
from: firstMember,
|
||||
to: secondMember,
|
||||
relationship: result
|
||||
})
|
||||
setShowRelationDialog(true)
|
||||
setSelectedMembers([])
|
||||
setRelationMode(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换关系查询模式
|
||||
const toggleRelationMode = () => {
|
||||
setRelationMode(!relationMode)
|
||||
setSelectedMembers([])
|
||||
setRelationResult(null)
|
||||
}
|
||||
|
||||
// 处理对话框关闭
|
||||
const handleDialogClose = (open: boolean) => {
|
||||
setShowRelationDialog(open)
|
||||
if (!open && relationResult) {
|
||||
// 清除选中样式
|
||||
const clearNodeStyle = (nodeId: string) => {
|
||||
const chartContainer = document.querySelector('.w-full.h-full.bg-background.rounded-lg.border')
|
||||
const svg = chartContainer?.querySelector('svg')
|
||||
if (!svg) return
|
||||
|
||||
const foreignObjects = svg.querySelectorAll('foreignObject')
|
||||
foreignObjects.forEach((fo: any) => {
|
||||
const nodeData = (fo.parentNode as any)?.__data__?.data
|
||||
if (nodeData?.id === nodeId) {
|
||||
const nameDiv = fo.querySelector('div[style*="font-size: 13px"]') as HTMLElement
|
||||
if (nameDiv) {
|
||||
// 移除勾选图标
|
||||
const checkIcon = nameDiv.querySelector('svg')
|
||||
if (checkIcon) {
|
||||
checkIcon.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
clearNodeStyle(relationResult.from.id)
|
||||
clearNodeStyle(relationResult.to.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用useCallback创建稳定的回调,通过ref访问最新的relationMode
|
||||
const handleD3MemberClick = useCallback((id: string) => {
|
||||
console.log('D3 onMemberClick triggered:', id, 'relationMode:', relationModeRef.current)
|
||||
if (relationModeRef.current) {
|
||||
console.log('Calling handleMemberClick')
|
||||
|
||||
// 更新节点样式的辅助函数
|
||||
const updateNodeStyle = (nodeId: string, isSelected: boolean) => {
|
||||
// 从DOM中查找D3图表容器
|
||||
const chartContainer = document.querySelector('.w-full.h-full.bg-background.rounded-lg.border')
|
||||
const svg = chartContainer?.querySelector('svg')
|
||||
if (!svg) return
|
||||
|
||||
const foreignObjects = svg.querySelectorAll('foreignObject')
|
||||
foreignObjects.forEach((fo: any) => {
|
||||
const nodeData = (fo.parentNode as any)?.__data__?.data
|
||||
if (nodeData?.id === nodeId) {
|
||||
const nameDiv = fo.querySelector('div[style*="font-size: 13px"]') as HTMLElement
|
||||
if (nameDiv) {
|
||||
if (isSelected) {
|
||||
// 添加勾选图标
|
||||
if (!nameDiv.querySelector('svg')) {
|
||||
const iconHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="margin-left: 4px"><polyline points="20 6 9 17 4 12"/></svg>`
|
||||
nameDiv.style.display = 'flex'
|
||||
nameDiv.style.alignItems = 'center'
|
||||
nameDiv.insertAdjacentHTML('beforeend', iconHTML)
|
||||
}
|
||||
} else {
|
||||
// 移除勾选图标
|
||||
const checkIcon = nameDiv.querySelector('svg')
|
||||
if (checkIcon) {
|
||||
checkIcon.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 直接处理选择逻辑
|
||||
if (selectedMembersRef.current.length === 0) {
|
||||
updateNodeStyle(id, true)
|
||||
setSelectedMembers([id])
|
||||
} else if (selectedMembersRef.current.length === 1) {
|
||||
const [firstId] = selectedMembersRef.current
|
||||
if (firstId === id) {
|
||||
updateNodeStyle(id, false)
|
||||
setSelectedMembers([])
|
||||
return
|
||||
}
|
||||
|
||||
updateNodeStyle(id, true)
|
||||
|
||||
const result = calculateRelationship(firstId, id)
|
||||
const firstMember = treeData.members[firstId]
|
||||
const secondMember = treeData.members[id]
|
||||
|
||||
setRelationResult({
|
||||
from: firstMember,
|
||||
to: secondMember,
|
||||
relationship: result
|
||||
})
|
||||
setShowRelationDialog(true)
|
||||
setSelectedMembers([])
|
||||
setRelationMode(false)
|
||||
// 选中样式会在对话框关闭时清除
|
||||
}
|
||||
} else {
|
||||
console.log('Navigating to member detail')
|
||||
router.push(`/members/${id}`)
|
||||
}
|
||||
}, [router, calculateRelationship, treeData.members])
|
||||
|
||||
const handleExportImage = async () => {
|
||||
if (!containerRef.current) return
|
||||
|
||||
@@ -195,8 +354,20 @@ export default function TreePage() {
|
||||
<div className="h-screen flex flex-col bg-muted/30 overflow-hidden">
|
||||
<SiteHeader />
|
||||
|
||||
{/* 视图切换按钮 */}
|
||||
<div className="absolute top-20 right-4 z-30">
|
||||
{/* 视图切换和关系查询按钮 */}
|
||||
<div className="absolute top-20 right-4 z-30 flex gap-2">
|
||||
{/* 关系查询按钮 */}
|
||||
<Button
|
||||
variant={relationMode ? "default" : "outline"}
|
||||
className="gap-2 shadow-lg"
|
||||
onClick={toggleRelationMode}
|
||||
>
|
||||
<Users className="h-4 w-4" />
|
||||
{relationMode ? (
|
||||
selectedMembers.length === 0 ? '选择第一个成员' : '选择第二个成员'
|
||||
) : '关系查询'}
|
||||
</Button>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="gap-2 shadow-lg">
|
||||
@@ -272,7 +443,12 @@ export default function TreePage() {
|
||||
}}
|
||||
className="absolute min-w-full min-h-full flex justify-center pt-20 pb-20"
|
||||
>
|
||||
<TreeLayout rootId={treeData.rootId} />
|
||||
<TreeLayout
|
||||
rootId={treeData.rootId}
|
||||
relationMode={relationMode}
|
||||
selectedMembers={selectedMembers}
|
||||
onMemberClick={handleMemberClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -300,11 +476,71 @@ export default function TreePage() {
|
||||
ref={d3ChartRef}
|
||||
members={treeData.members}
|
||||
rootId={treeData.rootId}
|
||||
onMemberClick={(id: string) => router.push(`/members/${id}`)}
|
||||
relationMode={relationMode}
|
||||
selectedMembers={selectedMembers}
|
||||
onMemberClick={handleD3MemberClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 关系查询结果对话框 */}
|
||||
<Dialog open={showRelationDialog} onOpenChange={handleDialogClose}>
|
||||
<DialogContent className="max-w-2xl" aria-describedby="relation-description">
|
||||
<DialogHeader>
|
||||
<DialogTitle>关系查询结果</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{relationResult && (
|
||||
<div className="space-y-4">
|
||||
{/* 成员信息 */}
|
||||
<div className="flex items-center justify-center gap-4 p-6 bg-muted rounded-lg">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold font-serif mb-1">
|
||||
{relationResult.from.fullName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
第{relationResult.from.generation}世
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Badge variant="default" className="text-lg px-4 py-2">
|
||||
{relationResult.relationship.term}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold font-serif mb-1">
|
||||
{relationResult.to.fullName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
第{relationResult.to.generation}世
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关系说明 */}
|
||||
<div id="relation-description" className="p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">关系说明:</div>
|
||||
<div className="text-muted-foreground">
|
||||
<strong>{relationResult.from.fullName}</strong> 是 <strong>{relationResult.to.fullName}</strong> 的 <strong className="text-primary">{relationResult.relationship.term}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关系路径 */}
|
||||
{relationResult.relationship.path && (
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<div className="text-sm font-medium mb-2">关系路径:</div>
|
||||
<div className="text-xs text-muted-foreground break-all">
|
||||
{relationResult.relationship.path}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user