260 lines
8.2 KiB
Vue
260 lines
8.2 KiB
Vue
<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>
|