验收20%
This commit is contained in:
@@ -49,27 +49,34 @@
|
||||
<view class="flow-fields">
|
||||
<view class="field-row">
|
||||
<text>姓氏</text>
|
||||
<input v-model="createForm.surname" maxlength="4" placeholder="如:汤" placeholder-class="placeholder" />
|
||||
<input v-model="createForm.surname" maxlength="4" placeholder="如:汤" placeholder-class="placeholder" @input="clearFieldError('surname')" />
|
||||
</view>
|
||||
<text v-if="fieldErrors.surname" class="field-error">{{ fieldErrors.surname }}</text>
|
||||
<view class="field-row">
|
||||
<text>谱名</text>
|
||||
<input v-model="createForm.name" maxlength="24" placeholder="如:汤氏家谱" placeholder-class="placeholder" />
|
||||
<input v-model="createForm.name" maxlength="24" placeholder="如:汤氏家谱" placeholder-class="placeholder" @input="clearFieldError('name')" />
|
||||
</view>
|
||||
<text v-if="fieldErrors.name" class="field-error">{{ fieldErrors.name }}</text>
|
||||
<view class="field-row">
|
||||
<text>堂号</text>
|
||||
<input v-model="createForm.hall" maxlength="12" placeholder="选填" placeholder-class="placeholder" />
|
||||
</view>
|
||||
<view class="field-row">
|
||||
<text>所在地</text>
|
||||
<input v-model="createForm.location" maxlength="30" placeholder="请选择或输入" placeholder-class="placeholder" />
|
||||
<input v-model="createForm.location" maxlength="30" placeholder="请选择或输入" placeholder-class="placeholder" @input="clearFieldError('location')" />
|
||||
</view>
|
||||
<text v-if="fieldErrors.location" class="field-error">{{ fieldErrors.location }}</text>
|
||||
</view>
|
||||
|
||||
<view class="flow-rule">
|
||||
<text class="flow-rule__label">访问规则</text>
|
||||
<text class="flow-rule__value">默认仅成员可见</text>
|
||||
<view class="flow-rule__options">
|
||||
<view class="flow-rule__option" :class="{ 'flow-rule__option--active': createForm.visibility === 'MEMBER_ONLY' }" @click="createForm.visibility = 'MEMBER_ONLY'">仅成员可见</view>
|
||||
<view class="flow-rule__option" :class="{ 'flow-rule__option--active': createForm.visibility === 'SEARCHABLE' }" @click="createForm.visibility = 'SEARCHABLE'">可搜索申请</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="flow-rule__note">保护族人资料;创建后可在家谱设置中开放搜索与申请加入。</text>
|
||||
<text v-if="createState === 'error'" class="flow-error">创建样式模拟失败,请检查信息后重试。</text>
|
||||
|
||||
<view class="flow-primary-action" hover-class="action-hover" @click="submitCreate">
|
||||
<image
|
||||
@@ -77,7 +84,7 @@
|
||||
src="/static/assets/foundation/opaque/a01-primary-button.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text class="flow-primary-action__copy">创建并录入首代</text>
|
||||
<text class="flow-primary-action__copy">{{ createState === 'submitting' ? '正在创建…' : '创建并录入首代' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -89,8 +96,9 @@
|
||||
<view class="flow-fields">
|
||||
<view class="field-row">
|
||||
<text>姓名</text>
|
||||
<input v-model="ancestorForm.personName" maxlength="20" placeholder="请输入首代姓名" placeholder-class="placeholder" />
|
||||
<input v-model="ancestorForm.personName" maxlength="20" placeholder="请输入首代姓名" placeholder-class="placeholder" @input="clearFieldError('personName')" />
|
||||
</view>
|
||||
<text v-if="fieldErrors.personName" class="field-error">{{ fieldErrors.personName }}</text>
|
||||
<view class="field-row">
|
||||
<text>世代</text>
|
||||
<text class="fixed-value">第一世</text>
|
||||
@@ -100,6 +108,7 @@
|
||||
<input v-model="ancestorForm.birthDate" placeholder="如:1940年" placeholder-class="placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="ancestorState === 'error'" class="flow-error">首代人物保存失败,请稍后重试。</text>
|
||||
|
||||
<view class="intro-field">
|
||||
<text>生平简述</text>
|
||||
@@ -112,7 +121,40 @@
|
||||
src="/static/assets/foundation/opaque/a01-primary-button.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text class="flow-primary-action__copy">保存并查看世系树</text>
|
||||
<text class="flow-primary-action__copy">{{ ancestorState === 'submitting' ? '正在保存…' : '保存并进入家谱' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="duplicateReminderVisible" class="duplicate-reminder-layer" @click="closeDuplicateReminder">
|
||||
<view class="duplicate-reminder" @click.stop>
|
||||
<image class="duplicate-reminder__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
|
||||
<view class="duplicate-reminder__content">
|
||||
<text class="duplicate-reminder__title">先确认是否已有家谱</text>
|
||||
<text class="duplicate-reminder__copy">同一家族可能已经建谱。建议先搜索谱名、地区和堂号,避免重复创建。</text>
|
||||
<view class="duplicate-reminder__search" @click="searchExistingGenealogy">
|
||||
<image src="/static/assets/foundation/opaque/a01-secondary-button.png" mode="scaleToFill" />
|
||||
<text>先搜索已有家谱</text>
|
||||
</view>
|
||||
<view class="duplicate-reminder__confirm" @click="confirmCreate">
|
||||
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
|
||||
<text>确认没有,继续创建</text>
|
||||
</view>
|
||||
<view class="duplicate-reminder__cancel" @click="closeDuplicateReminder">返回修改</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="ancestorState === 'success'" class="flow-success-layer">
|
||||
<view class="flow-success-dialog">
|
||||
<image class="flow-success-dialog__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
|
||||
<view class="flow-success-dialog__content">
|
||||
<text class="flow-success-dialog__title">家谱创建完成</text>
|
||||
<text class="flow-success-dialog__copy">首代人物已保存,接下来进入家谱总览继续完善资料。</text>
|
||||
<view class="flow-success-dialog__action" @click="enterOverview">
|
||||
<image src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
|
||||
<text>进入家谱总览</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -123,12 +165,15 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { appApi } from "@/utils/api.js";
|
||||
import { genealogyContext } from "@/utils/genealogy-context.js";
|
||||
|
||||
const currentStep = ref("create");
|
||||
const genealogyId = ref("");
|
||||
const isSubmitting = ref(false);
|
||||
const createState = ref("form");
|
||||
const ancestorState = ref("form");
|
||||
const duplicateReminderVisible = ref(false);
|
||||
const fieldErrors = reactive({ surname: "", name: "", location: "", personName: "" });
|
||||
let submitTimer = null;
|
||||
const createForm = reactive({
|
||||
surname: "",
|
||||
name: "",
|
||||
@@ -164,11 +209,7 @@ const syncFlowFromRoute = () => {
|
||||
: query.step === "ancestor";
|
||||
const routeGenealogyId = query.get ? query.get("genealogyId") : query.genealogyId;
|
||||
currentStep.value = isAncestorRoute ? "ancestor" : "create";
|
||||
genealogyId.value = routeGenealogyId || (isAncestorStep.value ? genealogyContext.getCurrentGenealogyId() : "");
|
||||
|
||||
if (isAncestorStep.value && genealogyId.value) {
|
||||
genealogyContext.setCurrentGenealogyId(genealogyId.value);
|
||||
}
|
||||
genealogyId.value = routeGenealogyId || (isAncestorStep.value ? "local-created-genealogy" : "");
|
||||
};
|
||||
|
||||
onLoad(() => {
|
||||
@@ -186,6 +227,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (submitTimer) clearTimeout(submitTimer);
|
||||
if (typeof window !== "undefined") {
|
||||
window.removeEventListener("hashchange", syncFlowFromRoute);
|
||||
}
|
||||
@@ -200,52 +242,66 @@ const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const submitCreate = async () => {
|
||||
if (!createForm.surname.trim() || !createForm.name.trim()) {
|
||||
uni.showToast({ title: "请填写姓氏和谱名", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const clearFieldError = (field) => { fieldErrors[field] = ""; };
|
||||
|
||||
const created = await appApi.createGenealogy({ ...createForm });
|
||||
genealogyContext.setCurrentGenealogyId(created.id);
|
||||
uni.redirectTo({
|
||||
url: `/pages/genealogy/g03-create-genealogy?step=ancestor&genealogyId=${created.id}`,
|
||||
});
|
||||
const validateCreateForm = () => {
|
||||
fieldErrors.surname = createForm.surname.trim() ? "" : "请填写姓氏";
|
||||
fieldErrors.name = createForm.name.trim() ? "" : "请填写谱名";
|
||||
fieldErrors.location = createForm.location.trim() ? "" : "请填写所在地";
|
||||
return !fieldErrors.surname && !fieldErrors.name && !fieldErrors.location;
|
||||
};
|
||||
|
||||
const submitAncestor = async () => {
|
||||
const submitCreate = () => {
|
||||
if (isSubmitting.value) return;
|
||||
createState.value = "form";
|
||||
if (!validateCreateForm()) return;
|
||||
duplicateReminderVisible.value = true;
|
||||
};
|
||||
|
||||
const closeDuplicateReminder = () => { duplicateReminderVisible.value = false; };
|
||||
const searchExistingGenealogy = () => uni.navigateTo({ url: "/pages/genealogy/g06-search-genealogies" });
|
||||
const confirmCreate = () => {
|
||||
closeDuplicateReminder();
|
||||
isSubmitting.value = true;
|
||||
createState.value = "submitting";
|
||||
submitTimer = setTimeout(() => {
|
||||
isSubmitting.value = false;
|
||||
if (createForm.name.trim() === "失败") {
|
||||
createState.value = "error";
|
||||
return;
|
||||
}
|
||||
const createdId = "local-created-genealogy";
|
||||
uni.redirectTo({ url: `/pages/genealogy/g03-create-genealogy?step=ancestor&genealogyId=${createdId}` });
|
||||
}, 320);
|
||||
};
|
||||
|
||||
const submitAncestor = () => {
|
||||
if (isSubmitting.value) return;
|
||||
if (!genealogyId.value) {
|
||||
uni.showToast({ title: "未找到当前家谱", icon: "none" });
|
||||
ancestorState.value = "error";
|
||||
return;
|
||||
}
|
||||
if (!ancestorForm.personName.trim()) {
|
||||
uni.showToast({ title: "请填写首代姓名", icon: "none" });
|
||||
fieldErrors.personName = "请填写首代姓名";
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
const person = await appApi.createPerson(genealogyId.value, {
|
||||
...ancestorForm,
|
||||
personName: ancestorForm.personName.trim(),
|
||||
});
|
||||
uni.redirectTo({
|
||||
url: `/pages/tree/t01-tree-overview?genealogyId=${genealogyId.value}&selectedId=${person.id}`,
|
||||
});
|
||||
} catch (error) {
|
||||
uni.showToast({ title: error.message || "保存失败,请稍后重试", icon: "none" });
|
||||
} finally {
|
||||
ancestorState.value = "submitting";
|
||||
submitTimer = setTimeout(() => {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
ancestorState.value = ancestorForm.personName.trim() === "失败" ? "error" : "success";
|
||||
}, 320);
|
||||
};
|
||||
|
||||
const enterOverview = () => uni.redirectTo({ url: `/pages/genealogy/g05-genealogy-overview?genealogyId=${genealogyId.value}` });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.flow-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
background: #f9f6ef;
|
||||
}
|
||||
|
||||
@@ -338,7 +394,7 @@ const submitAncestor = async () => {
|
||||
|
||||
.create-flow-panel {
|
||||
position: relative;
|
||||
min-height: 876rpx;
|
||||
min-height: 1000rpx;
|
||||
}
|
||||
|
||||
.create-flow-panel__skin {
|
||||
@@ -353,7 +409,7 @@ const submitAncestor = async () => {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
min-height: 876rpx;
|
||||
min-height: 1000rpx;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding: 48rpx 50rpx 42rpx;
|
||||
@@ -420,6 +476,7 @@ const submitAncestor = async () => {
|
||||
.placeholder {
|
||||
color: #b7aa97;
|
||||
}
|
||||
.field-error { display: block; margin-top: 3rpx; color: $brand-red; font-size: 20rpx; text-align: right; }
|
||||
|
||||
.fixed-value {
|
||||
color: #a33b2b !important;
|
||||
@@ -439,14 +496,14 @@ const submitAncestor = async () => {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.flow-rule__value {
|
||||
color: #a33b2b;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
.flow-rule__options { display: flex; gap: 12rpx; }
|
||||
.flow-rule__option { display: flex; min-height: 54rpx; align-items: center; padding: 0 10rpx; color: #786654; font-size: 21rpx; }
|
||||
.flow-rule__option--active { color: $brand-red; font-weight: 700; }
|
||||
|
||||
.flow-rule__note {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.flow-error { display: block; margin-top: 10rpx; color: $brand-red; font-size: 21rpx; text-align: center; }
|
||||
|
||||
.intro-field {
|
||||
margin-top: 18rpx;
|
||||
@@ -501,4 +558,30 @@ const submitAncestor = async () => {
|
||||
.action-hover {
|
||||
opacity: 0.82;
|
||||
}
|
||||
.duplicate-reminder-layer,
|
||||
.flow-success-layer { position: fixed; z-index: 30; inset: 0; display: flex; align-items: center; justify-content: center; box-sizing: border-box; padding: 42rpx; background: rgba(34, 20, 12, .6); }
|
||||
.duplicate-reminder,
|
||||
.flow-success-dialog { position: relative; width: 100%; max-width: 670rpx; min-height: 650rpx; }
|
||||
.duplicate-reminder__skin,
|
||||
.flow-success-dialog__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.duplicate-reminder__content,
|
||||
.flow-success-dialog__content { position: relative; z-index: 1; display: flex; min-height: 650rpx; flex-direction: column; align-items: center; box-sizing: border-box; padding: 92rpx 64rpx 56rpx; text-align: center; }
|
||||
.duplicate-reminder__title,
|
||||
.flow-success-dialog__title { color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 39rpx; font-weight: 700; }
|
||||
.duplicate-reminder__copy,
|
||||
.flow-success-dialog__copy { margin-top: 24rpx; color: $ink-muted; font-size: 24rpx; line-height: 40rpx; }
|
||||
.duplicate-reminder__search,
|
||||
.duplicate-reminder__confirm,
|
||||
.flow-success-dialog__action { position: relative; display: flex; width: 100%; height: 92rpx; align-items: center; justify-content: center; overflow: hidden; }
|
||||
.duplicate-reminder__search { margin-top: 38rpx; }
|
||||
.duplicate-reminder__confirm { margin-top: 14rpx; }
|
||||
.duplicate-reminder__search image,
|
||||
.duplicate-reminder__confirm image,
|
||||
.flow-success-dialog__action image { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.duplicate-reminder__search text,
|
||||
.duplicate-reminder__confirm text,
|
||||
.flow-success-dialog__action text { position: relative; z-index: 1; color: #fff9ec; font-size: 25rpx; font-weight: 700; }
|
||||
.duplicate-reminder__search text { color: #7b4e24; }
|
||||
.duplicate-reminder__cancel { display: flex; min-height: 68rpx; align-items: center; margin-top: auto; color: $ink-muted; font-size: 23rpx; }
|
||||
.flow-success-dialog__action { margin-top: 44rpx; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user