初始化
This commit is contained in:
483
src/App.vue
483
src/App.vue
@@ -1,10 +1,481 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<div class="min-h-screen w-full bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<!-- 登录模态框 -->
|
||||
<LoginModal v-if="showLoginModal" @login="handleLogin" @close="showLoginModal = false" />
|
||||
|
||||
<!-- 顶部导航 - 粘性布局 -->
|
||||
<header class="sticky top-0 z-50 bg-gradient-to-r from-primary-600 to-primary-800 py-5 px-6 rounded-b-2xl shadow-lg">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="flex flex-col sm:flex-row items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="w-12 h-12 rounded-xl bg-white bg-opacity-20 flex items-center justify-center shadow-md">
|
||||
<i class="fas fa-calendar-alt text-b text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="ml-4 text-2xl font-bold text-b drop-shadow">智能日程管家</h1>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 sm:mt-0 flex items-center">
|
||||
<div class="flex rounded-xl overflow-hidden bg-white bg-opacity-20">
|
||||
<button
|
||||
@click="changeView('day')"
|
||||
:class="{'bg-white text-primary-600': view === 'day'}"
|
||||
class="px-4 py-2 font-medium transition-all hover:transform hover:-translate-y-0.5"
|
||||
>
|
||||
<i class="fas fa-sun mr-2"></i> 日视图
|
||||
</button>
|
||||
<button
|
||||
@click="changeView('week')"
|
||||
:class="{'bg-white text-primary-600': view === 'week'}"
|
||||
class="px-4 py-2 font-medium transition-all hover:transform hover:-translate-y-0.5"
|
||||
>
|
||||
<i class="fas fa-calendar-week mr-2"></i> 周视图
|
||||
</button>
|
||||
<button
|
||||
@click="changeView('month')"
|
||||
:class="{'bg-white text-primary-600': view === 'month'}"
|
||||
class="px-4 py-2 font-medium transition-all hover:transform hover:-translate-y-0.5"
|
||||
>
|
||||
<i class="fas fa-calendar-days mr-2"></i> 月视图
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ml-4 flex items-center">
|
||||
<div class="w-10 h-10 rounded-full overflow-hidden border-2 border-white">
|
||||
|
||||
</div>
|
||||
<span class="ml-2 text-white text-sm hidden md:block">{{ user.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 py-8 sm:px-6">
|
||||
<!-- 日历导航 -->
|
||||
<div data-aos="fade-up" class="flex items-center justify-between mb-8 bg-white rounded-xl p-4 shadow">
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
@click="changeDate(-1)"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-white hover:bg-gray-100 transition-all shadow hover:scale-105"
|
||||
>
|
||||
<i class="fas fa-chevron-left text-primary-600"></i>
|
||||
</button>
|
||||
|
||||
<div class="mx-4">
|
||||
<div class="text-xl font-bold text-gray-800 flex items-center">
|
||||
{{ displayDate }}
|
||||
<span class="ml-3 text-sm px-3 py-1 bg-primary-600 text-white rounded-full">当前视图</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mt-1">{{ dayjs().format('YYYY年MM月') }}</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="changeDate(1)"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-white hover:bg-gray-100 transition-all shadow hover:scale-105"
|
||||
>
|
||||
<i class="fas fa-chevron-right text-primary-600"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="currentDate = dayjs().format('YYYY-MM-DD')"
|
||||
class="flex items-center px-4 py-2 bg-gradient-to-r from-primary-500 to-primary-700 hover:from-primary-600 hover:to-primary-800 text-white rounded-xl transition-all shadow-lg hover:shadow-xl hover:transform hover:-translate-y-0.5"
|
||||
>
|
||||
<i class="fas fa-calendar-check mr-2"></i> 返回今天
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
<!-- 左侧面板 -->
|
||||
<div class="lg:col-span-1" data-aos="fade-right" data-aos-delay="100">
|
||||
<CommonEvents
|
||||
:commonEvents="commonEvents"
|
||||
@add-common-event="showCommonEventModal = true"
|
||||
@drag-start="handleDragStart"
|
||||
@drag-end="handleDragEnd"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 中间面板 - 日历 -->
|
||||
<div class="lg:col-span-2" data-aos="fade-up" data-aos-delay="200">
|
||||
<CalendarView
|
||||
:view="view"
|
||||
:currentDate="currentDate"
|
||||
:selectedDate="selectedDate"
|
||||
:events="events"
|
||||
:commonEvents="commonEvents"
|
||||
@select-date="selectDate"
|
||||
@show-event-detail="showEventDetail"
|
||||
@drop-event="handleDropEvent"
|
||||
@drag-start="handleDragStart"
|
||||
@drag-end="handleDragEnd"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 右侧面板 - 详情 -->
|
||||
<div class="lg:col-span-1" data-aos="fade-left" data-aos-delay="300">
|
||||
<EventDetail
|
||||
:selectedDate="selectedDate"
|
||||
:events="events"
|
||||
:commonEvents="commonEvents"
|
||||
@add-event="showEventModal = true"
|
||||
@edit-event="showEventDetail"
|
||||
@drop-event="handleDropEvent"
|
||||
@drag-start="handleDragStart"
|
||||
@drag-end="handleDragEnd"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模态框 -->
|
||||
<EventModal
|
||||
v-if="showEventModal"
|
||||
:event="editingEvent || newEvent"
|
||||
:isEditing="!!editingEvent"
|
||||
@close="closeModal"
|
||||
@save="saveEvent"
|
||||
@delete="deleteEvent"
|
||||
/>
|
||||
|
||||
<CommonEventModal
|
||||
v-if="showCommonEventModal"
|
||||
@close="closeCommonModal"
|
||||
@save="saveCommonEvent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import CalendarView from './components/CalendarView.vue';
|
||||
import EventDetail from './components/EventDetail.vue';
|
||||
import CommonEvents from './components/CommonEvents.vue';
|
||||
import EventModal from './components/EventModal.vue';
|
||||
import CommonEventModal from './components/CommonEventModal.vue';
|
||||
import LoginModal from './components/LoginModal.vue';
|
||||
|
||||
</style>
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
// 状态管理
|
||||
const view = ref('month');
|
||||
const currentDate = ref(dayjs().format('YYYY-MM-DD'));
|
||||
const selectedDate = ref(dayjs().format('YYYY-MM-DD'));
|
||||
const showEventModal = ref(false);
|
||||
const showCommonEventModal = ref(false);
|
||||
const showLoginModal = ref(true); // 默认显示登录模态框
|
||||
const editingEvent = ref(null);
|
||||
|
||||
// 用户信息
|
||||
const user = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
team: '萧康云医技术开发团队-医生组'
|
||||
});
|
||||
|
||||
// 数据
|
||||
const events = ref([
|
||||
{
|
||||
id: 'event-1',
|
||||
title: '团队周会',
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
startTime: '10:00',
|
||||
duration: 60,
|
||||
location: '会议室A',
|
||||
color: '#645eff',
|
||||
creator: { id: 'user-1', name: '李齐' },
|
||||
participants: [
|
||||
{ id: 'user-1', name: '李齐' },
|
||||
{ id: 'user-2', name: '张明' },
|
||||
{ id: 'user-3', name: '王芳' }
|
||||
],
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
id: 'event-2',
|
||||
title: '午餐会议',
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
startTime: '12:30',
|
||||
duration: 90,
|
||||
location: '公司餐厅',
|
||||
color: '#0fb8a9',
|
||||
creator: { id: 'user-2', name: '张明' },
|
||||
participants: [
|
||||
{ id: 'user-2', name: '张明' },
|
||||
{ id: 'user-4', name: '赵琳' }
|
||||
],
|
||||
completed: true
|
||||
}
|
||||
]);
|
||||
|
||||
const commonEvents = ref([
|
||||
{
|
||||
id: 'common-1',
|
||||
title: '健身时间',
|
||||
duration: 60,
|
||||
color: '#e85d75'
|
||||
},
|
||||
{
|
||||
id: 'common-2',
|
||||
title: '学习时间',
|
||||
duration: 90,
|
||||
color: '#8c6bed'
|
||||
},
|
||||
{
|
||||
id: 'common-3',
|
||||
title: '家庭晚餐',
|
||||
duration: 120,
|
||||
color: '#f0af56'
|
||||
}
|
||||
]);
|
||||
|
||||
// 新事件模板
|
||||
const newEvent = ref({
|
||||
id: null,
|
||||
title: '',
|
||||
date: selectedDate.value,
|
||||
startTime: '10:00',
|
||||
duration: 60,
|
||||
location: '',
|
||||
color: '#645eff',
|
||||
creator: null,
|
||||
participants: [],
|
||||
completed: false
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const displayDate = computed(() => {
|
||||
if (view.value === 'day') {
|
||||
return dayjs(currentDate.value).format('YYYY年MM月DD日');
|
||||
} else if (view.value === 'week') {
|
||||
const start = dayjs(currentDate.value).startOf('week');
|
||||
const end = start.add(6, 'day');
|
||||
return `${start.format('MM月DD日')} - ${end.format('MM月DD日')}`;
|
||||
} else {
|
||||
return dayjs(currentDate.value).format('YYYY年MM月');
|
||||
}
|
||||
});
|
||||
|
||||
// 方法
|
||||
const changeView = (v) => {
|
||||
view.value = v;
|
||||
};
|
||||
|
||||
const changeDate = (offset) => {
|
||||
if (view.value === 'day') {
|
||||
currentDate.value = dayjs(currentDate.value).add(offset, 'day').format('YYYY-MM-DD');
|
||||
} else if (view.value === 'week') {
|
||||
currentDate.value = dayjs(currentDate.value).add(offset, 'week').format('YYYY-MM-DD');
|
||||
} else {
|
||||
currentDate.value = dayjs(currentDate.value).add(offset, 'month').format('YYYY-MM-DD');
|
||||
}
|
||||
};
|
||||
|
||||
const selectDate = (date) => {
|
||||
selectedDate.value = date;
|
||||
newEvent.value.date = date;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
showEventModal.value = false;
|
||||
editingEvent.value = null;
|
||||
newEvent.value = {
|
||||
id: null,
|
||||
title: '',
|
||||
date: selectedDate.value,
|
||||
startTime: '10:00',
|
||||
duration: 60,
|
||||
location: '',
|
||||
color: '#645eff',
|
||||
creator: null,
|
||||
participants: [],
|
||||
completed: false
|
||||
};
|
||||
};
|
||||
|
||||
const closeCommonModal = () => {
|
||||
showCommonEventModal.value = false;
|
||||
};
|
||||
|
||||
const saveEvent = (eventData) => {
|
||||
if (!eventData.title.trim()) return;
|
||||
|
||||
if (!editingEvent.value) {
|
||||
// 为新事件设置创建者
|
||||
eventData.creator = {
|
||||
id: user.value.id,
|
||||
name: user.value.name
|
||||
};
|
||||
|
||||
// 自动添加创建者为参与者
|
||||
if (!eventData.participants.some(p => p.id === user.value.id)) {
|
||||
eventData.participants.push({
|
||||
id: user.value.id,
|
||||
name: user.value.name
|
||||
});
|
||||
}
|
||||
|
||||
eventData.id = 'event-' + Date.now();
|
||||
events.value.push({ ...eventData });
|
||||
} else {
|
||||
const index = events.value.findIndex(e => e.id === editingEvent.value.id);
|
||||
if (index !== -1) {
|
||||
events.value[index] = { ...eventData };
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem('events', JSON.stringify(events.value));
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const deleteEvent = () => {
|
||||
if (editingEvent.value) {
|
||||
events.value = events.value.filter(e => e.id !== editingEvent.value.id);
|
||||
localStorage.setItem('events', JSON.stringify(events.value));
|
||||
}
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const saveCommonEvent = (eventData) => {
|
||||
if (!eventData.title.trim()) return;
|
||||
|
||||
commonEvents.value.push({
|
||||
...eventData,
|
||||
id: 'common-' + Date.now()
|
||||
});
|
||||
|
||||
localStorage.setItem('commonEvents', JSON.stringify(commonEvents.value));
|
||||
closeCommonModal();
|
||||
};
|
||||
|
||||
const showEventDetail = (event) => {
|
||||
editingEvent.value = event;
|
||||
showEventModal.value = true;
|
||||
};
|
||||
|
||||
// 处理拖拽开始
|
||||
const handleDragStart = (event, item, type) => {
|
||||
event.dataTransfer.setData('type', type);
|
||||
event.dataTransfer.setData('data', JSON.stringify(item));
|
||||
event.target.classList.add('dragging');
|
||||
};
|
||||
|
||||
// 处理拖拽结束
|
||||
const handleDragEnd = (event) => {
|
||||
document.querySelectorAll('.dragging').forEach(el => {
|
||||
el.classList.remove('dragging');
|
||||
});
|
||||
};
|
||||
|
||||
// 处理放置事件
|
||||
const handleDropEvent = (event, date, time = null) => {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const data = JSON.parse(event.dataTransfer.getData('data'));
|
||||
|
||||
if (type === 'common') {
|
||||
// 添加新日程
|
||||
const newEvent = {
|
||||
id: 'event-' + Date.now(),
|
||||
title: data.title,
|
||||
date: date,
|
||||
startTime: time || '09:00',
|
||||
duration: data.duration,
|
||||
location: '',
|
||||
color: data.color,
|
||||
creator: {
|
||||
id: user.value.id,
|
||||
name: user.value.name
|
||||
},
|
||||
participants: [{
|
||||
id: user.value.id,
|
||||
name: user.value.name
|
||||
}],
|
||||
completed: false
|
||||
};
|
||||
|
||||
events.value.push(newEvent);
|
||||
} else if (type === 'event') {
|
||||
// 移动现有日程
|
||||
const eventToUpdate = events.value.find(e => e.id === data.id);
|
||||
if (eventToUpdate) {
|
||||
eventToUpdate.date = date;
|
||||
if (time) {
|
||||
eventToUpdate.startTime = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem('events', JSON.stringify(events.value));
|
||||
|
||||
// 移除拖拽样式
|
||||
document.querySelectorAll('.dropzone').forEach(el => {
|
||||
el.classList.remove('drag-over');
|
||||
});
|
||||
};
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = (userData) => {
|
||||
user.value = {
|
||||
...userData,
|
||||
team: '萧康云医技术开发团队-医生组'
|
||||
};
|
||||
showLoginModal.value = false;
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
const savedEvents = localStorage.getItem('events');
|
||||
if (savedEvents) {
|
||||
events.value = JSON.parse(savedEvents);
|
||||
}
|
||||
|
||||
const savedCommonEvents = localStorage.getItem('commonEvents');
|
||||
if (savedCommonEvents) {
|
||||
commonEvents.value = JSON.parse(savedCommonEvents);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dragging {
|
||||
opacity: 0.7;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.dropzone.drag-over {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: inset 0 0 0 0 rgba(100, 94, 255, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: inset 0 0 0 10px rgba(100, 94, 255, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: inset 0 0 0 0 rgba(100, 94, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 按钮悬停效果 */
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user