OfficeSystem/components/index/scheduleEditor/ScheduleEditor.vue
2025-11-12 15:36:06 +08:00

292 lines
8.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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/index/scheduleEditor/MonthCalendar.vue';
import TimeTable from '@/components/index/scheduleEditor/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); // 当前滑动偏移量,初始值设为 -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>