实现一个 时间选择器 组件

实现一个时间选择器组件

场景

第一层 筛选是 日 周 月 年。
第二层 筛选是 上一天/周/月/年 以及 下一天/周/月/年
第三层 筛选是 点击第一层筛选、点击中间的时间需要符合第一层筛选的时间类型
在这里插入图片描述

技术栈(思路都是一样的)
  1. uniapp + vue3 + nutui
  2. uniapp + vue3 + uview plus
实现
  1. 第一层切换 日 周 月 年

思路:-自己封装html元素,可以更好定义样式和处理逻辑

//我这里随便写的
<view class="tab-item"  v-for="(tab, i) in tabs" :class='selectIndex == i?"selectClass":""' :key="i" @tap.stop="tabClick(i,tab)">{{tab}}</view>

var tabs = ['日','周','月','年' ]
var selectIndex = 0 //默认选中

tabClick(index,tab){
selectIndex = index
}

.selectClass{
font-size:50rpx;
color:white;
}
  1. 实现第二层和第三层筛选,(我这里单独将这俩层封装成了组件,所有第1点的实现之后需要通过组件传值给到这里,用的组件库是nutui)
// 因为nutui的周选择有现成的日历组件,不用另外写逻辑,所以我这里就将他们区分
<template>
  <view class="time-picker_wrap">
    <!---->
    <view class="left_btn" @tap="preTime"></view>
    <!---->
    <view class="timer_btn" @tap="handleTap">{{ timer }}</view>
    <!---->
    <view class="right_btn" @tap="nextTime"></view>
    <!-- 日、月选择 -->
    <nut-popup v-model:visible="show" position="bottom" safe-area-inset-bottom>
      <nut-date-picker v-model="pickerTime" is-show-chinese @confirm="onConfirm" :type="type" @cancel="show = false" >
      </nut-date-picker>
    </nut-popup>
    <!-- 周选择 -->
    <nut-calendar v-model:visible="isVisible" :default-value="pickerTime" type="week" :start-date="`2000-01-01`" :end-date="endTime" @close="closeSwitch()" @choose="setChooseValue">
    </nut-calendar>
    <!---->
    <nut-popup v-model:visible="isPickerVisible" position="bottom" safe-area-inset-bottom>
      <nut-picker :columns="columns" @confirm="onYearConfirm"></nut-picker>
    </nut-popup>
  </view>
</template>

<script setup name='time-picker'>
import { ref, reactive, onMounted, defineProps, watch, defineEmits } from 'vue'
//值
const timer = ref('')
//储存日、月选择的值,用来进行转换
const pickerTime = ref('')
//控制日、月选择器的显示隐藏
const show = ref(false)
//控制周日历选择器的显示隐藏
const isVisible = ref(false)
//控制年选择器的显示隐藏
const isPickerVisible = ref(false)
//默认显示类型
const type = ref('week')
//周选择器最大可选时间
const endTime = ref('')
//年选择器的默认值
const columns = ref([])
//父组件传值
const props = defineProps({
  selectTab: String
})

onMounted(() => {
  if (type.value == 'year-month') {
    timer.value = formatMonth(new Date())
    pickerTime.value = timer.value
  } else if (type.value == 'date') {
    timer.value = getNowTimer()
    pickerTime.value = timer.value
  } else if (type.value == 'week') {
    timer.value = formatWeek(new Date())
    pickerTime.value = timer.value.split(' - ')
    endTime.value = pickerTime.value[1]
  } else {
    const currentYear = new Date().getFullYear() // 获取当前年份
    const startYear = 2000 // 开始年份
    timer.value = currentYear
    // 生成年份数组
    columns.value = Array.from({ length: currentYear - startYear + 1 }, (v, i) => ({
      text: startYear + i, value: startYear + i
    }))
  }
})

watch(() => props.selectTab,
  (newValue) => {
    if (newValue == 'month') {
      type.value = 'year-month'
      timer.value = formatMonth(new Date())
      pickerTime.value = timer.value
    } else if (newValue == 'day') {
      type.value = 'date'
      timer.value = getNowTimer()
      pickerTime.value = timer.value
    } else if (newValue == 'week') {
      type.value = 'week'
      timer.value = formatWeek(new Date())
      pickerTime.value = timer.value.split(' - ')
      endTime.value = pickerTime.value[1]
    } else {
      type.value = 'year'
      const currentYear = new Date().getFullYear() // 获取当前年份
      const startYear = 2000 // 开始年份
      timer.value = currentYear
      // 生成年份数组
      columns.value = Array.from({ length: currentYear - startYear + 1 }, (v, i) => ({
        text: startYear + i, value: startYear + i
      }))
    }
  },
  { immediate: true, deep: true }
)
const emits = defineEmits(['changeTime'])
watch(() => timer.value, (newValue) => {
  if (type.value == 'date') {
    const obj = {
      startDate: newValue,
      endDate: newValue
    }
    emits('changeTime', obj)
  } else if (type.value == 'week') {
    const [startDate, endDate] = newValue.split(' - ')
    emits('changeTime', { startDate, endDate })
  } else if (type.value == 'year-month') {
    emits('changeTime', getStartAndEndOfMonth(newValue))
  } else {
    emits('changeTime', getStartAndEndOfYear(newValue))
  }

})
const getStartAndEndOfMonth = (dateString) => {
  // 拆分年份和月份
  const [year, month] = dateString.split('-').map(Number) // 将字符串转换为数字
  // 获取该月份的开始日期
  const startDate = new Date(year, month - 1, 1) // 1号
  // 获取下一个月的 0 日,即上一个月的最后一天
  const endDate = new Date(year, month, 0)
  return {
    startDate: formatDate(startDate), // 格式化为 YYYY-MM-DD
    endDate: formatDate(endDate),     // 格式化为 YYYY-MM-DD
  }
}
const getStartAndEndOfYear = (year) => {
  const startDate = new Date(year, 0, 1) // 1月1日
  const endDate = new Date(year + 1, 0, 0) // 下一个年的 0 日,即上一个年的最后一天
  return {
    startDate: formatDate(startDate), // 格式化为 YYYY-MM-DD
    endDate: formatDate(endDate), // 格式化为 YYYY-MM-DD
  }
}

// 处理点击事件
const handleTap = () => {
  if (type.value == 'week') {
    isVisible.value = true
  } else if (type.value == 'year') {
    isPickerVisible.value = true
  } else {
    show.value = true
  }
}
// 获取当前日期
const getNowTimer = () => {
  const date = new Date()
  return formatDate(date)
}

// 获取当前月份
const formatMonth = (date) => {
  let year = date.getFullYear()
  let month = String(date.getMonth() + 1).padStart(2, '0')
  return `${year}-${month}` // 格式化为 YYYY-MM
}

// 格式化日期为 YYYY-MM-DD
const formatDate = (date) => {
  let year = date.getFullYear()
  let month = String(date.getMonth() + 1).padStart(2, '0')
  let day = String(date.getDate()).padStart(2, '0')

  return `${year}-${month}-${day}` // 格式化为 YYYY-MM-DD
}
// 格式化为当前周的起始日期(周日为一周的开始)
const formatWeek = (date) => {
  const startOfWeek = new Date(date)
  const day = startOfWeek.getDay() // 获取当前日期是周几,周日是 0
  startOfWeek.setDate(startOfWeek.getDate() - day) // 周日作为开始
  const endOfWeek = new Date(startOfWeek)
  endOfWeek.setDate(endOfWeek.getDate() + 6) // 结束日期为下周六
  return formatDate(startOfWeek) + ' - ' + formatDate(endOfWeek) // 返回格式化的日期范围
}

// 获取前一个月或前一天/前一周的日期
const preTime = () => {
  const minDate = new Date('2000-01-01') // 设置最小日期为2000年1月1日

  if (type.value === 'year-month') {
    const currentMonth = new Date(timer.value + '-01')
    currentMonth.setMonth(currentMonth.getMonth() - 1)

    // 判断是否小于最小日期
    if (currentMonth >= minDate) {
      timer.value = formatMonth(currentMonth)
      pickerTime.value = timer.value
    }
  } else if (type.value === 'week') {
    const currentWeekStart = new Date(timer.value.split(' - ')[0])
    currentWeekStart.setDate(currentWeekStart.getDate() - 7)

    // 判断是否小于最小日期
    if (currentWeekStart >= minDate) {
      timer.value = formatWeek(currentWeekStart)
      pickerTime.value = timer.value.split(' - ')
      endTime.value = pickerTime.value[1]
    }
  } else if (type.value === 'year') {
    if (timer.value > 2000) {
      timer.value = timer.value - 1
    }
  } else {
    const currentDay = new Date(timer.value)
    currentDay.setDate(currentDay.getDate() - 1)

    // 判断是否小于最小日期
    if (currentDay >= minDate) {
      timer.value = formatDate(currentDay)
      pickerTime.value = timer.value
    }
  }
}
// 获取后一个月或后一天/后一周的日期
const nextTime = () => {
  const currentDate = new Date() // 获取当前日期
  if (type.value === 'year-month') {
    const currentMonth = new Date(timer.value + '-01')
    currentMonth.setMonth(currentMonth.getMonth() + 1)
    // 判断是否超过当前日期
    if (currentMonth <= currentDate) {
      timer.value = formatMonth(currentMonth)
      pickerTime.value = timer.value
    }
  } else if (type.value === 'week') {
    const currentWeekStart = new Date(timer.value.split(' - ')[0])
    currentWeekStart.setDate(currentWeekStart.getDate() + 7)
    // 判断是否超过当前日期
    if (currentWeekStart <= currentDate) {
      timer.value = formatWeek(currentWeekStart)
      pickerTime.value = timer.value.split(' - ')
      endTime.value = pickerTime.value[1]
    }
  } else if (type.value === 'year') {
    const currentYear = new Date().getFullYear()
    if (timer.value < currentYear) {
      timer.value = timer.value + 1
    }
  } else {
    const currentDay = new Date(timer.value)
    currentDay.setDate(currentDay.getDate() + 1)
    // 判断是否超过当前日期
    if (currentDay <= currentDate) {
      timer.value = formatDate(currentDay)
      pickerTime.value = timer.value
    }
  }
}

const onConfirm = ({ date, selectedValue, selectedOptions }) => {
  if (type.value === 'year-month') {
    show.value = false
    timer.value = formatMonth(new Date(selectedValue)) // 确保 selectedValue 是有效的日期字符串
  } else if (type.value === 'date') {
    show.value = false
    timer.value = formatDate(new Date(selectedValue))
  }
  pickerTime.value = timer.value // 更新 pickerTime
}
const onYearConfirm = ({ selectedValue, selectedOptions }) => {
  timer.value = selectedValue[0] // 更新选中的年份
  isPickerVisible.value = false // 关闭选择器
}
const closeSwitch = param => {
  isVisible.value = false
}
const setChooseValue = param => {
  let { weekDate } = param
  pickerTime.value = [weekDate[0].date[3], weekDate[1].date[3]]
  timer.value = pickerTime.value.join(' - ')
}
</script>
<style lang="scss" scoped>
.nut-cell__value {
  flex: initial;
}
.time-picker_wrap {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  //   padding: 24rpx;
  display: flex;
  justify-content: space-between; /* Center the elements */
  align-items: center; /* Align items vertically */
}
.left_btn,
.right_btn,
.timer_btn {
  cursor: pointer;
  padding: 10rpx; /* Add some padding for a better touch target */
}
.timer_btn {
  font-weight: bold; /* Make the time display bold */
}
</style>
使用
//引入组件
//第一层组件已全局注册,不需要局部引入。也可以直接写在这个timePicker文件里面
import timePicker from './time-picker.vue'

//第一层筛选tab栏
<tabs-top :tabs='tabsArray' nameKey="label" v-model:value="activeIndex" @change="changeTab" />
//时间选择器
<time-picker :selectTab="selectTab" @changeTime="changeTime" class="piker"></time-picker>

//tab栏数据
const tabsArray = [
  {
    label: "日",
    value: "day",
  },
  {
    label: "周",
    value: "week",
  },
  {
    label: "月",
    value: "month",
  },
  {
    label: "年",
    value: "year",
  },
]
//默认选中
const activeIndex = ref(0)
const selectTab = ref('day')
//子传父
const changeTab = ({ value }) => {
//父传子
  selectTab.value = value
}
//时间选择器 返回值格式都是数组 ['2024-08-24','2024-08-25']
const changeTime = (value) => {
  console.log(value)
  //日  ['2024-08-24','2024-08-24']
  //周  ['2024-08-19','2024-08-25']
  //月  ['2024-08-01','2024-08-31']
  //年 ['2024-01-01','2024-12-31']
  //之后拿到值可以做一步操作 ~~~~
}

ui可以自己改,逻辑直接套用就好~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值