最近在使用uni-app进行小程序开发,涉及到日历的开发,由于是多语言版本,需要找到能够自定义周一到周末各个名称的日历插件,逛了一圈插件市场没有找到满意的,只好自己动手,丰衣足食
话不多说,直接开干,先看下我们想要实现的效果
准备工作:
弹出板这些就不多说了,uni的插件市场还是比较丰富的,当然还是要推荐一下uview这个组件库,从文档到使用上,都有比较丝滑的体验
然后dayjs建议引入,一个轻量的时间处理库
TIP
由于业务的原因,日历部分的年月日显示那一栏,最终实现出来,只会显示年和月份,不会显示日期,否则会有逻辑上的错误,所以最终实现的效果是这样
- 年月显示
先放上代码
template
class= <button type="default" class="plain-btn" @click="reduceMonth"> <u-icon name="weibiaoti--11" custom-prefix="iconfont" size="34" class="text-grey-1">u-icon> button> <view class="text-18 text-bold text-primary"> <text>{{year}}text> <text>-text> <text>{{month > 9 ? month : `0${month}`}}text> view> <button type="default" class="plain-btn" @click="addMonth"> <u-icon name="weibiaoti--11" custom-prefix="iconfont" size="34" class="text-grey-1 trans-icon">u-icon> button> view>
js
data () { return { year: '', month: '', date: '', week: '', now: '', choose: '', weekList: [ { week: 0, name: '周日' }, { week: 1, name: '周一' }, { week: 2, name: '周二' }, { week: 3, name: '周三' }, { week: 4, name: '周四' }, { week: 5, name: '周五' }, { week: 6, name: '周六' } ] } }, // methods 上面template中涉及到的方法 reduceMonth () { // 月份前推 if (this.month !== 1) { this.month-- } else { this.month = 12 this.year-- } }, addMonth () { // 月份后推 if (this.month !== 12) { this.month++ } else { this.month = 1 this.year++ } }
这一坨的显示就没有问题了
但还不够,我们得给一个初始值,生成一个setToday()方法做为初始方法,在created生命周期中调用
// watch // watch watch: { now (val) { // 设置显示的年月日用作显示 this.year = val.getFullYear() this.month = val.getMonth() + 1 this.date = val.getDate() this.week = val.getDay() } },// methodsetToday () { // init设置当前日 const vm = this vm.now = new Date() vm.choose = { // choose存放用户选择的日期,初始为当天,用作提交 year: vm.now.getFullYear(), month: vm.now.getMonth() + 1, date: vm.now.getDate(), week: vm.now.getDay(), isInMonth: true, // 是否是当月内 isBetween: vm.compareDays(dayjs()) // 是否在可选范围内,下面会讲到compareDays() } },
再提一嘴,这里用dayjs提供的方法要舒服得多,我是后面做着才想起引入,然后现在正在打字的我表示不想改了,将就看吧
,说一下choose.isInMonth,这个字段用来标识是否是当前显示的月份内的日期,眼睛多的朋友应该已经发现,日历面板上只显示了当月份的所有天,而前面空置的是空白
-
周一到周末
代码如下
// temlateclass= class=// propsweekNames: { // 周末到周六的每一天的名称 type: Array, default: function () { return [] } }// created当中加入if (this.weekNames.length === 7) { this.weekList.forEach((item, idx) => { item.name = this.weekNames[idx] }) }
如上面代码所示,为了实现业务需求中多语言的周一到周末,在props中定义了weekNames,并将其传递进weekList
- 最重要的日期面板
// templateclass= <view v-for="(k,idx) in daysList" :key="idx" :class="k.isInMonth ? 'self-day' : 'out-month'" class="day-item"> <view class="days-container flex-row flex-jst-center flex-ali-center" v-if="k.isInMonth" @click="chooseDays(k)" :class="{'is-btw': k.isBetween, 'choosed': k.date === choose.date && k.month === choose.month}"> {{k.date}} view> view> </view>
先说一下样式:
self-day: 是否显示出日期(即是否在所显示的月份内)
is-btw: 决定是否为可选日期(有border)
choosed: 决定是否为所选日期
先看下daysList的生成
// computedmonthList () { // 每月天数 const vm = this const list = [ { month: 1, days: 31 }, { month: 2, days: 28 }, { month: 3, days: 31 }, { month: 4, days: 30 }, { month: 5, days: 31 }, { month: 6, days: 30 }, { month: 7, days: 31 }, { month: 8, days: 31 }, { month: 9, days: 30 }, { month: 10, days: 31 }, { month: 11, days: 30 }, { month: 12, days: 31 } ] // isLeapYear为判断是否是闰年的方法,傻子才自己写,聪明的人都用dayjs里面现成的!! if (vm.isLeapYear(vm.year)) { list[1].days = 29 } return list},daysList () { // 要显示在页面上的日期列 const vm = this let result = [] if (vm.month) { const len = vm.monthList.find(item => item.month === vm.month).days // 获得对应月份总天数 for (let k=1;k<=len;k++) { const dayInfo = { year: vm.year, month: vm.month, date: k, week: new Date(`${vm.year}/${vm.month}/${k}`).getDay(), isInMonth: true, // 是否是当月内 isBetween: vm.compareDays(dayjs(`${vm.year}/${vm.month}/${k}`)) } result.push(dayInfo) } // 月前补足 const start = result[0].week for (let k=0;k result.unshift({isInMonth: false}) } } return result}
要知道当前该显示多少个日期,我们需要知道该月到底有多少天,那么就需要定义一个monthList用作存放当年每月的天数,唯一的变动就是需要用isLeapYear去判断是否是闰年,以决定二月份是29天还是28天
dayList当中,我们增加了一个属性isBetween,这个属性决定了该日期是否在可选日期内
compareDays (day) { const vm = this return dayjs(day).isBetween(dayjs(vm.startDate), dayjs(vm.endDate), null, '[]')},
如上,通过props内定义的startDate,和endDate,决定了可选日期范围范围,然后根据这个范围,决定对应日期是否可选(is-btw)
注意月前补足,为了显示出非本月份的空隙,所以要添加进对应数量的空对象填充进daysList前,并设置这类对象isInMonth = false,来控制其显示空白
到了这里,核心逻辑基本已经完善了,剩下的就是点击日期然后提交日期了
放上我的整个组件,当中有些公用类名,比如flex-row,text-primary眼睛多的应该看的懂我就不贴了,眼睛少的可以留言问我,我来告诉你
calendar.vue
<template> <view class="calendar-contaienr border-box"> <view class="calendar-name text-18 text-center">{{name}}view> <view class="title-show flex-row flex-jst-btw flex-ali-center ma-col-md"> <button type="default" class="plain-btn" @click="reduceMonth"> <u-icon name="weibiaoti--11" custom-prefix="iconfont" size="34" class="text-grey-1">u-icon> button> <view class="text-18 text-bold text-primary"> <text>{{year}}text> <text>-text> <text>{{month > 9 ? month : `0${month}`}}text> view> <button type="default" class="plain-btn" @click="addMonth"> <u-icon name="weibiaoti--11" custom-prefix="iconfont" size="34" class="text-grey-1 trans-icon">u-icon> button> view> <view class="week-show flex-row flex-nowrap flex-jst-btw flex-ali-center"> <text class="text-12 text-grey flex-1 text-center" v-for="k in weekList" :key="k.week">{{k.name}}text> view> <view class="days-show flex-row flex-wrap flex-jst-start flex-ali-start ma-col-md"> <view v-for="(k,idx) in daysList" :key="idx" :class="k.isInMonth ? 'self-day' : 'out-month'" class="day-item"> <view class="days-container flex-row flex-jst-center flex-ali-center" v-if="k.isInMonth" @click="chooseDays(k)" :class="{'is-btw': k.isBetween, 'choosed': k.date === choose.date && k.month === choose.month}"> {{k.date}} view> view> view> <view class="width-80 flex-row flex-jst-center flex-ali-center btn-container"> <button type="default" class="my-btn-primary text-white" @click="subDays">{{btnText}}button> view> view>template> <script> import dayjs from 'dayjs' const isBetween = require('dayjs/plugin/isBetween') dayjs.extend(isBetween) export default { name: 'calendar', props: { formatValue: { // 获取最终值的format格式 type: String, default: 'YYYY-MM-DD' }, name: { // 标题名字 default: '日期选择' }, btnText: { // 确认按钮文字 default: '确认' }, weekNames: { // 周末到周六的每一天的名称 type: Array, default: function () { return [] } }, startDate: { // 可选时间范围 default: function () { return dayjs().format('YYYY/MM/DD') } }, endDate: { // 可选时间范围结束 default: function () { return dayjs().add(30,'day').format('YYYY/MM/DD') } } }, data () { return { year: '', month: '', date: '', week: '', now: '', choose: '', weekList: [ { week: 0, name: '周日' }, { week: 1, name: '周一' }, { week: 2, name: '周二' }, { week: 3, name: '周三' }, { week: 4, name: '周四' }, { week: 5, name: '周五' }, { week: 6, name: '周六' } ] } }, created () { this.setToday() this.compareDays() if (this.weekNames.length === 7) { this.weekList.forEach((item, idx) => { item.name = this.weekNames[idx] }) } }, watch: { now (val) { this.year = val.getFullYear() this.month = val.getMonth() + 1 this.date = val.getDate() this.week = val.getDay() } }, computed: { monthList () { // 每月天数 const vm = this const list = [ { month: 1, days: 31 }, { month: 2, days: 28 }, { month: 3, days: 31 }, { month: 4, days: 30 }, { month: 5, days: 31 }, { month: 6, days: 30 }, { month: 7, days: 31 }, { month: 8, days: 31 }, { month: 9, days: 30 }, { month: 10, days: 31 }, { month: 11, days: 30 }, { month: 12, days: 31 } ] if (vm.isLeapYear(vm.year)) { list[1].days = 29 } return list }, daysList () { // 要显示在页面上的日期列 const vm = this let result = [] if (vm.month) { const len = vm.monthList.find(item => item.month === vm.month).days // 获得对应月份总天数 const arr = [1,2,3] for (let k=1;k<=len;k++) { const dayInfo = { year: vm.year, month: vm.month, date: k, week: new Date(`${vm.year}/${vm.month}/${k}`).getDay(), isInMonth: true, // 是否是当月内 isBetween: vm.compareDays(dayjs(`${vm.year}/${vm.month}/${k}`)) } result.push(dayInfo) } // 月前补足 const start = result[0].week for (let k=0;k result.unshift({isInMonth: false}) } } return result } }, methods: { chooseDays (target) { // 点击日期 if (target.isBetween) { this.choose = target } }, subDays () { // 提交日期 const str = `${this.choose.year}/${this.choose.month}/${this.choose.date}` this.$emit('choose', dayjs(str).format(this.formatValue)) }, compareDays (day) { const vm = this return dayjs(day).isBetween(dayjs(vm.startDate), dayjs(vm.endDate), null, '[]') }, isLeapYear (year) { // 判断是否是闰年 let result = false const reason1 = (year % 4 === 0) && (year % 100 !== 0) const reason2 = (year % 100 === 0) && (year % 400 === 0) if (reason1 || reason2) { result = true } return result }, setToday () { // init设置当前日 const vm = this vm.now = new Date() vm.choose = { year: vm.now.getFullYear(), month: vm.now.getMonth() + 1, date: vm.now.getDate(), week: vm.now.getDay(), isInMonth: true, // 是否是当月内 isBetween: vm.compareDays(dayjs()) } }, reduceMonth () { // 月份前推 if (this.month !== 1) { this.month-- } else { this.month = 12 this.year-- } }, addMonth () { // 月份后推 if (this.month !== 12) { this.month++ } else { this.month = 1 this.year++ } } } }script><style lang="scss" scoped> @import './calendar.scss';style>
calendar.scss
.calendar-contaienr{ width: 100%; min-height: 80vh; background: #FFFFFF; padding: 15px 15px 0 15px; position: relative; .btn-container{ margin: auto; position: absolute; bottom: 0; left: 50%; transform: translate(-50%, -100%); } .title-show{ box-sizing: border-box; padding: 3px; border-top: 1px solid #CCCCCC; border-bottom: 1px solid #CCCCCC; .plain-btn{ margin: 0; .trans-icon{ transform: rotate(180deg); } } } .days-show{ .day-item{ width: calc(100% / 7); box-sizing: border-box; padding: 5px; } .self-day{ // background: green; .days-container{ padding: 10px; box-sizing: border-box; width: 100%; border-radius: 10.41rpx; color: #aaaaaa; &.is-btw{ border: 1px solid #cfcfcf; color: #000000; &.choosed{ color: #ffffff; background: $uni-color-primary; border-color: $uni-color-primary; } } } } .out-month{ // background: grey; } }}