0.5.0.0
This commit is contained in:
+89
-38
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user