Files
jiapuapp/pages/genealogy/g03-create-genealogy.vue
T
2026-07-14 17:36:43 +08:00

505 lines
12 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.
<!-- 页面编号G-03用途创建家谱与录入首代人物的同路由两步流程 -->
<template>
<view class="flow-page">
<!-- 纸纹页头和山水均由完整位图承载流程内容只叠加文字和控件 -->
<image
class="page-paper-texture"
src="/static/assets/foundation/opaque/page-paper.jpg"
mode="scaleToFill"
/>
<image
class="page-footer-landscape"
src="/static/assets/foundation/transparent/footer-mountain-bamboo.png"
mode="scaleToFill"
/>
<view class="flow-header">
<image
class="flow-header__skin"
src="/static/assets/foundation/opaque/root-header-cinnabar.jpg"
mode="scaleToFill"
/>
<view class="flow-header__content">
<view class="flow-header__back" @click="goBack">
<image
class="flow-header__back-icon"
src="/static/assets/foundation/transparent/chevron-right.png"
mode="aspectFit"
/>
<text>返回</text>
</view>
<text class="flow-header__title">{{ isAncestorStep ? "录入首代人物" : "创建家谱" }}</text>
<view class="flow-header__side" />
</view>
</view>
<view class="flow-content">
<view class="create-flow-panel">
<image
class="create-flow-panel__skin"
src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png"
mode="scaleToFill"
/>
<view v-if="!isAncestorStep" class="create-flow-panel__content">
<view class="flow-step-label"><text>第一步 · 立谱信息</text></view>
<text class="flow-heading">为家族立一部可传承的谱</text>
<text class="flow-note">家谱建立后可继续完善名称与访问规则由创建者维护</text>
<view class="flow-fields">
<view class="field-row">
<text>姓氏</text>
<input v-model="createForm.surname" maxlength="4" placeholder="如:汤" placeholder-class="placeholder" />
</view>
<view class="field-row">
<text>谱名</text>
<input v-model="createForm.name" maxlength="24" placeholder="如:汤氏家谱" placeholder-class="placeholder" />
</view>
<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" />
</view>
</view>
<view class="flow-rule">
<text class="flow-rule__label">访问规则</text>
<text class="flow-rule__value">默认仅成员可见</text>
</view>
<text class="flow-rule__note">保护族人资料创建后可在家谱设置中开放搜索与申请加入</text>
<view class="flow-primary-action" hover-class="action-hover" @click="submitCreate">
<image
class="flow-primary-action__skin"
src="/static/assets/foundation/opaque/a01-primary-button.png"
mode="scaleToFill"
/>
<text class="flow-primary-action__copy">创建并录入首代</text>
</view>
</view>
<view v-else class="create-flow-panel__content">
<view class="flow-step-label"><text>第二步 · 首代人物</text></view>
<text class="flow-heading">家谱从这一位开始</text>
<text class="flow-note">可先录入姓名世代和简要生平其他资料后续随时补充</text>
<view class="flow-fields">
<view class="field-row">
<text>姓名</text>
<input v-model="ancestorForm.personName" maxlength="20" placeholder="请输入首代姓名" placeholder-class="placeholder" />
</view>
<view class="field-row">
<text>世代</text>
<text class="fixed-value">第一世</text>
</view>
<view class="field-row">
<text>出生日期</text>
<input v-model="ancestorForm.birthDate" placeholder="如:1940年" placeholder-class="placeholder" />
</view>
</view>
<view class="intro-field">
<text>生平简述</text>
<textarea v-model="ancestorForm.introduction" maxlength="200" placeholder="可选,记录家训、迁徙或重要经历" placeholder-class="placeholder" />
</view>
<view class="flow-primary-action" hover-class="action-hover" @click="submitAncestor">
<image
class="flow-primary-action__skin"
src="/static/assets/foundation/opaque/a01-primary-button.png"
mode="scaleToFill"
/>
<text class="flow-primary-action__copy">保存并查看世系树</text>
</view>
</view>
</view>
</view>
</view>
</template>
<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 createForm = reactive({
surname: "",
name: "",
hall: "",
location: "",
visibility: "MEMBER_ONLY",
});
const ancestorForm = reactive({
personName: "",
generationNo: 1,
generationName: "一世",
sex: "0",
birthDate: "",
introduction: "",
});
const isAncestorStep = computed(() => currentStep.value === "ancestor");
const getFlowQuery = () => {
if (typeof location !== "undefined") {
return new URLSearchParams(location.hash.split("?")[1] || "");
}
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
return currentPage?.options || {};
};
const syncFlowFromRoute = () => {
const query = getFlowQuery();
const isAncestorRoute = query.get
? query.get("step") === "ancestor"
: 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);
}
};
onLoad(() => {
syncFlowFromRoute();
});
onShow(() => {
syncFlowFromRoute();
});
onMounted(() => {
if (typeof window !== "undefined") {
window.addEventListener("hashchange", syncFlowFromRoute);
}
});
onUnmounted(() => {
if (typeof window !== "undefined") {
window.removeEventListener("hashchange", syncFlowFromRoute);
}
});
const goBack = () => {
if (isAncestorStep.value) {
uni.redirectTo({ url: "/pages/genealogy/g03-create-genealogy" });
return;
}
uni.navigateBack();
};
const submitCreate = async () => {
if (!createForm.surname.trim() || !createForm.name.trim()) {
uni.showToast({ title: "请填写姓氏和谱名", icon: "none" });
return;
}
const created = await appApi.createGenealogy({ ...createForm });
genealogyContext.setCurrentGenealogyId(created.id);
uni.redirectTo({
url: `/pages/genealogy/g03-create-genealogy?step=ancestor&genealogyId=${created.id}`,
});
};
const submitAncestor = async () => {
if (isSubmitting.value) return;
if (!genealogyId.value) {
uni.showToast({ title: "未找到当前家谱", icon: "none" });
return;
}
if (!ancestorForm.personName.trim()) {
uni.showToast({ title: "请填写首代姓名", icon: "none" });
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 {
isSubmitting.value = false;
}
};
</script>
<style scoped lang="scss">
.flow-page {
position: relative;
min-height: 100vh;
overflow: hidden;
background: #f9f6ef;
}
.page-paper-texture {
position: absolute;
top: 112rpx;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
opacity: 0.74;
pointer-events: none;
}
.page-footer-landscape {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
width: 100%;
height: 430rpx;
opacity: 0.42;
pointer-events: none;
}
.flow-header {
position: relative;
z-index: 3;
height: 112rpx;
overflow: hidden;
}
.flow-header__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.flow-header__content {
position: relative;
z-index: 1;
display: flex;
height: 100%;
align-items: center;
justify-content: space-between;
padding: 0 26rpx;
box-sizing: border-box;
}
.flow-header__back,
.flow-header__side {
display: flex;
width: 130rpx;
align-items: center;
}
.flow-header__back {
color: #fff3d9;
font-family: "STKaiti", "KaiTi", serif;
font-size: 27rpx;
}
.flow-header__back-icon {
width: 30rpx;
height: 30rpx;
margin-right: 4rpx;
transform: rotate(180deg);
}
.flow-header__title {
flex: 1;
color: #ffe4a7;
font-family: "STKaiti", "KaiTi", serif;
font-size: 39rpx;
font-weight: 700;
letter-spacing: 4rpx;
text-align: center;
}
.flow-content {
position: relative;
z-index: 2;
padding: 24rpx 32rpx 48rpx;
}
.create-flow-panel {
position: relative;
min-height: 876rpx;
}
.create-flow-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.create-flow-panel__content {
position: relative;
z-index: 1;
display: flex;
min-height: 876rpx;
flex-direction: column;
box-sizing: border-box;
padding: 48rpx 50rpx 42rpx;
}
.flow-step-label {
color: #a33b2b;
font-family: "STKaiti", "KaiTi", serif;
font-size: 25rpx;
font-weight: 700;
letter-spacing: 2rpx;
}
.flow-heading {
display: block;
margin-top: 13rpx;
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
font-size: 39rpx;
font-weight: 700;
letter-spacing: 2rpx;
}
.flow-note,
.flow-rule__note {
display: block;
color: #786654;
font-size: 23rpx;
line-height: 1.65;
}
.flow-note {
min-height: 56rpx;
margin-top: 12rpx;
}
.flow-fields {
margin-top: 10rpx;
}
.field-row {
display: flex;
min-height: 86rpx;
align-items: center;
border-bottom: 1rpx solid rgba(181, 138, 75, 0.36);
}
.field-row > text {
width: 126rpx;
flex: 0 0 auto;
color: #786654;
font-family: "STKaiti", "KaiTi", serif;
font-size: 29rpx;
}
.field-row input {
min-width: 0;
flex: 1;
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
font-size: 28rpx;
}
.placeholder {
color: #b7aa97;
}
.fixed-value {
color: #a33b2b !important;
}
.flow-rule {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
}
.flow-rule__label {
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
font-size: 29rpx;
font-weight: 700;
}
.flow-rule__value {
color: #a33b2b;
font-size: 23rpx;
}
.flow-rule__note {
margin-top: 8rpx;
}
.intro-field {
margin-top: 18rpx;
}
.intro-field > text {
display: block;
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
font-size: 29rpx;
font-weight: 700;
}
.intro-field textarea {
width: 100%;
height: 142rpx;
margin-top: 8rpx;
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
font-size: 26rpx;
line-height: 1.6;
}
.flow-primary-action {
position: relative;
display: flex;
width: 100%;
height: 96rpx;
align-items: center;
justify-content: center;
margin-top: auto;
overflow: hidden;
}
.flow-primary-action__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.flow-primary-action__copy {
position: relative;
z-index: 1;
color: #fff9ec;
font-family: "STKaiti", "KaiTi", serif;
font-size: 31rpx;
font-weight: 700;
letter-spacing: 4rpx;
}
.action-hover {
opacity: 0.82;
}
</style>