OfficeSystem/pages/index/index.vue
2025-11-04 17:10:40 +08:00

397 lines
12 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>
<!-- 顶部Tabs栏 -->
<uv-tabs :list="topTabs" @click="clickTab"></uv-tabs>
<!-- 内容区域 -->
<view class="content-wrapper">
<!-- 月份日历组件 -->
<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>
<!-- 悬浮新建按钮 -->
<FabPlus @click="handleAddClick" />
<!-- 新建日程弹窗保留以备后用 -->
<AddEventModal :show="showAdd" @ok="addEvent" @cancel="showAdd = false" />
<!-- 底部导航 -->
<uv-tabbar :value="value" @change="index=>value = index">
<uv-tabbar-item text="首页" icon="home"></uv-tabbar-item>
<uv-tabbar-item text="工作台" icon="calendar"></uv-tabbar-item>
<uv-tabbar-item text="月度考核 " icon="integral"></uv-tabbar-item>
<uv-tabbar-item text="管理" icon="account"></uv-tabbar-item>
</uv-tabbar>
</template>
<script setup>
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';
// 顶部tabs选项
const topTabs = [
{ name: '日程编辑', value: 0 },
{ name: '内容看板', value: 1 },
{ name: '待办事项', value: 2 },
{ name: '消息内容', value: 3 }
];
const topTabValue = ref(0);
function clickTab(item) {
topTabValue.value = item.value;
console.log('切换tab:', item.name);
}
// 当前选择的日期格式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);
// 获取明天的日期
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = tomorrow.toISOString().slice(0, 10);
// 获取昨天的日期
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = yesterday.toISOString().slice(0, 10);
const allEvents = ref([
{id:1,title:'今天的日程1',startHour:10,startMin:30,color:'#e3fae6',date:today},
{id:5,title:'明天的日程2',startHour:10,startMin:30,color:'#fae1e1',date:tomorrowStr},
{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 });
// 处理日期变化
const handleDateChange = (dateStr) => {
selectedDate.value = dateStr;
console.log('通过日历组件更新选择日期:', selectedDate.value);
console.log('过滤后的事件数:', eventsInDay.value.length);
}
// 悬浮按钮/弹窗控制
const showAdd = ref(false);
// 处理添加按钮点击
const handleAddClick = () => {
uni.navigateTo({
url: `/pages/add-event/index?date=${selectedDate.value}`
});
};
// 添加日程
function addEvent(e) {
// e是{title, startHour, startMin, color, date等}如果没有date则使用selectedDate
allEvents.value.push({
...e,
date: e.date || selectedDate.value,
id: Date.now()
});
showAdd.value = false;
}
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(() => {
// 从全局存储中读取新添加的日程数据
try {
const newEventData = uni.getStorageSync('newEventData');
if (newEventData) {
addEvent(newEventData);
// 清除存储的数据
uni.removeStorageSync('newEventData');
}
} catch (e) {
console.error('读取新日程数据失败:', e);
}
});
</script>
<style lang="scss" scoped>
.status_bar {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
.fixed-tabs {
position: fixed;
left: 0;
right: 0;
width: 100%;
background: #fff;
z-index: 999;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.content-wrapper {
min-height: 100vh;
overflow: hidden;
}
: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>