修改完成

This commit is contained in:
2026-07-21 07:53:03 +08:00
parent 01d246c47b
commit ff60119277
172 changed files with 7982 additions and 2999 deletions
+191 -3
View File
@@ -1,5 +1,193 @@
<!-- 页面编号T-05用途编辑成员资料与保存结果 -->
<template><TreeMemberForm kind="edit" /></template>
<!-- 页面编号T-05用途维护指定成员的身份与生平资料 -->
<template>
<view
class="edit-member-page"
:class="{
'edit-state--form': editState === 'form',
'edit-state--success': editState === 'success',
'edit-state--error': editState === 'error',
'edit-state--no-permission': editState === 'no-permission',
}"
>
<ModulePageBackground module="tree" />
<view class="edit-member-page__header"><PageHeader title="编辑成员" /></view>
<view class="edit-member-panel">
<view v-if="editState === 'form'" class="edit-member-form">
<text class="form-eyebrow">成员档案维护</text>
<text class="form-title">完善{{ originalMember.name }}的生命记录</text>
<text class="form-copy">基础身份用于世系展示生平说明会显示在有权限查看的成员档案中</text>
<view class="member-context">
<text>成员身份</text><text> {{ originalMember.generation }} · {{ originalMember.branch }}</text>
</view>
<view class="form-field">
<text>姓名</text>
<input v-model="editForm.name" maxlength="20" placeholder="请输入姓名" placeholder-class="form-placeholder" @input="clearError('name')" />
</view>
<text v-if="fieldErrors.name" class="field-error">{{ fieldErrors.name }}</text>
<view class="form-field">
<text>字辈</text>
<input v-model="editForm.generationName" maxlength="12" placeholder="例如:文字辈" placeholder-class="form-placeholder" />
</view>
<picker mode="date" :value="editForm.birthDate" @change="selectDate('birthDate', $event)">
<view class="form-field form-field--picker"><text>出生日期</text><text>{{ editForm.birthDate || "未填写" }}</text></view>
</picker>
<picker mode="date" :value="editForm.deathDate" @change="selectDate('deathDate', $event)">
<view class="form-field form-field--picker"><text>离世日期</text><text>{{ editForm.deathDate || "在世或未填写" }}</text></view>
</picker>
<view class="form-field form-field--summary">
<text>人物简介</text>
<textarea v-model="editForm.summary" auto-height maxlength="500" placeholder="记录生平、迁徙或重要经历" placeholder-class="form-placeholder" />
</view>
<text v-if="fieldErrors.dates" class="field-error">{{ fieldErrors.dates }}</text>
<text class="form-note">隐私字段只向本人和具备维护权限的家谱管理员展示</text>
<view class="form-action" @click="saveMember">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" />
<text>{{ isSubmitting ? "正在保存…" : "保存资料" }}</text>
</view>
</view>
<view v-else class="edit-result">
<text class="form-eyebrow">{{ resultCopy.eyebrow }}</text>
<text class="form-title">{{ resultCopy.title }}</text>
<text class="form-copy">{{ resultCopy.copy }}</text>
<view class="form-action" @click="handleResultAction">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" />
<text>{{ resultCopy.action }}</text>
</view>
</view>
</view>
<AppDialog
:visible="discardDialogVisible"
eyebrow="资料尚未保存"
title="要放弃本次修改吗"
message="返回后,本次对成员档案的修改不会保留。"
cancel-text="继续编辑"
confirm-text="放弃修改"
show-cancel
@close="discardDialogVisible = false"
@cancel="discardDialogVisible = false"
@confirm="discardAndBack"
/>
</view>
</template>
<script setup>
import TreeMemberForm from "@/components/tree/TreeMemberForm.vue";
import { computed, reactive, ref } from "vue";
import { onBackPress, 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 editState = ref("form");
const genealogyId = ref("");
const personId = ref("");
const isSubmitting = ref(false);
const discardDialogVisible = ref(false);
let submitTimer = null;
const memberFixtures = {
101: { id: 101, name: "汤文远", generation: 12, generationName: "文字辈", branch: "主支", birthDate: "1940-03-01", deathDate: "2012-08-16", summary: "一生敦亲睦族,参与整理家族旧谱。" },
102: { id: 102, name: "汤正国", generation: 13, generationName: "正字辈", branch: "长房", birthDate: "1965-05-12", deathDate: "", summary: "负责长房资料核对。" },
103: { id: 103, name: "汤正华", generation: 13, generationName: "正字辈", branch: "二房", birthDate: "1968-09-03", deathDate: "", summary: "资料仍在补充。" },
};
const fallbackMember = { id: "", name: "当前成员", generation: "待确认", generationName: "", branch: "待确认", birthDate: "", deathDate: "", summary: "" };
const originalMember = ref({ ...fallbackMember });
const baseline = ref("");
const editForm = reactive({ name: "", generationName: "", birthDate: "", deathDate: "", summary: "" });
const fieldErrors = reactive({ name: "", dates: "" });
const formSnapshot = computed(() => JSON.stringify(editForm));
const isDirty = computed(
() => editState.value === "form" && baseline.value && formSnapshot.value !== baseline.value,
);
const resultCopy = computed(() => ({
success: { eyebrow: "成员资料已更新", title: `${editForm.name}的档案已保存`, copy: "返回成员档案后可以查看本次修改。", action: "返回成员档案" },
error: { eyebrow: "资料未保存", title: "暂时无法保存成员档案", copy: "当前修改仍保留,可返回表单后重试。", action: "返回修改" },
"no-permission": { eyebrow: "权限不足", title: "当前账号不能编辑这位成员", copy: "本人或具备成员维护权限的家谱管理员才能修改档案。", action: "返回成员档案" },
}[editState.value] || {}));
const loadMember = (id) => {
const member = memberFixtures[id] || { ...fallbackMember, id, name: "待核实成员" };
originalMember.value = { ...member };
Object.assign(editForm, {
name: member.name,
generationName: member.generationName,
birthDate: member.birthDate,
deathDate: member.deathDate,
summary: member.summary,
});
baseline.value = formSnapshot.value;
};
onLoad((query) => {
genealogyId.value = query.genealogyId || genealogyContext.getCurrentGenealogyId() || "";
personId.value = query.personId || "";
if (genealogyId.value) genealogyContext.setCurrentGenealogyId(genealogyId.value);
loadMember(personId.value);
editState.value = query.state === "success" ? "success" : query.state === "error" ? "error" : query.state === "no-permission" ? "no-permission" : !personId.value ? "error" : "form";
});
onUnload(() => { if (submitTimer) clearTimeout(submitTimer); });
onBackPress(() => {
if (discardDialogVisible.value) { discardDialogVisible.value = false; return true; }
if (isDirty.value) { discardDialogVisible.value = true; return true; }
return false;
});
const clearError = (field) => { fieldErrors[field] = ""; };
const selectDate = (field, event) => {
editForm[field] = event.detail.value || "";
fieldErrors.dates = "";
};
const validateEditForm = () => {
fieldErrors.name = editForm.name.trim() ? "" : "请填写成员姓名";
fieldErrors.dates = editForm.birthDate && editForm.deathDate && editForm.deathDate < editForm.birthDate ? "离世日期不能早于出生日期" : "";
return !fieldErrors.name && !fieldErrors.dates;
};
const saveMember = () => {
if (isSubmitting.value || !validateEditForm()) return;
isSubmitting.value = true;
submitTimer = setTimeout(() => {
editState.value = editForm.name.trim() === "失败" ? "error" : "success";
if (editState.value === "success") baseline.value = formSnapshot.value;
isSubmitting.value = false;
submitTimer = null;
}, 280);
};
const handleResultAction = () => {
if (editState.value === "error") { editState.value = "form"; return; }
uni.navigateBack();
};
const discardAndBack = () => {
discardDialogVisible.value = false;
uni.navigateBack();
};
</script>
<style scoped lang="scss">
.edit-member-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.edit-member-page__header { z-index: 3; }
.edit-member-panel { z-index: 2; width: calc(100% - 32rpx); min-height: min(690px, calc((100vw - 16px) * 1.52)); 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; }
.member-context, .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; }
.member-context text:first-child, .form-field > text:first-child { color: $ink; font-size: 24rpx; font-weight: 700; }
.member-context text:last-child, .form-field > text:last-child, .form-field input, .form-field textarea { min-width: 0; color: $ink; font-size: 24rpx; line-height: 1.45; text-align: right; }
.form-field textarea { width: auto; min-height: 54rpx; text-align: left; }
.form-field--summary { align-items: start; }
.form-placeholder { color: #a79884; }
.field-error { display: block; margin: 5rpx 18rpx 0; color: $brand-red; font-size: 22rpx; line-height: 32rpx; }
.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; }
.edit-result { margin-top: 30%; text-align: center; }
.edit-result .form-eyebrow, .edit-result .form-copy { text-align: center; }
.edit-result .form-action { width: 420rpx; max-width: 100%; margin-right: auto; margin-left: auto; }
@media (min-width: 400px) { .edit-member-panel { width: calc(100% - 48rpx); } }
</style>