简介:
Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发 微信 / 京东 / 百度 / 支付宝 / 字节跳动 / QQ 小程序 / H5 等应用。现如今市面上端的形态多种多样,Web、React Native、微信小程序等各种端大行其道,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。
前景:
我司某项目某页面需要一个日期时间的五列选择器,我在taro的组件库之picker中找了又找,只发现了mode=date的日期选择器和mode=time的时间选择器,如下:
由于Taro是基于微信小程序仿照的,截图来源如微信小程序的官方demo
需求:
组件库picker功能很完善和强大,日期和时间的都有,然后项目中要求的选择器如下
问题:
what???需要既可以选日期又可以选时间的picker选择器,但目前picker组件只能要么选日期要么选时间,想省事儿的我当时的第一想法就是能不能让设计把时间选择器拆开为两个,这样就直接可以套用原有的组件了。但是我仔细看了下设计,同页面有活动时间和截止时间两个地方需要选择,如果拆开的话,可能样式不是那么美观。再加上技术的角度不能遇到点小困难就换设计(虽然大多数都换不了),下定决心的我决心自己写一个
实现:
仔细看了下组件库的picker,mode=date,mode=time对我是没啥用了,mode=multiSelector自定义的多列选择器,仔细看了下小程序的picker的demo,只需要控制range的数据为[[year],[month],[day],[hour],[minute]],picker组件即可展示出五列。组件库中还有个rangeKey属性,由于页面中展示的选择器一般都是xxx年xx月,但是提交给服务端的一般都是数字,所以该组件我采用的是Object Array结构,[{name:‘1997年’,id:1997}],展示的时候用:rangeKey="'name'",即可展示name的字段,提交数据的时候用id字段。
这里有两种思路:
一、控制数据都为有效数据,默认展示的是起止时间的年月日时分,只需要注意滑动到起始年月日时分和截止年月日时分的时候数据需要通过计算得到然后展示,该逻辑如我另一篇文章:juejin.cn/post/693341…
二、数据年份固定为起止时间的前后xxx年,通过索引来控制有效数据的展示,如果滑到无效数据,则回到(默认,起始,终止)时间的一种,根据滑到数据重置索引来控制选择器的数据都为有效数据
该文章介绍的是第二种思路
具体过程:
要实现第二种能思路的选择器,首先需要准备[year],[month],[day],[hour],[minute]的数据,这块就比第一种思路要简单的多。
**第一步:**获取二维数组activityArray([[year],[month],[day],[hour],[minute]])
[year]:只需要根据起止时间的年份,起始年份-x年,截止年份+x年(项目中x为20),得出一个人数组
[month]:月份固定为1到12月的数组
[day]:日固定为1-31天的数组
[hour]:时固定为0-24的数组
[minute]:固定为0-60的数组
**第二步:**timeIndex(【0,0,0,0,0】的一维数组,分别代表activityArray数组每列的索引)
根据默认时间,计算默认索引defaultIndex,首先取出默认时间的年月日时分,然后拿默认年份去activityArray的year数组中找到它的索引,以此类推,得到月,日,时,分的索引。
根据这个思路依次可以得到起始的索引数组startIndex,截止的索引数组endIndex。
**第三步:**滑动时,如果滑动到无效数据时,重置索引timeIndex
通过columnChange方法监听滑动的行为,当滑动到该时间<起始时间,则重置索引为startIndex。相反该时间>截止时间,则重置索引为endIndex。
代码展示:
**第一步:**dateTimePicker.js
首选需要一个用来获取[[year],[month],[day],[hour],[minute]]的数据的方法,由于日期是需要每次都重新计算,又避免全局污染,这里我采用了闭包函数,整体代码如下:
var getDaysInOneMonth = function (year, month) { let _month = parseInt(month, 10); let d = new Date(year, _month, 0); return d.getDate();}var dateDate = function (date) { let year = date.getFullYear(); let month = date.getMonth() + 1; let day = date.getDate(); let hours = date.getHours(); let minutes = date.getMinutes(); return { year, month, day, hours, minutes }}var dateTimePicker = function (startyear, endyear) { // 获取date time 年份,月份,天数,小时,分钟推后30分 const years = []; const months = []; const hours = []; const minutes = []; for (let i = startyear - 20; i <= endyear + 20; i++) { years.push({ name: i + '年', id: i }); } //获取月份 for (let i = 1; i <= 12; i++) { if (i < 10) { i = "0" + i; } months.push({ name: i + '月', id: i }); } //获取小时 for (let i = 0; i < 24; i++) { if (i < 10) { i = "0" + i; } hours.push({ name: i + '时', id: i }); } //获取分钟 for (let i = 0; i < 60; i++) { if (i < 10) { i = "0" + i; } minutes.push({ name: i + '分', id: i }); } return function (_year, _month) { const days = []; _year = parseInt(_year); _month = parseInt(_month); //获取日期 for (let i = 1; i <= getDaysInOneMonth(_year, _month); i++) { if (i < 10) { i = "0" + i; } days.push({ name: i + '日', id: i }); } return [years, months, days, hours, minutes]; }}export { dateTimePicker, getDaysInOneMonth, dateDate}
**第二步:**picker.vue组件
需要注意的就是当选择月份的时候需要计算下当月的天数,然后就是滑动到无效数组的时候,重置索引timeIndex
<template> <picker mode="multiSelector" :range-key="'name'" :value="timeIndex" :range="activityArray" :disabled="disabled" @change="bindMultiPickerChange" @columnChange="bindMultiPickerColumnChange" > <slot /> </picker></template><script>import { dateTimePicker, dateDate } from "./dateTimePicker.js";export default { props: { startTime: { type: [Object, Date], default: new Date(), }, endTime: { type: [Object, Date], default: new Date(), }, defaultTime: { type: [Object, Date], default: new Date(), }, disabled: { type: Boolean, default: false, }, }, data() { return { timeIndex: [0, 0, 0, 0, 0], activityArray: [], year: 0, month: 1, day: 1, hour: 0, minute: 0, datePicker: "", defaultIndex: [0, 0, 0, 0, 0], startIndex: [0, 0, 0, 0, 0], endIndex: [0, 0, 0, 0, 0], }; }, computed: { timeDate() { const { startTime, endTime } = this; return { startTime, endTime }; }, }, watch: { timeDate() { this.initData(); }, }, created() { this.initData(); }, methods: { initData() { let startTime = this.startTime; let endTime = this.endTime; this.datePicker = dateTimePicker( startTime.getFullYear(), endTime.getFullYear() ); this.setDateData(this.defaultTime); this.getKeyIndex(this.startTime, "startIndex"); // 截止时间索引 this.getKeyIndex(this.endTime, "endIndex"); // 默认索引 this.getKeyIndex(this.defaultTime, "defaultIndex"); this.timeIndex = this.defaultIndex; // 初始时间 this.initTime(); }, getKeyIndex(time, key) { let Arr = dateDate(time); let _index = this.getIndex(Arr); this[key] = _index; }, getIndex(arr) { let timeIndex = []; let indexKey = ["year", "month", "day", "hours", "minutes"]; this.activityArray.forEach((element, index) => { let _index = element.findIndex( (item) => parseInt(item.id) === parseInt(arr[indexKey[index]]) ); timeIndex[index] = _index >= 0 ? _index : 0; }); return timeIndex; }, initTime() { let _index = this.timeIndex; this.year = this.activityArray[0][_index[0]].id; this.month = this.activityArray[1][_index[1]].id; this.day = this.activityArray[2][_index[2]].id; this.hour = this.activityArray[3][_index[3]].id; this.minute = this.activityArray[4][_index[4]].id; }, setDateData(_date) { let _data = dateDate(_date); this.activityArray = this.datePicker(_data.year, _data.month); }, bindMultiPickerChange(e) { console.log("picker发送选择改变,携带值为", e.detail.value); let activityArray = JSON.parse(JSON.stringify(this.activityArray)), { value } = e.detail, _result = []; for (let i = 0; i < value.length; i++) { _result[i] = activityArray[i][value[i]].id; } this.$emit("result", _result); }, bindMultiPickerColumnChange(e) { console.log("修改的列为", e.detail.column, ",值为", e.detail.value); let _data = JSON.parse(JSON.stringify(this.activityArray)), timeIndex = JSON.parse(JSON.stringify(this.timeIndex)), { startIndex, endIndex } = this, { column, value } = e.detail, _value = _data[column][value].id, _start = dateDate(this.startTime), _end = dateDate(this.endTime); switch (e.detail.column) { case 0: if (_value <= _start.year) { timeIndex = startIndex; this.year = _start.year; this.setDateData(this.startTime); } else if (_value >= _end.year) { this.year = _end.year; timeIndex = [endIndex[0], 0, 0, 0, 0]; this.setDateData(this.endTime); } else { this.year = _value; timeIndex = [value, 0, 0, 0, 0]; this.activityArray = this.datePicker(_value, 1); } timeIndex = this.timeIndex = JSON.parse(JSON.stringify(timeIndex)); this.timeIndex = timeIndex; break; case 1: if (this.year == _start.year && value <= startIndex[1]) { timeIndex = startIndex; this.month = _start.month; this.setDateData(this.startTime); } else if (this.year == _end.year && value >= endIndex[1]) { timeIndex = endIndex; this.month = _end.month; this.setDateData(this.endTime); } else { this.month = _value; _data[2] = this.datePicker(this.year, this.month)[2]; timeIndex = [timeIndex[0], value, 0, 0, 0]; this.activityArray = _data; } this.timeIndex = JSON.parse(JSON.stringify(timeIndex)); break; case 2: if ( this.year == _start.year && this.month == _start.month && value <= startIndex[2] ) { this.day = _start.day; timeIndex = startIndex; } else if ( this.year == _end.year && this.month == _end.month && value >= endIndex[2] ) { this.day = _end.day; timeIndex = endIndex; } else { this.day = _value; timeIndex = [timeIndex[0], timeIndex[1], value, 0, 0]; } this.timeIndex = JSON.parse(JSON.stringify(timeIndex)); break; case 3: if ( this.year == _start.year && this.month == _start.month && this.day == _start.day && value <= startIndex[3] ) { this.hour = _start.hours; timeIndex = startIndex; } else if ( this.year == _end.year && this.month == _end.month && this.day == _end.day && value >= endIndex[3] ) { this.hour = _end.hours; timeIndex = endIndex; } else { this.hour = _value; timeIndex[3] = value; timeIndex[4] = 0; } this.timeIndex = JSON.parse(JSON.stringify(timeIndex)); break; case 4: timeIndex[4] = value; if ( this.year == _start.year && this.month == _start.month && this.day == _start.day && this.hour == _start.hours && value <= startIndex[4] ) { timeIndex = startIndex; } else if ( this.year == _end.year && this.month == _end.month && this.day == _end.day && this.hour == _end.hours && value >= endIndex[4] ) { timeIndex = endIndex; } this.timeIndex = JSON.parse(JSON.stringify(timeIndex)); break; } }, },};</script>
这里面要注意的就是获取选择器数据和索引的时候一定要加深拷贝, let _data = JSON.parse(JSON.stringify(this.activityArray)), timeIndex = JSON.parse(JSON.stringify(this.timeIndex)), 不然下面this.activityArray赋值为新数组的时候,视图一直不更新,这个问题坑了我蛮久
组件引用:
<template> <view class="page-body"> <view class="page-section"> <text>时间日期选择器-有默认</text> <view> <timePickerColumn :start-time="startTime" :end-time="endTime" :default-time="defaultTime" @result="onResult" > <input placeholder="请选择" :value="time" > </timePickerColumn> </view> </view> </view></template><script>import timePickerColumn from "./component/time-picker-column/picker";export default { components:{ timePickerColumn }, data() { return { time:'', startTime: new Date(), endTime: new Date(), default:new Date(), }; }, created(){ this.startTime = this.getTime("min", 1); this.endTime = this.getTime("year", 2); this.defaultTime = this.getTime("min", 30); }, methods: { getTime(key, number, date) { let _date = date ? new Date(date) : new Date(); if (key === "min") { _date.setMinutes(_date.getMinutes() + number); } if (key === "hour") { _date.setHours(_date.getHours() + number); } if (key === "year") { _date.setFullYear(_date.getFullYear() + number); } return _date; }, onResult(arr) { let time = arr[0] + "-" + arr[1] + "-" + arr[2] + " " + arr[3] + ":" + arr[4]; this.time = time; }, },};</script>
友情提示:
该组件也是我写项目时临时开发的,并没有真正的适应所有的情况下的日期时间选择器,在初始值那块可能好需要你自己灵活的改动下
仓库地址: