ow
This commit is contained in:
1 parent
8285fb4090
commit
8a13dfb836
6 files changed
+740
-25
No files matched your search
@@ -1,8 +1,18 @@
|
||||
<template>
|
||||
<dragModal v-model:open="visibleModel" width="85%" :title="title" :mask-closable="false" :fullscreen="fullscreen"
|
||||
@cancel="onCancel">
|
||||
<div ref="containerRef" v-if="hasPerson" class="family-chart-container"></div>
|
||||
<div v-if="hasPerson" class="family-chart-wrap">
|
||||
<div ref="containerRef" class="family-chart-container"></div>
|
||||
<div class="ftc-zoom-controls">
|
||||
<button class="ftc-zoom-btn" title="放大" @click="zoomBy(ZOOM_STEP)">+</button>
|
||||
<button class="ftc-zoom-btn" title="缩小" @click="zoomBy(-ZOOM_STEP)">-</button>
|
||||
<button class="ftc-zoom-btn" title="适应画布" @click="fitGraph">⤢</button>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-else description="暂无当前族员数据" />
|
||||
<template #footer>
|
||||
<a-button type="primary" @click="onCancel">关闭</a-button>
|
||||
</template>
|
||||
</dragModal>
|
||||
</template>
|
||||
|
||||
@@ -81,7 +91,7 @@ const renderCard = (d: any) => {
|
||||
: "";
|
||||
const clickable = data.hasChildren || !data.loaded ? "ftc-clickable" : "";
|
||||
return `
|
||||
<div class="ftc-card ${clickable}" style="border-top: 3px solid ${theme};">
|
||||
<div class="ftc-card ftc-card-lg ${clickable}" style="border-top: 3px solid ${theme};">
|
||||
<div class="ftc-head">
|
||||
<span class="ftc-ico" style="color:${theme};">${ico}</span>
|
||||
<span class="ftc-name" title="${esc(data.name)}">${esc(data.name)}</span>
|
||||
@@ -94,6 +104,9 @@ const renderCard = (d: any) => {
|
||||
</div>`;
|
||||
};
|
||||
|
||||
/** 同级(配偶/孩子)按 Orderby 升序排列 */
|
||||
const byOrder = (a: any, b: any) => (a.Orderby ?? 0) - (b.Orderby ?? 0);
|
||||
|
||||
/** 每个族员的描述:人员信息 + 配偶 + 孩子(根来自 data,其他来自 loadedNodes) */
|
||||
const describePerson = (personId: string): any => {
|
||||
if (personId === rootPersonId.value) {
|
||||
@@ -101,8 +114,8 @@ const describePerson = (personId: string): any => {
|
||||
if (!dp?.FamilyPerson) return null;
|
||||
return {
|
||||
person: dp.FamilyPerson,
|
||||
spouses: dp.FamilyTreeSpouseMaps ?? [],
|
||||
children: dp.FamilyTreeChildrenMaps ?? [],
|
||||
spouses: [...(dp.FamilyTreeSpouseMaps ?? [])].sort(byOrder),
|
||||
children: [...(dp.FamilyTreeChildrenMaps ?? [])].sort(byOrder),
|
||||
loaded: true,
|
||||
};
|
||||
}
|
||||
@@ -110,8 +123,8 @@ const describePerson = (personId: string): any => {
|
||||
if (loaded) {
|
||||
return {
|
||||
person: loaded.person,
|
||||
spouses: loaded.spouses ?? [],
|
||||
children: loaded.children ?? [],
|
||||
spouses: [...(loaded.spouses ?? [])].sort(byOrder),
|
||||
children: [...(loaded.children ?? [])].sort(byOrder),
|
||||
loaded: true,
|
||||
};
|
||||
}
|
||||
@@ -125,7 +138,13 @@ const buildGraphData = () => {
|
||||
const root = rootPersonId.value;
|
||||
if (!root) return { nodes, edges };
|
||||
|
||||
const Y_STEP = 116;
|
||||
const CARD_WIDTH = 360;
|
||||
const CARD_HEIGHT = 192;
|
||||
const H_GAP = 60;
|
||||
const V_GAP = 40;
|
||||
const X_STEP = CARD_WIDTH + H_GAP; // 420,避免水平重叠
|
||||
const Y_STEP = CARD_HEIGHT + V_GAP; // 232,避免垂直重叠
|
||||
const SPOUSE_GAP = X_STEP;
|
||||
let cursor = 0;
|
||||
|
||||
const walk = (personId: string, depth: number, xBase: number) => {
|
||||
@@ -156,13 +175,12 @@ const buildGraphData = () => {
|
||||
},
|
||||
});
|
||||
|
||||
// 配偶与本人同一行,水平间隔 200 对齐
|
||||
const SPOUSE_GAP = 200;
|
||||
// 配偶与本人同一行,水平间隔 X_STEP 对齐
|
||||
desc.spouses.forEach((s: any, i: number) => {
|
||||
const sid = "s-" + personId + "-" + i;
|
||||
nodes.push({
|
||||
id: sid,
|
||||
style: { x: x + 210 + i * SPOUSE_GAP, y },
|
||||
style: { x: xBase + X_STEP + i * SPOUSE_GAP, y },
|
||||
data: {
|
||||
kind: "spouse",
|
||||
tag: "配偶",
|
||||
@@ -177,8 +195,8 @@ const buildGraphData = () => {
|
||||
});
|
||||
|
||||
if (expanded) {
|
||||
// 孩子列与本人第一行的配偶列对齐(x + 210),即孩子位于配偶正下方,竖直跟第一行对齐
|
||||
const childBaseX = x + 210;
|
||||
// 孩子列与本人第一行的配偶列对齐(xBase + X_STEP),即孩子位于配偶正下方,竖直跟第一行对齐
|
||||
const childBaseX = xBase + X_STEP;
|
||||
|
||||
desc.children.forEach((c: any) => {
|
||||
const childId = c.ChildrenId;
|
||||
@@ -193,7 +211,7 @@ const buildGraphData = () => {
|
||||
personId: childId,
|
||||
tag: c.RelationType ? relationLabel(c.RelationType) : "子女",
|
||||
name: (c.ChildrenSurname ?? "") + (c.ChildrenGenealogyName ?? ""),
|
||||
commonName: c.CommonName,
|
||||
commonName: c.ChildrenCommonName,
|
||||
gender: c.Gender,
|
||||
hasChildren: false,
|
||||
expanded: false,
|
||||
@@ -214,6 +232,28 @@ const buildGraphData = () => {
|
||||
const containerRef = ref<HTMLDivElement>();
|
||||
let graph: any = null;
|
||||
|
||||
/** 卡片最小显示比例:世代过多时 fitView 会把整图缩得很小,卡片将看不清,低于该比例时放大 */
|
||||
const MIN_ZOOM = 0.4;
|
||||
|
||||
/** 缩放按钮单次增量 */
|
||||
const ZOOM_STEP = 0.1;
|
||||
|
||||
/** 单击按钮放大/缩小一点点(按增量叠加并限制在 zoomRange 内) */
|
||||
const zoomBy = (delta: number) => {
|
||||
if (!graph) return;
|
||||
const next = graph.getZoom() + delta;
|
||||
graph.zoomTo(Math.min(Math.max(next, MIN_ZOOM), 10));
|
||||
};
|
||||
|
||||
/** 还原为适配整个画布 */
|
||||
const fitGraph = async () => {
|
||||
if (!graph) return;
|
||||
await graph.fitView();
|
||||
if (graph.getZoom() < MIN_ZOOM) {
|
||||
await graph.zoomTo(MIN_ZOOM);
|
||||
}
|
||||
};
|
||||
|
||||
const destroyGraph = () => {
|
||||
if (graph) {
|
||||
graph.destroy();
|
||||
@@ -233,14 +273,16 @@ const initGraph = async () => {
|
||||
|
||||
graph = new Graph({
|
||||
container: containerRef.value,
|
||||
autoFit: "view",
|
||||
// 内容小于视口时不缩放(卡片保持原始大小),内容溢出时才适配
|
||||
autoFit: { type: "view", options: { when: "overflow", direction: "both" } },
|
||||
zoomRange: [MIN_ZOOM, 10],
|
||||
data: { nodes, edges },
|
||||
node: {
|
||||
type: "html",
|
||||
style: {
|
||||
size: [180, 96],
|
||||
dx: -90,
|
||||
dy: -48,
|
||||
size: [360, 192],
|
||||
dx: -180,
|
||||
dy: -96,
|
||||
// 官方文档推荐函数形式,类型声明仅支持 string/HTMLElement,故断言
|
||||
innerHTML: renderCard as any,
|
||||
},
|
||||
@@ -276,6 +318,10 @@ const initGraph = async () => {
|
||||
}
|
||||
});
|
||||
await graph.render();
|
||||
// 世代过多导致 fitView 缩得太小时,放大到最小显示比例,保证卡片可读
|
||||
if (graph.getZoom() < MIN_ZOOM) {
|
||||
await graph.zoomTo(MIN_ZOOM);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpanded = (personId: string) => {
|
||||
@@ -322,20 +368,61 @@ watch(
|
||||
|
||||
onBeforeUnmount(() => destroyGraph());
|
||||
|
||||
const onCancel = () => emit("cancel");
|
||||
const onCancel = () => {
|
||||
visibleModel.value = false;
|
||||
emit("cancel");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.family-chart-wrap {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 480px;
|
||||
}
|
||||
|
||||
.family-chart-container {
|
||||
width: 100%;
|
||||
height: 72vh;
|
||||
min-height: 480px;
|
||||
height: 100%;
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 画布缩放按钮组(放大/缩小/适应) */
|
||||
.ftc-zoom-controls {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ftc-zoom-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.ftc-zoom-btn:hover {
|
||||
color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
/* G6 HTML 节点卡片样式(注入到 canvas 容器,使用独立前缀避免污染全局) */
|
||||
.ftc-card {
|
||||
box-sizing: border-box;
|
||||
@@ -351,6 +438,29 @@ const onCancel = () => emit("cancel");
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 谱系图大卡片(节点尺寸放大 2 倍,内部文字同步放大) */
|
||||
.ftc-card-lg {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.ftc-card-lg .ftc-ico {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.ftc-card-lg .ftc-name {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ftc-card-lg .ftc-sub {
|
||||
font-size: 15px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.ftc-card-lg .ftc-tag {
|
||||
font-size: 13px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.ftc-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in new issue
Block a user