feat: 完成前端业务闭环与后端联调
This commit is contained in:
+83
-20
@@ -8,7 +8,7 @@
|
||||
<text class="create-card__eyebrow">立谱信息</text>
|
||||
<text class="create-card__title">为家族创建一部家谱</text>
|
||||
<text class="create-card__note"
|
||||
>创建成功后会回到“我的家谱”;可在世系树中录入首位成员。</text
|
||||
>创建时会同步建立始迁祖人物;创建成功后会回到“我的家谱”。</text
|
||||
>
|
||||
|
||||
<view class="field-row">
|
||||
@@ -84,6 +84,37 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-row__label"
|
||||
><text class="required-mark">*</text>始迁祖</text
|
||||
>
|
||||
<input
|
||||
v-model="form.firstAncestorName"
|
||||
maxlength="30"
|
||||
placeholder="请输入始迁祖姓名"
|
||||
placeholder-class="placeholder"
|
||||
@input="clearFieldError('firstAncestorName')"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.firstAncestorName" class="field-error">{{
|
||||
fieldErrors.firstAncestorName
|
||||
}}</text>
|
||||
|
||||
<view class="owner-ancestor-field">
|
||||
<view>
|
||||
<text class="owner-ancestor-field__label">谱主本人就是始迁祖</text>
|
||||
<text class="owner-ancestor-field__hint"
|
||||
>仅本人确为始迁祖时开启,开启后账号会绑定到该人物。</text
|
||||
>
|
||||
</view>
|
||||
<switch
|
||||
:checked="form.ownerIsFirstAncestor"
|
||||
color="#9f170f"
|
||||
aria-label="谱主本人就是始迁祖"
|
||||
@change="form.ownerIsFirstAncestor = $event.detail.value"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-row__label">所在地</text>
|
||||
<input
|
||||
@@ -208,7 +239,6 @@ import {
|
||||
import { genealogyApi } from "@/services/api/genealogy-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
|
||||
import { createNonIdempotentWriteGuard } from "@/utils/non-idempotent-write-guard.js";
|
||||
import {
|
||||
GENEALOGY_ACCESS_PRESET,
|
||||
GENEALOGY_ACCESS_PRESET_OPTIONS,
|
||||
@@ -229,6 +259,8 @@ const form = reactive({
|
||||
surname: "",
|
||||
genealogyName: "",
|
||||
ancestralHall: "",
|
||||
firstAncestorName: "",
|
||||
ownerIsFirstAncestor: false,
|
||||
originPlace: "",
|
||||
addressDetail: "",
|
||||
intro: "",
|
||||
@@ -237,6 +269,7 @@ const form = reactive({
|
||||
const fieldErrors = reactive({
|
||||
surname: "",
|
||||
genealogyName: "",
|
||||
firstAncestorName: "",
|
||||
regionCode: "",
|
||||
});
|
||||
const submitError = ref("");
|
||||
@@ -248,7 +281,11 @@ const coverOssId = ref(null);
|
||||
const coverFileName = ref("");
|
||||
const coverUploadRequestController = createRequestController();
|
||||
const genealogyCreateRequestController = createRequestController();
|
||||
const genealogyCreateGuard = createNonIdempotentWriteGuard();
|
||||
const createGenealogyRequestId = () =>
|
||||
typeof globalThis.crypto?.randomUUID === "function"
|
||||
? `app-genealogy-${globalThis.crypto.randomUUID()}`
|
||||
: `app-genealogy-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
const genealogyCreateRequestId = ref(createGenealogyRequestId());
|
||||
const selectedRegion = ref(null);
|
||||
const regionPickerTrail = ref([]);
|
||||
const regionPickerDialog = ref(null);
|
||||
@@ -262,6 +299,8 @@ const hasDraft = computed(
|
||||
Object.entries(form).some(([key, value]) =>
|
||||
key === "accessPreset"
|
||||
? value !== GENEALOGY_ACCESS_PRESET.MEMBER_ONLY
|
||||
: key === "ownerIsFirstAncestor"
|
||||
? value === true
|
||||
: String(value).trim(),
|
||||
) ||
|
||||
Boolean(selectedRegion.value?.regionCode) ||
|
||||
@@ -282,12 +321,16 @@ const clearFieldError = (field) => {
|
||||
const validate = () => {
|
||||
fieldErrors.surname = form.surname.trim() ? "" : "请填写姓氏";
|
||||
fieldErrors.genealogyName = form.genealogyName.trim() ? "" : "请填写谱名";
|
||||
fieldErrors.firstAncestorName = form.firstAncestorName.trim()
|
||||
? ""
|
||||
: "请填写始迁祖姓名";
|
||||
fieldErrors.regionCode = selectedRegion.value?.regionCode
|
||||
? ""
|
||||
: "请选择所在地区";
|
||||
return (
|
||||
!fieldErrors.surname &&
|
||||
!fieldErrors.genealogyName &&
|
||||
!fieldErrors.firstAncestorName &&
|
||||
!fieldErrors.regionCode
|
||||
);
|
||||
};
|
||||
@@ -354,7 +397,6 @@ const submitCreate = async () => {
|
||||
if (!createdGenealogyId.value && !validate()) return;
|
||||
|
||||
let createPayload = null;
|
||||
let createAttempt = null;
|
||||
if (!createdGenealogyId.value) {
|
||||
const access = toApiGenealogyAccess(form.accessPreset);
|
||||
if (!access) {
|
||||
@@ -366,18 +408,15 @@ const submitCreate = async () => {
|
||||
genealogyName: form.genealogyName,
|
||||
regionCode: selectedRegion.value.regionCode,
|
||||
ancestralHall: form.ancestralHall,
|
||||
firstAncestorName: form.firstAncestorName,
|
||||
ownerIsFirstAncestor: form.ownerIsFirstAncestor,
|
||||
requestId: genealogyCreateRequestId.value,
|
||||
originPlace: form.originPlace,
|
||||
addressDetail: form.addressDetail,
|
||||
intro: form.intro,
|
||||
coverOssId: coverOssId.value,
|
||||
...access,
|
||||
};
|
||||
createAttempt = genealogyCreateGuard.begin(createPayload);
|
||||
if (createAttempt === null) {
|
||||
submitError.value =
|
||||
"上次创建结果暂时无法确认,请先返回“我的家谱”检查,避免重复创建。";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
@@ -402,15 +441,6 @@ const submitCreate = async () => {
|
||||
);
|
||||
} catch (error) {
|
||||
if (!pageActive) return;
|
||||
if (
|
||||
!createdGenealogyId.value &&
|
||||
createAttempt &&
|
||||
genealogyCreateGuard.recordFailure(createAttempt, error)
|
||||
) {
|
||||
submitError.value =
|
||||
"创建结果暂时无法确认,请先返回“我的家谱”检查,避免重复创建。";
|
||||
return;
|
||||
}
|
||||
if (isRequestCancelled(error)) return;
|
||||
submitError.value = createdGenealogyId.value
|
||||
? "家谱已经创建,但页面返回失败。请再次点击“返回我的家谱”,不要重复创建。"
|
||||
@@ -439,7 +469,7 @@ onUnload(() => {
|
||||
}
|
||||
.page-content {
|
||||
z-index: 1;
|
||||
padding: 24rpx 32rpx 72rpx;
|
||||
padding: 24rpx 32rpx calc(72rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.create-card {
|
||||
@include adaptive-genealogy-state-panel;
|
||||
@@ -500,6 +530,7 @@ onUnload(() => {
|
||||
}
|
||||
.field-row input {
|
||||
min-width: 0;
|
||||
min-height: var(--app-touch-min);
|
||||
flex: 1;
|
||||
color: $ink;
|
||||
font-size: clamp(16px, 28rpx, 20px);
|
||||
@@ -552,6 +583,38 @@ onUnload(() => {
|
||||
font-size: clamp(16px, 27rpx, 20px);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.owner-ancestor-field {
|
||||
display: flex;
|
||||
min-height: 112rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
margin-top: 22rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border: 1rpx solid rgba(128, 89, 49, 0.34);
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255, 252, 245, 0.7);
|
||||
}
|
||||
.owner-ancestor-field > view {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.owner-ancestor-field__label,
|
||||
.owner-ancestor-field__hint {
|
||||
display: block;
|
||||
}
|
||||
.owner-ancestor-field__label {
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: clamp(17px, 30rpx, 22px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.owner-ancestor-field__hint {
|
||||
margin-top: 6rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(13px, 21rpx, 16px);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.cover-field {
|
||||
display: grid;
|
||||
gap: 12rpx;
|
||||
|
||||
Reference in New Issue
Block a user