Files
Docker/uniapp/youjiala/src/components/person-quick-create/person-quick-create.vue
T
2026-10-16 11:57:29 +08:00

260 lines
8.2 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.
<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 { useThemeTokens } from "@/composables";
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 { primary } = useThemeTokens();
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__card">
<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>
<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 }"
:style="{ background: primary }" @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: 32rpx 24rpx 0;
background-color: #f2f3f5;
&__title {
display: block;
padding-bottom: 24rpx;
font-size: 32rpx;
font-weight: 600;
color: #1f2329;
text-align: center;
}
&__card {
padding: 28rpx 28rpx 4rpx;
border-radius: 20rpx;
background-color: #ffffff;
}
&__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: 28rpx;
}
/* 底部按键统一规格(同 option-picker):等高圆角矩形 + 细描边,非胶囊 */
&__btn {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
height: 96rpx;
border: 2rpx solid #e5e6eb;
border-radius: 24rpx;
background-color: #ffffff;
&--primary {
border-color: transparent;
background-color: #2b8fff;
}
/* 提交中:纯色 + 透明度置灰(色相不脱节) */
&--muted {
opacity: 0.45;
}
}
&__btn-text {
font-size: 32rpx;
color: #5c626b;
&--primary {
font-weight: 600;
color: #ffffff;
}
}
&__safe {
height: calc(32rpx + env(safe-area-inset-bottom));
}
}
</style>