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:
parent
ae767adbdf
commit
f5fe717d7d
|
|
@ -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>
|
|
||||||
|
|
@ -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: '结束'
|
|
||||||
},
|
|
||||||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
|
||||||
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) {
|
|
||||||
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
|
|
||||||
week = (week === 0 ? 7 : week) - 1
|
|
||||||
style.marginLeft = addUnit(week * dayWidth, 'px')
|
|
||||||
}
|
|
||||||
if (this.mode === 'range') {
|
|
||||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端导致的bug
|
|
||||||
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 = {}
|
|
||||||
// 判断date是否在selected数组中(哈希查找替代逐个 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-app的iOS客户端的bug
|
|
||||||
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
|
|
||||||
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]) {
|
|
||||||
// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
|
|
||||||
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下,$nextTick并不是100%可靠的
|
|
||||||
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))
|
|
||||||
},
|
|
||||||
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
|
|
||||||
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++) {
|
|
||||||
// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
|
|
||||||
topArr[i] = height
|
|
||||||
height += sizes[i].height
|
|
||||||
}
|
|
||||||
// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
|
|
||||||
this.$emit('updateMonthTop', topArr)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 获取每个月份区域的尺寸
|
|
||||||
getMonthRectByPromise(el) {
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview-plus.jiangruyi.com/js/getRect.html
|
|
||||||
// 组件内部一般用this.$uGetRect,对外的为uni.$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) {
|
|
||||||
// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
|
|
||||||
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)))
|
|
||||||
// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
|
|
||||||
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下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
|
|
||||||
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>
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -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: {
|
|
||||||
// 由于maxDate和minDate可以为字符串(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() {
|
|
||||||
// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
|
|
||||||
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,
|
|
||||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
|
||||||
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() {
|
|
||||||
// 校验maxDate,不能小于minDate。
|
|
||||||
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-6,0为周日
|
|
||||||
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) {
|
|
||||||
// 不允许小于0的滚动值,如果scroll-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 = []) {
|
|
||||||
// 设置对应月份的top值,用于onScroll方法更新月份
|
|
||||||
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>
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, // 窗口宽(px),onLoad 时取系统信息,用于 px→rpx 换算
|
||||||
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
|
||||||
},
|
},
|
||||||
// 日历回显:已选范围作为 defaultDate(range 模式要求数组)。
|
// 懒渲染:只渲染滚动窗口(±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() {
|
||||||
|
// px→rpx 换算基准
|
||||||
|
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 滚动值换算回 rpx(750 / 窗口宽),驱动懒渲染窗口移动
|
||||||
// 只记录临时选择,不关闭抽屉——最终提交由底部自绘确定按钮完成。
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--start,
|
||||||
|
&--end {
|
||||||
|
background: #2ccb98;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--start.cal-day--end {
|
||||||
|
background: #2ccb98;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--in {
|
||||||
|
background: #e9fff3;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #ddf6ec;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-drawer-body :deep(.u-calendar-header) {
|
.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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue