feat(event): 移除补丁日历组件,时间抽屉改为自绘滚动日历

- 移除 components/patched-calendar 五件补丁版日历组件与 pages.json 的
  up-calendar 映射,恢复使用 uview-plus 原始 up-calendar
- 时间抽屉改为自绘:顶置"近 7/30/90 天"快捷范围、星期行 + 月份网格,
  scroll-view 懒渲染(窗口外月份占位,避免一次渲染 130+ 月)
- 滚动驱动懒渲染窗口更新(按 rpx 偏移量差阈值触发)
- 最多回溯 10 年,提供按快捷范围点选直接填满起止
This commit is contained in:
ozh 2026-08-19 16:31:33 +08:00
parent ae767adbdf
commit f5fe717d7d
7 changed files with 389 additions and 1590 deletions

View File

@ -1,109 +0,0 @@
<template>
<view class="u-calendar-header u-border-bottom">
<text
class="u-calendar-header__title"
v-if="showTitle"
>{{ title }}</text>
<text
class="u-calendar-header__subtitle"
v-if="showSubtitle"
>{{ subtitle }}</text>
<view class="u-calendar-header__weekdays">
<text class="u-calendar-header__weekdays__weekday">{{ weekText[0] }}</text>
<text class="u-calendar-header__weekdays__weekday">{{ weekText[1] }}</text>
<text class="u-calendar-header__weekdays__weekday">{{ weekText[2] }}</text>
<text class="u-calendar-header__weekdays__weekday">{{ weekText[3] }}</text>
<text class="u-calendar-header__weekdays__weekday">{{ weekText[4] }}</text>
<text class="u-calendar-header__weekdays__weekday">{{ weekText[5] }}</text>
<text class="u-calendar-header__weekdays__weekday">{{ weekText[6] }}</text>
</view>
</view>
</template>
<script>
import { mpMixin } from 'uview-plus/libs/mixin/mpMixin';
import { mixin } from 'uview-plus/libs/mixin/mixin';
export default {
name: 'u-calendar-header',
mixins: [mpMixin, mixin],
props: {
//
title: {
type: String,
default: ''
},
//
subtitle: {
type: String,
default: ''
},
//
showTitle: {
type: Boolean,
default: true
},
//
showSubtitle: {
type: Boolean,
default: true
},
//
weekText: {
type: Array,
default: () => {
return []
}
},
},
data() {
return {
}
},
methods: {
name() {
}
},
}
</script>
<style lang="scss" scoped>
.u-calendar-header {
display: flex;
flex-direction: column;
padding-bottom: 4px;
&__title {
font-size: 16px;
color: $u-main-color;
text-align: center;
height: 42px;
line-height: 42px;
font-weight: bold;
}
&__subtitle {
font-size: 14px;
color: $u-main-color;
height: 40px;
text-align: center;
line-height: 40px;
font-weight: bold;
}
&__weekdays {
@include flex;
justify-content: space-between;
&__weekday {
font-size: 13px;
color: $u-main-color;
line-height: 30px;
flex: 1;
text-align: center;
}
}
}
</style>

View File

@ -1,632 +0,0 @@
<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">{{ monthTitle(item) }}</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 || isForbid(item1) ) ? '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 || isForbid(item1) ) ? '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 { mpMixin } from 'uview-plus/libs/mixin/mpMixin';
import { mixin } from 'uview-plus/libs/mixin/mixin';
import { addUnit, toast, sleep } from 'uview-plus/libs/function/index';
import { colorGradient } from 'uview-plus/libs/function/colorGradient';
import test from 'uview-plus/libs/function/test';
import defProps from 'uview-plus/libs/config/props';
import dayjs from 'uview-plus/components/u-datetime-picker/dayjs.esm.min.js';
import { t } from 'uview-plus/libs/i18n'
export default {
name: 'u-calendar-month',
mixins: [mpMixin, 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: '结束'
},
// modemultiplerange
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: () => defProps.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
},
forbidDays: {
type: Array,
default: () => []
},
forbidDaysToast: {
type: String,
default: ''
}
},
data() {
return {
//
width: 0,
// item
item: {},
selected: []
}
},
watch: {
selectedChange: {
immediate: true,
handler(n) {
this.setDefaultDate()
}
}
},
computed: {
//
selectedChange() {
return [this.minDate, this.maxDate, this.defaultDate]
},
// O(1) 396+
// selected.some(dateSame)dayjs O( × )
// selected YYYY-MM-DD Set
selectedSet() {
return new Set(this.selected)
},
// range dayjs
selectedRange() {
if (this.mode === 'range' && this.selected.length >= 2) {
const first = this.selected[0]
const last = this.selected[this.selected.length - 1]
return {
first,
last,
firstTs: dayjs(first).valueOf(),
lastTs: dayjs(last).valueOf()
}
}
return null
},
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 = addUnit(dayWidth, 'px')
// #endif
style.height = addUnit(this.rowHeight, 'px')
if (index2 === 0) {
// 0item
week = (week === 0 ? 7 : week) - 1
style.marginLeft = addUnit(week * dayWidth, 'px')
}
if (this.mode === 'range') {
// DCloudiOSbug
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 = {}
// dateselected dayjs
if (this.selectedSet.has(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) {
//
if (date === this.selectedRange.first) {
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
}
//
if (date === this.selectedRange.last) {
style.borderTopRightRadius = '3px'
style.borderBottomRightRadius = '3px'
}
// dayjs
const ts = dayjs(date).valueOf()
if (ts > this.selectedRange.firstTs && ts < this.selectedRange.lastTs) {
style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
// mark
style.opacity = 0.7
}
} else if (this.selected.length === 1) {
// uni-appiOSbug
// nvueiOSuni-appbug
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
}
} else {
if (this.selectedSet.has(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.selectedSet.has(date)) {
style.color = '#ffffff'
}
if (this.mode === 'range' && this.selectedRange) {
//
const ts = dayjs(date).valueOf()
if (ts > this.selectedRange.firstTs && ts < this.selectedRange.lastTs) {
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 (date === this.selected[0]) return this.startText
else return bottomInfo
} else {
// 2
if (date === this.selected[0] && date === this.selected[1]) {
// 2item
return `${this.startText}/${this.endText}`
} else if (date === this.selected[0]) {
return this.startText
} else if (date === this.selected[this.selected.length - 1]) {
return this.endText
} else {
return bottomInfo
}
}
} else {
return bottomInfo
}
}
}
},
mounted() {
this.init()
},
emits: ['monthSelected', 'updateMonthTop'],
methods: {
init() {
//
this.$emit('monthSelected', this.selected)
this.$nextTick(() => {
//
// nvue$nextTick100%
sleep(10).then(() => {
this.getWrapperWidth()
this.getMonthRect()
})
})
},
monthTitle(item) {
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
return item.year + '年' + (item.month < 10 ? '0' + item.month : item.month) + '月'
} else {
return (item.month < 10 ? '0' + item.month : item.month) + '/' + item.year
}
},
isForbid(item) {
let date = dayjs(item.date).format("YYYY-MM-DD")
if (this.mode !== 'range' && this.forbidDays.includes(date)) {
return true
}
return false
},
//
dateSame(date1, date2) {
return dayjs(date1).isSame(dayjs(date2))
},
// nvuecssitem
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++) {
// monthsscroll-view
topArr[i] = height
height += sizes[i].height
}
// this.months[i].top()monthtop使
this.$emit('updateMonthTop', topArr)
})
},
//
getMonthRectByPromise(el) {
// #ifndef APP-NVUE
// $uGetRectuViewhttps://uview-plus.jiangruyi.com/js/getRect.html
// this.$uGetRectuni.$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
if (this.isForbid(item)) {
uni.showToast({
title: this.forbidDaysToast
})
return
}
// selected YYYY-MM-DD
let selected = this.selected.slice()
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) {
// 02
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) {
toast(this.rangePrompt)
} else {
toast(t("up.calendar.daysExceed", { days: 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)))
// computedarr
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 (!test.array(this.defaultDate)) {
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
} else {
defaultDate = [this.defaultDate[0]]
}
} else {
//
if (!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,'tap')
}
}
}
</script>
<style lang="scss" scoped>
.u-calendar-month-wrapper {
margin-top: 4px;
}
.u-calendar-month {
&__title {
display: flex;
flex-direction: column;
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使cssjs
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>

View File

@ -1,169 +0,0 @@
import { defineMixin } from 'uview-plus/libs/vue'
import defProps from 'uview-plus/libs/config/props.js'
export const props = defineMixin({
props: {
// 日历顶部标题
title: {
type: String,
default: () => defProps.calendar.title
},
// 是否显示标题
showTitle: {
type: Boolean,
default: () => defProps.calendar.showTitle
},
// 是否显示副标题
showSubtitle: {
type: Boolean,
default: () => defProps.calendar.showSubtitle
},
// 日期类型选择single-选择单个日期multiple-可以选择多个日期range-选择日期范围
mode: {
type: String,
default: () => defProps.calendar.mode
},
// mode=range时第一个日期底部的提示文字
startText: {
type: String,
default: () => defProps.calendar.startText
},
// mode=range时最后一个日期底部的提示文字
endText: {
type: String,
default: () => defProps.calendar.endText
},
// 自定义列表
customList: {
type: Array,
default: () => defProps.calendar.customList
},
// 主题色,对底部按钮和选中日期有效
color: {
type: String,
default: () => defProps.calendar.color
},
// 最小的可选日期
minDate: {
type: [String, Number],
default: () => defProps.calendar.minDate
},
// 最大可选日期
maxDate: {
type: [String, Number],
default: () => defProps.calendar.maxDate
},
// 默认选中的日期mode为multiple或range是必须为数组格式
defaultDate: {
type: [Array, String, Date, null],
default: () => defProps.calendar.defaultDate
},
// mode=multiple时最多可选多少个日期
maxCount: {
type: [String, Number],
default: () => defProps.calendar.maxCount
},
// 日期行高
rowHeight: {
type: [String, Number],
default: () => defProps.calendar.rowHeight
},
// 日期格式化函数
formatter: {
type: [Function, null],
default: () => defProps.calendar.formatter
},
// 是否显示农历
showLunar: {
type: Boolean,
default: () => defProps.calendar.showLunar
},
// 是否显示月份背景色
showMark: {
type: Boolean,
default: () => defProps.calendar.showMark
},
// 确定按钮的文字
confirmText: {
type: String,
default: () => defProps.calendar.confirmText
},
// 确认按钮处于禁用状态时的文字
confirmDisabledText: {
type: String,
default: () => defProps.calendar.confirmDisabledText
},
// 是否显示日历弹窗
show: {
type: Boolean,
default: () => defProps.calendar.show
},
// 是否允许点击遮罩关闭日历
closeOnClickOverlay: {
type: Boolean,
default: () => defProps.calendar.closeOnClickOverlay
},
// 是否为只读状态,只读状态下禁止选择日期
readonly: {
type: Boolean,
default: () => defProps.calendar.readonly
},
// 是否展示确认按钮
showConfirm: {
type: Boolean,
default: () => defProps.calendar.showConfirm
},
// 日期区间最多可选天数默认无限制mode = range时有效
maxRange: {
type: [Number, String],
default: () => defProps.calendar.maxRange
},
// 范围选择超过最多可选天数时的提示文案mode = range时有效
rangePrompt: {
type: String,
default: () => defProps.calendar.rangePrompt
},
// 范围选择超过最多可选天数时是否展示提示文案mode = range时有效
showRangePrompt: {
type: Boolean,
default: () => defProps.calendar.showRangePrompt
},
// 是否允许日期范围的起止时间为同一天mode = range时有效
allowSameDay: {
type: Boolean,
default: () => defProps.calendar.allowSameDay
},
// 圆角值
round: {
type: [Boolean, String, Number],
default: () => defProps.calendar.round
},
// 最多展示月份数量
monthNum: {
type: [Number, String],
default: 3
},
// 星期文案
weekText: {
type: Array,
default: defProps.calendar.weekText
},
forbidDays: {
type: Array,
default: defProps.calendar.forbidDays
},
forbidDaysToast:{
type: String,
default: defProps.calendar.forbidDaysToast
},
monthFormat:{
type: String,
default: defProps.calendar.monthFormat
},
// 是否页面内展示
pageInline:{
type: Boolean,
default: defProps.calendar.pageInline
}
}
})

View File

@ -1,421 +0,0 @@
<template>
<u-popup
:show="show"
mode="bottom"
:closeable="!pageInline"
@close="close"
:round="round"
:pageInline="pageInline"
:closeOnClickOverlay="closeOnClickOverlay"
>
<view class="u-calendar">
<uHeader
:title="title"
:subtitle="subtitle"
:showSubtitle="showSubtitle"
:showTitle="showTitle"
:weekText="weekText"
></uHeader>
<scroll-view
:style="{
height: addUnit(listHeight, 'px')
}"
scroll-y
@scroll="onScroll"
:scroll-top="scrollTop"
:scrollIntoView="scrollIntoView"
>
<uMonth
:color="color"
:rowHeight="rowHeight"
:showMark="showMark"
:months="months"
:mode="mode"
:maxCount="maxCount"
:startText="startText"
:endText="endText"
:defaultDate="defaultDate"
:minDate="innerMinDate"
:maxDate="innerMaxDate"
:maxMonth="monthNum"
:readonly="readonly"
:maxRange="maxRange"
:rangePrompt="rangePrompt"
:showRangePrompt="showRangePrompt"
:allowSameDay="allowSameDay"
:forbidDays="forbidDays"
:forbidDaysToast="forbidDaysToast"
:monthFormat="monthFormat"
ref="month"
@monthSelected="monthSelected"
@updateMonthTop="updateMonthTop"
></uMonth>
</scroll-view>
<slot name="footer" v-if="showConfirm">
<view class="u-calendar__confirm">
<u-button
shape="circle"
:text="
buttonDisabled ? confirmDisabledText : confirmText
"
:color="color"
@click="confirm"
:disabled="buttonDisabled"
></u-button>
</view>
</slot>
</view>
</u-popup>
</template>
<script>
import uHeader from './header.vue'
import uMonth from './month.vue'
import { props } from './props.js'
import util from './util.js'
import dayjs from 'uview-plus/components/u-datetime-picker/dayjs.esm.min.js';
import Calendar from 'uview-plus/libs/util/calendar.js'
import { mpMixin } from 'uview-plus/libs/mixin/mpMixin.js'
import { mixin } from 'uview-plus/libs/mixin/mixin.js'
import { addUnit, getPx, range, error, padZero } from 'uview-plus/libs/function/index';
import test from 'uview-plus/libs/function/test';
/**
* Calendar 日历
* @description 此组件用于单个选择日期范围选择日期等日历被包裹在底部弹起的容器中.
* @tutorial https://uview-plus.jiangruyi.com/components/calendar.html
*
* @property {String} title 标题内容 (默认 日期选择 )
* @property {Boolean} showTitle 是否显示标题 (默认 true )
* @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
* @property {String} mode 日期类型选择 single-选择单个日期multiple-可以选择多个日期range-选择日期范围 默认 'single' )
* @property {String} startText mode=range时第一个日期底部的提示文字 (默认 '开始' )
* @property {String} endText mode=range时最后一个日期底部的提示文字 (默认 '结束' )
* @property {Array} customList 自定义列表
* @property {String} color 主题色对底部按钮和选中日期有效 (默认 #3c9cff' )
* @property {String | Number} minDate 最小的可选日期 (默认 0 )
* @property {String | Number} maxDate 最大可选日期 (默认 0 )
* @property {Array | String| Date} defaultDate 默认选中的日期mode为multiple或range是必须为数组格式
* @property {String | Number} maxCount mode=multiple时最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
* @property {String | Number} rowHeight 日期行高 (默认 56 )
* @property {Function} formatter 日期格式化函数
* @property {Boolean} showLunar 是否显示农历 (默认 false )
* @property {Boolean} showMark 是否显示月份背景色 (默认 true )
* @property {String} confirmText 确定按钮的文字 (默认 '确定' )
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
* @property {Boolean} show 是否显示日历弹窗 (默认 false )
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
* @property {Boolean} readonly 是否为只读状态只读状态下禁止选择日期 (默认 false )
* @property {String | Number} maxRange 日期区间最多可选天数默认无限制mode = range时有效
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案mode = range时有效
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时是否展示提示文案mode = range时有效 (默认 true )
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天mode = range时有效 (默认 false )
* @property {Number|String} round 圆角值默认无圆角 (默认 0 )
* @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
* @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] )
*
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
* @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
* @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
</u-calendar>
* */
export default {
name: 'u-calendar',
mixins: [mpMixin, mixin, props],
components: {
uHeader,
uMonth
},
data() {
return {
//
months: [],
// index
monthIndex: 0,
//
listHeight: 0,
// month
selected: [],
scrollIntoView: '',
scrollIntoViewScroll: '',
scrollTop:0,
//
innerFormatter: (value) => value
}
},
watch: {
scrollIntoView: {
immediate: true,
handler(n) {
// console.log('scrollIntoView', n)
}
},
selectedChange: {
immediate: true,
handler(n) {
this.setMonth()
}
},
//
show: {
immediate: true,
handler(n) {
if (n) {
this.setMonth()
} else {
// scrollIntoView
// scrollIntoView
this.scrollIntoView = ''
}
}
}
},
computed: {
// maxDateminDate(2021-10-10)()dayjs
innerMaxDate() {
return test.number(this.maxDate)
? Number(this.maxDate)
: this.maxDate
},
innerMinDate() {
return test.number(this.minDate)
? Number(this.minDate)
: this.minDate
},
//
selectedChange() {
return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
},
subtitle() {
// this.months
if (this.months.length) {
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
return this.months[this.monthIndex].year + '年' + (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '月'
} else {
return (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '/' + this.months[this.monthIndex].year
}
} else {
return ''
}
},
buttonDisabled() {
// range1disabled
if (this.mode === 'range') {
if (this.selected.length <= 1) {
return true
} else {
return false
}
} else {
return false
}
}
},
mounted() {
this.start = Date.now()
this.init()
},
emits: ["confirm", "close"],
methods: {
addUnit,
// propsref
setFormatter(e) {
this.innerFormatter = e
},
// month
monthSelected(e,scene ='init') {
this.selected = e
if (!this.showConfirm) {
// 2
if (
this.mode === 'multiple' ||
this.mode === 'single' ||
(this.mode === 'range' && this.selected.length >= 2)
) {
if( scene === 'init'){
return
}
if( scene === 'tap') {
this.$emit('confirm', this.selected)
}
}
}
},
init() {
// maxDateminDate
if (
this.innerMaxDate &&
this.innerMinDate &&
new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
) {
return error('maxDate不能小于minDate时间')
}
//
let bottomPadding = 0;
if (this.pageInline) {
bottomPadding = 0
} else {
bottomPadding = 30
}
this.listHeight = this.rowHeight * 5 + bottomPadding
this.setMonth()
},
close() {
this.$emit('close')
},
//
confirm() {
if (!this.buttonDisabled) {
this.$emit('confirm', this.selected)
}
},
//
getMonths(minDate, maxDate) {
const minYear = dayjs(minDate).year()
const minMonth = dayjs(minDate).month() + 1
const maxYear = dayjs(maxDate).year()
const maxMonth = dayjs(maxDate).month() + 1
return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
},
//
setMonth() {
//
const minDate = this.innerMinDate || dayjs().valueOf()
// 3
const maxDate =
this.innerMaxDate ||
dayjs(minDate)
.add(this.monthNum - 1, 'month')
.valueOf()
//
const months = range(
1,
this.monthNum,
this.getMonths(minDate, maxDate)
)
//
this.months = []
for (let i = 0; i < months; i++) {
this.months.push({
date: new Array(
dayjs(minDate).add(i, 'month').daysInMonth()
)
.fill(1)
.map((item, index) => {
// 1-31
let day = index + 1
// 0-60
const week = dayjs(minDate)
.add(i, 'month')
.date(day)
.day()
const date = dayjs(minDate)
.add(i, 'month')
.date(day)
.format('YYYY-MM-DD')
let bottomInfo = ''
if (this.showLunar) {
//
const lunar = Calendar.solar2lunar(
dayjs(date).year(),
dayjs(date).month() + 1,
dayjs(date).date()
)
bottomInfo = lunar.IDayCn
}
let config = {
day,
week,
// disabled
disabled:
dayjs(date).isBefore(
dayjs(minDate).format('YYYY-MM-DD')
) ||
dayjs(date).isAfter(
dayjs(maxDate).format('YYYY-MM-DD')
),
// formatter
date: new Date(date),
bottomInfo,
dot: false,
month:
dayjs(minDate).add(i, 'month').month() + 1
}
const formatter =
this.formatter || this.innerFormatter
return formatter(config)
}),
//
month: dayjs(minDate).add(i, 'month').month() + 1,
//
year: dayjs(minDate).add(i, 'month').year()
})
}
},
//
scrollIntoDefaultMonth(selected) {
//
const _index = this.months.findIndex(({
year,
month
}) => {
month = padZero(month)
return `${year}-${month}` === selected
})
if (_index !== -1) {
// #ifndef MP-WEIXIN
this.$nextTick(() => {
this.scrollIntoView = `month-${_index}`
this.scrollIntoViewScroll = this.scrollIntoView
})
// #endif
// #ifdef MP-WEIXIN
this.scrollTop = this.months[_index].top || 0;
// #endif
}
},
// scroll-view
onScroll(event) {
// 0scroll-view
const scrollTop = Math.max(0, event.detail.scrollTop)
//
for (let i = 0; i < this.months.length; i++) {
if (scrollTop >= (this.months[i].top || this.listHeight)) {
this.monthIndex = i
this.scrollIntoViewScroll = `month-${i}`
}
}
},
// top
updateMonthTop(topArr = []) {
// toponScroll
topArr.map((item, index) => {
this.months[index].top = item
})
//
if (!this.defaultDate) {
//
const selected = dayjs().format("YYYY-MM")
this.scrollIntoDefaultMonth(selected)
return
}
let selected = dayjs().format("YYYY-MM");
// Date
if (!test.array(this.defaultDate)) {
selected = dayjs(this.defaultDate).format("YYYY-MM")
} else {
selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
}
this.scrollIntoDefaultMonth(selected)
}
}
}
</script>
<style lang="scss" scoped>
.u-calendar {
&__confirm {
padding: 7px 18px;
}
}
</style>

View File

@ -1,86 +0,0 @@
import dayjs from 'uview-plus/components/u-datetime-picker/dayjs.esm.min.js';
export default {
methods: {
// 设置月份数据
setMonth() {
// 月初是周几
const day = dayjs(this.date).date(1).day()
const start = day == 0 ? 6 : day - 1
// 本月天数
const days = dayjs(this.date).endOf('month').format('D')
// 上个月天数
const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
// 日期数据
const arr = []
// 清空表格
this.month = []
// 添加上月数据
arr.push(
...new Array(start).fill(1).map((e, i) => {
const day = prevDays - start + i + 1
return {
value: day,
disabled: true,
date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
}
})
)
// 添加本月数据
arr.push(
...new Array(days - 0).fill(1).map((e, i) => {
const day = i + 1
return {
value: day,
date: dayjs(this.date).date(day).format('YYYY-MM-DD')
}
})
)
// 添加下个月
arr.push(
...new Array(42 - days - start).fill(1).map((e, i) => {
const day = i + 1
return {
value: day,
disabled: true,
date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
}
})
)
// 分割数组
for (let n = 0; n < arr.length; n += 7) {
this.month.push(
arr.slice(n, n + 7).map((e, i) => {
e.index = i + n
// 自定义信息
const custom = this.customList.find((c) => c.date == e.date)
// 农历
if (this.lunar) {
const {
IDayCn,
IMonthCn
} = this.getLunar(e.date)
e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
}
return {
...e,
...custom
}
})
)
}
}
}
}

View File

@ -79,7 +79,6 @@
"easycom": { "easycom": {
"autoscan": true, "autoscan": true,
"custom": { "custom": {
"^up-calendar$": "@/components/patched-calendar/u-calendar.vue",
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue", "^u-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue" "^up-(.*)": "uview-plus/components/u-$1/u-$1.vue"
} }

View File

@ -79,9 +79,7 @@
</view> </view>
</up-popup> </up-popup>
<!-- 时间抽屉顶部下拉内嵌日历范围选择 --> <!-- 时间抽屉自绘遮罩 + 顶部面板滚动式日历懒渲染窗口外月份占位 -->
<!-- 注意up-popup 内容区自带 @touchmove.stop.prevent会吞掉日历内 scroll-view 的滚动手势
所以日历不放在 up-popup 插槽里而是用条件渲染 + 自绘遮罩实现抽屉 -->
<view v-if="drawer === 'time'" class="time-mask" @click="closeDrawer" @touchmove.stop.prevent="noop" /> <view v-if="drawer === 'time'" class="time-mask" @click="closeDrawer" @touchmove.stop.prevent="noop" />
<view v-if="drawer === 'time'" class="time-drawer"> <view v-if="drawer === 'time'" class="time-drawer">
<view class="time-drawer-header"> <view class="time-drawer-header">
@ -90,40 +88,70 @@
<up-icon name="close" size="20" color="#909399" /> <up-icon name="close" size="20" color="#909399" />
</view> </view>
</view> </view>
<view class="time-drawer-body">
<up-calendar <!-- 快捷范围点选直接填满起止 -->
:key="calendarKey" <view class="quick-row">
mode="range" <view
:show="drawer === 'time'" v-for="q in quickRanges"
:page-inline="true" :key="q.label"
:show-confirm="false" class="quick-chip"
:show-title="false" :class="{ 'quick-chip--active': activeQuick === q.label }"
:show-subtitle="true" @click="applyQuick(q)"
:row-height="48" >
:month-num="calendarMonthNum" <text>{{ q.label }}</text>
:min-date="calendarMinDate"
:max-date="calendarMaxDate"
:allow-same-day="true"
:max-range="31"
range-prompt="单次最多选择31天"
:show-range-prompt="true"
start-text="开始"
end-text="结束"
color="#2CCB98"
:default-date="calendarDefaultDate"
@confirm="onCalendarConfirm"
@close="closeDrawer"
/>
</view> </view>
<!-- 确定按钮自绘钉底组件内 footer pageInline 多层嵌套下定位不可控 </view>
选满起止日期时 showConfirm=false 的组件会自动 emit confirm中间态
这里只记录值不关闭抽屉由这枚按钮确认后才写入筛选 --> <!-- 星期行钉在滚动区上方 -->
<view class="cal-weekdays">
<text v-for="w in weekdayLabels" :key="w" class="cal-weekday">{{ w }}</text>
</view>
<!-- 滚动日历scroll-view 纵向滚月懒渲染窗口外月份只渲染占位高度 -->
<view class="cal-scroll-wrap">
<scroll-view
class="cal-scroll"
scroll-y
:scroll-into-view="calAnchorId"
scroll-anchoring
@scroll="onCalScroll"
>
<view
v-for="m in renderedMonths"
:key="m.key"
:id="m.key"
class="cal-month"
:style="m.placeholder ? { height: m.height + 'rpx' } : {}"
>
<template v-if="!m.placeholder">
<view class="cal-month-title">
<text>{{ m.title }}</text>
</view>
<view class="cal-grid">
<view
v-for="(cell, i) in m.cells"
:key="i"
class="cal-cell"
:class="cell.cls"
@click="pickDate(cell)"
>
<view v-if="cell.day" class="cal-day" :class="cell.dayCls">
<text class="cal-day-num">{{ cell.day }}</text>
<text v-if="cell.tag" class="cal-day-tag">{{ cell.tag }}</text>
</view>
</view>
</view>
</template>
</view>
</scroll-view>
</view>
<view class="time-drawer-footer"> <view class="time-drawer-footer">
<up-button <up-button
shape="circle" shape="circle"
:text="calendarReady ? '确定' : '请选择起止日期'" :text="rangeReady ? '确定' : '请选择起止日期'"
color="#2CCB98" color="#2CCB98"
:disabled="!calendarReady" :disabled="!rangeReady"
@click="onConfirmClick" @click="onConfirmClick"
/> />
</view> </view>
@ -133,15 +161,38 @@
<script> <script>
import { getSceneList } from '@/api/scene' import { getSceneList } from '@/api/scene'
// monthNum minDate //
const CALENDAR_YEARS_BACK = 1 const CALENDAR_YEARS_BACK = 10
const WEEKDAY_LABELS = ['一', '二', '三', '四', '五', '六', '日']
const pad2 = (n) => String(n).padStart(2, '0')
// Date -> 'YYYY-MM-DD'
const fmtDate = (d) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
// 'YYYY-MM-DD' -> Date
const parseDate = (s) => {
const [y, m, d] = s.split('-').map(Number)
return new Date(y, m - 1, d)
}
// N
const quickRange = (days) => {
const end = new Date()
const start = new Date()
start.setDate(start.getDate() - (days - 1))
return { label: `${days}`, start: fmtDate(start), end: fmtDate(end) }
}
export default { export default {
data() { data() {
return { return {
drawer: '', // '' | 'scene' | 'status' | 'time' drawer: '', // '' | 'scene' | 'status' | 'time'
calendarKey: 0, // selected windowWidth: 375, // pxonLoad pxrpx
pendingRange: [], // filters calScroll: 0, // rpx
calAnchorId: '', // scroll-into-view
pendingStart: '', //
pendingEnd: '', //
activeQuick: '',
weekdayLabels: WEEKDAY_LABELS,
quickRanges: [quickRange(7), quickRange(30), quickRange(90)],
filters: { filters: {
sceneId: 0, // 0 sceneId: 0, // 0
status: 0, status: 0,
@ -167,42 +218,121 @@ export default {
const hit = this.statusOptions.find((o) => o.id === this.filters.status) const hit = this.statusOptions.find((o) => o.id === this.filters.status)
return hit ? hit.name : '全部状态' return hit ? hit.name : '全部状态'
}, },
// ~ minDate //
calendarMinDate() { minLimit() {
const d = new Date() const d = new Date()
d.setFullYear(d.getFullYear() - CALENDAR_YEARS_BACK) d.setFullYear(d.getFullYear() - CALENDAR_YEARS_BACK)
return d.getTime() d.setDate(1)
return fmtDate(d)
}, },
calendarMaxDate() { maxLimit() {
return Date.now() return fmtDate(new Date())
}, },
// up-calendar monthNum maxDate maxDate //
// minDate + monthNum maxDate monthMetas() {
// min~max const metas = []
calendarMonthNum() { const start = parseDate(this.minLimit)
return ( const now = new Date()
(new Date(this.calendarMaxDate).getFullYear() - const endY = now.getFullYear()
new Date(this.calendarMinDate).getFullYear()) * const endM = now.getMonth()
12 + let y = start.getFullYear()
new Date(this.calendarMaxDate).getMonth() - let m = start.getMonth()
new Date(this.calendarMinDate).getMonth() + let top = 0
1 while (y < endY || (y === endY && m <= endM)) {
) const weeks = this.monthWeeks(y, m)
// + × aspect ×1.15 750rpx/7
const height = 96 + weeks * Math.round((750 / 7) * 1.15) + 16
metas.push({
key: `m-${y}-${m}`,
year: y,
month: m,
title: `${y}${pad2(m + 1)}`,
top,
height
})
top += height
if (m === 11) {
y += 1
m = 0
} else {
m += 1
}
}
return metas
}, },
// defaultDaterange // ±4
calendarDefaultDate() { renderedMonths() {
const dates = [this.filters.startDate, this.filters.endDate].filter(Boolean) // top calScroll
return dates.length === 2 ? dates : [] const viewRpx = 1400 // rpx
const buffer = 3000 // rpx
const lo = this.calScroll - buffer
const hi = this.calScroll + viewRpx + buffer
return this.monthMetas.map((meta) => {
const inWindow = meta.top + meta.height > lo && meta.top < hi
if (!inWindow) {
return { ...meta, placeholder: true, cells: [] }
}
return {
...meta,
placeholder: false,
cells: this.buildMonthCells(meta.year, meta.month)
}
})
}, },
// pendingRange //
calendarReady() { rangeReady() {
return this.pendingRange.length >= 2 return !!this.pendingStart && !!this.pendingEnd
} }
}, },
onLoad() { onLoad() {
// pxrpx
const info = uni.getWindowInfo ? uni.getWindowInfo() : uni.getSystemInfoSync()
this.windowWidth = info.windowWidth || 375
this.loadScenes() this.loadScenes()
}, },
methods: { methods: {
// //
buildCell(dateStr, day) {
const disabled = dateStr < this.minLimit || dateStr > this.maxLimit
let dayCls = ''
let tag = ''
if (!disabled) {
if (this.pendingStart && dateStr === this.pendingStart) {
dayCls = 'cal-day--start'
tag = this.pendingStart === this.pendingEnd ? '起/止' : '开始'
} else if (this.pendingEnd && dateStr === this.pendingEnd) {
dayCls = 'cal-day--end'
tag = '结束'
} else if (
this.pendingStart &&
this.pendingEnd &&
dateStr > this.pendingStart &&
dateStr < this.pendingEnd
) {
dayCls = 'cal-day--in'
}
}
return { day, date: dateStr, disabled, cls: { 'cal-cell--disabled': disabled }, dayCls, tag }
},
//
buildMonthCells(y, m) {
const firstDay = new Date(y, m, 1)
const lead = (firstDay.getDay() + 6) % 7
const daysInMonth = new Date(y, m + 1, 0).getDate()
const cells = []
for (let i = 0; i < lead; i++) cells.push({ day: 0 })
for (let d = 1; d <= daysInMonth; d++) {
cells.push(this.buildCell(`${y}-${pad2(m + 1)}-${pad2(d)}`, d))
}
return cells
},
// 1
monthWeeks(y, m) {
const firstDay = new Date(y, m, 1)
const lead = (firstDay.getDay() + 6) % 7
const daysInMonth = new Date(y, m + 1, 0).getDate()
return Math.ceil((lead + daysInMonth) / 7)
},
// /scene/list // /scene/list
// ""toast // ""toast
async loadScenes() { async loadScenes() {
@ -217,11 +347,21 @@ export default {
noop() {}, noop() {},
openDrawer(name) { openDrawer(name) {
if (name === 'time') { if (name === 'time') {
// up-calendar defaultDate //
this.calendarKey += 1 this.pendingStart = this.filters.startDate
// this.pendingEnd = this.filters.endDate
const dates = [this.filters.startDate, this.filters.endDate].filter(Boolean) this.activeQuick = ''
this.pendingRange = dates.length === 2 ? dates : [] const focus = this.filters.startDate || fmtDate(new Date())
const d = parseDate(focus)
const meta = this.monthMetas.find(
(mm) => mm.year === d.getFullYear() && mm.month === d.getMonth()
)
this.calScroll = meta ? meta.top : 0
// scroll-into-view
this.calAnchorId = ''
this.$nextTick(() => {
this.calAnchorId = meta ? meta.key : ''
})
} }
this.drawer = name this.drawer = name
}, },
@ -244,26 +384,53 @@ export default {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[event filters]', { ...this.filters }) console.log('[event filters]', { ...this.filters })
}, },
// showConfirm=false confirm // px rpx750 /
// onCalScroll(e) {
onCalendarConfirm(dateArr) { const px = e.detail.scrollTop || 0
if (!Array.isArray(dateArr) || dateArr.length < 2) { const rpx = px * (750 / this.windowWidth)
return // computed
if (Math.abs(rpx - this.calScroll) > 200) {
this.calScroll = rpx
} }
this.pendingRange = dateArr
}, },
// //
// range confirm pickDate(cell) {
// if (!cell.day || cell.disabled) return
const date = cell.date
if (!this.pendingStart || (this.pendingStart && this.pendingEnd)) {
this.pendingStart = date
this.pendingEnd = ''
this.activeQuick = ''
} else if (date < this.pendingStart) {
this.pendingStart = date
this.activeQuick = ''
} else {
this.pendingEnd = date
this.activeQuick = ''
}
},
//
applyQuick(q) {
this.pendingStart = q.start
this.pendingEnd = q.end
this.activeQuick = q.label
const d = parseDate(q.start)
const meta = this.monthMetas.find(
(mm) => mm.year === d.getFullYear() && mm.month === d.getMonth()
)
this.calAnchorId = ''
this.$nextTick(() => {
this.calAnchorId = meta ? meta.key : ''
})
},
//
onConfirmClick() { onConfirmClick() {
if (!this.calendarReady) { if (!this.rangeReady) {
return return
} }
const start = this.pendingRange[0] this.filters.startDate = this.pendingStart
const end = this.pendingRange[this.pendingRange.length - 1] this.filters.endDate = this.pendingEnd
this.filters.startDate = start this.filters.timeText = `${this.pendingStart}${this.pendingEnd}`
this.filters.endDate = end
this.filters.timeText = `${start}${end}`
this.closeDrawer() this.closeDrawer()
this.handle() this.handle()
} }
@ -442,116 +609,166 @@ export default {
justify-content: center; justify-content: center;
} }
.time-drawer-body { /* 快捷范围行 */
/* scroll-view calc body .quick-row {
组件内的确定按钮再用绝对定位钉回 body 底部消除按钮下方空白 */ display: flex;
padding: 20rpx 24rpx 4rpx;
}
.quick-chip {
flex: 1;
margin-right: 16rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f6f8;
border: 1rpx solid transparent;
border-radius: 999rpx;
font-size: 24rpx;
color: #606266;
&:last-child {
margin-right: 0;
}
&--active {
background: #e9fff3;
border-color: #2ccb98;
color: #12a37a;
font-weight: 600;
}
}
/* 星期行:钉在滚动区上方 */
.cal-weekdays {
display: flex;
padding: 0 16rpx;
border-bottom: 1rpx solid #f0f1f3;
}
.cal-weekday {
flex: 1;
text-align: center;
font-size: 24rpx;
color: #909399;
line-height: 48rpx;
}
/* 滚动日历容器:占满抽屉剩余空间 */
.cal-scroll-wrap {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
position: relative;
} }
/* .cal-scroll {
pageInline up-calendar 内部仍套着 u-popup.u-popup .u-transition .u-popup__content
这几层默认高度都是 auto导致 scroll-view 的百分比高度无法解析
退化成内容高度=全部月份堆叠滚动窗口失效这里把中间层逐级撑到 100%
再让 scroll-view flex 填满 header 以下的剩余空间实现自适应无空隙 */
.time-drawer-body :deep(.u-popup),
.time-drawer-body :deep(.u-transition),
.time-drawer-body :deep(.u-popup__content) {
height: 100%; height: 100%;
} }
.time-drawer-body :deep(.u-calendar) { /* 月份块 */
.cal-month {
padding: 8rpx 16rpx 0;
}
.cal-month-title {
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
color: #2a2d33;
}
/* 日期网格:固定 42 格(前置空格 + 当月天),节点数恒定 */
.cal-grid {
display: flex;
flex-wrap: wrap;
}
.cal-cell {
width: calc(100% / 7);
box-sizing: border-box; box-sizing: border-box;
height: 100%; padding: 4rpx;
aspect-ratio: 1 / 1.15;
display: flex;
align-items: stretch;
&--disabled {
opacity: 0.3;
}
}
.cal-day {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 16rpx; align-items: center;
justify-content: center;
border-radius: 12rpx;
&:active {
background: #f2f3f5;
} }
.time-drawer-body :deep(.u-calendar-header) { &--start,
&--end {
background: #2ccb98;
}
&--start.cal-day--end {
background: #2ccb98;
}
&--in {
background: #e9fff3;
border-radius: 0;
&:active {
background: #ddf6ec;
}
}
}
.cal-day-num {
font-size: 28rpx;
font-weight: 500;
color: #2a2d33;
line-height: 40rpx;
}
.cal-day--start .cal-day-num,
.cal-day--end .cal-day-num {
color: #ffffff;
font-weight: 600;
}
.cal-day--in .cal-day-num {
color: #12a37a;
}
/* 开始/结束小字标注 */
.cal-day-tag {
font-size: 18rpx;
line-height: 24rpx;
color: rgba(255, 255, 255, 0.9);
}
/* 起止回显:只占内容高度,不与日历滚动区平分空间 */
.range-preview {
flex-shrink: 0; flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #6b6f76;
min-height: 56rpx;
} }
.time-drawer-body :deep(.u-calendar .u-scroll-view),
.time-drawer-body :deep(.u-calendar scroll-view) {
flex: 1;
min-height: 0;
height: auto !important;
}
/* footer/
body 底部自绘一枚按钮选满起止日期后可点 */
.time-drawer-footer { .time-drawer-footer {
flex-shrink: 0; flex-shrink: 0;
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f1f3; border-top: 1rpx solid #f0f1f3;
background: #ffffff; background: #ffffff;
} }
/* 副标题(当前滚动到的月份):弱化为居中小胶囊 */
.time-drawer-body :deep(.u-calendar-header__subtitle) {
align-self: center;
margin: 8rpx 0 4rpx;
padding: 4rpx 24rpx;
background: #f2f3f5;
border-radius: 999rpx;
height: auto;
line-height: 40rpx;
font-size: 24rpx;
font-weight: 400;
color: #6b6f76;
}
/* 星期行 */
.time-drawer-body :deep(.u-calendar-header__weekdays) {
padding: 8rpx 0;
border-bottom: 1rpx solid #f0f1f3;
}
.time-drawer-body :deep(.u-calendar-header__weekdays__weekday) {
text-align: center;
font-size: 24rpx;
color: #909399;
line-height: 44rpx;
}
.time-drawer-body :deep(.u-calendar-month-wrapper) {
/* padding JS CSS
两者对 padding 的包含不同会造成偏差偏移大的月份首行末尾格子会被挤换行 */
margin-top: 0;
}
/* 每月标题行 */
.time-drawer-body :deep(.u-calendar-month__title) {
font-size: 28rpx;
font-weight: 600;
color: #2a2d33;
padding: 16rpx 12rpx;
line-height: 64rpx;
height: 64rpx;
}
/* 月份背景水印:默认 155px 过大过亮,缩小压淡 */
.time-drawer-body :deep(.u-calendar-month__days__month-mark-wrapper__text) {
font-size: 110rpx;
color: rgba(231, 232, 234, 0.4);
}
.time-drawer-body :deep(.u-calendar-month__days__day) {
padding: 8rpx;
}
/* 日期字号恢复正常可读尺寸(此前随抽屉缩小被误压) */
.time-drawer-body :deep(.u-calendar-month__days__day__select__info) {
font-size: 28rpx;
font-weight: 500;
color: #2a2d33;
}
/* 开始/结束提示小字 */
.time-drawer-body :deep(.u-calendar-month__days__day__select__buttom-info) {
font-size: 20rpx;
color: #6b6f76;
}
</style> </style>