验收完成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;
}
.card-meta {
color: $ink-muted;
color: #62584c;
font-size: 26rpx;
font-weight: 500;
}
.card-updated {
display: flex;
flex: 0 0 auto;
align-items: center;
margin-left: 10rpx;
color: $ink-muted;
font-size: 22rpx;
color: #62584c;
font-size: 24rpx;
font-weight: 500;
white-space: nowrap;
}
@@ -224,8 +226,9 @@ defineEmits(["select"]);
}
.card-role {
color: $ink-muted;
font-size: 24rpx;
color: #62584c;
font-size: 26rpx;
font-weight: 500;
white-space: nowrap;
}
.card-chevron {
+28 -20
View File
@@ -9,9 +9,8 @@
'module-state--error': moduleState === 'error'
}
]">
<image class="module-page__paper" src="/static/assets/foundation/opaque/page-paper.jpg" mode="aspectFill" />
<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" :action="page.number" /></view>
<ModulePageBackground :module="moduleKey" />
<view class="module-page__header"><PageHeader :title="page.title" /></view>
<view v-if="moduleState === 'ready'" class="module-page__content">
<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" />
</view>
<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>
@@ -37,7 +36,7 @@
<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>
<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 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">
<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 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>
@@ -58,7 +57,7 @@
<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>
<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 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)">
<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 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>
@@ -76,7 +75,7 @@
<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">
<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>
@@ -86,27 +85,35 @@
<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">
<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>
<AppTabbar v-if="page.tab" :active="page.tab" />
<AppToast :visible="toastVisible" :message="toastMessage" />
</view>
</template>
<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 AppTabbar from '@/components/AppTabbar.vue'
import AppToast from '@/components/AppToast.vue'
import ModulePageBackground from '@/components/ModulePageBackground.vue'
import { pageCatalog } from '@/data/page-catalog.js'
const props = defineProps({ pageId: { type: String, required: true } })
const page = computed(() => pageCatalog[props.pageId])
const moduleKey = computed(() => ({ f: 'family', r: 'records', n: 'notification', m: 'profile' }[props.pageId[0]] || 'profile'))
const query = (() => {
if (typeof location !== 'undefined') return Object.fromEntries(new URLSearchParams(location.hash.split('?')[1] || ''))
const pages = getCurrentPages(); return pages[pages.length - 1]?.options || {}
})()
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
const normalizedNote = computed(() => (page.value.note || '').replace(/待接入|服务待接入|功能待接入/g, '将在后续功能阶段开放'))
const stateCopy = computed(() => ({
@@ -115,13 +122,17 @@ const stateCopy = computed(() => ({
error: { eyebrow: '内容暂不可用', title: `暂时无法打开${page.value.title}`, copy: '请稍后重新进入,现有资料不会受到影响。', action: '重新查看' }
}[moduleState.value] || {}))
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>
<style scoped lang="scss">
.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__content { padding: 20rpx 24rpx 50rpx; }
.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-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 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 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-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; }
.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%; }
.module-form > .app-button, .module-detail > .app-button, .module-settings > .app-button, .content-stack > .app-button { margin-top: 18rpx; }
.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__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__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 .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; }
@media (min-width: 400px) { .module-page__content { padding-right: 32rpx; padding-left: 32rpx; } }
</style>
+6 -4
View File
@@ -8,7 +8,9 @@
<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" />
</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>
<text class="header-title">{{ title }}</text>
@@ -27,7 +29,6 @@
const props = defineProps({
title: { type: String, required: true },
action: { type: String, default: '' },
backLabel: { type: String, default: '返回' },
root: { type: Boolean, default: false },
unreadCount: { type: Number, default: 0 }
})
@@ -114,13 +115,14 @@ const goBack = () => uni.navigateBack()
background: #fff9ed;
}
.header-back,
.header-action {
width: 100%;
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-title {
+15 -24
View File
@@ -6,7 +6,7 @@
'form-state--conflict': formState === 'conflict',
'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-panel">
<image class="member-form-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
@@ -22,7 +22,7 @@
</view>
<text class="member-form__note">{{ config.note }}</text>
<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>
@@ -32,36 +32,33 @@
<text class="member-form__copy">{{ resultCopy.copy }}</text>
<view v-if="formState === 'conflict'" class="conflict-actions">
<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 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 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 v-if="conflictHelpVisible" class="conflict-help" @click.self="conflictHelpVisible = false">
<view class="conflict-help__card">
<image class="conflict-help__skin" src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" />
<view class="conflict-help__content">
<text class="member-form__eyebrow">关系校验规则</text>
<text class="member-form__title">关系冲突说明</text>
<text class="member-form__copy">同一成员不能同时存在两个生父也不能形成上下代循环请返回世系树核对原关系后再调整</text>
<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>
<AppDialog
:visible="conflictHelpVisible"
eyebrow="关系校验规则"
title="关系冲突说明"
message="同一成员不能同时存在两个生父,也不能形成上下代循环。请返回世系树核对原关系后再调整。"
@confirm="conflictHelpVisible = false"
@close="conflictHelpVisible = false"
/>
</view>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
import AppDialog from '@/components/AppDialog.vue'
import ModulePageBackground from '@/components/ModulePageBackground.vue'
import PageHeader from '@/components/PageHeader.vue'
const props = defineProps({ kind: { type: String, required: true } })
@@ -124,7 +121,6 @@ const showConflictHelp = () => { conflictHelpVisible.value = true }
<style scoped lang="scss">
.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-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%; }
@@ -148,10 +144,5 @@ const showConflictHelp = () => { conflictHelpVisible.value = true }
.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 .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); } }
</style>