OfficeSystem/pages/index/index.vue
2025-10-31 14:14:51 +08:00

421 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">
<view>
<uv-calendar ref="calendar" mode="single" @confirm="handleConfirm" ></uv-calendar>
<button @click="openCalendar">选择日期</button>
<view style=" font-size: 12px; color: #666;">
当前选择日期{{ selectedDate }}事件数{{ eventsInDay.length }}
</view>
</view>
<!-- 滑动容器 -->
<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="showAdd = true" />
<!-- 新建日程弹窗 -->
<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 TimeTable from '@/components/TimeTable.vue';
import FabPlus from '@/components/FabPlus.vue';
import AddEventModal from '@/components/AddEventModal.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 calendar = ref(null)
// 格式化日期为 YYYY-MM-DD
function formatDateToYYYYMMDD(dateInput) {
if (!dateInput) return '';
let dateStr = '';
// 如果已经是字符串格式 YYYY-MM-DD直接返回
if (typeof dateInput === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
return dateInput;
}
// 处理各种可能的输入格式
if (typeof dateInput === 'string') {
dateStr = dateInput;
} else if (dateInput?.date) {
dateStr = dateInput.date;
} else if (dateInput?.value) {
dateStr = dateInput.value;
} else if (Array.isArray(dateInput) && dateInput.length > 0) {
dateStr = typeof dateInput[0] === 'string' ? dateInput[0] : (dateInput[0]?.date || dateInput[0]?.value || '');
}
if (!dateStr) return '';
// 尝试解析日期
const date = new Date(dateStr);
if (!isNaN(date.getTime())) {
// 格式化为 YYYY-MM-DD
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 如果已经是 YYYY-MM-DD 格式但 new Date 解析失败(如时区问题),尝试直接使用
if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) {
return dateStr.slice(0, 10);
}
return '';
}
// 打开日历方法
const openCalendar = () => {
if (calendar.value) {
calendar.value.open()
}
}
// confirm 事件处理(可能在确认时触发)
const handleConfirm = (e) => {
console.log('日历 confirm 事件:', e, typeof e);
const formattedDate = formatDateToYYYYMMDD(e);
if (formattedDate) {
selectedDate.value = formattedDate;
console.log('通过 confirm 更新选择日期:', selectedDate.value);
console.log('过滤后的事件数:', eventsInDay.value.length);
}
}
// 悬浮按钮/弹窗控制
const showAdd = ref(false);
function addEvent(e) {
// e是{title, startHour, startMin, color}补充date
allEvents.value.push({
...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 initScreenWidth = () => {
uni.getSystemInfo({
success: (res) => {
const width = res.windowWidth || res.screenWidth || 375;
screenWidth.value = width;
// 只有在初始值时才更新,避免覆盖用户操作
if (translateX.value === -375) {
translateX.value = -width;
baseTranslateX.value = -width;
}
}
});
};
// 触摸开始
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;
}, 1000);
};
// 滑动到前一天的位置(动画完成后更新日期)
const slideToPreviousDay = () => {
isAnimating.value = true;
// 从当前位置继续滑动到前一天的完整位置(-screenWidth * 2
const targetX = -screenWidth.value*2;
translateX.value = targetX;
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 已禁用
}, 1000);
};
// 滑动到后一天的位置(动画完成后更新日期)
const slideToNextDay = () => {
isAnimating.value = true;
// 从当前位置继续滑动到后一天的完整位置0
const targetX = 0;
translateX.value = targetX;
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 已禁用
}, 1000);
};
// 监听日期变化,重置位置
watch(selectedDate, () => {
if (!isDragging.value && !isAnimating.value) {
resetToCenter();
}
});
// 组件挂载时初始化
onMounted(() => {
initScreenWidth();
});
</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>