466 lines
17 KiB
Vue
466 lines
17 KiB
Vue
<template>
|
||
<view class="u-date-strip" :style="[containerStyle]">
|
||
<!-- 周模式 - swiper切换 -->
|
||
<template v-if="mode === 'week'">
|
||
<swiper
|
||
class="u-date-strip__swiper"
|
||
:current="currentIndex"
|
||
@change="onSwiperChange"
|
||
>
|
||
<swiper-item v-for="(week, index) in weekList" :key="index">
|
||
<view class="u-date-strip__wrapper">
|
||
<view
|
||
v-for="(item, idx) in week"
|
||
:key="idx"
|
||
class="u-date-strip__item"
|
||
>
|
||
<view class="u-date-strip__item-content" :style="[itemStyle('week',item)]" @click="selectDate(item)">
|
||
<view class="u-date-strip__weekday">
|
||
{{ item.weekday }}
|
||
</view>
|
||
<view class="u-date-strip__date" :style="[itemStyle('date',item)]">
|
||
<text class="u-date-strip__date-text">{{ item.date }}</text>
|
||
<text v-if="showLunar && item.lunar" class="u-date-strip__lunar" :style="[itemStyle('lunar',item)]">{{ item.lunar }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
</template>
|
||
|
||
<!-- 平铺模式 - scroll-view -->
|
||
<template v-else>
|
||
<scroll-view
|
||
class="u-date-strip__scroll"
|
||
scroll-x
|
||
:scroll-into-view="scrollIntoView"
|
||
>
|
||
<view class="u-date-strip__scroll-wrapper">
|
||
<view
|
||
v-for="(item, index) in allDays"
|
||
:key="index"
|
||
:id="`item-${index}`"
|
||
class="u-date-strip__item"
|
||
>
|
||
<view class="u-date-strip__item-content" :style="[itemStyle('week',item)]" @click="selectDate(item)">
|
||
<text class="u-date-strip__weekday" :style="[itemStyle('weekday',item)]">
|
||
{{ item.weekday }}
|
||
</text>
|
||
<view class="u-date-strip__date" :style="[itemStyle('date',item)]">
|
||
<text class="u-date-strip__date-text">{{ item.date }}</text>
|
||
<text v-if="item.bottomInfo" class="u-date-strip__lunar" :style="[itemStyle('lunar',item)]">{{ item.bottomInfo }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</template>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import props from './props.js';
|
||
import mixin from '../../libs/mixin/mixin'
|
||
import mpMixin from '../../libs/mixin/mpMixin';
|
||
import dayjs from '../../libs/util/dayjs.js';
|
||
import Calendar from '../../libs/util/calendar.js';
|
||
|
||
/**
|
||
* dateStrip 日期横条
|
||
* @description 用于展示周日历或一组日历信息
|
||
* @tutorial https://uviewui.com/components/dateStrip.html
|
||
*
|
||
* @property {Number} v-model/modelValue 选中的日期时间戳
|
||
* @property {Number} defaultDate 默认选中的日期时间戳
|
||
* @property {String} mode 切换模式 week|none (默认 'week' )
|
||
* @property {String} activeMode 高亮模式 both|date|text (默认 'both' )
|
||
* @property {Number} minDate 可选择的最小日期时间戳
|
||
* @property {Number} maxDate 可选择的最大日期时间戳
|
||
* @property {String} height 组件高度 (默认 '86px' )
|
||
* @property {String} itemWidth 每格日期宽度 (默认 '50px' )
|
||
* @property {String} itemRound 每格日期圆角 (默认 '6px' )
|
||
* @property {String} activeBgColor 选中框背景色
|
||
* @property {String} activeColor 选中框文本色
|
||
* @property {String} bgColor 横条背景色
|
||
* @property {String} round 选中框圆角
|
||
* @property {Number} firstDayOfWeek 第一天从星期几开始 0-6 (默认 0 )
|
||
* @property {Number} monthNum 最多展示月份数量 (默认 3 )
|
||
* @property {Array|String} disabledDate 禁止选择的日期
|
||
* @property {Function} disabledFun 禁止选择的日期函数
|
||
* @property {String} disabledColor 禁用日期的文字颜色
|
||
* @property {Boolean} showLunar 是否显示农历
|
||
* @property {Boolean} padZero 是否对小于10的数字补0
|
||
* @property {Function} formatter 日期格式化函数
|
||
* @property {Object} customStyle 定义需要用到的外部样式
|
||
* @event {Function} change 点击日期时触发
|
||
* @example <u-date-strip v-model="date" @change="onChange"></u-date-strip>
|
||
*/
|
||
export default {
|
||
name: 'u-date-strip',
|
||
mixins: [mpMixin, mixin, props],
|
||
data() {
|
||
return {
|
||
currentIndex: 0,
|
||
weekList: [],
|
||
allDays: [],
|
||
scrollIntoView: '',
|
||
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
|
||
innerSelected: null, // 内部选中的时间戳
|
||
innerFormatter: (value) => value
|
||
};
|
||
},
|
||
computed: {
|
||
// 当前选中的值
|
||
currentValue() {
|
||
// #ifdef VUE2
|
||
return this.value || this.defaultDate;
|
||
// #endif
|
||
// #ifdef VUE3
|
||
return this.modelValue || this.defaultDate;
|
||
// #endif
|
||
},
|
||
|
||
// 容器样式
|
||
containerStyle() {
|
||
const style = {};
|
||
if (this.bgColor) {
|
||
style.backgroundColor = this.bgColor;
|
||
}
|
||
if (this.height) {
|
||
style.height = this.height;
|
||
}
|
||
|
||
if(this.round){
|
||
style.borderRadius = this.round;
|
||
}
|
||
|
||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
|
||
},
|
||
|
||
// 获取日期数字样式的计算属性
|
||
itemStyle() {
|
||
return (type, item) => {
|
||
let style = {};
|
||
|
||
if(type == 'week'){
|
||
style.width = this.itemWidth;
|
||
}
|
||
|
||
// 禁用日期的样式
|
||
if (item.disabled) {
|
||
style.color = this.disabledColor;
|
||
style.pointerEvents = 'none';
|
||
}
|
||
|
||
if (this.isSameDay(dayjs(this.innerSelected), dayjs(item.timestamp))) {
|
||
style.borderRadius = this.itemRound;
|
||
|
||
if(type == 'week'){
|
||
// 整个容器的高亮样式
|
||
if (this.activeMode == 'both') {
|
||
style.backgroundColor = this.activeBgColor;
|
||
style.color = this.activeColor;
|
||
style = uni.$u.deepMerge(style, uni.$u.addStyle(this.activeStyle))
|
||
}else if(this.activeMode == 'text'){
|
||
style.color = this.activeBgColor;
|
||
}
|
||
}
|
||
|
||
if(type == 'date'){
|
||
if (this.activeMode == 'date') {
|
||
//只高亮日期部分
|
||
style.backgroundColor = this.activeBgColor;
|
||
style.color = this.activeColor;
|
||
style = uni.$u.deepMerge(style, uni.$u.addStyle(this.activeStyle))
|
||
} else if (this.activeMode == 'both') {
|
||
// 只高亮文字颜色
|
||
style.color = this.activeColor;
|
||
}else if(this.activeMode == 'text'){
|
||
style.color = this.activeBgColor;
|
||
}
|
||
}
|
||
|
||
if(type == 'lunar'){
|
||
style.color = this.activeColor;
|
||
}
|
||
}
|
||
|
||
return style;
|
||
};
|
||
},
|
||
},
|
||
watch: {
|
||
currentValue: {
|
||
immediate: true,
|
||
handler(newVal) {
|
||
this.innerSelected = newVal;
|
||
},
|
||
}
|
||
},
|
||
mounted() {
|
||
this.$nextTick(() => {
|
||
this.init();
|
||
});
|
||
},
|
||
methods: {
|
||
// 初始化
|
||
init() {
|
||
if (this.mode === 'week') {
|
||
this.initWeekMode();
|
||
} else {
|
||
this.initScrollMode();
|
||
}
|
||
},
|
||
|
||
// 获取日期范围
|
||
getDateRange() {
|
||
const monthNum = parseInt(this.monthNum);
|
||
const minDate = this.minDate ? dayjs(this.minDate) : dayjs().subtract(monthNum, 'month');
|
||
const maxDate = this.maxDate ? dayjs(this.maxDate) : dayjs().add(monthNum, 'month');
|
||
return { minDate, maxDate };
|
||
},
|
||
|
||
// 初始化周模式
|
||
initWeekMode() {
|
||
this.weekList = [];
|
||
const current = dayjs(this.currentValue);
|
||
|
||
const currentWeekStart = this.getWeekStart(current);
|
||
const { minDate, maxDate } = this.getDateRange();
|
||
|
||
// 计算需要生成的周数
|
||
const startWeek = this.getWeekStart(minDate);
|
||
const endWeek = this.getWeekStart(maxDate);
|
||
const totalWeeks = endWeek.diff(startWeek, 'week') + 1;
|
||
|
||
// 生成指定范围内的周数据
|
||
for (let i = 0; i < totalWeeks; i++) {
|
||
const weekStart = startWeek.add(i, 'week');
|
||
const week = this.generateWeek(weekStart);
|
||
|
||
this.weekList.push(week);
|
||
}
|
||
|
||
// 设置当前周的索引
|
||
const currentWeekIndex = currentWeekStart.diff(startWeek, 'week');
|
||
this.currentIndex = Math.max(0, currentWeekIndex + 1);
|
||
},
|
||
|
||
// 初始化滚动模式
|
||
initScrollMode() {
|
||
this.allDays = [];
|
||
const { minDate, maxDate } = this.getDateRange();
|
||
|
||
let currentDate = minDate;
|
||
let index = 0;
|
||
let selectedIndex = -1;
|
||
|
||
while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate, 'day')) {
|
||
const day = this.generateDay(currentDate);
|
||
this.allDays.push(day);
|
||
|
||
// 记录选中日期的索引
|
||
if (this.isSameDay(currentDate, dayjs(this.currentValue))) {
|
||
selectedIndex = index;
|
||
}
|
||
|
||
currentDate = currentDate.add(1, 'day');
|
||
index++;
|
||
}
|
||
|
||
// 滚动到选中的日期
|
||
if (selectedIndex >= 0) {
|
||
this.$nextTick(() => {
|
||
this.scrollIntoView = `item-${selectedIndex}`;
|
||
});
|
||
}
|
||
},
|
||
|
||
// 获取周的开始日期
|
||
getWeekStart(date) {
|
||
const day = date.day();
|
||
const diff = (day - this.firstDayOfWeek + 7) % 7;
|
||
return date.subtract(diff, 'day');
|
||
},
|
||
|
||
// 生成一周的数据
|
||
generateWeek(weekStart) {
|
||
const week = [];
|
||
for (let i = 0; i < 7; i++) {
|
||
const date = weekStart.add(i, 'day');
|
||
|
||
week.push(this.generateDay(date));
|
||
}
|
||
return week;
|
||
},
|
||
|
||
// 生成单天数据
|
||
generateDay(date) {
|
||
const day = {
|
||
date: this.padZero && date.date() < 10 ? `0${date.date()}` : date.date(),
|
||
weekday: this.weekdays[date.day()],
|
||
timestamp: date.valueOf(),
|
||
disabled: false,
|
||
bottomInfo: '',
|
||
};
|
||
|
||
// 计算农历
|
||
if (this.showLunar) {
|
||
const lunar = Calendar.solar2lunar(
|
||
date.year(),
|
||
date.month() + 1,
|
||
date.date(),
|
||
);
|
||
day.bottomInfo = lunar.IDayCn;
|
||
}
|
||
|
||
// 检查是否在可选范围内
|
||
if (this.minDate && date.isBefore(this.minDate)) {
|
||
day.disabled = true;
|
||
}
|
||
if (this.maxDate && date.isAfter(this.maxDate)) {
|
||
day.disabled = true;
|
||
}
|
||
|
||
// 检查是否在禁用日期列表中
|
||
if (this.disabledDate) {
|
||
if (uni.$u.test.string(this.disabledDate)) {
|
||
this.disabledDate = [
|
||
this.disabledDate,
|
||
];
|
||
}
|
||
|
||
this.disabledDate.forEach((item) => {
|
||
if (this.isSameDay(dayjs(item), date)) {
|
||
day.disabled = true;
|
||
}
|
||
});
|
||
}
|
||
|
||
if (this.disabledFun && uni.$u.test.func(this.disabledFun)) {
|
||
let result = this.disabledFun(day);
|
||
if (uni.$u.test.array(result)) {
|
||
day.disabled = result[0];
|
||
day.bottomInfo = result[1];
|
||
} else {
|
||
day.disabled = result;
|
||
}
|
||
}
|
||
|
||
// 应用格式化函数
|
||
const formatter = this.formatter || this.innerFormatter;
|
||
return formatter(day);
|
||
},
|
||
|
||
// 判断是否是同一天
|
||
isSameDay(date1, date2) {
|
||
return date1.isSame(date2, 'day');
|
||
},
|
||
|
||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
||
setFormatter(e) {
|
||
this.innerFormatter = e
|
||
},
|
||
|
||
// 选择日期
|
||
selectDate(item) {
|
||
if (item.disabled) return;
|
||
|
||
this.innerSelected = item.timestamp;
|
||
|
||
// #ifdef VUE2
|
||
this.$emit('input', item.timestamp);
|
||
// #endif
|
||
|
||
// #ifdef VUE3
|
||
this.$emit('update:modelValue', item.timestamp);
|
||
// #endif
|
||
|
||
this.$emit('change', {
|
||
weekday: item.weekday,
|
||
date: item.date,
|
||
timestamp: item.timestamp,
|
||
lunar: item.lunar
|
||
});
|
||
},
|
||
|
||
onSwiperChange(e) {
|
||
this.currentIndex = e.detail.current;
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '../../libs/css/components.scss';
|
||
|
||
.u-date-strip {
|
||
padding: 8px 0;
|
||
&__swiper {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
&__scroll {
|
||
width: 100%;
|
||
height: 100%;
|
||
white-space: nowrap;
|
||
&-wrapper {
|
||
@include flex(row);
|
||
align-items: center;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
&__wrapper {
|
||
@include flex(row);
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
height: 100%;
|
||
}
|
||
|
||
&__item {
|
||
flex: 1;
|
||
height: 100%;
|
||
@include flex(column);
|
||
align-items: center;
|
||
justify-content: center;
|
||
&-content{
|
||
@include flex(column);
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
padding: 5px 0;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
&__weekday {
|
||
font-size: 14px;
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
&__date {
|
||
flex: 1;
|
||
@include flex(column);
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
width: 100%;
|
||
&-text{
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
&__lunar {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
text-align: center;
|
||
margin-bottom: 5px;
|
||
}
|
||
}
|
||
</style>
|