DatePicker.vue 5.8 KB
<template>
  <div>
    <!-- 标签 -->
    <div v-if="label" class="text-sm text-gray-600 mb-2 flex items-center">
      <span v-if="required" class="text-red-500 mr-1">*</span>
      <span>{{ label }}</span>
    </div>

    <!-- 触发区域 -->
    <div
      class="flex justify-between items-center border border-gray-200 rounded-lg p-3 bg-gray-50"
      @tap="openDatePicker"
    >
      <span :class="displayValue ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
        {{ displayValue || placeholder }}
      </span>
      <IconFont name="right" size="14" color="#9CA3AF" />
    </div>

    <!-- DatePicker 弹窗 -->
    <nut-popup position="bottom" v-model:visible="showDatePicker" :close-on-click-overlay="false">
      <nut-date-picker
        v-model="currentDate"
        :min-date="minDate"
        :max-date="maxDate"
        :is-show-chinese="true"
        @confirm="onConfirm"
        @cancel="onCancel"
      >
      </nut-date-picker>
    </nut-popup>
  </div>
</template>

<script setup>
/**
 * 日期选择器组件
 *
 * @description 使用 NutUI DatePicker + Popup 实现日期选择
 *              - 支持年龄范围限制(minAge, maxAge)
 *              - 格式:YYYY-MM-DD
 *              - 可触发自动计算年龄
 * @author Claude Code
 * @example
 * <DatePicker
 *   v-model="birthday"
 *   label="出生年月日"
 *   placeholder="请选择日期"
 *   :min-age="0"
 *   :max-age="120"
 *   @change="onBirthdayChange"
 * />
 */
import { ref, computed, watch, inject } from 'vue'
import IconFont from '@/components/IconFont.vue'

// 注入父组件提供的弹窗控制函数
const popupControl = inject('popupControl', null)

/**
 * 组件属性
 */
const props = defineProps({
  /**
   * 标签文本
   * @type {string}
   */
  label: {
    type: String,
    default: ''
  },

  /**
   * 是否必填
   * @type {boolean}
   */
  required: {
    type: Boolean,
    default: false
  },

  /**
   * 占位符文本
   * @type {string}
   */
  placeholder: {
    type: String,
    default: '请选择日期'
  },

  /**
   * 绑定的值(格式:YYYY-MM-DD)
   * @type {string}
   */
  modelValue: {
    type: String,
    default: ''
  },

  /**
   * 最小年龄(用于计算最大出生日期)
   * @type {number}
   * @default 0
   */
  minAge: {
    type: Number,
    default: 0
  },

  /**
   * 最大年龄(用于计算最小出生日期)
   * @type {number}
   * @default 120
   */
  maxAge: {
    type: Number,
    default: 120 }
})

/**
 * 组件事件
 */
const emit = defineEmits([
  /**
   * 更新值事件
   * @event update:modelValue
   * @param {string} value - 选中的日期(格式:YYYY-MM-DD)
   */
  'update:modelValue',

  /**
   * 值变化事件(可用于触发自动计算年龄)
   * @event change
   * @param {string} value - 选中的日期(格式:YYYY-MM-DD)
   */
  'change',
  /**
   * 弹窗打开事件
   * @event open
   */
  'open',
  /**
   * 弹窗关闭事件
   * @event close
   */
  'close'
])

/**
 * 控制 DatePicker 显示
 */
const showDatePicker = ref(false)

/**
 * 当前选中的日期(Date 对象)
 * 用于绑定给 nut-date-picker
 */
const currentDate = ref(new Date())

/**
 * 打开日期选择器
 * @description 打开时将传入的 modelValue 转换为 Date 对象
 */
const openDatePicker = () => {
  // 调用父组件提供的 open 函数
  if (popupControl && popupControl.open) {
    popupControl.open()
  }

  if (props.modelValue) {
    // 兼容 iOS 的日期格式 (YYYY/MM/DD)
    const dateStr = props.modelValue.replace(/-/g, '/')
    const date = new Date(dateStr)
    if (!Number.isNaN(date.getTime())) {
      currentDate.value = date
    }
  } else {
    // 如果没有值,默认选中最小日期(通常是18岁或0岁对应的时间)
    // 或者默认选中当前时间,视业务需求而定。这里默认选中当前时间。
    currentDate.value = new Date()
  }
  showDatePicker.value = true
}

/**
 * 计算最小可选日期(基于最大年龄)
 * @description maxAge 岁对应的出生日期
 * @example
 * // maxAge = 75, 当前日期 = 2026-02-06
 * // minDate() // 返回: 1951-02-06
 */
const minDate = computed(() => {
  const date = new Date()
  date.setFullYear(date.getFullYear() - props.maxAge)
  return date
})

/**
 * 计算最大可选日期(基于最小年龄)
 * @description minAge 岁对应的出生日期
 * @example
 * // minAge = 0, 当前日期 = 2026-02-06
 * // maxDate() // 返回: 2026-02-06
 */
const maxDate = computed(() => {
  const date = new Date()
  date.setFullYear(date.getFullYear() - props.minAge)
  return date
})

/**
 * 显示的值
 */
const displayValue = computed(() => {
  return props.modelValue || ''
})

/**
 * 确认选择
 * @param {Object} { selectedValue } - DatePicker 返回的日期对象
 *
 * @example
 * // 用户选择 2020-01-01
 * onConfirm({ selectedValue: ['2020', '01', '01'] })
 */
const onConfirm = ({ selectedValue }) => {
  // NutUI DatePicker confirm 事件返回 { selectedValue: [year, month, day], selectedOptions: [...] }
  // 或者直接返回 Date 对象,取决于版本。
  // 安全起见,我们查看 currentDate.value,它会被 v-model 更新

  const date = currentDate.value
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')

  const formattedDate = `${year}-${month}-${day}`
  emit('update:modelValue', formattedDate)
  emit('change', formattedDate)

  // 调用父组件提供的 close 函数
  if (popupControl && popupControl.close) {
    popupControl.close()
  }

  showDatePicker.value = false
}

/**
 * 取消选择
 */
const onCancel = () => {
  // 调用父组件提供的 close 函数
  if (popupControl && popupControl.close) {
    popupControl.close()
  }

  showDatePicker.value = false
}
</script>

<style lang="less">
/* 组件样式 */
</style>