修改完成55%
This commit is contained in:
@@ -1,44 +1,119 @@
|
||||
<!-- 页面编号:T-01;用途:世系树总览。 -->
|
||||
<!-- 页面编号:T-01;用途:世系树阅读、同页提示、空态与失败状态(页面设计阶段使用本地模拟数据)。 -->
|
||||
<template>
|
||||
<view class="tree-page">
|
||||
<PageHeader title="世系树" action="树形视图" />
|
||||
<view class="tree-toolbar"><view class="branch-select">当前家谱世系</view><view class="tree-tools"><text @click="toDirectory">查找</text><text @click="toRelationship">编辑</text></view></view>
|
||||
<scroll-view class="tree-scroll" scroll-x scroll-y>
|
||||
<view class="tree-canvas">
|
||||
<view v-if="!members.length" class="tree-empty">{{ loadError || '暂未录入族人' }}</view>
|
||||
<view v-for="member in members" :key="member.id" class="member-node" :class="{ selected: selected && selected.id === member.id }" :style="nodeStyle(member)" @click="selected = member"><text class="node-name">{{ member.name }}</text><text class="node-relation">{{ member.relation }}</text><text class="node-years">{{ member.years }}</text></view>
|
||||
<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'
|
||||
}">
|
||||
<image class="tree-page__paper" src="/static/assets/foundation/opaque/page-paper.jpg" mode="aspectFill" />
|
||||
<view class="tree-page__header"><PageHeader title="世系树" action="成员目录" @action="toDirectory" /></view>
|
||||
|
||||
<view class="tree-toolbar">
|
||||
<view class="tree-toolbar__title">
|
||||
<text>汤氏家谱</text><text>主支 · 第 12—14 世</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/opaque/a01-primary-button.png" mode="scaleToFill" />
|
||||
<text>{{ stateCopy.action }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="selected" class="member-sheet"><view><text class="sheet-name">{{ selected.name }}</text><text class="sheet-meta">第 {{ selected.generation }} 世 · {{ selected.relation }} · {{ selected.branch }}</text></view><view class="sheet-actions"><text @click="toMember">查看资料</text><text @click="toAddRelative">添加亲属</text></view></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/opaque/a01-secondary-button.png" mode="scaleToFill" /><text>查看资料</text>
|
||||
</view>
|
||||
<view class="sheet-action" @click="toAddRelative">
|
||||
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>添加亲属</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { appApi } from '@/utils/api.js'
|
||||
import { genealogyContext } from '@/utils/genealogy-context.js'
|
||||
|
||||
const members = ref([])
|
||||
const selected = ref(null)
|
||||
const genealogyId = ref('')
|
||||
const loadError = 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 loadTree = async (query) => {
|
||||
genealogyId.value = query.genealogyId || genealogyContext.getCurrentGenealogyId()
|
||||
if (!genealogyId.value) { loadError.value = '未找到当前家谱。'; return }
|
||||
genealogyContext.setCurrentGenealogyId(genealogyId.value)
|
||||
try {
|
||||
members.value = await appApi.getTree(genealogyId.value)
|
||||
selected.value = members.value.find((item) => String(item.id) === String(query.selectedId)) || members.value[0] || null
|
||||
} catch (error) {
|
||||
loadError.value = error.message || '世系树加载失败。'
|
||||
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'
|
||||
})
|
||||
|
||||
onLoad(loadTree)
|
||||
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}` })
|
||||
@@ -46,20 +121,44 @@ const toAddRelative = () => uni.navigateTo({ url: `/pages/tree/t04-add-relative?
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tree-page { height: 100vh; overflow: hidden; background: $paper; }
|
||||
.tree-toolbar { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 28rpx; border-bottom: 1rpx solid $gold-soft; background: #fffaf0; }
|
||||
.branch-select { padding: 12rpx 18rpx; border: 1rpx solid $gold; color: $ink; font-size: 25rpx; }
|
||||
.tree-tools { display: flex; gap: 24rpx; color: $brand-red; font-size: 26rpx; }
|
||||
.tree-scroll { height: calc(100vh - 382rpx); white-space: nowrap; }
|
||||
.tree-canvas { position: relative; width: 1180rpx; height: 1120rpx; background: $paper; }
|
||||
.tree-empty { padding: 240rpx 0; color: $ink-muted; font-size: 28rpx; text-align: center; }
|
||||
.member-node { position: absolute; width: 150rpx; min-height: 148rpx; padding: 20rpx 12rpx; box-sizing: border-box; border: 2rpx solid $gold-soft; background: #fffaf0; transform: translateX(-50%); white-space: normal; text-align: center; }
|
||||
.member-node.selected { border-color: $brand-red; box-shadow: 0 0 0 6rpx #f2dfcf; }
|
||||
.node-name { display: block; color: $ink; font-family: serif; font-size: 32rpx; font-weight: 700; }
|
||||
.node-relation { display: block; margin-top: 10rpx; color: $brand-red; font-size: 22rpx; }
|
||||
.node-years { display: block; margin-top: 8rpx; color: $ink-muted; font-size: 20rpx; }
|
||||
.member-sheet { position: fixed; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: space-between; padding: 26rpx 28rpx calc(26rpx + env(safe-area-inset-bottom)); border-top: 1rpx solid $gold; background: $navy; color: #fff8ec; }
|
||||
.sheet-name { display: block; font-family: serif; font-size: 36rpx; font-weight: 700; }
|
||||
.sheet-meta { display: block; margin-top: 8rpx; color: #dfc590; font-size: 21rpx; }
|
||||
.sheet-actions { display: flex; flex-direction: column; gap: 12rpx; color: #f3dba7; font-size: 24rpx; text-align: right; }
|
||||
.tree-page { position: relative; height: 100vh; overflow: hidden; background: $paper; }
|
||||
.tree-page__paper { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.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>
|
||||
|
||||
Reference in New Issue
Block a user