This commit is contained in:
freedakgmail
2025-11-30 21:20:51 +08:00
parent a2873bf3c2
commit 025f2b619f
186 changed files with 809 additions and 653 deletions
+89 -38
View File
@@ -196,54 +196,95 @@ export default function TreePage() {
const handleD3MemberClick = useCallback((id: string) => {
console.log('D3 onMemberClick triggered:', id, 'relationMode:', relationModeRef.current)
if (relationModeRef.current) {
console.log('Calling handleMemberClick')
console.log('Calling handleMemberClick, current selected:', selectedMembersRef.current)
// 更新节点样式的辅助函数
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
// 直接操作 DOM 来显示选中标记
// D3 图表的节点在 SVG 的 foreignObject 中,需要特殊查询
// 注意:配偶没有独立的节点,需要在配偶区域显示选中标记
const updateSelectionMark = (memberId: string, selected: boolean) => {
// 首先尝试查找主成员节点
let nodeElement = document.querySelector(`[data-member-id="${memberId}"]`) as HTMLElement
let isSpouse = false
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 (!nodeElement) {
const spouseArea = document.querySelector(`[data-spouse-id="${memberId}"]`) as HTMLElement
if (spouseArea) {
nodeElement = spouseArea
isSpouse = true
}
}
if (!nodeElement) {
console.log('Node element not found for:', memberId)
return
}
console.log('Updating selection mark for:', memberId, 'isSpouse:', isSpouse, 'selected:', selected)
if (selected) {
if (isSpouse) {
// 配偶区域:添加边框和选中标记
nodeElement.style.background = 'rgba(37, 99, 235, 0.1)'
nodeElement.style.borderRadius = '8px'
nodeElement.style.padding = '4px'
nodeElement.style.margin = '-4px'
// 添加打钩标记到配偶区域(右边)
if (!nodeElement.querySelector('.selection-mark')) {
const checkMark = document.createElement('div')
checkMark.className = 'selection-mark'
checkMark.style.cssText = 'position: absolute; top: -6px; right: -6px; z-index: 20;'
checkMark.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="#2563eb" stroke="white" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10" fill="none" stroke="white" stroke-width="2.5"/></svg>`
nodeElement.style.position = 'relative'
nodeElement.appendChild(checkMark)
}
} else {
// 主成员节点:添加边框效果
nodeElement.style.border = '3px solid #2563eb'
nodeElement.style.boxShadow = '0 0 0 3px rgba(37, 99, 235, 0.3), 0 4px 12px rgba(0,0,0,0.12)'
// 添加打钩标记到节点左上角(左边)
if (!nodeElement.querySelector('.selection-mark')) {
const checkMark = document.createElement('div')
checkMark.className = 'selection-mark'
checkMark.style.cssText = 'position: absolute; top: 6px; left: 6px; z-index: 20;'
checkMark.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="#2563eb" stroke="white" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10" fill="none" stroke="white" stroke-width="2.5"/></svg>`
nodeElement.appendChild(checkMark)
}
}
})
} else {
// 移除选中效果
if (isSpouse) {
nodeElement.style.background = ''
nodeElement.style.borderRadius = ''
nodeElement.style.padding = ''
nodeElement.style.margin = ''
} else {
nodeElement.style.border = ''
nodeElement.style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)'
}
const mark = nodeElement.querySelector('.selection-mark')
if (mark) mark.remove()
}
}
// 直接处理选择逻辑
if (selectedMembersRef.current.length === 0) {
updateNodeStyle(id, true)
// 选择第一个成员
updateSelectionMark(id, true)
setSelectedMembers([id])
} else if (selectedMembersRef.current.length === 1) {
const [firstId] = selectedMembersRef.current
if (firstId === id) {
updateNodeStyle(id, false)
// 点击同一个成员,取消选择
updateSelectionMark(id, false)
setSelectedMembers([])
return
}
updateNodeStyle(id, true)
// 选择第二个成员,显示选中标记
updateSelectionMark(id, true)
setSelectedMembers([firstId, id])
const result = calculateRelationship(firstId, id)
const firstMember = treeData.members[firstId]
@@ -255,9 +296,19 @@ export default function TreePage() {
relationship: result
})
setShowRelationDialog(true)
setSelectedMembers([])
setRelationMode(false)
// 选中样式会在对话框关闭时清除
// 延迟清除选中状态 - 使用更可靠的方式清除所有选中标记
setTimeout(() => {
// 清除所有选中标记
document.querySelectorAll('.selection-mark').forEach(mark => mark.remove())
// 重置所有节点的边框样式
document.querySelectorAll('[data-member-id]').forEach(node => {
;(node as HTMLElement).style.border = ''
;(node as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)'
})
setSelectedMembers([])
setRelationMode(false)
}, 300)
}
} else {
console.log('Navigating to member detail')
@@ -421,7 +472,7 @@ export default function TreePage() {
<DropdownMenuTrigger asChild>
<Button variant="outline" className="gap-2 shadow-lg">
<LayoutGrid className="h-4 w-4" />
{viewMode === 'traditional' ? '传统视图' : 'D3家谱图'}
{viewMode === 'traditional' ? '传统族谱' : 'D3族谱'}
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
@@ -435,7 +486,7 @@ export default function TreePage() {
className={viewMode === 'traditional' ? 'bg-accent' : ''}
>
<div className="flex items-center justify-between w-full">
<span></span>
<span></span>
{viewMode === 'traditional' && <span className="text-primary"></span>}
</div>
</DropdownMenuItem>
@@ -448,7 +499,7 @@ export default function TreePage() {
className={viewMode === 'd3-org-chart' ? 'bg-accent' : ''}
>
<div className="flex items-center justify-between w-full">
<span>D3 </span>
<span>D3族谱</span>
{viewMode === 'd3-org-chart' && <span className="text-primary"></span>}
</div>
</DropdownMenuItem>