修改完成待测试
This commit is contained in:
+166
-44
@@ -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 }
|
||||
: {
|
||||
|
||||
Reference in New Issue
Block a user