验收完成40%

This commit is contained in:
2026-07-17 08:08:31 +08:00
parent 23cfd365a1
commit 887481a4ab
172 changed files with 855 additions and 577 deletions
+169 -144
View File
@@ -1,16 +1,10 @@
<!-- 页面编号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"
/>
<view
class="page-shell genealogy-index"
:class="{ 'genealogy-index--split': isListLayout }"
>
<GenealogyPageBackground />
<PageHeader
root
title="我的家谱"
@@ -31,14 +25,15 @@
<text class="state-title">暂时无法读取家谱</text>
<text class="state-copy">网络或服务暂不可用请稍后重新查看</text>
<view class="state-retry" hover-class="action-hover" @click="retryLoad">
<image class="state-retry__skin" src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
<image class="state-retry__skin" src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" />
<text class="state-retry__copy">重新加载</text>
</view>
</view>
</view>
<template v-else-if="hasGenealogies">
<view class="current-slip" @click="openSwitcher">
<view class="genealogy-fixed-zone">
<view class="current-slip" @click="openSwitcher">
<image
class="current-frame"
src="/static/assets/modules/genealogy/transparent/current-slip-frame.png"
@@ -87,90 +82,98 @@
<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="list-section application-section">
<view class="section-heading"><text>加入申请</text></view>
<view class="shortcut-grid">
<view
v-for="item in applicationRecords"
:key="item.id"
class="application-record"
hover-class="action-hover"
@click="openApplication(item)"
v-for="item in shortcuts"
:key="item.key"
class="shortcut-item"
@click="openShortcut(item.key)"
>
<image class="application-record__skin" src="/static/assets/modules/genealogy/transparent/list-slip-frame.png" mode="scaleToFill" />
<view class="application-record__main">
<view class="application-record__title-row">
<text class="application-record__name">{{ item.name }}</text>
<text class="application-record__status" :class="`application-record__status--${item.tone}`">{{ item.statusLabel }}</text>
</view>
<text class="application-record__copy">{{ item.description }}</text>
</view>
<image class="application-record__chevron" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
<image class="shortcut-icon" :src="item.icon" mode="aspectFit" />
<text class="shortcut-label">{{ item.label }}</text>
</view>
</view>
<view class="create-action" @click="openAddDialog">
<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>
<image
class="section-divider"
src="/static/assets/modules/genealogy/transparent/section-divider.png"
mode="scaleToFill"
/>
</view>
<scroll-view
class="genealogy-list-scroll"
scroll-y
:scroll-top="listScrollCommand"
@scroll="handleListScroll"
>
<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="list-section application-section">
<view class="section-heading"><text>加入申请</text></view>
<view
v-for="item in applicationRecords"
:key="item.id"
class="application-record"
hover-class="action-hover"
@click="openApplication(item)"
>
<image class="application-record__skin" src="/static/assets/modules/genealogy/transparent/list-slip-frame.png" mode="scaleToFill" />
<view class="application-record__main">
<view class="application-record__title-row">
<text class="application-record__name">{{ item.name }}</text>
<text class="application-record__status" :class="`application-record__status--${item.tone}`">{{ item.statusLabel }}</text>
</view>
<text class="application-record__copy">{{ item.description }}</text>
</view>
<image class="application-record__chevron" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
</view>
</view>
<view class="create-action" @click="openAddDialog">
<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>
</scroll-view>
</template>
<view v-else class="genealogy-empty-state">
@@ -203,8 +206,8 @@
>
<image
class="empty-search-action__skin"
src="/static/assets/foundation/opaque/a01-primary-button.png"
mode="scaleToFill"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text class="empty-search-action__copy empty-search-action__copy--primary">搜索家谱</text>
</view>
@@ -215,8 +218,8 @@
>
<image
class="empty-invite-action__skin"
src="/static/assets/foundation/opaque/a01-secondary-button.png"
mode="scaleToFill"
src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
mode="aspectFit"
/>
<text class="empty-invite-action__copy">邀请码加入</text>
</view>
@@ -231,19 +234,13 @@
<view v-if="addDialogVisible" class="add-dialog-layer" @click="closeAddDialog">
<view class="add-dialog" @click.stop>
<image class="add-dialog__skin" src="/static/assets/modules/genealogy/opaque/g01-empty-panel.png" mode="scaleToFill" />
<image class="add-dialog__skin" src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png" mode="aspectFit" />
<view class="add-dialog__content">
<text class="dialog-title">添加家谱</text>
<text class="dialog-copy">建议先搜索已有家谱避免重复创建</text>
<view class="dialog-primary" @click="applyToJoin">
<image class="dialog-action__skin" src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
<text class="dialog-primary__copy">搜索家谱</text>
</view>
<view class="dialog-secondary" @click="joinByInvite">
<image class="dialog-action__skin" src="/static/assets/foundation/opaque/a01-secondary-button.png" mode="scaleToFill" />
<text>邀请码加入</text>
</view>
<view class="dialog-create" @click="createGenealogy">确认没有现有家谱继续创建</view>
<AppButton block label="搜索家谱" @click="applyToJoin" />
<AppButton block type="secondary" label="邀请码加入" @click="joinByInvite" />
<AppButton block type="secondary" label="确认没有现有家谱,继续创建" @click="createGenealogy" />
<view class="dialog-close" @click="closeAddDialog">取消</view>
</view>
</view>
@@ -251,7 +248,7 @@
<view v-if="switcherVisible" class="genealogy-switcher-layer" @click="closeSwitcher">
<view class="genealogy-switcher" @click.stop>
<image class="genealogy-switcher__skin" src="/static/assets/modules/genealogy/opaque/g01-empty-panel.png" mode="scaleToFill" />
<image class="genealogy-switcher__skin" src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png" mode="aspectFit" />
<view class="genealogy-switcher__content">
<text class="dialog-title">切换当前家谱</text>
<view
@@ -277,10 +274,12 @@
</template>
<script setup>
import { computed, ref } from "vue";
import { computed, nextTick, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import AppTabbar from "@/components/AppTabbar.vue";
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";
@@ -291,6 +290,8 @@ const forceEmptyState = ref(false);
const addDialogVisible = ref(false);
const switcherVisible = ref(false);
const selectedGenealogyId = ref(genealogies[0]?.id || null);
const listScrollCommand = ref(0);
const currentListScrollTop = ref(0);
const readPresentationState = () => {
if (typeof location !== "undefined") {
@@ -322,6 +323,9 @@ const unreadCount = computed(
() => notifications.filter((item) => item.unread).length,
);
const hasGenealogies = computed(() => !forceEmptyState.value && list.value.length > 0);
const isListLayout = computed(
() => !isLoading.value && !hasError.value && hasGenealogies.value,
);
const createdGenealogies = computed(() =>
list.value.filter((item, index) => index === 0),
);
@@ -376,9 +380,19 @@ const openAddDialog = () => { addDialogVisible.value = true; };
const closeAddDialog = () => { addDialogVisible.value = false; };
const openSwitcher = () => { switcherVisible.value = true; };
const closeSwitcher = () => { switcherVisible.value = false; };
const selectGenealogy = (genealogy) => {
const handleListScroll = (event) => {
currentListScrollTop.value = Number(event?.detail?.scrollTop || 0);
};
const resetListScroll = async () => {
listScrollCommand.value = currentListScrollTop.value;
await nextTick();
listScrollCommand.value = 0;
currentListScrollTop.value = 0;
};
const selectGenealogy = async (genealogy) => {
selectedGenealogyId.value = genealogy.id;
closeSwitcher();
await resetListScroll();
};
const openApplication = (record) => {
const statusQuery = record.id === "pending" ? "pending" : record.id;
@@ -408,37 +422,40 @@ const openShortcut = (key) => {
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;
}
.genealogy-index--split {
display: flex;
height: 100vh;
min-height: 0;
flex-direction: column;
padding-bottom: calc(112rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.genealogy-index--split .genealogy-content {
display: flex;
min-height: 0;
flex: 1;
flex-direction: column;
padding-bottom: 0;
}
.genealogy-fixed-zone {
flex: 0 0 auto;
}
.genealogy-list-scroll {
width: 100%;
height: 0;
min-height: 0;
flex: 1;
}
.current-slip {
position: relative;
display: block;
@@ -531,8 +548,9 @@ const openShortcut = (key) => {
flex-wrap: nowrap;
justify-content: space-between;
margin-top: 0;
color: $ink-muted;
font-size: 25rpx;
color: #62584c;
font-size: 27rpx;
font-weight: 500;
line-height: 1.4;
}
@@ -635,10 +653,23 @@ const openShortcut = (key) => {
.application-record__main { position: relative; z-index: 1; min-width: 0; flex: 1; }
.application-record__title-row { display: flex; align-items: center; justify-content: space-between; }
.application-record__name { overflow: hidden; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 32rpx; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.application-record__status { flex: 0 0 auto; margin-left: 16rpx; color: #9a641f; font-size: 23rpx; }
.application-record__status {
flex: 0 0 auto;
margin-left: 16rpx;
color: #7f4f16;
font-size: 27rpx;
font-weight: 600;
}
.application-record__status--rejected { color: #a7160c; }
.application-record__status--muted { color: $ink-muted; }
.application-record__copy { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 23rpx; }
.application-record__status--muted { color: #62584c; }
.application-record__copy {
display: block;
margin-top: 10rpx;
color: #62584c;
font-size: 28rpx;
font-weight: 500;
line-height: 1.4;
}
.application-record__chevron { position: relative; z-index: 1; width: 32rpx; height: 32rpx; margin-left: 14rpx; }
.create-action {
@@ -862,14 +893,8 @@ const openShortcut = (key) => {
.genealogy-switcher__content { position: relative; z-index: 1; display: flex; min-height: 720rpx; flex-direction: column; align-items: center; box-sizing: border-box; padding: 78rpx 58rpx 50rpx; }
.dialog-title { color: $brand-red; font-family: "STKaiti", "KaiTi", serif; font-size: 42rpx; font-weight: 700; letter-spacing: 4rpx; }
.dialog-copy { margin-top: 18rpx; color: $ink-muted; font-size: 23rpx; }
.dialog-primary,
.dialog-secondary { position: relative; display: flex; width: 100%; height: 98rpx; align-items: center; justify-content: center; overflow: hidden; font-family: "STKaiti", "KaiTi", serif; font-size: 29rpx; }
.dialog-primary { margin-top: 42rpx; color: #fff9ec; }
.dialog-secondary { margin-top: 16rpx; color: #7b4e24; }
.dialog-action__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.dialog-primary__copy,
.dialog-secondary > text { position: relative; z-index: 1; }
.dialog-create { display: flex; min-height: 76rpx; align-items: center; margin-top: 8rpx; color: $brand-red; font-size: 23rpx; }
.add-dialog__content > .app-button:first-of-type { margin-top: 34rpx; }
.add-dialog__content > .app-button + .app-button { margin-top: 10rpx; }
.dialog-close { display: flex; min-height: 72rpx; align-items: center; justify-content: center; margin-top: auto; color: $ink-muted; font-size: 24rpx; }
.switcher-item { display: flex; width: 100%; min-height: 108rpx; align-items: center; justify-content: space-between; box-sizing: border-box; padding: 18rpx 12rpx; border-bottom: 1rpx solid rgba(181, 138, 75, 0.42); }
.switcher-item:first-of-type { margin-top: 28rpx; }