This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
import type { ExtractPropTypes } from 'vue'
import { CHOOSE_EVENT, SELECT_EVENT } from '../_constants'
import { commonProps, getDay, makeNumberProp, makeStringProp, truthProp } from '../_utils'
export const calendaritemProps = {
...commonProps,
/**
* @description 是否可见
*/
visible: Boolean,
/**
* @description 类型,日期单选 `one`,区间选择 `range`,日期多选 `multiple`,周选择 `week`
*/
type: makeStringProp<'one' | 'range' | 'multiple' | 'week'>('one'),
/**
* @description 是否弹窗状态展示
*/
poppable: truthProp,
/**
* @description 自动回填
*/
isAutoBackFill: Boolean,
/**
* @description 显示标题
*/
title: makeStringProp('日期选择'),
/**
* @description 默认值,单个日期选择为 `string`,其他为 `string[]`
*/
defaultValue: {
type: [String, Array],
},
/**
* @description 开始日期
*/
startDate: makeStringProp(getDay(0)),
/**
* @description 结束日期
*/
endDate: makeStringProp(getDay(365)),
/**
* @description 范围选择,开始信息文案
*/
startText: makeStringProp('开始'),
/**
* @description 范围选择,结束信息文案
*/
endText: makeStringProp('结束'),
/**
* @description 底部确认按钮文案
*/
confirmText: makeStringProp('确认'),
/**
* @description 是否展示今天标记
*/
showToday: truthProp,
/**
* @description 是否在展示日历标题
*/
showTitle: truthProp,
/**
* @description 是否展示日期标题
*/
showSubTitle: truthProp,
/**
* @description 是否启动滚动动画
*/
toDateAnimation: truthProp,
/**
* @description 设置周起始日
*/
firstDayOfWeek: makeNumberProp(0),
/**
* @description 一个用来判断该日期是否被禁用的函数,接受一个 `年 - 月 - 日` 作为参数。 应该返回一个 Boolean 值。
* @default undefined
*/
disabledDate: Function,
/**
* @description 是否使用 footer 插槽,如果使用,此值必须为 true
*/
footerSlot: Boolean,
/**
* @description 是否使用 btn 插槽,如果使用,此值必须为 true
*/
btnSlot: Boolean,
}
export type CalendarItemProps = ExtractPropTypes<typeof calendaritemProps>
/* eslint-disable unused-imports/no-unused-vars */
export const calendaritemEmits = {
[CHOOSE_EVENT]: (value: string | object) => true,
[SELECT_EVENT]: (value: any) => true,
update: () => true,
close: () => true,
}
/* eslint-enable unused-imports/no-unused-vars */
export type CalendarItemEmits = typeof calendaritemEmits

View File

@@ -0,0 +1,860 @@
<script lang="ts" setup>
import type { ScrollViewOnScrollEvent } from '@uni-helper/uni-app-types'
import { computed, defineComponent, onMounted, reactive, ref, useSlots, watch } from 'vue'
import { CHOOSE_EVENT, PREFIX, SELECT_EVENT } from '../_constants'
import {
compareDate,
date2Str,
formatResultDate,
getDay,
getMainClass,
getMonthDays,
getMonthPreDay,
getMonthWeek,
getNumTwoBit,
getWeekDate,
getWhatDay,
getYearWeek,
isEqual,
isH5,
} from '../_utils'
import requestAniFrame from '../_utils/raf'
import { useTranslate } from '../../locale'
import { calendaritemEmits, calendaritemProps } from './calendaritem'
import type { CalendarDateProp, CalendarTaroState, Day, MonthInfo, StringArr } from './types'
const props = defineProps(calendaritemProps)
const emit = defineEmits(calendaritemEmits)
const slots = useSlots()
const componentName = `${PREFIX}-calendar-item`
const { translate } = useTranslate(componentName)
const state: CalendarTaroState = reactive({
yearMonthTitle: '',
defaultRange: [],
containerHeight: '100%',
currDate: '',
propStartDate: '',
propEndDate: '',
unLoadPrev: false,
touchParams: {
startY: 0,
endY: 0,
startTime: 0,
endTime: 0,
lastY: 0,
lastTime: 0,
},
transformY: 0,
translateY: 0,
scrollDistance: 0,
defaultData: [],
chooseData: [],
monthsData: [],
dayPrefix: 'nut-calendar__day',
startData: '',
endData: '',
isRange: props.type === 'range',
timer: 0,
currentIndex: 0,
avgHeight: 0,
scrollTop: 0,
monthsNum: 0,
})
const classes = computed(() => {
return getMainClass(props, componentName, {
'nut-calendar--nopop': !props.poppable,
'nut-calendar--nofooter': props.isAutoBackFill,
})
})
// 新增:自定义周起始日
const weekdays = (translate('weekdays') as any).map((day: string, index: number) => ({
day,
weekend: index === 0 || index === 6,
}))
const weeks = ref([...weekdays.slice(props.firstDayOfWeek, 7), ...weekdays.slice(0, props.firstDayOfWeek)])
const months = ref<HTMLElement | null>(null)
const scalePx = ref(2)
const viewHeight = ref(0)
const compConthsData = computed(() => {
return state.monthsData.slice(state.defaultRange[0], state.defaultRange[1])
})
const scrollWithAnimation = ref(false)
// 日期转化成数组
function splitDate(date: string) {
return date.split('-')
}
// 判断是否为开始时间
function isStart(currDate: string) {
return isEqual(state.currDate[0], currDate)
}
// 判断是否为结束时间
function isEnd(currDate: string) {
return isEqual(state.currDate[1], currDate)
}
function isMultiple(currDate: string) {
if (state.currDate?.length > 0) {
return (state.currDate as StringArr)?.some((item: string) => {
return isEqual(item, currDate)
})
}
else {
return false
}
}
// 获取当前数据
function getCurrDate(day: Day, month: MonthInfo) {
return `${month.curData[0]}-${month.curData[1]}-${getNumTwoBit(+day.day)}`
}
// 获取样式
function getClass(day: Day, month: MonthInfo, index?: number) {
const res = []
if (
typeof index === 'number'
&& ((index + 1 + props.firstDayOfWeek) % 7 === 0 || (index + props.firstDayOfWeek) % 7 === 0)
) {
res.push('weekend')
}
const currDate = getCurrDate(day, month)
const { type } = props
if (day.type === 'curr') {
if (
isEqual(state.currDate as string, currDate)
|| ((type === 'range' || type === 'week') && (isStart(currDate) || isEnd(currDate)))
|| (type === 'multiple' && isMultiple(currDate))
) {
res.push(`${state.dayPrefix}--active`)
}
else if (
(state.propStartDate && compareDate(currDate, state.propStartDate))
|| (state.propEndDate && compareDate(state.propEndDate, currDate))
|| (props.disabledDate && props.disabledDate(currDate))
) {
res.push(`${state.dayPrefix}--disabled`)
}
else if (
(type === 'range' || type === 'week')
&& Array.isArray(state.currDate)
&& Object.values(state.currDate).length === 2
&& compareDate(state.currDate[0], currDate)
&& compareDate(currDate, state.currDate[1])
) {
res.push(`${state.dayPrefix}--choose`)
}
}
else {
res.push(`${state.dayPrefix}--disabled`)
}
return res
}
// 确认选择时触发
function confirm() {
const { type } = props
if ((type === 'range' && state.chooseData.length === 2) || type !== 'range') {
let selectData: any = state.chooseData.slice(0)
if (type === 'week') {
selectData = {
weekDate: [handleWeekDate(state.chooseData[0] as string[]), handleWeekDate(state.chooseData[1] as string[])],
}
}
emit(CHOOSE_EVENT, selectData)
if (props.poppable)
emit('update')
}
}
// 选中数据
function chooseDay(day: Day, month: MonthInfo, isFirst = false) {
if (!getClass(day, month).includes(`${state.dayPrefix}--disabled`)) {
const { type } = props
const [y, m] = month.curData
const days = [...month.curData]
days[2] = getNumTwoBit(Number(day.day))
days[3] = `${days[0]}-${days[1]}-${days[2]}`
days[4] = getWhatDay(+days[0], +days[1], +days[2])
if (type === 'multiple') {
if (state.currDate?.length > 0) {
let hasIndex: number | undefined;
(state.currDate as StringArr)?.forEach((item: string, index: number) => {
if (item === days[3])
hasIndex = index
})
if (isFirst) {
state.chooseData.push([...days])
}
else {
if (hasIndex !== undefined) {
(state.currDate as StringArr).splice(hasIndex, 1)
state.chooseData.splice(hasIndex, 1)
}
else {
(state.currDate as StringArr).push(days[3])
state.chooseData.push([...days])
}
}
}
else {
state.currDate = [days[3]]
state.chooseData = [[...days]]
}
}
else if (type === 'range') {
const curDataLength = Object.values(state.currDate).length
if (curDataLength === 2 || curDataLength === 0) {
state.currDate = [days[3]]
}
else {
if (compareDate(state.currDate[0], days[3]))
Array.isArray(state.currDate) && state.currDate.push(days[3])
else
Array.isArray(state.currDate) && state.currDate.unshift(days[3])
}
if (state.chooseData.length === 2 || !state.chooseData.length) {
state.chooseData = [[...days]]
}
else {
if (compareDate(state.chooseData[0][3], days[3]))
state.chooseData = [...state.chooseData, [...days]]
else
state.chooseData = [[...days], ...state.chooseData]
}
}
else if (type === 'week') {
const weekArr = getWeekDate(y, m, day.day, props.firstDayOfWeek)
if (state.propStartDate && compareDate(weekArr[0], state.propStartDate))
weekArr.splice(0, 1, state.propStartDate)
if (state.propEndDate && compareDate(state.propEndDate, weekArr[1]))
weekArr.splice(1, 1, state.propEndDate)
state.currDate = weekArr
state.chooseData = [formatResultDate(weekArr[0]), formatResultDate(weekArr[1])]
}
else {
state.currDate = days[3]
state.chooseData = [...days]
}
if (!isFirst) {
let selectData: any = state.chooseData
if (type === 'week') {
selectData = {
weekDate: [
handleWeekDate(state.chooseData[0] as string[]),
handleWeekDate(state.chooseData[1] as string[]),
],
}
}
// 点击日期 触发
emit(SELECT_EVENT, selectData)
if (props.isAutoBackFill || !props.poppable)
confirm()
}
}
}
function handleWeekDate(weekDate: string[]) {
const [y, m, d] = weekDate
return {
date: weekDate,
monthWeekNum: getMonthWeek(y, m, d, props.firstDayOfWeek),
yearWeekNum: getYearWeek(y, m, d),
}
}
// 获取当前月数据
function getCurrData(type: string) {
const monthData = type === 'prev' ? state.monthsData[0] : state.monthsData[state.monthsData.length - 1]
let year = Number.parseInt(monthData.curData[0])
let month = Number.parseInt(monthData.curData[1].toString().replace(/^0/, ''))
switch (type) {
case 'prev':
month === 1 && (year -= 1)
month = month === 1 ? 12 : --month
break
case 'next':
month === 12 && (year += 1)
month = month === 12 ? 1 : ++month
break
}
return [`${year}`, getNumTwoBit(month), `${getMonthDays(String(year), String(month))}`]
}
// 获取日期状态
function getDaysStatus(days: number, type: string, dateInfo: CalendarDateProp) {
// 修复当某个月的1号是周日时月份下方会空出来一行
const { year, month } = dateInfo
if (type === 'prev' && days >= 7)
days -= 7
return Array.from(Array.from({ length: days }), (v, k) => {
return {
day: String(k + 1),
type,
year,
month,
}
})
}
// 获取上一个月的最后一周天数,填充当月空白
function getPreDaysStatus(days: number, type: string, dateInfo: CalendarDateProp, preCurrMonthDays: number) {
// 新增:自定义周起始日
days = days - props.firstDayOfWeek
// 修复当某个月的1号是周日时月份下方会空出来一行
const { year, month } = dateInfo
if (type === 'prev' && days >= 7)
days -= 7
const months = Array.from(Array.from({ length: preCurrMonthDays }), (v, k) => {
return {
day: String(k + 1),
type,
year,
month,
}
})
return months.slice(preCurrMonthDays - days)
}
// 获取月数据
function getMonth(curData: string[], type: string) {
// 一号为周几
const preMonthDays = getMonthPreDay(+curData[0], +curData[1])
let preMonth = Number(curData[1]) - 1
let preYear = Number(curData[0])
if (preMonth <= 0) {
preMonth = 12
preYear += 1
}
// 当月天数与上个月天数
const currMonthDays = getMonthDays(String(curData[0]), String(curData[1]))
const preCurrMonthDays = getMonthDays(`${preYear}`, `${preMonth}`)
const title = {
year: curData[0],
month: curData[1],
}
const monthInfo: MonthInfo = {
curData,
title: translate('monthTitle', title.year, title.month),
monthData: [
...(getPreDaysStatus(
preMonthDays,
'prev',
{ month: String(preMonth), year: String(preYear) },
preCurrMonthDays,
) as Day[]),
...(getDaysStatus(currMonthDays, 'curr', title) as Day[]),
],
cssHeight: 0,
cssScrollHeight: 0,
}
let titleHeight, itemHeight
if (isH5) {
titleHeight = 46 * scalePx.value + 16 * scalePx.value * 2
itemHeight = 128 * scalePx.value
}
else {
titleHeight = Math.floor(46 * scalePx.value) + Math.floor(16 * scalePx.value) * 2
itemHeight = Math.floor(128 * scalePx.value)
}
monthInfo.cssHeight = titleHeight + (monthInfo.monthData.length > 35 ? itemHeight * 6 : itemHeight * 5)
let cssScrollHeight = 0
if (state.monthsData.length > 0) {
cssScrollHeight
= (state.monthsData[state.monthsData.length - 1] as MonthInfo).cssScrollHeight
+ (state.monthsData[state.monthsData.length - 1] as MonthInfo).cssHeight
}
monthInfo.cssScrollHeight = cssScrollHeight
if (type === 'next') {
// 判断当前日期 是否大于 最后一天
if (
!state.endData
|| !compareDate(
`${state.endData[0]}-${state.endData[1]}-${getMonthDays(state.endData[0], state.endData[1])}`,
`${curData[0]}-${curData[1]}-${curData[2]}`,
)
) {
state.monthsData.push(monthInfo)
}
}
else {
// 判断当前日期 是否小于 第一天
if (
!state.startData
|| !compareDate(
`${curData[0]}-${curData[1]}-${curData[2]}`,
`${state.startData[0]}-${state.startData[1]}-01`,
)
) {
state.monthsData.unshift(monthInfo)
}
else {
state.unLoadPrev = true
}
}
}
// 初始化数据
function initData() {
// 初始化开始结束数据
const propStartDate = props.startDate ? props.startDate : getDay(0)
const propEndDate = props.endDate ? props.endDate : getDay(365)
state.propStartDate = propStartDate
state.propEndDate = propEndDate
state.startData = splitDate(propStartDate)
state.endData = splitDate(propEndDate)
// 根据是否存在默认时间,初始化当前日期,
if (props.defaultValue || (Array.isArray(props.defaultValue) && (props.defaultValue as any[]).length > 0)) {
state.currDate
= props.type !== 'one' ? ([...props.defaultValue] as StringArr) : (props.defaultValue as string | StringArr)
}
// 判断时间范围内存在多少个月
const startDate = {
year: Number(state.startData[0]),
month: Number(state.startData[1]),
}
const endDate = {
year: Number(state.endData[0]),
month: Number(state.endData[1]),
}
let monthsNum = endDate.month - startDate.month
if (endDate.year - startDate.year > 0)
monthsNum = monthsNum + 12 * (endDate.year - startDate.year)
if (monthsNum <= 0)
monthsNum = 1
// 设置月份数据
getMonth(state.startData, 'next')
let i = 1
do
getMonth(getCurrData('next'), 'next')
while (i++ < monthsNum)
state.monthsNum = monthsNum
// 日期转化为数组,限制初始日期。判断时间范围
if (props.type === 'range' && Array.isArray(state.currDate)) {
if (state.currDate.length > 0) {
if (propStartDate && compareDate(state.currDate[0], propStartDate))
state.currDate.splice(0, 1, propStartDate)
if (propEndDate && compareDate(propEndDate, state.currDate[1]))
state.currDate.splice(1, 1, propEndDate)
state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])]
}
}
else if (props.type === 'multiple' && Array.isArray(state.currDate)) {
if (state.currDate.length > 0) {
const defaultArr: string[] = []
const obj: any = {}
state.currDate.forEach((item: string) => {
if (
propStartDate
&& !compareDate(item, propStartDate)
&& propEndDate
&& !compareDate(propEndDate, item)
) {
if (!Object.hasOwnProperty.call(obj, item)) {
defaultArr.push(item)
obj[item] = item
}
}
})
state.currDate = [...defaultArr]
state.defaultData = [...splitDate(defaultArr[0])]
}
}
else if (props.type === 'week' && Array.isArray(state.currDate)) {
if (state.currDate.length > 0) {
const [y, m, d] = splitDate(state.currDate[0])
state.currDate = getWeekDate(y, m, d, props.firstDayOfWeek)
if (propStartDate && compareDate(state.currDate[0], propStartDate))
state.currDate.splice(0, 1, propStartDate)
if (propEndDate && compareDate(propEndDate, state.currDate[1]))
state.currDate.splice(1, 1, propEndDate)
state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])]
}
}
else {
if (state.currDate) {
if (propStartDate && compareDate(state.currDate as string, propStartDate))
state.currDate = propStartDate
else if (propEndDate && !compareDate(state.currDate as string, propEndDate))
state.currDate = propEndDate
state.defaultData = [...splitDate(state.currDate as string)]
}
}
// 设置默认可见区域
let current = 0
let lastCurrent = 0
if (state.defaultData.length > 0) {
state.monthsData.forEach((item, index) => {
if (item.title === translate('monthTitle', state.defaultData[0], state.defaultData[1]))
current = index
if (props.type === 'range' || props.type === 'week') {
if (item.title === translate('monthTitle', state.defaultData[3], state.defaultData[4]))
lastCurrent = index
}
})
}
setDefaultRange(monthsNum, current)
state.currentIndex = current
state.yearMonthTitle = state.monthsData[state.currentIndex].title
if (state.defaultData.length > 0) {
// 设置当前选中日期
if (state.isRange) {
chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true)
chooseDay({ day: state.defaultData[5], type: 'curr' }, state.monthsData[lastCurrent], true)
}
else if (props.type === 'week') {
chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true)
}
else if (props.type === 'multiple') {
[...state.currDate].forEach((item: string) => {
const dateArr = splitDate(item)
let current = state.currentIndex
state.monthsData.forEach((item, index) => {
if (item.title === translate('monthTitle', dateArr[0], dateArr[1]))
current = index
})
chooseDay({ day: dateArr[2], type: 'curr' }, state.monthsData[current], true)
})
}
else {
chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true)
}
}
const lastItem = state.monthsData[state.monthsData.length - 1]
const containerHeight = lastItem.cssHeight + lastItem.cssScrollHeight
state.containerHeight = `${containerHeight}px`
state.scrollTop = Math.ceil(state.monthsData[state.currentIndex].cssScrollHeight)
state.avgHeight = Math.floor(containerHeight / (monthsNum + 1))
if (months?.value)
viewHeight.value = months.value.clientHeight
}
function scrollToDate(date: string) {
if (compareDate(date, state.propStartDate))
date = state.propStartDate
else if (!compareDate(date, state.propEndDate))
date = state.propEndDate
const dateArr = splitDate(date)
state.monthsData.forEach((item, index) => {
if (item.title === translate('monthTitle', dateArr[0], dateArr[1])) {
// scrollTop 不会实时变更。当再次赋值时scrollTop无变化时不会触发滚动
state.scrollTop += 1
scrollWithAnimation.value = props.toDateAnimation
requestAniFrame(() => {
setTimeout(() => {
state.scrollTop = state.monthsData[index].cssScrollHeight
setTimeout(() => {
scrollWithAnimation.value = false
}, 200)
}, 10)
})
}
})
}
function initPosition() {
state.scrollTop = Math.ceil(state.monthsData[state.currentIndex].cssScrollHeight)
}
// 设置当前可见月份
function setDefaultRange(monthsNum: number, current: number) {
if (monthsNum >= 3) {
if (current > 0 && current < monthsNum)
state.defaultRange = [current - 1, current + 3]
else if (current === 0)
state.defaultRange = [current, current + 4]
else if (current === monthsNum)
state.defaultRange = [current - 2, current + 2]
}
else {
state.defaultRange = [0, monthsNum + 2]
}
state.translateY = state.monthsData[state.defaultRange[0]].cssScrollHeight
}
// 区间选择&&当前月&&选中态
function isActive(day: Day, month: MonthInfo) {
return (
(props.type === 'range' || props.type === 'week')
&& day.type === 'curr'
&& getClass(day, month).includes('nut-calendar__day--active')
)
}
// 是否有开始提示
function isStartTip(day: Day, month: MonthInfo) {
return isActive(day, month) && isStart(getCurrDate(day, month))
}
// 是否有结束提示
function isEndTip(day: Day, month: MonthInfo) {
if (state.currDate.length >= 2 && isEnd(getCurrDate(day, month)))
return isActive(day, month)
return false
}
// 开始结束时间是否相等
function rangeTip() {
if (state.currDate.length >= 2)
return isEqual(state.currDate[0], state.currDate[1])
}
// 是否有 当前日期
function isCurrDay(dateInfo: Day) {
const date = `${dateInfo.year}-${dateInfo.month}-${Number(dateInfo.day) < 10 ? `0${dateInfo.day}` : dateInfo.day}`
return isEqual(date, date2Str(new Date()))
}
// 滚动处理事件
function mothsViewScroll(e: ScrollViewOnScrollEvent) {
if (state.monthsData.length <= 1)
return
const currentScrollTop = e.detail.scrollTop
let current = Math.floor(currentScrollTop / state.avgHeight)
if (current === 0) {
if (currentScrollTop >= state.monthsData[current + 1].cssScrollHeight)
current += 1
}
else if (current > 0 && current < state.monthsNum - 1) {
if (currentScrollTop >= state.monthsData[current + 1].cssScrollHeight)
current += 1
if (currentScrollTop < state.monthsData[current].cssScrollHeight)
current -= 1
}
if (state.currentIndex !== current) {
state.currentIndex = current
setDefaultRange(state.monthsNum, current)
}
state.yearMonthTitle = state.monthsData[current].title
}
// 重新渲染
function resetRender() {
state.chooseData.splice(0)
state.monthsData.splice(0)
initData()
}
// 监听 默认值更改
watch(() => props.defaultValue, (value) => {
if (value) {
if (props.poppable) {
resetRender()
}
}
})
onMounted(() => {
// 初始化数据
uni.getSystemInfo({
success(res) {
let scale = 2
let toFixed = 3
if (isH5) {
toFixed = 5
const fontSize = document.documentElement.style.fontSize
scale = Number((Number.parseInt(fontSize) / 40).toFixed(toFixed))
}
else {
const screenWidth = res.screenWidth
scale = Number((screenWidth / 750).toFixed(toFixed))
}
scalePx.value = scale
initData()
},
})
})
defineExpose({
scrollToDate,
initPosition,
})
</script>
<script lang="ts">
export default defineComponent({
name: `${PREFIX}-calendar-item`,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="classes" :style="props.customStyle">
<view class="nut-calendar__header">
<view v-if="props.showTitle" class="nut-calendar__header-title">
{{ props.title || translate('title') }}
</view>
<view v-if="props.btnSlot" class="nut-calendar__header-slot">
<slot name="btn" />
</view>
<view v-if="props.showSubTitle" class="nut-calendar__header-subtitle">
{{ state.yearMonthTitle }}
</view>
<view class="nut-calendar__weekdays">
<view
v-for="(item, index) of weeks"
:key="index"
class="nut-calendar__weekday"
:class="{ weekend: item.weekend }"
>
{{ item.day }}
</view>
</view>
</view>
<scroll-view
ref="months"
class="nut-calendar__content"
:scroll-y="true"
:scroll-top="state.scrollTop"
:scroll-with-animation="scrollWithAnimation"
@scroll="mothsViewScroll"
>
<view class="nut-calendar__panel" :style="{ height: state.containerHeight }">
<view class="nut-calendar__body" :style="{ transform: `translateY(${state.translateY}px)` }">
<view v-for="(month, index) of compConthsData" :key="index" class="nut-calendar__month">
<view class="nut-calendar__month-title">
{{ month.title }}
</view>
<view class="nut-calendar__days">
<view
class="nut-calendar__days-item"
:class="{ 'nut-calendar__days-item--range': props.type === 'range' }"
>
<template v-for="(day, i) of month.monthData" :key="i">
<view
class="nut-calendar__day"
:class="getClass(day, month, i)"
@click="chooseDay(day, month)"
>
<!-- 日期显示slot -->
<view class="nut-calendar__day-value">
<!-- #ifdef MP -->
{{ day.type === 'curr' ? day.day : '' }}
<!-- #endif -->
<!-- #ifndef MP -->
<slot name="day" :date="day.type === 'curr' ? day : ''">
{{ day.type === 'curr' ? day.day : '' }}
</slot>
<!-- #endif -->
</view>
<!-- #ifdef H5 -->
<view v-if="slots.topInfo" class="nut-calendar__day-tips nut-calendar__day-tips--top">
<slot name="topInfo" :date="day.type === 'curr' ? day : ''" />
</view>
<view v-if="slots.bottomInfo" class="nut-calendar__day-tips nut-calendar__day-tips--bottom">
<slot name="bottomInfo" :date="day.type === 'curr' ? day : ''" />
</view>
<!-- #endif -->
<!-- #ifndef MP -->
<view
v-if="!slots.bottomInfo && props.showToday && isCurrDay(day)"
class="nut-calendar__day-tips--curr"
>
{{ translate('today') }}
</view>
<!-- #endif -->
<!-- #ifdef MP -->
<view v-if="props.showToday && isCurrDay(day)" class="nut-calendar__day-tips--curr">
{{ translate('today') }}
</view>
<!-- #endif -->
<view
v-if="isStartTip(day, month)"
class="nut-calendar__day-tip"
:class="{ 'nut-calendar__day-tips--top': rangeTip() }"
>
{{ props.startText || translate('start') }}
</view>
<view v-if="isEndTip(day, month)" class="nut-calendar__day-tip">
{{ props.endText || translate('end') }}
</view>
</view>
</template>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view v-if="props.poppable && !props.isAutoBackFill" class="nut-calendar__footer">
<slot v-if="props.footerSlot" name="footer" :date="state.chooseData" />
<view v-else class="nut-calendar__confirm" @click="confirm">
{{ props.confirmText || translate('confirm') }}
</view>
</view>
</view>
</template>
<style lang="scss">
@import "./index";
</style>

View File

@@ -0,0 +1,270 @@
.nut-theme-dark {
.nut-calendar {
$block: &;
&-item {
color: $dark-color;
background: $dark-background;
}
&__header {
color: $dark-color;
background: $dark-background;
}
&__content {
#{$block}__panel {
#{$block}__days {
#{$block}__day {
&--disabled {
color: $dark-calendar-disabled !important;
}
}
.calendar-month-day {
&-choose {
color: $calendar-choose-font-color;
background-color: $dark-calendar-choose-color;
}
}
}
}
}
&__footer {
color: $dark-color;
background: $dark-background2;
}
}
}
.nut-calendar {
$block: &;
&-item {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow: hidden;
font-size: 16px;
color: #333;
background-color: $white;
}
&#{$block}--nopop {
#{$block}__header {
#{$block}__header-title {
font-size: $calendar-base-font;
}
}
}
.popup-box {
height: 100%;
}
::-webkit-scrollbar {
display: none;
}
&__header {
display: flex;
flex-direction: column;
padding-top: 1px;
text-align: center;
background-color: $white;
&-title {
font-size: $calendar-title-font;
font-weight: $calendar-title-font-weight;
line-height: 44px;
}
&-slot {
min-height: 24px;
}
&-subtitle {
padding: 7px 0;
font-size: $calendar-sub-title-font;
line-height: 22px;
}
#{$block}__weekdays {
display: flex;
align-items: center;
justify-content: space-around;
height: 36px;
border-radius: 0 0 12px 12px;
box-shadow: 0 4px 10px 0 rgba($color: #000, $alpha: 6%);
#{$block}__weekday {
&.weekend {
color: $calendar-day67-font-color;
}
}
}
}
&__content {
display: block;
flex: 1;
width: 100%;
overflow-y: auto;
#{$block}__panel {
position: relative;
box-sizing: border-box;
display: block;
width: 100%;
height: auto;
#{$block}__body {
display: block;
}
#{$block}__month {
display: flex;
flex-direction: column;
text-align: center;
}
view:nth-of-type(2) {
#{$block}__month-title {
padding-top: 0;
}
}
.calendar-loading-tip {
position: absolute;
top: -50px;
right: 0;
left: 0;
height: 50px;
font-size: $calendar-text-font;
line-height: 50px;
color: $text-color;
text-align: center;
}
#{$block}__month-title {
height: 23px;
margin: 8px 0;
font-size: $calendar-month-title-font-size;
line-height: 23px;
}
#{$block}__days {
overflow: hidden;
#{$block}__day {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
float: left;
width: 14.28%;
height: 64px;
font-weight: $calendar-day-font-weight;
&.weekend {
color: $calendar-day67-font-color;
}
#{$block}__day-tips {
position: absolute;
width: 100%;
}
#{$block}__day-tips--curr {
position: absolute;
bottom: 6px;
width: 100%;
font-size: 12px;
line-height: 14px;
}
#{$block}__day-tip {
position: absolute;
bottom: 6px;
width: 100%;
font-size: 12px;
line-height: 14px;
color: $calendar-primary-color;
}
#{$block}__day-tips--top {
top: 6px;
}
#{$block}__day-tips--bottom {
bottom: 6px;
}
&--active {
color: $white !important;
background-color: $calendar-primary-color;
border-radius: $calendar-day-active-border-radius;
#{$block}__day-tips {
visibility: hidden;
}
#{$block}__day-tips--curr {
visibility: hidden;
}
#{$block}__day-tip {
color: $white;
}
}
&--disabled {
color: $calendar-disable-color !important;
}
&--choose {
color: $calendar-choose-font-color;
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
background-color: $calendar-choose-color;
opacity: 0.09;
}
}
#{$block}__day-value {
padding: 2px 0;
font-size: $calendar-day-font;
}
}
}
}
}
&__footer {
display: flex;
width: 100%;
height: 62px;
background-color: $white;
#{$block}__confirm {
width: 100%;
height: 44px;
margin: 10px 18px;
line-height: 44px;
color: $white;
text-align: center;
background: $button-primary-background-color;
border-radius: 22px;
}
}
}

View File

@@ -0,0 +1 @@
export * from './calendaritem'

View File

@@ -0,0 +1,65 @@
export interface TouchParam {
startY: number
endY: number
startTime: number
endTime: number
lastY: number
lastTime: number
}
export type InputDate = string | string[]
export type StringArr = string[]
export interface CalendarState {
yearMonthTitle: string
currDate: string | string[]
propStartDate: string
propEndDate: string
currentIndex: number
unLoadPrev: boolean
touchParams: TouchParam
transformY: number
translateY: number
scrollDistance: number
defaultData: InputDate
chooseData: (string | string[])[]
monthsData: MonthInfo[]
dayPrefix: string
startData: InputDate
endData: InputDate
isRange: boolean
timer: number
avgHeight: number
monthsNum: number
defaultRange: number[]
}
export interface CalendarTaroState extends CalendarState {
scrollTop: number
containerHeight: string
}
export interface CalendarDateProp {
year: string
month: string
}
export interface Day {
day: string
type: string
year?: string
month?: string
}
export interface MonthInfo {
curData: string[] | string
title: string
monthData: Day[]
cssHeight: number
cssScrollHeight: number
}
export interface CalendarInst extends HTMLElement {
scrollToDate: (date: string) => void
initPosition: () => void
}