验收20%

This commit is contained in:
2026-07-16 07:42:07 +08:00
parent 0dec90ffdd
commit cb25317412
108 changed files with 6477 additions and 1308 deletions
+129 -46
View File
@@ -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>