修改完成
This commit is contained in:
+220
-157
@@ -25,13 +25,20 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tree-stage">
|
||||
<view v-if="treeState === 'tree'" class="generation-rail">
|
||||
<view
|
||||
class="tree-stage"
|
||||
:class="{ 'tree-stage--lineage': treeState === 'tree' }"
|
||||
>
|
||||
<view
|
||||
v-if="treeState === 'tree'"
|
||||
class="generation-rail"
|
||||
:style="generationRailStyle"
|
||||
>
|
||||
<view
|
||||
v-for="row in generationRows"
|
||||
:key="row.generation"
|
||||
class="generation-band"
|
||||
:style="generationRowStyle(row)"
|
||||
:style="generationBandStyle(row)"
|
||||
>
|
||||
<image
|
||||
src="/static/assets/modules/genealogy/transparent/section-divider.png"
|
||||
@@ -50,10 +57,12 @@
|
||||
scroll-x
|
||||
:scroll-left="initialScrollLeft"
|
||||
:show-scrollbar="false"
|
||||
:style="treeScrollStyle"
|
||||
>
|
||||
<view
|
||||
class="tree-canvas"
|
||||
:class="{ 'tree-canvas--state': treeState !== 'tree' }"
|
||||
:style="treeMetricsStyle"
|
||||
>
|
||||
<AppLoading
|
||||
v-if="treeState === 'loading'"
|
||||
@@ -66,24 +75,19 @@
|
||||
<text>同代分支可左右查看</text>
|
||||
</view>
|
||||
|
||||
<view class="lineage-connector lineage-connector--root" />
|
||||
<view class="lineage-connector lineage-connector--root-branch" />
|
||||
<view class="lineage-connector lineage-connector--gen13-left" />
|
||||
<view class="lineage-connector lineage-connector--gen13-right" />
|
||||
<view class="lineage-connector lineage-connector--left-trunk" />
|
||||
<view class="lineage-connector lineage-connector--left-branch" />
|
||||
<view class="lineage-connector lineage-connector--gen14-left" />
|
||||
<view class="lineage-connector lineage-connector--gen14-middle" />
|
||||
<view class="lineage-connector lineage-connector--right-trunk" />
|
||||
<view class="lineage-connector lineage-connector--right-branch" />
|
||||
<view class="lineage-connector lineage-connector--gen14-right" />
|
||||
<view
|
||||
v-for="connector in lineageConnectors"
|
||||
:key="connector.id"
|
||||
class="lineage-connector"
|
||||
:style="connector.style"
|
||||
/>
|
||||
|
||||
<view
|
||||
v-for="member in members"
|
||||
v-for="member in layoutMembers"
|
||||
:key="member.id"
|
||||
class="member-node"
|
||||
:class="{ 'member-node--selected': selected?.id === member.id }"
|
||||
:style="nodeStyle(member)"
|
||||
:style="nodeGridStyle(member)"
|
||||
@click="selected = member"
|
||||
>
|
||||
<image
|
||||
@@ -95,11 +99,13 @@
|
||||
"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text class="node-name">{{ member.name }}</text>
|
||||
<text class="node-relation"
|
||||
>{{ member.relation }} · {{ member.branch }}</text
|
||||
>
|
||||
<text class="node-years">{{ member.years }}</text>
|
||||
<view class="member-node__copy">
|
||||
<text class="node-name">{{ member.name }}</text>
|
||||
<text class="node-relation"
|
||||
>{{ member.relation }} · {{ member.branch }}</text
|
||||
>
|
||||
<text class="node-years">{{ member.years }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -171,11 +177,15 @@ const genealogyId = ref("");
|
||||
const treeState = ref("loading");
|
||||
const selected = ref(null);
|
||||
const initialScrollLeft = ref(90);
|
||||
const generationRows = [
|
||||
{ generation: 12, label: "第十二世", summary: "始祖", y: 135 },
|
||||
{ generation: 13, label: "第十三世", summary: "两房", y: 350 },
|
||||
{ generation: 14, label: "第十四世", summary: "三支", y: 650 },
|
||||
];
|
||||
const GRID_UNIT = 5;
|
||||
const NODE_HALF_HEIGHT = 47;
|
||||
const MEMBER_GAP = 250;
|
||||
const GENERATION_GAP = 220;
|
||||
const generationMeta = {
|
||||
12: { label: "第十二世", summary: "始祖" },
|
||||
13: { label: "第十三世", summary: "两房" },
|
||||
14: { label: "第十四世", summary: "三支" },
|
||||
};
|
||||
const members = ref([
|
||||
{
|
||||
id: 101,
|
||||
@@ -184,8 +194,6 @@ const members = ref([
|
||||
years: "1940—2012",
|
||||
generation: 12,
|
||||
branch: "主支",
|
||||
x: 450,
|
||||
y: 135,
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
@@ -193,9 +201,8 @@ const members = ref([
|
||||
relation: "长子",
|
||||
years: "1965—",
|
||||
generation: 13,
|
||||
parentId: 101,
|
||||
branch: "长房",
|
||||
x: 275,
|
||||
y: 350,
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
@@ -203,9 +210,8 @@ const members = ref([
|
||||
relation: "次子",
|
||||
years: "1968—",
|
||||
generation: 13,
|
||||
parentId: 101,
|
||||
branch: "二房",
|
||||
x: 625,
|
||||
y: 350,
|
||||
},
|
||||
{
|
||||
id: 104,
|
||||
@@ -213,9 +219,8 @@ const members = ref([
|
||||
relation: "长孙",
|
||||
years: "1992—",
|
||||
generation: 14,
|
||||
parentId: 102,
|
||||
branch: "长房",
|
||||
x: 150,
|
||||
y: 650,
|
||||
},
|
||||
{
|
||||
id: 105,
|
||||
@@ -223,9 +228,8 @@ const members = ref([
|
||||
relation: "长孙女",
|
||||
years: "1995—",
|
||||
generation: 14,
|
||||
parentId: 102,
|
||||
branch: "长房",
|
||||
x: 400,
|
||||
y: 650,
|
||||
},
|
||||
{
|
||||
id: 106,
|
||||
@@ -233,12 +237,132 @@ const members = ref([
|
||||
relation: "次孙",
|
||||
years: "1998—",
|
||||
generation: 14,
|
||||
parentId: 103,
|
||||
branch: "二房",
|
||||
x: 690,
|
||||
y: 650,
|
||||
},
|
||||
]);
|
||||
|
||||
const snapToGrid = (value) => Math.ceil(value / GRID_UNIT) * GRID_UNIT;
|
||||
const layoutMembers = computed(() => {
|
||||
const generations = Array.from(
|
||||
new Set(members.value.map((member) => Number(member.generation))),
|
||||
).sort((left, right) => left - right);
|
||||
const groups = new Map(
|
||||
generations.map((generation) => [
|
||||
generation,
|
||||
members.value
|
||||
.filter((member) => Number(member.generation) === generation)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
Number(left.parentId || 0) - Number(right.parentId || 0) ||
|
||||
Number(left.id) - Number(right.id),
|
||||
),
|
||||
]),
|
||||
);
|
||||
const maxCount = Math.max(1, ...Array.from(groups.values()).map((group) => group.length));
|
||||
const canvasWidth = Math.max(720, maxCount * MEMBER_GAP + 100);
|
||||
return generations.flatMap((generation, generationIndex) => {
|
||||
const group = groups.get(generation) || [];
|
||||
const occupiedWidth = Math.max(0, (group.length - 1) * MEMBER_GAP);
|
||||
const startX = (canvasWidth - occupiedWidth) / 2;
|
||||
return group.map((member, memberIndex) => ({
|
||||
...member,
|
||||
x: snapToGrid(startX + memberIndex * MEMBER_GAP),
|
||||
y: snapToGrid(135 + generationIndex * GENERATION_GAP),
|
||||
}));
|
||||
});
|
||||
});
|
||||
const treeMetrics = computed(() => {
|
||||
const memberList = layoutMembers.value;
|
||||
const maxX = Math.max(0, ...memberList.map((member) => member.x));
|
||||
const maxY = Math.max(0, ...memberList.map((member) => member.y));
|
||||
const width = Math.max(720, snapToGrid(maxX + 210));
|
||||
const height = Math.max(640, snapToGrid(maxY + 250));
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
columns: width / GRID_UNIT,
|
||||
rows: height / GRID_UNIT,
|
||||
};
|
||||
});
|
||||
const treeMetricsStyle = computed(() => ({
|
||||
width: `${treeMetrics.value.width}rpx`,
|
||||
height: `${treeMetrics.value.height}rpx`,
|
||||
gridTemplateColumns: `repeat(${treeMetrics.value.columns}, ${GRID_UNIT}rpx)`,
|
||||
gridTemplateRows: `repeat(${treeMetrics.value.rows}, ${GRID_UNIT}rpx)`,
|
||||
}));
|
||||
const generationRailStyle = computed(() => ({
|
||||
height: `${treeMetrics.value.height}rpx`,
|
||||
gridTemplateRows: `repeat(${treeMetrics.value.rows}, ${GRID_UNIT}rpx)`,
|
||||
}));
|
||||
const treeScrollStyle = computed(() => ({
|
||||
height: `${treeMetrics.value.height}rpx`,
|
||||
}));
|
||||
const generationRows = computed(() => {
|
||||
const groups = new Map();
|
||||
layoutMembers.value.forEach((member) => {
|
||||
const group = groups.get(member.generation) || [];
|
||||
group.push(member);
|
||||
groups.set(member.generation, group);
|
||||
});
|
||||
return Array.from(groups.entries())
|
||||
.sort(([left], [right]) => left - right)
|
||||
.map(([generation, group]) => {
|
||||
const y = Math.min(...group.map((member) => member.y));
|
||||
return {
|
||||
generation,
|
||||
label: generationMeta[generation]?.label || `第 ${generation} 世`,
|
||||
summary: generationMeta[generation]?.summary || `${group.length} 位成员`,
|
||||
y,
|
||||
};
|
||||
});
|
||||
});
|
||||
const generationBandStyle = (row) => ({
|
||||
gridRow: `${Math.max(1, Math.round((row.y - 88) / GRID_UNIT) + 1)} / span 12`,
|
||||
});
|
||||
const lineageConnectors = computed(() => {
|
||||
const memberById = new Map(
|
||||
layoutMembers.value.map((member) => [member.id, member]),
|
||||
);
|
||||
const childrenByParent = new Map();
|
||||
layoutMembers.value.forEach((member) => {
|
||||
if (!member.parentId || !memberById.has(member.parentId)) return;
|
||||
const children = childrenByParent.get(member.parentId) || [];
|
||||
children.push(member);
|
||||
childrenByParent.set(member.parentId, children);
|
||||
});
|
||||
|
||||
const connectors = [];
|
||||
childrenByParent.forEach((children, parentId) => {
|
||||
const parent = memberById.get(parentId);
|
||||
const childTop = Math.min(...children.map((child) => child.y - NODE_HALF_HEIGHT));
|
||||
const parentBottom = parent.y + NODE_HALF_HEIGHT;
|
||||
const branchY = snapToGrid((parentBottom + childTop) / 2);
|
||||
const minX = Math.min(...children.map((child) => child.x));
|
||||
const maxX = Math.max(...children.map((child) => child.x));
|
||||
const verticalStyle = (x, top, bottom) => ({
|
||||
gridColumn: `${Math.round(x / GRID_UNIT) + 1} / span 1`,
|
||||
gridRow: `${Math.round(top / GRID_UNIT) + 1} / ${Math.round(bottom / GRID_UNIT) + 1}`,
|
||||
});
|
||||
connectors.push({
|
||||
id: `${parentId}-trunk`,
|
||||
style: verticalStyle(parent.x, parentBottom, branchY),
|
||||
});
|
||||
connectors.push({
|
||||
id: `${parentId}-branch`,
|
||||
style: {
|
||||
gridColumn: `${Math.round(minX / GRID_UNIT) + 1} / ${Math.round(maxX / GRID_UNIT) + 2}`,
|
||||
gridRow: `${Math.round(branchY / GRID_UNIT) + 1} / span 1`,
|
||||
},
|
||||
});
|
||||
children.forEach((child) => connectors.push({
|
||||
id: `${parentId}-${child.id}`,
|
||||
style: verticalStyle(child.x, branchY, child.y - NODE_HALF_HEIGHT),
|
||||
}));
|
||||
});
|
||||
return connectors;
|
||||
});
|
||||
|
||||
const stateCopy = computed(
|
||||
() =>
|
||||
({
|
||||
@@ -279,18 +403,15 @@ onLoad((query) => {
|
||||
}
|
||||
genealogyContext.setCurrentGenealogyId(genealogyId.value);
|
||||
selected.value =
|
||||
members.value.find(
|
||||
layoutMembers.value.find(
|
||||
(item) => String(item.id) === String(query.selectedId),
|
||||
) || members.value[0];
|
||||
) || layoutMembers.value[0];
|
||||
treeState.value = "tree";
|
||||
});
|
||||
|
||||
const nodeStyle = (member) => ({
|
||||
left: `${member.x}rpx`,
|
||||
top: `${member.y}rpx`,
|
||||
});
|
||||
const generationRowStyle = (row) => ({
|
||||
top: `${row.y - 88}rpx`,
|
||||
const nodeGridStyle = (member) => ({
|
||||
gridColumn: `${member.x / 5 + 1}`,
|
||||
gridRow: `${member.y / 5 + 1}`,
|
||||
});
|
||||
const handleStateAction = () => {
|
||||
if (treeState.value === "empty") {
|
||||
@@ -299,7 +420,7 @@ const handleStateAction = () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
selected.value = members.value[0];
|
||||
selected.value = layoutMembers.value[0];
|
||||
treeState.value = "tree";
|
||||
};
|
||||
const toMember = () =>
|
||||
@@ -322,17 +443,16 @@ const toAddRelative = () =>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tree-page {
|
||||
position: relative;
|
||||
display: grid;
|
||||
height: 100vh;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
background: $paper;
|
||||
}
|
||||
.tree-page__header,
|
||||
.tree-toolbar,
|
||||
.tree-stage,
|
||||
.member-sheet {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
.tree-stage {
|
||||
z-index: 1;
|
||||
}
|
||||
.tree-toolbar {
|
||||
display: flex;
|
||||
@@ -361,8 +481,13 @@ const toAddRelative = () =>
|
||||
font-size: 23rpx;
|
||||
}
|
||||
.tree-stage {
|
||||
position: relative;
|
||||
height: calc(100vh - 272rpx);
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tree-stage--lineage {
|
||||
display: grid;
|
||||
grid-template-columns: 190rpx minmax(0, 1fr);
|
||||
align-items: start;
|
||||
}
|
||||
.tree-scroll {
|
||||
width: 100%;
|
||||
@@ -370,40 +495,37 @@ const toAddRelative = () =>
|
||||
white-space: nowrap;
|
||||
}
|
||||
.generation-rail {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: grid;
|
||||
z-index: 4;
|
||||
width: 190rpx;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-right: 1rpx solid rgba(143, 108, 63, 0.2);
|
||||
}
|
||||
.tree-scroll--lineage {
|
||||
width: calc(100% - 190rpx);
|
||||
margin-left: 190rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
.tree-canvas {
|
||||
position: relative;
|
||||
width: 900rpx;
|
||||
height: 900rpx;
|
||||
display: grid;
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
.tree-canvas--state {
|
||||
display: block;
|
||||
width: calc(100vw - 32rpx);
|
||||
}
|
||||
.lineage-pan-cue {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 22rpx;
|
||||
grid-area: 1 / 1 / -1 / -1;
|
||||
align-self: start;
|
||||
justify-self: end;
|
||||
z-index: 3;
|
||||
margin: 8rpx 22rpx 0 0;
|
||||
color: #9a7748;
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
.generation-band {
|
||||
position: absolute;
|
||||
left: 12rpx;
|
||||
display: grid;
|
||||
justify-self: center;
|
||||
z-index: 3;
|
||||
width: 166rpx;
|
||||
height: 58rpx;
|
||||
@@ -411,14 +533,15 @@ const toAddRelative = () =>
|
||||
font-size: 22rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.generation-band image,
|
||||
.generation-band__copy {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.generation-band image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.generation-band__copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -431,78 +554,15 @@ const toAddRelative = () =>
|
||||
font-size: 19rpx;
|
||||
}
|
||||
.lineage-connector {
|
||||
position: absolute;
|
||||
align-self: stretch;
|
||||
justify-self: stretch;
|
||||
z-index: 1;
|
||||
background: #b78a42;
|
||||
}
|
||||
.lineage-connector--root {
|
||||
left: 449rpx;
|
||||
top: 177rpx;
|
||||
width: 2rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
.lineage-connector--root-branch {
|
||||
left: 275rpx;
|
||||
top: 264rpx;
|
||||
width: 350rpx;
|
||||
height: 2rpx;
|
||||
}
|
||||
.lineage-connector--gen13-left {
|
||||
left: 274rpx;
|
||||
top: 264rpx;
|
||||
width: 2rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
.lineage-connector--gen13-right {
|
||||
left: 624rpx;
|
||||
top: 264rpx;
|
||||
width: 2rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
.lineage-connector--left-trunk {
|
||||
left: 274rpx;
|
||||
top: 392rpx;
|
||||
width: 2rpx;
|
||||
height: 149rpx;
|
||||
}
|
||||
.lineage-connector--left-branch {
|
||||
left: 150rpx;
|
||||
top: 540rpx;
|
||||
width: 250rpx;
|
||||
height: 2rpx;
|
||||
}
|
||||
.lineage-connector--gen14-left {
|
||||
left: 149rpx;
|
||||
top: 540rpx;
|
||||
width: 2rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
.lineage-connector--gen14-middle {
|
||||
left: 399rpx;
|
||||
top: 540rpx;
|
||||
width: 2rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
.lineage-connector--right-trunk {
|
||||
left: 624rpx;
|
||||
top: 392rpx;
|
||||
width: 2rpx;
|
||||
height: 149rpx;
|
||||
}
|
||||
.lineage-connector--right-branch {
|
||||
left: 625rpx;
|
||||
top: 540rpx;
|
||||
width: 65rpx;
|
||||
height: 2rpx;
|
||||
}
|
||||
.lineage-connector--gen14-right {
|
||||
left: 689rpx;
|
||||
top: 540rpx;
|
||||
width: 2rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
.member-node {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
align-self: start;
|
||||
justify-self: start;
|
||||
z-index: 2;
|
||||
width: 224rpx;
|
||||
height: 86rpx;
|
||||
@@ -510,16 +570,19 @@ const toAddRelative = () =>
|
||||
text-align: center;
|
||||
}
|
||||
.member-node__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.member-node__skin,
|
||||
.member-node__copy {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.member-node__copy {
|
||||
z-index: 1;
|
||||
}
|
||||
.node-name,
|
||||
.node-relation,
|
||||
.node-years {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
}
|
||||
.node-name {
|
||||
@@ -547,22 +610,23 @@ const toAddRelative = () =>
|
||||
color: $brand-red;
|
||||
}
|
||||
.tree-state-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
z-index: 2;
|
||||
width: calc(100% - 48rpx);
|
||||
height: 360rpx;
|
||||
min-height: 360rpx;
|
||||
margin: 150rpx 24rpx 0;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
}
|
||||
.tree-state-card__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tree-state-card__skin,
|
||||
.tree-state-card__content {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.tree-state-card__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 62rpx 58rpx 42rpx;
|
||||
}
|
||||
@@ -588,19 +652,20 @@ const toAddRelative = () =>
|
||||
line-height: 1.65;
|
||||
}
|
||||
.tree-state-card__action {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 360rpx;
|
||||
height: 70rpx;
|
||||
min-height: 70rpx;
|
||||
margin: 22rpx auto 0;
|
||||
}
|
||||
.tree-state-card__action image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tree-state-card__action image,
|
||||
.tree-state-card__action text {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.tree-state-card__action text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -616,7 +681,7 @@ const toAddRelative = () =>
|
||||
bottom: calc(14rpx + env(safe-area-inset-bottom));
|
||||
left: 18rpx;
|
||||
display: flex;
|
||||
height: 240rpx;
|
||||
min-height: 240rpx;
|
||||
flex-direction: column;
|
||||
padding: 38rpx 44rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
@@ -629,7 +694,6 @@ const toAddRelative = () =>
|
||||
pointer-events: none;
|
||||
}
|
||||
.member-sheet__copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.sheet-name {
|
||||
@@ -646,7 +710,6 @@ const toAddRelative = () =>
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.sheet-actions {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -667,7 +730,7 @@ const toAddRelative = () =>
|
||||
gap: 14rpx;
|
||||
}
|
||||
.member-sheet {
|
||||
height: 240rpx;
|
||||
min-height: 240rpx;
|
||||
padding-top: 32rpx;
|
||||
padding-right: 36rpx;
|
||||
padding-left: 36rpx;
|
||||
|
||||
Reference in New Issue
Block a user