7021f2fd创建于 2025年5月14日历史提交
<template>
  <view class="DateTime">
    <view class="inputArea" :class="{ clear: !!showText }" @click="handleClick">
      <wd-input
        :placeholder="getPlaceholder($attrs)"
        v-bind="$attrs"
        readonly
        v-model="showText"
      ></wd-input>
      <view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
    </view>
    <wd-popup v-if="popupShow" position="bottom" v-model="popupShow">
      <view class="content">
        <view class="operation">
          <view class="cancel text-gray-5" @click.stop="cancel">取消</view>
          <view class="confrim" @click.stop="confirm">确定</view>
        </view>
        <picker-view class="picker-view" :value="pickerValue" @change="handleChange">
          <!-- 年 -->
          <picker-view-column v-if="showYear">
            <view class="picker-item" v-for="(item, index) in years" :key="index">
              {{ item }}年
            </view>
          </picker-view-column>
          <!-- 月 -->
          <picker-view-column v-if="showMonth">
            <view class="picker-item" v-for="(item, index) in months" :key="index">
              {{ item }}月
            </view>
          </picker-view-column>
          <!-- 日 -->
          <picker-view-column v-if="showDay">
            <view class="picker-item" v-for="(item, index) in days" :key="index">{{ item }}日</view>
          </picker-view-column>
          <!-- 时 -->
          <picker-view-column v-if="showHour">
            <view class="picker-item" v-for="(item, index) in hours" :key="index">
              {{ item }}时
            </view>
          </picker-view-column>
          <!-- 分 -->
          <picker-view-column v-if="showMinute">
            <view class="picker-item" v-for="(item, index) in minutes" :key="index">
              {{ item }}分
            </view>
          </picker-view-column>
          <!-- 秒 -->
          <picker-view-column v-if="showSecond">
            <view class="picker-item" v-for="(item, index) in seconds" :key="index">
              {{ item }}秒
            </view>
          </picker-view-column>
        </picker-view>
      </view>
    </wd-popup>
  </view>
</template>

<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { useToast } from 'wot-design-uni'
import dayjs from 'dayjs'
import { getPlaceholder } from '@/common/uitls'

defineOptions({
  name: 'DateTime',
  options: {
    styleIsolation: 'shared',
  },
})

const props = defineProps({
  modelValue: {
    type: String,
    default: '',
  },
  // 显示格式:year/month/day/hour/minute/second
  format: {
    type: String,
    default: 'YYYY-MM-DD HH:mm:ss',
  },
  // 开始时间
  startTime: {
    type: String,
    default: '1970-01-01 00:00:00',
  },
  // 结束时间
  endTime: {
    type: String,
    default: '2100-12-31 23:59:59',
  },
  // 是否禁用
  disabled: {
    type: Boolean,
    default: false,
  },
})

const emit = defineEmits(['change', 'update:modelValue'])
const toast = useToast()

// 显示控制
const showYear = computed(() => props.format.includes('YYYY'))
const showMonth = computed(() => props.format.includes('MM'))
const showDay = computed(() => props.format.includes('DD'))
const showHour = computed(() => props.format.includes('HH'))
const showMinute = computed(() => props.format.includes('mm'))
const showSecond = computed(() => props.format.includes('ss'))

// 数据源
const years = ref<number[]>([])
const months = ref<number[]>([])
const days = ref<number[]>([])
const hours = ref<number[]>([])
const minutes = ref<number[]>([])
const seconds = ref<number[]>([])

// 当前选中的值
const pickerValue = ref<number[]>([])
const showText = computed(() => props.modelValue)
const popupShow = ref(false)

// 初始化数据
const initData = () => {
  const start = dayjs(props.startTime)
  const end = dayjs(props.endTime)

  // 初始化年份
  if (showYear.value) {
    years.value = Array.from({ length: end.year() - start.year() + 1 }, (_, i) => start.year() + i)
  }

  // 初始化月份
  if (showMonth.value) {
    months.value = Array.from({ length: 12 }, (_, i) => i + 1)
  }

  // 初始化日期
  if (showDay.value) {
    const currentYear = props.modelValue ? dayjs(props.modelValue).year() : start.year()
    const currentMonth = props.modelValue ? dayjs(props.modelValue).month() + 1 : 1
    const daysInMonth = dayjs(`${currentYear}-${currentMonth}`).daysInMonth()
    days.value = Array.from({ length: daysInMonth }, (_, i) => i + 1)
  }

  // 初始化小时
  if (showHour.value) {
    hours.value = Array.from({ length: 24 }, (_, i) => i)
  }

  // 初始化分钟
  if (showMinute.value) {
    minutes.value = Array.from({ length: 60 }, (_, i) => i)
  }

  // 初始化秒
  if (showSecond.value) {
    seconds.value = Array.from({ length: 60 }, (_, i) => i)
  }
}

// 更新日期
const updateDays = () => {
  if (!showYear.value || !showMonth.value) return

  const year = years.value[pickerValue.value[0] || 0]
  const month = months.value[pickerValue.value[1] || 0]
  const daysInMonth = dayjs(`${year}-${month}`).daysInMonth()
  days.value = Array.from({ length: daysInMonth }, (_, i) => i + 1)
}

// 初始化pickerValue
const initializePickerValue = () => {
  let newPickerValue = []
  if (
    [
      'YYYY-MM-DD HH:mm:ss',
      'YYYY-MM-DD HH:mm',
      'YYYY-MM-DD HH',
      'YYYY',
      'YYYY-MM',
      'YYYY-MM-DD',
    ].includes(props.format)
  ) {
    const dateTime = props.modelValue ? dayjs(props.modelValue) : dayjs()
    if (showYear.value) {
      const yearIndex = years.value.indexOf(dateTime.year())
      newPickerValue.push(yearIndex >= 0 ? yearIndex : 0)
    }

    if (showMonth.value) {
      const monthIndex = months.value.indexOf(dateTime.month() + 1)
      newPickerValue.push(monthIndex >= 0 ? monthIndex : 0)
    }

    if (showDay.value) {
      const dayIndex = days.value.indexOf(dateTime.date())
      newPickerValue.push(dayIndex >= 0 ? dayIndex : 0)
    }

    if (showHour.value) {
      newPickerValue.push(dateTime.hour())
    }

    if (showMinute.value) {
      newPickerValue.push(dateTime.minute())
    }
    if (showSecond.value) {
      newPickerValue.push(dateTime.second())
    }
  } else if (['HH:mm:ss', 'HH:mm', 'mm:ss', 'HH', 'mm', 'ss'].includes(props.format)) {
    const dateTime = props.modelValue
    if (dateTime) {
      newPickerValue = dateTime.split(':').map((item) => +item)
    } else {
      // 获取当前时间
      const now = dayjs()
      if (showHour.value) {
        newPickerValue.push(now.hour())
      }
      if (showMinute.value) {
        newPickerValue.push(now.minute())
      }
      if (showSecond.value) {
        newPickerValue.push(now.second())
      }
    }
  }
  pickerValue.value = newPickerValue
}

// 处理选择变化
const handleChange = (e: any) => {
  const values = e.detail.value
  pickerValue.value = values

  // 如果选择了年月,需要更新日期
  if (showYear.value && showMonth.value && showDay.value) {
    updateDays()
  }
}

// 点击确认
const confirm = () => {
  const values = pickerValue.value
  let selectedDate = dayjs()
  let index = 0

  // 只设置用户选择的时间部分,未选择的保持当前时间
  if (showYear.value && values[index] !== undefined) {
    selectedDate = selectedDate.year(years.value[values[index]])
    index++
  }
  if (showMonth.value && values[index] !== undefined) {
    selectedDate = selectedDate.month(months.value[values[index]] - 1)
    index++
  }
  if (showDay.value && values[index] !== undefined) {
    selectedDate = selectedDate.date(days.value[values[index]])
    index++
  }
  if (showHour.value && values[index] !== undefined) {
    selectedDate = selectedDate.hour(hours.value[values[index]])
    index++
  }
  if (showMinute.value && values[index] !== undefined) {
    selectedDate = selectedDate.minute(minutes.value[values[index]])
    index++
  }
  if (showSecond.value && values[index] !== undefined) {
    selectedDate = selectedDate.second(seconds.value[values[index]])
    index++
  }

  popupShow.value = false
  const output = selectedDate.format(props.format)
  emit('update:modelValue', output)
  emit('change', output)
}

// 点击取消
const cancel = () => {
  popupShow.value = false
}

// 清空选择
const handleClear = () => {
  emit('update:modelValue', '')
  emit('change', '')
}

// 点击输入框
const handleClick = () => {
  if (!props.disabled) {
    initData()
    initializePickerValue()
    popupShow.value = true
  }
}
</script>

<style lang="scss" scoped>
.DateTime {
  .picker-view {
    width: 100%;
    height: 400rpx;
  }
  .picker-item {
    line-height: 88rpx;
    text-align: center;
  }
  .operation {
    display: flex;
    justify-content: space-between;
    padding: 20rpx;
    border-bottom: 1rpx solid #eee;
    .cancel,
    .confrim {
      padding: 10rpx 30rpx;
    }
    .confrim {
      color: #2979ff;
    }
  }
  .inputArea {
    position: relative;
    .u-icon-close {
      position: absolute;
      right: 15px;
      top: calc(14px + 4px);
      color: #585858;
      font-size: 15px;
    }
    &.clear {
      :deep(.wd-input__body) {
        padding-right: 20px;
      }
    }
  }
}
</style>