OfficeSystem/pages/index/index.vue

402 lines
12 KiB
Vue
Raw Normal View History

2025-10-30 16:42:12 +08:00
<template>
2025-10-31 10:16:00 +08:00
<!-- 顶部Tabs栏 -->
2025-11-04 17:17:11 +08:00
<view class="fixed-tabs">
2025-10-31 10:52:48 +08:00
<uv-tabs :list="topTabs" @click="clickTab"></uv-tabs>
2025-11-04 17:17:11 +08:00
</view>
2025-10-31 10:16:00 +08:00
<!-- 内容区域 -->
2025-10-31 14:14:51 +08:00
<view class="content-wrapper">
2025-11-04 17:10:40 +08:00
<!-- 月份日历组件 -->
<MonthCalendar
:selected-date="selectedDate"
:events="allEvents"
@change="handleDateChange"
/>
<view
2025-10-31 14:14:51 +08:00
class="swipe-container"
2025-11-04 17:10:40 +08:00
@touchstart="handleTouchStart"
2025-10-31 14:14:51 +08:00
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
2025-11-04 17:10:40 +08:00
<view
2025-10-31 14:14:51 +08:00
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>
2025-10-31 09:35:40 +08:00
</view>
2025-10-30 18:00:30 +08:00
<!-- 悬浮新建按钮 -->
2025-11-03 09:47:11 +08:00
<FabPlus @click="handleAddClick" />
2025-10-30 18:00:30 +08:00
2025-11-03 09:47:11 +08:00
<!-- 新建日程弹窗保留以备后用 -->
2025-10-30 18:00:30 +08:00
<AddEventModal :show="showAdd" @ok="addEvent" @cancel="showAdd = false" />
<!-- 底部导航 -->
2025-10-31 09:35:40 +08:00
<uv-tabbar :value="value" @change="index=>value = index">
<uv-tabbar-item text="首页" icon="home"></uv-tabbar-item>
2025-10-31 10:52:48 +08:00
<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>
2025-10-31 09:35:40 +08:00
</uv-tabbar>
2025-10-31 10:52:48 +08:00
2025-10-30 16:42:12 +08:00
</template>
2025-10-30 17:35:16 +08:00
<script setup>
2025-10-31 14:14:51 +08:00
import { ref, computed, watch, onMounted } from 'vue';
2025-11-03 09:47:11 +08:00
import { onShow } from '@dcloudio/uni-app';
2025-10-31 10:16:00 +08:00
2025-10-30 18:00:30 +08:00
import TimeTable from '@/components/TimeTable.vue';
import FabPlus from '@/components/FabPlus.vue';
import AddEventModal from '@/components/AddEventModal.vue';
2025-11-04 17:10:40 +08:00
import MonthCalendar from '@/components/MonthCalendar.vue';
2025-10-30 17:19:26 +08:00
2025-10-30 18:00:30 +08:00
// 顶部tabs选项
const topTabs = [
2025-10-31 09:35:40 +08:00
{ name: '日程编辑', value: 0 },
{ name: '内容看板', value: 1 },
{ name: '待办事项', value: 2 },
{ name: '消息内容', value: 3 }
2025-10-30 17:35:16 +08:00
];
2025-10-30 18:00:30 +08:00
const topTabValue = ref(0);
2025-10-30 16:42:12 +08:00
2025-10-31 10:52:48 +08:00
function clickTab(item) {
2025-10-31 10:16:00 +08:00
topTabValue.value = item.value;
console.log('切换tab:', item.name);
}
2025-10-31 10:45:23 +08:00
// 当前选择的日期格式YYYY-MM-DD
const selectedDate = ref(new Date().toISOString().slice(0, 10));
2025-10-30 17:19:26 +08:00
2025-10-30 18:00:30 +08:00
// 小时段
2025-10-31 10:52:48 +08:00
const hours = Array.from({length: 24}, (_,i)=>i); // 0~23点
2025-10-30 18:00:30 +08:00
// 示例日程
2025-10-31 10:45:23 +08:00
const today = new Date().toISOString().slice(0, 10);
2025-10-31 10:52:48 +08:00
// 获取明天的日期
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);
2025-10-30 18:00:30 +08:00
const allEvents = ref([
2025-10-31 14:14:51 +08:00
{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},
2025-10-30 18:00:30 +08:00
]);
2025-10-30 17:19:26 +08:00
2025-10-30 18:00:30 +08:00
// 根据当前选择日期过滤
2025-10-31 10:52:48 +08:00
const eventsInDay = computed(() => {
const filtered = allEvents.value.filter(e=>e.date===selectedDate.value);
console.log('计算 eventsInDayselectedDate:', selectedDate.value, '过滤后事件数:', filtered.length);
return filtered;
});
2025-10-31 14:14:51 +08:00
// 计算相邻日期
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);
});
2025-10-31 10:52:48 +08:00
// 监控 selectedDate 的变化
watch(selectedDate, (newDate, oldDate) => {
console.log('selectedDate 发生变化:', oldDate, '->', newDate);
console.log('eventsInDay 新值:', eventsInDay.value);
}, { immediate: true });
2025-10-30 18:00:30 +08:00
2025-11-04 17:10:40 +08:00
// 处理日期变化
const handleDateChange = (dateStr) => {
selectedDate.value = dateStr;
console.log('通过日历组件更新选择日期:', selectedDate.value);
console.log('过滤后的事件数:', eventsInDay.value.length);
2025-10-31 09:35:40 +08:00
}
2025-10-30 18:00:30 +08:00
// 悬浮按钮/弹窗控制
const showAdd = ref(false);
2025-11-03 09:47:11 +08:00
// 处理添加按钮点击
const handleAddClick = () => {
uni.navigateTo({
url: `/pages/add-event/index?date=${selectedDate.value}`
});
};
// 添加日程
2025-10-30 18:00:30 +08:00
function addEvent(e) {
2025-11-03 09:47:11 +08:00
// e是{title, startHour, startMin, color, date等}如果没有date则使用selectedDate
2025-10-30 18:00:30 +08:00
allEvents.value.push({
2025-11-03 09:47:11 +08:00
...e,
date: e.date || selectedDate.value,
2025-10-30 18:00:30 +08:00
id: Date.now()
});
showAdd.value = false;
}
2025-11-03 09:47:11 +08:00
2025-10-31 09:35:40 +08:00
const value=ref(0);
2025-10-31 14:14:51 +08:00
// 滑动相关变量
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); // 是否正在拖动
2025-11-03 09:18:31 +08:00
const isInitialized = ref(false); // 是否已完成初始化
2025-10-31 14:14:51 +08:00
// 初始化屏幕宽度
const initScreenWidth = () => {
uni.getSystemInfo({
success: (res) => {
const width = res.windowWidth || res.screenWidth || 375;
screenWidth.value = width;
2025-11-03 09:18:31 +08:00
// 只有在未初始化时才更新,避免覆盖用户操作
if (!isInitialized.value) {
2025-10-31 14:14:51 +08:00
translateX.value = -width;
baseTranslateX.value = -width;
2025-11-03 09:18:31 +08:00
isInitialized.value = true;
2025-10-31 14:14:51 +08:00
}
}
});
};
// 触摸开始
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);
2025-11-03 09:18:31 +08:00
// // 如果是水平滑动(水平距离大于垂直距离),阻止页面滚动
// if (Math.abs(deltaX) > deltaY && Math.abs(deltaX) > 10) {
// e.preventDefault();
// }
2025-10-31 14:14:51 +08:00
// 实时更新偏移量
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) {
2025-11-03 09:18:31 +08:00
// 向左滑动,滑动到前一天的位置
slideToPreviousDay();
2025-10-31 14:14:51 +08:00
} else {
2025-11-03 09:18:31 +08:00
// 向右滑动,滑动到后一天的位置
2025-10-31 14:14:51 +08:00
slideToNextDay();
}
} else {
// 滑动距离不够,回到中间位置
resetToCenter();
}
};
// 重置到中心位置(今天)
const resetToCenter = () => {
isAnimating.value = true;
translateX.value = -screenWidth.value;
setTimeout(() => {
isAnimating.value = false;
2025-11-03 10:52:56 +08:00
}, 0); // 与 transition 时间一致0.3s
2025-10-31 14:14:51 +08:00
};
// 滑动到前一天的位置(动画完成后更新日期)
const slideToPreviousDay = () => {
isAnimating.value = true;
// 从当前位置继续滑动到前一天的完整位置(-screenWidth * 2
const targetX = -screenWidth.value;
translateX.value = targetX;
2025-10-31 14:14:51 +08:00
// 等待动画完成300ms与 transition 时间一致)
2025-10-31 14:14:51 +08:00
setTimeout(() => {
// 滑动动画完成后,先暂时禁用 transition
isAnimating.value = false;
2025-10-31 14:14:51 +08:00
// 在下一帧更新日期和重置位置(确保没有 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 已禁用
2025-11-03 10:52:56 +08:00
}, 0); // 与 transition 时间一致0.3s
2025-10-31 14:14:51 +08:00
};
// 滑动到后一天的位置(动画完成后更新日期)
const slideToNextDay = () => {
isAnimating.value = true;
// 从当前位置继续滑动到后一天的完整位置0
const targetX = -screenWidth.value;
translateX.value = targetX;
2025-10-31 14:14:51 +08:00
// 等待动画完成300ms与 transition 时间一致)
2025-10-31 14:14:51 +08:00
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
2025-10-31 14:14:51 +08:00
};
// 监听日期变化,重置位置
watch(selectedDate, () => {
if (!isDragging.value && !isAnimating.value) {
resetToCenter();
}
});
// 组件挂载时初始化
onMounted(() => {
initScreenWidth();
});
2025-11-03 09:47:11 +08:00
// 页面显示时处理从新建日程页面返回的数据
onShow(() => {
// 从全局存储中读取新添加的日程数据
try {
const newEventData = uni.getStorageSync('newEventData');
if (newEventData) {
addEvent(newEventData);
// 清除存储的数据
uni.removeStorageSync('newEventData');
}
} catch (e) {
console.error('读取新日程数据失败:', e);
}
});
2025-10-30 16:42:12 +08:00
</script>
<style lang="scss" scoped>
2025-10-31 09:35:40 +08:00
.status_bar {
width: 100%;
2025-10-31 10:16:00 +08:00
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
.fixed-tabs {
position: fixed;
2025-11-04 17:17:11 +08:00
top: 0;
2025-10-31 10:16:00 +08:00
left: 0;
right: 0;
width: 100%;
background: #fff;
z-index: 999;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.content-wrapper {
2025-11-04 17:17:11 +08:00
padding-top: 8rpx;
2025-10-31 10:16:00 +08:00
min-height: 100vh;
2025-10-31 14:14:51 +08:00
overflow: hidden;
2025-11-04 17:17:11 +08:00
margin-top: var(--status-bar-height, 0);
2025-10-31 09:35:40 +08:00
}
2025-10-30 16:42:12 +08:00
2025-10-30 18:00:30 +08:00
:deep(.bottom-tabbar) { z-index: 1000 !important; }
.schedule-timeline {
padding-bottom: 130rpx !important;
2025-10-30 17:35:16 +08:00
}
2025-10-31 14:14:51 +08:00
.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;
}
2025-10-30 17:35:16 +08:00
</style>