add customer lunar birthdate

This commit is contained in:
Timi
2026-08-03 18:54:28 +08:00
parent 386d5e0613
commit dd8534eaad
5 changed files with 577 additions and 2 deletions
+262
View File
@@ -0,0 +1,262 @@
<template>
<div class="birthdate-picker">
<t-radio-group class="mode" v-model="calendarProxy" variant="default-filled">
<t-radio :block="false" borderless value="SOLAR">公历</t-radio>
<t-radio :block="false" borderless value="LUNAR">农历</t-radio>
<t-button
v-if="modelValue"
class="clear"
size="extra-small"
@click="clearValue"
>
清除
</t-button>
</t-radio-group>
<div class="value" @click="openPicker">
<span :class="{ placeholder: !displayValue }" v-text="displayValue || placeholder" />
<t-icon name="chevron-right" size="1.25rem" />
</div>
<t-popup v-model="visible" placement="bottom">
<t-date-time-picker
v-if="calendarProxy === 'SOLAR'"
:title="'选择公历出生日期'"
mode="date"
:value="solarDraft"
:start="startDate"
:end="today"
format="YYYY-MM-DD"
cancel-btn="取消"
confirm-btn="确定"
@cancel="visible = false"
@confirm="handleSolarConfirm"
/>
<t-picker
v-else
title="选择农历出生日期"
:columns="lunarColumns"
:value="lunarPickerValue"
cancel-btn="取消"
confirm-btn="确定"
@pick="handleLunarPick"
@cancel="visible = false"
@confirm="handleLunarConfirm"
/>
</t-popup>
</div>
</template>
<script setup lang="ts">
import { LunarCalendar, type LunarDate } from "@/utils/LunarCalendar";
defineOptions({
name: "BirthdatePicker"
});
type BirthdateCalendar = "SOLAR" | "LUNAR";
type PickerValue = string | number;
const props = withDefaults(defineProps<{
modelValue?: string;
calendar?: BirthdateCalendar;
placeholder?: string;
}>(), {
modelValue: "",
calendar: "SOLAR",
placeholder: "请选择出生日期"
});
const emit = defineEmits<{
(e: "update:modelValue", value: string): void;
(e: "update:calendar", value: BirthdateCalendar): void;
}>();
const startDate = `${LunarCalendar.startYear}-01-31`;
const today = LunarCalendar.today();
const visible = ref(false);
const solarDraft = ref(today);
const lunarDraft = ref<LunarDate>(LunarCalendar.solarToLunar(today) || {
year: LunarCalendar.startYear,
month: 1,
day: 1,
isLeap: false
});
const calendarProxy = computed<BirthdateCalendar>({
get: () => props.calendar,
set: value => emit("update:calendar", value)
});
const displayValue = computed(() => {
if (!props.modelValue) {
return "";
}
if (calendarProxy.value === "SOLAR") {
return props.modelValue;
}
const lunarDate = LunarCalendar.solarToLunar(props.modelValue);
return lunarDate ? LunarCalendar.formatLunarDate(lunarDate) : props.modelValue;
});
const lunarPickerValue = computed(() => [
`${lunarDraft.value.year}`,
`${lunarDraft.value.month}${lunarDraft.value.isLeap ? "-leap" : ""}`,
`${lunarDraft.value.day}`
]);
const lunarColumns = computed(() => {
const yearOptions = LunarCalendar.getYearOptions(today);
const monthOptions = LunarCalendar.getMonthOptions(lunarDraft.value.year)
.filter(option => isLunarDateSelectable({
year: lunarDraft.value.year,
month: option.month,
day: 1,
isLeap: option.isLeap
}));
const selectedMonth = monthOptions.some(option => option.value === lunarPickerValue.value[1])
? lunarPickerValue.value[1]
: monthOptions[0]?.value;
const selectedOption = monthOptions.find(option => option.value === selectedMonth) || monthOptions[0];
const dayCount = selectedOption ? LunarCalendar.getMonthDays({
year: lunarDraft.value.year,
month: selectedOption.month,
isLeap: selectedOption.isLeap
}) : 29;
const dayOptions = Array.from({ length: dayCount }, (_, index) => {
const day = index + 1;
return {
value: `${day}`,
label: LunarCalendar.formatLunarDate({
year: lunarDraft.value.year,
month: selectedOption?.month || 1,
day,
isLeap: selectedOption?.isLeap || false
})
.split(" ")
.slice(-1)[0] || `${day}`
};
}).filter(option => isLunarDateSelectable({
year: lunarDraft.value.year,
month: selectedOption?.month || 1,
day: Number(option.value),
isLeap: selectedOption?.isLeap || false
}));
return [
yearOptions,
monthOptions,
dayOptions
];
});
function openPicker(): void {
if (calendarProxy.value === "SOLAR") {
solarDraft.value = props.modelValue || today;
} else {
const lunarDate = props.modelValue ? LunarCalendar.solarToLunar(props.modelValue) : LunarCalendar.solarToLunar(today);
if (lunarDate) {
lunarDraft.value = lunarDate;
}
}
visible.value = true;
}
function handleSolarConfirm(value: PickerValue): void {
const date = String(value);
if (!date) {
return;
}
emit("update:modelValue", date);
visible.value = false;
}
function handleLunarPick(value: PickerValue[]): void {
applyLunarPickerValue(value);
}
function handleLunarConfirm(value: PickerValue[]): void {
applyLunarPickerValue(value);
const solarDate = LunarCalendar.lunarToSolar(lunarDraft.value);
if (!solarDate || solarDate > today) {
return;
}
emit("update:modelValue", solarDate);
visible.value = false;
}
function applyLunarPickerValue(value: PickerValue[]): void {
const year = Number(value[0]);
const monthValue = String(value[1] || "1");
const monthOptions = LunarCalendar.getMonthOptions(year)
.filter(option => isLunarDateSelectable({
year,
month: option.month,
day: 1,
isLeap: option.isLeap
}));
const selectedMonth = monthOptions.find(option => option.value === monthValue) || monthOptions[0];
const day = Number(value[2]);
if (!year || !selectedMonth || !day) {
return;
}
const monthDays = LunarCalendar.getMonthDays({
year,
month: selectedMonth.month,
isLeap: selectedMonth.isLeap
});
lunarDraft.value = {
year,
month: selectedMonth.month,
day: Math.max(1, Math.min(day, monthDays)),
isLeap: selectedMonth.isLeap
};
}
function isLunarDateSelectable(lunarDate: LunarDate): boolean {
const solarDate = LunarCalendar.lunarToSolar(lunarDate);
return !!solarDate && solarDate <= today;
}
function clearValue(): void {
emit("update:modelValue", "");
visible.value = false;
}
</script>
<style scoped lang="less">
.birthdate-picker {
gap: .5rem;
display: flex;
flex-direction: column;
.mode {
gap: .5rem;
display: flex;
}
.value {
gap: .25rem;
flex: 1;
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
span {
line-height: 1.5;
}
.placeholder {
color: var(--td-text-color-placeholder);
}
}
.clear {
flex: none;
}
}
:global(html.theme-dark) {
.birthdate-picker {
.placeholder {
color: #94a3b8;
}
}
}
</style>