feat: 完成前端业务闭环与后端联调
This commit is contained in:
+270
-17
@@ -134,11 +134,20 @@
|
||||
}}</text>
|
||||
|
||||
<view class="form-field">
|
||||
<text>别名</text>
|
||||
<text>表字</text>
|
||||
<input
|
||||
v-model="addForm.courtesyName"
|
||||
maxlength="40"
|
||||
placeholder="如族谱有记载可填写"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<text>别号</text>
|
||||
<input
|
||||
v-model="addForm.aliasName"
|
||||
maxlength="40"
|
||||
placeholder="按家谱记载填写"
|
||||
placeholder="别号、昵称或曾用名"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
@@ -193,6 +202,63 @@
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<picker
|
||||
:range="zodiacOptions.map((item) => item.label)"
|
||||
:value="optionIndex(zodiacOptions, addForm.zodiac)"
|
||||
@change="selectOption('zodiac', zodiacOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>生肖</text><text>{{ optionLabel(zodiacOptions, addForm.zodiac) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-field">
|
||||
<text>现居地</text>
|
||||
<input
|
||||
v-model="addForm.currentAddress"
|
||||
maxlength="120"
|
||||
placeholder="填写当前常住地区"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<text>联系电话</text>
|
||||
<input
|
||||
v-model="addForm.mobile"
|
||||
maxlength="30"
|
||||
placeholder="仅授权成员可见"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<text>电子邮箱</text>
|
||||
<input
|
||||
v-model="addForm.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, addForm.education)"
|
||||
@change="selectOption('education', educationOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>学历分类</text><text>{{ optionLabel(educationOptions, addForm.education) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-field">
|
||||
<text>职业</text>
|
||||
<input
|
||||
v-model="addForm.occupation"
|
||||
maxlength="80"
|
||||
placeholder="填写主要职业"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<picker
|
||||
v-if="isDeceased"
|
||||
@@ -218,6 +284,18 @@
|
||||
}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view v-if="isDeceased" class="form-field">
|
||||
<text>享年</text>
|
||||
<input
|
||||
v-model="addForm.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
|
||||
@@ -227,6 +305,50 @@
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<picker
|
||||
v-if="isDeceased"
|
||||
:range="deathExpressionOptions.map((item) => item.label)"
|
||||
:value="optionIndex(deathExpressionOptions, addForm.deathType)"
|
||||
@change="selectOption('deathType', deathExpressionOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>逝世表述</text><text>{{ optionLabel(deathExpressionOptions, addForm.deathType) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view v-if="isDeceased" class="form-field form-field--summary">
|
||||
<text>遗传病史</text>
|
||||
<textarea
|
||||
v-model="addForm.hereditaryMedicalHistory"
|
||||
auto-height
|
||||
maxlength="500"
|
||||
placeholder="敏感信息,无明确依据可不填写"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="isDeceased" class="form-note"
|
||||
>遗传病史属于敏感健康信息,后端必须按权限返回并记录访问审计。</text
|
||||
>
|
||||
<picker
|
||||
v-if="relationVariantOptions.length"
|
||||
:range="relationVariantOptions.map((item) => item.label)"
|
||||
:value="optionIndex(relationVariantOptions, addForm.relationVariantCode)"
|
||||
@change="selectOption('relationVariantCode', relationVariantOptions, $event)"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>关系称谓</text><text>{{ optionLabel(relationVariantOptions, addForm.relationVariantCode) || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<picker
|
||||
v-if="isDeceased"
|
||||
mode="date"
|
||||
:value="addForm.burialDate"
|
||||
@change="selectBurialDate"
|
||||
>
|
||||
<view class="form-field form-field--picker">
|
||||
<text>安葬日期</text
|
||||
><text>{{ addForm.burialDate || "请选择" }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view v-if="isDeceased" class="form-field">
|
||||
<text>安葬地</text>
|
||||
<input
|
||||
@@ -236,6 +358,9 @@
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.dates" class="field-error">{{
|
||||
fieldErrors.dates
|
||||
}}</text>
|
||||
<picker
|
||||
:range="personStatusOptions.map((item) => item.label)"
|
||||
:value="optionIndex(personStatusOptions, addForm.personStatus)"
|
||||
@@ -329,6 +454,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 { createNonIdempotentWriteGuard } from "@/utils/non-idempotent-write-guard.js";
|
||||
@@ -366,6 +492,14 @@ const memberDetailRequestController = createRequestController();
|
||||
const memberOptionsRequestController = createRequestController();
|
||||
const avatarUploadRequestController = createRequestController();
|
||||
const memberCreationRequestController = createRequestController();
|
||||
const sensitiveProfileRequestController = createRequestController();
|
||||
const dictionaryRequestControllers = {
|
||||
zodiac: createRequestController(),
|
||||
education: createRequestController(),
|
||||
deathExpression: createRequestController(),
|
||||
parentVariant: createRequestController(),
|
||||
spouseVariant: createRequestController(),
|
||||
};
|
||||
const memberCreationGuard = createNonIdempotentWriteGuard();
|
||||
let pageActive = true;
|
||||
let loadSequence = 0;
|
||||
@@ -387,6 +521,7 @@ const addForm = reactive({
|
||||
appUserId: "",
|
||||
name: "",
|
||||
relation: "",
|
||||
courtesyName: "",
|
||||
aliasName: "",
|
||||
sex: "",
|
||||
generation: "",
|
||||
@@ -395,18 +530,41 @@ const addForm = reactive({
|
||||
birthDate: "",
|
||||
birthLunar: "",
|
||||
birthPlace: "",
|
||||
zodiac: "",
|
||||
currentAddress: "",
|
||||
mobile: "",
|
||||
email: "",
|
||||
education: "",
|
||||
occupation: "",
|
||||
deathDate: "",
|
||||
deathLunar: "",
|
||||
deathAge: "",
|
||||
deathPlace: "",
|
||||
deathType: "",
|
||||
hereditaryMedicalHistory: "",
|
||||
relationVariantCode: "",
|
||||
burialDate: "",
|
||||
burialPlace: "",
|
||||
personStatus: "",
|
||||
biography: "",
|
||||
remark: "",
|
||||
sortOrder: "",
|
||||
});
|
||||
const fieldErrors = reactive({ name: "", relation: "", bindingMode: "" });
|
||||
const fieldErrors = reactive({
|
||||
name: "",
|
||||
relation: "",
|
||||
bindingMode: "",
|
||||
email: "",
|
||||
dates: "",
|
||||
deathAge: "",
|
||||
});
|
||||
const memberOptionsState = ref("loading");
|
||||
const memberOptions = ref([]);
|
||||
const zodiacOptions = ref([]);
|
||||
const educationOptions = ref([]);
|
||||
const deathExpressionOptions = ref([]);
|
||||
const parentRelationVariantOptions = ref([]);
|
||||
const spouseRelationVariantOptions = ref([]);
|
||||
|
||||
const isFirstMember = computed(() => mode.value === "first");
|
||||
const isDeceased = computed(() => addForm.personStatus === "1");
|
||||
@@ -422,12 +580,27 @@ const relationOptions = computed(() => {
|
||||
const relationLabel = computed(
|
||||
() => activeRelationIntent.value?.label || addForm.relation || "亲属关系",
|
||||
);
|
||||
const selectedRelationType = computed(() =>
|
||||
isFirstMember.value
|
||||
? ""
|
||||
: activeRelationIntent.value
|
||||
? relationType.value
|
||||
: genericRelationTypes[relationOptions.value.indexOf(addForm.relation)] || "",
|
||||
);
|
||||
const relationVariantOptions = computed(() => {
|
||||
if ([memberRelationTypes.FATHER, memberRelationTypes.MOTHER].includes(selectedRelationType.value)) {
|
||||
return parentRelationVariantOptions.value;
|
||||
}
|
||||
return selectedRelationType.value === memberRelationTypes.SPOUSE
|
||||
? spouseRelationVariantOptions.value
|
||||
: [];
|
||||
});
|
||||
const memberOptionLabels = computed(() =>
|
||||
memberOptions.value.map((item) => item.label),
|
||||
);
|
||||
const hasValidContext = computed(
|
||||
() =>
|
||||
Boolean(genealogyId.value) &&
|
||||
/^[1-9]\d*$/.test(genealogyId.value) &&
|
||||
(isFirstMember.value ? !personId.value : Boolean(currentMember.value)),
|
||||
);
|
||||
const relationIndex = computed(() =>
|
||||
@@ -532,14 +705,38 @@ const loadMemberOptions = async () => {
|
||||
memberOptionsState.value = "error";
|
||||
}
|
||||
};
|
||||
const loadBusinessOptions = async () => {
|
||||
try {
|
||||
const [zodiacRows, educationRows, deathRows, parentRows, spouseRows] = 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 }),
|
||||
businessDictionaryApi.getBusinessDictionaryOptions("gen_parent_relationship_variant", { requestController: dictionaryRequestControllers.parentVariant }),
|
||||
businessDictionaryApi.getBusinessDictionaryOptions("gen_spouse_relationship_variant", { requestController: dictionaryRequestControllers.spouseVariant }),
|
||||
]);
|
||||
if (!pageActive) return;
|
||||
zodiacOptions.value = zodiacRows;
|
||||
educationOptions.value = educationRows;
|
||||
deathExpressionOptions.value = deathRows;
|
||||
parentRelationVariantOptions.value = parentRows;
|
||||
spouseRelationVariantOptions.value = spouseRows;
|
||||
} catch (error) {
|
||||
if (!pageActive || isRequestCancelled(error)) return;
|
||||
zodiacOptions.value = [];
|
||||
educationOptions.value = [];
|
||||
deathExpressionOptions.value = [];
|
||||
parentRelationVariantOptions.value = [];
|
||||
spouseRelationVariantOptions.value = [];
|
||||
}
|
||||
};
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query.genealogyId || "");
|
||||
personId.value = String(query.personId || "");
|
||||
mode.value = query.mode === "first" ? "first" : "relative";
|
||||
relationType.value = String(query.relationType || "");
|
||||
if (
|
||||
!genealogyId.value ||
|
||||
(!isFirstMember.value && !personId.value)
|
||||
!/^[1-9]\d*$/.test(genealogyId.value) ||
|
||||
(!isFirstMember.value && !/^[1-9]\d*$/.test(personId.value))
|
||||
) {
|
||||
addState.value = "error";
|
||||
errorMessage.value = "这个页面已经过期,请从世系树重新进入。";
|
||||
@@ -554,6 +751,7 @@ onLoad((query) => {
|
||||
addForm.relation = activeRelationIntent.value.label;
|
||||
}
|
||||
void loadMemberOptions();
|
||||
void loadBusinessOptions();
|
||||
if (isFirstMember.value) {
|
||||
addState.value = "form";
|
||||
return;
|
||||
@@ -567,6 +765,8 @@ onUnload(() => {
|
||||
memberOptionsRequestController.abort();
|
||||
avatarUploadRequestController.abort();
|
||||
memberCreationRequestController.abort();
|
||||
sensitiveProfileRequestController.abort();
|
||||
Object.values(dictionaryRequestControllers).forEach((controller) => controller.abort());
|
||||
discardConfirmation.dispose();
|
||||
});
|
||||
|
||||
@@ -587,6 +787,7 @@ const clearError = (field) => {
|
||||
};
|
||||
const selectRelation = (event) => {
|
||||
addForm.relation = relationOptions.value[Number(event.detail.value)] || "";
|
||||
addForm.relationVariantCode = "";
|
||||
clearError("relation");
|
||||
};
|
||||
const optionIndex = findMemberOptionIndex;
|
||||
@@ -640,6 +841,9 @@ const selectBirthDate = (event) => {
|
||||
const selectDeathDate = (event) => {
|
||||
addForm.deathDate = event.detail.value || "";
|
||||
};
|
||||
const selectBurialDate = (event) => {
|
||||
addForm.burialDate = event.detail.value || "";
|
||||
};
|
||||
const validateAddForm = () => {
|
||||
fieldErrors.name = addForm.name.trim() ? "" : "请填写成员姓名";
|
||||
fieldErrors.relation =
|
||||
@@ -650,7 +854,22 @@ const validateAddForm = () => {
|
||||
? "可绑定成员暂不可用,请稍后重试"
|
||||
: "请选择可绑定成员"
|
||||
: "";
|
||||
return !fieldErrors.name && !fieldErrors.relation && !fieldErrors.bindingMode;
|
||||
fieldErrors.email =
|
||||
addForm.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(addForm.email.trim())
|
||||
? "请填写有效的电子邮箱"
|
||||
: "";
|
||||
fieldErrors.dates =
|
||||
addForm.birthDate && addForm.deathDate && addForm.deathDate < addForm.birthDate
|
||||
? "逝世日期不能早于出生日期"
|
||||
: addForm.deathDate && addForm.burialDate && addForm.burialDate < addForm.deathDate
|
||||
? "安葬日期不能早于逝世日期"
|
||||
: "";
|
||||
const deathAge = addForm.deathAge === "" ? null : Number(addForm.deathAge);
|
||||
fieldErrors.deathAge =
|
||||
deathAge !== null && (!Number.isSafeInteger(deathAge) || deathAge < 0 || deathAge > 200)
|
||||
? "享年必须是 0 至 200 的整数"
|
||||
: "";
|
||||
return !Object.values(fieldErrors).some(Boolean);
|
||||
};
|
||||
const submitAdd = async () => {
|
||||
if (isSubmitting.value || isAvatarUploading.value || !validateAddForm())
|
||||
@@ -661,6 +880,7 @@ const submitAdd = async () => {
|
||||
? { appUserId: addForm.appUserId }
|
||||
: {}),
|
||||
name: addForm.name,
|
||||
courtesyName: addForm.courtesyName,
|
||||
aliasName: addForm.aliasName,
|
||||
sex: addForm.sex,
|
||||
generationName: addForm.generationName,
|
||||
@@ -668,10 +888,23 @@ const submitAdd = async () => {
|
||||
birthDate: addForm.birthDate,
|
||||
birthLunar: addForm.birthLunar,
|
||||
birthPlace: addForm.birthPlace,
|
||||
deathDate: addForm.deathDate,
|
||||
deathLunar: addForm.deathLunar,
|
||||
deathPlace: addForm.deathPlace,
|
||||
burialPlace: addForm.burialPlace,
|
||||
zodiacCode: addForm.zodiac,
|
||||
currentAddress: addForm.currentAddress,
|
||||
mobile: addForm.mobile,
|
||||
email: addForm.email,
|
||||
educationCode: addForm.education,
|
||||
occupation: addForm.occupation,
|
||||
...(isDeceased.value
|
||||
? {
|
||||
deathDate: addForm.deathDate,
|
||||
deathLunar: addForm.deathLunar,
|
||||
deathAge: addForm.deathAge,
|
||||
deathPlace: addForm.deathPlace,
|
||||
deathExpressionCode: addForm.deathType,
|
||||
burialDate: addForm.burialDate,
|
||||
burialPlace: addForm.burialPlace,
|
||||
}
|
||||
: {}),
|
||||
personStatus: addForm.personStatus,
|
||||
biography: addForm.biography,
|
||||
remark: addForm.remark,
|
||||
@@ -685,6 +918,9 @@ const submitAdd = async () => {
|
||||
...(relationType.value === memberRelationTypes.SPOUSE
|
||||
? { relationName: relationLabel.value }
|
||||
: {}),
|
||||
...(addForm.relationVariantCode
|
||||
? { relationVariantCode: addForm.relationVariantCode }
|
||||
: {}),
|
||||
}),
|
||||
};
|
||||
const submittedRelationType = isFirstMember.value
|
||||
@@ -708,12 +944,13 @@ const submitAdd = async () => {
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
let createdPerson;
|
||||
if (isFirstMember.value) {
|
||||
await lineageApi.createPerson(genealogyId.value, payload, {
|
||||
createdPerson = await lineageApi.createPerson(genealogyId.value, payload, {
|
||||
requestController: memberCreationRequestController,
|
||||
});
|
||||
} else {
|
||||
await lineageApi.createRelatedPerson(
|
||||
createdPerson = await lineageApi.createRelatedPerson(
|
||||
genealogyId.value,
|
||||
personId.value,
|
||||
submittedRelationType,
|
||||
@@ -723,6 +960,19 @@ const submitAdd = async () => {
|
||||
}
|
||||
if (!pageActive) return;
|
||||
memberCreationCommitted.value = true;
|
||||
const sensitiveHistory = addForm.hereditaryMedicalHistory.trim();
|
||||
if (sensitiveHistory) {
|
||||
const createdPersonId = String(createdPerson?.personId || "");
|
||||
if (!/^[1-9]\d*$/.test(createdPersonId)) {
|
||||
throw new Error("成员已经保存,但响应缺少人物标识,敏感健康资料尚未保存。请返回成员档案补充。");
|
||||
}
|
||||
await lineageApi.saveSensitiveProfile(
|
||||
genealogyId.value,
|
||||
createdPersonId,
|
||||
sensitiveHistory,
|
||||
{ requestController: sensitiveProfileRequestController },
|
||||
);
|
||||
}
|
||||
await returnTo("T01", { genealogyId: genealogyId.value });
|
||||
} catch (error) {
|
||||
if (!pageActive) return;
|
||||
@@ -774,7 +1024,7 @@ const returnToTree = async () => {
|
||||
@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 {
|
||||
@@ -837,9 +1087,12 @@ const returnToTree = async () => {
|
||||
}
|
||||
.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;
|
||||
@@ -853,7 +1106,7 @@ const returnToTree = async () => {
|
||||
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);
|
||||
@@ -882,7 +1135,7 @@ const returnToTree = async () => {
|
||||
.form-action {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 76rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
.form-action image,
|
||||
|
||||
+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,
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
v-if="hasMore"
|
||||
block
|
||||
type="secondary"
|
||||
:label="loadingMore ? '正在加载…' : '加载更多成员'"
|
||||
:label="loadingMore ? '正在加载…' : loadMoreError ? '加载失败,重新加载' : '加载更多成员'"
|
||||
@click="loadMore"
|
||||
/>
|
||||
</template>
|
||||
@@ -109,9 +109,10 @@ const members = ref([]);
|
||||
const total = ref(0);
|
||||
const pageNum = ref(1);
|
||||
const loadingMore = ref(false);
|
||||
const loadMoreError = ref(false);
|
||||
const memberDirectoryRequestController = createRequestController();
|
||||
let loadSequence = 0;
|
||||
const hasValidContext = computed(() => Boolean(genealogyId.value));
|
||||
const hasValidContext = computed(() => /^[1-9]\d*$/.test(genealogyId.value));
|
||||
const memberMeta = (member) =>
|
||||
`第 ${member.generation} 世 · ${member.generationName || "字辈待补"} · ${member.branch}`;
|
||||
const memberStatus = (member) => {
|
||||
@@ -124,22 +125,30 @@ const hasMore = computed(() => members.value.length < total.value);
|
||||
const loadMembers = async ({ append = false } = {}) => {
|
||||
if (!hasValidContext.value) return;
|
||||
const activeLoad = ++loadSequence;
|
||||
if (append) loadingMore.value = true;
|
||||
const requestedPage = append ? pageNum.value + 1 : 1;
|
||||
if (append) {
|
||||
loadingMore.value = true;
|
||||
loadMoreError.value = false;
|
||||
}
|
||||
else directoryState.value = "loading";
|
||||
try {
|
||||
const personPage = await lineageApi.getPersonPage(
|
||||
genealogyId.value,
|
||||
{ pageNum: pageNum.value, pageSize: 10, keyword: keyword.value },
|
||||
{ pageNum: requestedPage, pageSize: 10, keyword: keyword.value },
|
||||
{ requestController: memberDirectoryRequestController },
|
||||
);
|
||||
if (activeLoad !== loadSequence) return;
|
||||
members.value = append ? [...members.value, ...personPage.rows] : personPage.rows;
|
||||
pageNum.value = requestedPage;
|
||||
total.value = personPage.total;
|
||||
directoryState.value = members.value.length ? "list" : "empty";
|
||||
} catch (error) {
|
||||
if (activeLoad !== loadSequence || isRequestCancelled(error)) return;
|
||||
if (!append) members.value = [];
|
||||
directoryState.value = "error";
|
||||
if (append) loadMoreError.value = true;
|
||||
else {
|
||||
members.value = [];
|
||||
directoryState.value = "error";
|
||||
}
|
||||
} finally {
|
||||
if (activeLoad === loadSequence) loadingMore.value = false;
|
||||
}
|
||||
@@ -153,17 +162,16 @@ onLoad((query) => {
|
||||
void loadMembers();
|
||||
});
|
||||
const searchMembers = () => {
|
||||
pageNum.value = 1;
|
||||
loadMoreError.value = false;
|
||||
void loadMembers();
|
||||
};
|
||||
const loadMore = () => {
|
||||
if (loadingMore.value || !hasMore.value) return;
|
||||
pageNum.value += 1;
|
||||
void loadMembers({ append: true });
|
||||
};
|
||||
const retryDirectory = () => {
|
||||
if (!hasValidContext.value) return goBack();
|
||||
pageNum.value = 1;
|
||||
loadMoreError.value = false;
|
||||
return loadMembers();
|
||||
};
|
||||
const openMember = (item) =>
|
||||
@@ -199,16 +207,21 @@ onUnload(() => {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 22rpx 32rpx 0;
|
||||
}
|
||||
.directory-context__name {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
color: $ink;
|
||||
font-family: "STKaiti", "KaiTi", serif;
|
||||
font-size: clamp(17px, 32rpx, 22px);
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.directory-context__meta {
|
||||
flex: 0 0 auto;
|
||||
color: #62584c;
|
||||
font-size: clamp(15px, 24rpx, 18px);
|
||||
font-weight: 500;
|
||||
@@ -248,7 +261,7 @@ onUnload(() => {
|
||||
color: #776956;
|
||||
}
|
||||
.directory-content {
|
||||
padding: 22rpx 24rpx 50rpx;
|
||||
padding: 22rpx 24rpx calc(50rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.directory-summary {
|
||||
display: flex;
|
||||
|
||||
@@ -202,7 +202,9 @@ const genealogyName = ref("汤氏家谱");
|
||||
const memberTrail = reactive([]);
|
||||
const trailIndex = ref(-1);
|
||||
const memberReadRequestController = createRequestController();
|
||||
const sensitiveProfileRequestController = createRequestController();
|
||||
const memberDeletionRequestController = createRequestController();
|
||||
const sensitiveProfileError = ref("");
|
||||
let memberLoadGeneration = 0;
|
||||
let pageActive = true;
|
||||
|
||||
@@ -212,7 +214,8 @@ const profileDetails = computed(() => {
|
||||
const visibleProfileValue = (profileValue) =>
|
||||
member.value.status === "forbidden" ? "按权限隐藏" : profileValue;
|
||||
return [
|
||||
{ label: "别名", value: member.value.aliasName },
|
||||
{ label: "表字", value: member.value.courtesyName },
|
||||
{ label: "别号", value: member.value.aliasName },
|
||||
{ label: "字辈", value: member.value.generationName },
|
||||
{
|
||||
label: "性别",
|
||||
@@ -227,8 +230,14 @@ const profileDetails = computed(() => {
|
||||
label: "出生农历",
|
||||
value: visibleProfileValue(member.value.birthLunarLabel),
|
||||
},
|
||||
{ label: "生肖", value: visibleProfileValue(member.value.zodiac) },
|
||||
{ label: "生卒信息", value: member.value.years },
|
||||
{ label: "出生地", value: visibleProfileValue(member.value.birthplace) },
|
||||
{ label: "现居地", value: visibleProfileValue(member.value.currentAddress) },
|
||||
{ label: "联系电话", value: visibleProfileValue(member.value.mobile) },
|
||||
{ label: "电子邮箱", value: visibleProfileValue(member.value.email) },
|
||||
{ label: "教育经历", value: visibleProfileValue(member.value.education) },
|
||||
{ label: "职业", value: visibleProfileValue(member.value.occupation) },
|
||||
...(isDeceased.value
|
||||
? [
|
||||
{ label: "逝世日期", value: visibleProfileValue(member.value.deathDate) },
|
||||
@@ -236,7 +245,29 @@ const profileDetails = computed(() => {
|
||||
label: "逝世农历",
|
||||
value: visibleProfileValue(member.value.deathLunarLabel),
|
||||
},
|
||||
{
|
||||
label: "享年",
|
||||
value:
|
||||
member.value.deathAge === null
|
||||
? ""
|
||||
: visibleProfileValue(`${member.value.deathAge} 岁`),
|
||||
},
|
||||
{ label: "逝世地", value: visibleProfileValue(member.value.deathPlace) },
|
||||
{
|
||||
label: "逝世原因或类型",
|
||||
value: visibleProfileValue(member.value.deathType),
|
||||
},
|
||||
...(member.value.canManageSensitiveMedicalHistory
|
||||
? [
|
||||
{
|
||||
label: "遗传病史",
|
||||
value: sensitiveProfileError.value
|
||||
? "敏感资料暂时无法读取"
|
||||
: visibleProfileValue(member.value.hereditaryMedicalHistory),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: "安葬日期", value: visibleProfileValue(member.value.burialDate) },
|
||||
{ label: "安葬地", value: visibleProfileValue(member.value.burialPlace) },
|
||||
]
|
||||
: []),
|
||||
@@ -307,6 +338,27 @@ const loadMember = async (nextPersonId, { preserveCurrent = false } = {}) => {
|
||||
normalizedPersonId,
|
||||
{ requestController: memberReadRequestController },
|
||||
);
|
||||
if (nextMember.canManageSensitiveMedicalHistory) {
|
||||
try {
|
||||
const sensitiveProfile = await lineageApi.getSensitiveProfile(
|
||||
genealogyId.value,
|
||||
normalizedPersonId,
|
||||
{ requestController: sensitiveProfileRequestController },
|
||||
);
|
||||
nextMember.hereditaryMedicalHistory =
|
||||
sensitiveProfile.hereditaryMedicalHistory;
|
||||
sensitiveProfileError.value = "";
|
||||
} catch (error) {
|
||||
if (isRequestCancelled(error)) return false;
|
||||
nextMember.hereditaryMedicalHistory = "";
|
||||
sensitiveProfileError.value = getRequestErrorMessage(
|
||||
error,
|
||||
"敏感资料暂时无法读取。",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sensitiveProfileError.value = "";
|
||||
}
|
||||
if (activeLoadGeneration !== memberLoadGeneration) return false;
|
||||
if (personId.value && personId.value !== String(nextMember.id)) {
|
||||
personDocumentDialog.value?.reset();
|
||||
@@ -415,7 +467,10 @@ const requestBack = () =>
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query.genealogyId || "");
|
||||
const initialPersonId = String(query.personId || "");
|
||||
if (!genealogyId.value || !initialPersonId) {
|
||||
if (
|
||||
!/^[1-9]\d*$/.test(genealogyId.value) ||
|
||||
!/^[1-9]\d*$/.test(initialPersonId)
|
||||
) {
|
||||
memberState.value = "error";
|
||||
errorMessage.value = !initialPersonId
|
||||
? "没有指定成员,请从世系树重新选择。"
|
||||
@@ -449,6 +504,7 @@ onUnload(() => {
|
||||
pageActive = false;
|
||||
memberLoadGeneration += 1;
|
||||
memberReadRequestController.abort();
|
||||
sensitiveProfileRequestController.abort();
|
||||
memberDeletionRequestController.abort();
|
||||
});
|
||||
|
||||
@@ -517,7 +573,7 @@ const toTree = requestBack;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
padding-bottom: 28rpx;
|
||||
padding-bottom: calc(28rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background: $paper;
|
||||
}
|
||||
@@ -571,6 +627,10 @@ const toTree = requestBack;
|
||||
box-shadow: 0 3rpx 7rpx rgba(100, 65, 29, 0.14);
|
||||
overflow: hidden;
|
||||
}
|
||||
.member-heading > view:last-child {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.member-heading > view:last-child text {
|
||||
display: block;
|
||||
color: $ink-muted;
|
||||
@@ -592,6 +652,7 @@ const toTree = requestBack;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin-top: 18rpx;
|
||||
padding: 18rpx 22rpx;
|
||||
}
|
||||
@@ -637,6 +698,8 @@ const toTree = requestBack;
|
||||
}
|
||||
.member-relatives > view {
|
||||
display: flex;
|
||||
min-height: var(--app-touch-min);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
padding: 12rpx 18rpx;
|
||||
@@ -661,7 +724,7 @@ const toTree = requestBack;
|
||||
.member-record-actions > view {
|
||||
@include adaptive-scroll-button(secondary);
|
||||
display: flex;
|
||||
min-height: 72rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8rpx 14rpx;
|
||||
@@ -676,7 +739,7 @@ const toTree = requestBack;
|
||||
.member-profile-action {
|
||||
@include adaptive-scroll-button(primary);
|
||||
display: flex;
|
||||
min-height: 76rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 22rpx;
|
||||
@@ -714,7 +777,7 @@ const toTree = requestBack;
|
||||
.member-restricted-action {
|
||||
@include adaptive-scroll-button(secondary);
|
||||
display: flex;
|
||||
min-height: 76rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 22rpx;
|
||||
|
||||
+46
-10
@@ -67,12 +67,25 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="discardVisible"
|
||||
eyebrow="未保存修改"
|
||||
title="放弃排行修改?"
|
||||
message="当前排序值还没有保存。"
|
||||
confirm-text="确认放弃"
|
||||
cancel-text="继续编辑"
|
||||
show-cancel
|
||||
:close-on-mask="false"
|
||||
@confirm="confirmDiscard"
|
||||
@cancel="cancelDiscard"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
@@ -82,7 +95,8 @@ import {
|
||||
} from "@/services/api/request-controller.js";
|
||||
import { lineageApi } from "@/services/api/lineage-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import { goBack, handleBackPress, returnTo } from "@/utils/navigation/gateway.js";
|
||||
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
|
||||
import { goBack, handleBackPress, returnTo, runBackGuard } from "@/utils/navigation/gateway.js";
|
||||
|
||||
const rankState = ref("loading");
|
||||
const genealogyId = ref("");
|
||||
@@ -93,10 +107,21 @@ const sortOrder = ref("");
|
||||
const savingRank = ref(false);
|
||||
const rankError = ref("");
|
||||
const committedSortOrder = ref("");
|
||||
const sortOrderBaseline = ref("");
|
||||
const discardVisible = ref(false);
|
||||
const memberRankReadRequestController = createRequestController();
|
||||
const memberRankSaveRequestController = createRequestController();
|
||||
let pageActive = true;
|
||||
let loadSequence = 0;
|
||||
const discardConfirmation = createDiscardConfirmation((visible) => {
|
||||
discardVisible.value = visible;
|
||||
});
|
||||
const requestDiscardConfirmation = discardConfirmation.request;
|
||||
const confirmDiscard = discardConfirmation.confirm;
|
||||
const cancelDiscard = discardConfirmation.cancel;
|
||||
const isDirty = computed(() =>
|
||||
rankState.value === "form" && sortOrder.value !== sortOrderBaseline.value,
|
||||
);
|
||||
|
||||
const loadMember = async () => {
|
||||
const activeLoad = ++loadSequence;
|
||||
@@ -110,6 +135,7 @@ const loadMember = async () => {
|
||||
sortOrder.value = personDetail.sortOrder === null || personDetail.sortOrder === undefined
|
||||
? ""
|
||||
: String(personDetail.sortOrder);
|
||||
sortOrderBaseline.value = sortOrder.value;
|
||||
committedSortOrder.value = "";
|
||||
errorMessage.value = "";
|
||||
rankState.value = "form";
|
||||
@@ -146,6 +172,7 @@ const saveRank = async () => {
|
||||
);
|
||||
if (!pageActive) return;
|
||||
committedSortOrder.value = normalizedSortOrder;
|
||||
sortOrderBaseline.value = normalizedSortOrder;
|
||||
}
|
||||
await returnTo("T01", {
|
||||
genealogyId: genealogyId.value,
|
||||
@@ -162,15 +189,23 @@ const saveRank = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const requestBack = () => {
|
||||
if (savingRank.value) return;
|
||||
return goBack();
|
||||
};
|
||||
const requestBack = () => runBackGuard({
|
||||
transientOpen: discardVisible.value,
|
||||
dirty: isDirty.value,
|
||||
submitting: savingRank.value,
|
||||
"close-transient": cancelDiscard,
|
||||
"block-submitting": () => true,
|
||||
"confirm-discard": requestDiscardConfirmation,
|
||||
});
|
||||
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query.genealogyId || "");
|
||||
personId.value = String(query.personId || "");
|
||||
if (!genealogyId.value || !personId.value || query.mode !== "rank") {
|
||||
if (
|
||||
!/^[1-9]\d*$/.test(genealogyId.value) ||
|
||||
!/^[1-9]\d*$/.test(personId.value) ||
|
||||
query.mode !== "rank"
|
||||
) {
|
||||
rankState.value = "error";
|
||||
errorMessage.value = "请从成员资料页重新进入排行调整。";
|
||||
return;
|
||||
@@ -183,6 +218,7 @@ onUnload(() => {
|
||||
loadSequence += 1;
|
||||
memberRankReadRequestController.abort();
|
||||
memberRankSaveRequestController.abort();
|
||||
discardConfirmation.dispose();
|
||||
});
|
||||
|
||||
onBackPress((event) => handleBackPress(event, requestBack));
|
||||
@@ -204,7 +240,7 @@ onBackPress((event) => handleBackPress(event, requestBack));
|
||||
@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 {
|
||||
@@ -255,7 +291,7 @@ onBackPress((event) => handleBackPress(event, requestBack));
|
||||
}
|
||||
.rank-field input {
|
||||
min-width: 0;
|
||||
min-height: 56rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
padding: 0 14rpx;
|
||||
border: 1rpx solid rgba(143, 108, 63, 0.34);
|
||||
border-radius: 8rpx;
|
||||
@@ -269,7 +305,7 @@ onBackPress((event) => handleBackPress(event, requestBack));
|
||||
.form-action {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 76rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
.form-action--disabled { opacity: 0.58; pointer-events: none; }
|
||||
|
||||
@@ -107,7 +107,7 @@ const states = {
|
||||
const activeStatus = computed(() => states[statusState.value] || states.error);
|
||||
const pageTitle = computed(() => "成员状态");
|
||||
const hasValidContext = computed(() =>
|
||||
Boolean(genealogyId.value && personId.value),
|
||||
/^[1-9]\d*$/.test(genealogyId.value) && /^[1-9]\d*$/.test(personId.value),
|
||||
);
|
||||
const memberIdentityCopy = computed(() => {
|
||||
if (!member.value) return "";
|
||||
@@ -157,7 +157,7 @@ const handleAction = () => goBack();
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
padding-bottom: 34rpx;
|
||||
padding-bottom: calc(34rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background: $paper;
|
||||
}
|
||||
@@ -236,7 +236,7 @@ const handleAction = () => goBack();
|
||||
.status-action {
|
||||
display: grid;
|
||||
width: calc(100% - 72rpx);
|
||||
min-height: 76rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin: 22rpx auto 0;
|
||||
}
|
||||
.status-action image,
|
||||
|
||||
+12
-30
@@ -158,17 +158,6 @@
|
||||
@select-action="openMemberAction"
|
||||
/>
|
||||
|
||||
<AppDialog
|
||||
:visible="unavailableActionVisible"
|
||||
eyebrow="服务状态"
|
||||
:title="unavailableAction?.label || '当前操作'"
|
||||
:message="
|
||||
unavailableAction?.unavailableCopy || '这项功能还在准备中,暂时无法使用。'
|
||||
"
|
||||
confirm-text="我知道了"
|
||||
@confirm="unavailableActionVisible = false"
|
||||
@cancel="unavailableActionVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -176,7 +165,6 @@
|
||||
import { computed, nextTick, ref } from "vue";
|
||||
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
|
||||
import AppAvatar from "@/components/AppAvatar.vue";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import MemberActionPanel from "@/components/tree/MemberActionPanel.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
@@ -198,8 +186,6 @@ const genealogyId = ref("");
|
||||
const treeState = ref("loading");
|
||||
const selectedMember = ref(null);
|
||||
const memberActionPanelVisible = ref(false);
|
||||
const unavailableActionVisible = ref(false);
|
||||
const unavailableAction = ref(null);
|
||||
const treeScrollLeft = ref(90);
|
||||
const currentTreeScrollLeft = ref(90);
|
||||
const centeredTreeScrollLeft = ref(90);
|
||||
@@ -282,13 +268,6 @@ const memberActions = Object.freeze([
|
||||
routeKey: "T04",
|
||||
relationType: memberRelationTypes.DAUGHTER,
|
||||
},
|
||||
{
|
||||
key: "BIND_INVITE",
|
||||
label: "邀请绑定",
|
||||
group: "MANAGEMENT",
|
||||
unavailableCopy:
|
||||
"邀请绑定暂未开放,请稍后再试。",
|
||||
},
|
||||
{
|
||||
key: "EDIT_PROFILE",
|
||||
label: "编辑信息",
|
||||
@@ -370,7 +349,7 @@ const loadTree = async (query = {}) => {
|
||||
genealogyId.value = String(
|
||||
query.genealogyId || genealogyContext.getCurrentGenealogyId() || "",
|
||||
);
|
||||
if (!genealogyId.value) {
|
||||
if (!/^[1-9]\d*$/.test(genealogyId.value)) {
|
||||
members.value = [];
|
||||
treeState.value = "empty";
|
||||
return;
|
||||
@@ -488,7 +467,7 @@ const recenterSelectedMember = async () => {
|
||||
const nodeGridStyle = treeNodeGridStyle;
|
||||
const handleStateAction = () => {
|
||||
if (treeState.value === "empty") {
|
||||
if (!genealogyId.value) return Promise.resolve(false);
|
||||
if (!/^[1-9]\d*$/.test(genealogyId.value)) return Promise.resolve(false);
|
||||
return openPage(
|
||||
"T04",
|
||||
{ genealogyId: genealogyId.value, mode: "first" },
|
||||
@@ -534,13 +513,8 @@ const closeMemberActionPanel = () => {
|
||||
memberActionPanelVisible.value = false;
|
||||
};
|
||||
const openMemberAction = (action) => {
|
||||
if (!selectedMember.value) return Promise.resolve(false);
|
||||
if (!selectedMember.value || !action?.routeKey) return Promise.resolve(false);
|
||||
closeMemberActionPanel();
|
||||
if (!action.routeKey) {
|
||||
unavailableAction.value = action;
|
||||
unavailableActionVisible.value = true;
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
const params = {
|
||||
genealogyId: genealogyId.value,
|
||||
personId: String(selectedMember.value.id),
|
||||
@@ -589,12 +563,20 @@ const openMemberAction = (action) => {
|
||||
}
|
||||
.tree-toolbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
color: $brand-red;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
}
|
||||
.tree-toolbar__actions > text {
|
||||
display: flex;
|
||||
min-height: var(--app-touch-min);
|
||||
align-items: center;
|
||||
}
|
||||
.tree-stage {
|
||||
min-height: 0;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tree-action--pressed {
|
||||
@@ -832,7 +814,7 @@ const openMemberAction = (action) => {
|
||||
.tree-state-card__action {
|
||||
display: grid;
|
||||
width: 360rpx;
|
||||
min-height: 70rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin: 22rpx auto 0;
|
||||
}
|
||||
.tree-state-card__action image {
|
||||
|
||||
+24
-4
@@ -52,6 +52,9 @@
|
||||
<view class="member-node__relation"><text>{{ memberRelation(member) }}</text></view>
|
||||
<view
|
||||
class="member-node__name"
|
||||
:class="{
|
||||
'member-node__name--horizontal': !isVerticalPedigreeText(member.name),
|
||||
}"
|
||||
role="button"
|
||||
:aria-label="`查看${member.name}的资料`"
|
||||
@click.stop="openMemberProfile(member)"
|
||||
@@ -135,7 +138,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
|
||||
import { onBackPress, onLoad, onShow, onUnload } from "@dcloudio/uni-app";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
@@ -147,7 +150,7 @@ import {
|
||||
import { lineageApi } from "@/services/api/lineage-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import { genealogyContext } from "@/utils/genealogy/context.js";
|
||||
import { openPage, returnTo } from "@/utils/navigation/gateway.js";
|
||||
import { handleBackPress, openPage, returnTo } from "@/utils/navigation/gateway.js";
|
||||
|
||||
const PEDIGREE_PAGE_SIZE = 5;
|
||||
const genealogyId = ref("");
|
||||
@@ -169,6 +172,10 @@ let skipNextShowRefresh = true;
|
||||
const compareMembers = (left, right) =>
|
||||
Number(left.generation) - Number(right.generation) ||
|
||||
String(left.id).localeCompare(String(right.id));
|
||||
const isVerticalPedigreeText = (value) =>
|
||||
/^[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff·〇零一二三四五六七八九十百千万]+$/.test(
|
||||
String(value || ""),
|
||||
);
|
||||
const generationRows = computed(() => {
|
||||
const groups = new Map();
|
||||
members.value.forEach((member) => {
|
||||
@@ -283,6 +290,9 @@ const toTree = () => {
|
||||
if (selectedId.value) params.selectedId = selectedId.value;
|
||||
return returnTo("T01", params);
|
||||
};
|
||||
onBackPress((event) =>
|
||||
detailVisible.value ? handleBackPress(event, closeMemberDetail) : false,
|
||||
);
|
||||
const handleStateAction = () => {
|
||||
if (treeState.value === "empty") {
|
||||
return openPage(
|
||||
@@ -302,7 +312,7 @@ const loadPedigree = async (query = {}) => {
|
||||
query.genealogyId || genealogyContext.getCurrentGenealogyId() || "",
|
||||
);
|
||||
selectedId.value = String(query.selectedId || "");
|
||||
if (!genealogyId.value) {
|
||||
if (!/^[1-9]\d*$/.test(genealogyId.value)) {
|
||||
members.value = [];
|
||||
treeState.value = "empty";
|
||||
return;
|
||||
@@ -361,6 +371,8 @@ onUnload(() => {
|
||||
}
|
||||
.pedigree-stage {
|
||||
min-height: 0;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.pedigree-layout {
|
||||
display: grid;
|
||||
@@ -428,6 +440,14 @@ onUnload(() => {
|
||||
font-size: clamp(19px, 34rpx, 24px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-node__name--horizontal {
|
||||
padding: 12rpx 8rpx;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
text-orientation: mixed;
|
||||
writing-mode: horizontal-tb;
|
||||
}
|
||||
.pedigree-column__detail,
|
||||
.member-node__copy {
|
||||
align-items: flex-start;
|
||||
@@ -550,7 +570,7 @@ onUnload(() => {
|
||||
.pedigree-state-card__action {
|
||||
display: grid;
|
||||
width: 360rpx;
|
||||
min-height: 70rpx;
|
||||
min-height: var(--app-touch-min);
|
||||
margin: 22rpx auto 0;
|
||||
}
|
||||
.pedigree-state-card__action image,
|
||||
|
||||
Reference in New Issue
Block a user