Files
jiapuapp/pages/genealogy/g01-my-genealogies.vue
T
2026-07-15 08:31:33 +08:00

767 lines
18 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-01用途我的家谱列表视觉基准已由用户验收地基迁移不得改变可见效果 -->
<template>
<view class="page-shell genealogy-index">
<image
class="page-paper-texture"
src="/static/assets/foundation/opaque/page-paper.jpg"
mode="scaleToFill"
/>
<image
class="page-footer-landscape"
src="/static/assets/foundation/transparent/footer-mountain-bamboo.png"
mode="scaleToFill"
/>
<PageHeader
root
title="我的家谱"
:unread-count="unreadCount"
@notice="toNotifications"
/>
<view class="genealogy-content">
<view v-if="isLoading" class="state-panel state-panel--loading">
<view class="loading-seal"></view>
<text class="state-title">正在整理家谱</text>
<text class="state-copy">请稍候家族记忆正在归卷</text>
</view>
<template v-else-if="hasGenealogies">
<view class="current-slip" @click="openGenealogy(currentGenealogy)">
<image
class="current-frame"
src="/static/assets/modules/genealogy/transparent/current-slip-frame.png"
mode="scaleToFill"
/>
<view class="current-summary">
<view class="current-seal">
<image
class="current-seal-frame"
src="/static/assets/modules/genealogy/transparent/current-seal-frame.png"
mode="scaleToFill"
/>
<text class="current-seal-title">家谱</text>
</view>
<text class="current-name">{{ currentGenealogy.name }}</text>
</view>
<view class="current-info-divider"></view>
<view class="current-meta">
<view class="current-meta-item">
<image
class="current-meta-icon"
src="/static/assets/foundation/transparent/meta-location.png"
mode="aspectFit"
/>
<text class="current-meta-item-text">{{
currentGenealogy.location
}}</text>
</view>
<view class="current-meta-item">
<image
class="current-meta-icon"
src="/static/assets/foundation/transparent/meta-member.png"
mode="aspectFit"
/>
<text class="current-meta-item-text"
>{{ currentGenealogy.memberCount }} 位成员</text
>
</view>
<view class="current-meta-item">
<image
class="current-meta-icon"
src="/static/assets/foundation/transparent/meta-admin.png"
mode="aspectFit"
/>
<text class="current-meta-item-text">管理员</text>
</view>
</view>
</view>
<view class="shortcut-grid">
<view
v-for="item in shortcuts"
:key="item.key"
class="shortcut-item"
@click="openShortcut(item.key)"
>
<image class="shortcut-icon" :src="item.icon" mode="aspectFit" />
<text class="shortcut-label">{{ item.label }}</text>
</view>
</view>
<image
class="section-divider"
src="/static/assets/modules/genealogy/transparent/section-divider.png"
mode="scaleToFill"
/>
<view class="genealogy-lower">
<view v-if="createdGenealogies.length" class="list-section">
<view class="section-heading"><text>我创建的</text></view>
<GenealogyCard
v-for="item in createdGenealogies"
:key="item.id"
:genealogy="item"
role="管理员"
:selected="item.id === currentGenealogy.id"
@select="openGenealogy"
/>
</view>
<view v-if="joinedGenealogies.length" class="list-section">
<view class="section-heading"><text>我加入的</text></view>
<GenealogyCard
v-for="item in joinedGenealogies"
:key="item.id"
:genealogy="item"
role="成员"
@select="openGenealogy"
/>
</view>
<view class="create-action" @click="createGenealogy">
<image
class="create-cloud"
src="/static/assets/modules/genealogy/transparent/create-cloud.png"
mode="aspectFit"
/>
<image
class="create-icon"
src="/static/assets/modules/genealogy/transparent/add.png"
mode="aspectFit"
/>
<text>新建家谱</text>
<image
class="create-cloud create-cloud--right"
src="/static/assets/modules/genealogy/transparent/create-cloud.png"
mode="aspectFit"
/>
</view>
</view>
</template>
<view v-else class="genealogy-empty-state">
<view class="empty-panel">
<image
class="empty-panel__skin"
src="/static/assets/modules/genealogy/opaque/g01-empty-panel.png"
mode="scaleToFill"
/>
<view class="empty-panel__content">
<view class="empty-seal">
<image
class="empty-seal__skin"
src="/static/assets/modules/genealogy/transparent/current-seal-frame.png"
mode="scaleToFill"
/>
<text class="empty-seal__text">家谱</text>
</view>
<text class="empty-title">还没有加入任何家谱</text>
<text class="empty-copy">先为自己的家族立一部谱或搜索家谱申请加入</text>
<image
class="empty-divider"
src="/static/assets/modules/genealogy/transparent/section-divider.png"
mode="scaleToFill"
/>
<view
class="empty-create-action"
hover-class="action-hover"
@click="createGenealogy"
>
<image
class="empty-create-action__skin"
src="/static/assets/foundation/opaque/a01-primary-button.png"
mode="scaleToFill"
/>
<view class="empty-create-action__content">
<image
class="empty-create-action__icon"
src="/static/assets/modules/genealogy/transparent/add.png"
mode="aspectFit"
/>
<text>新建家谱</text>
<image
class="empty-create-action__cloud"
src="/static/assets/modules/genealogy/transparent/create-cloud.png"
mode="aspectFit"
/>
</view>
</view>
<view
class="empty-search-action"
hover-class="action-hover"
@click="applyToJoin"
>
<image
class="empty-search-action__skin"
src="/static/assets/foundation/opaque/a01-secondary-button.png"
mode="scaleToFill"
/>
<text class="empty-search-action__copy">搜索并申请加入</text>
</view>
</view>
</view>
</view>
</view>
<AppTabbar active="genealogy" />
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import AppTabbar from "@/components/AppTabbar.vue";
import GenealogyCard from "@/components/GenealogyCard.vue";
import PageHeader from "@/components/PageHeader.vue";
import { genealogies, notifications } from "@/data/mock.js";
const isLoading = ref(false);
const list = ref(genealogies);
const forceEmptyState = ref(false);
const isEmptyStateRoute = () => {
if (typeof location !== "undefined") {
const query = new URLSearchParams(location.hash.split("?")[1] || "");
return query.get("state") === "empty";
}
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
return currentPage?.options?.state === "empty";
};
const syncEmptyStateFromRoute = () => {
forceEmptyState.value = isEmptyStateRoute();
};
onLoad(() => {
syncEmptyStateFromRoute();
});
onShow(() => {
syncEmptyStateFromRoute();
});
const unreadCount = computed(
() => notifications.filter((item) => item.unread).length,
);
const hasGenealogies = computed(() => !forceEmptyState.value && list.value.length > 0);
const createdGenealogies = computed(() =>
list.value.filter((item, index) => index === 0),
);
const joinedGenealogies = computed(() =>
list.value.filter((item, index) => index > 0),
);
const currentGenealogy = computed(() => list.value[0]);
const shortcuts = [
{
key: "tree",
label: "世系图",
icon: "/static/assets/modules/genealogy/transparent/shortcut-tree.png",
},
{
key: "members",
label: "成员",
icon: "/static/assets/modules/genealogy/transparent/shortcut-members.png",
},
{
key: "poem",
label: "字辈诗",
icon: "/static/assets/modules/genealogy/transparent/shortcut-generation-poem.png",
},
{
key: "applications",
label: "申请审核",
icon: "/static/assets/modules/genealogy/transparent/shortcut-application.png",
},
];
const openGenealogy = (genealogy) =>
uni.navigateTo({ url: `/pages/genealogy/g05-genealogy-overview?genealogyId=${genealogy.id}` });
const createGenealogy = () =>
uni.navigateTo({ url: "/pages/genealogy/g03-create-genealogy" });
const applyToJoin = () => uni.navigateTo({ url: "/pages/genealogy/g06-search-genealogies" });
const toNotifications = () =>
uni.navigateTo({ url: "/pages/notification/n01-message-center" });
const openShortcut = (key) => {
const paths = {
tree: "/pages/tree/t01-tree-overview",
members: `/pages/genealogy/g05-genealogy-overview?genealogyId=${currentGenealogy.value.id}`,
poem: "/pages/genealogy/g12-generation-poems",
applications: "/pages/genealogy/g10-application-review",
};
uni.navigateTo({ url: paths[key] });
};
</script>
<style scoped lang="scss">
.genealogy-index {
position: relative;
min-height: 100vh;
overflow: hidden;
background: #f9f6ef;
}
.page-paper-texture {
position: absolute;
top: calc(124rpx + var(--status-bar-height, 0px));
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
opacity: 0.72;
pointer-events: none;
}
.page-footer-landscape {
position: absolute;
right: 0;
bottom: 112rpx;
left: 0;
z-index: 1;
width: 100%;
height: 480rpx;
opacity: 0.5;
pointer-events: none;
}
.genealogy-content {
position: relative;
z-index: 2;
padding: 24rpx 32rpx 176rpx;
}
.current-slip {
position: relative;
display: block;
min-height: 268rpx;
padding: 30rpx 34rpx 28rpx;
box-sizing: border-box;
background: transparent;
}
.current-frame {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.current-seal {
position: relative;
z-index: 1;
display: flex;
width: 80rpx;
height: 128rpx;
flex: 0 0 auto;
align-items: center;
justify-content: center;
margin-right: 24rpx;
color: #fff7e7;
}
.current-seal-frame {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
}
.current-seal-title {
position: relative;
z-index: 1;
color: #fff7e7;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
font-weight: 700;
letter-spacing: 2rpx;
writing-mode: vertical-rl;
}
.current-summary {
position: relative;
z-index: 1;
display: flex;
min-width: 0;
align-items: center;
}
.current-name {
overflow: hidden;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 50rpx;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.current-info-divider {
position: relative;
z-index: 1;
width: 100%;
height: 1rpx;
margin: 14rpx 0 16rpx;
background: rgba(181, 138, 75, 0.48);
}
.current-meta {
position: relative;
z-index: 1;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
margin-top: 0;
color: $ink-muted;
font-size: 25rpx;
line-height: 1.4;
}
.current-meta-item {
display: flex;
align-items: center;
margin: 0;
white-space: nowrap;
.current-meta-item-text{
white-space: nowrap;
}
}
.current-meta-icon {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
}
.shortcut-grid {
display: flex;
min-height: 194rpx;
align-items: center;
margin-top: 24rpx;
padding: 8rpx 0;
}
.shortcut-item {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
align-items: center;
}
.shortcut-icon {
width: 82rpx;
height: 82rpx;
}
.shortcut-label {
overflow: hidden;
width: 100%;
margin-top: 12rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 29rpx;
font-weight: 700;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.section-divider {
display: block;
width: 100%;
height: 30rpx;
margin: 4rpx 0 0;
}
.genealogy-lower {
padding: 12rpx 0 36rpx;
}
.list-section {
margin-top: 16rpx;
}
.section-heading {
display: flex;
min-height: 56rpx;
align-items: center;
margin-bottom: 8rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 34rpx;
font-weight: 700;
}
.section-heading::before {
width: 8rpx;
height: 34rpx;
margin-right: 14rpx;
border-radius: 8rpx;
background: $brand-red;
content: "";
}
.list-section + .list-section {
margin-top: 18rpx;
}
.create-action {
display: flex;
min-height: 96rpx;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 30rpx;
}
.create-action {
width: 350rpx;
margin: 32rpx auto 0;
border: 2rpx solid $brand-red;
border-radius: 48rpx;
background: rgba(255, 249, 238, 0.66);
color: $brand-red;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
font-weight: 700;
letter-spacing: 3rpx;
}
.create-icon {
width: 38rpx;
height: 38rpx;
margin-right: 12rpx;
}
.create-action .create-icon {
width: 40rpx;
height: 40rpx;
margin: 0 8rpx;
}
.create-cloud {
width: 54rpx;
height: 28rpx;
flex: 0 0 auto;
}
.create-action > text {
flex: 0 0 auto;
white-space: nowrap;
}
.create-cloud--right {
transform: scaleX(-1);
}
.state-panel {
display: flex;
min-height: 540rpx;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 42rpx;
box-sizing: border-box;
border: 2rpx solid $gold-soft;
border-radius: 18rpx;
background: rgba(255, 249, 238, 0.76);
text-align: center;
}
.state-title {
margin-top: 24rpx;
color: $ink;
font-family: serif;
font-size: 38rpx;
font-weight: 700;
}
.state-copy {
margin-top: 14rpx;
color: $ink-muted;
font-size: 25rpx;
line-height: 1.7;
}
.loading-seal {
border: 2rpx solid $gold;
}
.loading-seal {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
}
.genealogy-empty-state {
min-height: 856rpx;
}
.empty-panel {
position: relative;
height: 856rpx;
}
.empty-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.empty-panel__content {
position: relative;
z-index: 1;
display: flex;
height: 100%;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: 58rpx 60rpx 54rpx;
text-align: center;
}
.empty-seal {
position: relative;
display: flex;
width: 92rpx;
height: 142rpx;
align-items: center;
justify-content: center;
}
.empty-seal__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.empty-seal__text {
position: relative;
z-index: 1;
color: #fff7e7;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
font-weight: 700;
letter-spacing: 2rpx;
writing-mode: vertical-rl;
}
.empty-title {
display: block;
margin-top: 24rpx;
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
font-size: 40rpx;
font-weight: 700;
letter-spacing: 2rpx;
}
.empty-copy {
display: block;
min-height: 74rpx;
margin-top: 14rpx;
color: #786654;
font-size: 24rpx;
line-height: 1.75;
}
.empty-divider {
display: block;
width: 100%;
height: 30rpx;
margin-top: 16rpx;
}
.empty-create-action,
.empty-search-action {
position: relative;
display: flex;
width: 468rpx;
height: 104rpx;
align-items: center;
justify-content: center;
overflow: hidden;
}
.empty-create-action {
margin-top: 20rpx;
}
.empty-search-action {
margin-top: 18rpx;
}
.empty-create-action__skin,
.empty-search-action__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.empty-create-action__content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
color: #fff9ec;
font-family: "STKaiti", "KaiTi", serif;
font-size: 31rpx;
font-weight: 700;
letter-spacing: 4rpx;
}
.empty-create-action__icon {
width: 34rpx;
height: 34rpx;
margin-right: 8rpx;
}
.empty-create-action__cloud {
width: 46rpx;
height: 24rpx;
margin-left: 8rpx;
}
.empty-search-action__copy {
position: relative;
z-index: 1;
color: #7b4e24;
font-family: "STKaiti", "KaiTi", serif;
font-size: 29rpx;
font-weight: 700;
letter-spacing: 3rpx;
}
.action-hover {
opacity: 0.82;
}
@media screen and (max-width: 340px) {
.genealogy-content {
padding-right: 20rpx;
padding-left: 20rpx;
}
.current-name {
font-size: 40rpx;
}
.shortcut-label {
font-size: 23rpx;
}
.current-meta-icon {
width: 28rpx;
height: 28rpx;
}
.create-action {
width: 320rpx;
}
.create-cloud {
width: 46rpx;
}
}
</style>