uniapp 自写自用 时间选择器 组件,按需自取

组件代码

<script setup lang="ts">
import {computed, h, ref} from "vue";

let daysInMonthNormalYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let daysInMonthLeapYear = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

function isLeapYear(year: number) {
  return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}

let visible = ref<boolean>(false);

let props = withDefaults(defineProps<{
  startDate?: Date,
  endDate?: Date
  columnAmount?: number;
  title?: string;
}>(), {
  title: "日期时间选择器",
  columnAmount: 6,
  startDate() {
    let date = new Date();
    date.setTime(0);
    return date;
  },
  endDate() {
    let date = new Date();
    date.setFullYear(2100, 0, 1);
    date.setHours(0, 0, 0);
    return date;
  }
})

type ColumnValue = string | number;

let currentIndex = ref([0, 0, 0, 0, 0, 0]);

let emit = defineEmits(["submit"]);

let columns = computed(() => {
  // 获取开始与结束时间
  let startDate = props.startDate, endDate = props.endDate;

  // 年月日时分秒索引
  let [yearIndex, monthIndex, dateIndex, hourIndex, minuteIndex, secondIndex] = currentIndex.value;

  let years: ColumnValue[] = [];
  let months: ColumnValue[] = [];
  let dates: ColumnValue[] = [];
  let hours: ColumnValue[] = [];
  let minutes: ColumnValue[] = [];
  let seconds: ColumnValue[] = [];

  for (let i = startDate.getFullYear(); i <= endDate.getFullYear(); i++) {
    years.push(i.toString());
  }

  let yearLastIndex = years.length - 1;

  // 处理月份
  for (let i = 1; i <= 12; i++) {
    if (yearIndex === 0 && i < startDate.getMonth() + 1) continue;
    if (yearIndex === yearLastIndex && i > endDate.getMonth() + 1) continue;
    months.push(i >= 10 ? i.toString() : '0' + i);
  }

  let monthLastIndex = months.length - 1;

  // 平年闰年计算
  let leapYear = isLeapYear(Number(years[yearIndex]));
  let month = Number(months[monthIndex]);
  let monthDays = leapYear ? daysInMonthLeapYear[month - 1] : daysInMonthNormalYear[month - 1];

  // 处理天数
  for (let i = 1; i <= monthDays; i++) {
    if (yearIndex === 0 && monthIndex === 0 && i < startDate.getDate()) continue;
    if (yearIndex === yearLastIndex && monthIndex === monthLastIndex && i > endDate.getDate()) continue;
    dates.push(i >= 10 ? i.toString() : '0' + i)
  }

  let dateLastIndex = dates.length - 1;

  // 处理小时
  for (let i = 1; i <= 24; i++) {
    if (yearIndex === 0 && monthIndex === 0 && dateIndex === 0 && i < startDate.getHours()) continue;
    if (yearIndex === yearLastIndex && monthIndex === monthLastIndex && dateIndex === dateLastIndex && i > endDate.getHours()) continue;
    hours.push(i >= 10 ? i.toString() : '0' + i)
  }

  let hourLastIndex = hours.length - 1;

  // 处理分钟
  for (let i = 1; i <= 59; i++) {
    if (yearIndex === 0 && monthIndex === 0 && dateIndex === 0 && hourIndex === 0 && i < startDate.getMinutes()) continue;
    if (yearIndex === yearLastIndex && monthIndex === monthLastIndex && dateIndex === dateLastIndex && hourIndex === hourLastIndex && i > endDate.getMinutes()) continue;
    minutes.push(i >= 10 ? i.toString() : '0' + i)
  }

  let minuteLastIndex = minutes.length - 1;

  // 处理秒
  for (let i = 1; i <= 59; i++) {
    if (yearIndex === 0 && monthIndex === 0 && dateIndex === 0 && hourIndex === 0 && minuteIndex == 0 && i < startDate.getSeconds()) continue;
    if (yearIndex === yearLastIndex && monthIndex === monthLastIndex && dateIndex === dateLastIndex && hourIndex === hourLastIndex && minuteIndex === minuteLastIndex && i > endDate.getSeconds()) continue;
    seconds.push(i >= 10 ? i.toString() : '0' + i)
  }


  let arr = [years, months, dates, hours, minutes, seconds];

  return arr.slice(0, props.columnAmount);
})

function onChange(e: any) {
  let newValue = [...e.detail.value];
  let oldValue = [...currentIndex.value];

  let updateValue = new Array(props.columnAmount).fill(0);

  for (let i = 0; i < newValue.length; i++) {
    if (newValue[i] !== oldValue[i]) {
      updateValue.splice(0, i, ...newValue.slice(0, i + 1));
      break;
    }
  }

  currentIndex.value = updateValue;
}

function show() {
  visible.value = true;
}

function hide() {
  visible.value = false;
}

function onSubmit() {
  let [years, months, dates, hours, minutes, seconds] = columns.value;
  let [yearIndex, monthIndex, dateIndex, hourIndex, minuteIndex, secondIndex] = currentIndex.value;

  let year = years ? Number(years[yearIndex]) : 0;
  let month = months ? Number(months[monthIndex]) : 0;
  let date = dates ? Number(dates[dateIndex]) : 0;
  let hour = hours ? Number(hours[hourIndex]) : 0;
  let minute = minutes ? Number(minutes[minuteIndex]) : 0;
  let second = seconds ? Number(seconds[secondIndex]) : 0;

  let result = new Date();
  result.setFullYear(year, month - 1, date);
  result.setHours(hour, minute, second);

  emit("submit", result);

  hide();
}

defineExpose({show, hide})
</script>

<template>
  <view class="ui-popup" v-if="visible">
    <view class="ui-popup-mask" @touchmove.stop.prevent="()=>{}" @click="hide"></view>
    <view class="ui-popup-layer">
      <view class="ui-popup-layer-header">
        <view class="ui-popup-layer-header-cancel" @click="hide">取消</view>
        <view class="ui-popup-layer-header-label">{{ props.title }}</view>
        <view class="ui-popup-layer-header-confirm" @click="onSubmit">确认</view>
      </view>
      <view class="ui-popup-layer-main">
        <picker-view style="height: 440rpx" :value="currentIndex" indicator-style="height: 88rpx;" @change="onChange">
          <picker-view-column v-for="(column,c) in columns" :key="c">
            <view class="column-row" v-for="(row,r) in column" :key="r">{{ row }}</view>
          </picker-view-column>
        </picker-view>
      </view>
    </view>
  </view>
  <view @click="show">
    <slot></slot>
  </view>
</template>

<style scoped lang="scss">
.column-row {
  height: 60rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 30rpx;
  color: #333333;
}
</style>

样式部分


.ui-popup {
  .ui-popup-mask {
    position: fixed;
    z-index: 99;
    background-color: rgba(0, 0, 0, 0.5);
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    animation: popupMaskAnimate .2s linear;
  }

  .ui-popup-layer {
    position: fixed;
    z-index: 100;
    left: 0;
    right: 0;
    bottom: 0;
    background: #FFFFFF;
    border-radius: 24rpx 24rpx 0 0;
    padding-bottom: env(safe-area-inset-bottom);
    animation: popupLayerAnimate .2s linear;

    .ui-popup-layer-header {
      height: 98rpx;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 30rpx;

      .ui-popup-layer-header-cancel,
      .ui-popup-layer-header-confirm {
        flex-shrink: 0;
        font-size: 28rpx;
      }

      .ui-popup-layer-header-confirm {
        color: var(--theme-primary);
      }

      .ui-popup-layer-header-label {
        flex: 1;
        text-align: center;
        font-size: 36rpx;
        font-weight: bold;
      }

    }

    .ui-popup-layer-main {
      display: block;
    }
  }
}