完成40%

This commit is contained in:
rain
2026-07-23 17:21:27 +08:00
parent f1edc6b533
commit bb6431b319
114 changed files with 10931 additions and 877 deletions
+65 -29
View File
@@ -16,8 +16,8 @@
<view v-if="treeState !== 'loading'" class="tree-toolbar">
<view class="tree-toolbar__title">
<text>汤氏家谱</text>
<text>主支 · 1214 </text>
<text>世系成员</text>
<text>{{ generationRangeLabel }}</text>
</view>
<view class="tree-toolbar__actions">
<text
@@ -177,12 +177,16 @@
<script setup>
import { computed, ref } from "vue";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppLoading from "@/components/AppLoading.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { getGenealogyFixtureAccess, listTreeMemberFixtures } from "@/data/mock.js";
import {
appApi,
createRequestController,
isRequestCancelled,
} from "@/utils/api.js";
import { genealogyContext } from "@/utils/genealogy-context.js";
import { openPage } from "@/utils/navigation.js";
@@ -195,15 +199,14 @@ const centeredTreeScrollLeft = ref(90);
const treeHasDrifted = ref(false);
let ignoreTreeScroll = false;
let recenterTimer = null;
const treeRequestController = createRequestController();
let treeLoadGeneration = 0;
let pageActive = true;
let skipNextShowRefresh = true;
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([]);
const snapToGrid = (value) => Math.ceil(value / GRID_UNIT) * GRID_UNIT;
@@ -218,8 +221,8 @@ const layoutMembers = computed(() => {
.filter((member) => Number(member.generation) === generation)
.sort(
(left, right) =>
Number(left.parentId || 0) - Number(right.parentId || 0) ||
Number(left.id) - Number(right.id),
String(left.parentId || "").localeCompare(String(right.parentId || "")) ||
String(left.id).localeCompare(String(right.id)),
),
]),
);
@@ -275,12 +278,19 @@ const generationRows = computed(() => {
const y = Math.min(...group.map((member) => member.y));
return {
generation,
label: generationMeta[generation]?.label || `${generation}`,
summary: generationMeta[generation]?.summary || `${group.length} 位成员`,
label: `${generation}`,
summary: `${group.length} 位成员`,
y,
};
});
});
const generationRangeLabel = computed(() => {
const rows = generationRows.value;
if (!rows.length) return "世系关系";
const first = rows[0].generation;
const last = rows[rows.length - 1].generation;
return first === last ? `${first}` : `${first}${last}`;
});
const generationBandStyle = (row) => ({
gridRow: `${Math.max(1, Math.round((row.y - 88) / GRID_UNIT) + 1)} / span 12`,
});
@@ -351,7 +361,9 @@ const stateCopy = computed(
})[treeState.value] || {},
);
onLoad((query) => {
const loadTree = async (query = {}) => {
const generation = ++treeLoadGeneration;
treeRequestController.abort();
if (genealogyContext.isCurrentGenealogyInvalidated()) {
genealogyId.value = "";
members.value = [];
@@ -366,15 +378,6 @@ onLoad((query) => {
treeState.value = "empty";
return;
}
const access = getGenealogyFixtureAccess(genealogyId.value);
if (!["owner", "member"].includes(access.accessRole)) {
genealogyContext.invalidateCurrentGenealogyId();
genealogyId.value = "";
members.value = [];
treeState.value = "error";
return;
}
members.value = listTreeMemberFixtures(genealogyId.value);
if (
query.state === "loading" ||
["landscape", "empty", "error"].includes(query.state)
@@ -382,15 +385,45 @@ onLoad((query) => {
treeState.value = query.state;
return;
}
genealogyContext.setCurrentGenealogyId(genealogyId.value);
selected.value =
layoutMembers.value.find(
(item) => String(item.id) === String(query.selectedId),
) || layoutMembers.value[0];
treeState.value = members.value.length ? "tree" : "empty";
try {
const result = await appApi.getTree(genealogyId.value, {
requestController: treeRequestController,
});
if (!pageActive || generation !== treeLoadGeneration) return;
members.value = result;
genealogyContext.setCurrentGenealogyId(genealogyId.value);
selected.value =
layoutMembers.value.find(
(item) => String(item.id) === String(query.selectedId),
) || layoutMembers.value[0] || null;
treeState.value = members.value.length ? "tree" : "empty";
} catch (error) {
if (
!pageActive ||
generation !== treeLoadGeneration ||
isRequestCancelled(error)
) {
return;
}
members.value = [];
selected.value = null;
treeState.value = "error";
}
};
onLoad(loadTree);
onShow(() => {
if (skipNextShowRefresh) {
skipNextShowRefresh = false;
return;
}
if (genealogyId.value) loadTree({ genealogyId: genealogyId.value });
});
onUnload(() => {
pageActive = false;
treeLoadGeneration += 1;
treeRequestController.abort();
if (recenterTimer) clearTimeout(recenterTimer);
});
@@ -443,6 +476,9 @@ const handleStateAction = () => {
"T01",
);
}
if (treeState.value === "error") {
return loadTree({ genealogyId: genealogyId.value });
}
selected.value = layoutMembers.value[0];
treeState.value = "tree";
};