This commit is contained in:
freedakgmail
2025-11-30 22:04:36 +08:00
parent 8a1e03d616
commit dd2f67073b
134 changed files with 322 additions and 346 deletions
+50 -50
View File
@@ -14,6 +14,7 @@ import { useFamily } from "@/context/family-context"
declare global {
interface Window {
d3ChartContextMenu?: (event: MouseEvent, memberId: string) => void
d3ChartSpouseClick?: (event: Event, spouseId: string) => void
}
}
@@ -346,6 +347,7 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
// 检查是否被选中
const isSelected = selectedMembers.includes(node.id)
const isSpouseSelected = node.spouseId && selectedMembers.includes(node.spouseId)
// 动态宽度:有配偶时加宽,与 OrgChart 配置保持一致
const width = hasSpouse ? 260 : 160
@@ -455,8 +457,8 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
<div
class="spouse-area"
data-spouse-id="${node.spouseId}"
style="display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; cursor: pointer;"
onclick="event.stopPropagation()"
style="display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; cursor: pointer; position: relative;"
onclick="event.stopPropagation(); window.d3ChartSpouseClick && window.d3ChartSpouseClick(event, '${node.spouseId}')"
>
<div style="
width: 50px;
@@ -521,41 +523,34 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
.render()
.expandAll() // 初始化时展开所有节点
// 添加配偶区域点击事件监听
setTimeout(() => {
const spouseAreas = chartRef.current?.querySelectorAll('.spouse-area')
spouseAreas?.forEach((area) => {
area.addEventListener('click', (e) => {
e.stopPropagation() // 阻止事件冒泡到节点
const spouseId = (area as HTMLElement).getAttribute('data-spouse-id')
if (spouseId && onMemberClick) {
onMemberClick(spouseId)
}
})
})
// 注册全局配偶点击处理函数
window.d3ChartSpouseClick = (event: Event, spouseId: string) => {
event.stopPropagation()
if (spouseId && onMemberClick) {
onMemberClick(spouseId)
}
}
// 添加右键菜单支持
// 直接为节点添加右键事件监听
setTimeout(() => {
const nodes = chartRef.current?.querySelectorAll('[data-member-id]')
console.log('Found nodes for context menu:', nodes?.length)
nodes?.forEach((node) => {
const memberId = (node as HTMLElement).getAttribute('data-member-id')
if (memberId) {
node.addEventListener('contextmenu', (e) => {
e.preventDefault()
const mouseEvent = e as MouseEvent
const member = members[memberId]
if (member && !relationMode) {
console.log('Right click on member:', member.fullName, 'at', mouseEvent.clientX, mouseEvent.clientY)
setContextMenuNode(member)
setContextMenuPosition({ x: mouseEvent.clientX, y: mouseEvent.clientY })
}
})
}
})
}, 200)
}, 100)
// 添加右键菜单支持
setTimeout(() => {
const nodes = chartRef.current?.querySelectorAll('[data-member-id]')
console.log('Found nodes for context menu:', nodes?.length)
nodes?.forEach((node) => {
const memberId = (node as HTMLElement).getAttribute('data-member-id')
if (memberId) {
node.addEventListener('contextmenu', (e) => {
e.preventDefault()
const mouseEvent = e as MouseEvent
const member = members[memberId]
if (member && !relationMode) {
console.log('Right click on member:', member.fullName, 'at', mouseEvent.clientX, mouseEvent.clientY)
setContextMenuNode(member)
setContextMenuPosition({ x: mouseEvent.clientX, y: mouseEvent.clientY })
}
})
}
})
}, 200)
chartInstanceRef.current = chart
setIsReady(true)
@@ -569,6 +564,9 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
if (window.d3ChartContextMenu) {
delete window.d3ChartContextMenu
}
if (window.d3ChartSpouseClick) {
delete window.d3ChartSpouseClick
}
}
}, [members, rootId, onMemberClick, avatarCache])
@@ -581,28 +579,30 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
const existingMarks = chartRef.current.querySelectorAll('.selection-mark')
existingMarks.forEach(mark => mark.remove())
// 重置所有节点的边框样式
const allNodes = chartRef.current.querySelectorAll('[data-member-id]')
allNodes.forEach(node => {
;(node as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)'
})
// 为选中的成员添加选中标记
// 为选中的成员添加选中标记(只打钩,不改变边框和背景)
selectedMembers.forEach(memberId => {
console.log('Marking member as selected:', memberId)
// 先尝试查找主成员节点
const nodeElement = chartRef.current?.querySelector(`[data-member-id="${memberId}"]`)
console.log('Found node element:', nodeElement)
// 再尝试查找配偶区域
const spouseElement = chartRef.current?.querySelector(`[data-spouse-id="${memberId}"]`)
if (nodeElement) {
// 添加选中边框效果
;(nodeElement as HTMLElement).style.border = '3px solid #2563eb'
;(nodeElement as HTMLElement).style.boxShadow = '0 0 0 3px rgba(37, 99, 235, 0.3), 0 4px 12px rgba(0,0,0,0.12)'
// 添加打钩标记
// 主成员:只添加打钩标记(左边)
const checkMark = document.createElement('div')
checkMark.className = 'selection-mark'
checkMark.style.cssText = 'position: absolute; top: 6px; right: 6px; z-index: 20;'
checkMark.style.cssText = 'position: absolute; top: 6px; left: 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" stroke-linecap="round" stroke-linejoin="round"><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 (spouseElement) {
// 配偶:只添加打钩标记
const checkMark = document.createElement('div')
checkMark.className = 'selection-mark'
checkMark.style.cssText = 'position: absolute; top: -5px; right: -5px; 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" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10" fill="none" stroke="white" stroke-width="2.5"/></svg>`
spouseElement.appendChild(checkMark)
}
})
}, [selectedMembers, isReady])
+14 -10
View File
@@ -192,9 +192,7 @@ function PersonCard({
"border-[3px] rounded-none",
"shadow-lg hover:shadow-xl",
// 根据状态设置样式
isSelected
? "border-blue-600 bg-blue-50 dark:bg-blue-950/50 ring-2 ring-blue-400"
: isHighlighted
isHighlighted
? "border-yellow-600 bg-yellow-50/90 dark:bg-yellow-950/50 ring-2 ring-yellow-400 animate-pulse"
: isFounder || isRoot
? "border-amber-500 bg-gradient-to-b from-amber-100 via-yellow-50 to-amber-100 dark:from-amber-900/40 dark:to-yellow-900/20 shadow-xl ring-1 ring-amber-300"
@@ -205,15 +203,14 @@ function PersonCard({
: isMale
? "border-amber-800 bg-gradient-to-b from-amber-50 via-orange-50/30 to-amber-50/80 dark:from-amber-950/40 dark:to-orange-950/20"
: "border-rose-800 bg-gradient-to-b from-rose-50 via-pink-50/30 to-rose-50/80 dark:from-rose-950/40 dark:to-pink-950/20",
relationMode && "hover:border-blue-600 hover:shadow-xl"
relationMode && "hover:shadow-xl"
)}
onClick={() => onSelect?.(member)}
>
{/* 内边框装饰 */}
<div className={cn(
"absolute inset-[3px] border pointer-events-none",
isSelected ? "border-blue-400/50"
: isHighlighted ? "border-yellow-500/50"
isHighlighted ? "border-yellow-500/50"
: isFounder || isRoot ? "border-amber-400/50"
: isSpouse ? "border-stone-400/30"
: isDead ? "border-stone-400/40"
@@ -224,21 +221,28 @@ function PersonCard({
{/* 四角装饰 - 更精致的角花 */}
<div className={cn(
"absolute top-0 left-0 w-4 h-4 border-t-[3px] border-l-[3px]",
isSelected ? "border-blue-600" : isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
)} />
<div className={cn(
"absolute top-0 right-0 w-4 h-4 border-t-[3px] border-r-[3px]",
isSelected ? "border-blue-600" : isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
)} />
<div className={cn(
"absolute bottom-0 left-0 w-4 h-4 border-b-[3px] border-l-[3px]",
isSelected ? "border-blue-600" : isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
)} />
<div className={cn(
"absolute bottom-0 right-0 w-4 h-4 border-b-[3px] border-r-[3px]",
isSelected ? "border-blue-600" : isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
isHighlighted ? "border-yellow-600" : isFounder || isRoot ? "border-amber-500" : isSpouse ? "border-stone-500" : isDead ? "border-stone-500" : isMale ? "border-amber-800" : "border-rose-800"
)} />
{/* 选中打钩标记 */}
{isSelected && (
<div className="absolute top-1 left-1 z-20">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="#2563eb" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10" fill="none" stroke="white" strokeWidth="2.5"/></svg>
</div>
)}
{/* 世代标签 - 庄严印章风格 */}
<div className={cn(
"absolute -top-3 px-3 py-1 text-[11px] font-serif font-bold tracking-wider",
+1
View File
@@ -66,6 +66,7 @@ function DialogContent({
className,
)}
{...props}
aria-describedby={props['aria-describedby'] ?? undefined}
>
{children}
{showCloseButton && (