完成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
+153 -66
View File
@@ -7,6 +7,7 @@
<GenealogyPageBackground />
<PageHeader
root
notice
title="我的家谱"
:unread-count="unreadCount"
@notice="toNotifications"
@@ -44,6 +45,12 @@
</view>
</view>
<view v-else-if="contextInvalidated" class="state-panel state-panel--context">
<text class="state-title">当前家谱已不可用</text>
<text class="state-copy">权限或家谱列表可能已经变化请明确选择仍可访问的家谱</text>
<AppButton block label="选择可用家谱" @click="openSwitcher" />
</view>
<template v-else-if="hasGenealogies">
<view class="genealogy-fixed-zone">
<view class="current-slip" @click="openSwitcher">
@@ -132,6 +139,7 @@
:key="item.id"
:genealogy="item"
role="成员"
:selected="item.id === currentGenealogy.id"
@select="openGenealogy"
/>
</view>
@@ -310,13 +318,15 @@
/>
</view>
<scroll-view class="genealogy-switcher__list" scroll-y>
<view
<button
v-for="item in availableGenealogies"
:key="item.id"
class="switcher-item"
:class="{
'switcher-item--active': item.id === selectedGenealogyId,
}"
:aria-pressed="item.id === selectedGenealogyId"
:aria-label="`${item.name}${item.location}${item.memberCount} 位成员`"
@click="selectGenealogy(item)"
>
<view>
@@ -328,7 +338,7 @@
<text class="switcher-item__state">{{
item.id === selectedGenealogyId ? "当前" : "选择"
}}</text>
</view>
</button>
</scroll-view>
</view>
</view>
@@ -347,21 +357,25 @@ import AppButton from "@/components/AppButton.vue";
import GenealogyCard from "@/components/GenealogyCard.vue";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { genealogies, notifications } from "@/data/mock.js";
import {
findGenealogyFixture,
genealogies,
getGenealogyFixtureAccess,
listNotificationFixtures,
} from "@/data/mock.js";
import { genealogyContext } from "@/utils/genealogy-context.js";
import { handleBackPress, openPage, runBackGuard } from "@/utils/navigation.js";
const isLoading = ref(false);
const hasError = ref(false);
const list = ref(genealogies);
const forceEmptyState = ref(false);
const contextInvalidated = ref(false);
const contextReconcileFailed = ref(false);
const requestedGenealogyId = ref("");
const addDialogVisible = ref(false);
const switcherVisible = ref(false);
const storedGenealogyId = Number(genealogyContext.getCurrentGenealogyId());
const selectedGenealogyId = ref(
genealogies.some((item) => item.id === storedGenealogyId)
? storedGenealogyId
: genealogies[0]?.id || null,
);
const selectedGenealogyId = ref(null);
const listScrollCommand = ref(0);
const currentListScrollTop = ref(0);
@@ -376,41 +390,75 @@ const syncEmptyStateFromRoute = (query = {}) => {
hasError.value = presentationState === "error";
};
onLoad((query) => {
const requestedId = Number(query?.genealogyId);
if (genealogies.some((item) => item.id === requestedId)) {
selectedGenealogyId.value = requestedId;
genealogyContext.setCurrentGenealogyId(requestedId);
} else if (selectedGenealogyId.value) {
genealogyContext.setCurrentGenealogyId(selectedGenealogyId.value);
const reconcilePageGenealogyContext = () => {
try {
const availableIds = list.value.map((item) => String(item.id));
const previousId = genealogyContext.getCurrentGenealogyId();
selectedGenealogyId.value =
genealogyContext.reconcileCurrentGenealogyId(
availableIds,
requestedGenealogyId.value,
) ||
null;
contextReconcileFailed.value = false;
contextInvalidated.value = Boolean(
availableIds.length &&
!selectedGenealogyId.value &&
(requestedGenealogyId.value ||
previousId ||
genealogyContext.isCurrentGenealogyInvalidated()),
);
return true;
} catch {
genealogyContext.invalidateCurrentGenealogyId();
selectedGenealogyId.value = null;
contextReconcileFailed.value = true;
contextInvalidated.value = false;
hasError.value = true;
return false;
}
};
onLoad((query) => {
syncEmptyStateFromRoute(query);
requestedGenealogyId.value = String(query?.genealogyId || "");
reconcilePageGenealogyContext();
});
const unreadCount = computed(
() => notifications.filter((item) => item.unread).length,
() => listNotificationFixtures().filter((item) => item.unread).length,
);
const hasGenealogies = computed(
() => !forceEmptyState.value && list.value.length > 0,
);
const isListLayout = computed(
() => !isLoading.value && !hasError.value && hasGenealogies.value,
() =>
!isLoading.value &&
!hasError.value &&
!contextInvalidated.value &&
hasGenealogies.value,
);
const createdGenealogies = computed(() =>
list.value.filter((item) => item.membership === "created"),
list.value.filter(
(item) => getGenealogyFixtureAccess(item.id).accessRole === "owner",
),
);
const joinedGenealogies = computed(() =>
list.value.filter((item) => item.membership === "joined"),
list.value.filter(
(item) => getGenealogyFixtureAccess(item.id).accessRole === "member",
),
);
const availableGenealogies = computed(() => list.value);
const currentGenealogy = computed(
() =>
availableGenealogies.value.find(
(item) => item.id === selectedGenealogyId.value,
) || availableGenealogies.value[0],
) || null,
);
const isCurrentGenealogyOwner = computed(
() => currentGenealogy.value?.membership === "created",
() =>
getGenealogyFixtureAccess(currentGenealogy.value?.id).accessRole ===
"owner",
);
const currentRoleLabel = computed(() =>
isCurrentGenealogyOwner.value ? "管理员" : "成员",
@@ -420,26 +468,29 @@ const currentRoleLabel = computed(() =>
const applicationRecords = [
{
id: "pending",
name: "汤氏南阳宗谱",
genealogyId: "2003",
statusLabel: "审核中",
tone: "pending",
description: "申请已提交,等待管理员审核",
},
{
id: "rejected",
name: "汤氏清河家谱",
genealogyId: "2004",
statusLabel: "被拒绝",
tone: "rejected",
description: "可修改关系说明后重新申请",
},
{
id: "removed",
name: "汤氏汝南支谱",
genealogyId: "2005",
statusLabel: "已退出",
tone: "muted",
description: "如需恢复成员身份,可重新申请加入",
},
];
].map((record) => ({
...record,
name: findGenealogyFixture(record.genealogyId)?.name || "未知家谱",
}));
const shortcuts = [
{
@@ -469,22 +520,34 @@ const visibleShortcuts = computed(() =>
: shortcuts.filter((item) => item.key !== "applications"),
);
const openGenealogy = (genealogy) => {
genealogyContext.setCurrentGenealogyId(genealogy.id);
uni.navigateTo({
url: `/pages/genealogy/g05-genealogy-overview?genealogyId=${genealogy.id}`,
});
const openGenealogy = (genealogy) =>
openPage("G05", { genealogyId: String(genealogy.id) }, "G01").then(
(opened) => {
if (opened) {
selectedGenealogyId.value = String(genealogy.id);
genealogyContext.setCurrentGenealogyId(genealogy.id);
}
return opened;
},
);
const createGenealogy = () => {
closeAddDialog();
return openPage("G03", {}, "G01");
};
const applyToJoin = () => {
closeAddDialog();
return openPage("G06", {}, "G01");
};
const joinByInvite = () => {
closeAddDialog();
return openPage("G06", { mode: "invite" }, "G01");
};
const toNotifications = () => {
const notificationParams = currentGenealogy.value
? { genealogyId: String(currentGenealogy.value.id) }
: {};
return openPage("N01", notificationParams, "G01");
};
const createGenealogy = () =>
uni.navigateTo({ url: "/pages/genealogy/g03-create-genealogy" });
const applyToJoin = () =>
uni.navigateTo({ url: "/pages/genealogy/g06-search-genealogies" });
const joinByInvite = () =>
uni.navigateTo({
url: "/pages/genealogy/g06-search-genealogies?mode=invite",
});
const toNotifications = () =>
uni.navigateTo({ url: "/pages/notification/n01-message-center" });
const openAddDialog = () => {
addDialogVisible.value = true;
@@ -492,23 +555,25 @@ const openAddDialog = () => {
const closeAddDialog = () => {
addDialogVisible.value = false;
};
onBackPress(() => {
if (switcherVisible.value) {
closeSwitcher();
return true;
}
if (addDialogVisible.value) {
closeAddDialog();
return true;
}
return false;
});
const openSwitcher = () => {
switcherVisible.value = true;
};
const closeSwitcher = () => {
switcherVisible.value = false;
};
const closeActiveOverlay = () => {
if (switcherVisible.value) closeSwitcher();
else closeAddDialog();
};
const requestBack = () =>
runBackGuard({
transientOpen: switcherVisible.value || addDialogVisible.value,
"close-transient": closeActiveOverlay,
});
onBackPress((event) => {
if (!switcherVisible.value && !addDialogVisible.value) return false;
return handleBackPress(event, requestBack);
});
const handleListScroll = (event) => {
currentListScrollTop.value = Number(event?.detail?.scrollTop || 0);
};
@@ -519,33 +584,44 @@ const resetListScroll = async () => {
currentListScrollTop.value = 0;
};
const selectGenealogy = async (genealogy) => {
selectedGenealogyId.value = genealogy.id;
genealogyContext.setCurrentGenealogyId(genealogy.id);
selectedGenealogyId.value = String(genealogy.id);
genealogyContext.setCurrentGenealogyId(String(genealogy.id));
contextInvalidated.value = false;
closeSwitcher();
await resetListScroll();
};
const openApplication = (record) => {
const statusQuery = record.id === "pending" ? "pending" : record.id;
uni.navigateTo({
url: `/pages/genealogy/g09-my-applications?status=${statusQuery}`,
});
if (record.id === "pending")
return openPage("G09", { status: "pending" }, "G01");
return openPage(
"G08",
{
genealogyId: String(record.genealogyId),
source: "search",
},
"G01",
);
};
const retryLoad = () => {
if (contextReconcileFailed.value) {
hasError.value = false;
reconcilePageGenealogyContext();
return;
}
hasError.value = false;
isLoading.value = false;
};
const openShortcut = (key) => {
if (!currentGenealogy.value) return;
const genealogyId = currentGenealogy.value.id;
genealogyContext.setCurrentGenealogyId(genealogyId);
const paths = {
tree: `/pages/tree/t01-tree-overview?genealogyId=${currentGenealogy.value.id}`,
members: `/pages/genealogy/g05-genealogy-overview?genealogyId=${currentGenealogy.value.id}`,
poem: `/pages/genealogy/g12-generation-poems?genealogyId=${currentGenealogy.value.id}`,
applications: `/pages/genealogy/g10-application-review?genealogyId=${currentGenealogy.value.id}`,
const genealogyId = String(currentGenealogy.value.id);
const actions = {
tree: () => openPage("T01", { genealogyId }, "G01"),
members: () => openPage("G05", { genealogyId }, "G01"),
poem: () => openPage("G12", { genealogyId }, "G01"),
applications: () => openPage("G10", { genealogyId }, "G01"),
};
uni.navigateTo({ url: paths[key] });
return actions[key]?.();
};
</script>
@@ -869,6 +945,10 @@ const openShortcut = (key) => {
padding: 0;
}
.state-panel--context > .app-button {
margin-top: 30rpx;
}
.error-panel__content {
display: flex;
width: 100%;
@@ -1189,9 +1269,16 @@ const openShortcut = (key) => {
align-items: center;
justify-content: space-between;
box-sizing: border-box;
margin: 0;
padding: 18rpx 16rpx;
border: 1rpx solid transparent;
border-bottom-color: rgba(181, 138, 75, 0.42);
background: transparent;
line-height: normal;
text-align: left;
}
.switcher-item::after {
border: 0;
}
.switcher-item__name,
.switcher-item__meta {