Files
jiapuapp/pages/genealogy/join-application.vue
T

296 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="join-page">
<GenealogyPageBackground />
<view class="page-header"
><PageHeader title="申请加入家谱" custom-back @back="requestBack"
/></view>
<view class="page-content">
<view v-if="!hasValidContext" class="state-card">
<text>暂时无法提交申请</text>
<text>未找到可申请的家谱请返回后重新进入</text>
<AppButton block label="返回搜索家谱" @click="backToSearch" />
</view>
<view v-else-if="joinApplicationState === 'success'" class="state-card">
<text>申请已提交</text>
<text>申请已提交可在我的申请中查看审核进度</text>
<AppButton block label="查看我的申请" @click="openMyApplications" />
</view>
<view v-else class="join-form">
<view class="form-heading">
<text>申请加入</text>
<text>{{ genealogyName || "当前家谱" }}</text>
</view>
<view v-for="field in fields" :key="field.key" class="form-field">
<text>{{ field.label }}</text>
<textarea
v-if="field.multiline"
v-model.trim="form[field.key]"
auto-height
:maxlength="field.maxlength"
:placeholder="field.placeholder"
:disabled="joinApplicationState === 'submitting'"
@input="error = ''"
/>
<input
v-else
v-model.trim="form[field.key]"
:type="field.inputType || 'text'"
:maxlength="field.maxlength"
:placeholder="field.placeholder"
:disabled="joinApplicationState === 'submitting'"
@input="error = ''"
/>
<text v-if="field.key === 'relationDesc'" class="form-field__hint"
>请填写您与家谱成员的关系例如我是某某某的堂侄</text
>
</view>
<text v-if="error" class="form-error">{{ error }}</text>
<AppButton
block
:disabled="joinApplicationState === 'submitting'"
:label="joinApplicationState === 'submitting' ? '正在提交' : '提交申请'"
@click="submitJoinApplication"
/>
</view>
</view>
<AppDialog
:visible="discardVisible"
:close-on-mask="false"
eyebrow="放弃确认"
title="放弃当前申请?"
message="当前填写内容尚未提交,离开后会清除。"
confirm-text="确认放弃"
cancel-text="继续填写"
show-cancel
@confirm="confirmDiscard"
@cancel="cancelDiscard"
/>
</view>
</template>
<script setup>
import { computed, reactive, ref } from "vue";
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppDialog from "@/components/AppDialog.vue";
import GenealogyPageBackground from "@/components/genealogy/PageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
createRequestController,
isRequestCancelled
} from "@/services/api/request-controller.js";
import { genealogyMembershipApi } from "@/services/api/genealogy-membership-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 { handleBackPress, openPage, returnTo, runBackGuard } from "@/utils/navigation/gateway.js";
const genealogyId = ref("");
const genealogyName = ref("");
const joinApplicationState = ref("form");
const error = ref("");
const discardVisible = ref(false);
const form = reactive({
applicantName: "",
phone: "",
relationDesc: "",
applyReason: "",
});
const fields = [
{
key: "applicantName",
label: "申请人姓名",
maxlength: 50,
placeholder: "填写您的姓名",
},
{
key: "phone",
label: "手机号",
inputType: "number",
maxlength: 11,
placeholder: "填写联系电话",
},
{
key: "relationDesc",
label: "关系说明",
maxlength: 100,
placeholder: "例如:本族成员",
},
{
key: "applyReason",
label: "申请理由",
multiline: true,
maxlength: 500,
placeholder: "说明申请加入的原因",
},
];
const joinApplicationSubmitController = createRequestController();
const joinApplicationGuard = createNonIdempotentWriteGuard();
let pageActive = true;
const hasValidContext = computed(() => /^[1-9]\d*$/.test(genealogyId.value));
const dirty = computed(() => Object.values(form).some((value) => value.trim()));
const confirmation = createDiscardConfirmation((visible) => {
discardVisible.value = visible;
});
const confirmDiscard = confirmation.confirm;
const cancelDiscard = confirmation.cancel;
onLoad((query) => {
genealogyId.value = String(query?.genealogyId || "");
genealogyName.value = String(query?.genealogyName || "");
});
const backToSearch = () => returnTo("G06");
const openMyApplications = () => openPage("G09", {}, "G08");
const submitJoinApplication = async () => {
if (joinApplicationState.value === "submitting" || !hasValidContext.value) return;
const payload = { ...form };
const submitAttempt = joinApplicationGuard.begin(payload);
if (submitAttempt === null) {
error.value =
"上次提交结果暂时无法确认,请先查看“我的申请”,避免重复提交。";
return;
}
joinApplicationState.value = "submitting";
error.value = "";
try {
await genealogyMembershipApi.applyToJoin(
genealogyId.value,
payload,
{ requestController: joinApplicationSubmitController },
);
if (!pageActive) return;
joinApplicationState.value = "success";
} catch (cause) {
if (!pageActive) return;
joinApplicationState.value = "form";
if (joinApplicationGuard.recordFailure(submitAttempt, cause)) {
error.value =
"申请结果暂时无法确认,请先查看“我的申请”,避免重复提交。";
return;
}
if (!isRequestCancelled(cause))
error.value = getRequestErrorMessage(cause, "申请提交失败,请稍后重试。");
}
};
const requestBack = () =>
runBackGuard({
transientOpen: discardVisible.value,
dirty: dirty.value && joinApplicationState.value === "form",
submitting: joinApplicationState.value === "submitting",
"close-transient": cancelDiscard,
"block-submitting": () => true,
"confirm-discard": confirmation.request,
});
onBackPress((event) => handleBackPress(event, requestBack));
onUnload(() => {
pageActive = false;
joinApplicationSubmitController.abort();
confirmation.dispose();
});
</script>
<style scoped lang="scss">
@import "../../styles/adaptive-frame-profiles.scss";
.join-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $paper;
}
.page-header,
.page-content {
z-index: 1;
}
.page-content {
padding: 18rpx 24rpx 72rpx;
}
.state-card,
.join-form {
@include adaptive-genealogy-state-panel;
}
.state-card {
min-height: 340rpx;
padding: 78rpx 52rpx 50rpx;
text-align: center;
}
.state-card text {
display: block;
}
.state-card text:first-child,
.form-heading text:first-child {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: clamp(19px, 35rpx, 24px);
font-weight: 700;
}
.state-card text:nth-child(2) {
margin-top: 18rpx;
color: $ink-muted;
font-size: clamp(15px, 24rpx, 18px);
line-height: 1.65;
}
.state-card .app-button,
.join-form .app-button {
margin-top: 28rpx;
}
.join-form {
padding: 30rpx;
}
.form-heading {
display: flex;
flex-direction: column;
gap: 8rpx;
padding-bottom: 22rpx;
border-bottom: 1rpx solid rgba(128, 89, 49, 0.24);
}
.form-heading text:last-child {
color: $brand-red;
font-size: clamp(15px, 24rpx, 18px);
}
.form-field {
margin-top: 22rpx;
}
.form-field > text {
display: block;
color: $ink;
font-size: clamp(15px, 24rpx, 18px);
font-weight: 700;
}
.form-field input,
.form-field textarea {
@include adaptive-genealogy-form-field;
box-sizing: border-box;
display: block;
width: 100%;
margin-top: 10rpx;
padding: 16rpx 22rpx;
color: $ink;
font-size: clamp(15px, 24rpx, 18px);
}
.form-field > text.form-field__hint {
margin-top: 8rpx;
color: $ink-muted;
font-size: clamp(13px, 21rpx, 16px);
font-weight: 400;
line-height: 1.5;
}
.form-field input {
min-height: 76rpx;
padding-top: 0;
padding-bottom: 0;
}
.form-field textarea {
min-height: 120rpx;
line-height: 1.55;
}
.form-error {
display: block;
margin-top: 16rpx;
color: $brand-red;
font-size: clamp(14px, 22rpx, 17px);
line-height: 1.45;
}
</style>