Files
jiapuapp/pages/tree/t01-tree-overview.vue
T
2026-07-17 08:08:33 +08:00

165 lines
10 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.
<!-- 页面编号T-01用途世系树阅读同页提示空态与失败状态页面设计阶段使用本地模拟数据)。 -->
<template>
<view class="tree-page" :class="{
'tree-state--tree': treeState === 'tree',
'tree-state--landscape': treeState === 'landscape',
'tree-state--empty': treeState === 'empty',
'tree-state--error': treeState === 'error'
}">
<ModulePageBackground module="tree" />
<view class="tree-page__header"><PageHeader title="世系树" action="成员目录" @action="toDirectory" /></view>
<view class="tree-toolbar">
<view class="tree-toolbar__title">
<text>汤氏家谱</text><text>主支 · 1214 </text>
</view>
<view class="tree-toolbar__actions">
<text @click="treeState = 'landscape'">阅读提示</text>
<text @click="toRelationship">关系维护</text>
</view>
</view>
<scroll-view class="tree-scroll" scroll-x :show-scrollbar="false">
<view class="tree-canvas" :class="{ 'tree-canvas--state': treeState !== 'tree' }">
<image class="tree-canvas__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
<template v-if="treeState === 'tree'">
<view class="generation-label generation-label--one"><text>第十二世</text></view>
<view class="generation-label generation-label--two"><text>第十三世</text></view>
<view class="generation-label generation-label--three"><text>第十四世</text></view>
<view v-for="member in members" :key="member.id" class="member-node" :class="{ 'member-node--selected': selected?.id === member.id }" :style="nodeStyle(member)" @click="selected = member">
<image class="member-node__skin" src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
<text class="node-name">{{ member.name }}</text>
<text class="node-relation">{{ member.relation }}</text>
<text class="node-years">{{ member.years }}</text>
</view>
</template>
<view v-else class="tree-state-card">
<text class="tree-state-card__eyebrow">{{ stateCopy.eyebrow }}</text>
<text class="tree-state-card__title">{{ stateCopy.title }}</text>
<text class="tree-state-card__copy">{{ stateCopy.copy }}</text>
<view class="tree-state-card__action" @click="handleStateAction">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" />
<text>{{ stateCopy.action }}</text>
</view>
</view>
</view>
</scroll-view>
<view v-if="treeState === 'tree' && selected" class="member-sheet">
<image class="member-sheet__skin" src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" />
<view class="member-sheet__copy">
<text class="sheet-name">{{ selected.name }}</text>
<text class="sheet-meta"> {{ selected.generation }} · {{ selected.relation }} · {{ selected.branch }}</text>
</view>
<view class="sheet-actions">
<view class="sheet-action sheet-member-action" @click="toMember">
<image src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png" mode="aspectFit" /><text>查看资料</text>
</view>
<view class="sheet-action" @click="toAddRelative">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>添加亲属</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ModulePageBackground from '@/components/ModulePageBackground.vue'
import PageHeader from '@/components/PageHeader.vue'
import { genealogyContext } from '@/utils/genealogy-context.js'
const genealogyId = ref('')
const treeState = ref('loading')
const selected = ref(null)
const members = ref([
{ id: 101, name: '汤文远', relation: '始祖', years: '1940—2012', generation: 12, branch: '主支', x: 50, y: 15 },
{ id: 102, name: '汤正国', relation: '长子', years: '1965—', generation: 13, branch: '长房', x: 31, y: 43 },
{ id: 103, name: '汤正华', relation: '次子', years: '1968—', generation: 13, branch: '二房', x: 69, y: 43 },
{ id: 104, name: '汤凯', relation: '长孙', years: '1992—', generation: 14, branch: '长房', x: 20, y: 72 },
{ id: 105, name: '汤悦', relation: '长孙女', years: '1995—', generation: 14, branch: '长房', x: 50, y: 72 },
{ id: 106, name: '汤晨', relation: '次孙', years: '1998—', generation: 14, branch: '二房', x: 80, y: 72 }
])
const stateCopy = computed(() => ({
landscape: { eyebrow: '横向阅读提示', title: '左右拖动查看完整世系', copy: '世系按代际从上到下排列;横向拖动画布可以查看同一世代的其他支系。', action: '进入世系图' },
empty: { eyebrow: '尚未建立世系', title: '从第一位先祖开始记录', copy: '录入首代成员后,即可继续补充子女、配偶与后代关系。', action: '录入首代成员' },
error: { eyebrow: '世系暂不可用', title: '暂时无法读取成员关系', copy: '请从当前家谱重新进入;已经录入的成员资料不会受到影响。', action: '重新查看' },
loading: { eyebrow: '正在整理世系', title: '请稍候', copy: '正在准备家谱成员关系。', action: '重新查看' }
}[treeState.value] || {}))
onLoad((query) => {
genealogyId.value = query.genealogyId || genealogyContext.getCurrentGenealogyId() || ''
if (query.state === 'landscape' || query.state === 'empty' || query.state === 'error') {
treeState.value = query.state
return
}
if (!genealogyId.value) {
treeState.value = 'empty'
return
}
genealogyContext.setCurrentGenealogyId(genealogyId.value)
selected.value = members.value.find((item) => String(item.id) === String(query.selectedId)) || members.value[0]
treeState.value = 'tree'
})
const nodeStyle = (member) => ({ left: `${member.x}%`, top: `${member.y}%` })
const handleStateAction = () => {
if (treeState.value === 'empty') {
uni.navigateTo({ url: `/pages/tree/t04-add-relative?genealogyId=${genealogyId.value || 1001}&mode=first` })
return
}
selected.value = members.value[0]
treeState.value = 'tree'
}
const toMember = () => uni.navigateTo({ url: `/pages/tree/t03-member-profile?genealogyId=${genealogyId.value}&personId=${selected.value.id}` })
const toDirectory = () => uni.navigateTo({ url: `/pages/tree/t07-member-directory?genealogyId=${genealogyId.value}` })
const toRelationship = () => uni.navigateTo({ url: `/pages/tree/t06-edit-relationship?genealogyId=${genealogyId.value}` })
const toAddRelative = () => uni.navigateTo({ url: `/pages/tree/t04-add-relative?genealogyId=${genealogyId.value}&personId=${selected.value.id}` })
</script>
<style scoped lang="scss">
.tree-page { position: relative; height: 100vh; overflow: hidden; background: $paper; }
.tree-page__header, .tree-toolbar, .tree-scroll, .member-sheet { position: relative; z-index: 2; }
.tree-toolbar { display: flex; align-items: center; justify-content: space-between; padding: 18rpx 28rpx 14rpx; }
.tree-toolbar__title text { display: block; }
.tree-toolbar__title text:first-child { color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 28rpx; font-weight: 700; }
.tree-toolbar__title text:last-child { margin-top: 5rpx; color: $ink-muted; font-size: 19rpx; }
.tree-toolbar__actions { display: flex; gap: 24rpx; color: $brand-red; font-size: 21rpx; }
.tree-scroll { width: 100%; height: calc(100vh - 330rpx); white-space: nowrap; }
.tree-canvas { position: relative; width: 1180rpx; height: 1040rpx; margin: 0 18rpx; }
.tree-canvas--state { width: calc(100vw - 36rpx); }
.tree-canvas__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.generation-label { position: absolute; left: 3.5%; z-index: 2; color: #92764f; font-size: 19rpx; writing-mode: vertical-rl; letter-spacing: 3rpx; }
.generation-label--one { top: 16%; }
.generation-label--two { top: 44%; }
.generation-label--three { top: 73%; }
.member-node { position: absolute; width: 190rpx; height: 132rpx; transform: translate(-50%, -50%); text-align: center; }
.member-node__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.node-name, .node-relation, .node-years { position: relative; z-index: 1; display: block; }
.node-name { padding-top: 22rpx; color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 29rpx; font-weight: 700; }
.node-relation { margin-top: 6rpx; color: $brand-red; font-size: 19rpx; }
.node-years { margin-top: 4rpx; color: $ink-muted; font-size: 17rpx; }
.member-node--selected .node-name { color: $brand-red; }
.tree-state-card { position: absolute; top: 28%; right: 9%; left: 9%; z-index: 2; text-align: center; white-space: normal; }
.tree-state-card__eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.tree-state-card__title { display: block; margin-top: 14rpx; color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 36rpx; font-weight: 700; }
.tree-state-card__copy { display: block; margin-top: 18rpx; color: $ink-muted; font-size: 22rpx; line-height: 1.7; }
.tree-state-card__action { position: relative; width: 380rpx; height: 76rpx; margin: 30rpx auto 0; }
.tree-state-card__action image { position: absolute; inset: 0; width: 100%; height: 100%; }
.tree-state-card__action text { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; color: #fff9ed; font-size: 24rpx; font-weight: 700; }
.member-sheet { position: fixed; right: 18rpx; bottom: calc(14rpx + env(safe-area-inset-bottom)); left: 18rpx; height: 178rpx; }
.member-sheet__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.member-sheet__copy { position: absolute; top: 34rpx; left: 44rpx; z-index: 1; }
.sheet-name { display: block; color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 31rpx; font-weight: 700; }
.sheet-meta { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 19rpx; }
.sheet-actions { position: absolute; top: 46rpx; right: 34rpx; z-index: 1; display: flex; gap: 10rpx; }
.sheet-action { position: relative; width: 130rpx; height: 62rpx; }
.sheet-action image { position: absolute; inset: 0; width: 100%; height: 100%; }
.sheet-action text { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; color: $ink; font-size: 20rpx; font-weight: 700; }
.sheet-action:last-child text { color: #fff9ed; }
</style>