验收完成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
+8 -5
View File
@@ -198,16 +198,18 @@ defineEmits(["select"]);
margin-right: 6rpx; margin-right: 6rpx;
} }
.card-meta { .card-meta {
color: $ink-muted; color: #62584c;
font-size: 26rpx; font-size: 26rpx;
font-weight: 500;
} }
.card-updated { .card-updated {
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
align-items: center; align-items: center;
margin-left: 10rpx; margin-left: 10rpx;
color: $ink-muted; color: #62584c;
font-size: 22rpx; font-size: 24rpx;
font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
@@ -224,8 +226,9 @@ defineEmits(["select"]);
} }
.card-role { .card-role {
color: $ink-muted; color: #62584c;
font-size: 24rpx; font-size: 26rpx;
font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
.card-chevron { .card-chevron {
+28 -20
View File
@@ -9,9 +9,8 @@
'module-state--error': moduleState === 'error' 'module-state--error': moduleState === 'error'
} }
]"> ]">
<image class="module-page__paper" src="/static/assets/foundation/opaque/page-paper.jpg" mode="aspectFill" /> <ModulePageBackground :module="moduleKey" />
<image class="module-page__footer" src="/static/assets/foundation/transparent/footer-mountain-bamboo.png" mode="widthFix" /> <view class="module-page__header"><PageHeader :title="page.title" /></view>
<view class="module-page__header"><PageHeader :title="page.title" :action="page.number" /></view>
<view v-if="moduleState === 'ready'" class="module-page__content"> <view v-if="moduleState === 'ready'" class="module-page__content">
<view class="page-intro"> <view class="page-intro">
@@ -28,7 +27,7 @@
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" /><text>{{ field }}</text><input :placeholder="`请输入${field}`" placeholder-class="form-placeholder" /> <image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" /><text>{{ field }}</text><input :placeholder="`请输入${field}`" placeholder-class="form-placeholder" />
</view> </view>
<text v-if="page.note" class="form-note">{{ normalizedNote }}</text> <text v-if="page.note" class="form-note">{{ normalizedNote }}</text>
<view class="primary-action" @click="completeAction"><image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ page.action }}</text></view> <AppButton block :label="page.action" @click="completeAction" />
</view> </view>
</view> </view>
@@ -37,7 +36,7 @@
<image src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" /> <image src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" />
<view class="list-card__copy"><text>{{ item[0] }}</text><text>{{ item[1] }}</text><text>点按查看详情</text></view> <view class="list-card__copy"><text>{{ item[0] }}</text><text>{{ item[1] }}</text><text>点按查看详情</text></view>
</view> </view>
<view class="primary-action primary-action--compact" @click="completeAction"><image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ page.action }}</text></view> <AppButton block :label="page.action" @click="completeAction" />
</view> </view>
<view v-else-if="page.template === 'detail'" class="module-panel module-panel--detail"> <view v-else-if="page.template === 'detail'" class="module-panel module-panel--detail">
@@ -49,7 +48,7 @@
<view v-for="item in page.sections" :key="item[0]" class="detail-card"> <view v-for="item in page.sections" :key="item[0]" class="detail-card">
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" /><view><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view> <image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" /><view><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view>
</view> </view>
<view class="primary-action" @click="completeAction"><image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ page.action }}</text></view> <AppButton block :label="page.action" @click="completeAction" />
</view> </view>
</view> </view>
@@ -58,7 +57,7 @@
<image src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" /> <image src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" />
<view><text> {{ index + 1 }} </text><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view> <view><text> {{ index + 1 }} </text><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view>
</view> </view>
<view class="primary-action primary-action--compact" @click="completeAction"><image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ page.action }}</text></view> <AppButton block :label="page.action" @click="completeAction" />
</view> </view>
<view v-else-if="page.template === 'settings'" class="module-panel"> <view v-else-if="page.template === 'settings'" class="module-panel">
@@ -68,7 +67,7 @@
<view v-for="item in page.sections" :key="item[0]" class="settings-row" @click="openPreview(item)"> <view v-for="item in page.sections" :key="item[0]" class="settings-row" @click="openPreview(item)">
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" /><view><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view><text>查看</text> <image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" /><view><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view><text>查看</text>
</view> </view>
<view class="primary-action" @click="completeAction"><image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ page.action }}</text></view> <AppButton block :label="page.action" @click="completeAction" />
</view> </view>
</view> </view>
@@ -76,7 +75,7 @@
<image class="module-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" /> <image class="module-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
<view class="module-panel__body status-card"> <view class="module-panel__body status-card">
<text class="status-card__eyebrow">{{ page.badge }} · 服务说明</text><text class="status-card__lead">{{ page.lead }}</text><text class="status-card__note">{{ normalizedNote }}</text> <text class="status-card__eyebrow">{{ page.badge }} · 服务说明</text><text class="status-card__lead">{{ page.lead }}</text><text class="status-card__note">{{ normalizedNote }}</text>
<view class="primary-action" @click="completeAction"><image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ page.action }}</text></view> <AppButton block :label="page.action" @click="completeAction" />
</view> </view>
</view> </view>
</view> </view>
@@ -86,27 +85,35 @@
<image class="module-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" /> <image class="module-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
<view class="module-panel__body status-card"> <view class="module-panel__body status-card">
<text class="status-card__eyebrow">{{ stateCopy.eyebrow }}</text><text class="status-card__lead">{{ stateCopy.title }}</text><text class="status-card__note">{{ stateCopy.copy }}</text> <text class="status-card__eyebrow">{{ stateCopy.eyebrow }}</text><text class="status-card__lead">{{ stateCopy.title }}</text><text class="status-card__note">{{ stateCopy.copy }}</text>
<view class="primary-action" @click="moduleState = 'ready'"><image :src="moduleState === 'error' ? '/static/assets/foundation/opaque/a01-secondary-button.png' : '/static/assets/foundation/opaque/a01-primary-button.png'" mode="scaleToFill" /><text :class="{ 'primary-action__secondary': moduleState === 'error' }">{{ stateCopy.action }}</text></view> <AppButton block :type="moduleState === 'error' ? 'secondary' : 'primary'" :label="stateCopy.action" @click="moduleState = 'ready'" />
</view> </view>
</view> </view>
</view> </view>
<AppTabbar v-if="page.tab" :active="page.tab" /> <AppTabbar v-if="page.tab" :active="page.tab" />
<AppToast :visible="toastVisible" :message="toastMessage" />
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, ref } from 'vue' import { computed, onUnmounted, ref } from 'vue'
import AppButton from '@/components/AppButton.vue'
import PageHeader from '@/components/PageHeader.vue' import PageHeader from '@/components/PageHeader.vue'
import AppTabbar from '@/components/AppTabbar.vue' import AppTabbar from '@/components/AppTabbar.vue'
import AppToast from '@/components/AppToast.vue'
import ModulePageBackground from '@/components/ModulePageBackground.vue'
import { pageCatalog } from '@/data/page-catalog.js' import { pageCatalog } from '@/data/page-catalog.js'
const props = defineProps({ pageId: { type: String, required: true } }) const props = defineProps({ pageId: { type: String, required: true } })
const page = computed(() => pageCatalog[props.pageId]) const page = computed(() => pageCatalog[props.pageId])
const moduleKey = computed(() => ({ f: 'family', r: 'records', n: 'notification', m: 'profile' }[props.pageId[0]] || 'profile'))
const query = (() => { const query = (() => {
if (typeof location !== 'undefined') return Object.fromEntries(new URLSearchParams(location.hash.split('?')[1] || '')) if (typeof location !== 'undefined') return Object.fromEntries(new URLSearchParams(location.hash.split('?')[1] || ''))
const pages = getCurrentPages(); return pages[pages.length - 1]?.options || {} const pages = getCurrentPages(); return pages[pages.length - 1]?.options || {}
})() })()
const moduleState = ref('ready') const moduleState = ref('ready')
const toastVisible = ref(false)
const toastMessage = ref('')
let toastTimer = null
if (query.state === 'empty' || query.state === 'success' || query.state === 'error') moduleState.value = query.state if (query.state === 'empty' || query.state === 'success' || query.state === 'error') moduleState.value = query.state
const normalizedNote = computed(() => (page.value.note || '').replace(/待接入|服务待接入|功能待接入/g, '将在后续功能阶段开放')) const normalizedNote = computed(() => (page.value.note || '').replace(/待接入|服务待接入|功能待接入/g, '将在后续功能阶段开放'))
const stateCopy = computed(() => ({ const stateCopy = computed(() => ({
@@ -115,13 +122,17 @@ const stateCopy = computed(() => ({
error: { eyebrow: '内容暂不可用', title: `暂时无法打开${page.value.title}`, copy: '请稍后重新进入,现有资料不会受到影响。', action: '重新查看' } error: { eyebrow: '内容暂不可用', title: `暂时无法打开${page.value.title}`, copy: '请稍后重新进入,现有资料不会受到影响。', action: '重新查看' }
}[moduleState.value] || {})) }[moduleState.value] || {}))
const completeAction = () => { moduleState.value = 'success' } const completeAction = () => { moduleState.value = 'success' }
const openPreview = (item) => uni.showToast({ title: item[0], icon: 'none' }) const openPreview = (item) => {
toastMessage.value = item[0]
toastVisible.value = true
if (toastTimer) clearTimeout(toastTimer)
toastTimer = setTimeout(() => { toastVisible.value = false; toastTimer = null }, 1800)
}
onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.module-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; } .module-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; }
.module-page__paper { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }
.module-page__footer { position: absolute; right: 0; bottom: 0; left: 0; z-index: 1; width: 100%; opacity: .12; pointer-events: none; }
.module-page__header, .module-page__content { position: relative; z-index: 2; } .module-page__header, .module-page__content { position: relative; z-index: 2; }
.module-page__content { padding: 20rpx 24rpx 50rpx; } .module-page__content { padding: 20rpx 24rpx 50rpx; }
.page-intro { display: flex; align-items: center; min-height: 86rpx; margin: 0 8rpx 14rpx; } .page-intro { display: flex; align-items: center; min-height: 86rpx; margin: 0 8rpx 14rpx; }
@@ -135,15 +146,12 @@ const openPreview = (item) => uni.showToast({ title: item[0], icon: 'none' })
.module-heading { display: block; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 34rpx; font-weight: 700; } .module-heading { display: block; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 34rpx; font-weight: 700; }
.module-copy { display: block; margin-top: 8rpx; color: $ink-muted; font-size: 20rpx; line-height: 1.5; } .module-copy { display: block; margin-top: 8rpx; color: $ink-muted; font-size: 20rpx; line-height: 1.5; }
.form-row { position: relative; height: 76rpx; margin-top: 12rpx; } .form-row { position: relative; height: 76rpx; margin-top: 12rpx; }
.form-row image, .detail-card image, .settings-row image, .list-card > image, .timeline-row > image, .primary-action image { position: absolute; inset: 0; width: 100%; height: 100%; } .form-row image, .detail-card image, .settings-row image, .list-card > image, .timeline-row > image { position: absolute; inset: 0; width: 100%; height: 100%; }
.form-row > text { position: absolute; top: 26rpx; left: 22rpx; z-index: 1; color: $ink; font-size: 21rpx; font-weight: 700; } .form-row > text { position: absolute; top: 26rpx; left: 22rpx; z-index: 1; color: $ink; font-size: 21rpx; font-weight: 700; }
.form-row input { position: absolute; top: 0; right: 18rpx; bottom: 0; left: 164rpx; z-index: 1; height: 76rpx; color: $ink; font-size: 21rpx; line-height: 76rpx; } .form-row input { position: absolute; top: 0; right: 18rpx; bottom: 0; left: 164rpx; z-index: 1; height: 76rpx; color: $ink; font-size: 21rpx; line-height: 76rpx; }
.form-placeholder { color: #a79884; } .form-placeholder { color: #a79884; }
.form-note { display: block; margin-top: 14rpx; color: $ink-muted; font-size: 19rpx; text-align: center; } .form-note { display: block; margin-top: 14rpx; color: $ink-muted; font-size: 19rpx; text-align: center; }
.primary-action { position: relative; width: 100%; height: 76rpx; margin-top: 18rpx; } .module-form > .app-button, .module-detail > .app-button, .module-settings > .app-button, .content-stack > .app-button { margin-top: 18rpx; }
.primary-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; letter-spacing: 2rpx; }
.primary-action .primary-action__secondary { color: $ink; }
.primary-action--compact { width: 100%; }
.content-stack { display: flex; flex-direction: column; gap: 14rpx; } .content-stack { display: flex; flex-direction: column; gap: 14rpx; }
.list-card, .timeline-row { position: relative; width: 100%; height: calc((100vw - 24px) * .34286); min-height: 220rpx; } .list-card, .timeline-row { position: relative; width: 100%; height: calc((100vw - 24px) * .34286); min-height: 220rpx; }
.list-card__copy { position: absolute; inset: 19% 9%; z-index: 1; } .list-card__copy { position: absolute; inset: 19% 9%; z-index: 1; }
@@ -171,7 +179,7 @@ const openPreview = (item) => uni.showToast({ title: item[0], icon: 'none' })
.status-card__eyebrow { display: block; color: $brand-red; font-size: 21rpx; letter-spacing: 3rpx; } .status-card__eyebrow { display: block; color: $brand-red; font-size: 21rpx; letter-spacing: 3rpx; }
.status-card__lead { display: block; margin-top: 14rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 35rpx; font-weight: 700; } .status-card__lead { display: block; margin-top: 14rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 35rpx; font-weight: 700; }
.status-card__note { display: block; margin-top: 17rpx; color: $ink-muted; font-size: 21rpx; line-height: 1.65; } .status-card__note { display: block; margin-top: 17rpx; color: $ink-muted; font-size: 21rpx; line-height: 1.65; }
.status-card .primary-action { width: 420rpx; max-width: 100%; margin: 30rpx auto 0; } .status-card .app-button { margin: 30rpx auto 0; }
.module-page__content--state { padding-top: 18rpx; } .module-page__content--state { padding-top: 18rpx; }
@media (min-width: 400px) { .module-page__content { padding-right: 32rpx; padding-left: 32rpx; } } @media (min-width: 400px) { .module-page__content { padding-right: 32rpx; padding-left: 32rpx; } }
</style> </style>
+6 -4
View File
@@ -8,7 +8,9 @@
<view v-if="root" class="header-icon-button" @click="$emit('brand')"> <view v-if="root" class="header-icon-button" @click="$emit('brand')">
<image class="header-logo" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" /> <image class="header-logo" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
</view> </view>
<view v-else class="header-back" @click="goBack">{{ backLabel }}</view> <view v-else class="header-back" hover-class="header-back--pressed" @click="goBack">
<image class="header-back__icon" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
</view>
</view> </view>
<text class="header-title">{{ title }}</text> <text class="header-title">{{ title }}</text>
@@ -27,7 +29,6 @@
const props = defineProps({ const props = defineProps({
title: { type: String, required: true }, title: { type: String, required: true },
action: { type: String, default: '' }, action: { type: String, default: '' },
backLabel: { type: String, default: '返回' },
root: { type: Boolean, default: false }, root: { type: Boolean, default: false },
unreadCount: { type: Number, default: 0 } unreadCount: { type: Number, default: 0 }
}) })
@@ -114,13 +115,14 @@ const goBack = () => uni.navigateBack()
background: #fff9ed; background: #fff9ed;
} }
.header-back,
.header-action { .header-action {
width: 100%; width: 100%;
font-size: 27rpx; font-size: 27rpx;
} }
.header-back { text-align: left; } .header-back { display: flex; width: 88rpx; height: 88rpx; align-items: center; justify-content: flex-start; }
.header-back__icon { width: 42rpx; height: 42rpx; filter: brightness(0) invert(1); transform: scaleX(-1); }
.header-back--pressed { opacity: .62; }
.header-action { color: #ffe3a7; text-align: right; } .header-action { color: #ffe3a7; text-align: right; }
.header-title { .header-title {
+15 -24
View File
@@ -6,7 +6,7 @@
'form-state--conflict': formState === 'conflict', 'form-state--conflict': formState === 'conflict',
'form-state--error': formState === 'error' 'form-state--error': formState === 'error'
}"> }">
<image class="member-form-page__paper" src="/static/assets/foundation/opaque/page-paper.jpg" mode="aspectFill" /> <ModulePageBackground module="tree" />
<view class="member-form-page__header"><PageHeader :title="config.pageTitle" /></view> <view class="member-form-page__header"><PageHeader :title="config.pageTitle" /></view>
<view class="member-form-panel"> <view class="member-form-panel">
<image class="member-form-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" /> <image class="member-form-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
@@ -22,7 +22,7 @@
</view> </view>
<text class="member-form__note">{{ config.note }}</text> <text class="member-form__note">{{ config.note }}</text>
<view class="member-form-action" @click="saveForm"> <view class="member-form-action" @click="saveForm">
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ config.action }}</text> <image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>{{ config.action }}</text>
</view> </view>
</view> </view>
@@ -32,36 +32,33 @@
<text class="member-form__copy">{{ resultCopy.copy }}</text> <text class="member-form__copy">{{ resultCopy.copy }}</text>
<view v-if="formState === 'conflict'" class="conflict-actions"> <view v-if="formState === 'conflict'" class="conflict-actions">
<view class="member-form-action" @click="formState = 'form'"> <view class="member-form-action" @click="formState = 'form'">
<image src="/static/assets/foundation/opaque/a01-secondary-button.png" mode="scaleToFill" /><text class="member-form-action__secondary">返回核对</text> <image src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png" mode="aspectFit" /><text class="member-form-action__secondary">返回核对</text>
</view> </view>
<view class="member-form-action" @click="showConflictHelp"> <view class="member-form-action" @click="showConflictHelp">
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>查看冲突</text> <image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>查看冲突</text>
</view> </view>
</view> </view>
<view v-else class="member-form-action" @click="formState = 'form'"> <view v-else class="member-form-action" @click="formState = 'form'">
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>{{ formState === 'success' ? '继续完善' : '重新填写' }}</text> <image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>{{ formState === 'success' ? '继续完善' : '重新填写' }}</text>
</view> </view>
</view> </view>
</view> </view>
<view v-if="conflictHelpVisible" class="conflict-help" @click.self="conflictHelpVisible = false"> <AppDialog
<view class="conflict-help__card"> :visible="conflictHelpVisible"
<image class="conflict-help__skin" src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" /> eyebrow="关系校验规则"
<view class="conflict-help__content"> title="关系冲突说明"
<text class="member-form__eyebrow">关系校验规则</text> message="同一成员不能同时存在两个生父,也不能形成上下代循环。请返回世系树核对原关系后再调整。"
<text class="member-form__title">关系冲突说明</text> @confirm="conflictHelpVisible = false"
<text class="member-form__copy">同一成员不能同时存在两个生父也不能形成上下代循环请返回世系树核对原关系后再调整</text> @close="conflictHelpVisible = false"
<view class="member-form-action conflict-help__action" @click="conflictHelpVisible = false"> />
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><text>我知道了</text>
</view>
</view>
</view>
</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import AppDialog from '@/components/AppDialog.vue'
import ModulePageBackground from '@/components/ModulePageBackground.vue'
import PageHeader from '@/components/PageHeader.vue' import PageHeader from '@/components/PageHeader.vue'
const props = defineProps({ kind: { type: String, required: true } }) const props = defineProps({ kind: { type: String, required: true } })
@@ -124,7 +121,6 @@ const showConflictHelp = () => { conflictHelpVisible.value = true }
<style scoped lang="scss"> <style scoped lang="scss">
.member-form-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; } .member-form-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; }
.member-form-page__paper { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }
.member-form-page__header { position: relative; z-index: 3; } .member-form-page__header { position: relative; z-index: 3; }
.member-form-panel { position: relative; z-index: 2; width: calc(100% - 32rpx); height: min(640px, calc((100vw - 16px) * 1.48)); margin: 18rpx auto 0; } .member-form-panel { position: relative; z-index: 2; width: calc(100% - 32rpx); height: min(640px, calc((100vw - 16px) * 1.48)); margin: 18rpx auto 0; }
.member-form-panel__skin { position: absolute; inset: 0; width: 100%; height: 100%; } .member-form-panel__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
@@ -148,10 +144,5 @@ const showConflictHelp = () => { conflictHelpVisible.value = true }
.member-form-result > .member-form-action { width: 420rpx; max-width: 100%; margin: 32rpx auto 0; } .member-form-result > .member-form-action { width: 420rpx; max-width: 100%; margin: 32rpx auto 0; }
.conflict-actions { display: flex; gap: 14rpx; margin-top: 30rpx; } .conflict-actions { display: flex; gap: 14rpx; margin-top: 30rpx; }
.conflict-actions .member-form-action { width: calc(50% - 7rpx); margin-top: 0; } .conflict-actions .member-form-action { width: calc(50% - 7rpx); margin-top: 0; }
.conflict-help { position: fixed; inset: 0; z-index: 20; display: flex; align-items: center; justify-content: center; padding: 32rpx; background: rgba(34, 20, 13, .58); }
.conflict-help__card { position: relative; width: min(620rpx, 92vw); height: 390rpx; }
.conflict-help__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.conflict-help__content { position: absolute; inset: 62rpx 56rpx 46rpx; text-align: center; }
.conflict-help__action { margin-top: 30rpx; }
@media (min-width: 400px) { .member-form-panel { width: calc(100% - 48rpx); } } @media (min-width: 400px) { .member-form-panel { width: calc(100% - 48rpx); } }
</style> </style>
@@ -1,7 +1,9 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"page": "G01", "page": "G01",
"status": "candidates-pending-user-selection", "status": "long-flagship-selected-for-genealogy-module",
"selectedCandidateId": "g01-list-background-long-flagship",
"runtimeOutput": "static/assets/modules/genealogy/opaque/genealogy-page-background-long.png",
"generatedDate": "2026-07-16", "generatedDate": "2026-07-16",
"referenceAssets": [ "referenceAssets": [
"static/assets/foundation/transparent/footer-mountain-bamboo.png", "static/assets/foundation/transparent/footer-mountain-bamboo.png",
@@ -44,6 +46,17 @@
"processingMode": "opaque-paper", "processingMode": "opaque-paper",
"sourcePixels": { "width": 1024, "height": 1536 }, "sourcePixels": { "width": 1024, "height": 1536 },
"prompt": "1024x1536 纵向 G01 列表背景独立资产;暖宣纸底,顶部 5% 即出现云气、枝叶和淡金竹影,左侧约 40% 高度放置亭台,山水从中部连续延伸到底部,中央低对比度;无文字、无 UI、无卡片、无标志、无红色。" "prompt": "1024x1536 纵向 G01 列表背景独立资产;暖宣纸底,顶部 5% 即出现云气、枝叶和淡金竹影,左侧约 40% 高度放置亭台,山水从中部连续延伸到底部,中央低对比度;无文字、无 UI、无卡片、无标志、无红色。"
},
{
"id": "g01-list-background-long-flagship",
"title": "旗舰长屏连续云竹亭台宣纸景",
"imageGenSource": "docs/design/assets/g01-background/masters/genealogy-page-background-long-flagship-imagegen-source.png",
"master": "docs/design/assets/g01-background/masters/genealogy-page-background-long-flagship-master.png",
"generatedOutput": "design-pipeline/generated/g01-background/genealogy-page-background-long-flagship.png",
"processingMode": "opaque-paper-resize",
"sourcePixels": { "width": 1536, "height": 3840 },
"outputPixels": { "width": 1440, "height": 3600 },
"prompt": "1536x3840 纵向 G 类型共享连续长背景;延续 C 方向的暖灰宣纸、淡金竹影和灰褐水墨,顶部疏竹淡云作为短屏安全裁切区,中段低对比留给页面内容,下半部连续布置亭台、湖面、远山和岸边竹影;无文字、无 Logo、无 UI、无卡片、无按钮、无红色元素、无横向接缝、无重复图案。"
} }
] ]
} }
@@ -10,8 +10,8 @@ const manifestPath = path.join(pipelineDirectory, 'manifests', 'g01-background-c
const reportPath = path.join(pipelineDirectory, 'generated', 'g01-background', 'build-report.json') const reportPath = path.join(pipelineDirectory, 'generated', 'g01-background', 'build-report.json')
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
if (manifest.schemaVersion !== 1 || manifest.page !== 'G01' || manifest.candidates?.length !== 3) { if (manifest.schemaVersion !== 1 || manifest.page !== 'G01' || manifest.candidates?.length !== 4) {
throw new Error('G01 background manifest must declare exactly three schema v1 candidates') throw new Error('G01 background manifest must declare exactly four schema v1 candidates')
} }
for (const candidate of manifest.candidates) { for (const candidate of manifest.candidates) {
@@ -35,9 +35,11 @@ def process_chroma_image(source: Image.Image, config: dict[str, Any]) -> Image.I
return output return output
def process_opaque_image(source: Image.Image) -> Image.Image: def process_opaque_image(source: Image.Image, output_size: tuple[int, int] | None = None) -> Image.Image:
"""Normalize a paper-backed master to deterministic opaque RGBA.""" """Normalize a paper-backed master to deterministic opaque RGBA."""
output = source.convert("RGBA") output = source.convert("RGBA")
if output_size is not None and output.size != output_size:
output = output.resize(output_size, Image.Resampling.LANCZOS)
output.putalpha(255) output.putalpha(255)
return output return output
@@ -83,8 +85,12 @@ def build(manifest_path: Path, workspace: Path, report_path: Path) -> list[dict[
) )
if candidate["processingMode"] == "chroma-key": if candidate["processingMode"] == "chroma-key":
output = process_chroma_image(opened, chroma) output = process_chroma_image(opened, chroma)
elif candidate["processingMode"] == "opaque-paper": elif candidate["processingMode"] in {"opaque-paper", "opaque-paper-resize"}:
output = process_opaque_image(opened) output_size = None
if candidate["processingMode"] == "opaque-paper-resize":
target = candidate["outputPixels"]
output_size = (target["width"], target["height"])
output = process_opaque_image(opened, output_size)
else: else:
raise ValueError(f"unsupported processingMode: {candidate['processingMode']}") raise ValueError(f"unsupported processingMode: {candidate['processingMode']}")
@@ -9,20 +9,22 @@ const workspace = path.resolve(testDirectory, '..', '..')
const manifestPath = path.join(workspace, 'design-pipeline', 'manifests', 'g01-background-candidates.json') const manifestPath = path.join(workspace, 'design-pipeline', 'manifests', 'g01-background-candidates.json')
const wrapperPath = path.join(workspace, 'design-pipeline', 'scripts', 'build-g01-backgrounds.mjs') const wrapperPath = path.join(workspace, 'design-pipeline', 'scripts', 'build-g01-backgrounds.mjs')
test('G01 background candidate manifest preserves three portable masters', async () => { test('G01 background candidate manifest preserves four portable masters and selects the approved long background', async () => {
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) const manifest = JSON.parse(await readFile(manifestPath, 'utf8'))
assert.equal(manifest.schemaVersion, 1) assert.equal(manifest.schemaVersion, 1)
assert.equal(manifest.page, 'G01') assert.equal(manifest.page, 'G01')
assert.equal(manifest.status, 'candidates-pending-user-selection') assert.equal(manifest.status, 'long-flagship-selected-for-genealogy-module')
assert.equal(manifest.candidates.length, 3) assert.equal(manifest.selectedCandidateId, 'g01-list-background-long-flagship')
assert.equal(manifest.runtimeOutput, 'static/assets/modules/genealogy/opaque/genealogy-page-background-long.png')
assert.equal(manifest.candidates.length, 4)
const ids = new Set() const ids = new Set()
for (const candidate of manifest.candidates) { for (const candidate of manifest.candidates) {
assert.ok(!ids.has(candidate.id), `duplicate candidate id: ${candidate.id}`) assert.ok(!ids.has(candidate.id), `duplicate candidate id: ${candidate.id}`)
ids.add(candidate.id) ids.add(candidate.id)
assert.match(candidate.prompt, /1024x1536/) assert.match(candidate.prompt, /\d+x\d+/)
assert.deepEqual(candidate.sourcePixels, { width: 1024, height: 1536 }) assert.ok(candidate.sourcePixels.width > 0 && candidate.sourcePixels.height > 0)
assert.ok(['chroma-key', 'opaque-paper'].includes(candidate.processingMode)) assert.ok(['chroma-key', 'opaque-paper', 'opaque-paper-resize'].includes(candidate.processingMode))
for (const key of ['master', 'generatedOutput']) { for (const key of ['master', 'generatedOutput']) {
const absolute = path.resolve(workspace, candidate[key]) const absolute = path.resolve(workspace, candidate[key])
@@ -30,10 +32,16 @@ test('G01 background candidate manifest preserves three portable masters', async
assert.ok(!relative.startsWith('..') && !path.isAbsolute(relative), `${key} escapes workspace`) assert.ok(!relative.startsWith('..') && !path.isAbsolute(relative), `${key} escapes workspace`)
} }
} }
const selected = manifest.candidates.find(({ id }) => id === manifest.selectedCandidateId)
assert.deepEqual(selected.sourcePixels, { width: 1536, height: 3840 })
assert.deepEqual(selected.outputPixels, { width: 1440, height: 3600 })
assert.equal(selected.processingMode, 'opaque-paper-resize')
}) })
test('G01 Node wrapper delegates pixel decoding to locked Pillow without Sharp', async () => { test('G01 Node wrapper delegates pixel decoding to locked Pillow without Sharp', async () => {
const wrapper = await readFile(wrapperPath, 'utf8') const wrapper = await readFile(wrapperPath, 'utf8')
assert.doesNotMatch(wrapper, /from ['"]sharp['"]/) assert.doesNotMatch(wrapper, /from ['"]sharp['"]/)
assert.match(wrapper, /build_g01_backgrounds\.py/) assert.match(wrapper, /build_g01_backgrounds\.py/)
assert.match(wrapper, /candidates\?\.length !== 4/)
}) })
@@ -43,6 +43,13 @@ class G01BackgroundBuilderTests(unittest.TestCase):
self.assertEqual("RGBA", output.mode) self.assertEqual("RGBA", output.mode)
self.assertEqual((240, 235, 220, 255), output.getpixel((0, 0))) self.assertEqual((240, 235, 220, 255), output.getpixel((0, 0)))
def test_opaque_master_can_be_resized_to_locked_runtime_dimensions(self):
source = Image.new("RGB", (2, 3), (240, 235, 220))
output = process_opaque_image(source, (4, 6))
self.assertEqual((4, 6), output.size)
self.assertEqual("RGBA", output.mode)
self.assertEqual((240, 235, 220, 255), output.getpixel((3, 5)))
def test_saved_png_declares_srgb_color_profile(self): def test_saved_png_declares_srgb_color_profile(self):
with tempfile.TemporaryDirectory() as directory: with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "asset.png" path = Path(directory) / "asset.png"
+195 -1
View File
@@ -1,3 +1,60 @@
# Design QA — G 模块公共 C 背景贴底自适应
- source visual truth path: `design-pipeline/generated/g01-background/g01-list-background-direction-c.png`
- implementation screenshot path: `docs/design/screens/runtime/2026-07-16/G01-shared-c-bottom-412x915.png`;超高屏压力证据 `G01-shared-c-bottom-412x1000.png`
- viewport: 412×915;另查 320×568、360×640、360×800,并增加 412×1000 压力测试
- state: G01 默认三分组列表,公共 C 背景按宽度等比完整显示并固定贴底,上方宣纸底承接
- full-view comparison evidence: 同一比较输入内打开 C 原始输出与 412×915 真实 H5 截图,核对左右竹枝、左侧亭台、下方山水均完整保留并落在列表下半区,没有 `aspectFill` 横向裁剪
- focused region comparison evidence: 另查 320×568、360×640、360×800、412×1000,确认图片宽度始终等于视口宽度、高度保持 2:3、底边贴合视口底部,高屏多出的宣纸区位于上方且不纵向拉伸
## Findings
当前没有公共 C 背景贴底自适应相关的 P0/P1/P2 问题。
- 字体与层级:背景没有烘焙文字,页面原有标题、卡片和状态文字保持独立节点;中央低对比留白没有削弱主要层级。
- 间距与布局:背景固定贴底后,亭台、湖面和山体承托“我创建的”“我加入的”“加入申请”区域;高屏多出的宣纸区移动到当前家谱卡片和快捷入口附近,主要被内容覆盖。内容与 Tabbar 尺寸未改变,四档视口没有新增横向溢出或主操作裁切,320×568 保持自然纵向滚动。
- 色彩:暖宣纸、淡金竹影和灰褐山水与现有朱砂、古金、墨褐体系一致;审核中和被拒绝状态色仍可识别。
- 图片质量:公共运行资产与 C 构建输出 SHA-256 完全一致,使用 `widthFix` 保留完整 2:3 画面,没有 `aspectFill` 裁剪、`scaleToFill` 拉伸、透明边或绿幕残留。
- 文案与内容:本轮只替换背景,不改变 G01 的谱名、分组、申请状态或操作文案。
## Comparison history
1. 首轮截图只有宣纸色,定位为页面缺少公共组件显式 import;加入契约后补齐 9 页 import。
2. 第二轮顶部对齐截图完整显示左右画面,但图片结束后的纯宣纸区落在“加入申请”下半区,用户要求继续优化。
3. 第三轮只使用现有 C 图并改为固定贴底;四档截图中下半区空白显著减少,同轮源图/实现对照没有发现 P0/P1/P2 差异。
4. 用户确认额外增加 412×1000 超高屏压力测试;真实截图中新增高度落在当前家谱卡片和快捷入口附近,下方山水继续覆盖列表,未发现横向溢出、裁剪或拉伸。
## Evidence limits
本段仅记录已经被后续旗舰长背景替代的公共 C 背景贴底历史候选。固定区/独立滚动结构已在后续同日形成新的 H5 候选;G01 整页与 Android/HBuilderX 真机或模拟器仍未验收。
final result: passed
---
# Design QA — 全模块基础视觉系统(无截图验证)
- source specification: `docs/superpowers/specs/2026-07-16-global-heritage-visual-system-design.md`
- implementation handoff: `docs/design/全模块长背景与A系列控件交接.md`
- scope: 52 个活动页面的 A 系列控件统一、普通页返回箭头、G 系列共享母体,以及 T/F/R/N/M 独立长背景
- evidence mode: 用户明确要求本轮不生成截图,因此只记录可重复构建资产、静态契约和 H5 运行 smoke,不把本节冒充视觉截图审计
## Findings
- A 系列透明卷轴按钮、对话框、Toast 和自定义 Loading 已建立统一组件;活动页面与组件中的原生 Toast、Modal、Loading、ActionSheet 调用以及旧不透明 A01 按钮引用均已清零。
- G 系列继续消费既有 G01 公共长背景;G01 元数据、角色和更新时间文字提高字重与对比度。T/F/R/N/M 分别消费独立的 1440×3600 RGB 长背景,并共享浅色国风主题。
- 普通页面标题栏以真实左箭头替代“返回”文字;根页面品牌标题栏保持原结构。
- 静态契约与无截图 H5 运行 smoke 覆盖 G、T、根页面及共享 ModulePage 的主要状态、按钮事件、导航和自定义弹层。
## Evidence limits
- 本轮没有生成或比较新的实现截图,不能据此宣称逐页视觉 QA 已完成。
- 52 页仍全部为内部基础候选,尚未获得用户逐页正式验收。
- Android/HBuilderX 的系统字体、状态栏、返回手势、软键盘以及 4GB 设备长图解码与回收仍待验证。
final result: contract/runtime passed (visual review and Android verification pending)
---
# Design QA — A01 卷轴祥云四件套 v3 # Design QA — A01 卷轴祥云四件套 v3
- approved visual source: `docs/design/assets/a01-vnext/A01-shared-scroll-skins-with-dialog-approved.png` - approved visual source: `docs/design/assets/a01-vnext/A01-shared-scroll-skins-with-dialog-approved.png`
@@ -456,7 +513,7 @@ final result: user H5 visual review passed for implemented states; real slider,
# Design QA — A01 红色宗祠水墨登录页 # Design QA — A01 红色宗祠水墨登录页
- source visual truth path: `docs/design/screens/handoff/2026-07-16/A01-selected-password-red-hall-ink-412x915.png``docs/design/screens/handoff/2026-07-16/A01-selected-sms-red-hall-ink-412x915.png` - source visual truth path: `docs/design/assets/a01-vnext/source/A01-layered-source-v1.psd`;长期实现代表证据为 `docs/design/screens/handoff/2026-07-16/A01-current-password-sms-contact-824x915.png`
- implementation screenshot path: `docs/design/screens/runtime/2026-07-16/A01-password-final-412x915.png``docs/design/screens/runtime/2026-07-16/A01-sms-final-412x915.png` - implementation screenshot path: `docs/design/screens/runtime/2026-07-16/A01-password-final-412x915.png``docs/design/screens/runtime/2026-07-16/A01-sms-final-412x915.png`
- viewport: 412×915;另查 320×568、360×640、360×800 - viewport: 412×915;另查 320×568、360×640、360×800
- state: 密码登录、短信验证码登录;运行 smoke 另查协议页内错误与自定义行为验证层 - state: 密码登录、短信验证码登录;运行 smoke 另查协议页内错误与自定义行为验证层
@@ -567,3 +624,140 @@ final result: passed
H5 Chrome 截图不能证明 Android 真机字体放大、读屏语义、系统返回手势、软键盘遮挡和所有触摸目标;这些项目不在本轮候选 QA 中冒充完成,留到用户视觉审核后的 Android 设备复核。 H5 Chrome 截图不能证明 Android 真机字体放大、读屏语义、系统返回手势、软键盘遮挡和所有触摸目标;这些项目不在本轮候选 QA 中冒充完成,留到用户视觉审核后的 Android 设备复核。
final result: passed (internal candidate QA only; user review pending) final result: passed (internal candidate QA only; user review pending)
# Design QA — G01 旗舰长屏公共背景
- source visual truth path: `static/assets/modules/genealogy/opaque/genealogy-page-background-long.png`
- implementation screenshot path: `docs/design/screens/runtime/2026-07-16/G01-shared-long-background-412x915.png`
- additional responsive evidence: `G01-shared-long-background-320x568.png``360x640.png``360x800.png``412x1000.png`(同目录)
- viewport: 412×915;另查 320×568、360×640、360×800、412×1000
- state: G01 默认三分组列表,公共背景固定贴底
- full-view comparison evidence: 同一比较输入内打开 1440×3600 源图与 320×568、412×915、412×1000 实现截图,核对全屏覆盖、画面连续性和响应式裁切
- focused region comparison evidence: 不另做局部裁图;横向分界线、左右边缘、顶部竹枝和底部山水在全尺寸原图及原像素实现截图中均可直接辨认
## Findings
没有遗留的 H5 P0/P1/P2 问题。
- 字体与排版:本轮未改变页面字体、字号、行高和文案;背景中央保持低对比,未压低标题、卡片和状态文字可读性。
- 间距与布局:背景层固定全屏且不参与布局;320×568 允许自然滚动,五档均无横向溢出,底栏未被背景层遮挡。
- 颜色与视觉令牌:暖灰宣纸、淡金竹影和灰褐山水连续覆盖视口;旧 C 图与 `#e7ded1` 的明显横向拼接线不再出现。
- 图片质量与资产一致性:原稿、1536×3840 母版和 1440×3600 运行图均为真实位图;`widthFix` 保证左右完整且不拉伸,短屏只裁顶部安全区。
- 文案与内容:背景无文字、Logo、UI、按钮或红色污染;页面现有业务文案未改变。
- 交互与运行:G01 空态、添加弹层、当前家谱切换、loading/error 和五档响应式 smoke 通过;截图脚本检查浏览器异常和 `console.error`
## Comparison history
1. 旧方案 P1:1024×1536 C 图固定贴底,高屏上方由纯色宣纸承接,出现明显横向分界线。
2. 当前方案:用户确认连续长图后,改用 1440×3600 单张背景覆盖最高压力档;五档复核未见分界线、左右裁切或拉伸。
## Evidence limits
当前结论只覆盖 HBuilderX H5 + Chrome 内部候选。尚未完成 Android/HBuilderX 真机或模拟器复核,也未验证 4GB Android 上约 19.8MiB 解码内存、切页和回收;本结论不表示 G01 整页验收或冻结。
## Implementation checklist
- [x] 用户确认旗舰长屏方向
- [x] 原稿、归一化母版、运行图和清单可恢复
- [x] 9 个活动 G 页面仍只使用一个共享背景组件
- [x] 五档 H5、背景静态契约和 G 页面运行 smoke
- [ ] Android/HBuilderX 与 4GB Android 图片性能复核
- [x] G01 固定区/独立滚动方向已确认并形成五档 H5 候选
- [ ] G01 整页用户验收
final result: passed
---
# Design QA — G01 固定信息区与独立列表滚动
- source visual truth path: `docs/superpowers/specs/2026-07-16-g01-fixed-header-independent-list-scroll-design.md`
- interactive design reference: `.superpowers/brainstorm/120-1784206869/content/g01-scroll-layout-comparison.html`(本机可再生成设计会话,不作为长期交接证据)
- implementation screenshot path: `docs/design/screens/runtime/2026-07-16/G01-fixed-list-scroll-412x915.png`
- additional responsive evidence: `G01-fixed-list-scroll-320x568.png``360x640.png``360x800.png``412x1000.png`(同目录)
- viewport: 412×915;另查 320×568、360×640、360×800、412×1000
- state: 已有家谱正常态,列表滚动 135px;另查添加家谱弹层、切换家谱回顶、loading、error、empty
- full-view comparison evidence: 同一比较输入内打开改前 412×915、改后 320×568、改后 412×915 和改后 412×1000,核对固定范围、列表裁切边界、底栏和背景连续性
- focused region comparison evidence: 固定区和列表边界在原像素全页图中可直接辨认;运行 smoke 另以元素矩形坐标验证滚动前后固定元素位移不超过 1px,因此不另建局部裁图
## Findings
没有遗留的 H5 P0/P1/P2 问题。
- 字体与排版:标题、当前谱名、快捷入口、分组标题、卡片和申请状态的字体、字号与行高未改变;320×568 未压缩字号。
- 间距与布局:标题栏、当前家谱卡、四快捷入口、金色分隔线和底部导航固定;三组标题、卡片及“添加家谱”在单一滚动区内移动。加入申请相邻卡继续保持 `12rpx`
- 颜色与视觉令牌:未改变朱砂、古金、暖宣纸和状态色;滚动边界没有增加阴影、渐变或新的视觉层。
- 图片质量与资产一致性:公共 1440×3600 长背景继续固定全屏,不随内部列表滚动;现有卡框、谱印、快捷图标、分隔线和底栏资产未替换、裁切或拉伸。
- 文案与内容:页面文案、分组顺序、申请状态和“添加家谱”入口未改变。
- 交互与运行:五档运行 smoke 验证固定元素位置变化不超过 1px、列表内容确实位移、滚到底可完整点击“添加家谱”;弹层开关保持列表位置,切换当前家谱后回顶。loading、error、empty 保持原布局。
## Comparison history
1. 改前:整个 G01 内容随页面滚动,当前家谱与四快捷入口会离开视口。
2. 可操作示意稿:用户选择“上方固定、下方列表独立滚动”,并确认异常状态继续使用原布局。
3. 实现后:五档 H5 均形成可计算的列表高度,固定区不移动;未发现横向溢出、底栏遮挡或滚动穿透。
## Evidence limits
当前结论只覆盖 HBuilderX H5 + Chrome 内部候选。Android/HBuilderX 真机或模拟器上的惯性、短暂系统滚动指示条、状态栏、安全区、返回现场和 4GB 设备性能仍未验证;用户已查看本次实际 H5 实现并确认共享背景浓度,但未给出 G01 整页正式验收,因此不表示 G01 已验收或冻结。
## Implementation checklist
- [x] 正常态上方固定、列表单独滚动
- [x] loading/error/empty 保持原布局
- [x] 五档尺寸、弹层位置保持、切换家谱回顶、底部入口可达
- [x] 公共背景图片画层按后续确认统一调整为 28% 不透明度,页面结构与背景资产不变
- [x] 用户查看实际 H5 页面
- [ ] Android/HBuilderX 与 4GB Android 性能复核
- [ ] G01 整页验收
final result: passed
---
# Design QA — G 模块公共背景 28% 不透明度
- source visual truth path: `docs/superpowers/specs/2026-07-16-genealogy-background-opacity-design.md`
- implementation screenshot path: `docs/design/screens/runtime/2026-07-16/G01-fixed-list-visible-audit-412x915.png`
- additional responsive evidence: `G01-fixed-list-scroll-320x568.png``360x640.png``360x800.png``412x915.png``412x1000.png`(同目录)
- viewport: 412×915 初始位置;另查五档滚动状态
- state: G01 已有家谱正常态,共享长背景图片画层使用 `opacity: 0.28`
## Findings
没有遗留的 H5 P0/P1/P2 问题。
- 视觉层级:竹枝、山水和亭台退为淡水印,标题、卡框、文字、状态色和底部导航保持原有不透明度。
- 布局与图片:背景继续 `widthFix` 固定贴底,左右完整显示;五档未新增横向溢出、接缝、底栏遮挡或滚动结构变化。
- 组件边界:透明度由 `GenealogyPageBackground.vue` 单一持有,9 个活动 G 页面没有页面级覆盖;运行 PNG 和可恢复母版未重制。
- 运行:共享背景契约完成 RED/GREEN/回归证明;G01、G03、G05、G06、G08G10、G11G12 六组运行 smoke 通过。
## Evidence limits
当前仅覆盖 H5 + Chrome 内部候选。用户确认的是 28% 背景浓度,不等于 G01 或其他 G 页面整页验收;Android/HBuilderX 与 4GB Android 长图解码、切页和回收仍待复核。
final result: passed (internal candidate QA only)
---
# Design QA — G01 申请次要文字可读性
- source visual truth path: `docs/superpowers/specs/2026-07-16-g01-secondary-text-legibility-design.md`
- before screenshot: `docs/design/screens/runtime/2026-07-16/g01-text-legibility-audit/01-current-list.png`
- first-round screenshot: `docs/design/screens/runtime/2026-07-16/g01-text-legibility-audit/02-enhanced-list.png`
- final B screenshot: `docs/design/screens/runtime/2026-07-16/g01-text-legibility-audit/03-b-medium-weight.png`
- viewport: 412×915;另查 320×568、360×640、360×800、412×1000
- state: G01 已有家谱正常态,内部列表滚到“加入申请”区域
## Findings
没有遗留的 H5 P0/P1/P2 问题。
- 说明文字首轮从 `23rpx / #776b5c` 调整为 `26rpx / #62584c / 1.4`,用户认为变化仍不明显;最终 B 方案为 `28rpx / 500 / #62584c / 1.4`412px 视口实测约 15.38px,估算对比度在宣纸与背景承接色上约为 6.13:1 和 5.22:1。
- 状态文字首轮从 `23rpx / 400` 调整为 `25rpx / 500`,最终 B 方案为 `27rpx / 600`412px 视口实测约 14.83px;审核中使用 `#7f4f16`,估算对比度约为 6.11:1 和 5.20:1;被拒绝保留语义红,已退出使用更深 muted 色。
- 五档截图未见说明与状态/箭头重叠,卡片高度、12rpx 连续间距、固定区、独立滚动和底栏均未改变。
- 共享 `GenealogyCard.vue` 的“更新于”等文字不在本轮范围,未随手修改。
## Evidence limits
对比度为基于 CSS 色值和两种代表底色的估算,不能代替 Android 真机面板、户外亮度、系统字体缩放或读屏复核;本轮不表示 G01 整页已验收。
final result: passed (internal candidate QA only)
---
@@ -6,9 +6,8 @@
## 1. 已归档的美术锚点 ## 1. 已归档的美术锚点
- 文件:[`screens/A01-启动登录引导-效果稿-v1-显式协议.png`](screens/A01-启动登录引导-效果稿-v1-显式协议.png) - v1–v3 早期整页效果稿已于 2026-07-17 上传前清理;历史入口结构保留在本记录与项目外完整归档中,不再作为当前方案依据。
- 效果稿 v2[`screens/A01-启动登录引导-效果稿-v2-独立注册.png`](screens/A01-启动登录引导-效果稿-v2-独立注册.png),记录此前“四个独立按钮”的尝试;该入口结构已失效,不作当前方案依据 - 当前可恢复美术源:[`assets/a01-vnext/source/A01-layered-source-v1.psd`](assets/a01-vnext/source/A01-layered-source-v1.psd) 与 [`a01-psd-manifest.json`](assets/a01-vnext/source/a01-psd-manifest.json)
- 当前效果稿 v3[`screens/A01-启动登录引导-效果稿-v3-文字注册入口.png`](screens/A01-启动登录引导-效果稿-v3-文字注册入口.png),保留第 2 节的入口层级归档;它不再定义运行时像素坐标。
- 当前运行时视觉基准:[`screens/A01-启动登录引导-栅格验收-412x915.png`](screens/A01-启动登录引导-栅格验收-412x915.png),用于 A-01 协议未勾选状态的同尺寸像素比对;`pages/auth/entry.vue` 必须使用真实页面结构还原,不得将整张效果图作为页面内容。 - 当前运行时视觉基准:[`screens/A01-启动登录引导-栅格验收-412x915.png`](screens/A01-启动登录引导-栅格验收-412x915.png),用于 A-01 协议未勾选状态的同尺寸像素比对;`pages/auth/entry.vue` 必须使用真实页面结构还原,不得将整张效果图作为页面内容。
- 用途:保留本轮已确认的“家祠卷轴”欢迎页美术方向,不作为可直接还原的代码布局图,也不作为功能图标资产。 - 用途:保留本轮已确认的“家祠卷轴”欢迎页美术方向,不作为可直接还原的代码布局图,也不作为功能图标资产。
- 画面方向:朱砂家祠红头、暖宣纸主体、古金细线与云纹、墨褐标题、两侧淡墨建筑/竹影与底部山水。登录欢迎页后续必须使用独立的红头/宣纸背景组合,不复用 G01 的列表页大山水底图。 - 画面方向:朱砂家祠红头、暖宣纸主体、古金细线与云纹、墨褐标题、两侧淡墨建筑/竹影与底部山水。登录欢迎页后续必须使用独立的红头/宣纸背景组合,不复用 G01 的列表页大山水底图。
@@ -59,7 +58,7 @@
| 资产 | 用途 | 来源或生成提示 | 尺寸与验收 | | 资产 | 用途 | 来源或生成提示 | 尺寸与验收 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `docs/design/screens/A01-启动登录引导-效果稿-v3-文字注册入口.png` | A-01 视觉比对依据 | 已归档并由用户确认的 v3 效果稿 | `852 × 1846px` PNG;仅作真实 DOM 页面还原和运行时验收对照,不直接作为页面内容 | | `docs/design/assets/a01-vnext/source/A01-layered-source-v1.psd` | A-01 可恢复视觉源 | 当前分层 PSD 与清单 | 页面以真实 DOM 和导出位图还原,不把整页设计图作为页面内容 |
| `static/assets/backgrounds/auth-ancestral-header-v1.png``auth-rice-paper-v1.jpg``auth-ink-scenery-v1.png` | A-01 分层页面背景 | 2026-07-13 内置图像生成 | 当前 A-01 使用;不得复用 G-01 背景 | | `static/assets/backgrounds/auth-ancestral-header-v1.png``auth-rice-paper-v1.jpg``auth-ink-scenery-v1.png` | A-01 分层页面背景 | 2026-07-13 内置图像生成 | 当前 A-01 使用;不得复用 G-01 背景 |
| `static/assets/icons/auth/login-outline-v1.png``static/assets/icons/auth/wechat-licensed-v1.png` | A-01 两个按钮图标 | 登录图标复用项目既有资产;微信图标来源与许可见旧记录 | 当前 A-01 使用;微信图标免费使用须在应用“关于/设置”及应用商店描述中保留 Icons8 署名链接 | | `static/assets/icons/auth/login-outline-v1.png``static/assets/icons/auth/wechat-licensed-v1.png` | A-01 两个按钮图标 | 登录图标复用项目既有资产;微信图标来源与许可见旧记录 | 当前 A-01 使用;微信图标免费使用须在应用“关于/设置”及应用商店描述中保留 Icons8 署名链接 |
| `static/assets/foundation/opaque/a01-primary-button.png``a01-secondary-button.png` | A01 登录与微信登录的完整按钮皮肤 | 2026-07-13 以运行时视觉基准的比例与画风生成;不裁切设计图 | `1877 × 418px``1881 × 419px` 不透明 PNG;分别承担底色、双线边框和四角纹样,DOM 仅叠放图标与文案 | | `static/assets/foundation/opaque/a01-primary-button.png``a01-secondary-button.png` | A01 登录与微信登录的完整按钮皮肤 | 2026-07-13 以运行时视觉基准的比例与画风生成;不裁切设计图 | `1877 × 418px``1881 × 419px` 不透明 PNG;分别承担底色、双线边框和四角纹样,DOM 仅叠放图标与文案 |
@@ -1,23 +1,25 @@
# G01 列表背景候选与换机重建 # G01 列表背景候选与换机重建
> 日期:2026-07-16 > 日期:2026-07-16
> 状态:三个母版已生成并通过本机确定性构建;尚未由用户选择,不得接入页面或写成已验收 > 状态:A/B/C 历史母版与旗舰长屏母版均已保存并通过本机确定性构建;用户已确认旗舰长屏方向作为 9 个活动 G 页面的公共背景,并确认共享图片画层使用 28% 不透明度。当前仅有 H5 运行证据,不得写成任一页面整页已验收或 Android 已通过
## 1. 为什么重做 ## 1. 为什么重做
现有 `footer-mountain-bamboo.png``750×360`,视觉主体集中在底部,上半部留白较大;页面又把它定位到整个内容末尾并使用 `scaleToFill` 拉伸,因此列表变长时山水被推到“添加家谱”下方。用户提出背景应更早出现,并进一步提出 G01 可能只滚动下方列表、上方当前家谱与快捷入口固定。 现有 `footer-mountain-bamboo.png``750×360`,视觉主体集中在底部,上半部留白较大;页面又把它定位到整个内容末尾并使用 `scaleToFill` 拉伸,因此列表变长时山水被推到“添加家谱”下方。用户提出背景应更早出现,并进一步提出 G01 可能只滚动下方列表、上方当前家谱与快捷入口固定。
滚动结构暂不实施。新背景先作为列表滚动窗口候选资产保存,下一轮选择方向并确认滚动边界后再接入 连续长背景只替换各 G 页面最底层视觉。G01 固定区/独立滚动边界已在后续同日按用户确认方案单独实施,不影响其他 G 页面;具体合同见 `docs/superpowers/specs/2026-07-16-g01-fixed-header-independent-list-scroll-design.md`
## 2. 稳定输入 ## 2. 稳定输入
三个母版位于: 稳定输入位于:
- `docs/design/assets/g01-background/masters/g01-list-background-direction-a-chroma-master.png` - `docs/design/assets/g01-background/masters/g01-list-background-direction-a-chroma-master.png`
- `docs/design/assets/g01-background/masters/g01-list-background-direction-b-chroma-master.png` - `docs/design/assets/g01-background/masters/g01-list-background-direction-b-chroma-master.png`
- `docs/design/assets/g01-background/masters/g01-list-background-direction-c-paper-master.png` - `docs/design/assets/g01-background/masters/g01-list-background-direction-c-paper-master.png`
- `docs/design/assets/g01-background/masters/genealogy-page-background-long-flagship-imagegen-source.png`(原始 ImageGen 输出,793×1983
- `docs/design/assets/g01-background/masters/genealogy-page-background-long-flagship-master.png`(确定性归一化母版,1536×3840)
准确提示词、标题、处理模式、源尺寸和生成输出路径由 `design-pipeline/manifests/g01-background-candidates.json` 单一维护。A/B 使用纯 `#00ff00` 绿幕并提取透明叠加层;C 保留完整暖宣纸底。 准确提示词、标题、处理模式、源尺寸、目标尺寸、选择状态和运行输出路径由 `design-pipeline/manifests/g01-background-candidates.json` 单一维护。A/B 使用纯 `#00ff00` 绿幕并提取透明叠加层;C 与旗舰长屏图保留完整暖宣纸底。当前公共运行输出是 `static/assets/modules/genealogy/opaque/genealogy-page-background-long.png`,由 `components/GenealogyPageBackground.vue` 唯一持有路径、贴底规则和 `opacity: 0.28` 显示规则;各 G 页面不得重复覆盖。旧 `genealogy-page-background.png` 不覆盖、不删除,仅作为上一轮 C 输出保留。
## 3. 跨电脑重建 ## 3. 跨电脑重建
@@ -42,6 +44,7 @@ node --test design-pipeline/tests/g01-background-candidates.test.mjs
- `g01-list-background-direction-a.png` - `g01-list-background-direction-a.png`
- `g01-list-background-direction-b.png` - `g01-list-background-direction-b.png`
- `g01-list-background-direction-c.png` - `g01-list-background-direction-c.png`
- `genealogy-page-background-long-flagship.png`
- `build-report.json` - `build-report.json`
Node 入口只使用内置模块做路径与清单编排;图片解码、尺寸检查和像素处理由锁定版本的 Pillow 统一负责,因此 G01 候选构建不依赖已删除的 `node_modules`。项目其他使用 Sharp 的旧流水线仍需执行 `npm.cmd ci --prefix design-pipeline` Node 入口只使用内置模块做路径与清单编排;图片解码、尺寸检查和像素处理由锁定版本的 Pillow 统一负责,因此 G01 候选构建不依赖已删除的 `node_modules`。项目其他使用 Sharp 的旧流水线仍需执行 `npm.cmd ci --prefix design-pipeline`
@@ -55,8 +58,9 @@ Node 入口只使用内置模块做路径与清单编排;图片解码、尺寸
| A | chroma-key | 1024×1536 | 2,280,297 | `7b8a8abecc651b1bc4c589607780c8ff00ea0f13de018ca4b27a479b2a870fe4` | | A | chroma-key | 1024×1536 | 2,280,297 | `7b8a8abecc651b1bc4c589607780c8ff00ea0f13de018ca4b27a479b2a870fe4` |
| B | chroma-key | 1024×1536 | 1,890,556 | `3a0420eaaac9a2821da0f9a2ddbbbd6fb29b463fa6f41be7fa5c23948f782e2a` | | B | chroma-key | 1024×1536 | 1,890,556 | `3a0420eaaac9a2821da0f9a2ddbbbd6fb29b463fa6f41be7fa5c23948f782e2a` |
| C | opaque-paper | 1024×1536 | 2,407,477 | `f14aeed81046d2d5e7ae78b1e62f572689c38261a2cc043142bef9f70200dc5b` | | C | opaque-paper | 1024×1536 | 2,407,477 | `f14aeed81046d2d5e7ae78b1e62f572689c38261a2cc043142bef9f70200dc5b` |
| 旗舰长屏 | opaque-paper-resize | 1440×3600 | 6,538,134 | `9aa9ebdd231eddbc151e238ba340a00f8603f23143117ad8c93009947dcca2e2` |
张输出均满足:可见绿幕残留 0、透明 RGB 0、尺寸错误 0、体积错误 0、sRGB 警告 0。本机连续构建两次的报告与文件 SHA-256 完全一致。若另一台电脑因 Python、Pillow 或压缩实现差异产生不同文件哈希,应先核对锁定依赖,再比较解码后的 RGBA 像素和质量报告,不能只凭压缩字节差异判定视觉失败。 张输出均通过清单尺寸、处理模式和 sRGB 构建检查;A/B/C 继续保持原质量基线。旗舰长屏原始 ImageGen 输出为 2,333,286 字节、SHA-256 `3d5ada7a23edd8db98b3511d308d2d240d80fdd4ff9280a8c434b50fb68cac3a`1536×3840 归一化母版为 6,822,092 字节、SHA-256 `2fb3aa9be7772beb6f620c49f096ed6d9c045a56cfe7ae342664ee05ca58862b`。若另一台电脑因 Python、Pillow 或压缩实现差异产生不同文件哈希,应先核对锁定依赖,再比较解码后的 RGBA 像素和质量报告,不能只凭压缩字节差异判定视觉失败。
## 5. 可复现边界 ## 5. 可复现边界
@@ -66,7 +70,6 @@ Node 入口只使用内置模块做路径与清单编排;图片解码、尺寸
## 6. 下一步 ## 6. 下一步
1. 用户比较 A/B/C 后选择一个方向,或提出组合要求 1. 用户查看 G01 固定信息区/独立列表滚动的实际 H5 页面;当前五档 runtime 截图仍是内部候选证据
2. 选择后只保留一个正式母版合同,创建版本化 `static/assets/` 运行输出 2. 用户确认 G01 整页后,再保存一张长期代表证据并更新视觉证据索引
3. 再讨论并实现:标题栏、当前家谱大卡片和快捷入口固定;从“我创建的”开始独立滚动;背景固定在列表滚动窗口背后 3. 完成 Android/HBuilderX 真机或模拟器复核,并专项检查区域滚动手感及 4GB Android 上约 19.8MiB 解码内存、切页与回收
4. 完成 320×568、360×640、360×800、412×915 H5 截图和 Android/HBuilderX 复核。
@@ -25,7 +25,7 @@
| `pages/auth/a05-reset-password` | A05 重置密码 | `pages/auth/a05-reset-password.vue` | 当前 7 类 H5 视觉状态已获用户确认;接口回流手机号/焦点、真实滑动验证与 Android/HBuilderX 待审核;保持 `[~]` | | `pages/auth/a05-reset-password` | A05 重置密码 | `pages/auth/a05-reset-password.vue` | 当前 7 类 H5 视觉状态已获用户确认;接口回流手机号/焦点、真实滑动验证与 Android/HBuilderX 待审核;保持 `[~]` |
| (已从 `pages.json` 移除) | A06 登录阻断状态(封存) | `pages/auth/a06-auth-status.vue` | 用户确认当前样式没有问题,但当前流程暂时不用;源码、测试和证据保留,不计入活动验收统计,恢复时重新接入真实入口并审核 | | (已从 `pages.json` 移除) | A06 登录阻断状态(封存) | `pages/auth/a06-auth-status.vue` | 用户确认当前样式没有问题,但当前流程暂时不用;源码、测试和证据保留,不计入活动验收统计,恢复时重新接入真实入口并审核 |
| `pages/auth/a02-login` | A02 账号登录 | `pages/auth/a02-login.vue` | 已迁移;已验收,视觉冻结 | | `pages/auth/a02-login` | A02 账号登录 | `pages/auth/a02-login.vue` | 已迁移;已验收,视觉冻结 |
| `pages/genealogy/g01-my-genealogies` | G01 我的家谱 | `pages/genealogy/g01-my-genealogies.vue` | 已迁移;视觉冻结 | | `pages/genealogy/g01-my-genealogies` | G01 我的家谱 | `pages/genealogy/g01-my-genealogies.vue` | 用户已选择 C 作为 9 个 G 页面的公共背景;完整宽度显示、固定贴底与上方宣纸承接的新四档 H5 候选待复核,整页仍为 `[~]`,固定区/独立滚动和 Android 待审核 |
| `pages/genealogy/g03-create-genealogy` | G03 创建家谱/始祖录入 | `pages/genealogy/g03-create-genealogy.vue` | `step=create|ancestor`;已获用户验收,视觉冻结 | | `pages/genealogy/g03-create-genealogy` | G03 创建家谱/始祖录入 | `pages/genealogy/g03-create-genealogy.vue` | `step=create|ancestor`;已获用户验收,视觉冻结 |
| `pages/genealogy/g05-genealogy-overview` | G05 家谱总览 | `pages/genealogy/g05-genealogy-overview.vue` | `loading|ready|empty|error`;视觉候选已实现,待集中审美确认 | | `pages/genealogy/g05-genealogy-overview` | G05 家谱总览 | `pages/genealogy/g05-genealogy-overview.vue` | `loading|ready|empty|error`;视觉候选已实现,待集中审美确认 |
| `pages/genealogy/g06-search-genealogies` | G06 公开家谱搜索/结果/无结果 | `pages/genealogy/g06-search-genealogies.vue` | 已收敛 G07;朱砂题签三态候选已截图,待集中审美确认 | | `pages/genealogy/g06-search-genealogies` | G06 公开家谱搜索/结果/无结果 | `pages/genealogy/g06-search-genealogies.vue` | 已收敛 G07;朱砂题签三态候选已截图,待集中审美确认 |
@@ -119,9 +119,13 @@ static/assets/
| `foundation/transparent/footer-mountain-bamboo.png` | transparent / 750×360 | 根页底部山水竹影 | G01、G06 | 保留 | | `foundation/transparent/footer-mountain-bamboo.png` | transparent / 750×360 | 根页底部山水竹影 | G01、G06 | 保留 |
| `foundation/transparent/notice.png` | transparent / 96×96 | 通知图标 | `PageHeader` | 保留 | | `foundation/transparent/notice.png` | transparent / 96×96 | 通知图标 | `PageHeader` | 保留 |
| `foundation/transparent/chevron-right.png` | transparent / 96×96 | 返回与列表箭头 | A02、A04、A05、A06、`GenealogyCard` | 保留 | | `foundation/transparent/chevron-right.png` | transparent / 96×96 | 返回与列表箭头 | A02、A04、A05、A06、`GenealogyCard` | 保留 |
| `docs/design/assets/g01-background/masters/g01-list-background-direction-a-chroma-master.png` | candidate master / 1024×1536 | G01 列表背景 A,绿幕透明候选 | G01 待选 | 母版保留;未接入页面 | | `docs/design/assets/g01-background/masters/g01-list-background-direction-a-chroma-master.png` | candidate master / 1024×1536 | G01 背景 A,绿幕透明历史候选 | 未接入 | 母版保留,不删除 |
| `docs/design/assets/g01-background/masters/g01-list-background-direction-b-chroma-master.png` | candidate master / 1024×1536 | G01 列表背景 B,绿幕透明候选 | G01 待选 | 母版保留;未接入页面 | | `docs/design/assets/g01-background/masters/g01-list-background-direction-b-chroma-master.png` | candidate master / 1024×1536 | G01 背景 B,绿幕透明历史候选 | 未接入 | 母版保留,不删除 |
| `docs/design/assets/g01-background/masters/g01-list-background-direction-c-paper-master.png` | candidate master / 1024×1536 | G01 列表背景 C,完整宣纸候选 | G01 待选 | 母版保留;未接入页面 | | `docs/design/assets/g01-background/masters/g01-list-background-direction-c-paper-master.png` | historical selected master / 1024×1536 | 上一轮 G 模块公共暖宣纸背景 C 的稳定母版 | `genealogy-page-background.png` | 历史方向保留,不删除 |
| `docs/design/assets/g01-background/masters/genealogy-page-background-long-flagship-imagegen-source.png` | source master / 793×1983 | 用户确认的连续长背景原始 ImageGen 输出 | 归一化母版输入 | 原稿保留,不删除 |
| `docs/design/assets/g01-background/masters/genealogy-page-background-long-flagship-master.png` | selected master / 1536×3840 | G 模块旗舰长屏连续背景稳定母版 | `genealogy-page-background-long.png` | 当前选定母版;确定性输出输入 |
| `modules/genealogy/opaque/genealogy-page-background.png` | opaque / 1024×1536 | 上一轮 C 公共运行图 | 当前无页面入口 | 历史输出保留,不删除 |
| `modules/genealogy/opaque/genealogy-page-background-long.png` | opaque / 1440×3600 | G 模块公共旗舰长屏云竹亭台连续宣纸景 | 9 个活动 G 页面通过 `GenealogyPageBackground.vue` 使用,`widthFix` 固定贴底 | 当前公共运行输出;H5 候选已覆盖五档,待逐页审核及 Android 复核 |
| `modules/genealogy/opaque/g01-empty-panel.png` | opaque / 1122×1402 | 无家谱空态完整宣纸卡,双金线、角饰、云纹与下沿山水 | G01 `state=empty` | 保留;由 ImageGen 生成,已获用户验收 | | `modules/genealogy/opaque/g01-empty-panel.png` | opaque / 1122×1402 | 无家谱空态完整宣纸卡,双金线、角饰、云纹与下沿山水 | G01 `state=empty` | 保留;由 ImageGen 生成,已获用户验收 |
| `modules/genealogy/opaque/g03-create-flow-panel.png` | opaque / 1122×1500 | 完整宣纸任务面板,双金线、回纹角饰、云纹与下沿淡墨山水 | G03、G08、G11、G12、T01、T03 至 T06、T08、F/R/N/M 表单/详情/设置/状态 | 保留;由 ImageGen 生成,G03 已获用户验收,复用页待审 | | `modules/genealogy/opaque/g03-create-flow-panel.png` | opaque / 1122×1500 | 完整宣纸任务面板,双金线、回纹角饰、云纹与下沿淡墨山水 | G03、G08、G11、G12、T01、T03 至 T06、T08、F/R/N/M 表单/详情/设置/状态 | 保留;由 ImageGen 生成,G03 已获用户验收,复用页待审 |
| `modules/genealogy/opaque/g05-overview-surface.png` | opaque / 1122×1506 | 家谱总览完整视觉表面,含墨蓝谱名区、四宫格宣纸槽、金线近况分隔与底部访问说明面 | G05 `loading|ready|empty|error` | 保留;由 ImageGen 参考 G05 改前截图生成,页面只叠放真实文字和点击层;待集中审美确认 | | `modules/genealogy/opaque/g05-overview-surface.png` | opaque / 1122×1506 | 家谱总览完整视觉表面,含墨蓝谱名区、四宫格宣纸槽、金线近况分隔与底部访问说明面 | G05 `loading|ready|empty|error` | 保留;由 ImageGen 参考 G05 改前截图生成,页面只叠放真实文字和点击层;待集中审美确认 |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Some files were not shown because too many files have changed in this diff Show More