This commit is contained in:
freedakgmail
2025-12-21 22:42:39 +08:00
parent 5983f13282
commit 5a604e89f9
10 changed files with 117 additions and 18 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -707,6 +707,7 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
"> ">
${sp.birthYear}${sp.deathYear ? `-${sp.deathYear}` : '-'} ${sp.birthYear}${sp.deathYear ? `-${sp.deathYear}` : '-'}
</div> </div>
${sp.ageText ? `<div style="font-size: 9px; color: #000000; margin-top: 2px; font-weight: 500; font-family: serif;">${sp.ageText}</div>` : ''}
` : ''} ` : ''}
</div> </div>
` `
-3
View File
@@ -214,9 +214,6 @@ function PersonCard({
// 是否显示右键菜单(有可添加的选项时才显示) // 是否显示右键菜单(有可添加的选项时才显示)
const hasAddOptions = canEdit && (canAddFather || canAddMother || canAddSpouse || canAddChild) const hasAddOptions = canEdit && (canAddFather || canAddMother || canAddSpouse || canAddChild)
// 调试日志
console.log('PersonCard render:', member.fullName, { canEdit, hasAddOptions, relationMode })
// 庄严正式的中国风配色 // 庄严正式的中国风配色
const isDead = !!member.deathDate const isDead = !!member.deathDate
+61 -11
View File
@@ -52,6 +52,9 @@ export function TreeLayout({
const VERTICAL_GAP = 48 // 3rem = 48px const VERTICAL_GAP = 48 // 3rem = 48px
const CONNECTOR_HEIGHT = 48 // 连接线垂直高度 const CONNECTOR_HEIGHT = 48 // 连接线垂直高度
// 节点注册计数,用于触发连线重新计算
const [nodeRegisteredCount, setNodeRegisteredCount] = useState(0)
// 折叠状态管理:存储被折叠的节点ID // 折叠状态管理:存储被折叠的节点ID
const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(() => new Set()) const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(() => new Set())
@@ -81,6 +84,8 @@ export function TreeLayout({
const handleNodeRef = useCallback((memberId: string, el: HTMLDivElement | null) => { const handleNodeRef = useCallback((memberId: string, el: HTMLDivElement | null) => {
if (el) { if (el) {
nodeRefs.current.set(memberId, el) nodeRefs.current.set(memberId, el)
// 触发连线重新计算
setNodeRegisteredCount(prev => prev + 1)
} else { } else {
nodeRefs.current.delete(memberId) nodeRefs.current.delete(memberId)
} }
@@ -102,10 +107,14 @@ export function TreeLayout({
}, [relationMode, onMemberClick, router]) }, [relationMode, onMemberClick, router])
// 检查节点是否被任何祖先折叠(使用闭包避免递归依赖问题) // 检查节点是否被任何祖先折叠(使用闭包避免递归依赖问题)
// 注意:这个函数用于判断节点是否应该被隐藏,不用于连线计算
const isNodeHiddenByCollapse = useCallback((nodeId: string): boolean => { const isNodeHiddenByCollapse = useCallback((nodeId: string): boolean => {
// 如果没有折叠的节点,直接返回 false
if (collapsedNodes.size === 0) return false
const checkHidden = (id: string): boolean => { const checkHidden = (id: string): boolean => {
const member = getMember(id) const member = getMember(id)
if (!member) return true if (!member) return false // 找不到成员时返回 false,不隐藏
// 检查父节点是否被折叠 // 检查父节点是否被折叠
const parentId = member.fatherId || member.motherId const parentId = member.fatherId || member.motherId
@@ -123,26 +132,54 @@ export function TreeLayout({
const calculateConnections = useCallback(() => { const calculateConnections = useCallback(() => {
const newConnections: Connection[] = [] const newConnections: Connection[] = []
const containerRect = containerRef.current?.getBoundingClientRect() const containerRect = containerRef.current?.getBoundingClientRect()
if (!containerRect) return if (!containerRect) {
return
}
// 检查是否有足够的节点已注册 // 检查是否有足够的节点已注册
if (nodeRefs.current.size === 0) return if (nodeRefs.current.size === 0) {
return
}
let processedCount = 0
let skippedNoChildren = 0
let skippedCollapsed = 0
let skippedHidden = 0
let skippedNotInDom = 0
let skippedZeroSize = 0
nodeRefs.current.forEach((nodeElement, nodeId) => { nodeRefs.current.forEach((nodeElement, nodeId) => {
const member = getMember(nodeId) const member = getMember(nodeId)
if (!member || !member.childrenIds || member.childrenIds.length === 0) return if (!member || !member.childrenIds || member.childrenIds.length === 0) {
skippedNoChildren++
return
}
// 如果该节点被折叠,不绘制到子节点的连线 // 如果该节点被折叠,不绘制到子节点的连线
if (collapsedNodes.has(nodeId)) return if (collapsedNodes.has(nodeId)) {
skippedCollapsed++
return
}
// 如果该节点被祖先折叠隐藏,跳过 // 如果该节点被祖先折叠隐藏,跳过
if (isNodeHiddenByCollapse(nodeId)) return if (isNodeHiddenByCollapse(nodeId)) {
skippedHidden++
return
}
// 检查元素是否仍在 DOM 中且可见 // 检查元素是否仍在 DOM 中且可见
if (!document.body.contains(nodeElement)) return if (!document.body.contains(nodeElement)) {
skippedNotInDom++
return
}
const parentRect = nodeElement.getBoundingClientRect() const parentRect = nodeElement.getBoundingClientRect()
// 如果元素不可见(宽高为0),跳过 // 如果元素不可见(宽高为0),跳过
if (parentRect.width === 0 || parentRect.height === 0) return if (parentRect.width === 0 || parentRect.height === 0) {
skippedZeroSize++
return
}
processedCount++
// 考虑缩放因素:getBoundingClientRect 返回的是缩放后的尺寸 // 考虑缩放因素:getBoundingClientRect 返回的是缩放后的尺寸
// 需要除以 scale 来获取实际的逻辑位置 // 需要除以 scale 来获取实际的逻辑位置
@@ -209,6 +246,8 @@ export function TreeLayout({
timers.push(setTimeout(calculateConnections, 50)) timers.push(setTimeout(calculateConnections, 50))
timers.push(setTimeout(calculateConnections, 150)) timers.push(setTimeout(calculateConnections, 150))
timers.push(setTimeout(calculateConnections, 300)) timers.push(setTimeout(calculateConnections, 300))
timers.push(setTimeout(calculateConnections, 500))
timers.push(setTimeout(calculateConnections, 1000))
return () => timers.forEach(timer => clearTimeout(timer)) return () => timers.forEach(timer => clearTimeout(timer))
}, [collapsedNodes, calculateConnections]) }, [collapsedNodes, calculateConnections])
@@ -217,13 +256,24 @@ export function TreeLayout({
calculateConnections() calculateConnections()
}, [scale, calculateConnections]) }, [scale, calculateConnections])
// 节点注册变化时重新计算连线(防抖)
useEffect(() => {
if (nodeRegisteredCount === 0) return
const timer = setTimeout(calculateConnections, 100)
return () => clearTimeout(timer)
}, [nodeRegisteredCount, calculateConnections])
useEffect(() => { useEffect(() => {
// 多次尝试计算,确保 DOM 完全渲染 // 多次尝试计算,确保 DOM 完全渲染
// 对于大型族谱(100+人),需要更长的延迟
const timers: NodeJS.Timeout[] = [] const timers: NodeJS.Timeout[] = []
timers.push(setTimeout(calculateConnections, 50)) timers.push(setTimeout(calculateConnections, 100))
timers.push(setTimeout(calculateConnections, 150))
timers.push(setTimeout(calculateConnections, 300)) timers.push(setTimeout(calculateConnections, 300))
timers.push(setTimeout(calculateConnections, 500)) timers.push(setTimeout(calculateConnections, 500))
timers.push(setTimeout(calculateConnections, 1000))
timers.push(setTimeout(calculateConnections, 2000))
timers.push(setTimeout(calculateConnections, 3000))
timers.push(setTimeout(calculateConnections, 5000))
// 监听窗口大小变化 // 监听窗口大小变化
window.addEventListener('resize', calculateConnections) window.addEventListener('resize', calculateConnections)
@@ -395,7 +445,7 @@ export function TreeLayout({
top: `${top}px`, top: `${top}px`,
width: `${width}px`, width: `${width}px`,
height: `${height}px`, height: `${height}px`,
backgroundColor: 'rgba(180, 83, 9, 0.7)', // amber-700 with 70% opacity backgroundColor: 'rgba(180, 83, 9, 0.7)',
transform: isHorizontal ? 'scaleY(0.5)' : 'scaleX(0.5)', transform: isHorizontal ? 'scaleY(0.5)' : 'scaleX(0.5)',
transformOrigin: 'top left', transformOrigin: 'top left',
}} }}
+1
View File
@@ -191,6 +191,7 @@ rsync -avz --progress --delete \
--exclude 'deploy-manual.sh' \ --exclude 'deploy-manual.sh' \
--exclude 'setup-ssh-key.sh' \ --exclude 'setup-ssh-key.sh' \
--exclude '*.tar.gz' \ --exclude '*.tar.gz' \
--exclude 'public/uploads' \
-e "ssh" \ -e "ssh" \
./ $SSH_HOST:$REMOTE_DIR/ ./ $SSH_HOST:$REMOTE_DIR/
+1 -1
View File
@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts"; import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Generated Vendored
+1 -1
View File
@@ -762,7 +762,7 @@ prunedAt: Sun, 21 Dec 2025 04:00:03 GMT
publicHoistPattern: [] publicHoistPattern: []
registries: registries:
'@jsr': https://npm.jsr.io/ '@jsr': https://npm.jsr.io/
default: https://registry.npmmirror.com/ default: https://registry.npmjs.org/
skipped: skipped:
- '@emnapi/runtime@1.7.1' - '@emnapi/runtime@1.7.1'
- '@esbuild/aix-ppc64@0.25.12' - '@esbuild/aix-ppc64@0.25.12'
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"lastValidatedTimestamp": 1766317151581, "lastValidatedTimestamp": 1766327831114,
"projects": {}, "projects": {},
"pnpmfiles": [], "pnpmfiles": [],
"settings": { "settings": {
+49
View File
@@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDNOR+75yVy/k2dDi5vK7ZMYlo8a/z4j39Di3QTpTXAgZap5HY80bh49m9EdCWxFwGU03V1r/G8IuTBj2HHlJWeARJzRzjnVBavgqUqDNyPcvMheJ7U1hiwrG6NUu3Lmk4xJTYVaWuGVDyjtbyE5Domv6xvdxnt1Hx1pUaBVZxoi+NcOwxy4+nolE5wgADncdjF8mNjngxrDtZlLXi7IrHL1x1OsYiEnD6LrGX7+mxnWjRMcV7EyipsGwVggM1/FdoOC2tdUEiVIGWWw9lS/lDEZUfuiO5c8Xxq4Q2nhsane7U4+gmbh38fGy0FEK189wLFoodgsGHw6S36a8ivpQfkG8/3ayi47I47iprlBtnrwcJX0Jj9VrMWL0ed8tnnDnddqq1vRkGG2U+l8qjHs2lOHW48xkjWKfeL70bd2jPuoXc0PMlUMIji2lLCK7Eviwl9/x9+VyznwN4D2iWmd+mBL23hdS5kQYJIKFTQyZOzW3W9AdtltB3bCWh+9L354W+HFM6krf7SKFG9b5rETRR6zAnMSirSVk8BwYllBXmnfcRG/tWS0TvwFJfcNAkEEVqat9W+SOf3f9HK2twrMX3hd/iSFqd/GJI+4Spqns7z8yZoKhsM/airrXE/Ho8K4DvBxEgS43e2d3GnjehK+QmOjrpg9iqJSxwdzXFmFeRp4Q== freedak@Mac-mini.local