Files
jiapuapp/pages/tree/t06-edit-relationship.vue
T
2026-07-21 07:53:08 +08:00

216 lines
13 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.
<!-- 页面编号T-06用途选择两位现有成员并校正其家族关系 -->
<template>
<view
class="relationship-page"
:class="{
'relationship-state--form': relationshipState === 'form',
'relationship-state--success': relationshipState === 'success',
'relationship-state--conflict': relationshipState === 'conflict',
'relationship-state--error': relationshipState === 'error',
}"
>
<ModulePageBackground module="tree" />
<view class="relationship-page__header"><PageHeader title="关系维护" /></view>
<view class="relationship-panel">
<view v-if="relationshipState === 'form'" class="relationship-form">
<text class="form-eyebrow">亲属关系校正</text>
<text class="form-title">确认两位成员的家族关系</text>
<text class="form-copy">选择成员和关系类型后先查看对世系的影响再决定是否保存</text>
<picker :range="memberLabels" :value="sourceIndex" @change="selectMember('sourceId', $event)">
<view class="form-field form-field--picker"><text>当前成员</text><text>{{ memberName(relationshipForm.sourceId) || "请选择成员" }}</text></view>
</picker>
<text v-if="fieldErrors.sourceId" class="field-error">{{ fieldErrors.sourceId }}</text>
<picker :range="memberLabels" :value="targetIndex" @change="selectMember('targetId', $event)">
<view class="form-field form-field--picker"><text>关联成员</text><text>{{ memberName(relationshipForm.targetId) || "请选择另一位成员" }}</text></view>
</picker>
<text v-if="fieldErrors.targetId" class="field-error">{{ fieldErrors.targetId }}</text>
<picker :range="relationshipOptions" :value="relationshipIndex" @change="selectRelationship">
<view class="form-field form-field--picker"><text>关系类型</text><text>{{ relationshipForm.relationship || "请选择关系" }}</text></view>
</picker>
<text v-if="fieldErrors.relationship" class="field-error">{{ fieldErrors.relationship }}</text>
<view class="relationship-preview">
<text>关系影响预览</text>
<text>{{ relationshipPreview }}</text>
</view>
<text class="form-note">父母子女关系会改变世系位置配偶和兄弟姐妹关系不会自动改写现有父母</text>
<view class="form-action" @click="saveRelationship">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" />
<text>{{ isSubmitting ? "正在校验…" : "校验并保存关系" }}</text>
</view>
</view>
<view v-else class="relationship-result">
<text class="form-eyebrow">{{ resultCopy.eyebrow }}</text>
<text class="form-title">{{ resultCopy.title }}</text>
<text class="form-copy">{{ resultCopy.copy }}</text>
<view v-if="relationshipState === 'conflict'" class="result-actions">
<view class="form-action form-action--secondary" @click="relationshipState = 'form'">
<image src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png" mode="scaleToFill" /><text>返回核对</text>
</view>
<view class="form-action" @click="conflictDialogVisible = true">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" /><text>查看规则</text>
</view>
</view>
<view v-else class="form-action" @click="handleResultAction">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" />
<text>{{ relationshipState === "success" ? "返回世系树" : "重新选择" }}</text>
</view>
</view>
</view>
<AppDialog
:visible="conflictDialogVisible"
eyebrow="关系校验规则"
title="为什么不能保存这段关系"
:message="conflictReason || '同一成员不能成为自己的亲属,也不能形成上下代循环或重复父母关系。'"
@confirm="conflictDialogVisible = false"
@close="conflictDialogVisible = false"
/>
</view>
</template>
<script setup>
import { computed, reactive, ref } from "vue";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import AppDialog from "@/components/AppDialog.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { genealogyContext } from "@/utils/genealogy-context.js";
const relationshipState = ref("form");
const genealogyId = ref("");
const personId = ref("");
const isSubmitting = ref(false);
const conflictDialogVisible = ref(false);
const conflictReason = ref("");
let submitTimer = null;
const memberOptions = [
{ id: "101", name: "汤文远", generation: 12, parentId: "" },
{ id: "102", name: "汤正国", generation: 13, parentId: "101" },
{ id: "103", name: "汤正华", generation: 13, parentId: "101" },
{ id: "104", name: "汤凯", generation: 14, parentId: "102" },
{ id: "105", name: "汤悦", generation: 14, parentId: "102" },
];
const relationshipOptions = ["父子(当前成员为父)", "父女(当前成员为父)", "母子(当前成员为母)", "母女(当前成员为母)", "配偶", "兄弟姐妹"];
const relationshipForm = reactive({ sourceId: "", targetId: "", relationship: "" });
const fieldErrors = reactive({ sourceId: "", targetId: "", relationship: "" });
const memberLabels = computed(() => memberOptions.map((item) => `${item.name} · 第 ${item.generation} 世`));
const memberById = computed(() => new Map(memberOptions.map((item) => [item.id, item])));
const sourceIndex = computed(() => Math.max(0, memberOptions.findIndex((item) => item.id === relationshipForm.sourceId)));
const targetIndex = computed(() => Math.max(0, memberOptions.findIndex((item) => item.id === relationshipForm.targetId)));
const relationshipIndex = computed(() => Math.max(0, relationshipOptions.indexOf(relationshipForm.relationship)));
const memberName = (id) => memberById.value.get(String(id))?.name || "";
const relationshipPreview = computed(
() => relationshipForm.sourceId && relationshipForm.targetId && relationshipForm.relationship
? `${memberName(relationshipForm.sourceId)}将以“${relationshipForm.relationship}”关联${memberName(relationshipForm.targetId)}。`
: "完成三项选择后,这里会说明世系位置将如何变化。",
);
const resultCopy = computed(() => ({
success: { eyebrow: "关系已保存", title: "世系关系已经更新", copy: relationshipPreview.value },
conflict: { eyebrow: "发现关系冲突", title: "这段关系会造成世系矛盾", copy: conflictReason.value },
error: { eyebrow: "关系未保存", title: "暂时无法完成关系调整", copy: "当前选择仍然保留,可返回后重新校验。" },
}[relationshipState.value] || {}));
onLoad((query) => {
genealogyId.value = query.genealogyId || genealogyContext.getCurrentGenealogyId() || "";
personId.value = query.personId || "";
relationshipForm.sourceId = personId.value && memberById.value.has(String(personId.value)) ? String(personId.value) : memberOptions[0].id;
if (genealogyId.value) genealogyContext.setCurrentGenealogyId(genealogyId.value);
relationshipState.value = query.state === "success" ? "success" : query.state === "conflict" ? "conflict" : query.state === "error" ? "error" : "form";
if (relationshipState.value === "conflict") conflictReason.value = "目标成员已经存在父级关系,请先核对原关系。";
});
onUnload(() => { if (submitTimer) clearTimeout(submitTimer); });
const clearFieldError = (field) => { fieldErrors[field] = ""; };
const selectMember = (field, event) => {
relationshipForm[field] = memberOptions[Number(event.detail.value)]?.id || "";
clearFieldError(field);
};
const selectRelationship = (event) => {
relationshipForm.relationship = relationshipOptions[Number(event.detail.value)] || "";
clearFieldError("relationship");
};
const isAncestor = (possibleAncestorId, memberId) => {
let current = memberById.value.get(String(memberId));
const visited = new Set();
while (current?.parentId && !visited.has(current.id)) {
if (current.parentId === String(possibleAncestorId)) return true;
visited.add(current.id);
current = memberById.value.get(current.parentId);
}
return false;
};
const validateRelationship = () => {
fieldErrors.sourceId = relationshipForm.sourceId ? "" : "请选择当前成员";
fieldErrors.targetId = relationshipForm.targetId ? "" : "请选择关联成员";
fieldErrors.relationship = relationshipForm.relationship ? "" : "请选择关系类型";
if (fieldErrors.sourceId || fieldErrors.targetId || fieldErrors.relationship) return false;
if (relationshipForm.sourceId === relationshipForm.targetId) {
conflictReason.value = "同一成员不能与自己建立亲属关系。";
return false;
}
const isParentRelationship = relationshipForm.relationship.includes("当前成员为");
const target = memberById.value.get(relationshipForm.targetId);
if (isParentRelationship && isAncestor(relationshipForm.targetId, relationshipForm.sourceId)) {
conflictReason.value = "保存后会形成上下代循环,请重新选择成员方向。";
return false;
}
if (isParentRelationship && target?.parentId && target.parentId !== relationshipForm.sourceId) {
conflictReason.value = `${target.name}已经存在父级成员,不能直接重复建立父母关系。`;
return false;
}
conflictReason.value = "";
return true;
};
const saveRelationship = () => {
if (isSubmitting.value) return;
if (!validateRelationship()) {
if (conflictReason.value) relationshipState.value = "conflict";
return;
}
isSubmitting.value = true;
submitTimer = setTimeout(() => {
relationshipState.value = relationshipForm.relationship === "失败" ? "error" : "success";
isSubmitting.value = false;
submitTimer = null;
}, 280);
};
const handleResultAction = () => {
if (relationshipState.value === "error") { relationshipState.value = "form"; return; }
uni.navigateBack();
};
</script>
<style scoped lang="scss">
.relationship-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.relationship-page__header { z-index: 3; }
.relationship-panel { z-index: 2; width: calc(100% - 32rpx); min-height: min(650px, calc((100vw - 16px) * 1.46)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t01-state-panel.png") center / 100% 100% no-repeat; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }
.form-field { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.form-field text:first-child { color: $ink; font-size: 24rpx; font-weight: 700; }
.form-field text:last-child { min-width: 0; color: $ink; font-size: 24rpx; line-height: 1.45; text-align: right; }
.field-error { display: block; margin: 5rpx 18rpx 0; color: $brand-red; font-size: 22rpx; line-height: 32rpx; }
.relationship-preview { margin-top: 18rpx; padding: 20rpx 22rpx; background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat; }
.relationship-preview text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.5; }
.relationship-preview text:first-child { color: $brand-red; font-weight: 700; }
.form-note { text-align: center; }
.form-action { display: grid; width: 100%; min-height: 76rpx; margin-top: 18rpx; }
.form-action image, .form-action text { grid-area: 1 / 1; width: 100%; height: 100%; }
.form-action text { z-index: 1; display: flex; align-items: center; justify-content: center; color: #fff9ed; font-size: 24rpx; font-weight: 700; }
.form-action--secondary text { color: $ink; }
.relationship-result { margin-top: 28%; text-align: center; }
.relationship-result .form-eyebrow, .relationship-result .form-copy { text-align: center; }
.relationship-result > .form-action { width: 420rpx; max-width: 100%; margin-right: auto; margin-left: auto; }
.result-actions { display: flex; gap: 14rpx; margin-top: 24rpx; }
.result-actions .form-action { width: calc(50% - 7rpx); margin-top: 0; }
@media (min-width: 400px) { .relationship-panel { width: calc(100% - 48rpx); } }
</style>