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.
 

881 lines
28 KiB

import Day from './func/day'
import Week from './func/week'
import Todo from './func/todo'
import WxData from './func/wxData'
import Calendar from './func/render'
import CalendarConfig from './func/config'
import convertSolarLunar from './func/convertSolarLunar'
import {
Logger,
GetDate,
isComponent,
initialTasks,
getCurrentPage,
getComponent,
getDateTimeStamp
} from './func/utils'
let Component = {}
let logger = new Logger()
let getDate = new GetDate()
let dataInstance = null
/**
* 全局赋值正在操作的组件实例,方便读/写各自的 data
* @param {string} componentId 要操作的日历组件ID
*/
function bindCurrentComponent(componentId) {
if (componentId) {
Component = getComponent(componentId)
}
return Component
}
/**
* 获取日历内部数据
* @param {string} key 获取值的键名
* @param {string} componentId 要操作的日历组件ID
*/
function getData(key, componentId) {
bindCurrentComponent(componentId)
dataInstance = new WxData(Component)
return dataInstance.getData(key)
}
/**
* 设置日历内部数据
* @param {object}} data 待设置的数据
* @param {function} callback 设置成功回调函数
*/
function setData(data, callback = () => {
}) {
const dataInstance = new WxData(Component)
return dataInstance.setData(data, callback)
}
const conf = {
/**
* 渲染日历
* @param {number} curYear
* @param {number} curMonth
* @param {number} curDate
*/
renderCalendar(curYear, curMonth, curDate) {
if (isComponent(this)) Component = this
return new Promise((resolve, reject) => {
Calendar(Component)
.renderCalendar(curYear, curMonth, curDate)
.then((info = {}) => {
if (!info.firstRender) {
return resolve({
year: curYear,
month: curMonth,
date: curDate
})
}
mountEventsOnPage(getCurrentPage())
Component.triggerEvent('afterCalendarRender', Component)
Component.firstRender = true
initialTasks.flag = 'finished'
if (initialTasks.tasks.length) {
initialTasks.tasks.shift()()
}
resolve({
year: curYear,
month: curMonth,
date: curDate
})
})
.catch(err => {
reject(err)
})
})
},
/**
* 当改变月份时触发
* @param {object} param
*/
whenChangeDate({curYear, curMonth, newYear, newMonth}) {
Component.triggerEvent('whenChangeMonth', {
current: {
year: curYear,
month: curMonth
},
next: {
year: newYear,
month: newMonth
}
})
},
/**
* 多选
* @param {number} dateIdx 当前选中日期索引值
*/
whenMulitSelect(dateIdx) {
if (isComponent(this)) Component = this
const {calendar = {}} = getData()
const {days, todoLabels} = calendar
const config = CalendarConfig(Component).getCalendarConfig()
let {selectedDay: selectedDays = []} = calendar
const currentDay = days[dateIdx]
if (!currentDay) return
currentDay.choosed = !currentDay.choosed
if (!currentDay.choosed) {
currentDay.cancel = true // 该次点击是否为取消日期操作
const currentDayStr = getDate.toTimeStr(currentDay)
selectedDays = selectedDays.filter(
item => currentDayStr !== getDate.toTimeStr(item)
)
if (todoLabels) {
todoLabels.forEach(item => {
if (currentDayStr === getDate.toTimeStr(item)) {
currentDay.showTodoLabel = true
}
})
}
} else {
currentDay.cancel = false
const {showLabelAlways} = getData('calendar')
if (showLabelAlways && currentDay.showTodoLabel) {
currentDay.showTodoLabel = true
} else {
currentDay.showTodoLabel = false
}
if (!config.takeoverTap) {
selectedDays.push(currentDay)
}
}
if (config.takeoverTap) {
return Component.triggerEvent('onTapDay', currentDay)
}
setData({
'calendar.days': days,
'calendar.selectedDay': selectedDays
})
conf.afterTapDay(currentDay, selectedDays)
},
/**
* 单选
* @param {number} dateIdx 当前选中日期索引值
*/
whenSingleSelect(dateIdx) {
if (isComponent(this)) Component = this
const {calendar = {}} = getData()
const {days, selectedDay: selectedDays = [], todoLabels} = calendar
let shouldMarkerTodoDay = []
const currentDay = days[dateIdx]
if (!currentDay) return
const preSelectedDate = [...selectedDays].pop() || {}
const {month: dMonth, year: dYear} = days[0] || {}
const config = CalendarConfig(Component).getCalendarConfig()
if (config.takeoverTap) {
return Component.triggerEvent('onTapDay', currentDay)
}
conf.afterTapDay(currentDay)
if (!config.inverse && preSelectedDate.day === currentDay.day) return
days.forEach((item, idx) => {
if (+item.day === +preSelectedDate.day) days[idx].choosed = false
})
if (todoLabels) {
// 筛选当月待办事项的日期
shouldMarkerTodoDay = todoLabels.filter(
item => +item.year === dYear && +item.month === dMonth
)
}
Todo(Component).showTodoLabels(shouldMarkerTodoDay, days, selectedDays)
const tmp = {
'calendar.days': days
}
if (preSelectedDate.day !== currentDay.day) {
preSelectedDate.choosed = false
currentDay.choosed = true
if (!calendar.showLabelAlways || !currentDay.showTodoLabel) {
currentDay.showTodoLabel = false
}
tmp['calendar.selectedDay'] = [currentDay]
} else if (config.inverse) {
if (currentDay.choosed) {
if (currentDay.showTodoLabel && calendar.showLabelAlways) {
currentDay.showTodoLabel = true
} else {
currentDay.showTodoLabel = false
}
}
tmp['calendar.selectedDay'] = []
}
if (config.weekMode) {
tmp['calendar.curYear'] = currentDay.year
tmp['calendar.curMonth'] = currentDay.month
}
setData(tmp)
},
gotoSetContinuousDates(start, end) {
return chooseDateArea([
`${getDate.toTimeStr(start)}`,
`${getDate.toTimeStr(end)}`
])
},
timeRangeHelper(currentDate, selectedDay) {
const currentDateTimestamp = getDateTimeStamp(currentDate)
const startDate = selectedDay[0]
let endDate
let endDateTimestamp
let selectedLen = selectedDay.length
if (selectedLen > 1) {
endDate = selectedDay[selectedLen - 1]
endDateTimestamp = getDateTimeStamp(endDate)
}
const startTimestamp = getDateTimeStamp(startDate)
return {
endDate,
startDate,
currentDateTimestamp,
endDateTimestamp,
startTimestamp
}
},
/**
* 计算连续日期选择的开始及结束日期
* @param {object} currentDate 当前选择日期
* @param {array} selectedDay 已选择的的日期
*/
calculateDateRange(currentDate, selectedDay) {
const {
endDate,
startDate,
currentDateTimestamp,
endDateTimestamp,
startTimestamp
} = this.timeRangeHelper(currentDate, selectedDay)
let range = []
let selectedLen = selectedDay.length
const isWantToChooseOneDate = selectedDay.filter(
item => getDate.toTimeStr(item) === getDate.toTimeStr(currentDate)
)
if (selectedLen === 2 && isWantToChooseOneDate.length) {
range = [currentDate, currentDate]
return range
}
if (
currentDateTimestamp >= startTimestamp &&
endDateTimestamp &&
currentDateTimestamp <= endDateTimestamp
) {
const currentDateIdxInChoosedDateArea = selectedDay.findIndex(
item => getDate.toTimeStr(item) === getDate.toTimeStr(currentDate)
)
if (selectedLen / 2 > currentDateIdxInChoosedDateArea) {
range = [currentDate, endDate]
} else {
range = [startDate, currentDate]
}
} else if (currentDateTimestamp < startTimestamp) {
range = [currentDate, endDate]
} else if (currentDateTimestamp > startTimestamp) {
range = [startDate, currentDate]
}
return range
},
chooseAreaWhenExistArea(currentDate, selectedDay) {
return new Promise((resolve, reject) => {
const range = conf.calculateDateRange(
currentDate,
getDate.sortDates(selectedDay)
)
conf
.gotoSetContinuousDates(...range)
.then(data => {
resolve(data)
conf.afterTapDay(currentDate)
})
.catch(err => {
reject(err)
conf.afterTapDay(currentDate)
})
})
},
chooseAreaWhenHasOneDate(currentDate, selectedDay, lastChoosedDate) {
return new Promise((resolve, reject) => {
const startDate = lastChoosedDate || selectedDay[0]
let range = [startDate, currentDate]
const currentDateTimestamp = getDateTimeStamp(currentDate)
const lastChoosedDateTimestamp = getDateTimeStamp(startDate)
if (lastChoosedDateTimestamp > currentDateTimestamp) {
range = [currentDate, startDate]
}
conf
.gotoSetContinuousDates(...range)
.then(data => {
resolve(data)
conf.afterTapDay(currentDate)
})
.catch(err => {
reject(err)
conf.afterTapDay(currentDate)
})
})
},
/**
* 日期范围选择模式
* @param {number} dateIdx 当前选中日期索引值
*/
whenChooseArea(dateIdx) {
return new Promise((resolve, reject) => {
if (isComponent(this)) Component = this
if (Component.weekMode) return
const {days = [], selectedDay, lastChoosedDate} = getData('calendar')
const currentDate = days[dateIdx]
if (currentDate.disable) return
const config = CalendarConfig(Component).getCalendarConfig()
if (config.takeoverTap) {
return Component.triggerEvent('onTapDay', currentDate)
}
if (selectedDay && selectedDay.length > 1) {
conf
.chooseAreaWhenExistArea(currentDate, selectedDay)
.then(dates => {
resolve(dates)
})
.catch(err => {
reject(err)
})
} else if (lastChoosedDate || (selectedDay && selectedDay.length === 1)) {
conf
.chooseAreaWhenHasOneDate(currentDate, selectedDay, lastChoosedDate)
.then(dates => {
resolve(dates)
})
.catch(err => {
reject(err)
})
} else {
days.forEach(date => {
if (+date.day === +currentDate.day) {
date.choosed = true
} else {
date.choosed = false
}
})
const dataInstance = new WxData(Component)
dataInstance.setData({
'calendar.days': [...days],
'calendar.lastChoosedDate': currentDate
})
}
})
},
/**
* 点击日期后触发事件
* @param {object} currentSelected 当前选择的日期
* @param {array} selectedDates 多选状态下选中的日期
*/
afterTapDay(currentSelected, selectedDates) {
const config = CalendarConfig(Component).getCalendarConfig()
const {multi} = config
if (!multi) {
Component.triggerEvent('afterTapDay', currentSelected)
} else {
Component.triggerEvent('afterTapDay', {
currentSelected,
selectedDates
})
}
},
/**
* 跳转至今天
*/
jumpToToday() {
return new Promise((resolve, reject) => {
const {year, month, date} = getDate.todayDate()
const timestamp = getDate.todayTimestamp()
const config = CalendarConfig(Component).getCalendarConfig()
setData({
'calendar.curYear': year,
'calendar.curMonth': month,
'calendar.selectedDay': [
{
year: year,
day: date,
month: month,
choosed: true,
lunar: config.showLunar
? convertSolarLunar.solar2lunar(year, month, date)
: null
}
],
'calendar.todayTimestamp': timestamp
})
conf
.renderCalendar(year, month, date)
.then(() => {
resolve({year, month, date})
})
.catch(() => {
reject('jump failed')
})
})
}
}
export const whenChangeDate = conf.whenChangeDate
export const renderCalendar = conf.renderCalendar
export const whenSingleSelect = conf.whenSingleSelect
export const whenChooseArea = conf.whenChooseArea
export const whenMulitSelect = conf.whenMulitSelect
export const calculatePrevWeekDays = conf.calculatePrevWeekDays
export const calculateNextWeekDays = conf.calculateNextWeekDays
/**
* 获取当前年月
* @param {string} componentId 要操作的日历组件ID
*/
export function getCurrentYM(componentId) {
bindCurrentComponent(componentId)
return {
year: getData('calendar.curYear'),
month: getData('calendar.curMonth')
}
}
/**
* 获取已选择的日期
* @param {object } options 日期配置选项 {lunar} 是否返回农历信息
* @param {string} componentId 要操作的日历组件ID
*/
export function getSelectedDay(options = {}, componentId) {
bindCurrentComponent(componentId)
const config = getCalendarConfig()
const dates = getData('calendar.selectedDay') || []
if (options.lunar && !config.showLunar) {
const datesWithLunar = getDate.convertLunar(dates)
return datesWithLunar
} else {
return dates
}
}
/**
* 取消选中日期
* @param {array} dates 需要取消的日期,不传则取消所有已选择的日期
* @param {string} componentId 要操作的日历组件ID
*/
export function cancelSelectedDates(dates, componentId) {
bindCurrentComponent(componentId)
const {days = [], selectedDay = []} = getData('calendar') || {}
if (!dates || !dates.length) {
days.forEach(item => {
item.choosed = false
})
setData({
'calendar.days': days,
'calendar.selectedDay': []
})
} else {
const cancelDatesStr = dates.map(
date => `${+date.year}-${+date.month}-${+date.day}`
)
const filterSelectedDates = selectedDay.filter(
date =>
!cancelDatesStr.includes(`${+date.year}-${+date.month}-${+date.day}`)
)
days.forEach(date => {
if (
cancelDatesStr.includes(`${+date.year}-${+date.month}-${+date.day}`)
) {
date.choosed = false
}
})
setData({
'calendar.days': days,
'calendar.selectedDay': filterSelectedDates
})
}
}
/**
* 周视图跳转
* @param {object} date info
* @param {boolean} disableSelected 跳转时是否需要选中,周视图切换调用该方法,如未选择日期时不选中日期
*/
function jumpWhenWeekMode({year, month, day}, disableSelected) {
return new Promise((resolve, reject) => {
Week(Component)
.jump(
{
year: +year,
month: +month,
day: +day
},
disableSelected
)
.then(date => {
resolve(date)
Component.triggerEvent('afterCalendarRender', Component)
})
.catch(err => {
reject(err)
Component.triggerEvent('afterCalendarRender', Component)
})
})
}
/**
* 月视图跳转
* @param {object} date info
*/
function jumpWhenNormalMode({year, month, day}) {
return new Promise((resolve, reject) => {
if (typeof +year !== 'number' || typeof +month !== 'number') {
return logger.warn('jump 函数年月日参数必须为数字')
}
const timestamp = getDate.todayTimestamp()
let tmp = {
'calendar.curYear': +year,
'calendar.curMonth': +month,
'calendar.todayTimestamp': timestamp
}
setData(tmp, () => {
conf
.renderCalendar(+year, +month, +day)
.then(date => {
resolve(date)
})
.catch(err => {
reject(err)
})
})
})
}
/**
* 跳转至指定日期
* @param {number} year
* @param {number} month
* @param {number} day
* @param {string} componentId 要操作的日历组件ID
*/
export function jump(year, month, day, componentId) {
return new Promise((resolve, reject) => {
bindCurrentComponent(componentId)
const {selectedDay = []} = getData('calendar') || {}
const {weekMode} = getData('calendarConfig') || {}
const {year: y, month: m, day: d} = selectedDay[0] || {}
if (+y === +year && +m === +month && +d === +day) {
return
}
if (weekMode) {
let disableSelected = false
if (!year || !month || !day) {
const today = getDate.todayDate()
year = today.year
month = today.month
day = today.date
disableSelected = true
}
jumpWhenWeekMode({year, month, day}, disableSelected)
.then(date => {
resolve(date)
})
.catch(err => {
reject(err)
})
mountEventsOnPage(getCurrentPage())
return
}
if (year && month) {
jumpWhenNormalMode({year, month, day})
.then(date => {
resolve(date)
})
.catch(err => {
reject(err)
})
} else {
conf
.jumpToToday()
.then(date => {
resolve(date)
})
.catch(err => {
reject(err)
})
}
})
}
/**
* 设置待办事项日期标记
* @param {object} todos 待办事项配置
* @param {string} [todos.pos] 标记显示位置,默认值'bottom' ['bottom', 'top']
* @param {string} [todos.dotColor] 标记点颜色,backgroundColor 支持的值都行
* @param {object[]} [todos.days] 需要标记的所有日期,如:[{year: 2015, month: 5, day: 12}],其中年月日字段必填
* @param {string} componentId 要操作的日历组件ID
*/
export function setTodoLabels(todos, componentId) {
bindCurrentComponent(componentId)
Todo(Component).setTodoLabels(todos)
}
/**
* 删除指定日期待办事项
* @param {array} todos 需要删除的待办日期数组
* @param {string} componentId 要操作的日历组件ID
*/
export function deleteTodoLabels(todos, componentId) {
bindCurrentComponent(componentId)
Todo(Component).deleteTodoLabels(todos)
}
/**
* 清空所有待办事项
* @param {string} componentId 要操作的日历组件ID
*/
export function clearTodoLabels(componentId) {
bindCurrentComponent(componentId)
Todo(Component).clearTodoLabels()
}
/**
* 获取所有待办事项
* @param {object } options 日期配置选项 {lunar} 是否返回农历信息
* @param {string} componentId 要操作的日历组件ID
*/
export function getTodoLabels(options = {}, componentId) {
bindCurrentComponent(componentId)
const config = getCalendarConfig()
const todoDates = Todo(Component).getTodoLabels() || []
if (options.lunar && !config.showLunar) {
const todoDatesWithLunar = getDate.convertLunar(todoDates)
return todoDatesWithLunar
} else {
return todoDates
}
}
/**
* 禁用指定日期
* @param {array} days 日期
* @param {number} [days.year]
* @param {number} [days.month]
* @param {number} [days.day]
* @param {string} componentId 要操作的日历组件ID
*/
export function disableDay(days = [], componentId) {
bindCurrentComponent(componentId)
Day(Component).disableDays(days)
}
/**
* 指定可选日期范围
* @param {array} area 日期访问数组
* @param {string} componentId 要操作的日历组件ID
*/
export function enableArea(area = [], componentId) {
bindCurrentComponent(componentId)
Day(Component).enableArea(area)
}
/**
* 指定特定日期可选
* @param {array} days 指定日期数组
* @param {string} componentId 要操作的日历组件ID
*/
export function enableDays(days = [], componentId) {
bindCurrentComponent(componentId)
Day(Component).enableDays(days)
}
/**
* 设置选中日期(多选模式下)
* @param {array} selected 需选中日期
* @param {string} componentId 要操作的日历组件ID
*/
export function setSelectedDays(selected, componentId) {
bindCurrentComponent(componentId)
Day(Component).setSelectedDays(selected)
}
/**
* 获取当前日历配置
* @param {string} componentId 要操作的日历组件ID
*/
export function getCalendarConfig(componentId) {
bindCurrentComponent(componentId)
return CalendarConfig(Component).getCalendarConfig()
}
/**
* 设置日历配置
* @param {object} config
* @param {string} componentId 要操作的日历组件ID
*/
export function setCalendarConfig(config, componentId) {
bindCurrentComponent(componentId)
if (!config || Object.keys(config).length === 0) {
return logger.warn('setCalendarConfig 参数必须为非空对象')
}
const existConfig = getCalendarConfig()
return new Promise((resolve, reject) => {
CalendarConfig(Component)
.setCalendarConfig(config)
.then(conf => {
resolve(conf)
const {date, type} = existConfig.disableMode || {}
const {_date, _type} = config.disableMode || {}
if (type !== _type || date !== _date) {
const {year, month} = getCurrentYM()
jump(year, month)
}
})
.catch(err => {
reject(err)
})
})
}
/**
* 获取当前日历面板日期
* @param {object } options 日期配置选项 {lunar} 是否返回农历信息
* @param {string} componentId 要操作的日历组件ID
*/
export function getCalendarDates(options = {}, componentId) {
bindCurrentComponent(componentId)
const config = getCalendarConfig()
const dates = getData('calendar.days', componentId) || []
if (options.lunar && !config.showLunar) {
const datesWithLunar = getDate.convertLunar(dates)
return datesWithLunar
} else {
return dates
}
}
/**
* 选择连续日期范围
* @param {string} componentId 要操作的日历组件ID
*/
export function chooseDateArea(dateArea, componentId) {
bindCurrentComponent(componentId)
return Day(Component).chooseArea(dateArea)
}
/**
* 设置指定日期样式
* @param {array} dates 待设置特殊样式的日期
* @param {string} componentId 要操作的日历组件ID
*/
export function setDateStyle(dates, componentId) {
if (!dates) return
bindCurrentComponent(componentId)
Day(Component).setDateStyle(dates)
}
/**
* 切换周月视图
* 切换视图时可传入指定日期,如: {year: 2019, month: 1, day: 3}
* args[0] view 视图模式[week, month]
* args[1]|args[2]为day object或者 componentId
*/
export function switchView(...args) {
return new Promise((resolve, reject) => {
const view = args[0]
if (!args[1]) {
return Week(Component)
.switchWeek(view)
.then(resolve)
.catch(reject)
}
if (typeof args[1] === 'string') {
bindCurrentComponent(args[1], this)
Week(Component)
.switchWeek(view, args[2])
.then(resolve)
.catch(reject)
} else if (typeof args[1] === 'object') {
if (typeof args[2] === 'string') {
bindCurrentComponent(args[1], this)
}
Week(Component)
.switchWeek(view, args[1])
.then(resolve)
.catch(reject)
}
})
}
/**
* 绑定日历事件至当前页面实例
* @param {object} page 当前页面实例
*/
function mountEventsOnPage(page) {
page.calendar = {
jump,
switchView,
disableDay,
enableArea,
enableDays,
chooseDateArea,
getCurrentYM,
getSelectedDay,
cancelSelectedDates,
setDateStyle,
setTodoLabels,
getTodoLabels,
deleteTodoLabels,
clearTodoLabels,
setSelectedDays,
getCalendarConfig,
setCalendarConfig,
getCalendarDates
}
}
function setWeekHeader(firstDayOfWeek) {
let weeksCh = ['日', '一', '二', '三', '四', '五', '六']
if (firstDayOfWeek === 'Mon') {
weeksCh = ['一', '二', '三', '四', '五', '六', '日']
}
setData({
'calendar.weeksCh': weeksCh
})
}
function autoSelectDay(defaultDay) {
Component.firstRenderWeekMode = true
if (defaultDay && typeof defaultDay === 'string') {
const day = defaultDay.split('-')
if (day.length < 3) {
return logger.warn('配置 jumpTo 格式应为: 2018-4-2 或 2018-04-02')
}
jump(+day[0], +day[1], +day[2])
} else {
if (!defaultDay) {
Component.config.noDefault = true
setData({
'config.noDefault': true
})
}
jump()
}
}
function init(component, config) {
initialTasks.flag = 'process'
Component = component
Component.config = config
setWeekHeader(config.firstDayOfWeek)
autoSelectDay(config.defaultDay)
logger.tips(
'使用中若遇问题请反馈至 https://github.com/treadpit/wx_calendar/issues ✍'
)
}
export default (component, config = {}) => {
if (initialTasks.flag === 'process') {
return initialTasks.tasks.push(function () {
init(component, config)
})
}
init(component, config)
}