363 lines
10 KiB
Vue
363 lines
10 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',
|
||
}"
|
||
>
|
||
<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>
|