| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 | 
							- <template>
 
-   <view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
 
-     <view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
 
-           :ref="`u-calendar-month-${index}`" :id="`month-${index}`">
 
-       <text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
 
-       <view class="u-calendar-month__days">
 
-         <view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
 
-           <text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
 
-         </view>
 
-         <view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
 
-               :style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
 
-               :class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
 
-           <view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
 
-             <text class="u-calendar-month__days__day__select__info"
 
-                   :class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
 
-                   :style="[textStyle(item1)]">{{ item1.day }}
 
-             </text>
 
-             <text v-if="getBottomInfo(index, index1, item1)"
 
-                   class="u-calendar-month__days__day__select__buttom-info"
 
-                   :class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
 
-                   :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}
 
-             </text>
 
-             <text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
 
-           </view>
 
-         </view>
 
-       </view>
 
-     </view>
 
-   </view>
 
- </template>
 
- <script>
 
- // #ifdef APP-NVUE
 
- // 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
 
- const dom = uni.requireNativePlugin('dom')
 
- // #endif
 
- import dayjs from '../../libs/util/dayjs.js';
 
- export default {
 
-   name: 'u-calendar-month',
 
-   mixins: [uni.$u.mpMixin, uni.$u.mixin],
 
-   props: {
 
-     // 是否显示月份背景色
 
-     showMark: {
 
-       type: Boolean,
 
-       default: true
 
-     },
 
-     // 主题色,对底部按钮和选中日期有效
 
-     color: {
 
-       type: String,
 
-       default: '#3c9cff'
 
-     },
 
-     // 月份数据
 
-     months: {
 
-       type: Array,
 
-       default: () => []
 
-     },
 
-     // 日期选择类型
 
-     mode: {
 
-       type: String,
 
-       default: 'single'
 
-     },
 
-     // 日期行高
 
-     rowHeight: {
 
-       type: [String, Number],
 
-       default: 58
 
-     },
 
-     // mode=multiple时,最多可选多少个日期
 
-     maxCount: {
 
-       type: [String, Number],
 
-       default: Infinity
 
-     },
 
-     // mode=range时,第一个日期底部的提示文字
 
-     startText: {
 
-       type: String,
 
-       default: '开始'
 
-     },
 
-     // mode=range时,最后一个日期底部的提示文字
 
-     endText: {
 
-       type: String,
 
-       default: '结束'
 
-     },
 
-     // 默认选中的日期,mode为multiple或range是必须为数组格式
 
-     defaultDate: {
 
-       type: [Array, String, Date],
 
-       default: null
 
-     },
 
-     // 最小的可选日期
 
-     minDate: {
 
-       type: [String, Number],
 
-       default: 0
 
-     },
 
-     // 最大可选日期
 
-     maxDate: {
 
-       type: [String, Number],
 
-       default: 0
 
-     },
 
-     // 如果没有设置maxDate,则往后推多少个月
 
-     maxMonth: {
 
-       type: [String, Number],
 
-       default: 2
 
-     },
 
-     // 是否为只读状态,只读状态下禁止选择日期
 
-     readonly: {
 
-       type: Boolean,
 
-       default: uni.$u.props.calendar.readonly
 
-     },
 
-     // 日期区间最多可选天数,默认无限制,mode = range时有效
 
-     maxRange: {
 
-       type: [Number, String],
 
-       default: Infinity
 
-     },
 
-     // 范围选择超过最多可选天数时的提示文案,mode = range时有效
 
-     rangePrompt: {
 
-       type: String,
 
-       default: ''
 
-     },
 
-     // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
 
-     showRangePrompt: {
 
-       type: Boolean,
 
-       default: true
 
-     },
 
-     // 是否允许日期范围的起止时间为同一天,mode = range时有效
 
-     allowSameDay: {
 
-       type: Boolean,
 
-       default: false
 
-     }
 
-   },
 
-   data() {
 
-     return {
 
-       // 每个日期的宽度
 
-       width: 0,
 
-       // 当前选中的日期item
 
-       item: {},
 
-       selected: []
 
-     }
 
-   },
 
-   watch: {
 
-     selectedChange: {
 
-       immediate: true,
 
-       handler(n) {
 
-         this.setDefaultDate()
 
-       }
 
-     }
 
-   },
 
-   computed: {
 
-     // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
 
-     selectedChange() {
 
-       return [this.minDate, this.maxDate, this.defaultDate]
 
-     },
 
-     dayStyle(index1, index2, item) {
 
-       return (index1, index2, item) => {
 
-         const style = {}
 
-         let week = item.week
 
-         // 不进行四舍五入的形式保留2位小数
 
-         const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
 
-         // 得出每个日期的宽度
 
-         // #ifdef APP-NVUE
 
-         style.width = uni.$u.addUnit(dayWidth)
 
-         // #endif
 
-         style.height = uni.$u.addUnit(this.rowHeight)
 
-         if (index2 === 0) {
 
-           // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
 
-           week = (week === 0 ? 7 : week) - 1
 
-           style.marginLeft = uni.$u.addUnit(week * dayWidth)
 
-         }
 
-         if (this.mode === 'range') {
 
-           // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
 
-           style.paddingLeft = 0
 
-           style.paddingRight = 0
 
-           style.paddingBottom = 0
 
-           style.paddingTop = 0
 
-         }
 
-         return style
 
-       }
 
-     },
 
-     daySelectStyle() {
 
-       return (index1, index2, item) => {
 
-         let date = dayjs(item.date).format("YYYY-MM-DD"),
 
-             style = {}
 
-         // 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
 
-         if (this.selected.some(item => this.dateSame(item, date))) {
 
-           style.backgroundColor = this.color
 
-         }
 
-         if (this.mode === 'single') {
 
-           if (date === this.selected[0]) {
 
-             // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
 
-             style.borderTopLeftRadius = '3px'
 
-             style.borderBottomLeftRadius = '3px'
 
-             style.borderTopRightRadius = '3px'
 
-             style.borderBottomRightRadius = '3px'
 
-           }
 
-         } else if (this.mode === 'range') {
 
-           if (this.selected.length >= 2) {
 
-             const len = this.selected.length - 1
 
-             // 第一个日期设置左上角和左下角的圆角
 
-             if (this.dateSame(date, this.selected[0])) {
 
-               style.borderTopLeftRadius = '3px'
 
-               style.borderBottomLeftRadius = '3px'
 
-             }
 
-             // 最后一个日期设置右上角和右下角的圆角
 
-             if (this.dateSame(date, this.selected[len])) {
 
-               style.borderTopRightRadius = '3px'
 
-               style.borderBottomRightRadius = '3px'
 
-             }
 
-             // 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
 
-             if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
 
-                 .selected[len]))) {
 
-               style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
 
-               // 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
 
-               style.opacity = 0.7
 
-             }
 
-           } else if (this.selected.length === 1) {
 
-             // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
 
-             // 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
 
-             style.borderTopLeftRadius = '3px'
 
-             style.borderBottomLeftRadius = '3px'
 
-           }
 
-         } else {
 
-           if (this.selected.some(item => this.dateSame(item, date))) {
 
-             style.borderTopLeftRadius = '3px'
 
-             style.borderBottomLeftRadius = '3px'
 
-             style.borderTopRightRadius = '3px'
 
-             style.borderBottomRightRadius = '3px'
 
-           }
 
-         }
 
-         return style
 
-       }
 
-     },
 
-     // 某个日期是否被选中
 
-     textStyle() {
 
-       return (item) => {
 
-         const date = dayjs(item.date).format("YYYY-MM-DD"),
 
-             style = {}
 
-         // 选中的日期,提示文字设置白色
 
-         if (this.selected.some(item => this.dateSame(item, date))) {
 
-           style.color = '#ffffff'
 
-         }
 
-         if (this.mode === 'range') {
 
-           const len = this.selected.length - 1
 
-           // 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
 
-           if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
 
-               .selected[len]))) {
 
-             style.color = this.color
 
-           }
 
-         }
 
-         return style
 
-       }
 
-     },
 
-     // 获取底部的提示文字
 
-     getBottomInfo() {
 
-       return (index1, index2, item) => {
 
-         const date = dayjs(item.date).format("YYYY-MM-DD")
 
-         const bottomInfo = item.bottomInfo
 
-         // 当为日期范围模式时,且选择的日期个数大于0时
 
-         if (this.mode === 'range' && this.selected.length > 0) {
 
-           if (this.selected.length === 1) {
 
-             // 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
 
-             if (this.dateSame(date, this.selected[0])) return this.startText
 
-             else return bottomInfo
 
-           } else {
 
-             const len = this.selected.length - 1
 
-             // 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
 
-             if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
 
-                 len === 1) {
 
-               // 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
 
-               return `${this.startText}/${this.endText}`
 
-             } else if (this.dateSame(date, this.selected[0])) {
 
-               return this.startText
 
-             } else if (this.dateSame(date, this.selected[len])) {
 
-               return this.endText
 
-             } else {
 
-               return bottomInfo
 
-             }
 
-           }
 
-         } else {
 
-           return bottomInfo
 
-         }
 
-       }
 
-     }
 
-   },
 
-   mounted() {
 
-     this.init()
 
-   },
 
-   methods: {
 
-     init() {
 
-       // 初始化默认选中
 
-       this.$emit('monthSelected', this.selected)
 
-       this.$nextTick(() => {
 
-         // 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
 
-         // 因为nvue下,$nextTick并不是100%可靠的
 
-         uni.$u.sleep(10).then(() => {
 
-           this.getWrapperWidth()
 
-           this.getMonthRect()
 
-         })
 
-       })
 
-     },
 
-     // 判断两个日期是否相等
 
-     dateSame(date1, date2) {
 
-       return dayjs(date1).isSame(dayjs(date2))
 
-     },
 
-     // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
 
-     getWrapperWidth() {
 
-       // #ifdef APP-NVUE
 
-       dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
 
-         this.width = res.size.width
 
-       })
 
-       // #endif
 
-       // #ifndef APP-NVUE
 
-       this.$uGetRect('.u-calendar-month-wrapper').then(size => {
 
-         this.width = size.width
 
-       })
 
-       // #endif
 
-     },
 
-     getMonthRect() {
 
-       // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
 
-       const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
 
-           `u-calendar-month-${index}`))
 
-       // 一次性返回
 
-       Promise.all(promiseAllArr).then(
 
-           sizes => {
 
-             let height = 1
 
-             const topArr = []
 
-             for (let i = 0; i < this.months.length; i++) {
 
-               // 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
 
-               topArr[i] = height
 
-               height += sizes[i].height
 
-             }
 
-             // 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
 
-             this.$emit('updateMonthTop', topArr)
 
-           })
 
-     },
 
-     // 获取每个月份区域的尺寸
 
-     getMonthRectByPromise(el) {
 
-       // #ifndef APP-NVUE
 
-       // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
 
-       // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
 
-       return new Promise(resolve => {
 
-         this.$uGetRect(`.${el}`).then(size => {
 
-           resolve(size)
 
-         })
 
-       })
 
-       // #endif
 
-       // #ifdef APP-NVUE
 
-       // nvue下,使用dom模块查询元素高度
 
-       // 返回一个promise,让调用此方法的主体能使用then回调
 
-       return new Promise(resolve => {
 
-         dom.getComponentRect(this.$refs[el][0], res => {
 
-           resolve(res.size)
 
-         })
 
-       })
 
-       // #endif
 
-     },
 
-     // 点击某一个日期
 
-     clickHandler(index1, index2, item) {
 
-       if (this.readonly) {
 
-         return;
 
-       }
 
-       this.item = item
 
-       const date = dayjs(item.date).format("YYYY-MM-DD")
 
-       if (item.disabled) return
 
-       // 对上一次选择的日期数组进行深度克隆
 
-       let selected = uni.$u.deepClone(this.selected)
 
-       if (this.mode === 'single') {
 
-         // 单选情况下,让数组中的元素为当前点击的日期
 
-         selected = [date]
 
-       } else if (this.mode === 'multiple') {
 
-         if (selected.some(item => this.dateSame(item, date))) {
 
-           // 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
 
-           const itemIndex = selected.findIndex(item => item === date)
 
-           selected.splice(itemIndex, 1)
 
-         } else {
 
-           // 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
 
-           if (selected.length < this.maxCount) selected.push(date)
 
-         }
 
-       } else {
 
-         // 选择区间形式
 
-         if (selected.length === 0 || selected.length >= 2) {
 
-           // 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
 
-           selected = [date]
 
-         } else if (selected.length === 1) {
 
-           // 如果已经选择了开始日期
 
-           const existsDate = selected[0]
 
-           // 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
 
-           if (dayjs(date).isBefore(existsDate)) {
 
-             selected = [date]
 
-           } else if (dayjs(date).isAfter(existsDate)) {
 
-             // 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
 
-             if (dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
 
-               if (this.rangePrompt) {
 
-                 uni.$u.toast(this.rangePrompt)
 
-               } else {
 
-                 uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
 
-               }
 
-               return
 
-             }
 
-             // 如果当前日期大于已有日期,将当前的添加到数组尾部
 
-             selected.push(date)
 
-             const startDate = selected[0]
 
-             const endDate = selected[1]
 
-             const arr = []
 
-             let i = 0
 
-             do {
 
-               // 将开始和结束日期之间的日期添加到数组中
 
-               arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
 
-               i++
 
-               // 累加的日期小于结束日期时,继续下一次的循环
 
-             } while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
 
-             // 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
 
-             arr.push(endDate)
 
-             selected = arr
 
-           } else {
 
-             // 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
 
-             if (selected[0] === date && !this.allowSameDay) return
 
-             selected.push(date)
 
-           }
 
-         }
 
-       }
 
-       this.setSelected(selected)
 
-     },
 
-     // 设置默认日期
 
-     setDefaultDate() {
 
-       if (!this.defaultDate) {
 
-         // 如果没有设置默认日期,则将当天日期设置为默认选中的日期
 
-         const selected = [dayjs().format("YYYY-MM-DD")]
 
-         return this.setSelected(selected, false)
 
-       }
 
-       let defaultDate = []
 
-       const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
 
-       const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
 
-       if (this.mode === 'single') {
 
-         // 单选模式,可以是字符串或数组,Date对象等
 
-         if (!uni.$u.test.array(this.defaultDate)) {
 
-           defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
 
-         } else {
 
-           defaultDate = [this.defaultDate[0]]
 
-         }
 
-       } else {
 
-         // 如果为非数组,则不执行
 
-         if (!uni.$u.test.array(this.defaultDate)) return
 
-         defaultDate = this.defaultDate
 
-       }
 
-       // 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
 
-       defaultDate = defaultDate.filter(item => {
 
-         return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
 
-             maxDate).add(1, 'day'))
 
-       })
 
-       this.setSelected(defaultDate, false)
 
-     },
 
-     setSelected(selected, event = true) {
 
-       this.selected = selected
 
-       event && this.$emit('monthSelected', this.selected)
 
-     }
 
-   }
 
- }
 
- </script>
 
- <style lang="scss" scoped>
 
- @import "../../libs/css/components.scss";
 
- .u-calendar-month-wrapper {
 
-   margin-top: 4px;
 
- }
 
- .u-calendar-month {
 
-   &__title {
 
-     font-size: 14px;
 
-     line-height: 42px;
 
-     height: 42px;
 
-     color: $u-main-color;
 
-     text-align: center;
 
-     font-weight: bold;
 
-   }
 
-   &__days {
 
-     position: relative;
 
-     @include flex;
 
-     flex-wrap: wrap;
 
-     &__month-mark-wrapper {
 
-       position: absolute;
 
-       top: 0;
 
-       bottom: 0;
 
-       left: 0;
 
-       right: 0;
 
-       @include flex;
 
-       justify-content: center;
 
-       align-items: center;
 
-       &__text {
 
-         font-size: 155px;
 
-         color: rgba(231, 232, 234, 0.83);
 
-       }
 
-     }
 
-     &__day {
 
-       @include flex;
 
-       padding: 2px;
 
-       /* #ifndef APP-NVUE */
 
-       // vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
 
-       width: calc(100% / 7);
 
-       box-sizing: border-box;
 
-       /* #endif */
 
-       &__select {
 
-         flex: 1;
 
-         @include flex;
 
-         align-items: center;
 
-         justify-content: center;
 
-         position: relative;
 
-         &__dot {
 
-           width: 7px;
 
-           height: 7px;
 
-           border-radius: 100px;
 
-           background-color: $u-error;
 
-           position: absolute;
 
-           top: 12px;
 
-           right: 7px;
 
-         }
 
-         &__buttom-info {
 
-           color: $u-content-color;
 
-           text-align: center;
 
-           position: absolute;
 
-           bottom: 5px;
 
-           font-size: 10px;
 
-           text-align: center;
 
-           left: 0;
 
-           right: 0;
 
-           &--selected {
 
-             color: #ffffff;
 
-           }
 
-           &--disabled {
 
-             color: #cacbcd;
 
-           }
 
-         }
 
-         &__info {
 
-           text-align: center;
 
-           font-size: 16px;
 
-           &--selected {
 
-             color: #ffffff;
 
-           }
 
-           &--disabled {
 
-             color: #cacbcd;
 
-           }
 
-         }
 
-         &--selected {
 
-           background-color: $u-primary;
 
-           @include flex;
 
-           justify-content: center;
 
-           align-items: center;
 
-           flex: 1;
 
-           border-radius: 3px;
 
-         }
 
-         &--range-selected {
 
-           opacity: 0.3;
 
-           border-radius: 0;
 
-         }
 
-         &--range-start-selected {
 
-           border-top-right-radius: 0;
 
-           border-bottom-right-radius: 0;
 
-         }
 
-         &--range-end-selected {
 
-           border-top-left-radius: 0;
 
-           border-bottom-left-radius: 0;
 
-         }
 
-       }
 
-     }
 
-   }
 
- }
 
- </style>
 
 
  |