This commit is contained in:
owenchen committed 2026-10-15 23:20:36 +08:00
1 parent dcc112c076
commit b858386e86
15 files changed
+3634 -69

No files matched your search

@@ -0,0 +1,240 @@
<script setup lang="ts">
/**
* 快速新增家族成员(底部抽屉):旧 WebVue components/master/FamilyPersonQuickCreateModal.vue 的移动端等价物
*
* 族谱关系维护里「新增配偶 / 新增孩子 / 新增祖先」都调它:只填 姓* / 名(谱名)* / 性别*,
* 落库走 FamilyPersonService.Insert(TreeId 由调用方带入),成功后 emit("created", person),
* created 取 result?.FamilyPerson || payload.FamilyPerson,由页面按用途回填到对应关系列表。
* 性别用 option-picker 单选(数据来自 GenderPolicy 语言包),与成员详情页同口径。
*/
import type { FamilyPersonDisplayData } from "@/data/auto/familyPersonDisplayData";
import { useLocale } from "uview-pro";
import { computed, ref, watch } from "vue";
import { usePolicyOptions } from "@/composables/usePolicyOptions";
import { FamilyPersonData } from "@/data/auto/familyPersonData";
import { FamilyPersonDisplayData as FamilyPersonDisplayDataCls } from "@/data/auto/familyPersonDisplayData";
import familyPersonService from "@/service/master/familyPersonService";
const props = defineProps({
visible: { type: Boolean, default: false },
treeId: { type: String, default: "" },
title: { type: String, default: "" },
});
const emit = defineEmits<{
(e: "update:visible", value: boolean): void;
(e: "created", person: FamilyPersonDisplayData): void;
}>();
const { t } = useLocale();
const { getPolicyOptions, getPolicyText } = usePolicyOptions();
const genderItems = computed(() => getPolicyOptions("GenderPolicy"));
const surname = ref("");
const genealogyName = ref("");
const gender = ref("");
const saving = ref(false);
const genderPickerVisible = ref(false);
const genderLabel = computed(() => getPolicyText("GenderPolicy", gender.value));
watch(
() => props.visible,
(on) => {
if (on) {
surname.value = "";
genealogyName.value = "";
gender.value = "";
}
},
);
function close(): void {
emit("update:visible", false);
}
function onGenderConfirm(key: string): void {
gender.value = key;
genderPickerVisible.value = false;
}
async function onConfirm(): Promise<void> {
if (!surname.value.trim()) {
uni.showToast({ title: t("FamilyPersonPage.surnameRequired"), icon: "none" });
return;
}
if (!genealogyName.value.trim()) {
uni.showToast({ title: t("FamilyPersonPage.genealogyNameRequired"), icon: "none" });
return;
}
if (!gender.value) {
uni.showToast({ title: t("FamilyPersonPage.genderRequired"), icon: "none" });
return;
}
const payload = new FamilyPersonData();
payload.FamilyPerson = new FamilyPersonDisplayDataCls();
payload.FamilyPerson.Surname = surname.value.trim();
payload.FamilyPerson.GenealogyName = genealogyName.value.trim();
payload.FamilyPerson.Gender = gender.value;
payload.FamilyPerson.TreeId = props.treeId || "";
try {
saving.value = true;
const result = await familyPersonService.Insert(payload);
const created = result?.FamilyPerson || payload.FamilyPerson;
emit("created", created);
close();
}
catch (error) {
console.error(error);
uni.showToast({ title: t("FamilyPersonPage.quickCreateFailed"), icon: "none" });
}
finally {
saving.value = false;
}
}
</script>
<template>
<u-popup :model-value="props.visible" mode="bottom" round="24" :close-on-click-overlay="!saving" @close="close">
<view class="pqc">
<text class="pqc__title">
{{ props.title || t('FamilyPersonPage.quickCreateTitle') }}
</text>
<view class="pqc__field">
<text class="pqc__label">
{{ t("FamilyPersonPage.surname") }}<text class="pqc__required">*</text>
</text>
<u-input v-model="surname" :border="true" :maxlength="100"
:placeholder="t('FamilyPersonPage.surnamePlaceholder')" />
</view>
<view class="pqc__field">
<text class="pqc__label">
{{ t("FamilyPersonPage.genealogyName") }}<text class="pqc__required">*</text>
</text>
<u-input v-model="genealogyName" :border="true" :maxlength="100"
:placeholder="t('FamilyPersonPage.genealogyNamePlaceholder')" />
</view>
<view class="pqc__field">
<text class="pqc__label">
{{ t("FamilyPersonPage.gender") }}<text class="pqc__required">*</text>
</text>
<view class="pqc__picker" @tap="genderPickerVisible = true">
<text :class="{ 'pqc__picker-empty': !genderLabel }">
{{ genderLabel || t('FamilyPersonPage.genderPlaceholder') }}
</text>
<u-icon name="arrow-right" size="26" color="#c4c8cf" />
</view>
</view>
<view class="pqc__footer">
<view class="pqc__btn" @tap="close">
<text class="pqc__btn-text">
{{ t("AdminPage.cancelBut") }}
</text>
</view>
<view class="pqc__btn pqc__btn--primary" :class="{ 'pqc__btn--muted': saving }" @tap="onConfirm">
<text class="pqc__btn-text pqc__btn-text--primary">
{{ saving ? t("AdminPage.savingTip") : t("AdminPage.confirmBut") }}
</text>
</view>
</view>
<view class="pqc__safe" />
</view>
<option-picker :visible="genderPickerVisible" :title="t('FamilyPersonPage.gender')" :items="genderItems"
:empty-text="t('FamilyPersonPage.genderEmpty')" @close="genderPickerVisible = false"
@confirm="onGenderConfirm" />
</u-popup>
</template>
<style lang="scss" scoped>
.pqc {
padding: 40rpx 32rpx 0;
background-color: #ffffff;
&__title {
display: block;
padding-bottom: 24rpx;
font-size: 32rpx;
font-weight: 600;
color: #1f2329;
text-align: center;
}
&__field {
margin-bottom: 24rpx;
}
&__label {
display: block;
margin-bottom: 12rpx;
font-size: 26rpx;
color: #646a73;
}
&__required {
margin-left: 2rpx;
color: #ff4d4f;
}
&__picker {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 76rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #1f2329;
border: 1px solid #e5e6eb;
border-radius: 12rpx;
}
&__picker-empty {
color: #8f959e;
}
&__footer {
display: flex;
gap: 20rpx;
margin-top: 8rpx;
}
&__btn {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
height: 76rpx;
border-radius: 999rpx;
background-color: #f5f6f8;
&--primary {
background-color: #2b8fff;
}
&--muted {
background-color: #b9d6ff;
}
}
&__btn-text {
font-size: 28rpx;
color: #5c626b;
&--primary {
color: #ffffff;
}
}
&__safe {
height: calc(32rpx + env(safe-area-inset-bottom));
}
}
</style>