138 lines
9.0 KiB
Vue
138 lines
9.0 KiB
Vue
<!-- T04-T06 共用成员表单:仅复用视觉与交互骨架,各页面通过 kind 拥有独立任务和文案。 -->
|
||
<template>
|
||
<view class="member-form-page" :class="{
|
||
'form-state--form': formState === 'form',
|
||
'form-state--success': formState === 'success',
|
||
'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" />
|
||
<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" />
|
||
|
||
<view v-if="formState === 'form'" class="member-form">
|
||
<text class="member-form__eyebrow">{{ config.eyebrow }}</text>
|
||
<text class="member-form__title">{{ config.title }}</text>
|
||
<text class="member-form__copy">{{ config.copy }}</text>
|
||
<view v-for="field in config.fields" :key="field.key" class="member-field">
|
||
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
|
||
<text>{{ field.label }}</text>
|
||
<input v-model="form[field.key]" :placeholder="field.placeholder" placeholder-class="member-placeholder" />
|
||
</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>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="member-form-result">
|
||
<text class="member-form__eyebrow">{{ resultCopy.eyebrow }}</text>
|
||
<text class="member-form__title">{{ resultCopy.title }}</text>
|
||
<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>
|
||
</view>
|
||
<view class="member-form-action" @click="showConflictHelp">
|
||
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" /><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>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, reactive, ref } from 'vue'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
|
||
const props = defineProps({ kind: { type: String, required: true } })
|
||
const configs = {
|
||
add: {
|
||
pageTitle: '新增亲属', eyebrow: '补全家族关系', title: '为汤文远添加一位亲属',
|
||
copy: '先确认与当前成员的关系,再录入基础身份信息。', action: '保存亲属', note: '保存后将在世系图中显示,可继续补充详细档案。',
|
||
fields: [
|
||
{ key: 'name', label: '姓名', placeholder: '请输入真实姓名' },
|
||
{ key: 'relation', label: '与本人关系', placeholder: '例如:长子、配偶' },
|
||
{ key: 'birthDate', label: '出生日期', placeholder: '例如:1992年' },
|
||
{ key: 'gender', label: '性别', placeholder: '请选择或输入' }
|
||
]
|
||
},
|
||
edit: {
|
||
pageTitle: '编辑成员', eyebrow: '成员档案维护', title: '完善汤文远的生命记录',
|
||
copy: '基础身份用于世系展示,生平信息可在成员档案中继续补充。', action: '保存资料', note: '隐私字段只向本人和有权限的家谱管理员展示。',
|
||
fields: [
|
||
{ key: 'name', label: '姓名', placeholder: '汤文远' },
|
||
{ key: 'generation', label: '字辈', placeholder: '例如:文' },
|
||
{ key: 'birthDate', label: '出生日期', placeholder: '1940年' },
|
||
{ key: 'summary', label: '人物简介', placeholder: '简要记录生平' }
|
||
]
|
||
},
|
||
relation: {
|
||
pageTitle: '关系维护', eyebrow: '亲属关系校正', title: '确认两位成员的家族关系',
|
||
copy: '调整关系会影响世系位置,保存前请核对成员和关系类型。', action: '保存关系', note: '若形成重复父母、代际倒置等情况,页面会先提示冲突。',
|
||
fields: [
|
||
{ key: 'source', label: '当前成员', placeholder: '汤文远' },
|
||
{ key: 'target', label: '关联成员', placeholder: '请选择家谱成员' },
|
||
{ key: 'relation', label: '关系类型', placeholder: '父子、配偶等' },
|
||
{ key: 'effectiveDate', label: '生效日期', placeholder: '选填' }
|
||
]
|
||
}
|
||
}
|
||
const config = computed(() => configs[props.kind])
|
||
const form = reactive({ name: '', relation: '', birthDate: '', gender: '', generation: '', summary: '', source: '', target: '', effectiveDate: '' })
|
||
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 formState = ref(query.state === 'success' ? 'success' : query.state === 'conflict' ? 'conflict' : query.state === 'error' ? 'error' : 'form')
|
||
const resultCopy = computed(() => ({
|
||
success: { eyebrow: '本地预览已更新', title: `${config.value.pageTitle}内容已保存`, copy: '返回世系树后可查看新的展示位置;正式数据将在接口阶段接入。' },
|
||
conflict: { eyebrow: '发现关系冲突', title: '这段关系会造成代际矛盾', copy: '目标成员已经存在父级关系,请先核对原关系,再决定是否调整。' },
|
||
error: { eyebrow: '内容未保存', title: '暂时无法完成这次操作', copy: '请返回成员档案重新进入,当前填写内容不会写入正式数据。' }
|
||
}[formState.value] || {}))
|
||
|
||
const saveForm = () => {
|
||
if (props.kind === 'relation' && (!form.target.trim() || !form.relation.trim())) {
|
||
formState.value = 'conflict'
|
||
return
|
||
}
|
||
formState.value = 'success'
|
||
}
|
||
const showConflictHelp = () => uni.showModal({ title: '关系冲突说明', content: '同一成员不能同时存在两个生父或形成上下代循环。请返回世系树核对原关系。', showCancel: false })
|
||
</script>
|
||
|
||
<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%; }
|
||
.member-form, .member-form-result { position: absolute; inset: 7.5% 8%; }
|
||
.member-form__eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
|
||
.member-form__title { display: block; margin-top: 10rpx; color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 34rpx; font-weight: 700; }
|
||
.member-form__copy { display: block; margin-top: 9rpx; color: $ink-muted; font-size: 20rpx; line-height: 1.5; }
|
||
.member-field { position: relative; height: 78rpx; margin-top: 12rpx; }
|
||
.member-field image { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||
.member-field > text { position: absolute; top: 27rpx; left: 22rpx; z-index: 1; color: $ink; font-size: 21rpx; font-weight: 700; }
|
||
.member-field input { position: absolute; top: 0; right: 18rpx; bottom: 0; left: 164rpx; z-index: 1; height: 78rpx; color: $ink; font-size: 21rpx; line-height: 78rpx; }
|
||
.member-placeholder { color: #a79884; }
|
||
.member-form__note { display: block; margin-top: 14rpx; color: $ink-muted; font-size: 19rpx; line-height: 1.45; text-align: center; }
|
||
.member-form-action { position: relative; width: 100%; height: 76rpx; margin-top: 17rpx; }
|
||
.member-form-action image { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||
.member-form-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-form-action .member-form-action__secondary { color: $ink; }
|
||
.member-form-result { top: 29%; text-align: center; }
|
||
.member-form-result .member-form__eyebrow { text-align: center; }
|
||
.member-form-result .member-form__copy { margin-top: 20rpx; }
|
||
.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; }
|
||
@media (min-width: 400px) { .member-form-panel { width: calc(100% - 48rpx); } }
|
||
</style>
|