日历组件封装

This commit is contained in:
WindowBird 2025-11-05 10:20:41 +08:00
parent 9cd65a61d6
commit b93b36c1b5
2 changed files with 307 additions and 270 deletions

View File

@ -0,0 +1,291 @@
<template>
<view class="schedule-editor">
<!-- 月份日历组件 -->
<MonthCalendar
:selected-date="selectedDate"
:events="allEvents"
@change="handleDateChange"
/>
<view
class="swipe-container"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
<view
class="swipe-wrapper"
:style="{ transform: `translateX(${translateX}px)`, transition: isAnimating ? 'transform 0.3s ease-out' : 'none' }"
>
<!-- 昨天的表格 -->
<view class="table-item">
<TimeTable :hours="hours" :events="prevDayEvents" />
</view>
<!-- 今天的表格 -->
<view class="table-item">
<TimeTable :hours="hours" :events="eventsInDay" />
</view>
<!-- 明天的表格 -->
<view class="table-item">
<TimeTable :hours="hours" :events="nextDayEvents" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import MonthCalendar from '@/components/MonthCalendar.vue';
import TimeTable from '@/components/TimeTable.vue';
const props = defineProps({
events: {
type: Array,
default: () => []
}
});
const emit = defineEmits(['date-change']);
// YYYY-MM-DD
const selectedDate = ref(new Date().toISOString().slice(0, 10));
//
const hours = Array.from({length: 24}, (_,i)=>i); // 0~23
//
const allEvents = computed(() => props.events || []);
//
const eventsInDay = computed(() => {
const filtered = allEvents.value.filter(e=>e.date===selectedDate.value);
console.log('计算 eventsInDayselectedDate:', selectedDate.value, '过滤后事件数:', filtered.length);
return filtered;
});
//
const prevDate = computed(() => {
const date = new Date(selectedDate.value);
date.setDate(date.getDate() - 1);
return date.toISOString().slice(0, 10);
});
const nextDate = computed(() => {
const date = new Date(selectedDate.value);
date.setDate(date.getDate() + 1);
return date.toISOString().slice(0, 10);
});
//
const prevDayEvents = computed(() => {
return allEvents.value.filter(e => e.date === prevDate.value);
});
//
const nextDayEvents = computed(() => {
return allEvents.value.filter(e => e.date === nextDate.value);
});
//
const handleDateChange = (dateStr) => {
selectedDate.value = dateStr;
console.log('通过日历组件更新选择日期:', selectedDate.value);
console.log('过滤后的事件数:', eventsInDay.value.length);
emit('date-change', dateStr);
}
//
const touchStartX = ref(0);
const touchStartY = ref(0);
const screenWidth = ref(375); // mounted
const translateX = ref(-375); // -375mounted
const baseTranslateX = ref(-375); //
const isAnimating = ref(false); //
const isDragging = ref(false); //
const isInitialized = ref(false); //
//
const initScreenWidth = () => {
uni.getSystemInfo({
success: (res) => {
const width = res.windowWidth || res.screenWidth || 375;
screenWidth.value = width;
//
if (!isInitialized.value) {
translateX.value = -width;
baseTranslateX.value = -width;
isInitialized.value = true;
}
}
});
};
//
const handleTouchStart = (e) => {
if (isAnimating.value) return;
const touch = e.touches[0];
touchStartX.value = touch.clientX;
touchStartY.value = touch.clientY;
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;
const deltaY = Math.abs(touch.clientY - touchStartY.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 deltaY = Math.abs(touch.clientY - touchStartY.value);
const minSwipeDistance = screenWidth.value * 0.2; // 20%
//
const isHorizontalSwipe = Math.abs(deltaX) > deltaY && Math.abs(deltaX) > minSwipeDistance;
isDragging.value = false;
if (isHorizontalSwipe) {
//
if (deltaX > 0) {
//
slideToPreviousDay();
} else {
//
slideToNextDay();
}
} else {
//
resetToCenter();
}
};
//
const resetToCenter = () => {
isAnimating.value = true;
translateX.value = -screenWidth.value;
setTimeout(() => {
isAnimating.value = false;
}, 300); // transition 0.3s
};
//
const slideToPreviousDay = () => {
isAnimating.value = true;
// -screenWidth * 2
const targetX = -screenWidth.value;
translateX.value = targetX;
// 300ms transition
setTimeout(() => {
// transition
isAnimating.value = false;
// transition
setTimeout(() => {
//
const currentDate = new Date(selectedDate.value);
currentDate.setDate(currentDate.getDate() - 1);
selectedDate.value = currentDate.toISOString().slice(0, 10);
// prev/next transition
translateX.value = -screenWidth.value;
baseTranslateX.value = -screenWidth.value;
console.log(`日期切换:上一天,新日期:${selectedDate.value}`);
emit('date-change', selectedDate.value);
}, 0); // transition
}, 300); // transition 0.3s
};
//
const slideToNextDay = () => {
isAnimating.value = true;
// 0
const targetX = -screenWidth.value;
translateX.value = targetX;
// 300ms transition
setTimeout(() => {
// transition
isAnimating.value = false;
// transition
setTimeout(() => {
//
const currentDate = new Date(selectedDate.value);
currentDate.setDate(currentDate.getDate() + 1);
selectedDate.value = currentDate.toISOString().slice(0, 10);
// prev/next transition
translateX.value = -screenWidth.value;
baseTranslateX.value = -screenWidth.value;
console.log(`日期切换:下一天,新日期:${selectedDate.value}`);
emit('date-change', selectedDate.value);
}, 16); // transition
}, 300); // transition 0.3s
};
//
watch(selectedDate, () => {
if (!isDragging.value && !isAnimating.value) {
resetToCenter();
}
});
//
onMounted(() => {
initScreenWidth();
});
//
defineExpose({
selectedDate
});
</script>
<style lang="scss" scoped>
.schedule-editor {
width: 100%;
height: 100%;
}
.swipe-container {
position: relative;
width: 100%;
overflow: hidden;
touch-action: pan-y; /* 允许垂直滚动,但控制水平滑动 */
}
.swipe-wrapper {
display: flex;
width: 300%; /* 三个表格的宽度 */
will-change: transform; /* 优化性能 */
}
.table-item {
flex: 0 0 33.333%; /* 每个表格占33.333% */
width: 33.333%;
min-width: 0;
}
</style>

View File

@ -7,39 +7,12 @@
<!-- 内容区域 -->
<view class="content-wrapper">
<!-- 日程编辑 -->
<view v-if="topTabValue === 0" class="schedule-content">
<!-- 月份日历组件 -->
<MonthCalendar
:selected-date="selectedDate"
:events="allEvents"
@change="handleDateChange"
/>
<view
class="swipe-container"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
<view
class="swipe-wrapper"
:style="{ transform: `translateX(${translateX}px)`, transition: isAnimating ? 'transform 0.3s ease-out' : 'none' }"
>
<!-- 昨天的表格 -->
<view class="table-item">
<TimeTable :hours="hours" :events="prevDayEvents" />
</view>
<!-- 今天的表格 -->
<view class="table-item">
<TimeTable :hours="hours" :events="eventsInDay" />
</view>
<!-- 明天的表格 -->
<view class="table-item">
<TimeTable :hours="hours" :events="nextDayEvents" />
</view>
</view>
</view>
</view>
<ScheduleEditor
v-if="topTabValue === 0"
ref="scheduleEditorRef"
:events="allEvents"
@date-change="handleDateChange"
/>
<!-- 内容看板 -->
<ContentDashboard v-if="topTabValue === 1" />
@ -75,10 +48,9 @@
import { ref, computed, watch, onMounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import TimeTable from '@/components/TimeTable.vue';
import FabPlus from '@/components/FabPlus.vue';
import AddEventModal from '@/components/AddEventModal.vue';
import MonthCalendar from '@/components/MonthCalendar.vue';
import ScheduleEditor from '@/components/ScheduleEditor.vue';
import ContentDashboard from '@/components/ContentDashboard.vue';
import TodoList from '@/components/TodoList.vue';
import MessageContent from '@/components/MessageContent.vue';
@ -98,11 +70,9 @@ function clickTab(item) {
console.log('切换tab:', item.name);
}
// YYYY-MM-DD
// YYYY-MM-DD
const selectedDate = ref(new Date().toISOString().slice(0, 10));
//
const hours = Array.from({length: 24}, (_,i)=>i); // 0~23
//
const today = new Date().toISOString().slice(0, 10);
//
@ -121,59 +91,28 @@ const allEvents = ref([
{id:6,title:'昨天的日程3',startHour:10,startMin:30,color:'#e3fae6',date:yesterdayStr},
]);
//
const eventsInDay = computed(() => {
const filtered = allEvents.value.filter(e=>e.date===selectedDate.value);
console.log('计算 eventsInDayselectedDate:', selectedDate.value, '过滤后事件数:', filtered.length);
return filtered;
});
//
const prevDate = computed(() => {
const date = new Date(selectedDate.value);
date.setDate(date.getDate() - 1);
return date.toISOString().slice(0, 10);
});
const nextDate = computed(() => {
const date = new Date(selectedDate.value);
date.setDate(date.getDate() + 1);
return date.toISOString().slice(0, 10);
});
//
const prevDayEvents = computed(() => {
return allEvents.value.filter(e => e.date === prevDate.value);
});
//
const nextDayEvents = computed(() => {
return allEvents.value.filter(e => e.date === nextDate.value);
});
// selectedDate
watch(selectedDate, (newDate, oldDate) => {
console.log('selectedDate 发生变化:', oldDate, '->', newDate);
console.log('eventsInDay 新值:', eventsInDay.value);
}, { immediate: true });
//
// ScheduleEditor
const handleDateChange = (dateStr) => {
selectedDate.value = dateStr;
console.log('通过日历组件更新选择日期:', selectedDate.value);
console.log('过滤后的事件数:', eventsInDay.value.length);
}
// /
const showAdd = ref(false);
// ScheduleEditor
const scheduleEditorRef = ref(null);
//
const handleAddClick = () => {
// ScheduleEditor
const currentDate = scheduleEditorRef.value?.selectedDate || selectedDate.value;
uni.navigateTo({
url: `/pages/add-event/index?date=${selectedDate.value}`
url: `/pages/add-event/index?date=${currentDate}`
});
};
//
function addEvent(e) {
// e{title, startHour, startMin, color, date}date使selectedDate
@ -187,171 +126,6 @@ function addEvent(e) {
const value=ref(0);
//
const touchStartX = ref(0);
const touchStartY = ref(0);
const screenWidth = ref(375); // mounted
const translateX = ref(-375); // -375mounted
const baseTranslateX = ref(-375); //
const isAnimating = ref(false); //
const isDragging = ref(false); //
const isInitialized = ref(false); //
//
const initScreenWidth = () => {
uni.getSystemInfo({
success: (res) => {
const width = res.windowWidth || res.screenWidth || 375;
screenWidth.value = width;
//
if (!isInitialized.value) {
translateX.value = -width;
baseTranslateX.value = -width;
isInitialized.value = true;
}
}
});
};
//
const handleTouchStart = (e) => {
if (isAnimating.value) return;
const touch = e.touches[0];
touchStartX.value = touch.clientX;
touchStartY.value = touch.clientY;
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;
const deltaY = Math.abs(touch.clientY - touchStartY.value);
// //
// if (Math.abs(deltaX) > deltaY && Math.abs(deltaX) > 10) {
// e.preventDefault();
// }
//
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 deltaY = Math.abs(touch.clientY - touchStartY.value);
const minSwipeDistance = screenWidth.value * 0.2; // 20%
//
const isHorizontalSwipe = Math.abs(deltaX) > deltaY && Math.abs(deltaX) > minSwipeDistance;
isDragging.value = false;
if (isHorizontalSwipe) {
//
if (deltaX > 0) {
//
slideToPreviousDay();
} else {
//
slideToNextDay();
}
} else {
//
resetToCenter();
}
};
//
const resetToCenter = () => {
isAnimating.value = true;
translateX.value = -screenWidth.value;
setTimeout(() => {
isAnimating.value = false;
}, 0); // transition 0.3s
};
//
const slideToPreviousDay = () => {
isAnimating.value = true;
// -screenWidth * 2
const targetX = -screenWidth.value;
translateX.value = targetX;
// 300ms transition
setTimeout(() => {
// transition
isAnimating.value = false;
// transition
setTimeout(() => {
//
const currentDate = new Date(selectedDate.value);
currentDate.setDate(currentDate.getDate() - 1);
selectedDate.value = currentDate.toISOString().slice(0, 10);
// prev/next transition
translateX.value = -screenWidth.value;
baseTranslateX.value = -screenWidth.value;
console.log(`日期切换:上一天,新日期:${selectedDate.value}`);
}, 0); // transition
}, 0); // transition 0.3s
};
//
const slideToNextDay = () => {
isAnimating.value = true;
// 0
const targetX = -screenWidth.value;
translateX.value = targetX;
// 300ms transition
setTimeout(() => {
// transition
isAnimating.value = false;
// transition
setTimeout(() => {
//
const currentDate = new Date(selectedDate.value);
currentDate.setDate(currentDate.getDate() + 1);
selectedDate.value = currentDate.toISOString().slice(0, 10);
// prev/next transition
translateX.value = -screenWidth.value;
baseTranslateX.value = -screenWidth.value;
console.log(`日期切换:下一天,新日期:${selectedDate.value}`);
}, 16); // transition
}, 300); // transition 0.3s
};
//
watch(selectedDate, () => {
if (!isDragging.value && !isAnimating.value) {
resetToCenter();
}
});
//
onMounted(() => {
initScreenWidth();
});
//
onShow(() => {
//
@ -396,33 +170,5 @@ onShow(() => {
margin-top: calc(var(--status-bar-height, 0) + 44px);
}
.schedule-content {
width: 100%;
height: 100%;
}
:deep(.bottom-tabbar) { z-index: 1000 !important; }
.schedule-timeline {
padding-bottom: 130rpx !important;
}
.swipe-container {
position: relative;
width: 100%;
overflow: hidden;
touch-action: pan-y; /* 允许垂直滚动,但控制水平滑动 */
}
.swipe-wrapper {
display: flex;
width: 300%; /* 三个表格的宽度 */
will-change: transform; /* 优化性能 */
}
.table-item {
flex: 0 0 33.333%; /* 每个表格占33.333% */
width: 33.333%;
min-width: 0;
}
</style>