0.0.8.5
This commit is contained in:
@@ -35,7 +35,7 @@ interface ChartNode {
|
||||
deathYear: string
|
||||
birthPlace: string
|
||||
gender: string
|
||||
avatarImageId: string | null | undefined
|
||||
avatarUrl: string | null | undefined
|
||||
spouseName: string
|
||||
spouseId: string | null
|
||||
hasChildren: boolean
|
||||
@@ -133,7 +133,7 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
deathYear: member.deathDate ? new Date(member.deathDate).getFullYear().toString() : '',
|
||||
birthPlace: member.birthPlace || '',
|
||||
gender: member.gender === 'MALE' ? '男' : '女',
|
||||
avatarImageId: member.avatarImageId,
|
||||
avatarUrl: member.avatarUrl,
|
||||
spouseName: spouses.length > 0 ? spouses.map(s => s.fullName).join(', ') : '',
|
||||
spouseId: spouses.length > 0 ? spouses[0].id : null,
|
||||
hasChildren: Object.values(members).some(m => m.fatherId === member.id || m.motherId === member.id),
|
||||
@@ -151,34 +151,17 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
return chartNodes
|
||||
}
|
||||
|
||||
// 加载所有成员的头像
|
||||
// 构建头像缓存(URL 直接使用)
|
||||
useEffect(() => {
|
||||
const loadAvatars = async () => {
|
||||
const cache = new Map<string, string>()
|
||||
|
||||
for (const member of Object.values(members)) {
|
||||
if (member.avatarImageId) {
|
||||
try {
|
||||
const image = await db.images.get(member.avatarImageId)
|
||||
if (image) {
|
||||
const url = URL.createObjectURL(image.blob)
|
||||
cache.set(member.avatarImageId, url)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load avatar for ${member.fullName}:`, error)
|
||||
}
|
||||
}
|
||||
const cache = new Map<string, string>()
|
||||
|
||||
for (const member of Object.values(members)) {
|
||||
if (member.avatarUrl) {
|
||||
cache.set(member.avatarUrl, member.avatarUrl)
|
||||
}
|
||||
|
||||
setAvatarCache(cache)
|
||||
}
|
||||
|
||||
loadAvatars()
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
avatarCache.forEach(url => URL.revokeObjectURL(url))
|
||||
}
|
||||
setAvatarCache(cache)
|
||||
}, [members])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -217,7 +200,7 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
const bgColor = node.gender === '男' ? '#dbeafe' : '#fce7f3'
|
||||
|
||||
// 获取头像URL
|
||||
const avatarUrl = node.avatarImageId ? avatarCache.get(node.avatarImageId) : null
|
||||
const avatarUrl = node.avatarUrl ? avatarCache.get(node.avatarUrl) : null
|
||||
|
||||
// 检查是否被选中
|
||||
const isSelected = selectedMembers.includes(node.id)
|
||||
@@ -260,8 +243,9 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
}
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<div style="font-size: 13px; font-weight: 600; color: #1f2937; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; gap: 4px;">
|
||||
<div style="font-size: 13px; font-weight: 600; color: ${node.deathYear ? '#9ca3af' : '#1f2937'}; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; gap: 4px; position: relative; padding-right: 12px;">
|
||||
<span>${node.name}</span>
|
||||
<span style="position: absolute; right: 2px; top: 4px; width: 8px; height: 8px; border-radius: 50%; background-color: ${node.deathYear ? '#9ca3af' : '#22c55e'};"></span>
|
||||
${isSelected ? `<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"><polyline points="20 6 9 17 4 12"/></svg>` : ''}
|
||||
</div>
|
||||
<div style="font-size: 9px; color: #6b7280; padding: 1px 4px; background: #f3f4f6; border-radius: 3px; display: inline-block;">
|
||||
@@ -304,19 +288,6 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
配偶: ${node.spouseName}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${node.deathYear ? `
|
||||
<div style="
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #737373;
|
||||
border: 2px solid white;
|
||||
"></div>
|
||||
` : ''}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { FamilyMember } from "@/types/family"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useAvatarCache } from "@/hooks/use-avatar-cache"
|
||||
import { CircleUser, CircleUserRound, Heart } from "lucide-react"
|
||||
import { MemberNameWithStatus } from "@/components/member-name-with-status"
|
||||
|
||||
interface FamilyNodeProps {
|
||||
member: FamilyMember
|
||||
@@ -42,7 +43,7 @@ function calculateAge(birthDate?: string, deathDate?: string): number | null {
|
||||
|
||||
// 单个人员卡片组件
|
||||
function PersonCard({ member, isRoot, isSpouse, onSelect, isHighlighted, relationMode, isSelected }: { member: FamilyMember; isRoot?: boolean; isSpouse?: boolean; onSelect?: (member: FamilyMember) => void; isHighlighted?: boolean; relationMode?: boolean; isSelected?: boolean }) {
|
||||
const { avatarUrl: avatarBlobUrl } = useAvatarCache(member?.avatarImageId)
|
||||
const { avatarUrl: avatarBlobUrl } = useAvatarCache(member?.avatarUrl)
|
||||
|
||||
const birthYear = formatYear(member.birthDate)
|
||||
const deathYear = formatYear(member.deathDate)
|
||||
@@ -98,7 +99,12 @@ function PersonCard({ member, isRoot, isSpouse, onSelect, isHighlighted, relatio
|
||||
</div>
|
||||
|
||||
<div className="text-center w-full">
|
||||
<div className="font-serif font-bold text-foreground leading-tight">{member.surname} {member.givenName}</div>
|
||||
<div className="font-serif font-bold text-foreground leading-tight flex items-center justify-center gap-1">
|
||||
<MemberNameWithStatus
|
||||
name={`${member.surname}${member.givenName}`}
|
||||
isDead={!!member.deathDate}
|
||||
/>
|
||||
</div>
|
||||
{member.courtesyName && (
|
||||
<div className="text-[10px] text-muted-foreground mt-0.5">字 {member.courtesyName}</div>
|
||||
)}
|
||||
@@ -118,10 +124,6 @@ function PersonCard({ member, isRoot, isSpouse, onSelect, isHighlighted, relatio
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{member.deathDate && (
|
||||
<div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-neutral-400 ring-2 ring-background" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ export function TreeLayout({
|
||||
if (relationMode && onMemberClick) {
|
||||
onMemberClick(member.id)
|
||||
} else {
|
||||
router.push(`/members/${member.id}`)
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
router.push(`/members/${member.id}?${params.toString()}`)
|
||||
}
|
||||
}, [relationMode, onMemberClick, router])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user