完成50%

This commit is contained in:
2026-07-23 08:23:59 +08:00
parent 9b0ad62df4
commit f1edc6b533
218 changed files with 24318 additions and 5514 deletions
+183 -43
View File
@@ -2,18 +2,27 @@
<template>
<view
class="member-page"
:data-current-person-id="personId"
:data-trail-length="memberTrail.length"
:data-trail-index="trailIndex"
:class="{
'member-state--detail': memberState === 'detail',
'member-state--restricted': memberState === 'restricted',
'member-state--error': memberState === 'error',
}"
>
<ModulePageBackground module="tree" />
<view class="member-page__header">
<PageHeader title="成员档案" :action="memberState === 'detail' && canEdit ? '编辑' : ''" @action="toEdit" />
<PageHeader title="成员档案"
:action="canPreviewEdit ? '编辑预览' : ''"
custom-back
@back="requestBack"
@action="toEdit"
/>
</view>
<view class="member-context">
<text>{{ genealogyName }}</text>
<text>{{ memberState === "detail" ? "成员身份与亲属关系" : "请重新选择成员" }}</text>
<text>{{ memberContextDescription }}</text>
</view>
<view class="member-panel">
@@ -28,7 +37,7 @@
</view>
</view>
<view class="member-status-entry" @click="openMemberState(member.status)">
<view class="member-status-entry" @click="openMemberState">
<text>{{ statusLabel }}</text>
<text>{{ statusDescription }}</text>
<text>查看</text>
@@ -41,13 +50,36 @@
<text class="member-section-title member-section-title--relation">亲属关系</text>
<view class="member-relatives">
<view v-for="relative in member.relatives" :key="relative.id" @click="openRelative(relative)">
<view
v-for="relative in member.relatives"
:key="relative.id"
:data-person-id="relative.id"
@click="openRelative(relative.id)"
>
<text>{{ relative.relation }}</text><text>{{ relative.name }}</text>
</view>
<text v-if="!member.relatives.length">尚未记录可查看的亲属</text>
</view>
<view v-if="canEdit" class="member-profile-action" @click="toEdit"><text>完善成员档案</text></view>
<view class="member-record-actions">
<view role="button" aria-label="查看成长日志" @click="toGrowthJournal"><text>成长日志</text></view>
<view role="button" aria-label="查看人生事服务状态" @click="toLifeEvents"><text>人生事待开放</text></view>
</view>
<view v-if="canPreviewEdit" class="member-profile-action" @click="toEdit"><text>制作成员编辑预览</text></view>
</view>
<view v-else-if="memberState === 'restricted' && member" class="member-restricted">
<view class="member-heading">
<view class="member-heading__seal"><text>{{ member.name.slice(0, 1) }}</text></view>
<view>
<text>{{ member.name }}</text>
<text> {{ member.generation }} · {{ member.relation }}</text>
</view>
</view>
<text>{{ restrictedCopy.title }}</text>
<text>{{ restrictedCopy.copy }}</text>
<view class="member-restricted-action" @click="toTree"><text>返回上一位成员</text></view>
</view>
<view v-else class="member-error">
@@ -60,12 +92,18 @@
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { computed, reactive, ref } from "vue";
import { onBackPress, onLoad, onShow } from "@dcloudio/uni-app";
import AppLoading from "@/components/AppLoading.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { genealogyContext } from "@/utils/genealogy-context.js";
import { findTreeMemberPresentationFixture } from "@/data/mock.js";
import {
consumeNavigationResult,
handleBackPress,
openPage,
runBackGuard,
} from "@/utils/navigation.js";
const genealogyId = ref("");
const personId = ref("");
@@ -73,24 +111,9 @@ const memberState = ref("loading");
const member = ref(null);
const errorMessage = ref("");
const genealogyName = ref("汤氏家谱");
const memberTrail = reactive([]);
const trailIndex = ref(-1);
const memberFixtures = {
101: {
id: 101, name: "汤文远", generation: 12, relation: "始祖", branch: "主支", generationName: "文字辈",
birthDate: "1940年3月", years: "1940—2012", birthplace: "河南南阳", status: "deceased", canEdit: true,
relatives: [{ id: 102, name: "汤正国", relation: "长子" }, { id: 103, name: "汤正华", relation: "次子" }],
},
102: {
id: 102, name: "汤正国", generation: 13, relation: "长子", branch: "长房", generationName: "正字辈",
birthDate: "1965年5月", years: "1965—", birthplace: "河南洛阳", status: "privacy", canEdit: true,
relatives: [{ id: 101, name: "汤文远", relation: "父亲" }, { id: 104, name: "汤凯", relation: "长子" }],
},
103: {
id: 103, name: "汤正华", generation: 13, relation: "次子", branch: "二房", generationName: "正字辈",
birthDate: "", years: "资料受限", birthplace: "", status: "forbidden", canEdit: false,
relatives: [{ id: 101, name: "汤文远", relation: "父亲" }],
},
};
const details = computed(() => member.value ? [
{ label: "字辈", value: member.value.generationName },
{ label: "出生日期", value: member.value.status === "forbidden" ? "按权限隐藏" : member.value.birthDate },
@@ -98,39 +121,147 @@ const details = computed(() => member.value ? [
{ label: "祖居地", value: member.value.status === "forbidden" ? "按权限隐藏" : member.value.birthplace },
{ label: "所属支系", value: member.value.branch },
] : []);
const canEdit = computed(() => Boolean(member.value?.canEdit));
const canPreviewEdit = computed(() => memberState.value === "detail");
const restrictedCopy = computed(() =>
member.value?.status === "forbidden"
? {
title: "当前成员档案访问受限",
copy: "页面只保留基础身份,不展示生平、居住地、支系、亲属或编辑入口。",
}
: {
title: "部分成员资料未公开",
copy: "页面只保留姓名、关系与世代,不展示其他资料或操作入口。",
},
);
const statusLabel = computed(() => ({ privacy: "隐私资料", deceased: "离世纪念", forbidden: "访问受限" })[member.value?.status] || "成员状态");
const statusDescription = computed(() => ({
privacy: "部分资料仅向授权成员展示",
deceased: "查看生平保留与纪念资料说明",
forbidden: "当前账号只能查看有限身份信息",
})[member.value?.status] || "查看成员状态说明");
const memberContextDescription = computed(() => ({
loading: "正在读取成员资料",
detail: "成员身份与亲属关系",
restricted: "隐私成员仅展示基础身份",
error: "请重新选择成员",
}[memberState.value] || "请重新选择成员"));
const loadMember = async (nextPersonId) => {
const normalizedPersonId = String(nextPersonId || "");
const fixture = findTreeMemberPresentationFixture(genealogyId.value, normalizedPersonId);
if (!fixture) {
errorMessage.value = "这位成员不存在或已不属于当前家谱。";
return false;
}
const restricted = ["privacy", "forbidden"].includes(fixture.status);
const relatives = restricted ? [] : fixture.relatives
.map((relative) => {
const relativeMember = findTreeMemberPresentationFixture(
genealogyId.value,
relative.personId,
);
return relativeMember
? { id: relativeMember.id, name: relativeMember.name, relation: relative.relation }
: null;
})
.filter(Boolean);
personId.value = normalizedPersonId;
member.value = {
...fixture,
relatives,
};
errorMessage.value = "";
memberState.value = restricted ? "restricted" : "detail";
return true;
};
const initializeMemberTrail = async (initialPersonId) => {
memberTrail.splice(0, memberTrail.length);
trailIndex.value = -1;
const normalizedPersonId = String(initialPersonId || "");
const loaded = await loadMember(normalizedPersonId);
if (!loaded) return false;
memberTrail.splice(0, memberTrail.length, normalizedPersonId);
trailIndex.value = 0;
return true;
};
const openRelative = async (nextPersonId) => {
const normalizedPersonId = String(nextPersonId || "");
if (!normalizedPersonId || normalizedPersonId === personId.value) return false;
// 先读取目标成员,只有真实读取成功后才截断前进历史并推进轨迹;失败时
// 当前成员、当前索引和既有历史全部保持不变。
const loaded = await loadMember(normalizedPersonId);
if (!loaded) return false;
memberTrail.splice(trailIndex.value + 1);
memberTrail.push(normalizedPersonId);
trailIndex.value = memberTrail.length - 1;
return true;
};
const popMemberTrail = async () => {
while (trailIndex.value > 0) {
const targetIndex = trailIndex.value - 1;
const historicalPersonId = memberTrail[targetIndex];
const loaded = await loadMember(historicalPersonId);
if (loaded) {
trailIndex.value = targetIndex;
return true;
}
// 历史成员可能已删除或失去权限。移除失效项后,当前成员在数组中的
// 索引同步左移,再继续寻找更早的有效成员,绝不创建新的 T03 页面。
memberTrail.splice(targetIndex, 1);
trailIndex.value -= 1;
}
return false;
};
const requestBack = () =>
runBackGuard({
internalTrail: trailIndex.value > 0,
"pop-internal-trail": popMemberTrail,
});
onLoad((query) => {
genealogyId.value = query.genealogyId || genealogyContext.getCurrentGenealogyId() || "";
personId.value = query.personId || "";
if (genealogyId.value) genealogyContext.setCurrentGenealogyId(genealogyId.value);
if (query.state === "loading") { memberState.value = "loading"; return; }
if (query.state === "error" || !personId.value || !memberFixtures[personId.value]) {
genealogyId.value = String(query.genealogyId || "");
const initialPersonId = String(query.personId || "");
if (query.state === "loading") return;
if (query.state === "error" || !genealogyId.value || !initialPersonId) {
memberState.value = "error";
errorMessage.value = !personId.value ? "没有指定成员,请从世系树重新选择。" : "这位成员不存在或已不属于当前家谱。";
errorMessage.value = !initialPersonId
? "没有指定成员,请从世系树重新选择。"
: "当前家谱上下文无效,请重新进入。";
return;
}
member.value = { ...memberFixtures[personId.value] };
memberState.value = "detail";
void initializeMemberTrail(initialPersonId).then((loaded) => {
if (loaded) return;
member.value = null;
memberState.value = "error";
});
});
onShow(() => {
const result = consumeNavigationResult("T03");
if (result?.operation === "member-open-requested" && result.entityId) {
void openRelative(result.entityId);
}
});
onBackPress((event) => handleBackPress(event, requestBack));
const toEdit = () => {
if (!canEdit.value) { openMemberState("forbidden"); return; }
uni.navigateTo({ url: `/pages/tree/t05-edit-member?genealogyId=${genealogyId.value}&personId=${personId.value}` });
if (!canPreviewEdit.value) return false;
return openPage("T05", { genealogyId: genealogyId.value, personId: personId.value }, "T03");
};
const openMemberState = (state) =>
uni.navigateTo({
url: `/pages/tree/t08-member-states?genealogyId=${genealogyId.value}&personId=${personId.value}&state=${state}`,
});
const openRelative = (relative) =>
uni.navigateTo({ url: `/pages/tree/t03-member-profile?genealogyId=${genealogyId.value}&personId=${relative.id}` });
const toTree = () => uni.navigateBack();
const openMemberState = () =>
openPage("T08", { genealogyId: genealogyId.value, personId: personId.value }, "T03");
const toGrowthJournal = () =>
openPage("R08", { genealogyId: genealogyId.value, personId: personId.value }, "T03");
const toLifeEvents = () =>
openPage("R09", { genealogyId: genealogyId.value, personId: personId.value }, "T03");
const toTree = requestBack;
</script>
<style scoped lang="scss">
@@ -160,8 +291,17 @@ const toTree = () => uni.navigateBack();
.member-relatives > view { display: flex; justify-content: space-between; gap: 20rpx; padding: 12rpx 18rpx; color: $ink; font-size: 23rpx; }
.member-relatives > view text:last-child { color: $brand-red; }
.member-relatives > text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.5; }
.member-record-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12rpx; margin-top: 20rpx; }
.member-record-actions > view { @include adaptive.adaptive-scroll-button(secondary); display: flex; min-height: 72rpx; align-items: center; justify-content: center; padding: 8rpx 14rpx; text-align: center; }
.member-record-actions text { color: $brand-red; font-size: 22rpx; font-weight: 700; line-height: 1.35; }
.member-profile-action { @include adaptive.adaptive-scroll-button(primary); display: flex; min-height: 76rpx; align-items: center; justify-content: center; margin-top: 22rpx; }
.member-profile-action text { color: #fff9ed; font-size: 24rpx; font-weight: 700; }
.member-restricted { padding-top: 14rpx; }
.member-restricted > text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.6; text-align: center; }
.member-restricted > text:first-of-type { margin-top: 28rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 31rpx; font-weight: 700; }
.member-restricted > text + text { margin-top: 12rpx; }
.member-restricted-action { @include adaptive.adaptive-scroll-button(secondary); display: flex; min-height: 76rpx; align-items: center; justify-content: center; margin-top: 22rpx; }
.member-restricted-action text { color: $brand-red; font-size: 24rpx; font-weight: 700; }
.member-error { margin-top: 30%; text-align: center; }
.member-error > text { display: block; color: $ink-muted; font-size: 24rpx; line-height: 1.55; }
.member-error > text:nth-child(2) { font-size: 24rpx; }