榆山数据端小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

755 lines
27 KiB

import baseComponent from '../helpers/baseComponent'
import classNames from '../helpers/classNames'
const defaults = {
prefixCls: 'wux-calendar',
monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
dayNames: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
dayNamesShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
firstDay: 1, // First day of the week, Monday
weekendDays: [0, 6], // Sunday and Saturday
multiple: false,
dateFormat: 'yyyy-mm-dd',
direction: 'horizontal', // or 'vertical'
minDate: null,
maxDate: null,
touchMove: true,
animate: true,
closeOnSelect: true,
weekHeader: true,
toolbar: true,
value: [],
onMonthAdd() {},
onChange() {},
onOpen() {},
onClose() {},
onDayClick() {},
onMonthYearChangeStart() {},
onMonthYearChangeEnd() {},
}
// 获取手指触摸点坐标
const getTouchPosition = (e) => {
const touches = e.touches[0] || e.changedTouches[0]
return {
x: touches.pageX,
y: touches.pageY,
}
}
// 获取元素旋转属性
const getTransform = (translate, isH) => `transform: translate3d(${isH ? translate : 0}%, ${isH ? 0 : translate}%, 0)`
// 判断两个日期是否在同一天
const isSameDate = (a, b) => {
const prev = new Date(a)
const next = new Date(b)
return prev.getFullYear() === next.getFullYear() && prev.getMonth() === next.getMonth() && prev.getDate() === next.getDate()
}
baseComponent({
useFunc: true,
data: defaults,
computed: {
classes: ['prefixCls, direction', function(prefixCls, direction) {
const wrap = classNames(prefixCls, {
[`${prefixCls}--${direction}`]: direction,
})
const content = `${prefixCls}__content`
const hd = `${prefixCls}__hd`
const toolbar = `${prefixCls}__toolbar`
const picker = `${prefixCls}__picker`
const link = `${prefixCls}__link`
const prev = classNames(`${prefixCls}__icon`, {
[`${prefixCls}__icon--prev`]: true,
})
const next = classNames(`${prefixCls}__icon`, {
[`${prefixCls}__icon--next`]: true,
})
const value = `${prefixCls}__value`
const bd = `${prefixCls}__bd`
const weekdays = `${prefixCls}__weekdays`
const weekday = `${prefixCls}__weekday`
const months = `${prefixCls}__months`
const monthsContent = `${prefixCls}__months-content`
const month = `${prefixCls}__month`
const days = `${prefixCls}__days`
const day = `${prefixCls}__day`
const text = `${prefixCls}__text`
return {
wrap,
content,
hd,
toolbar,
picker,
link,
prev,
next,
value,
bd,
weekdays,
weekday,
months,
monthsContent,
month,
days,
day,
text,
}
}],
},
methods: {
/**
* 打开日历
* @param {Object} opts
*/
open(opts = {}) {
const options = this.$$mergeOptionsAndBindMethods(Object.assign({}, defaults, opts))
this.monthsTranslate = 0
this.isH = options.direction === 'horizontal'
this.$$setData({ in: true, ...options }).then(() => this.init())
this.setValue(options.value)
if (typeof this.fns.onOpen === 'function') {
this.fns.onOpen.call(this)
}
},
/**
* 关闭日历
*/
close() {
this.$$setData({ in: false })
if (typeof this.fns.onClose === 'function') {
this.fns.onClose.call(this)
}
},
/**
* 初始化
*/
init() {
const weeks = this.setWeekHeader()
const months = this.setMonthsHTML()
const monthsTranslate = this.setMonthsTranslate()
if (typeof this.fns.onMonthAdd === 'function') {
months.forEach((month) => this.fns.onMonthAdd.call(this, month))
}
return this.$$setData({ weeks, months, monthsTranslate, wrapperTranslate: '' }).then(() => this.$$setData({...this.updateCurrentMonthYear() }))
},
/**
* 设置月份的位置信息
* @param {Number} translate
*/
setMonthsTranslate(translate = this.monthsTranslate) {
const prevMonthTranslate = -(translate + 1) * 100
const currentMonthTranslate = -translate * 100
const nextMonthTranslate = -(translate - 1) * 100
return [
getTransform(prevMonthTranslate, this.isH),
getTransform(currentMonthTranslate, this.isH),
getTransform(nextMonthTranslate, this.isH),
]
},
/**
* 更新当前年月
* @param {String} dir 方向
*/
updateCurrentMonthYear(dir) {
const { months, monthNames } = this.data
if (typeof dir === 'undefined') {
const currentMonth = parseInt(months[1].month, 10)
const currentYear = parseInt(months[1].year, 10)
const currentMonthName = monthNames[currentMonth]
return {
currentMonth,
currentYear,
currentMonthName,
}
}
const currentMonth = parseInt(months[dir === 'next' ? (months.length - 1) : 0].month, 10)
const currentYear = parseInt(months[dir === 'next' ? (months.length - 1) : 0].year, 10)
const currentMonthName = monthNames[currentMonth]
return {
currentMonth,
currentYear,
currentMonthName,
}
},
/**
* 手指触摸动作开始
* @param {Object} e 事件对象
*/
onTouchStart(e) {
if (!this.data.touchMove || this.isMoved || this.isRendered) return
this.start = getTouchPosition(e)
this.move = {}
this.touchesDiff = 0
this.allowItemClick = true
this.isMoved = false
},
/**
* 手指触摸后移动
* @param {Object} e 事件对象
*/
onTouchMove(e) {
if (!this.data.touchMove || this.isRendered) return
this.allowItemClick = false
if (!this.isMoved) {
this.isMoved = true
}
this.$$setData({ swiping: true })
const { prefixCls } = this.data
const query = wx.createSelectorQuery().in(this)
query.select(`.${prefixCls}__months-content`).boundingClientRect((rect) => {
// 由于 boundingClientRect 为异步方法,某些情况下其回调函数在 onTouchEnd 之后触发,导致 wrapperTranslate 计算错误
// 所以判断 this.isMoved = false 时阻止回调函数的执行
if (!rect || !this.isMoved) return
this.move = getTouchPosition(e)
this.touchesDiff = this.isH ? this.move.x - this.start.x : this.move.y - this.start.y
const { width, height } = rect
const percentage = this.touchesDiff / (this.isH ? width : height)
const currentTranslate = (this.monthsTranslate + percentage) * 100
const transform = getTransform(currentTranslate, this.isH)
this.$$setData({
wrapperTranslate: `transition-duration: 0s; ${transform}`,
})
})
query.exec()
},
/**
* 手指触摸动作结束
*/
onTouchEnd() {
if (!this.data.touchMove || !this.isMoved || this.isRendered) return
this.isMoved = false
this.$$setData({ swiping: false })
if (Math.abs(this.touchesDiff) < 30) {
this.resetMonth()
} else if (this.touchesDiff >= 30) {
this.prevMonth()
} else {
this.nextMonth()
}
// Allow click
setTimeout(() => (this.allowItemClick = true), 100)
},
/**
* 日期的点击事件
* @param {Object} e 事件对象
*/
onDayClick(e) {
if (this.allowItemClick) {
const dataset = e.currentTarget.dataset
const dateYear = dataset.year
const dateMonth = dataset.month
const dateDay = dataset.day
const dateType = dataset.type
if (dateType.selected && !this.data.multiple) return
if (dateType.disabled) return
if (dateType.next) this.nextMonth()
if (dateType.prev) this.prevMonth()
if (typeof this.fns.onDayClick === 'function') {
this.fns.onDayClick.call(this, dateYear, dateMonth, dateDay)
}
this.addValue(new Date(dateYear, dateMonth, dateDay).getTime())
if (this.data.closeOnSelect && !this.data.multiple) {
this.close()
}
}
},
/**
* 重置月份的位置信息
*/
resetMonth() {
const translate = this.monthsTranslate * 100
const transform = getTransform(translate, this.isH)
this.$$setData({
wrapperTranslate: `transition-duration: 0s; ${transform}`,
})
},
/**
* 设置年月
* @param {String} year 年份
* @param {String} month 月份
*/
setYearMonth(year = this.data.currentYear, month = this.data.currentMonth) {
const { months, monthsTranslate, maxDate, minDate, currentYear, currentMonth } = this.data
const targetDate = year < currentYear ? new Date(year, month + 1, -1).getTime() : new Date(year, month).getTime()
// 判断是否存在最大日期
if (maxDate && targetDate > new Date(maxDate).getTime()) return
// 判断是否存在最小日期
if (minDate && targetDate < new Date(minDate).getTime()) return
const currentDate = new Date(currentYear, currentMonth).getTime()
const dir = targetDate > currentDate ? 'next' : 'prev'
const newMonthHTML = this.monthHTML(new Date(year, month))
const prevTranslate = this.monthsTranslate = this.monthsTranslate || 0
if (targetDate > currentDate) {
this.monthsTranslate = this.monthsTranslate - 1
const translate = -(prevTranslate - 1) * 100
const nextMonthTranslate = getTransform(translate, this.isH)
this.$$setData({
months: [months[1], months[2], newMonthHTML],
monthsTranslate: [monthsTranslate[1], monthsTranslate[2], nextMonthTranslate],
})
} else {
this.monthsTranslate = this.monthsTranslate + 1
const translate = -(prevTranslate + 1) * 100
const prevMonthTranslate = getTransform(translate, this.isH)
this.$$setData({
months: [newMonthHTML, months[0], months[1]],
monthsTranslate: [prevMonthTranslate, monthsTranslate[0], monthsTranslate[1]],
})
}
this.onMonthChangeStart(dir)
const transform = getTransform(this.monthsTranslate * 100, this.isH)
const duration = this.data.animate ? .3 : 0
const wrapperTranslate = `transition-duration: ${duration}s; ${transform}`
this.$$setData({
wrapperTranslate,
})
setTimeout(() => this.onMonthChangeEnd(dir, true), duration)
},
/**
* 下一年
*/
nextYear() {
this.setYearMonth(this.data.currentYear + 1)
},
/**
* 上一年
*/
prevYear() {
this.setYearMonth(this.data.currentYear - 1)
},
/**
* 下一月
*/
nextMonth() {
const { months, monthsTranslate, maxDate, currentMonth } = this.data
const nextMonth = parseInt(months[months.length - 1].month, 10)
const nextYear = parseInt(months[months.length - 1].year, 10)
const nextDate = new Date(nextYear, nextMonth)
const nextDateTime = nextDate.getTime()
// 判断是否存在最大日期
if (maxDate && nextDateTime > new Date(maxDate).getTime()) {
return this.resetMonth()
}
this.monthsTranslate = this.monthsTranslate - 1
if (nextMonth === currentMonth) {
const translate = -(this.monthsTranslate) * 100
const nextMonthHTML = this.monthHTML(nextDateTime, 'next')
const nextMonthTranslate = getTransform(translate, this.isH)
const months = [this.data.months[1], this.data.months[2], nextMonthHTML]
this.$$setData({
months,
monthsTranslate: [monthsTranslate[1], monthsTranslate[2], nextMonthTranslate],
})
if (typeof this.fns.onMonthAdd === 'function') {
this.fns.onMonthAdd.call(this, months[months.length - 1])
}
}
this.onMonthChangeStart('next')
const transform = getTransform(this.monthsTranslate * 100, this.isH)
const duration = this.data.animate ? .3 : 0
const wrapperTranslate = `transition-duration: ${duration}s; ${transform}`
this.$$setData({
wrapperTranslate,
})
setTimeout(() => this.onMonthChangeEnd('next'), duration)
},
/**
* 上一月
*/
prevMonth() {
const { months, monthsTranslate, minDate, currentMonth } = this.data
const prevMonth = parseInt(months[0].month, 10)
const prevYear = parseInt(months[0].year, 10)
const prevDate = new Date(prevYear, prevMonth + 1, -1)
const prevDateTime = prevDate.getTime()
// 判断是否存在最小日期
if (minDate && prevDateTime < new Date(minDate).getTime()) {
return this.resetMonth()
}
this.monthsTranslate = this.monthsTranslate + 1
if (prevMonth === currentMonth) {
const translate = -(this.monthsTranslate) * 100
const prevMonthHTML = this.monthHTML(prevDateTime, 'prev')
const prevMonthTranslate = getTransform(translate, this.isH)
const months = [prevMonthHTML, this.data.months[0], this.data.months[1]]
this.$$setData({
months,
monthsTranslate: [prevMonthTranslate, monthsTranslate[0], monthsTranslate[1]],
})
if (typeof this.fns.onMonthAdd === 'function') {
this.fns.onMonthAdd.call(this, months[0])
}
}
this.onMonthChangeStart('prev')
const transform = getTransform(this.monthsTranslate * 100, this.isH)
const duration = this.data.animate ? .3 : 0
const wrapperTranslate = `transition-duration: ${duration}s; ${transform}`
this.$$setData({
wrapperTranslate,
})
setTimeout(() => this.onMonthChangeEnd('prev'), duration)
},
/**
* 月份变化开始时的回调函数
* @param {String} dir 方向
*/
onMonthChangeStart(dir) {
const params = this.updateCurrentMonthYear(dir)
this.$$setData(params)
if (typeof this.fns.onMonthYearChangeStart === 'function') {
this.fns.onMonthYearChangeStart.call(this, params.currentYear, params.currentMonth)
}
},
/**
* 月份变化完成时的回调函数
* @param {String} dir 方向
* @param {Boolean} rebuildBoth 重置
*/
onMonthChangeEnd(dir = 'next', rebuildBoth = false) {
const { currentYear, currentMonth } = this.data
let nextMonthHTML, prevMonthHTML, newMonthHTML, months = [...this.data.months]
if (!rebuildBoth) {
newMonthHTML = this.monthHTML(new Date(currentYear, currentMonth), dir)
if (dir === 'next') {
months = [months[1], months[2], newMonthHTML]
} else if (dir === 'prev') {
months = [newMonthHTML, months[0], months[1]]
}
} else {
prevMonthHTML = this.monthHTML(new Date(currentYear, currentMonth), 'prev')
nextMonthHTML = this.monthHTML(new Date(currentYear, currentMonth), 'next')
months = [prevMonthHTML, months[dir === 'next' ? months.length - 1 : 0], nextMonthHTML]
}
const monthsTranslate = this.setMonthsTranslate(this.monthsTranslate)
this.isRendered = true
this.$$setData({ months, monthsTranslate }).then(() => (this.isRendered = false))
if (typeof this.fns.onMonthAdd === 'function') {
this.fns.onMonthAdd.call(this, dir === 'next' ? months[months.length - 1] : months[0])
}
if (typeof this.fns.onMonthYearChangeEnd === 'function') {
this.fns.onMonthYearChangeEnd.call(this, currentYear, currentMonth)
}
},
/**
* 设置星期
*/
setWeekHeader() {
const { weekHeader, firstDay, dayNamesShort, weekendDays } = this.data
const weeks = []
if (weekHeader) {
for (let i = 0; i < 7; i++) {
const weekDayIndex = (i + firstDay > 6) ? (i - 7 + firstDay) : (i + firstDay)
const dayName = dayNamesShort[weekDayIndex]
const weekend = weekendDays.indexOf(weekDayIndex) >= 0
weeks.push({
weekend,
dayName,
})
}
}
return weeks
},
/**
* 判断日期是否存在
*/
daysInMonth(date) {
const d = new Date(date)
return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate()
},
/**
* 设置月份数据
*/
monthHTML(date, offset) {
date = new Date(date)
let year = date.getFullYear(),
month = date.getMonth(),
time = date.getTime()
const monthHTML = {
year,
month,
time,
items: [],
}
if (offset === 'next') {
if (month === 11) date = new Date(year + 1, 0)
else date = new Date(year, month + 1, 1)
}
if (offset === 'prev') {
if (month === 0) date = new Date(year - 1, 11)
else date = new Date(year, month - 1, 1)
}
if (offset === 'next' || offset === 'prev') {
month = date.getMonth()
year = date.getFullYear()
time = date.getTime()
}
let daysInPrevMonth = this.daysInMonth(new Date(date.getFullYear(), date.getMonth()).getTime() - 10 * 24 * 60 * 60 * 1000),
daysInMonth = this.daysInMonth(date),
firstDayOfMonthIndex = new Date(date.getFullYear(), date.getMonth()).getDay()
if (firstDayOfMonthIndex === 0) firstDayOfMonthIndex = 7
let dayDate, currentValues = [],
i, j,
rows = 6,
cols = 7,
dayIndex = 0 + (this.data.firstDay - 1),
today = new Date().setHours(0, 0, 0, 0),
minDate = this.data.minDate ? new Date(this.data.minDate).getTime() : null,
maxDate = this.data.maxDate ? new Date(this.data.maxDate).getTime() : null
if (this.data.value && this.data.value.length) {
for (let i = 0; i < this.data.value.length; i++) {
currentValues.push(new Date(this.data.value[i]).setHours(0, 0, 0, 0))
}
}
for (let i = 1; i <= rows; i++) {
let rowHTML = []
let row = i
for (let j = 1; j <= cols; j++) {
let col = j
dayIndex++
let dayNumber = dayIndex - firstDayOfMonthIndex
let type = {}
if (dayNumber < 0) {
dayNumber = daysInPrevMonth + dayNumber + 1
type.prev = true
dayDate = new Date(month - 1 < 0 ? year - 1 : year, month - 1 < 0 ? 11 : month - 1, dayNumber).getTime()
} else {
dayNumber = dayNumber + 1
if (dayNumber > daysInMonth) {
dayNumber = dayNumber - daysInMonth
type.next = true
dayDate = new Date(month + 1 > 11 ? year + 1 : year, month + 1 > 11 ? 0 : month + 1, dayNumber).getTime()
} else {
dayDate = new Date(year, month, dayNumber).getTime()
}
}
// Today
if (dayDate === today) type.today = true
// Selected
if (currentValues.indexOf(dayDate) >= 0) type.selected = true
// Weekend
if (this.data.weekendDays.indexOf(col - 1) >= 0) {
type.weekend = true
}
// Disabled
if ((minDate && dayDate < minDate) || (maxDate && dayDate > maxDate)) {
type.disabled = true
}
dayDate = new Date(dayDate)
const dayYear = dayDate.getFullYear()
const dayMonth = dayDate.getMonth()
rowHTML.push({
type,
year: dayYear,
month: dayMonth,
day: dayNumber,
date: `${dayYear}-${dayMonth + 1}-${dayNumber}`,
})
}
monthHTML.year = year
monthHTML.month = month
monthHTML.time = time
monthHTML.items.push(rowHTML)
}
return monthHTML
},
/**
* 设置月份
*/
setMonthsHTML() {
const layoutDate = this.data.value && this.data.value.length ? this.data.value[0] : new Date().setHours(0, 0, 0, 0)
const prevMonthHTML = this.monthHTML(layoutDate, 'prev')
const currentMonthHTML = this.monthHTML(layoutDate)
const nextMonthHTML = this.monthHTML(layoutDate, 'next')
return [prevMonthHTML, currentMonthHTML, nextMonthHTML]
},
/**
* 格式化日期
*/
formatDate(date) {
date = new Date(date)
const year = date.getFullYear()
const month = date.getMonth()
const month1 = month + 1
const day = date.getDate()
const weekDay = date.getDay()
return this.data.dateFormat
.replace(/yyyy/g, year)
.replace(/yy/g, (year + '').substring(2))
.replace(/mm/g, month1 < 10 ? '0' + month1 : month1)
.replace(/m/g, month1)
.replace(/MM/g, this.data.monthNames[month])
.replace(/M/g, this.data.monthNamesShort[month])
.replace(/dd/g, day < 10 ? '0' + day : day)
.replace(/d/g, day)
.replace(/DD/g, this.data.dayNames[weekDay])
.replace(/D/g, this.data.dayNamesShort[weekDay])
},
/**
* 添加选中值
*/
addValue(value) {
if (this.data.multiple) {
let arrValues = this.data.value || []
let inValuesIndex = -1
for (let i = 0; i < arrValues.length; i++) {
if (isSameDate(value, arrValues[i])) {
inValuesIndex = i
}
}
if (inValuesIndex === -1) {
arrValues.push(value)
} else {
arrValues.splice(inValuesIndex, 1)
}
this.setValue(arrValues)
} else {
this.setValue([value])
}
},
/**
* 设置选择值
*/
setValue(value) {
this.$$setData({ value }).then(() => this.updateValue())
},
/**
* 更新日历
*/
updateValue() {
const changedPath = {}
this.data.months.forEach((n, i) => {
n.items.forEach((v, k) => {
v.forEach((p, j) => {
if (p.type.selected) {
changedPath[`months[${i}].items[${k}][${j}].type.selected`] = false
}
})
})
})
for (let ii = 0; ii < this.data.value.length; ii++) {
const valueDate = new Date(this.data.value[ii])
const valueYear = valueDate.getFullYear()
const valueMonth = valueDate.getMonth()
const valueDay = valueDate.getDate()
this.data.months.forEach((n, i) => {
if (n.year === valueYear && n.month === valueMonth) {
n.items.forEach((v, k) => {
v.forEach((p, j) => {
if (p.year === valueYear && p.month === valueMonth && p.day === valueDay) {
changedPath[`months[${i}].items[${k}][${j}].type.selected`] = true
}
})
})
}
})
}
this.$$setData(changedPath)
if (typeof this.fns.onChange === 'function') {
this.fns.onChange.call(this, this.data.value, this.data.value.map((n) => this.formatDate(n)))
}
},
noop() {},
},
})