仿写美团酒店日期选择组件(小程序、React-Native)

4,078 阅读5分钟

日期选择

在哪

美团首页 - 酒店住宿 - 日期选择

美团上

美团小程序 & 美团App

我的DEMO效果

小程序(原生) & IOS(RN) & Android(RN)

为啥写

这几天没那么忙,偶然瞄到美团这个组件,觉得很舒服,就想写写

美团小程序这功能估计很可能是mpvue写的,我这里使用小程序原生的组件去写

美团App上的这个组件,我这里用React-Native去写

选择器

小程序这一块,是使用一个页面去完成这个选择的动作,而在App上的表现,是从底部弹出来的,好奇的我还去看了美团移动端页面这个组件的表现,也有一些差异。 整体上来说,功能是一样的,选日期,入店-离店日期。

咋写的

  • 日历怎么生成?
问题拆分:
1. 怎么获取今天是星期几?
2. 当前月份1号是星期几?
3. 怎么获取上个月多余出来的,要显示在当前月置灰状态的日期?
4. 上个月多余的日期,怎么控制有些要显示出来,有些用不上?
5. 怎么获取到本月所有日期?
使用moment.js日期处理库
获取今天是星期几
# 若星期一则返回1, 即可能的值是[1,2,3,4,5,6,7]
moment().format('E')
获取这个月1号的日期对象和1号是星期几
# 比如今天是12.05号,星期三
const m1 = moment().startOf('month')
const w1 = m1.format('E')
获取上个月多余的日期数组

获取19年02月,上个月多余的日期,即如下图红色日期

其中从左到右分别为

['日', '一', '二', '三', '四', '五', '六']

1号星期5,我知道,那这周的星期天,也就是这周的第一天的日期是可以推算出来的。

既然1号是星期五,那周日到周五,已经过了5天, 1号日期往后倒推5天就是周日那天的日期

firstDayTime.subtract(5, 'days')

从而获取到27 - 31号5天的一个日期数组

同样的思路, 19年02年月最后一行空缺的日期,也能算出来

那也就是说,我每一个月的日期面板,都是完整的矩形,不存在空缺,把空缺的问题转化为,怎么去隐藏非本月的日期的显示?

注意并非所有这个月1号之前的日期都是不可见的状态,例如下图

12月1号是不可见,也不需要显示的, 但2,3,4号是需要可见置灰状态的, 我们发现这种情况下,无非就是当前一周里,有过去了的日期,同时当月的1号也在这周里面,根据这样的条件,是不难去做出判断的

当本月日历列表出来后,那下一个月,下下个月的就变得简单了,因为当前月的情况会更多一些,未来月份的情况是固定的

今天、明天、后天

App上体验好一些,今、明、后天是直接标出的

const DAY_MAP = {
  [moment().format(FORMAT_TYPE)]: '今天',
  [moment().add(1, 'days').format(FORMAT_TYPE)]: '明天',
  [moment().add(2, 'days').format(FORMAT_TYPE)]: '后天'
}
...
!!DAY_MAP[formatDay] ? DAY_MAP[formatDay] : date
...

布局

flex布局

这里碰到一个问题,给每一个屏幕宽度一个七等分的处理,即每一个日期小框框宽度都是:(屏幕宽度 / 7)

750 / 7 
// 107.14285714285714

当每一个小框框都以这个宽度布局的时候, 肯定会有精度问题, 导致屏幕宽度没有完整铺满,还有一点小缝隙的空间,这小缝隙是每一个小框框误差累积出来的总和。

解决方法, 可以不使用flex-wrap:wrap, 不换行,然后一行全铺满flex:1, 这样的话需要把数组切割, 分行渲染。 如果要换行的话,可以简单粗暴的判断第七个小框框,给他一个flex:1, 也就是 (index + 1) % 7 == 0的时候

可供选择的日期

我看美团小程序和App上都一样, App上差不多半年日期可选,小程序上差不多是2个月,具体的算法不太清楚。 我这里是写死的。

React-Native上

react-native-vector-icons

其中有一个关闭的图标, 使用了react-native-vector-icons

react-native-animatable

弹起的动画使用了react-native-animatable, 也可以自己写这个过渡的动画, 之前我已经安装了这个动画组件,也想着试用下

冒泡问题

app上组件弹出来后,点击阴影区域是可以关闭的,点击日期区域不会关闭的,RN上没有冒泡机制配置,我这里是通过给可见阴影区域加上TouchableOpacity组件覆盖,加上专属的Press事件。

其实发现是可以用一个TouchableOpacity组件在父标签与子标签之间再隔一层,达到不冒泡的效果。

ScrollView

如果使用TouchableOpacity组件包裹ScrollView组件,ScrollView会有问题,什么问题,滚不了

SectionList

使用了这个组件完成日期列表分类,同时在IOS下有一个效果,滚动到顶部时,header粘连在屏幕的顶端,stickySectionHeadersEnabled属性,默认true

计算选取了多少晚

使用moment.js方法

eMoment.diff(sMonent, 'days')

问题

  • App上日期上的节假日未实现
  • App上‘请选择离店日期’的提示会出现被header盖住的情况,RN上没有zIndex设置,规律是后面的组件层级高于前面的组件。 这里使用了secondlist组件造成

源码

欢迎Star!!!!