Files
jiapuapp/components/tree/TreeMemberForm.vue
T
2026-07-20 17:23:13 +08:00

363 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 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',
}"
>
<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/tree/transparent/t01-state-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/tree/transparent/t07-search-input-frame.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/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/><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/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/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/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/><text>{{ formState === "success" ? "继续完善" : "重新填写" }}</text>
</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 } });
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 conflictHelpVisible = ref(false);
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 = () => {
conflictHelpVisible.value = true;
};
</script>
<style scoped lang="scss">
.member-form-page {
position: relative;
min-height: 100vh;
overflow: hidden;
background: $paper;
}
.member-form-page__header {
position: relative;
z-index: 3;
}
.member-form-panel {
position: relative;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(640px, calc((100vw - 16px) * 1.48));
margin: 18rpx auto 0;
padding: 7.5% 8%;
box-sizing: border-box;
}
.member-form-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.member-form,
.member-form-result {
position: relative;
z-index: 1;
}
.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: 24rpx;
line-height: 1.5;
}
.member-field {
position: relative;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
min-height: 78rpx;
align-items: center;
gap: 20rpx;
margin-top: 12rpx;
padding: 12rpx 22rpx;
box-sizing: border-box;
}
.member-field image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.member-field > text {
position: relative;
z-index: 1;
color: $ink;
font-size: 24rpx;
font-weight: 700;
}
.member-field input {
position: relative;
z-index: 1;
width: 100%;
min-width: 0;
height: 54rpx;
color: $ink;
font-size: 24rpx;
text-align: right;
}
.member-placeholder {
color: #a79884;
}
.member-form__note {
display: block;
margin-top: 14rpx;
color: $ink-muted;
font-size: 22rpx;
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%;
pointer-events: none;
}
.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 {
margin-top: 21.5%;
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>