feat: 完成前端业务闭环与后端联调
This commit is contained in:
+273
-12
@@ -115,10 +115,20 @@
|
||||
}}</text>
|
||||
|
||||
<view class="form-field">
|
||||
<text>别名</text>
|
||||
<text>表字</text>
|
||||
<input
|
||||
v-model="editForm.courtesyName"
|
||||
maxlength="40"
|
||||
placeholder="如族谱有记载可填写"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<text>别号</text>
|
||||
<input
|
||||
v-model="editForm.aliasName"
|
||||
placeholder="别名或曾用名"
|
||||
maxlength="40"
|
||||
placeholder="别号、昵称或曾用名"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
@@ -190,6 +200,63 @@
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<picker
|
||||
:range="zodiacOptions.map((item) => item.label)"
|
||||
:value="optionIndex(zodiacOptions, editForm.zodiac)"
|
||||
@change="selectOption('zodiac', zodiacOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>生肖</text><text>{{ optionLabel(zodiacOptions, editForm.zodiac) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-field">
|
||||
<text>现居地</text>
|
||||
<input
|
||||
v-model="editForm.currentAddress"
|
||||
maxlength="120"
|
||||
placeholder="填写当前常住地区"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<text>联系电话</text>
|
||||
<input
|
||||
v-model="editForm.mobile"
|
||||
maxlength="30"
|
||||
placeholder="仅授权成员可见"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<text>电子邮箱</text>
|
||||
<input
|
||||
v-model="editForm.email"
|
||||
maxlength="254"
|
||||
placeholder="仅授权成员可见"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.email" class="field-error">{{
|
||||
fieldErrors.email
|
||||
}}</text>
|
||||
<picker
|
||||
:range="educationOptions.map((item) => item.label)"
|
||||
:value="optionIndex(educationOptions, editForm.education)"
|
||||
@change="selectOption('education', educationOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>学历分类</text><text>{{ optionLabel(educationOptions, editForm.education) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-field">
|
||||
<text>职业</text>
|
||||
<input
|
||||
v-model="editForm.occupation"
|
||||
maxlength="80"
|
||||
placeholder="填写主要职业"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<picker
|
||||
v-if="isDeceased"
|
||||
mode="date"
|
||||
@@ -214,6 +281,18 @@
|
||||
}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view v-if="isDeceased" class="form-field">
|
||||
<text>享年</text>
|
||||
<input
|
||||
v-model="editForm.deathAge"
|
||||
type="number"
|
||||
placeholder="0 至 200 的整数"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.deathAge" class="field-error">{{
|
||||
fieldErrors.deathAge
|
||||
}}</text>
|
||||
<view v-if="isDeceased" class="form-field">
|
||||
<text>逝世地</text>
|
||||
<input
|
||||
@@ -222,6 +301,45 @@
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<picker
|
||||
v-if="isDeceased"
|
||||
:range="deathExpressionOptions.map((item) => item.label)"
|
||||
:value="optionIndex(deathExpressionOptions, editForm.deathType)"
|
||||
@change="selectOption('deathType', deathExpressionOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>逝世表述</text><text>{{ optionLabel(deathExpressionOptions, editForm.deathType) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view
|
||||
v-if="isDeceased && originalMember.canManageSensitiveMedicalHistory"
|
||||
class="form-field form-field--summary"
|
||||
>
|
||||
<text>遗传病史</text>
|
||||
<textarea
|
||||
v-model="editForm.hereditaryMedicalHistory"
|
||||
auto-height
|
||||
maxlength="500"
|
||||
placeholder="敏感信息,无明确依据可不填写"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text
|
||||
v-if="isDeceased && originalMember.canManageSensitiveMedicalHistory"
|
||||
class="form-note"
|
||||
>遗传病史属于敏感健康信息,仅在已授权时读取和保存。</text
|
||||
>
|
||||
<picker
|
||||
v-if="isDeceased"
|
||||
mode="date"
|
||||
:value="editForm.burialDate"
|
||||
@change="selectDate('burialDate', $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker"
|
||||
><text>安葬日期</text
|
||||
><text>{{ editForm.burialDate || "未填写" }}</text></view
|
||||
>
|
||||
</picker>
|
||||
<view v-if="isDeceased" class="form-field">
|
||||
<text>安葬地</text>
|
||||
<input
|
||||
@@ -328,6 +446,7 @@ import {
|
||||
isRequestCancelled
|
||||
} from "@/services/api/request-controller.js";
|
||||
import { genealogyMemberApi } from "@/services/api/genealogy-member-service.js";
|
||||
import { businessDictionaryApi } from "@/services/api/business-dictionary-service.js";
|
||||
import { lineageApi } from "@/services/api/lineage-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import {
|
||||
@@ -364,6 +483,12 @@ const personOptionsRequestController = createRequestController();
|
||||
const memberOptionsRequestController = createRequestController();
|
||||
const avatarUploadRequestController = createRequestController();
|
||||
const memberUpdateRequestController = createRequestController();
|
||||
const sensitiveProfileRequestController = createRequestController();
|
||||
const dictionaryRequestControllers = {
|
||||
zodiac: createRequestController(),
|
||||
education: createRequestController(),
|
||||
deathExpression: createRequestController(),
|
||||
};
|
||||
let pageActive = true;
|
||||
let loadSequence = 0;
|
||||
|
||||
@@ -374,6 +499,7 @@ const editForm = reactive({
|
||||
appUserId: "",
|
||||
bindingMode: "NONE",
|
||||
name: "",
|
||||
courtesyName: "",
|
||||
aliasName: "",
|
||||
sex: "",
|
||||
generation: "",
|
||||
@@ -384,16 +510,32 @@ const editForm = reactive({
|
||||
birthDate: "",
|
||||
birthLunar: "",
|
||||
birthPlace: "",
|
||||
zodiac: "",
|
||||
currentAddress: "",
|
||||
mobile: "",
|
||||
email: "",
|
||||
education: "",
|
||||
occupation: "",
|
||||
deathDate: "",
|
||||
deathLunar: "",
|
||||
deathAge: "",
|
||||
deathPlace: "",
|
||||
deathType: "",
|
||||
hereditaryMedicalHistory: "",
|
||||
burialDate: "",
|
||||
burialPlace: "",
|
||||
personStatus: "",
|
||||
summary: "",
|
||||
remark: "",
|
||||
sortOrder: "",
|
||||
});
|
||||
const fieldErrors = reactive({ name: "", dates: "", bindingMode: "" });
|
||||
const fieldErrors = reactive({
|
||||
name: "",
|
||||
dates: "",
|
||||
bindingMode: "",
|
||||
email: "",
|
||||
deathAge: "",
|
||||
});
|
||||
const sexOptions = memberFormOptions.sex;
|
||||
const lunarOptions = memberFormOptions.lunar;
|
||||
const personStatusOptions = memberFormOptions.personStatus;
|
||||
@@ -404,6 +546,10 @@ const personOptionLabels = computed(() =>
|
||||
);
|
||||
const memberOptionsState = ref("loading");
|
||||
const memberOptions = ref([]);
|
||||
const zodiacOptions = ref([]);
|
||||
const educationOptions = ref([]);
|
||||
const deathExpressionOptions = ref([]);
|
||||
const originalSensitiveProfilePresent = ref(false);
|
||||
const memberBindingOptions = computed(() => {
|
||||
const options = memberOptions.value.slice();
|
||||
if (
|
||||
@@ -422,7 +568,11 @@ const isDeceased = computed(() => editForm.personStatus === "1");
|
||||
|
||||
const formSnapshot = computed(() => JSON.stringify(editForm));
|
||||
const hasValidContext = computed(() =>
|
||||
Boolean(genealogyId.value && personId.value && originalMember.value),
|
||||
Boolean(
|
||||
/^[1-9]\d*$/.test(genealogyId.value) &&
|
||||
/^[1-9]\d*$/.test(personId.value) &&
|
||||
originalMember.value,
|
||||
),
|
||||
);
|
||||
const isDirty = computed(
|
||||
() =>
|
||||
@@ -484,11 +634,22 @@ const loadMember = async () => {
|
||||
if (!pageActive || activeLoad !== loadSequence) return;
|
||||
originalMember.value = member;
|
||||
const deceased = member.personStatus === "1";
|
||||
let sensitiveProfile = null;
|
||||
if (member.canManageSensitiveMedicalHistory) {
|
||||
sensitiveProfile = await lineageApi.getSensitiveProfile(
|
||||
genealogyId.value,
|
||||
personId.value,
|
||||
{ requestController: sensitiveProfileRequestController },
|
||||
);
|
||||
if (!pageActive || activeLoad !== loadSequence) return;
|
||||
}
|
||||
originalSensitiveProfilePresent.value = sensitiveProfile?.present === true;
|
||||
Object.assign(editForm, {
|
||||
appUserId: member.appUserId || "",
|
||||
bindingMode:
|
||||
member.bindingMode || (member.appUserId ? "SPECIFIED" : "NONE"),
|
||||
name: member.name,
|
||||
courtesyName: member.courtesyName || "",
|
||||
aliasName: member.aliasName || "",
|
||||
sex: member.sex || "",
|
||||
generation: member.generation || "",
|
||||
@@ -501,15 +662,35 @@ const loadMember = async () => {
|
||||
birthDate: datePart(member.birthDate),
|
||||
birthLunar: member.birthLunar || "",
|
||||
birthPlace: member.birthplace || "",
|
||||
zodiac: member.zodiacCode || "",
|
||||
currentAddress: member.currentAddress || "",
|
||||
mobile: member.mobile || "",
|
||||
email: member.email || "",
|
||||
education: member.educationCode || "",
|
||||
occupation: member.occupation || "",
|
||||
deathDate: deceased ? datePart(member.deathDate) : "",
|
||||
deathLunar: deceased ? member.deathLunar || "" : "",
|
||||
deathAge: deceased ? member.deathAge ?? "" : "",
|
||||
deathPlace: deceased ? member.deathPlace || "" : "",
|
||||
deathType: deceased ? member.deathExpressionCode || "" : "",
|
||||
hereditaryMedicalHistory:
|
||||
deceased && member.canManageSensitiveMedicalHistory
|
||||
? sensitiveProfile?.hereditaryMedicalHistory || ""
|
||||
: "",
|
||||
burialDate: deceased ? datePart(member.burialDate) : "",
|
||||
burialPlace: deceased ? member.burialPlace || "" : "",
|
||||
personStatus: member.personStatus || "",
|
||||
summary: member.biography || "",
|
||||
remark: member.remark || "",
|
||||
sortOrder: member.sortOrder ?? "",
|
||||
});
|
||||
zodiacOptions.value = preserveHistoricalOption(zodiacOptions.value, member.zodiacCode, member.zodiac);
|
||||
educationOptions.value = preserveHistoricalOption(educationOptions.value, member.educationCode, member.education);
|
||||
deathExpressionOptions.value = preserveHistoricalOption(
|
||||
deathExpressionOptions.value,
|
||||
member.deathExpressionCode,
|
||||
member.deathType,
|
||||
);
|
||||
baseline.value = formSnapshot.value;
|
||||
committedMemberSnapshot.value = "";
|
||||
errorMessage.value = "";
|
||||
@@ -548,6 +729,34 @@ const loadPersonOptions = async () => {
|
||||
personOptions.value = [{ label: "暂无可选人物", value: "" }];
|
||||
}
|
||||
};
|
||||
const preserveHistoricalOption = (options, value, label) => {
|
||||
if (!value || options.some((item) => item.value === value)) return options;
|
||||
return [{ value, label: label || value }, ...options];
|
||||
};
|
||||
const loadBusinessOptions = async () => {
|
||||
try {
|
||||
const [zodiacRows, educationRows, deathExpressionRows] = await Promise.all([
|
||||
businessDictionaryApi.getBusinessDictionaryOptions("gen_zodiac", {
|
||||
requestController: dictionaryRequestControllers.zodiac,
|
||||
}),
|
||||
businessDictionaryApi.getBusinessDictionaryOptions("gen_education_type", {
|
||||
requestController: dictionaryRequestControllers.education,
|
||||
}),
|
||||
businessDictionaryApi.getBusinessDictionaryOptions("gen_death_expression", {
|
||||
requestController: dictionaryRequestControllers.deathExpression,
|
||||
}),
|
||||
]);
|
||||
if (!pageActive) return;
|
||||
zodiacOptions.value = preserveHistoricalOption(zodiacRows, originalMember.value?.zodiacCode, originalMember.value?.zodiac);
|
||||
educationOptions.value = preserveHistoricalOption(educationRows, originalMember.value?.educationCode, originalMember.value?.education);
|
||||
deathExpressionOptions.value = preserveHistoricalOption(deathExpressionRows, originalMember.value?.deathExpressionCode, originalMember.value?.deathType);
|
||||
} catch (error) {
|
||||
if (!pageActive || isRequestCancelled(error)) return;
|
||||
zodiacOptions.value = preserveHistoricalOption([], originalMember.value?.zodiacCode, originalMember.value?.zodiac);
|
||||
educationOptions.value = preserveHistoricalOption([], originalMember.value?.educationCode, originalMember.value?.education);
|
||||
deathExpressionOptions.value = preserveHistoricalOption([], originalMember.value?.deathExpressionCode, originalMember.value?.deathType);
|
||||
}
|
||||
};
|
||||
const loadMemberOptions = async () => {
|
||||
memberOptionsState.value = "loading";
|
||||
try {
|
||||
@@ -572,14 +781,18 @@ const loadMemberOptions = async () => {
|
||||
};
|
||||
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query.genealogyId || "");
|
||||
personId.value = String(query.personId || "");
|
||||
if (!genealogyId.value || !personId.value) {
|
||||
genealogyId.value = String(query?.genealogyId || "");
|
||||
personId.value = String(query?.personId || "");
|
||||
if (
|
||||
!/^[1-9]\d*$/.test(genealogyId.value) ||
|
||||
!/^[1-9]\d*$/.test(personId.value)
|
||||
) {
|
||||
editState.value = "error";
|
||||
errorMessage.value = "这个页面已经过期,请从成员档案重新进入。";
|
||||
return;
|
||||
}
|
||||
void loadMember();
|
||||
void loadBusinessOptions();
|
||||
void loadPersonOptions();
|
||||
void loadMemberOptions();
|
||||
});
|
||||
@@ -591,6 +804,8 @@ onUnload(() => {
|
||||
memberOptionsRequestController.abort();
|
||||
avatarUploadRequestController.abort();
|
||||
memberUpdateRequestController.abort();
|
||||
sensitiveProfileRequestController.abort();
|
||||
Object.values(dictionaryRequestControllers).forEach((controller) => controller.abort());
|
||||
discardConfirmation.dispose();
|
||||
});
|
||||
|
||||
@@ -676,6 +891,19 @@ const validateEditForm = () => {
|
||||
editForm.deathDate &&
|
||||
editForm.deathDate < editForm.birthDate
|
||||
? "离世日期不能早于出生日期"
|
||||
: editForm.deathDate &&
|
||||
editForm.burialDate &&
|
||||
editForm.burialDate < editForm.deathDate
|
||||
? "安葬日期不能早于离世日期"
|
||||
: "";
|
||||
fieldErrors.email =
|
||||
editForm.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(editForm.email.trim())
|
||||
? "请填写有效的电子邮箱"
|
||||
: "";
|
||||
const deathAge = editForm.deathAge === "" ? null : Number(editForm.deathAge);
|
||||
fieldErrors.deathAge =
|
||||
deathAge !== null && (!Number.isSafeInteger(deathAge) || deathAge < 0 || deathAge > 200)
|
||||
? "享年必须是 0 至 200 的整数"
|
||||
: "";
|
||||
fieldErrors.bindingMode =
|
||||
editForm.bindingMode === "SPECIFIED" && !editForm.appUserId
|
||||
@@ -683,7 +911,7 @@ const validateEditForm = () => {
|
||||
? "可绑定成员暂不可用,请稍后重试"
|
||||
: "请选择可绑定成员"
|
||||
: "";
|
||||
return !fieldErrors.name && !fieldErrors.dates && !fieldErrors.bindingMode;
|
||||
return !Object.values(fieldErrors).some(Boolean);
|
||||
};
|
||||
const saveMember = async () => {
|
||||
if (isSubmitting.value || isAvatarUploading.value || !validateEditForm())
|
||||
@@ -701,6 +929,7 @@ const saveMember = async () => {
|
||||
? { appUserId: editForm.appUserId }
|
||||
: {}),
|
||||
name: editForm.name,
|
||||
courtesyName: editForm.courtesyName,
|
||||
aliasName: editForm.aliasName,
|
||||
sex: editForm.sex,
|
||||
generation: editForm.generation
|
||||
@@ -713,11 +942,20 @@ const saveMember = async () => {
|
||||
birthDate: editForm.birthDate,
|
||||
birthLunar: editForm.birthLunar,
|
||||
birthPlace: editForm.birthPlace,
|
||||
zodiacCode: editForm.zodiac,
|
||||
currentAddress: editForm.currentAddress,
|
||||
mobile: editForm.mobile,
|
||||
email: editForm.email,
|
||||
educationCode: editForm.education,
|
||||
occupation: editForm.occupation,
|
||||
...(isDeceased.value
|
||||
? {
|
||||
deathDate: editForm.deathDate,
|
||||
deathLunar: editForm.deathLunar,
|
||||
deathAge: editForm.deathAge,
|
||||
deathPlace: editForm.deathPlace,
|
||||
deathExpressionCode: editForm.deathType,
|
||||
burialDate: editForm.burialDate,
|
||||
burialPlace: editForm.burialPlace,
|
||||
}
|
||||
: {}),
|
||||
@@ -729,6 +967,26 @@ const saveMember = async () => {
|
||||
{ requestController: memberUpdateRequestController },
|
||||
);
|
||||
if (!pageActive) return;
|
||||
if (originalMember.value.canManageSensitiveMedicalHistory) {
|
||||
const sensitiveHistory = editForm.hereditaryMedicalHistory.trim();
|
||||
if (sensitiveHistory) {
|
||||
await lineageApi.saveSensitiveProfile(
|
||||
genealogyId.value,
|
||||
personId.value,
|
||||
sensitiveHistory,
|
||||
{ requestController: sensitiveProfileRequestController },
|
||||
);
|
||||
originalSensitiveProfilePresent.value = true;
|
||||
} else if (originalSensitiveProfilePresent.value) {
|
||||
await lineageApi.clearSensitiveProfile(
|
||||
genealogyId.value,
|
||||
personId.value,
|
||||
{ requestController: sensitiveProfileRequestController },
|
||||
);
|
||||
originalSensitiveProfilePresent.value = false;
|
||||
}
|
||||
}
|
||||
if (!pageActive) return;
|
||||
committedMemberSnapshot.value = currentSnapshot;
|
||||
baseline.value = currentSnapshot;
|
||||
}
|
||||
@@ -792,7 +1050,7 @@ const handleResultAction = () => {
|
||||
@include adaptive-tree-panel;
|
||||
z-index: 2;
|
||||
width: calc(100% - 32rpx);
|
||||
margin: 18rpx auto 28rpx;
|
||||
margin: 18rpx auto calc(28rpx + env(safe-area-inset-bottom));
|
||||
padding: 7.5% 8%;
|
||||
}
|
||||
.form-eyebrow {
|
||||
@@ -847,9 +1105,12 @@ const handleResultAction = () => {
|
||||
}
|
||||
.form-field textarea {
|
||||
width: auto;
|
||||
min-height: 54rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
text-align: left;
|
||||
}
|
||||
.form-field input {
|
||||
min-height: var(--app-touch-min);
|
||||
}
|
||||
.form-field__hint,
|
||||
.upload-receipt {
|
||||
color: $ink-muted;
|
||||
@@ -863,7 +1124,7 @@ const handleResultAction = () => {
|
||||
gap: 6rpx;
|
||||
}
|
||||
.upload-button {
|
||||
min-height: 54rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin: 0;
|
||||
padding: 0 16rpx;
|
||||
border: 1rpx solid rgba(159, 23, 15, 0.42);
|
||||
@@ -892,7 +1153,7 @@ const handleResultAction = () => {
|
||||
.form-action {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 76rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
.form-action image,
|
||||
|
||||
Reference in New Issue
Block a user