| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- <script setup lang="ts">
- /**
- * 日期选择器组件
- * @description 支持年月切换、日期选择、价格/库存状态展示
- */
- interface DayItem {
- day: number
- status?: string
- price?: number
- selected?: boolean
- disabled?: boolean
- }
- interface Props {
- /** 当前选中的日期 */
- modelValue?: Date
- /** 标题 */
- title?: string
- /** 日期数据 */
- dayData?: DayItem[]
- /** 是否显示星期标题 */
- showWeekHeader?: boolean
- /** 自定义类名 */
- customClass?: string
- }
- const props = withDefaults(defineProps<Props>(), {
- modelValue: () => new Date(),
- title: '选择日期',
- dayData: () => [],
- showWeekHeader: true,
- customClass: '',
- })
- const emit = defineEmits<{
- /** 选中日期变化 */
- (e: 'update:modelValue', value: Date): void
- /** 选中日期 */
- (e: 'select', value: { date: Date, day: number, item?: DayItem }): void
- /** 月份变化 */
- (e: 'monthChange', value: { year: number, month: number }): void
- }>()
- // 星期标题
- const weekDays = ['一', '二', '三', '四', '五', '六', '日']
- // 当前显示的日期
- const currentDate = ref(new Date(props.modelValue))
- // 当前年月显示文本
- const currentMonthText = computed(() => {
- return `${currentDate.value.getFullYear()}年${currentDate.value.getMonth() + 1}月`
- })
- // 当前年月值
- const currentYear = computed(() => currentDate.value.getFullYear())
- const currentMonth = computed(() => currentDate.value.getMonth())
- // 计算当前月日历数据
- const calendarDays = computed(() => {
- const year = currentYear.value
- const month = currentMonth.value
- // 获取当月第一天是星期几 (0=周日, 1=周一...)
- const firstDayOfMonth = new Date(year, month, 1).getDay()
- // 转换为周一开头 (0=周一, 6=周日)
- const firstDayIndex = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1
- // 获取当月天数
- const daysInMonth = new Date(year, month + 1, 0).getDate()
- // 获取上月天数(用于填充前置空白)
- const daysInPrevMonth = new Date(year, month, 0).getDate()
- const days: Array<{ day: number, isCurrentMonth: boolean, item?: DayItem, date: Date }> = []
- // 填充上月日期(灰色显示)
- for (let i = firstDayIndex - 1; i >= 0; i--) {
- const day = daysInPrevMonth - i
- days.push({
- day,
- isCurrentMonth: false,
- date: new Date(year, month - 1, day),
- })
- }
- // 填充当月日期
- for (let day = 1; day <= daysInMonth; day++) {
- // 查找对应的日期数据
- const dayItem = props.dayData.find(d => d.day === day)
- const date = new Date(year, month, day)
- // 判断是否选中
- const isSelected = props.modelValue
- && date.getFullYear() === props.modelValue.getFullYear()
- && date.getMonth() === props.modelValue.getMonth()
- && date.getDate() === props.modelValue.getDate()
- days.push({
- day,
- isCurrentMonth: true,
- item: dayItem ? { ...dayItem, selected: isSelected } : undefined,
- date,
- })
- }
- // 填充下月日期(补全到6行或5行)
- const remainingCells = 42 - days.length // 6行 x 7列 = 42
- for (let day = 1; day <= remainingCells; day++) {
- days.push({
- day,
- isCurrentMonth: false,
- date: new Date(year, month + 1, day),
- })
- }
- return days
- })
- // 切换到上月
- function prevMonth() {
- const newDate = new Date(currentDate.value)
- newDate.setMonth(newDate.getMonth() - 1)
- currentDate.value = newDate
- emit('monthChange', { year: newDate.getFullYear(), month: newDate.getMonth() + 1 })
- }
- // 切换到下月
- function nextMonth() {
- const newDate = new Date(currentDate.value)
- newDate.setMonth(newDate.getMonth() + 1)
- currentDate.value = newDate
- emit('monthChange', { year: newDate.getFullYear(), month: newDate.getMonth() + 1 })
- }
- // 选择日期
- function selectDay(dayInfo: typeof calendarDays.value[0]) {
- if (!dayInfo.isCurrentMonth)
- return
- const newDate = new Date(currentDate.value)
- newDate.setDate(dayInfo.day)
- emit('update:modelValue', newDate)
- emit('select', {
- date: newDate,
- day: dayInfo.day,
- item: dayInfo.item,
- })
- }
- // 监听外部modelValue变化
- watch(() => props.modelValue, (newVal) => {
- if (newVal) {
- currentDate.value = new Date(newVal)
- }
- })
- </script>
- <template>
- <view class="rounded-16rpx bg-#F6F6F6 p-20rpx" :class="customClass">
- <view class="text-32rpx font-semibold">
- {{ title }}
- </view>
- <!-- 年月切换 -->
- <view class="mt20rpx flex items-center justify-between">
- <view class="h60rpx w60rpx flex items-center justify-center" @click="prevMonth">
- <wd-icon name="arrow-left" size="20px" color="#999" />
- </view>
- <view class="rounded-24rpx bg-#F5F5F5 px32rpx py12rpx text-28rpx">
- {{ currentMonthText }}
- </view>
- <view class="h60rpx w60rpx flex items-center justify-center" @click="nextMonth">
- <wd-icon name="arrow-right" size="20px" color="#999" />
- </view>
- </view>
- <!-- 星期标题 -->
- <view v-if="showWeekHeader" class="mt20rpx flex items-center justify-between px12rpx">
- <view
- v-for="day in weekDays"
- :key="day"
- class="h60rpx w60rpx flex items-center justify-center text-28rpx"
- :class="day === '六' || day === '日' ? 'text-#52c41a' : 'text-#333'"
- >
- {{ day }}
- </view>
- </view>
- <!-- 日期网格 -->
- <view class="mt12rpx flex flex-wrap">
- <view
- v-for="(item, index) in calendarDays"
- :key="index"
- class="mb16rpx h90rpx w-14.28% flex flex-col items-center justify-center"
- @click="selectDay(item)"
- >
- <view
- class="h100rpx w76rpx flex flex-col items-center justify-center rounded-16rpx"
- :class="[
- item.item?.selected ? 'bg-#E2FF91' : '',
- item.item?.status === '售罄' || item.item?.disabled ? 'bg-#F5F5F5' : '',
- !item.isCurrentMonth ? 'opacity-30' : '',
- ]"
- >
- <text
- v-if="item.item?.status && item.isCurrentMonth"
- class="mt4rpx text-20rpx"
- :class="item.item.status === '充足' ? 'text-#52c41a' : 'text-#999'"
- >
- {{ item.item.status }}
- </text>
- <text
- class="text-28rpx"
- :class="[
- item.item?.selected ? 'text-#333 font-semibold' : 'text-#333',
- item.item?.status === '售罄' || item.item?.disabled ? 'text-#999' : '',
- !item.isCurrentMonth ? 'text-#999' : '',
- ]"
- >
- {{ item.day }}
- </text>
- <text
- v-if="item.item?.price && item.isCurrentMonth && item.item.status !== '售罄'"
- class="mt4rpx text-20rpx text-#FF4D3A"
- >
- ¥{{ item.item.price }}
- </text>
- </view>
- </view>
- </view>
- </view>
- </template>
- <style lang="scss" scoped></style>
|