完成70%

This commit is contained in:
2026-07-24 07:56:22 +08:00
parent bb6431b319
commit c7f278fe79
92 changed files with 4741 additions and 14440 deletions
+125 -52
View File
@@ -4,15 +4,16 @@
class="edit-member-page"
:class="{
'edit-state--form': editState === 'form',
'edit-state--preview': editState === 'preview',
'edit-state--loading': editState === 'loading',
'edit-state--error': editState === 'error',
'edit-state--no-permission': editState === 'no-permission',
}"
>
<ModulePageBackground module="tree" />
<view class="edit-member-page__header"><PageHeader title="编辑成员" custom-back @back="requestBack" /></view>
<view class="edit-member-panel">
<view v-if="editState === 'form'" class="edit-member-form">
<AppLoading v-if="editState === 'loading'" text="正在读取成员资料" description="请稍候,正在确认可编辑字段。" />
<view v-else-if="editState === 'form'" class="edit-member-form">
<text class="form-eyebrow">成员档案维护</text>
<text class="form-title">完善{{ originalMember.name }}的生命记录</text>
<text class="form-copy">基础身份用于世系展示生平说明会显示在有权限查看的成员档案中</text>
@@ -26,6 +27,10 @@
</view>
<text v-if="fieldErrors.name" class="field-error">{{ fieldErrors.name }}</text>
<view class="form-field">
<text>别名</text>
<input v-model="editForm.aliasName" placeholder="别名或曾用名" placeholder-class="form-placeholder" />
</view>
<view class="form-field">
<text>字辈</text>
<input v-model="editForm.generationName" maxlength="12" placeholder="例如:文字辈" placeholder-class="form-placeholder" />
@@ -33,19 +38,43 @@
<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>
<view class="form-field">
<text>出生农历</text>
<input v-model="editForm.birthLunar" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
</view>
<view class="form-field">
<text>出生地</text>
<input v-model="editForm.birthPlace" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
</view>
<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">
<text>逝世农历</text>
<input v-model="editForm.deathLunar" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
</view>
<view class="form-field">
<text>逝世地</text>
<input v-model="editForm.deathPlace" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
</view>
<view class="form-field">
<text>安葬地</text>
<input v-model="editForm.burialPlace" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
</view>
<view class="form-field form-field--summary">
<text>人物简介</text>
<textarea v-model="editForm.summary" auto-height maxlength="500" placeholder="记录生平、迁徙或重要经历" placeholder-class="form-placeholder" />
</view>
<view class="form-field form-field--summary">
<text>备注</text>
<textarea v-model="editForm.remark" auto-height 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>
<text>{{ isSubmitting ? "正在保存…" : "保存资料" }}</text>
</view>
</view>
@@ -78,10 +107,11 @@
<script setup>
import { computed, reactive, ref } from "vue";
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
import AppLoading from "@/components/AppLoading.vue";
import AppDialog from "@/components/AppDialog.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { findTreeMemberPresentationFixture } from "@/data/mock.js";
import { appApi, createRequestController, isRequestCancelled } from "@/utils/api.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import {
goBack,
@@ -89,16 +119,32 @@ import {
runBackGuard,
} from "@/utils/navigation.js";
const editState = ref("form");
const editState = ref("loading");
const genealogyId = ref("");
const personId = ref("");
const isSubmitting = ref(false);
const discardDialogVisible = ref(false);
let submitTimer = null;
const errorMessage = ref("");
const failedAction = ref("load");
const editRequestController = createRequestController();
let loadSequence = 0;
const originalMember = ref(null);
const baseline = ref("");
const editForm = reactive({ name: "", generationName: "", birthDate: "", deathDate: "", summary: "" });
const editForm = reactive({
name: "",
aliasName: "",
generationName: "",
birthDate: "",
birthLunar: "",
birthPlace: "",
deathDate: "",
deathLunar: "",
deathPlace: "",
burialPlace: "",
summary: "",
remark: "",
});
const fieldErrors = reactive({ name: "", dates: "" });
const formSnapshot = computed(() => JSON.stringify(editForm));
@@ -107,16 +153,16 @@ const hasValidContext = computed(() =>
);
const isDirty = computed(
() =>
(editState.value === "form" || editState.value === "preview") &&
editState.value === "form" &&
Boolean(baseline.value) &&
formSnapshot.value !== baseline.value,
);
const resultCopy = computed(() => ({
preview: { eyebrow: "本地流程预览", title: `${editForm.name}的资料已通过本地校验`, copy: "尚未提交服务器,返回成员档案后不会显示本次修改。", action: "返回成员档案(不保存)" },
error: hasValidContext.value
? { eyebrow: "资料未保存", title: "暂时无法保存成员档案", copy: "当前修改仍保留,可返回表单后重试。", action: "返回修改" }
: { eyebrow: "成员入口无效", title: "没有找到要编辑的成员", copy: "请从成员档案重新进入,页面不会创建临时成员身份。", action: "返回上一页" },
"no-permission": { eyebrow: "权限不足", title: "当前账号不能编辑这位成员", copy: "本人或具备成员维护权限的家谱管理员才能修改档案。", action: "返回成员档案" },
error: failedAction.value === "save"
? { eyebrow: "保存失败", title: "成员档案尚未保存", copy: errorMessage.value || "服务未确认本次修改,请检查填写后重试。", action: "返回修改" }
: hasValidContext.value
? { eyebrow: "资料读取失败", title: "暂时无法读取成员档案", copy: errorMessage.value || "请返回后重试。", action: "重新读取" }
: { eyebrow: "成员入口无效", title: "没有找到要编辑的成员", copy: errorMessage.value || "请从成员档案重新进入。", action: "返回上一页" },
}[editState.value] || {}));
const discardConfirmation = createDiscardConfirmation((visible) => {
discardDialogVisible.value = visible;
@@ -125,49 +171,55 @@ const requestDiscardConfirmation = discardConfirmation.request;
const confirmDiscard = discardConfirmation.confirm;
const cancelDiscard = discardConfirmation.cancel;
const loadMember = (id) => {
const member = findTreeMemberPresentationFixture(genealogyId.value, id);
if (!member) return false;
originalMember.value = { ...member };
if (["privacy", "forbidden"].includes(member.status)) {
Object.assign(editForm, {
name: member.name,
generationName: "",
birthDate: "",
deathDate: "",
summary: "",
const loadMember = async () => {
const activeLoad = ++loadSequence;
editState.value = "loading";
try {
const member = await appApi.getPerson(genealogyId.value, personId.value, {
requestController: editRequestController,
});
} else {
if (activeLoad !== loadSequence) return;
originalMember.value = member;
Object.assign(editForm, {
name: member.name,
aliasName: member.aliasName || "",
generationName: member.generationName || "",
birthDate: member.birthDate || "",
birthLunar: member.birthLunar || "",
birthPlace: member.birthplace || "",
deathDate: member.deathDate || "",
summary: member.summary || "",
deathLunar: member.deathLunar || "",
deathPlace: member.deathPlace || "",
burialPlace: member.burialPlace || "",
summary: member.biography || "",
remark: member.remark || "",
});
baseline.value = formSnapshot.value;
errorMessage.value = "";
failedAction.value = "load";
editState.value = "form";
} catch (error) {
if (activeLoad !== loadSequence || isRequestCancelled(error)) return;
originalMember.value = null;
failedAction.value = "load";
errorMessage.value = error?.message || "成员资料暂不可用。";
editState.value = "error";
}
baseline.value = formSnapshot.value;
return true;
};
onLoad((query) => {
genealogyId.value = String(query.genealogyId || "");
personId.value = String(query.personId || "");
const loaded = Boolean(genealogyId.value && personId.value) && loadMember(personId.value);
editState.value = !loaded
? "error"
: ["privacy", "forbidden"].includes(originalMember.value.status)
? "no-permission"
: query.state === "preview"
? "preview"
: query.state === "error"
? "error"
: "form";
if (!genealogyId.value || !personId.value || query.state === "error") {
editState.value = "error";
errorMessage.value = "当前家谱或成员上下文无效。";
return;
}
void loadMember();
});
onUnload(() => {
const timer = submitTimer;
submitTimer = null;
if (timer) clearTimeout(timer);
loadSequence += 1;
editRequestController.abort();
discardConfirmation.dispose();
});
@@ -193,17 +245,34 @@ const validateEditForm = () => {
fieldErrors.dates = editForm.birthDate && editForm.deathDate && editForm.deathDate < editForm.birthDate ? "离世日期不能早于出生日期" : "";
return !fieldErrors.name && !fieldErrors.dates;
};
const saveMember = () => {
const saveMember = async () => {
if (isSubmitting.value || !validateEditForm()) return;
const submitSnapshot = Object.freeze({ ...editForm });
isSubmitting.value = true;
const timer = setTimeout(() => {
if (submitTimer !== timer) return;
editState.value = submitSnapshot.name.trim() === "失败" ? "error" : "preview";
try {
await appApi.updatePerson(genealogyId.value, personId.value, {
name: editForm.name,
aliasName: editForm.aliasName,
generationName: editForm.generationName,
birthDate: editForm.birthDate,
birthLunar: editForm.birthLunar,
birthPlace: editForm.birthPlace,
deathDate: editForm.deathDate,
deathLunar: editForm.deathLunar,
deathPlace: editForm.deathPlace,
burialPlace: editForm.burialPlace,
biography: editForm.summary,
remark: editForm.remark,
}, { requestController: editRequestController });
baseline.value = formSnapshot.value;
await goBack();
} catch (error) {
if (isRequestCancelled(error)) return;
failedAction.value = "save";
errorMessage.value = error?.message || "服务未确认本次修改。";
editState.value = "error";
} finally {
isSubmitting.value = false;
submitTimer = null;
}, 280);
submitTimer = timer;
}
};
const returnToMember = async () => {
const confirmed = isDirty.value
@@ -213,11 +282,15 @@ const returnToMember = async () => {
return goBack();
};
const handleResultAction = () => {
if (editState.value === "error" && hasValidContext.value) {
if (failedAction.value === "save") {
editState.value = "form";
return;
}
return returnToMember();
if (editState.value === "error") {
if (!genealogyId.value || !personId.value) return returnToMember();
return loadMember();
}
editState.value = "form";
};
</script>