Files
jiapuapp/pages/genealogy/g05-genealogy-overview.vue
T
2026-07-21 20:59:10 +08:00

600 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 页面编号G-05用途家谱总览与加载空数据失败状态 -->
<template>
<view class="overview-page">
<GenealogyPageBackground />
<view class="overview-page__header">
<PageHeader
:title="viewMode === 'public' ? '家谱公开预览' : '家谱总览'"
:action="
overviewState === 'ready' &&
viewMode === 'member' &&
accessRole === 'owner'
? '管理'
: ''
"
@action="toSettings"
/>
</view>
<view
class="overview-surface"
:class="{ 'overview-surface--state': overviewState !== 'ready' }"
>
<template
v-if="overviewState === 'ready' && genealogy && viewMode === 'member'"
>
<view class="overview-ready">
<view class="overview-hero">
<text class="overview-hero__kicker"
>{{ genealogy.hall || "堂号待补" }} ·
{{ genealogy.location || "所在地待补" }}</text
>
<text class="overview-hero__title">{{ genealogy.name }}</text>
<text class="overview-hero__motto">{{
genealogy.motto || "敦亲睦族,敬祖传家。"
}}</text>
<view class="overview-hero__stats">
<text> {{ genealogy.memberCount || 0 }} </text>
<text>已激活 {{ genealogy.activeCount || 0 }} </text>
<text>{{ genealogy.visibility || "仅成员可见" }}</text>
</view>
<view class="overview-hero__stats">
<text>始祖 {{ genealogy.ancestorName }}</text>
<text>更新于 {{ genealogy.updatedAt }}</text>
</view>
</view>
<view
class="overview-actions"
:class="{ 'overview-actions--member': accessRole === 'member' }"
>
<view class="overview-action overview-action--tree" @click="toTree">
<text class="overview-action__title">世系树</text
><text>查看家脉关系</text>
</view>
<view
v-if="accessRole === 'owner'"
class="overview-action overview-action--ancestor"
@click="toFirstPerson"
>
<text class="overview-action__title">录入族人</text
><text>从首代开始完善</text>
</view>
<view
class="overview-action overview-action--poem"
@click="toGenerationPoems"
>
<text class="overview-action__title">字辈谱</text
><text>传承行辈次序</text>
</view>
<view
v-if="accessRole === 'owner'"
class="overview-action overview-action--review"
@click="toApplications"
>
<text class="overview-action__title">入谱审核</text
><text>处理加入申请</text>
</view>
</view>
<view class="overview-family" @click="toFamily">
<text>家族近况</text><text>进入家族圈查看动态</text
><text>查看</text>
</view>
<view class="overview-note">
<text class="overview-note__title">家谱资料仅向家人开放</text>
<text>公开范围访问说明与管理权由家谱管理员在设置中维护</text>
</view>
</view>
</template>
<view
v-else-if="overviewState === 'ready' && genealogy"
class="overview-public"
>
<view class="overview-public__hero">
<text class="overview-public__eyebrow">公开家谱</text>
<text class="overview-public__title">{{ genealogy.name }}</text>
<text class="overview-public__source">{{ genealogy.source }}</text>
</view>
<view class="overview-public__details">
<view
><text>姓氏</text><text>{{ genealogy.surname }}</text></view
>
<view
><text>地区</text><text>{{ genealogy.location }}</text></view
>
<view
><text>堂号</text><text>{{ genealogy.hall }}</text></view
>
<view
><text>当前支系</text><text>{{ genealogy.branchName }}</text></view
>
<view
><text>所属上级谱</text
><text>{{ genealogy.parentName }}</text></view
>
</view>
<view class="overview-public__notice">
<text>公开说明</text>
<text>{{ genealogy.publicDescription }}</text>
</view>
<view class="overview-public__action" @click="applyToJoin">
<text>申请加入这部家谱</text>
</view>
</view>
<view
v-else-if="overviewState === 'loading'"
class="overview-state overview-state--loading"
>
<AppLoading
text="正在展开家谱"
description="请稍候,正在读取家谱概览。"
/>
</view>
<view
v-else
class="overview-state overview-state-panel"
:class="{
'overview-state--loading': overviewState === 'loading',
'overview-state--empty': overviewState === 'empty',
'overview-state--error': overviewState === 'error',
'overview-state--no-permission': overviewState === 'no-permission',
}"
>
<view class="overview-state__content">
<image
class="overview-state__seal"
src="/static/assets/foundation/transparent/brand-seal.png"
mode="aspectFit"
/>
<text class="overview-state-panel__title">{{ stateTitle }}</text>
<text class="overview-state-panel__copy">{{ stateCopy }}</text>
<view
class="overview-state-panel__action"
@click="
overviewState === 'error' ? reloadOverview() : toGenealogies()
"
>
<text>{{
overviewState === "error" ? "重新查看" : "返回我的家谱"
}}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import AppLoading from "@/components/AppLoading.vue";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
const genealogy = ref(null);
const genealogyId = ref("");
const overviewState = ref("loading");
const loadError = ref("");
const viewMode = ref("member");
const accessRole = ref("owner");
const overviewFixture = {
id: "2001",
surname: "汤",
name: "汤氏南阳宗谱",
hall: "敦睦堂",
location: "河南·南阳",
parentName: "汤氏中华总谱",
branchName: "南阳主支",
source: "由南阳汤氏族人整理并维护",
publicDescription:
"公开展示家谱身份、地区、堂号与支系信息;成员资料和世系详情仅向已加入成员开放。",
motto: "敦亲睦族,敬祖传家。",
memberCount: 428,
activeCount: 316,
visibility: "仅成员可见",
ancestorName: "汤文远",
updatedAt: "2026-07-12",
};
const overviewFixtures = {
1001: {
...overviewFixture,
id: "1001",
name: "汤氏家谱",
location: "河南·洛阳",
memberCount: 158,
activeCount: 108,
},
1002: {
...overviewFixture,
id: "1002",
name: "汤氏宗谱",
hall: "承志堂",
location: "山东·济宁",
memberCount: 286,
activeCount: 215,
},
2001: overviewFixture,
};
const stateTitle = computed(
() =>
({
loading: "正在展开家谱…",
empty: "还没有可查看的家谱",
error: "家谱暂时无法打开",
"no-permission": "当前账号无权查看",
})[overviewState.value] || "",
);
const stateCopy = computed(
() =>
loadError.value ||
{
loading: "请稍候,正在读取家谱概览。",
empty: "请从“我的家谱”选择一部家谱后再进入。",
error: "可能是网络波动或家谱不存在。",
"no-permission": "这部家谱未向当前账号开放,请返回我的家谱或联系管理员。",
}[overviewState.value] ||
"",
);
const loadGenealogy = (query = {}) => {
overviewState.value = "loading";
loadError.value = "";
genealogy.value = null;
genealogyId.value = query.genealogyId || genealogyId.value || "";
viewMode.value = query.mode === "public" ? "public" : "member";
accessRole.value = query.role === "member" ? "member" : "owner";
if (query.state === "empty" || !genealogyId.value) {
overviewState.value = "empty";
return;
}
if (query.state === "error") {
overviewState.value = "error";
return;
}
if (query.state === "no-permission") {
overviewState.value = "no-permission";
return;
}
if (query.state === "loading") return;
genealogy.value = {
...(overviewFixtures[genealogyId.value] || overviewFixture),
id: genealogyId.value,
};
if (query.genealogyName) {
genealogy.value.name = decodeURIComponent(query.genealogyName);
}
overviewState.value = "ready";
};
onLoad(loadGenealogy);
const reloadOverview = () => loadGenealogy({ genealogyId: genealogyId.value });
const toGenealogies = () =>
uni.reLaunch({ url: "/pages/genealogy/g01-my-genealogies" });
const toTree = () =>
uni.navigateTo({
url: `/pages/tree/t01-tree-overview?genealogyId=${genealogyId.value}`,
});
const toFirstPerson = () =>
uni.navigateTo({
url: `/pages/genealogy/g03-create-genealogy?step=ancestor&genealogyId=${genealogyId.value}`,
});
const toFamily = () => uni.reLaunch({ url: "/pages/family/f01-family-feed" });
const toApplications = () =>
uni.navigateTo({
url: `/pages/genealogy/g10-application-review?genealogyId=${genealogyId.value}`,
});
const toSettings = () =>
uni.navigateTo({
url: `/pages/genealogy/g11-genealogy-settings?genealogyId=${genealogyId.value}`,
});
const toGenerationPoems = () =>
uni.navigateTo({
url: `/pages/genealogy/g12-generation-poems?genealogyId=${genealogyId.value}`,
});
const applyToJoin = () =>
uni.navigateTo({
url: `/pages/genealogy/g08-join-application?source=search&genealogyId=${genealogyId.value}`,
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.overview-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $paper;
}
.overview-page__header {
z-index: 3;
}
.overview-surface {
@include adaptive.adaptive-g05-overview-surface;
z-index: 2;
width: 100%;
min-height: min(483px, calc(100vw * 1.3422));
flex: 0 0 auto;
}
.overview-surface--state {
display: flex;
height: calc(100vh - 112rpx);
min-height: 1040rpx;
padding: 28rpx;
box-sizing: border-box;
background: none;
}
.overview-ready {
display: flex;
min-height: min(483px, calc(100vw * 1.3422));
flex-direction: column;
gap: 34rpx;
padding: 58rpx 0 42rpx;
box-sizing: border-box;
}
.overview-hero {
margin: 0 8%;
color: #fff8ec;
}
.overview-hero__kicker {
display: block;
color: #dec483;
font-size: 22rpx;
letter-spacing: 3rpx;
}
.overview-hero__title {
display: block;
margin-top: 12rpx;
font-family: "STKaiti", "KaiTi", serif;
font-size: 46rpx;
font-weight: 700;
letter-spacing: 4rpx;
}
.overview-hero__motto {
display: block;
margin-top: 6rpx;
color: #e9dcc1;
font-size: 23rpx;
}
.overview-hero__stats {
display: flex;
gap: 18rpx;
margin-top: 12rpx;
color: #dbc990;
font-size: 22rpx;
}
.overview-actions {
display: grid;
min-height: 386rpx;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto;
gap: 3.4% 3%;
margin: 0 7.2%;
}
.overview-actions--member {
min-height: 386rpx;
}
.overview-action,
.overview-action-lock {
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
padding: 24rpx 22rpx;
box-sizing: border-box;
color: $ink-muted;
font-size: 22rpx;
}
.overview-action-lock {
opacity: 0.58;
}
.overview-action__title {
margin-bottom: 9rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 30rpx;
font-weight: 700;
}
.overview-family {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
align-self: start;
gap: 16rpx;
margin: 0 7.5%;
color: $ink-muted;
font-size: 22rpx;
}
.overview-family text:first-child {
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 28rpx;
font-weight: 700;
}
.overview-family text:last-child {
color: $brand-red;
}
.overview-note {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 8.5%;
color: $ink-muted;
font-size: 22rpx;
line-height: 1.6;
margin-top: 100rpx;
}
.overview-note__title {
margin-bottom: 8rpx;
color: $brand-red;
font-family: "STKaiti", "KaiTi", serif;
font-size: 28rpx;
font-weight: 700;
}
.overview-state {
@include adaptive.adaptive-genealogy-state-panel;
display: flex;
width: 100%;
min-height: 0;
align-items: center;
justify-content: center;
text-align: center;
}
.overview-state__content {
z-index: 1;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 48rpx 34rpx;
}
.overview-state--loading .app-loading {
width: 100%;
height: 100%;
}
.overview-state__seal {
width: 132rpx;
height: 136rpx;
}
.overview-state-panel__title {
margin-top: 28rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 42rpx;
font-weight: 700;
}
.overview-state-panel__copy {
max-width: 520rpx;
margin-top: 18rpx;
color: $ink-muted;
font-size: 27rpx;
line-height: 1.7;
}
.overview-state-panel__action {
display: flex;
width: 560rpx;
min-height: 124rpx;
margin-top: 34rpx;
align-items: center;
justify-content: center;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.overview-state-panel__action text {
z-index: 1;
color: #fff9ed;
font-size: 31rpx;
font-weight: 700;
}
.overview-public {
display: flex;
min-height: min(483px, calc(100vw * 1.3422));
flex-direction: column;
gap: 0;
padding: 58rpx 0 28rpx;
box-sizing: border-box;
}
.overview-public__hero {
margin: 0 8%;
color: #fff8ec;
}
.overview-public__eyebrow {
display: block;
color: #dec483;
font-size: 21rpx;
letter-spacing: 3rpx;
}
.overview-public__title {
display: block;
margin-top: 10rpx;
font-family: "STKaiti", "KaiTi", serif;
font-size: 43rpx;
font-weight: 700;
letter-spacing: 4rpx;
}
.overview-public__source {
display: block;
margin-top: 7rpx;
color: #e9dcc1;
font-size: 22rpx;
}
.overview-public__details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx 28rpx;
min-height: 240rpx;
margin: 126rpx 9% 0;
}
.overview-public__details > view {
display: flex;
flex-direction: column;
}
.overview-public__details > view:nth-child(5) {
grid-column: 1 / -1;
align-self: start;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 0 24rpx;
}
.overview-public__details > view:nth-child(5) text:first-child {
flex: 0 0 auto;
}
.overview-public__details > view:nth-child(5) text:last-child {
margin-top: 0;
}
.overview-public__details text:first-child {
color: $brand-red;
font-size: 21rpx;
}
.overview-public__details text:last-child {
margin-top: 6rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 27rpx;
}
.overview-public__notice {
display: flex;
margin: 54rpx 9% 0;
color: $ink-muted;
font-size: 19rpx;
line-height: 26rpx;
}
.overview-public__notice text:first-child {
flex: 0 0 auto;
margin-right: 12rpx;
color: $brand-red;
font-weight: 700;
}
.overview-public__action {
display: flex;
min-height: 82rpx;
align-items: center;
justify-content: center;
margin: 204rpx 9% 0;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.overview-public__action text {
z-index: 1;
color: #fff9ed;
font-size: 27rpx;
font-weight: 700;
letter-spacing: 3rpx;
}
@media (min-width: 400px) {
.overview-action {
padding: 0 28rpx;
}
}
</style>