小程序 - 日期选择器

效果图

 

实现步骤

1. 创建组件

wxml

<template name="datepicker">
	<view class="datepicker-bg" wx:if="{{showDatePicker}}" bindtap="closeDatePicker"></view>
	<input
		wx:if="{{showInput}}"
		class="datepicker-input"
		placeholder="{{placeholder}}"
		value="{{selectedValue || ''}}"
		type="text"
		bindinput="onInputDate"
		bindfocus="showDatepicker"/>
	<view wx:if="{{showDatePicker}}" class="datepicker-wrap flex box box-tb box-align-center">
		<view class="calendar pink-color box box-tb">
			<view class="top-handle fs28 box box-lr box-align-center box-pack-center">
				<view class="prev box box-rl" catchtap="handleCalendar" data-handle="prev">
					<view class="prev-handle box box-lr box-align-center box-pack-center">《</view>
				</view>
				<view class="date-area box box-lr box-align-center box-pack-center">{{curYear || "--"}} 年 {{curMonth || "--"}} 月</view>
				<view class="next box box-lr" catchtap="handleCalendar" data-handle="next">
					<view class="next-handle box box-lr box-align-center box-pack-center">》</view>
				</view>
			</view>
			<view wx:if="{{weeksCh}}" class="weeks box box-lr box-pack-center box-align-center">
				<view class="flex week fs28" wx:for="{{weeksCh}}" wx:key="{{index}}" data-idx="{{index}}">{{item}}</view>
			</view>
			<view class="days box box-lr box-wrap" bindtouchstart="datepickerTouchstart" bindtouchmove="datepickerTouchmove">
				<view wx:if="{{empytGrids}}" class="grid disable-day-color  box box-align-center box-pack-center"
          wx:for="{{empytGrids}}"
          wx:key="{{index}}"
          data-idx="{{index}}">
            <view class="day box box-align-center box-pack-center">{{item}}</view>
        </view>
				<view class="grid normal-day-color box box-align-center box-pack-center"
          wx:for="{{days}}"
          wx:key="{{index}}"
          data-idx="{{index}}"
          data-disable="{{item.disable}}"
          catchtap="tapDayItem">
					<view class="day border-radius {{item.choosed ? 'day-choosed-color pink-bg' : ''}} {{ item.disable ? 'disable-day-color disable-day-circle' : '' }} box box-align-center box-pack-center">{{item.day}}</view>
				</view>
				<view class="grid disable-day-color  box box-align-center box-pack-center"
          wx:for="{{lastEmptyGrids}}"
          wx:key="{{index}}"
          data-idx="{{index}}">
            <view class="day box box-align-center box-pack-center">{{item}}</view>
        </view>
			</view>
		</view>
	</view>
</template>

 

wxss

.box {
  display: flex;
}

.box-lr {
  flex-direction: row;
}

.box-rl {
  flex-direction: row-reverse;
}

.box-tb {
  flex-direction: column;
}

.box-pack-center {
  justify-content: center;
}

.box-align-center {
  align-items: center;
}

.box-wrap {
  flex-wrap: wrap;
}

.flex {
  flex-grow: 1;
}

.bg {
  background-image: linear-gradient(to bottom, #faefe7, #ffcbd7);
  overflow: hidden;
}

.pink-color {
  color: #ff629a;
}

.white-color {
  color: #fff;
}

.fs24 {
  font-size: 24rpx;
}

.fs28 {
  font-size: 28rpx;
}

.fs32 {
  font-size: 32rpx;
}

.fs36 {
  font-size: 36rpx;
}

.datepicker-bg {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

.datepicker-input {
  width: 300rpx;
  height: 50rpx;
  border: 1rpx solid #dadada;
  border-radius: 10rpx;
  padding: 10rpx;
  font-size: 28rpx;
}
/* stylelint-disable-next-line */
.datepicker-input::-webkit-input-placeholder {
  color: #dadada;
}

.datepicker-wrap {
  background-color: #fff;
  box-shadow: 0 0 10rpx 0 #dadada;
  position: relative;
}

.top-handle {
  height: 80rpx;
}

.prev {
  text-align: right;
  height: 80rpx;
}

.next {
  height: 80rpx;
}

.prev-handle {
  width: 80rpx;
  height: 100%;
}

.next-handle {
  width: 80rpx;
  height: 100%;
}

.date-area {
  width: 50%;
  height: 80rpx;
  text-align: center;
}

.weeks {
  height: 50rpx;
  line-height: 50rpx;
  opacity: 0.5;
}

.week {
  text-align: center;
}

.days {
  height: 500rpx;
}

.grid {
  width: 14.285714285714286%;
}

.day {
  width: 60rpx;
  height: 60rpx;
  font-size: 26rpx;
  font-weight: 200;
}

.normal-day-color {
  color: #88d2ac;
}

.day-choosed-color {
  color: #fff;
}

.disable-day-color {
  color: #ddd;
}

.disable-day-circle {
  background-color: #fbfdff;
}

.border-radius {
  border-radius: 50%;
  position: relative;
  left: 0;
  top: 0;
}

.pink-bg {
  background-color: #ff629a;
}

.purple-bg {
  background-color: #b8b8f1;
}

.right-triangle::after {
  content: '';
  display: block;
  width: 0;
  height: 0;
  border: 15rpx solid transparent;
  border-left-color: #ff629a;
  position: absolute;
  right: -22rpx;
  top: 18rpx;
}

.left-triangle::before {
  content: '';
  display: block;
  width: 0;
  height: 0;
  border: 15rpx solid transparent;
  border-right-color: #ff629a;
  position: absolute;
  left: -22rpx;
  top: 18rpx;
}

.tips {
  text-align: center;
  margin-top: 20rpx;
  margin-bottom: 20rpx;
}

.types {
  background-color: #ffedf4;
  height: 50rpx;
}

.types-desc {
  padding: 0 20rpx;
}

.type-name {
  margin-top: 50rpx;
  margin-bottom: 30rpx;
}

.type-desc {
  padding: 0 35rpx;
  line-height: 38rpx;
}

.explain {
  border-top: 1px solid #eee;
  width: 90%;
  margin: 20rpx 5% 20rpx 5%;
  padding: 20rpx 0;
}

.explain-title {
  font-weight: bold;
  margin-bottom: 15rpx;
}

.explain-item {
  padding: 8rpx 20rpx;
  color: #fff;
}

.left-border-radius {
  border-top-left-radius: 20rpx;
  border-bottom-left-radius: 20rpx;
}

.right-border-radius {
  border-top-right-radius: 20rpx;
  border-bottom-right-radius: 20rpx;
}

js

/**
 * 上滑
 * @param {object} e 事件对象
 * @returns {boolean} 布尔值
 */
export function isUpSlide(e) {
  const { startX, startY } = this.data.gesture;
  if (this.slideLock) {
    const t = e.touches[0];
    const deltaX = t.clientX - startX;
    const deltaY = t.clientY - startY;
    if (deltaY < -60 && deltaX < 20 && deltaX > -20) {
      this.slideLock = false;
      return true;
    } else {
      return false;
    }
  }
}
/**
 * 下滑
 * @param {object} e 事件对象
 * @returns {boolean} 布尔值
 */
export function isDownSlide(e) {
  const { startX, startY } = this.data.gesture;
  if (this.slideLock) {
    const t = e.touches[0];
    const deltaX = t.clientX - startX;
    const deltaY = t.clientY - startY;
    if (deltaY > 60 && deltaX < 20 && deltaX > -20) {
      this.slideLock = false;
      return true;
    } else {
      return false;
    }
  }
}
/**
 * 左滑
 * @param {object} e 事件对象
 * @returns {boolean} 布尔值
 */
export function isLeftSlide(e) {
  const { startX, startY } = this.data.gesture;
  if (this.slideLock) {
    const t = e.touches[0];
    const deltaX = t.clientX - startX;
    const deltaY = t.clientY - startY;
    if (deltaX < -60 && deltaY < 20 && deltaY > -20) {
      this.slideLock = false;
      return true;
    } else {
      return false;
    }
  }
}
/**
 * 右滑
 * @param {object} e 事件对象
 * @returns {boolean} 布尔值
 */
export function isRightSlide(e) {
  const { startX, startY } = this.data.gesture;
  if (this.slideLock) {
    const t = e.touches[0];
    const deltaX = t.clientX - startX;
    const deltaY = t.clientY - startY;

    if (deltaX > 60 && deltaY < 20 && deltaY > -20) {
      this.slideLock = false;
      return true;
    } else {
      return false;
    }
  }
}

const conf = {
  /**
   * 计算指定月份共多少天
   * @param {number} year 年份
   * @param {number} month  月份
   */
  getThisMonthDays(year, month) {
    return new Date(year, month, 0).getDate();
  },
  /**
   * 计算指定月份第一天星期几
   * @param {number} year 年份
   * @param {number} month  月份
   */
  getFirstDayOfWeek(year, month) {
    return new Date(Date.UTC(year, month - 1, 1)).getDay();
  },
  /**
   * 计算当前月份前后两月应占的格子
   * @param {number} year 年份
   * @param {number} month  月份
   */
  calculateEmptyGrids(year, month) {
    conf.calculatePrevMonthGrids.call(this, year, month);
    conf.calculateNextMonthGrids.call(this, year, month);
  },
  /**
   * 计算上月应占的格子
   * @param {number} year 年份
   * @param {number} month  月份
   */
  calculatePrevMonthGrids(year, month) {
    const prevMonthDays = conf.getThisMonthDays(year, month - 1);
    const firstDayOfWeek = conf.getFirstDayOfWeek(year, month);
    let empytGrids = [];
    if (firstDayOfWeek > 0) {
      const len = prevMonthDays - firstDayOfWeek;
      for (let i = prevMonthDays; i > len; i--) {
        empytGrids.push(i);
      }
      this.setData({
        'datepicker.empytGrids': empytGrids.reverse()
      });
    } else {
      this.setData({
        'datepicker.empytGrids': null
      });
    }
  },
  /**
   * 计算下月应占的格子
   * @param {number} year 年份
   * @param {number} month  月份
   */
  calculateNextMonthGrids(year, month) {
    const thisMonthDays = conf.getThisMonthDays(year, month);
    const lastDayWeek = new Date(`${year}-${month}-${thisMonthDays}`).getDay();
    let lastEmptyGrids = [];
    if (+lastDayWeek !== 6) {
      const len = 7 - (lastDayWeek + 1);
      for (let i = 1; i <= len; i++) {
        lastEmptyGrids.push(i);
      }
      this.setData({
        'datepicker.lastEmptyGrids': lastEmptyGrids
      });
    } else {
      this.setData({
        'datepicker.lastEmptyGrids': null
      });
    }
  },
  /**
   * 设置日历面板数据
   * @param {number} year 年份
   * @param {number} month  月份
   */
  calculateDays(year, month, curDate) {
    const { todayTimestamp } = this.data.datepicker;
    let days = [];
    let day;
    let selectMonth;
    let selectYear;
    const thisMonthDays = conf.getThisMonthDays(year, month);
    const selectedDay = this.data.datepicker.selectedDay;
    if (selectedDay && selectedDay.length) {
      day = selectedDay[0].day;
      selectMonth = selectedDay[0].month;
      selectYear = selectedDay[0].year;
    }
    for (let i = 1; i <= thisMonthDays; i++) {
      days.push({
        day: i,
        choosed: curDate
          ? i === curDate
          : year === selectYear && month === selectMonth && i === day,
        year,
        month
      });
    }
    days.map(item => {
      const timestamp = new Date(
        `${item.year}-${item.month}-${item.day}`
      ).getTime();
      if (this.config.disablePastDay && timestamp - todayTimestamp < 0) {
        item.disable = true;
      }
    });
    const tmp = {
      'datepicker.days': days
    };
    if (curDate) {
      tmp['datepicker.selectedDay'] = [
        {
          day: curDate,
          choosed: true,
          year,
          month
        }
      ];
    }
    this.setData(tmp);
  },
  /**
   * 跳转至今天
   */
  jumpToToday() {
    const date = new Date();
    const curYear = date.getFullYear();
    const curMonth = date.getMonth() + 1;
    const curDate = date.getDate();
    conf.renderCalendar.call(this, curYear, curMonth, curDate);
  },
  /**
   * 渲染日历
   * @param {number} year
   * @param {number} month
   * @param {number} day
   */
  renderCalendar(year, month, day) {
    const timestamp = new Date(`${year}-${month}-${day}`).getTime();
    this.setData({
      'datepicker.curYear': year,
      'datepicker.curMonth': month,
      'datepicker.todayTimestamp': timestamp
    });
    conf.calculateEmptyGrids.call(this, year, month);
    conf.calculateDays.call(this, year, month, day);
  },
  /**
   * 初始化日历选择器
   * @param {number} curYear
   * @param {number} curMonth
   * @param {number} curDate
   */
  init(curYear, curMonth, curDate) {
    const self = _getCurrentPage();
    const weeksCh = ['日', '一', '二', '三', '四', '五', '六'];
    self.setData({
      'datepicker.weeksCh': weeksCh,
      'datepicker.showDatePicker': true
    });
    if (!curYear && !curMonth && !curDate) return conf.jumpToToday.call(self);
    conf.renderCalendar.call(self, curYear, curMonth, curDate);
  },
  /**
   * 点击输入框调起日历选择器
   * @param {object} e  事件对象
   */
  showDatepicker(e) {
    const value = e.detail.value;
    if (value && typeof value === 'string') {
      const tmp = value.split('-');
      conf.init(+tmp[0], +tmp[1], +tmp[2]);
    } else {
      conf.init();
    }
  },
  /**
   * 当输入日期时
   * @param {object} e  事件对象
   */
  onInputDate(e) {
    const self = _getCurrentPage();
    this.inputTimer && clearTimeout(this.inputTimer);
    this.inputTimer = setTimeout(() => {
      const v = e.detail.value;
      const _v = (v && v.split('-')) || [];
      const RegExpYear = /^\d{4}$/;
      const RegExpMonth = /^(([0]?[1-9])|([1][0-2]))$/;
      const RegExpDay = /^(([0]?[1-9])|([1-2][0-9])|(3[0-1]))$/;
      if (_v && _v.length === 3) {
        if (
          RegExpYear.test(_v[0]) &&
          RegExpMonth.test(_v[1]) &&
          RegExpDay.test(_v[2])
        ) {
          conf.renderCalendar.call(self, +_v[0], +_v[1], +_v[2]);
        }
      }
    }, 500);
  },
  /**
   * 计算当前日历面板月份的前一月数据
   */
  choosePrevMonth() {
    const { curYear, curMonth } = this.data.datepicker;
    let newMonth = curMonth - 1;
    let newYear = curYear;
    if (newMonth < 1) {
      newYear = curYear - 1;
      newMonth = 12;
    }

    conf.calculateDays.call(this, newYear, newMonth);
    conf.calculateEmptyGrids.call(this, newYear, newMonth);

    this.setData({
      'datepicker.curYear': newYear,
      'datepicker.curMonth': newMonth
    });
  },
  /**
   * 计算当前日历面板月份的后一月数据
   */
  chooseNextMonth() {
    const { curYear, curMonth } = this.data.datepicker;
    let newMonth = curMonth + 1;
    let newYear = curYear;
    if (newMonth > 12) {
      newYear = curYear + 1;
      newMonth = 1;
    }
    conf.calculateDays.call(this, newYear, newMonth);
    conf.calculateEmptyGrids.call(this, newYear, newMonth);

    this.setData({
      'datepicker.curYear': newYear,
      'datepicker.curMonth': newMonth
    });
  },
  /**
   * 切换月份
   * @param {!object} e 事件对象
   */
  handleCalendar(e) {
    const handle = e.currentTarget.dataset.handle;
    if (handle === 'prev') {
      conf.choosePrevMonth.call(this);
    } else {
      conf.chooseNextMonth.call(this);
    }
  },
  /**
   * 选择具体日期
   * @param {!object} e  事件对象
   */
  tapDayItem(e) {
    const { idx, disable } = e.currentTarget.dataset;
    if (disable) return;
    const config = this.config;
    const { afterTapDay, onTapDay } = config;
    const { curYear, curMonth, days } = this.data.datepicker;
    const key = `datepicker.days[${idx}].choosed`;
    const selectedValue = `${curYear}-${curMonth}-${days[idx].day}`;
    if (this.config.type === 'timearea') {
      if (onTapDay && typeof onTapDay === 'function') {
        config.onTapDay(this.data.datepicker.days[idx], e);
        return;
      }
      this.setData({
        [key]: !days[idx].choosed
      });
    } else if (this.config.type === 'normal' && !days[idx].choosed) {
      const prev = days.filter(item => item.choosed)[0];
      const prevKey = prev && `datepicker.days[${prev.day - 1}].choosed`;
      if (onTapDay && typeof onTapDay === 'function') {
        config.onTapDay(days[idx], e);
        return;
      }
      const data = {
        [key]: true,
        'datepicker.selectedValue': selectedValue,
        'datepicker.selectedDay': [days[idx]]
      };
      if (prevKey) {
        data[prevKey] = false;
      }
      this.setData(data);
    }
    if (afterTapDay && typeof afterTapDay === 'function') {
      config.afterTapDay(days[idx]);
    }
  },
  /**
   * 关闭日历选择器
   */
  closeDatePicker() {
    this.setData({
      'datepicker.showDatePicker': false
    });
  },
  datepickerTouchstart(e) {
    const t = e.touches[0];
    const startX = t.clientX;
    const startY = t.clientY;
    this.slideLock = true; // 滑动事件加锁
    this.setData({
      'gesture.startX': startX,
      'gesture.startY': startY
    });
  },
  datepickerTouchmove(e) {
    if (isLeftSlide.call(this, e)) {
      conf.chooseNextMonth.call(this);
    }
    if (isRightSlide.call(this, e)) {
      conf.choosePrevMonth.call(this);
    }
  }
};

function _getCurrentPage() {
  const pages = getCurrentPages();
  const last = pages.length - 1;
  return pages[last];
}

/**
 * 跳转至今天
 */
export const jumpToToday = () => {
  const self = _getCurrentPage();
  conf.jumpToToday.call(self);
};

export default (config = {}) => {
  const self = _getCurrentPage();
  if (!config.type) config.type = 'normal';
  self.config = config;
  self.setData({
    datepicker: {
      showDatePicker: false,
      showInput: config.showInput === true || config.showInput === undefined,
      placeholder: config.placeholder || '请选择日期'
    }
  });
  self.datepickerTouchstart = conf.datepickerTouchstart.bind(self);
  self.datepickerTouchmove = conf.datepickerTouchmove.bind(self);
  self.showDatepicker = conf.showDatepicker.bind(self);
  self.onInputDate = conf.onInputDate.bind(self);
  self.closeDatePicker = conf.closeDatePicker.bind(self);
  self.tapDayItem = conf.tapDayItem.bind(self);
  self.handleCalendar = conf.handleCalendar.bind(self);
};

/**
 * 获取已选择的日期
 */
export const getSelectedDay = () => {
  const self = _getCurrentPage();
  return self.data.datepicker.selectedDay;
};

 

2. 在需要使用的地方引入

wxml

<import src="../../template/datepicker/index.wxml"/>

<view class="datepicker-box">
	<!-- <button type="primary" bindtap="showDatepicker"> 点击唤起日期选择器 </button> -->
	<template is="datepicker" data="{{...datepicker}}" />
</view>

wxss

@import '../../template/datepicker/index.wxss';

.datepicker-box {
  margin: 100rpx;
}

button {
  margin-top: 100rpx;
}

 

js

import initDatepicker, {
  getSelectedDay,
  jumpToToday
} from '../../template/datepicker/index';
const conf = {
  onShow: function() {
    initDatepicker({
      // disablePastDay: true, // 是否禁选过去日期
      // showInput: false, // 默认为 true
      // placeholder: '请选择日期', // input 输入框
      // type: 'normal', // [normal 普通单选模式(默认), timearea 时间段选择模式(待开发), multiSelect 多选模式(待完善)]
      /**
       * 点击日期后执行的事件
       * @param { object } currentSelect 当前点击的日期
       */
      afterTapDay: currentSelect => {
        console.log('当前点击的日期', currentSelect);
        console.log('getSelectedDay方法', getSelectedDay());
      }
      /**
       * 日期点击事件(此事件会完全接管点击事件)
       * @param { object } currentSelect 当前点击的日期
       * @param {object} event 日期点击事件对象
       */
      // onTapDay(currentSelect, event) {
      //   console.log(currentSelect);
      //   console.log(event);
      // },
    });
  },
  /**
   * 跳转至今天
   */
  jump() {
    jumpToToday();
  }
};
Page(conf);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Taro 是一种支持使用 React 进行多端开发的框架,可用于开发小程序、H5、RN 等多个平台的应用。在 Taro 中,可以使用第三方组件库来快速构建复杂的 UI 组件,其中包括日期选择器。 在 Taro 中,可以使用 taro-ui 组件库中的 DatePicker 组件来实现日期选择器。使用 DatePicker 组件需要先安装 taro-ui 组件库,具体安装方法可以参考官方文档。 安装完成后,在需要使用日期选择器的页面中引入 DatePicker 组件,例如: ```jsx import { DatePicker } from '@tarojs/taro-ui' // ... <DatePicker mode='date' value={this.state.date} onChange={this.handleDateChange} /> ``` 在上述代码中,使用了 DatePicker 组件,并设置了 mode、value 和 onChange 三个属性。其中,mode 属性用于设置日期选择器的模式,可以是 date、time、month 和 year 中的一个;value 属性用于设置当前选中的日期值;onChange 属性用于监听日期选择器的变化事件,并将选中的日期值传递到回调函数中。 在页面中还需要定义 handleDateChange 回调函数来处理日期选择器的变化事件,例如: ```jsx handleDateChange = e => { this.setState({ date: e.detail.value }) } ``` 在上述代码中,使用了 setState 方法来更新页面的状态,从而实现日期选择器的更新。 除了 DatePicker 组件,taro-ui 还提供了 Calendar、Picker 等其他组件,可以根据具体需求选择合适的组件来实现日期选择器

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值