Review changes batch 6 of 6

This commit is contained in:
2026-07-20 06:52:33 +08:00
parent db97d3da27
commit 5a31a75da0
160 changed files with 9206 additions and 572 deletions
+352 -126
View File
@@ -2,50 +2,117 @@
<template>
<view class="join-page">
<GenealogyPageBackground />
<view class="join-page__header"><PageHeader :title="sourceContract.headerTitle" /></view>
<view class="join-page__header"
><PageHeader :title="sourceContract.headerTitle"
/></view>
<view class="join-panel" :class="{
'join-state--form': joinState === 'form',
'join-state--success': joinState === 'success',
'join-state--error': joinState === 'error'
}">
<image class="join-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
<view
class="join-panel"
:class="{
'join-state--form': joinState === 'form',
'join-state--success': joinState === 'success',
'join-state--error': joinState === 'error',
}"
>
<image
class="join-panel__skin"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<view v-if="joinState === 'form'" class="join-form">
<text class="join-form__eyebrow">{{ sourceContract.eyebrow }}</text>
<text class="join-form__title">{{ sourceContract.formTitle }} {{ genealogyName }}</text>
<text class="join-form__title"
>{{ sourceContract.formTitle }} {{ genealogyName }}</text
>
<text class="join-form__copy">{{ sourceContract.formCopy }}</text>
<view class="join-field">
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
<text>真实姓名</text><input v-model="form.realName" placeholder="请输入真实姓名" placeholder-class="join-placeholder" @input="clearFieldError('realName')" />
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>真实姓名</text
><input
v-model="form.realName"
placeholder="请输入真实姓名"
placeholder-class="join-placeholder"
@input="clearFieldError('realName')"
/>
</view>
<text v-if="fieldErrors.realName" class="join-field-error">{{ fieldErrors.realName }}</text>
<text v-if="fieldErrors.realName" class="join-field-error">{{
fieldErrors.realName
}}</text>
<view class="join-field">
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
<text>与家谱关系</text><input v-model="form.relation" placeholder="例如:汤正华堂侄" placeholder-class="join-placeholder" @input="clearFieldError('relation')" />
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>与家谱关系</text
><input
v-model="form.relation"
placeholder="例如:汤正华堂侄"
placeholder-class="join-placeholder"
@input="clearFieldError('relation')"
/>
</view>
<text v-if="fieldErrors.relation" class="join-field-error">{{ fieldErrors.relation }}</text>
<text class="relation-help">请以家谱中一位已知长辈为参照例如汤正华堂侄</text>
<text v-if="fieldErrors.relation" class="join-field-error">{{
fieldErrors.relation
}}</text>
<text class="relation-help"
>请以家谱中一位已知长辈为参照例如汤正华堂侄</text
>
<view class="join-field join-field--message">
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
<text>{{ sourceContract.thirdFieldLabel }}</text><textarea v-model="form.message" maxlength="80" :placeholder="sourceContract.thirdFieldPlaceholder" placeholder-class="join-placeholder" />
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>{{ sourceContract.thirdFieldLabel }}</text
><textarea
v-model="form.message"
maxlength="80"
:placeholder="sourceContract.thirdFieldPlaceholder"
placeholder-class="join-placeholder"
/>
</view>
<text class="join-form__note">{{ sourceContract.note }}</text>
<view class="join-action" @click="submitJoin">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" />
<text>{{ isSubmitting ? sourceContract.submittingLabel : sourceContract.submitLabel }}</text>
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
isSubmitting
? sourceContract.submittingLabel
: sourceContract.submitLabel
}}</text>
</view>
</view>
<view v-else class="join-result">
<text class="join-result__eyebrow">{{ joinState === 'success' ? sourceContract.successEyebrow : sourceContract.errorEyebrow }}</text>
<text class="join-result__title">{{ joinState === 'success' ? sourceContract.successTitle : sourceContract.errorTitle }}</text>
<text class="join-result__eyebrow">{{
joinState === "success"
? sourceContract.successEyebrow
: sourceContract.errorEyebrow
}}</text>
<text class="join-result__title">{{
joinState === "success"
? sourceContract.successTitle
: sourceContract.errorTitle
}}</text>
<text class="join-result__copy">{{ resultCopy }}</text>
<view class="join-action" @click="joinState === 'success' ? completeFlow() : retryForm()">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" />
<text>{{ joinState === 'success' ? sourceContract.nextLabel : '重新填写' }}</text>
<view
class="join-action"
@click="joinState === 'success' ? completeFlow() : retryForm()"
>
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
joinState === "success" ? sourceContract.nextLabel : "重新填写"
}}</text>
</view>
</view>
</view>
@@ -53,121 +120,280 @@
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import GenealogyPageBackground from '@/components/GenealogyPageBackground.vue'
import PageHeader from '@/components/PageHeader.vue'
import { computed, reactive, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
const genealogyId = ref('')
const source = ref('search')
const genealogyName = ref('这部家谱')
const genealogyId = ref("");
const source = ref("search");
const genealogyName = ref("这部家谱");
const genealogyPreview = {
'1001': '汤氏家谱',
'1002': '汝南汤氏家谱'
}
const joinState = ref('form')
const isSubmitting = ref(false)
const errorMessage = ref('')
const form = reactive({ realName: '', relation: '', message: '' })
const fieldErrors = reactive({ realName: '', relation: '' })
const sourceContract = computed(() => source.value === 'invite' ? {
headerTitle: '确认关系并加入',
eyebrow: '邀请码直接加入',
formTitle: '确认加入',
formCopy: '请填写真实身份和亲属关系;提交后直接加入,无需等待管理员审核。',
thirdFieldLabel: '补充信息',
thirdFieldPlaceholder: '选填:补充祖居地、长辈姓名等信息',
note: '邀请码来源提交后直接加入,无需等待审核。',
submitLabel: '确认加入',
submittingLabel: '正在加入…',
successEyebrow: '已加入家谱',
successTitle: '关系信息已提交',
errorEyebrow: '加入未完成',
errorTitle: '暂时无法加入家谱',
nextLabel: '返回我的家谱',
successCopy: `已直接加入“${genealogyName.value}”,无需等待审核;返回后将刷新并选中这部家谱。`
} : {
headerTitle: '申请加入家谱',
eyebrow: '公开家谱入谱申请',
formTitle: '申请加入',
formCopy: '请填写真实身份和亲属关系,管理员审核后会通过消息告知结果。',
thirdFieldLabel: '申请说明',
thirdFieldPlaceholder: '补充祖居地、长辈姓名等核验信息',
note: '提交后可在“我的申请”中查看审核进度。',
submitLabel: '提交申请',
submittingLabel: '正在提交…',
successEyebrow: '申请已送达',
successTitle: '等待管理员核实亲属关系',
errorEyebrow: '申请未提交',
errorTitle: '暂时无法提交申请',
nextLabel: '查看我的申请',
successCopy: `${genealogyName.value}”的管理员会在核实后给出结果,请留意消息中心。`
})
const resultCopy = computed(() => joinState.value === 'success'
? sourceContract.value.successCopy
: errorMessage.value || '请检查网络后重新填写;未成功提交的内容不会进入审核列表。')
1001: "汤氏家谱",
1002: "汝南汤氏家谱",
};
const joinState = ref("form");
const isSubmitting = ref(false);
const errorMessage = ref("");
const form = reactive({ realName: "", relation: "", message: "" });
const fieldErrors = reactive({ realName: "", relation: "" });
const sourceContract = computed(() =>
source.value === "invite"
? {
headerTitle: "确认关系并加入",
eyebrow: "邀请码直接加入",
formTitle: "确认加入",
formCopy:
"请填写真实身份和亲属关系;提交后直接加入,无需等待管理员审核。",
thirdFieldLabel: "补充信息",
thirdFieldPlaceholder: "选填:补充祖居地、长辈姓名等信息",
note: "邀请码来源提交后直接加入,无需等待审核。",
submitLabel: "确认加入",
submittingLabel: "正在加入…",
successEyebrow: "已加入家谱",
successTitle: "关系信息已提交",
errorEyebrow: "加入未完成",
errorTitle: "暂时无法加入家谱",
nextLabel: "返回我的家谱",
successCopy: `已直接加入“${genealogyName.value}”,无需等待审核;返回后将刷新并选中这部家谱。`,
}
: {
headerTitle: "申请加入家谱",
eyebrow: "公开家谱入谱申请",
formTitle: "申请加入",
formCopy: "请填写真实身份和亲属关系,管理员审核后会通过消息告知结果。",
thirdFieldLabel: "申请说明",
thirdFieldPlaceholder: "补充祖居地、长辈姓名等核验信息",
note: "提交后可在“我的申请”中查看审核进度。",
submitLabel: "提交申请",
submittingLabel: "正在提交…",
successEyebrow: "申请已送达",
successTitle: "等待管理员核实亲属关系",
errorEyebrow: "申请未提交",
errorTitle: "暂时无法提交申请",
nextLabel: "查看我的申请",
successCopy: `${genealogyName.value}”的管理员会在核实后给出结果,请留意消息中心。`,
},
);
const resultCopy = computed(() =>
joinState.value === "success"
? sourceContract.value.successCopy
: errorMessage.value ||
"请检查网络后重新填写;未成功提交的内容不会进入审核列表。",
);
onLoad((query) => {
genealogyId.value = query.genealogyId || ''
source.value = query.source === 'invite' ? 'invite' : 'search'
if (query.state === 'success') {
joinState.value = 'success'
return
genealogyId.value = query.genealogyId || "";
source.value = query.source === "invite" ? "invite" : "search";
if (query.state === "success") {
joinState.value = "success";
return;
}
if (!genealogyId.value) {
errorMessage.value = '没有找到要申请加入的家谱,请先返回公开家谱检索。'
joinState.value = 'error'
return
errorMessage.value = "没有找到要申请加入的家谱,请先返回公开家谱检索。";
joinState.value = "error";
return;
}
genealogyName.value = genealogyPreview[genealogyId.value] || '这部家谱'
})
genealogyName.value = genealogyPreview[genealogyId.value] || "这部家谱";
});
const submitJoin = () => {
if (isSubmitting.value) return
fieldErrors.realName = form.realName.trim() ? '' : '请填写真实姓名'
fieldErrors.relation = form.relation.trim() ? '' : '请填写与家谱的关系'
if (fieldErrors.realName || fieldErrors.relation) return
isSubmitting.value = true
if (isSubmitting.value) return;
fieldErrors.realName = form.realName.trim() ? "" : "请填写真实姓名";
fieldErrors.relation = form.relation.trim() ? "" : "请填写与家谱的关系";
if (fieldErrors.realName || fieldErrors.relation) return;
isSubmitting.value = true;
setTimeout(() => {
joinState.value = form.realName.trim() === '失败' ? 'error' : 'success'
if (joinState.value === 'error') errorMessage.value = source.value === 'invite' ? '邀请码加入暂未完成,请稍后重试。' : '申请暂未提交,请稍后重试。'
isSubmitting.value = false
}, 280)
}
const clearFieldError = (field) => { fieldErrors[field] = '' }
const retryForm = () => { joinState.value = 'form'; errorMessage.value = '' }
const toMyApplications = () => uni.redirectTo({ url: '/pages/genealogy/g09-my-applications' })
const toMyGenealogies = () => uni.reLaunch({ url: `/pages/genealogy/g01-my-genealogies?genealogyId=${genealogyId.value}` })
const completeFlow = () => source.value === 'invite' ? toMyGenealogies() : toMyApplications()
joinState.value = form.realName.trim() === "失败" ? "error" : "success";
if (joinState.value === "error")
errorMessage.value =
source.value === "invite"
? "邀请码加入暂未完成,请稍后重试。"
: "申请暂未提交,请稍后重试。";
isSubmitting.value = false;
}, 280);
};
const clearFieldError = (field) => {
fieldErrors[field] = "";
};
const retryForm = () => {
joinState.value = "form";
errorMessage.value = "";
};
const toMyApplications = () =>
uni.redirectTo({ url: "/pages/genealogy/g09-my-applications" });
const toMyGenealogies = () =>
uni.reLaunch({
url: `/pages/genealogy/g01-my-genealogies?genealogyId=${genealogyId.value}`,
});
const completeFlow = () =>
source.value === "invite" ? toMyGenealogies() : toMyApplications();
</script>
<style scoped lang="scss">
.join-page { position: relative; min-height: 100vh; overflow-x: hidden; background: $paper; }
.join-page__header { position: relative; z-index: 3; }
.join-panel { position: relative; z-index: 2; width: calc(100% - 32rpx); height: min(590px, calc((100vw - 16px) * 1.337)); margin: 22rpx auto 0; }
.join-panel__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.join-form { position: absolute; inset: 8.5% 8%; }
.join-form__eyebrow, .join-result__eyebrow { display: block; color: $brand-red; font-size: 23rpx; letter-spacing: 3rpx; }
.join-form__title, .join-result__title { display: block; margin-top: 12rpx; color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 36rpx; font-weight: 700; }
.join-form__copy, .join-result__copy { display: block; margin-top: 12rpx; color: $ink-muted; font-size: 22rpx; line-height: 1.6; }
.join-field { position: relative; height: 92rpx; margin-top: 18rpx; }
.join-field > image { position: absolute; inset: 0; width: 100%; height: 100%; }
.join-field > text { position: absolute; top: 31rpx; left: 24rpx; z-index: 1; color: $ink; font-size: 23rpx; font-weight: 700; }
.join-field input, .join-field textarea { position: absolute; top: 0; right: 20rpx; bottom: 0; left: 170rpx; z-index: 1; height: 92rpx; color: $ink; font-size: 23rpx; line-height: 92rpx; }
.join-field textarea { box-sizing: border-box; padding-top: 26rpx; line-height: 1.5; }
.join-placeholder { color: #a79884; }
.join-field-error { display: block; margin-top: 3rpx; color: $brand-red; font-size: 20rpx; text-align: right; }
.relation-help { display: block; margin-top: 5rpx; color: #8e7b67; font-size: 19rpx; line-height: 29rpx; }
.join-form__note { display: block; margin-top: 18rpx; color: $ink-muted; font-size: 21rpx; text-align: center; }
.join-action { position: relative; width: 100%; height: 82rpx; margin-top: 20rpx; }
.join-action image { position: absolute; inset: 0; width: 100%; height: 100%; }
.join-action text { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; color: #fff9ed; font-size: 27rpx; font-weight: 700; letter-spacing: 3rpx; }
.join-result { position: absolute; top: 28%; right: 12%; left: 12%; text-align: center; }
.join-result__eyebrow { text-align: center; }
.join-result__copy { margin-top: 22rpx; }
.join-result .join-action { width: 420rpx; max-width: 100%; margin: 34rpx auto 0; }
.join-page {
position: relative;
min-height: 100vh;
overflow-x: hidden;
background: $paper;
}
.join-page__header {
position: relative;
z-index: 3;
}
.join-panel {
position: relative;
z-index: 2;
width: calc(100% - 32rpx);
height: min(590px, calc((100vw - 16px) * 1.337));
margin: 22rpx auto 0;
}
.join-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.join-form {
position: absolute;
inset: 8.5% 8%;
}
.join-form__eyebrow,
.join-result__eyebrow {
display: block;
color: $brand-red;
font-size: 23rpx;
letter-spacing: 3rpx;
}
.join-form__title,
.join-result__title {
display: block;
margin-top: 12rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 36rpx;
font-weight: 700;
}
.join-form__copy,
.join-result__copy {
display: block;
margin-top: 12rpx;
color: $ink-muted;
font-size: 22rpx;
line-height: 1.6;
}
.join-field {
position: relative;
height: 92rpx;
margin-top: 18rpx;
}
.join-field > image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.join-field > text {
position: absolute;
top: 31rpx;
left: 24rpx;
z-index: 1;
color: $ink;
font-size: 23rpx;
font-weight: 700;
}
.join-field input,
.join-field textarea {
position: absolute;
top: 0;
right: 20rpx;
bottom: 0;
left: 170rpx;
z-index: 1;
height: 92rpx;
color: $ink;
font-size: 23rpx;
line-height: 92rpx;
}
.join-field textarea {
box-sizing: border-box;
padding-top: 26rpx;
line-height: 1.5;
}
.join-placeholder {
color: #a79884;
}
.join-field-error {
display: block;
margin-top: 3rpx;
color: $brand-red;
font-size: 24rpx;
line-height: 34rpx;
text-align: right;
}
.relation-help {
display: block;
margin-top: 5rpx;
color: #8e7b67;
font-size: 19rpx;
line-height: 29rpx;
}
.join-form__note {
display: block;
margin-top: 18rpx;
color: $ink-muted;
font-size: 21rpx;
text-align: center;
}
.join-action {
position: relative;
width: 100%;
height: 82rpx;
margin-top: 20rpx;
}
.join-action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.join-action text {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #fff9ed;
font-size: 27rpx;
font-weight: 700;
letter-spacing: 3rpx;
}
.join-result {
position: absolute;
top: 28%;
right: 12%;
left: 12%;
text-align: center;
}
.join-result__eyebrow {
text-align: center;
}
.join-result__copy {
margin-top: 22rpx;
}
.join-result .join-action {
width: 420rpx;
max-width: 100%;
margin: 34rpx auto 0;
}
@media (min-width: 400px) {
.join-panel { width: calc(100% - 48rpx); }
.join-form { right: 9%; left: 9%; }
.join-panel {
width: calc(100% - 48rpx);
}
.join-form {
right: 9%;
left: 9%;
}
}
</style>