magiccube-vue3
Version:
vue3-js版组件库
248 lines (217 loc) • 8.25 kB
JavaScript
import { computed } from 'vue'
import * as utils from '../../utils/common'
// 列表中周期表头
const WEEK_TITLE = [
{ key: 'week', string: '日', value: 0 },
{ key: 'week', string: '一', value: 1 },
{ key: 'week', string: '二', value: 2 },
{ key: 'week', string: '三', value: 3 },
{ key: 'week', string: '四', value: 4 },
{ key: 'week', string: '五', value: 5 },
{ key: 'week', string: '六', value: 6 },
]
const DAY_TIME = 86400000
/**
* 日历控件 核心模块
* 日历列表
* 根据传入的日期进行日历渲染
* 数据格式为Array (单选、多选、range都是用数组表示)
* 点击后将选中的数据emit
*/
const Calendar = {
name: 'McCalendar',
props: {
modelValue: Array,
/** 支持两种类型 default和range,其中default包含单选和多选 */
type: String,
/**
* 日历列表视图显示的月份
* 如果不传 则为当前月
*/
viewDate: String,
multi: Boolean,
/** 设置可选择的日历范围 可单独设置 */
enableStart: String,
enableEnd: String,
},
emits: ['change', 'update:modelValue'],
setup(props, { emit }) {
const model = computed({
get() {
return props.modelValue || []
},
set(value) {
emit('update:modelValue', value)
}
})
// 获取日历列表中 该月的 “天”元素的属性
const getAllDate = (date = '', selected = [], enStart, enEnd) => {
const { year, month } = getDate(date)
const { start, end, size } = getDaysInMonth(year, month)
const fmtSelected = selected.map(n => utils.dateFormat(n, 'YYYY/MM/DD'))
const today = utils.dateFormat(new Date(), 'YYYY/MM/DD')
const arr = []
/**
* 设置可选择日期范围
*/
const _s = enStart ? new Date(enStart).getTime() : 0
const _e = enEnd ? new Date(enEnd).getTime() : 99999999999999999999n
/**
* 获取 range 状态下 所包含的日期状态
*/
const selected_1 = fmtSelected[0] ? new Date(fmtSelected[0]).getTime() : ''
const selected_2 = fmtSelected[1] ? new Date(fmtSelected[1]).getTime() : ''
for (let i = 0; i < size; i++) {
const d = new Date(start + DAY_TIME * i)
const _t = d.getTime()
const isDisabled = _t < _s || _t > _e
const isInclude = getInclude(selected_1, selected_2, _t)
arr.push({
isDisabled,
isInclude,
string: d.getDate(),
value: _t,
week: d.getDay(),
isChecked: getSelectStatus(fmtSelected, d),
isRangeStart: getSelectStatus([fmtSelected[0]], d),
isRangeEnd: getSelectStatus([fmtSelected[1]], d),
isToday: today === utils.dateFormat(d, 'YYYY/MM/DD'),
})
}
return arr
}
// 分解 字符串 日期值 -> 日期格式
const getDate = (date = '') => {
const _d = date ? new Date(date) : new Date()
const year = _d.getFullYear()
const month = _d.getMonth() + 1
const day = _d.getDate()
const week = _d.getDay()
return {
year, month, day, week
}
}
// 计算一月中有多少天
const getDaysInMonth = (year, month) => {
const _d = new Date(`${year}/${month}/1`)
const start = _d.getTime()
_d.setMonth(month)
const end = _d.getTime()
const diff = end - start
return { start, end, size: diff / DAY_TIME }
}
// range模式中 判断是否在选中范围内
const getInclude = (selected_1, selected_2, value) => {
if (selected_1 && selected_2) {
return value >= selected_1 && value <= selected_2
} else {
return false
}
}
// 获取 “天” 的选中状态
const getSelectStatus = (selected, item) => {
return selected.some(n => n === utils.dateFormat(item, 'YYYY/MM/DD'))
}
// 日历开始(上月)补充元素
const getFillLastDate = (days = []) => {
if (!days?.length) return []
const first = days[0]
const times = first.week
const arr = []
for (let i = 1; i <= times; i++) {
const d = new Date(first.value - DAY_TIME * i)
arr.push({
string: d.getDate(),
value: d.getTime(),
isDisabled: true
})
}
return arr.reverse()
}
// 日历尾部(下月)补充元素
const getFillNextDate = (days = []) => {
if (!days?.length) return []
const last = days[days.length - 1]
const times = 6 - last.week
const arr = []
for (let i = 1; i <= times; i++) {
const d = new Date(last.value + DAY_TIME * i)
arr.push({
string: d.getDate(),
value: d.getTime(),
isDisabled: true
})
}
return arr
}
// 获取日历列表(全部)
const days = computed(() => {
/** 本月日期 */
const current = getAllDate(props.viewDate, model.value, props.enableStart, props.enableEnd)
// /** 月初填充 */
const fillLast = getFillLastDate(current)
// /** 月末填充 */
const fillNext = getFillNextDate(current)
return [...WEEK_TITLE, ...fillLast, ...current, ...fillNext]
})
const handleClick = (e, item) => {
e.stopPropagation()
if (item.isDisabled) return
let _arr = model.value
const str = utils.dateFormat(item.value, 'YYYY/MM/DD')
if (props.type === 'range') {
switch (_arr.length) {
case 2: {
_arr = [str]
break
}
case 1: {
_arr.push(str)
emit('change', _arr)
break
}
default: {
_arr.push(str)
break
}
}
} else if (props.multi) {
const idx = _arr.findIndex(n => n === str)
if (idx > -1) {
_arr.splice(idx, 1)
} else {
_arr.push(str)
}
} else {
_arr = [str]
emit('change', _arr)
}
model.value = _arr
}
return () => (
<div class="mc-date__calendar">
{
days.value.map((item) => (
<div class={[
'mc-date__calendar--cell',
{
active: item.isChecked,
today: item.isToday,
disabled: item.isDisabled,
include: props.type === 'range' && item.isInclude,
start: props.type === 'range' && item.isRangeStart,
end: props.type === 'range' && item.isRangeEnd,
}
]}
onClick={(e) => handleClick(e, item)}>
<span>
{item.string}
</span>
</div>
))
}
</div>
)
}
}
export { Calendar, Calendar as default }