自定义日历
This commit is contained in:
parent
a364931dc7
commit
44c8d503c2
663
components/MonthCalendar.vue
Normal file
663
components/MonthCalendar.vue
Normal file
|
|
@ -0,0 +1,663 @@
|
||||||
|
<template>
|
||||||
|
<view class="month-calendar-container">
|
||||||
|
<!-- 日期选择器头部 -->
|
||||||
|
<view class="calendar-header" @click="toggleCalendar">
|
||||||
|
<view class="date-display">
|
||||||
|
<text class="year-month">{{ displayYearMonth }}</text>
|
||||||
|
<text class="day">{{ displayDay }}</text>
|
||||||
|
<text class="weekday">{{ displayWeekday }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="event-count" v-if="eventCount > 0">
|
||||||
|
<text>日程数:{{ eventCount }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="arrow-icon" :class="{ 'rotate': isExpanded }">
|
||||||
|
<text>▼</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历下拉区域 -->
|
||||||
|
<view class="calendar-dropdown" :class="{ 'expanded': isExpanded }">
|
||||||
|
<!-- 月份切换栏 -->
|
||||||
|
<view class="month-header">
|
||||||
|
<view class="month-nav-btn" @click="prevMonth">
|
||||||
|
<text>‹</text>
|
||||||
|
</view>
|
||||||
|
<view class="month-title">{{ currentYear }}年{{ currentMonth }}月</view>
|
||||||
|
<view class="month-nav-btn" @click="nextMonth">
|
||||||
|
<text>›</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 星期标题 -->
|
||||||
|
<view class="weekdays">
|
||||||
|
<view class="weekday-item" v-for="day in weekdays" :key="day">{{ day }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历滑动容器 -->
|
||||||
|
<view
|
||||||
|
class="calendar-swipe-container"
|
||||||
|
@touchstart="handleTouchStart"
|
||||||
|
@touchmove="handleTouchMove"
|
||||||
|
@touchend="handleTouchEnd"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="calendar-wrapper"
|
||||||
|
:style="{
|
||||||
|
transform: `translateX(${translateX}px)`,
|
||||||
|
transition: isAnimating ? 'transform 0.3s ease-out' : 'none'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- 上一个月 -->
|
||||||
|
<view class="calendar-month">
|
||||||
|
<view
|
||||||
|
class="calendar-day"
|
||||||
|
v-for="(dayObj, index) in prevMonthDays"
|
||||||
|
:key="`prev-${index}`"
|
||||||
|
:class="{
|
||||||
|
'other-month': !dayObj.isCurrentMonth,
|
||||||
|
'today': isToday(dayObj),
|
||||||
|
'selected': isSelected(dayObj),
|
||||||
|
'has-event': hasEvent(dayObj)
|
||||||
|
}"
|
||||||
|
@click="selectDate(dayObj)"
|
||||||
|
>
|
||||||
|
<text class="day-number">{{ dayObj.day }}</text>
|
||||||
|
<view class="event-dot" v-if="hasEvent(dayObj)"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 当前月 -->
|
||||||
|
<view class="calendar-month">
|
||||||
|
<view
|
||||||
|
class="calendar-day"
|
||||||
|
v-for="(dayObj, index) in currentMonthDays"
|
||||||
|
:key="`current-${index}`"
|
||||||
|
:class="{
|
||||||
|
'other-month': !dayObj.isCurrentMonth,
|
||||||
|
'today': isToday(dayObj),
|
||||||
|
'selected': isSelected(dayObj),
|
||||||
|
'has-event': hasEvent(dayObj)
|
||||||
|
}"
|
||||||
|
@click="selectDate(dayObj)"
|
||||||
|
>
|
||||||
|
<text class="day-number">{{ dayObj.day }}</text>
|
||||||
|
<view class="event-dot" v-if="hasEvent(dayObj)"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 下一个月 -->
|
||||||
|
<view class="calendar-month">
|
||||||
|
<view
|
||||||
|
class="calendar-day"
|
||||||
|
v-for="(dayObj, index) in nextMonthDays"
|
||||||
|
:key="`next-${index}`"
|
||||||
|
:class="{
|
||||||
|
'other-month': !dayObj.isCurrentMonth,
|
||||||
|
'today': isToday(dayObj),
|
||||||
|
'selected': isSelected(dayObj),
|
||||||
|
'has-event': hasEvent(dayObj)
|
||||||
|
}"
|
||||||
|
@click="selectDate(dayObj)"
|
||||||
|
>
|
||||||
|
<text class="day-number">{{ dayObj.day }}</text>
|
||||||
|
<view class="event-dot" v-if="hasEvent(dayObj)"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
selectedDate: {
|
||||||
|
type: String,
|
||||||
|
default: () => new Date().toISOString().slice(0, 10)
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
// 展开/收起状态
|
||||||
|
const isExpanded = ref(false);
|
||||||
|
|
||||||
|
// 当前显示的年月
|
||||||
|
const currentYear = ref(new Date().getFullYear());
|
||||||
|
const currentMonth = ref(new Date().getMonth() + 1);
|
||||||
|
|
||||||
|
// 星期标题
|
||||||
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
||||||
|
|
||||||
|
// 滑动相关
|
||||||
|
const touchStartX = ref(0);
|
||||||
|
const screenWidth = ref(375);
|
||||||
|
const translateX = ref(0);
|
||||||
|
const baseTranslateX = ref(0);
|
||||||
|
const isAnimating = ref(false);
|
||||||
|
const isDragging = ref(false);
|
||||||
|
|
||||||
|
// 初始化屏幕宽度
|
||||||
|
const initScreenWidth = () => {
|
||||||
|
uni.getSystemInfo({
|
||||||
|
success: (res) => {
|
||||||
|
screenWidth.value = res.windowWidth || res.screenWidth || 375;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取月份的第一天是星期几(0-6,0是星期日)
|
||||||
|
const getFirstDayOfMonth = (year, month) => {
|
||||||
|
return new Date(year, month - 1, 1).getDay();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取月份的天数
|
||||||
|
const getDaysInMonth = (year, month) => {
|
||||||
|
return new Date(year, month, 0).getDate();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成月份的日期数组(包含前后月份的填充日期)
|
||||||
|
const generateMonthDays = (year, month) => {
|
||||||
|
const firstDay = getFirstDayOfMonth(year, month);
|
||||||
|
const daysInMonth = getDaysInMonth(year, month);
|
||||||
|
const days = [];
|
||||||
|
|
||||||
|
// 计算上个月的最后几天
|
||||||
|
const prevMonth = month === 1 ? 12 : month - 1;
|
||||||
|
const prevYear = month === 1 ? year - 1 : year;
|
||||||
|
const prevMonthDays = getDaysInMonth(prevYear, prevMonth);
|
||||||
|
|
||||||
|
// 填充前面月份的日期(从最后几天开始)
|
||||||
|
if (firstDay > 0) {
|
||||||
|
for (let i = firstDay - 1; i >= 0; i--) {
|
||||||
|
days.push({
|
||||||
|
day: prevMonthDays - i,
|
||||||
|
year: prevYear,
|
||||||
|
month: prevMonth,
|
||||||
|
isCurrentMonth: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充当前月份的日期
|
||||||
|
for (let i = 1; i <= daysInMonth; i++) {
|
||||||
|
days.push({
|
||||||
|
day: i,
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
isCurrentMonth: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充后面月份的日期(确保有6行,即42个格子)
|
||||||
|
const totalDays = days.length;
|
||||||
|
const remainingDays = 42 - totalDays; // 6行 * 7列 = 42
|
||||||
|
const nextMonth = month === 12 ? 1 : month + 1;
|
||||||
|
const nextYear = month === 12 ? year + 1 : year;
|
||||||
|
|
||||||
|
for (let i = 1; i <= remainingDays; i++) {
|
||||||
|
days.push({
|
||||||
|
day: i,
|
||||||
|
year: nextYear,
|
||||||
|
month: nextMonth,
|
||||||
|
isCurrentMonth: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return days;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算当前月份的日期
|
||||||
|
const currentMonthDays = computed(() => {
|
||||||
|
return generateMonthDays(currentYear.value, currentMonth.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算上一个月
|
||||||
|
const prevMonthYear = computed(() => {
|
||||||
|
if (currentMonth.value === 1) {
|
||||||
|
return currentYear.value - 1;
|
||||||
|
}
|
||||||
|
return currentYear.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevMonthMonth = computed(() => {
|
||||||
|
if (currentMonth.value === 1) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return currentMonth.value - 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevMonthDays = computed(() => {
|
||||||
|
return generateMonthDays(prevMonthYear.value, prevMonthMonth.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算下一个月
|
||||||
|
const nextMonthYear = computed(() => {
|
||||||
|
if (currentMonth.value === 12) {
|
||||||
|
return currentYear.value + 1;
|
||||||
|
}
|
||||||
|
return currentYear.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextMonthMonth = computed(() => {
|
||||||
|
if (currentMonth.value === 12) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return currentMonth.value + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextMonthDays = computed(() => {
|
||||||
|
return generateMonthDays(nextMonthYear.value, nextMonthMonth.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 格式化日期为 YYYY-MM-DD
|
||||||
|
const formatDate = (year, month, day) => {
|
||||||
|
const m = String(month).padStart(2, '0');
|
||||||
|
const d = String(day).padStart(2, '0');
|
||||||
|
return `${year}-${m}-${d}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否是今天
|
||||||
|
const isToday = (dayObj) => {
|
||||||
|
if (!dayObj || !dayObj.day) return false;
|
||||||
|
const today = new Date();
|
||||||
|
return (
|
||||||
|
dayObj.year === today.getFullYear() &&
|
||||||
|
dayObj.month === today.getMonth() + 1 &&
|
||||||
|
dayObj.day === today.getDate()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否被选中
|
||||||
|
const isSelected = (dayObj) => {
|
||||||
|
if (!dayObj || !dayObj.day) return false;
|
||||||
|
const selected = new Date(props.selectedDate);
|
||||||
|
return (
|
||||||
|
dayObj.year === selected.getFullYear() &&
|
||||||
|
dayObj.month === selected.getMonth() + 1 &&
|
||||||
|
dayObj.day === selected.getDate()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否有事件
|
||||||
|
const hasEvent = (dayObj) => {
|
||||||
|
if (!dayObj || !dayObj.day) return false;
|
||||||
|
const dateStr = formatDate(dayObj.year, dayObj.month, dayObj.day);
|
||||||
|
return props.events.some(event => event.date === dateStr);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择日期
|
||||||
|
const selectDate = (dayObj) => {
|
||||||
|
if (!dayObj || !dayObj.day) return;
|
||||||
|
const dateStr = formatDate(dayObj.year, dayObj.month, dayObj.day);
|
||||||
|
emit('change', dateStr);
|
||||||
|
// 选择后不收起日历
|
||||||
|
// setTimeout(() => {
|
||||||
|
// isExpanded.value = false;
|
||||||
|
// }, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换日历展开/收起
|
||||||
|
const toggleCalendar = () => {
|
||||||
|
isExpanded.value = !isExpanded.value;
|
||||||
|
if (isExpanded.value) {
|
||||||
|
initScreenWidth();
|
||||||
|
// 同步当前显示月份到选中日期
|
||||||
|
const selected = new Date(props.selectedDate);
|
||||||
|
currentYear.value = selected.getFullYear();
|
||||||
|
currentMonth.value = selected.getMonth() + 1;
|
||||||
|
translateX.value = -screenWidth.value;
|
||||||
|
baseTranslateX.value = -screenWidth.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换到上一个月
|
||||||
|
const prevMonth = () => {
|
||||||
|
if (currentMonth.value === 1) {
|
||||||
|
currentYear.value -= 1;
|
||||||
|
currentMonth.value = 12;
|
||||||
|
} else {
|
||||||
|
currentMonth.value -= 1;
|
||||||
|
}
|
||||||
|
translateX.value = -screenWidth.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换到下一个月
|
||||||
|
const nextMonth = () => {
|
||||||
|
if (currentMonth.value === 12) {
|
||||||
|
currentYear.value += 1;
|
||||||
|
currentMonth.value = 1;
|
||||||
|
} else {
|
||||||
|
currentMonth.value += 1;
|
||||||
|
}
|
||||||
|
translateX.value = -screenWidth.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 触摸开始
|
||||||
|
const handleTouchStart = (e) => {
|
||||||
|
if (isAnimating.value) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
touchStartX.value = touch.clientX;
|
||||||
|
isDragging.value = true;
|
||||||
|
baseTranslateX.value = translateX.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 触摸移动
|
||||||
|
const handleTouchMove = (e) => {
|
||||||
|
if (!isDragging.value || isAnimating.value) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const deltaX = touch.clientX - touchStartX.value;
|
||||||
|
translateX.value = baseTranslateX.value + deltaX;
|
||||||
|
|
||||||
|
// 限制滑动范围
|
||||||
|
const minTranslate = -screenWidth.value * 2;
|
||||||
|
const maxTranslate = 0;
|
||||||
|
translateX.value = Math.max(minTranslate, Math.min(maxTranslate, translateX.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 触摸结束
|
||||||
|
const handleTouchEnd = (e) => {
|
||||||
|
if (!isDragging.value || isAnimating.value) return;
|
||||||
|
|
||||||
|
const touch = e.changedTouches[0];
|
||||||
|
const deltaX = touch.clientX - touchStartX.value;
|
||||||
|
const minSwipeDistance = screenWidth.value * 0.2;
|
||||||
|
|
||||||
|
isDragging.value = false;
|
||||||
|
|
||||||
|
if (Math.abs(deltaX) > minSwipeDistance) {
|
||||||
|
if (deltaX > 0) {
|
||||||
|
// 向左滑动,显示上一个月
|
||||||
|
slideToPrevMonth();
|
||||||
|
} else {
|
||||||
|
// 向右滑动,显示下一个月
|
||||||
|
slideToNextMonth();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 回到中间位置
|
||||||
|
resetToCenter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置到中心位置
|
||||||
|
const resetToCenter = () => {
|
||||||
|
isAnimating.value = true;
|
||||||
|
translateX.value = -screenWidth.value;
|
||||||
|
setTimeout(() => {
|
||||||
|
isAnimating.value = false;
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 滑动到上一个月
|
||||||
|
const slideToPrevMonth = () => {
|
||||||
|
isAnimating.value = true;
|
||||||
|
translateX.value = -screenWidth.value * 2;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isAnimating.value = false;
|
||||||
|
prevMonth();
|
||||||
|
setTimeout(() => {
|
||||||
|
translateX.value = -screenWidth.value;
|
||||||
|
baseTranslateX.value = -screenWidth.value;
|
||||||
|
}, 0);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 滑动到下一个月
|
||||||
|
const slideToNextMonth = () => {
|
||||||
|
isAnimating.value = true;
|
||||||
|
translateX.value = 0;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isAnimating.value = false;
|
||||||
|
nextMonth();
|
||||||
|
setTimeout(() => {
|
||||||
|
translateX.value = -screenWidth.value;
|
||||||
|
baseTranslateX.value = -screenWidth.value;
|
||||||
|
}, 0);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示的年月日
|
||||||
|
const displayYearMonth = computed(() => {
|
||||||
|
const date = new Date(props.selectedDate);
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayDay = computed(() => {
|
||||||
|
const date = new Date(props.selectedDate);
|
||||||
|
return String(date.getDate()).padStart(2, '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayWeekday = computed(() => {
|
||||||
|
const date = new Date(props.selectedDate);
|
||||||
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
||||||
|
return `星期${weekdays[date.getDay()]}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日程数量
|
||||||
|
const eventCount = computed(() => {
|
||||||
|
return props.events.filter(e => e.date === props.selectedDate).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听选中日期变化,同步月份显示
|
||||||
|
watch(() => props.selectedDate, (newDate) => {
|
||||||
|
if (!isExpanded.value) {
|
||||||
|
const selected = new Date(newDate);
|
||||||
|
currentYear.value = selected.getFullYear();
|
||||||
|
currentMonth.value = selected.getMonth() + 1;
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
initScreenWidth();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.month-calendar-container {
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-display {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-month {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2885ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
|
||||||
|
&.rotate {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-dropdown {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease-out;
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
max-height: 800rpx;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-nav-btn {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekdays {
|
||||||
|
display: flex;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-swipe-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
touch-action: pan-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
width: 300%;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-month {
|
||||||
|
flex: 0 0 33.333%;
|
||||||
|
width: 33.333%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-day {
|
||||||
|
flex: 0 0 calc(100% / 7);
|
||||||
|
width: calc(100% / 7);
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.other-month {
|
||||||
|
.day-number {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.today {
|
||||||
|
.day-number {
|
||||||
|
color: #2885ff;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
.day-number {
|
||||||
|
color: #fff;
|
||||||
|
background: #2885ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-event {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8rpx;
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
background: #2885ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-number {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-dot {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8rpx;
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
background: #2885ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "办公管理"
|
"navigationBarTitleText": "办公管理"
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ const formData = ref({
|
||||||
allDay: false,
|
allDay: false,
|
||||||
repeat: 'none',
|
repeat: 'none',
|
||||||
description: '',
|
description: '',
|
||||||
color: '#0079FE',
|
color: '#B3D9FF',
|
||||||
reminder: 15 // 提前多少分钟提醒
|
reminder: 15 // 提前多少分钟提醒
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -284,8 +284,12 @@ const endDateCalendar = ref(null);
|
||||||
|
|
||||||
// 颜色选项
|
// 颜色选项
|
||||||
const colorOptions = [
|
const colorOptions = [
|
||||||
'#0079FE', '#FC902A', '#FF505D', '#18B48A',
|
'#B3D9FF', // 更淡的蓝色
|
||||||
'#FCBF28', '#8883F0',
|
'#FFE8CC', // 更淡的橙色
|
||||||
|
'#FFD6D9', // 更淡的红色
|
||||||
|
'#D4F3E8', // 更淡的绿色
|
||||||
|
'#FFF4CC', // 更淡的黄色
|
||||||
|
'#E6E5FC', // 更淡的紫色
|
||||||
];
|
];
|
||||||
|
|
||||||
// 重复选项
|
// 重复选项
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
<uv-tabs :list="topTabs" @click="clickTab"></uv-tabs>
|
<uv-tabs :list="topTabs" @click="clickTab"></uv-tabs>
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view class="content-wrapper">
|
<view class="content-wrapper">
|
||||||
<view>
|
<!-- 月份日历组件 -->
|
||||||
<uv-calendar ref="calendar" mode="single" @confirm="handleConfirm" ></uv-calendar>
|
<MonthCalendar
|
||||||
<button @click="openCalendar">{{ selectedDate }},日程数:{{ eventsInDay ? eventsInDay.length : 0 }}</button>
|
:selected-date="selectedDate"
|
||||||
|
:events="allEvents"
|
||||||
</view>
|
@change="handleDateChange"
|
||||||
|
/>
|
||||||
|
|
||||||
<view
|
<view
|
||||||
class="swipe-container"
|
class="swipe-container"
|
||||||
|
|
@ -58,6 +59,7 @@ import { onShow } from '@dcloudio/uni-app';
|
||||||
import TimeTable from '@/components/TimeTable.vue';
|
import TimeTable from '@/components/TimeTable.vue';
|
||||||
import FabPlus from '@/components/FabPlus.vue';
|
import FabPlus from '@/components/FabPlus.vue';
|
||||||
import AddEventModal from '@/components/AddEventModal.vue';
|
import AddEventModal from '@/components/AddEventModal.vue';
|
||||||
|
import MonthCalendar from '@/components/MonthCalendar.vue';
|
||||||
|
|
||||||
// 顶部tabs选项
|
// 顶部tabs选项
|
||||||
const topTabs = [
|
const topTabs = [
|
||||||
|
|
@ -132,65 +134,11 @@ watch(selectedDate, (newDate, oldDate) => {
|
||||||
console.log('eventsInDay 新值:', eventsInDay.value);
|
console.log('eventsInDay 新值:', eventsInDay.value);
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const calendar = ref(null)
|
// 处理日期变化
|
||||||
|
const handleDateChange = (dateStr) => {
|
||||||
// 格式化日期为 YYYY-MM-DD
|
selectedDate.value = dateStr;
|
||||||
function formatDateToYYYYMMDD(dateInput) {
|
console.log('通过日历组件更新选择日期:', selectedDate.value);
|
||||||
if (!dateInput) return '';
|
console.log('过滤后的事件数:', eventsInDay.value.length);
|
||||||
|
|
||||||
let dateStr = '';
|
|
||||||
|
|
||||||
// 如果已经是字符串格式 YYYY-MM-DD,直接返回
|
|
||||||
if (typeof dateInput === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
|
|
||||||
return dateInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理各种可能的输入格式
|
|
||||||
if (typeof dateInput === 'string') {
|
|
||||||
dateStr = dateInput;
|
|
||||||
} else if (dateInput?.date) {
|
|
||||||
dateStr = dateInput.date;
|
|
||||||
} else if (dateInput?.value) {
|
|
||||||
dateStr = dateInput.value;
|
|
||||||
} else if (Array.isArray(dateInput) && dateInput.length > 0) {
|
|
||||||
dateStr = typeof dateInput[0] === 'string' ? dateInput[0] : (dateInput[0]?.date || dateInput[0]?.value || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dateStr) return '';
|
|
||||||
|
|
||||||
// 尝试解析日期
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
if (!isNaN(date.getTime())) {
|
|
||||||
// 格式化为 YYYY-MM-DD
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
|
||||||
return `${year}-${month}-${day}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果已经是 YYYY-MM-DD 格式但 new Date 解析失败(如时区问题),尝试直接使用
|
|
||||||
if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) {
|
|
||||||
return dateStr.slice(0, 10);
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开日历方法
|
|
||||||
const openCalendar = () => {
|
|
||||||
if (calendar.value) {
|
|
||||||
calendar.value.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// confirm 事件处理(可能在确认时触发)
|
|
||||||
const handleConfirm = (e) => {
|
|
||||||
console.log('日历 confirm 事件:', e, typeof e);
|
|
||||||
const formattedDate = formatDateToYYYYMMDD(e);
|
|
||||||
if (formattedDate) {
|
|
||||||
selectedDate.value = formattedDate;
|
|
||||||
console.log('通过 confirm 更新选择日期:', selectedDate.value);
|
|
||||||
console.log('过滤后的事件数:', eventsInDay.value.length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 悬浮按钮/弹窗控制
|
// 悬浮按钮/弹窗控制
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user