日历组件封装
This commit is contained in:
parent
9cd65a61d6
commit
b93b36c1b5
291
components/ScheduleEditor.vue
Normal file
291
components/ScheduleEditor.vue
Normal 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('计算 eventsInDay,selectedDate:', 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); // 当前滑动偏移量,初始值设为 -375(假设屏幕宽度,会在mounted时更新)
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
@ -7,40 +7,13 @@
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view class="content-wrapper">
|
<view class="content-wrapper">
|
||||||
<!-- 日程编辑 -->
|
<!-- 日程编辑 -->
|
||||||
<view v-if="topTabValue === 0" class="schedule-content">
|
<ScheduleEditor
|
||||||
<!-- 月份日历组件 -->
|
v-if="topTabValue === 0"
|
||||||
<MonthCalendar
|
ref="scheduleEditorRef"
|
||||||
:selected-date="selectedDate"
|
|
||||||
:events="allEvents"
|
:events="allEvents"
|
||||||
@change="handleDateChange"
|
@date-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>
|
|
||||||
|
|
||||||
<!-- 内容看板 -->
|
<!-- 内容看板 -->
|
||||||
<ContentDashboard v-if="topTabValue === 1" />
|
<ContentDashboard v-if="topTabValue === 1" />
|
||||||
|
|
||||||
|
|
@ -75,10 +48,9 @@
|
||||||
import { ref, computed, watch, onMounted } from 'vue';
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
import { onShow } from '@dcloudio/uni-app';
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
|
|
||||||
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';
|
import ScheduleEditor from '@/components/ScheduleEditor.vue';
|
||||||
import ContentDashboard from '@/components/ContentDashboard.vue';
|
import ContentDashboard from '@/components/ContentDashboard.vue';
|
||||||
import TodoList from '@/components/TodoList.vue';
|
import TodoList from '@/components/TodoList.vue';
|
||||||
import MessageContent from '@/components/MessageContent.vue';
|
import MessageContent from '@/components/MessageContent.vue';
|
||||||
|
|
@ -98,11 +70,9 @@ function clickTab(item) {
|
||||||
console.log('切换tab:', item.name);
|
console.log('切换tab:', item.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前选择的日期,格式:YYYY-MM-DD
|
// 当前选择的日期,格式:YYYY-MM-DD(用于新建日程时传递日期)
|
||||||
const selectedDate = ref(new Date().toISOString().slice(0, 10));
|
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);
|
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},
|
{id:6,title:'昨天的日程3',startHour:10,startMin:30,color:'#e3fae6',date:yesterdayStr},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 根据当前选择日期过滤
|
// 处理日期变化(从 ScheduleEditor 组件传来)
|
||||||
const eventsInDay = computed(() => {
|
|
||||||
const filtered = allEvents.value.filter(e=>e.date===selectedDate.value);
|
|
||||||
console.log('计算 eventsInDay,selectedDate:', 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 });
|
|
||||||
|
|
||||||
// 处理日期变化
|
|
||||||
const handleDateChange = (dateStr) => {
|
const handleDateChange = (dateStr) => {
|
||||||
selectedDate.value = dateStr;
|
selectedDate.value = dateStr;
|
||||||
console.log('通过日历组件更新选择日期:', selectedDate.value);
|
console.log('通过日历组件更新选择日期:', selectedDate.value);
|
||||||
console.log('过滤后的事件数:', eventsInDay.value.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 悬浮按钮/弹窗控制
|
// 悬浮按钮/弹窗控制
|
||||||
const showAdd = ref(false);
|
const showAdd = ref(false);
|
||||||
|
|
||||||
|
// ScheduleEditor 组件引用
|
||||||
|
const scheduleEditorRef = ref(null);
|
||||||
|
|
||||||
// 处理添加按钮点击
|
// 处理添加按钮点击
|
||||||
const handleAddClick = () => {
|
const handleAddClick = () => {
|
||||||
|
// 从 ScheduleEditor 组件获取当前选择的日期
|
||||||
|
const currentDate = scheduleEditorRef.value?.selectedDate || selectedDate.value;
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/add-event/index?date=${selectedDate.value}`
|
url: `/pages/add-event/index?date=${currentDate}`
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 添加日程
|
// 添加日程
|
||||||
function addEvent(e) {
|
function addEvent(e) {
|
||||||
// e是{title, startHour, startMin, color, date等},如果没有date则使用selectedDate
|
// e是{title, startHour, startMin, color, date等},如果没有date则使用selectedDate
|
||||||
|
|
@ -187,171 +126,6 @@ function addEvent(e) {
|
||||||
|
|
||||||
const value=ref(0);
|
const value=ref(0);
|
||||||
|
|
||||||
// 滑动相关变量
|
|
||||||
const touchStartX = ref(0);
|
|
||||||
const touchStartY = ref(0);
|
|
||||||
const screenWidth = ref(375); // 屏幕宽度,默认值,会在mounted时更新
|
|
||||||
const translateX = ref(-375); // 当前滑动偏移量,初始值设为 -375(假设屏幕宽度,会在mounted时更新)
|
|
||||||
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(() => {
|
onShow(() => {
|
||||||
// 从全局存储中读取新添加的日程数据
|
// 从全局存储中读取新添加的日程数据
|
||||||
|
|
@ -396,33 +170,5 @@ onShow(() => {
|
||||||
margin-top: calc(var(--status-bar-height, 0) + 44px);
|
margin-top: calc(var(--status-bar-height, 0) + 44px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.bottom-tabbar) { z-index: 1000 !important; }
|
: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>
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user