完成50%
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user