修改完成待测试

This commit is contained in:
2026-09-13 17:45:47 +08:00
parent 9ad572907b
commit bbc024f7f2
80 changed files with 3844 additions and 1672 deletions
+166 -44
View File
@@ -55,7 +55,7 @@
<picker
:range="sexOptions.map((item) => item.label)"
:value="optionIndex(sexOptions, addForm.sex)"
@change="selectOption('sex', sexOptions, $event)"
@change="selectSex"
>
<view class="form-field form-field--picker">
<text>性别</text
@@ -168,19 +168,35 @@
type="number"
placeholder="可选正整数"
placeholder-class="form-placeholder"
@blur="loadRankOptions"
/>
<text v-else>首位成员固定为 1</text>
</view>
<picker
mode="date"
:value="addForm.birthDate"
@change="selectBirthDate"
v-if="rankOptions.length"
:range="rankPickerLabels"
:value="rankOptionIndex"
:disabled="rankOptionsState !== 'ready' || isSubmitting"
@change="selectRank"
>
<view class="form-field form-field--picker">
<text>出生日期</text
><text>{{ addForm.birthDate || "请选择" }}</text>
<text>排行称谓</text><text>{{ selectedRankName || "请选择" }}</text>
</view>
</picker>
<view v-else class="form-field form-field--disabled">
<text>排行称谓</text><text>{{ rankOptionStateLabel }}</text>
</view>
<text v-if="rankOptionsState === 'error'" class="field-error"
>排行配置暂未就绪请稍后重试</text
>
<view
class="form-field form-field--picker"
role="button"
aria-label="选择出生日期"
@click="openDatePicker('birthDate', '选择出生日期')"
>
<text>出生日期</text><text>{{ addForm.birthDate || "请选择" }}</text>
</view>
<picker
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, addForm.birthLunar)"
@@ -260,17 +276,15 @@
/>
</view>
<picker
<view
v-if="isDeceased"
mode="date"
:value="addForm.deathDate"
@change="selectDeathDate"
class="form-field form-field--picker"
role="button"
aria-label="选择逝世日期"
@click="openDatePicker('deathDate', '选择逝世日期')"
>
<view class="form-field form-field--picker">
<text>逝世日期</text
><text>{{ addForm.deathDate || "请选择" }}</text>
</view>
</picker>
<text>逝世日期</text><text>{{ addForm.deathDate || "请选择" }}</text>
</view>
<picker
v-if="isDeceased"
:range="lunarOptions.map((item) => item.label)"
@@ -338,17 +352,15 @@
<text>关系称谓</text><text>{{ optionLabel(relationVariantOptions, addForm.relationVariantCode) || "请选择" }}</text>
</view>
</picker>
<picker
<view
v-if="isDeceased"
mode="date"
:value="addForm.burialDate"
@change="selectBurialDate"
class="form-field form-field--picker"
role="button"
aria-label="选择安葬日期"
@click="openDatePicker('burialDate', '选择安葬日期')"
>
<view class="form-field form-field--picker">
<text>安葬日期</text
><text>{{ addForm.burialDate || "请选择" }}</text>
</view>
</picker>
<text>安葬日期</text><text>{{ addForm.burialDate || "请选择" }}</text>
</view>
<view v-if="isDeceased" class="form-field">
<text>安葬地</text>
<input
@@ -373,15 +385,6 @@
}}</text>
</view>
</picker>
<view class="form-field">
<text>排序值</text>
<input
v-model="addForm.sortOrder"
type="number"
placeholder="可选整数,值越小越靠前"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field form-field--summary">
<text>人物简介</text>
<textarea
@@ -427,6 +430,14 @@
</view>
</view>
<MemberDatePickerSheet
:visible="datePickerVisible"
:title="datePickerTitle"
:value="datePickerValue"
@cancel="closeDatePicker"
@confirm="confirmDatePicker"
/>
<AppDialog
:visible="discardDialogVisible"
eyebrow="尚未保存"
@@ -447,6 +458,7 @@ 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 MemberDatePickerSheet from "@/components/tree/MemberDatePickerSheet.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
@@ -486,10 +498,14 @@ const isAvatarUploading = ref(false);
const avatarFileName = ref("");
const avatarUploadError = ref("");
const discardDialogVisible = ref(false);
const datePickerVisible = ref(false);
const datePickerField = ref("");
const datePickerTitle = ref("选择日期");
const currentMember = ref(null);
const errorMessage = ref("");
const memberDetailRequestController = createRequestController();
const memberOptionsRequestController = createRequestController();
const rankOptionsRequestController = createRequestController();
const avatarUploadRequestController = createRequestController();
const memberCreationRequestController = createRequestController();
const sensitiveProfileRequestController = createRequestController();
@@ -503,6 +519,8 @@ const dictionaryRequestControllers = {
const memberCreationGuard = createNonIdempotentWriteGuard();
let pageActive = true;
let loadSequence = 0;
let rankLoadSequence = 0;
let rankOptionsContext = "";
const relationIntents = Object.freeze(
Object.fromEntries(
@@ -526,6 +544,7 @@ const addForm = reactive({
sex: "",
generation: "",
generationName: "",
rankId: "",
avatarOssId: "",
birthDate: "",
birthLunar: "",
@@ -548,7 +567,6 @@ const addForm = reactive({
personStatus: "",
biography: "",
remark: "",
sortOrder: "",
});
const fieldErrors = reactive({
name: "",
@@ -560,6 +578,8 @@ const fieldErrors = reactive({
});
const memberOptionsState = ref("loading");
const memberOptions = ref([]);
const rankOptionsState = ref("idle");
const rankOptions = ref([]);
const zodiacOptions = ref([]);
const educationOptions = ref([]);
const deathExpressionOptions = ref([]);
@@ -568,6 +588,7 @@ const spouseRelationVariantOptions = ref([]);
const isFirstMember = computed(() => mode.value === "first");
const isDeceased = computed(() => addForm.personStatus === "1");
const datePickerValue = computed(() => addForm[datePickerField.value] || "");
const activeRelationIntent = computed(
() => relationIntents[relationType.value] || null,
);
@@ -617,6 +638,23 @@ const formCopy = computed(() =>
: "先确认新成员与当前成员的关系,再填写可核实的身份信息。",
);
const formNote = computed(() => "红色 * 为必填项,其余内容可按家谱记载补充。");
const rankPickerLabels = computed(() => [
"请选择排行称谓",
...rankOptions.value.map((item) => item.rankName),
]);
const rankOptionIndex = computed(() => {
const index = rankOptions.value.findIndex((item) => item.rankId === addForm.rankId);
return index < 0 ? 0 : index + 1;
});
const selectedRankName = computed(
() => rankOptions.value.find((item) => item.rankId === addForm.rankId)?.rankName || "",
);
const rankOptionStateLabel = computed(() => {
if (rankOptionsState.value === "loading") return "正在读取";
if (rankOptionsState.value === "error") return "暂时无法读取";
if (rankOptionsState.value === "idle") return "请先选择性别和世代";
return "当前条件暂无可选排行";
});
const resultCopy = computed(() => {
if (memberCreationOutcomeUnknown.value) {
return {
@@ -675,6 +713,7 @@ const loadCurrentMember = async () => {
});
if (!pageActive || activeLoad !== loadSequence) return;
currentMember.value = member;
applyRelationDefaults();
addState.value = "form";
} catch (error) {
if (!pageActive || activeLoad !== loadSequence || isRequestCancelled(error)) return;
@@ -753,6 +792,7 @@ onLoad((query) => {
void loadMemberOptions();
void loadBusinessOptions();
if (isFirstMember.value) {
applyRelationDefaults();
addState.value = "form";
return;
}
@@ -763,6 +803,7 @@ onUnload(() => {
loadSequence += 1;
memberDetailRequestController.abort();
memberOptionsRequestController.abort();
rankOptionsRequestController.abort();
avatarUploadRequestController.abort();
memberCreationRequestController.abort();
sensitiveProfileRequestController.abort();
@@ -772,10 +813,13 @@ onUnload(() => {
const requestBack = () =>
runBackGuard({
transientOpen: discardDialogVisible.value,
transientOpen: datePickerVisible.value || discardDialogVisible.value,
dirty: addState.value === "form" && hasDraft.value,
submitting: isSubmitting.value || isAvatarUploading.value,
"close-transient": cancelDiscard,
"close-transient": () => {
if (datePickerVisible.value) return closeDatePicker();
return cancelDiscard();
},
"block-submitting": () => true,
"confirm-discard": requestDiscardConfirmation,
});
@@ -788,6 +832,7 @@ const clearError = (field) => {
const selectRelation = (event) => {
addForm.relation = relationOptions.value[Number(event.detail.value)] || "";
addForm.relationVariantCode = "";
applyRelationDefaults();
clearError("relation");
};
const optionIndex = findMemberOptionIndex;
@@ -795,6 +840,64 @@ const optionLabel = findMemberOptionLabel;
const selectOption = (field, options, event) => {
updateMemberFormOption(addForm, field, options, event);
};
const relationGeneration = () => {
const currentGeneration = Number(currentMember.value?.generation);
if (!Number.isSafeInteger(currentGeneration) || currentGeneration < 1) return "";
if ([memberRelationTypes.FATHER, memberRelationTypes.MOTHER].includes(selectedRelationType.value)) {
return String(Math.max(1, currentGeneration - 1));
}
if ([memberRelationTypes.SON, memberRelationTypes.DAUGHTER].includes(selectedRelationType.value)) {
return String(currentGeneration + 1);
}
return String(currentGeneration);
};
const applyRelationDefaults = () => {
addForm.generation = isFirstMember.value ? "1" : relationGeneration();
if ([memberRelationTypes.FATHER, memberRelationTypes.SON].includes(selectedRelationType.value)) {
addForm.sex = "0";
} else if ([memberRelationTypes.MOTHER, memberRelationTypes.DAUGHTER].includes(selectedRelationType.value)) {
addForm.sex = "1";
}
rankOptionsContext = "";
addForm.rankId = "";
void loadRankOptions();
};
const loadRankOptions = async () => {
const generation = Number(isFirstMember.value ? 1 : addForm.generation);
const sex = String(addForm.sex || "");
const context = `${generation}:${sex}`;
const activeLoad = ++rankLoadSequence;
if (!Number.isSafeInteger(generation) || generation < 1 || !["0", "1", "2"].includes(sex)) {
rankOptionsContext = "";
rankOptions.value = [];
rankOptionsState.value = "idle";
addForm.rankId = "";
return;
}
if (context !== rankOptionsContext) addForm.rankId = "";
rankOptionsContext = context;
rankOptionsState.value = "loading";
try {
const options = await lineageApi.getRankOptions(genealogyId.value, generation, sex, {
requestController: rankOptionsRequestController,
});
if (!pageActive || activeLoad !== rankLoadSequence) return;
rankOptions.value = options;
rankOptionsState.value = "ready";
} catch (error) {
if (!pageActive || activeLoad !== rankLoadSequence || isRequestCancelled(error)) return;
rankOptions.value = [];
rankOptionsState.value = "error";
}
};
const selectSex = (event) => {
selectOption("sex", sexOptions, event);
void loadRankOptions();
};
const selectRank = (event) => {
const selectedIndex = Number(event.detail.value) - 1;
addForm.rankId = rankOptions.value[selectedIndex]?.rankId || "";
};
const selectBindingMode = (event) => {
updateMemberBindingMode(addForm, event);
fieldErrors.bindingMode = "";
@@ -835,14 +938,26 @@ const uploadAvatar = async () => {
if (pageActive) isAvatarUploading.value = false;
}
};
const selectBirthDate = (event) => {
addForm.birthDate = event.detail.value || "";
const openDatePicker = (field, title) => {
datePickerField.value = field;
datePickerTitle.value = title;
datePickerVisible.value = true;
};
const selectDeathDate = (event) => {
addForm.deathDate = event.detail.value || "";
const closeDatePicker = () => {
datePickerVisible.value = false;
};
const selectBurialDate = (event) => {
addForm.burialDate = event.detail.value || "";
const confirmDatePicker = (value) => {
if (!datePickerField.value) return closeDatePicker();
addForm[datePickerField.value] = value;
fieldErrors.dates = "";
closeDatePicker();
};
const localToday = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
const day = String(today.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const validateAddForm = () => {
fieldErrors.name = addForm.name.trim() ? "" : "请填写成员姓名";
@@ -858,8 +973,15 @@ const validateAddForm = () => {
addForm.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(addForm.email.trim())
? "请填写有效的电子邮箱"
: "";
const today = localToday();
fieldErrors.dates =
addForm.birthDate && addForm.deathDate && addForm.deathDate < addForm.birthDate
addForm.birthDate && addForm.birthDate > today
? "出生日期不能晚于今天"
: addForm.deathDate && addForm.deathDate > today
? "逝世日期不能晚于今天"
: addForm.burialDate && addForm.burialDate > today
? "安葬日期不能晚于今天"
: addForm.birthDate && addForm.deathDate && addForm.deathDate < addForm.birthDate
? "逝世日期不能早于出生日期"
: addForm.deathDate && addForm.burialDate && addForm.burialDate < addForm.deathDate
? "安葬日期不能早于逝世日期"
@@ -884,6 +1006,7 @@ const submitAdd = async () => {
aliasName: addForm.aliasName,
sex: addForm.sex,
generationName: addForm.generationName,
...(addForm.rankId ? { rankId: addForm.rankId } : {}),
avatarOssId: addForm.avatarOssId,
birthDate: addForm.birthDate,
birthLunar: addForm.birthLunar,
@@ -908,7 +1031,6 @@ const submitAdd = async () => {
personStatus: addForm.personStatus,
biography: addForm.biography,
remark: addForm.remark,
sortOrder: addForm.sortOrder,
...(isFirstMember.value
? { generation: 1 }
: {