263 lines
6.3 KiB
Vue
263 lines
6.3 KiB
Vue
<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>
|