农历
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"aos": "^2.3.4",
|
||||
"axios": "^1.6.2",
|
||||
"chinese-lunar": "^0.1.4",
|
||||
"chinese-lunar-calendar": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"lucide-vue-next": "^0.507.0",
|
||||
"pinia": "^2.1.7",
|
||||
|
||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
||||
chinese-lunar:
|
||||
specifier: ^0.1.4
|
||||
version: 0.1.4
|
||||
chinese-lunar-calendar:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
@@ -308,36 +311,42 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||
@@ -395,56 +404,67 @@ packages:
|
||||
resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.40.0':
|
||||
resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.40.0':
|
||||
resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.40.0':
|
||||
resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.40.0':
|
||||
resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
|
||||
resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.40.0':
|
||||
resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.40.0':
|
||||
resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.40.0':
|
||||
resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.40.0':
|
||||
resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.40.0':
|
||||
resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.40.0':
|
||||
resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==}
|
||||
@@ -502,24 +522,28 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.4':
|
||||
resolution: {integrity: sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.4':
|
||||
resolution: {integrity: sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.4':
|
||||
resolution: {integrity: sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.4':
|
||||
resolution: {integrity: sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==}
|
||||
@@ -647,6 +671,9 @@ packages:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
chinese-lunar-calendar@1.0.1:
|
||||
resolution: {integrity: sha512-T5XjtA6ygOkxjwltYf8dEB1DLsPKewDKajf74fNeT6rH2g9EZPf2AHk6vAlK/SAImTYKXoUNQ3Q4JwN0BduqRA==}
|
||||
|
||||
chinese-lunar@0.1.4:
|
||||
resolution: {integrity: sha512-E4seb9UZkSJtkm65Ry2ZLWTw5Z87DOyGcCePdg3ft7da342hnEliMtcDAPSn+YSo/o0xl1Egmhh671kxEJgY+g==}
|
||||
|
||||
@@ -846,24 +873,28 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.29.2:
|
||||
resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.29.2:
|
||||
resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.29.2:
|
||||
resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.29.2:
|
||||
resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
|
||||
@@ -1570,6 +1601,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
chinese-lunar-calendar@1.0.1: {}
|
||||
|
||||
chinese-lunar@0.1.4: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
|
||||
69
src/App.vue
69
src/App.vue
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="min-h-screen w-full bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div class="min-h-screen w-full bg-gradient-to-br from-gray-50 to-gray-100 bg-fixed">
|
||||
<!-- 登录模态框 -->
|
||||
<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">
|
||||
<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 w-full">
|
||||
<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>
|
||||
<i class="fas fa-calendar-alt text-white text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="ml-4 text-2xl font-bold text-b drop-shadow">智能日程管家</h1>
|
||||
<h1 class="ml-4 text-2xl font-bold text-white drop-shadow">智能日程管家</h1>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 sm:mt-0 flex items-center">
|
||||
@@ -50,13 +50,13 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 py-8 sm:px-6">
|
||||
<div class="w-full 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"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-white hover:bg-gray-100 transition-all shadow hover:scale-105 active:scale-95"
|
||||
>
|
||||
<i class="fas fa-chevron-left text-primary-600"></i>
|
||||
</button>
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<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"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-white hover:bg-gray-100 transition-all shadow hover:scale-105 active:scale-95"
|
||||
>
|
||||
<i class="fas fa-chevron-right text-primary-600"></i>
|
||||
</button>
|
||||
@@ -79,14 +79,14 @@
|
||||
|
||||
<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"
|
||||
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 active:translate-y-0"
|
||||
>
|
||||
<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="grid grid-cols-1 lg:grid-cols-[1fr_2fr_1fr] gap-6">
|
||||
<!-- 左侧面板 -->
|
||||
<div class="lg:col-span-1" data-aos="fade-right" data-aos-delay="100">
|
||||
<CommonEvents
|
||||
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 中间面板 - 日历 -->
|
||||
<div class="lg:col-span-2" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="lg:col-span-1" data-aos="fade-up" data-aos-delay="200">
|
||||
<CalendarView
|
||||
:view="view"
|
||||
:currentDate="currentDate"
|
||||
@@ -132,8 +132,9 @@
|
||||
<!-- 模态框 -->
|
||||
<EventModal
|
||||
v-if="showEventModal"
|
||||
:event="editingEvent || newEvent"
|
||||
:event="editingEvent"
|
||||
:isEditing="!!editingEvent"
|
||||
:isOpen="showEventModal"
|
||||
@close="closeModal"
|
||||
@save="saveEvent"
|
||||
@delete="deleteEvent"
|
||||
@@ -141,6 +142,7 @@
|
||||
|
||||
<CommonEventModal
|
||||
v-if="showCommonEventModal"
|
||||
:isOpen="showCommonEventModal"
|
||||
@close="closeCommonModal"
|
||||
@save="saveCommonEvent"
|
||||
/>
|
||||
@@ -157,6 +159,8 @@ import CommonEvents from './components/CommonEvents.vue';
|
||||
import EventModal from './components/EventModal.vue';
|
||||
import CommonEventModal from './components/CommonEventModal.vue';
|
||||
import LoginModal from './components/LoginModal.vue';
|
||||
import AOS from 'aos';
|
||||
import 'aos/dist/aos.css';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
@@ -235,20 +239,6 @@ const commonEvents = ref([
|
||||
}
|
||||
]);
|
||||
|
||||
// 新事件模板
|
||||
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') {
|
||||
@@ -279,24 +269,11 @@ const changeDate = (offset) => {
|
||||
|
||||
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 = () => {
|
||||
@@ -430,6 +407,13 @@ const handleLogin = (userData) => {
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
duration: 800,
|
||||
easing: 'ease-out-quart',
|
||||
once: true,
|
||||
mirror: false
|
||||
});
|
||||
|
||||
const savedEvents = localStorage.getItem('events');
|
||||
if (savedEvents) {
|
||||
events.value = JSON.parse(savedEvents);
|
||||
@@ -439,6 +423,13 @@ onMounted(() => {
|
||||
if (savedCommonEvents) {
|
||||
commonEvents.value = JSON.parse(savedCommonEvents);
|
||||
}
|
||||
|
||||
const savedUser = localStorage.getItem('user');
|
||||
if (savedUser) {
|
||||
console.log(savedUser, 'ssssssssss')
|
||||
user.value = JSON.parse(savedUser);
|
||||
showLoginModal.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -478,4 +469,4 @@ button:hover {
|
||||
button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="bg-white rounded-2xl shadow-soft p-6">
|
||||
<div class="bg-gradient-to-br from-white to-gray-50 rounded-2xl shadow-soft p-6">
|
||||
<!-- 月视图 -->
|
||||
<div v-if="view === 'month'" class="month-view">
|
||||
<div v-if="view === 'month'" class="month-view" data-aos="fade-up" data-aos-duration="500">
|
||||
<div class="grid grid-cols-7 gap-1">
|
||||
<div
|
||||
v-for="day in ['日', '一', '二', '三', '四', '五', '六']"
|
||||
@@ -20,8 +20,11 @@
|
||||
@dragover.prevent="handleDragOver($event, day.date)"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDropEvent($event, day.date)"
|
||||
:class="{'bg-primary-50': selectedDate === day.date}"
|
||||
class="calendar-cell dropzone rounded-xl border border-gray-100 p-3 cursor-pointer transition-all relative min-h-[120px]"
|
||||
:class="{
|
||||
'bg-primary-50': selectedDate === day.date,
|
||||
'drag-over': isDragOverCell === day.date
|
||||
}"
|
||||
class="calendar-cell dropzone rounded-xl border border-gray-100 p-3 cursor-pointer transition-all relative min-h-[120px] hover:shadow-md"
|
||||
>
|
||||
<!-- 日期和工作状态 -->
|
||||
<div class="flex justify-between items-start">
|
||||
@@ -59,25 +62,28 @@
|
||||
</div>
|
||||
|
||||
<!-- 事件展示 -->
|
||||
<div v-if="hasEvent(day.date)" class="mt-2 text-xs truncate max-w-full">
|
||||
{{ getFirstEventTitle(day.date) }}
|
||||
</div>
|
||||
|
||||
<!-- 事件指示器 -->
|
||||
<div v-if="hasEvent(day.date)" class="absolute bottom-3 right-3 flex">
|
||||
<span
|
||||
v-for="(color, index) in getEventColorsForDay(day.date)"
|
||||
:key="index"
|
||||
class="event-dot"
|
||||
:style="{backgroundColor: color}"
|
||||
></span>
|
||||
<div v-if="hasEvent(day.date)" class="mt-2 text-xs max-w-full overflow-hidden">
|
||||
<div
|
||||
v-for="(event, idx) in getEventsForDate(day.date).slice(0, 2)"
|
||||
:key="idx"
|
||||
class="truncate mb-1 flex items-center"
|
||||
>
|
||||
<span
|
||||
class="event-dot mr-1"
|
||||
:style="{ backgroundColor: event.color }"
|
||||
></span>
|
||||
{{ event.title }}
|
||||
</div>
|
||||
<div v-if="getEventsForDate(day.date).length > 2" class="text-gray-500">
|
||||
+{{ getEventsForDate(day.date).length - 2 }}个日程
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 周视图 -->
|
||||
<div v-else-if="view === 'week'" class="week-view">
|
||||
<div v-else-if="view === 'week'" class="week-view" data-aos="fade-up" data-aos-duration="500">
|
||||
<div class="flex mb-3 bg-gray-50 rounded-lg">
|
||||
<div class="w-20"></div>
|
||||
<div
|
||||
@@ -85,7 +91,7 @@
|
||||
:key="index"
|
||||
@click="selectDate(day.format('YYYY-MM-DD'))"
|
||||
:class="{'bg-primary-100': selectedDate === day.format('YYYY-MM-DD')}"
|
||||
class="flex-1 text-center py-3 rounded-lg transition-colors cursor-pointer"
|
||||
class="flex-1 text-center py-3 rounded-lg transition-colors cursor-pointer hover:bg-gray-100"
|
||||
>
|
||||
<p class="font-medium text-gray-700">{{ day.format('ddd') }}</p>
|
||||
<p
|
||||
@@ -120,7 +126,8 @@
|
||||
@drop="handleDropEvent($event, day.format('YYYY-MM-DD'), (hour + 8) + ':00')"
|
||||
:class="{
|
||||
'bg-gray-50': index % 2 === 0,
|
||||
'border-l border-gray-200': index > 0
|
||||
'border-l border-gray-200': index > 0,
|
||||
'drag-over': isDragOverCell === day.format('YYYY-MM-DD') + '-' + (hour + 8)
|
||||
}"
|
||||
class="flex-1 min-h-16 p-2 dropzone"
|
||||
>
|
||||
@@ -128,7 +135,7 @@
|
||||
v-for="event in getEventsForDate(day.format('YYYY-MM-DD'), (hour + 8) + ':00')"
|
||||
:key="event.id"
|
||||
@click="showEventDetail(event)"
|
||||
class="text-xs p-2 rounded-lg mb-1 truncate"
|
||||
class="text-xs p-2 rounded-lg mb-1 truncate cursor-pointer hover:shadow-sm"
|
||||
:style="{
|
||||
backgroundColor: `${event.color}10`,
|
||||
borderLeft: `3px solid ${event.color}`
|
||||
@@ -142,7 +149,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 日视图 -->
|
||||
<div v-else class="day-view">
|
||||
<div v-else class="day-view" data-aos="fade-up" data-aos-duration="500">
|
||||
<div
|
||||
class="flex items-center justify-center py-3 mb-4 rounded-xl day-header"
|
||||
@click="selectDate(currentDate)"
|
||||
@@ -175,6 +182,9 @@
|
||||
@dragover.prevent="handleDragOver($event, currentDay.format('YYYY-MM-DD'), (hour + 7) + ':00')"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDropEvent($event, currentDay.format('YYYY-MM-DD'), (hour + 7) + ':00')"
|
||||
:class="{
|
||||
'drag-over': isDragOverCell === currentDay.format('YYYY-MM-DD') + '-' + (hour + 7)
|
||||
}"
|
||||
class="flex-1 min-h-16 p-3 dropzone"
|
||||
>
|
||||
<div
|
||||
@@ -183,7 +193,7 @@
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, event, 'event')"
|
||||
@dragend="handleDragEnd"
|
||||
class="schedule-item mb-2 p-3 rounded-lg shadow-sm cursor-pointer border-l-4"
|
||||
class="schedule-item mb-2 p-3 rounded-lg shadow-sm cursor-pointer border-l-4 hover:shadow-md"
|
||||
:style="{
|
||||
backgroundColor: `${event.color}10`,
|
||||
borderLeftColor: event.color
|
||||
@@ -205,7 +215,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits } from 'vue';
|
||||
import { computed, defineProps, defineEmits, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { getLunarDate, getFestival, getSolarTerm, isHoliday } from '@/utils/calendarUtils';
|
||||
|
||||
@@ -219,6 +229,9 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['select-date', 'show-event-detail', 'drop-event', 'drag-start', 'drag-end']);
|
||||
|
||||
// 拖拽状态
|
||||
const isDragOverCell = ref(null);
|
||||
|
||||
// 计算月视图日期
|
||||
const monthDays = computed(() => {
|
||||
const startDay = dayjs(props.currentDate).startOf('month').startOf('week');
|
||||
@@ -257,16 +270,15 @@ const hasEvent = (date) => {
|
||||
return props.events.some(e => e.date === date);
|
||||
};
|
||||
|
||||
// 获取当天第一个事件的标题
|
||||
const getFirstEventTitle = (date) => {
|
||||
const events = props.events.filter(e => e.date === date);
|
||||
return events.length > 0 ? events[0].title : '';
|
||||
};
|
||||
// 获取当天的所有事件
|
||||
const getEventsForDate = (date, time = null) => {
|
||||
let filteredEvents = props.events.filter(e => e.date === date);
|
||||
|
||||
// 获取当天所有事件颜色
|
||||
const getEventColorsForDay = (date) => {
|
||||
const events = props.events.filter(e => e.date === date);
|
||||
return [...new Set(events.map(e => e.color))];
|
||||
if (time) {
|
||||
filteredEvents = filteredEvents.filter(e => e.startTime === time);
|
||||
}
|
||||
|
||||
return filteredEvents;
|
||||
};
|
||||
|
||||
// 检查是否是今天
|
||||
@@ -279,17 +291,6 @@ const isWorkDayOnWeekend = (date) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// 获取指定时间的日程
|
||||
const getEventsForDate = (date, time = null) => {
|
||||
let filteredEvents = props.events.filter(e => e.date === date);
|
||||
|
||||
if (time) {
|
||||
filteredEvents = filteredEvents.filter(e => e.startTime === time);
|
||||
}
|
||||
|
||||
return filteredEvents;
|
||||
};
|
||||
|
||||
const selectDate = (date) => {
|
||||
emit('select-date', date);
|
||||
};
|
||||
@@ -310,17 +311,23 @@ const handleDragEnd = () => {
|
||||
|
||||
// 处理拖拽悬停
|
||||
const handleDragOver = (event, date, time = null) => {
|
||||
event.currentTarget.classList.add('drag-over');
|
||||
// 生成唯一标识,用于标记当前拖拽悬停的单元格
|
||||
if (time) {
|
||||
isDragOverCell.value = `${date}-${time}`;
|
||||
} else {
|
||||
isDragOverCell.value = date;
|
||||
}
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
// 处理拖拽离开
|
||||
const handleDragLeave = (event) => {
|
||||
event.currentTarget.classList.remove('drag-over');
|
||||
isDragOverCell.value = null;
|
||||
};
|
||||
|
||||
// 处理放置事件
|
||||
const handleDropEvent = (event, date, time = null) => {
|
||||
event.currentTarget.classList.remove('drag-over');
|
||||
isDragOverCell.value = null;
|
||||
emit('drop-event', event, date, time);
|
||||
};
|
||||
</script>
|
||||
@@ -400,4 +407,4 @@ const handleDropEvent = (event, date, time = null) => {
|
||||
.day-header {
|
||||
background: linear-gradient(to right, #f0f4ff, #e0e7ff);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,101 +1,141 @@
|
||||
<template>
|
||||
<transition name="modal">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 px-4 py-6 backdrop-blur-sm">
|
||||
<div class="modal-container bg-white rounded-2xl w-full max-w-md mx-auto shadow-soft-lg transition-all duration-300 transform scale-100">
|
||||
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
|
||||
<h3 class="text-lg font-bold text-gray-800">添加快捷日程</h3>
|
||||
|
||||
<button @click="$emit('close')" class="w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center transition-colors">
|
||||
<i class="fas fa-times text-gray-600"></i>
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-6">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">标题</label>
|
||||
|
||||
<input
|
||||
v-model="eventData.title"
|
||||
type="text"
|
||||
placeholder="快捷日程名称"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
:class="{'border-red-500': !eventData.title}"
|
||||
>
|
||||
<p v-if="!eventData.title" class="text-red-500 text-xs mt-1">标题不能为空</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">默认持续时间</label>
|
||||
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="eventData.duration"
|
||||
type="number"
|
||||
min="5"
|
||||
max="240"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors pr-12"
|
||||
:class="{'border-red-500': !eventData.duration}"
|
||||
>
|
||||
<span class="absolute inset-y-0 right-4 flex items-center text-gray-500">分钟</span>
|
||||
|
||||
<p v-if="!eventData.duration" class="text-red-500 text-xs mt-1">请填写持续时间</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">选择颜色</label>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
@click="eventData.color = color"
|
||||
:class="{'ring-2 ring-offset-2 ring-gray-800': eventData.color === color}"
|
||||
class="w-8 h-8 rounded-full focus:outline-none transition-all hover:scale-110"
|
||||
:style="{ backgroundColor: color }"
|
||||
></button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
@click="save"
|
||||
class="px-5 py-2.5 text-sm rounded-lg bg-gradient-to-r from-secondary-500 to-secondary-700 hover:from-secondary-600 hover:to-secondary-800 text-white transition-colors font-medium hover:shadow-md"
|
||||
>
|
||||
添加快捷日程
|
||||
<transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="isOpen" class="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 px-4 py-6">
|
||||
<transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div v-if="isOpen" class="modal-container bg-white rounded-2xl w-full max-w-md mx-auto shadow-2xl transition-all duration-300 transform">
|
||||
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
|
||||
<h3 class="text-lg font-bold text-gray-800">添加快捷日程</h3>
|
||||
<button @click="$emit('close')" class="w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center transition-colors hover:scale-110 active:scale-95">
|
||||
<i class="fas fa-times text-gray-600"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
标题 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="eventData.title"
|
||||
type="text"
|
||||
placeholder="快捷日程名称"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
:class="{'border-red-500 ring-2 ring-red-200': !eventData.title && touched.title}"
|
||||
@blur="touched.title = true"
|
||||
>
|
||||
<p v-if="!eventData.title && touched.title" class="text-red-500 text-xs mt-1 animate-pulse">
|
||||
<i class="fas fa-exclamation-circle mr-1"></i> 标题不能为空
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
描述
|
||||
</label>
|
||||
<textarea
|
||||
v-model="eventData.description"
|
||||
placeholder="可选:添加日程描述"
|
||||
rows="3"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
默认持续时间 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="eventData.duration"
|
||||
type="number"
|
||||
min="5"
|
||||
max="240"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200 pr-12"
|
||||
:class="{'border-red-500 ring-2 ring-red-200': !eventData.duration && touched.duration}"
|
||||
@blur="touched.duration = true"
|
||||
>
|
||||
<span class="absolute inset-y-0 right-4 flex items-center text-gray-500">分钟</span>
|
||||
<p v-if="!eventData.duration && touched.duration" class="text-red-500 text-xs mt-1 animate-pulse">
|
||||
<i class="fas fa-exclamation-circle mr-1"></i> 请填写持续时间
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">选择颜色</label>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
@click="eventData.color = color"
|
||||
:class="{'ring-2 ring-offset-2 ring-gray-800': eventData.color === color}"
|
||||
class="w-8 h-8 rounded-full focus:outline-none transition-all hover:scale-110 active:scale-95"
|
||||
:style="{ backgroundColor: color }"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
重复设置
|
||||
</label>
|
||||
<select
|
||||
v-model="eventData.repeat"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
>
|
||||
<option value="">不重复</option>
|
||||
<option value="daily">每天</option>
|
||||
<option value="weekly">每周</option>
|
||||
<option value="monthly">每月</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
@click="$emit('close')"
|
||||
class="px-5 py-2.5 text-sm rounded-lg border border-gray-300 hover:bg-gray-50 text-gray-700 transition-colors font-medium hover:shadow-md hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
@click="save"
|
||||
class="px-5 py-2.5 text-sm rounded-lg bg-gradient-to-r from-secondary-500 to-secondary-700 hover:from-secondary-600 hover:to-secondary-800 text-white transition-colors font-medium hover:shadow-md hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
添加快捷日程
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
isOpen: Boolean
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const colors = [
|
||||
@@ -108,32 +148,37 @@ const colors = [
|
||||
|
||||
const eventData = ref({
|
||||
title: '',
|
||||
description: '',
|
||||
duration: 30,
|
||||
color: colors[1]
|
||||
color: colors[1],
|
||||
repeat: ''
|
||||
});
|
||||
|
||||
const touched = ref({
|
||||
title: false,
|
||||
duration: false
|
||||
});
|
||||
|
||||
const save = () => {
|
||||
if (!eventData.value.title.trim()) {
|
||||
// 标记字段为已触摸
|
||||
touched.value.title = true;
|
||||
touched.value.duration = true;
|
||||
|
||||
if (!eventData.value.title.trim() || !eventData.value.duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('save', eventData.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
/* 模态框动画 */
|
||||
.modal-enter-active,
|
||||
.modal-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-enter-from,
|
||||
.modal-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
</style>
|
||||
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div data-aos="zoom-in" data-aos-duration="500">
|
||||
<!-- 说明卡片 -->
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden mb-6" data-aos="zoom-in">
|
||||
<div class="bg-gradient-to-br from-white to-gray-50 rounded-2xl shadow-soft overflow-hidden mb-6">
|
||||
<div class="bg-gradient-to-r from-primary-500 to-primary-700 px-6 py-4">
|
||||
<h2 class="text-white font-bold text-lg flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
操作指南
|
||||
</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
@@ -16,69 +15,50 @@
|
||||
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-primary-100 text-primary-700 flex items-center justify-center text-xs mr-3">
|
||||
1
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-medium">拖放快捷日程到日历</p>
|
||||
|
||||
<p class="text-sm text-gray-600 mt-1">将左侧的快捷日程拖放到日历中的日期添加新日程</p>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="flex">
|
||||
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-primary-100 text-primary-700 flex items-center justify-center text-xs mr-3">
|
||||
2
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-medium">拖放日程到其他日期</p>
|
||||
|
||||
<p class="text-sm text-gray-600 mt-1">将右侧详情中的日程拖放到日历中的其他日期进行移动</p>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="flex">
|
||||
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-primary-100 text-primary-700 flex items-center justify-center text-xs mr-3">
|
||||
3
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-medium">自定义快捷日程</p>
|
||||
|
||||
<p class="text-sm text-gray-600 mt-1">点击底部按钮添加快捷日程模板</p>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 常用日程卡片 -->
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden" data-aos="zoom-in" data-aos-delay="100">
|
||||
<div class="bg-gradient-to-br from-white to-gray-50 rounded-2xl shadow-soft overflow-hidden" data-aos="zoom-in" data-aos-delay="100">
|
||||
<div class="flex justify-between items-center bg-gradient-to-r from-secondary-500 to-secondary-700 px-6 py-4">
|
||||
<h2 class="text-white font-bold text-lg flex items-center">
|
||||
<i class="fas fa-star mr-2"></i>
|
||||
常用日程
|
||||
<span class="ml-2 text-sm bg-white text-secondary-700 px-2 py-1 rounded-md">可拖拽</span>
|
||||
|
||||
</h2>
|
||||
|
||||
<button
|
||||
@click="$emit('add-common-event')"
|
||||
class="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center transition-colors hover:bg-white/30 hover:scale-110"
|
||||
class="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center transition-colors hover:bg-white/30 hover:scale-110 hover:shadow-sm active:scale-95"
|
||||
>
|
||||
<i class="fas fa-plus text-white text-sm"></i>
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-5 max-h-[420px] overflow-y-auto">
|
||||
@@ -98,72 +78,51 @@
|
||||
|
||||
<div class="ml-4 flex-1">
|
||||
<h3 class="font-medium text-gray-800">{{ event.title }}</h3>
|
||||
|
||||
<div class="flex items-center mt-1 text-xs text-gray-500">
|
||||
<span>持续: {{ event.duration }}分钟</span>
|
||||
|
||||
<span class="mx-2">|</span>
|
||||
|
||||
<span>类型: 快捷</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<span class="text-gray-400 group-hover:text-primary-600 transition-colors">
|
||||
<i class="fas fa-grip-lines"></i>
|
||||
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="commonEvents.length === 0" class="py-8 text-center text-gray-500">
|
||||
<div
|
||||
class="bg-gradient-to-br from-gray-100 to-gray-200 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"
|
||||
>
|
||||
<i class="fas fa-inbox text-2xl text-gray-400"></i>
|
||||
|
||||
</div>
|
||||
|
||||
<p>暂无快捷日程</p>
|
||||
|
||||
<button
|
||||
@click="$emit('add-common-event')"
|
||||
class="mt-4 inline-flex items-center px-4 py-2 bg-gradient-to-r from-secondary-500 to-secondary-700 hover:from-secondary-600 hover:to-secondary-800 text-white rounded-xl transition-all hover:shadow-md"
|
||||
class="mt-4 inline-flex items-center px-4 py-2 bg-gradient-to-r from-secondary-500 to-secondary-700 hover:from-secondary-600 hover:to-secondary-800 text-white rounded-xl transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
添加快捷日程
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-4">
|
||||
<button
|
||||
@click="$emit('add-common-event')"
|
||||
class="w-full flex items-center justify-center py-3 bg-gray-50 hover:bg-gray-100 rounded-xl text-secondary-600 font-medium transition-colors hover:shadow-sm"
|
||||
class="w-full flex items-center justify-center py-3 bg-gray-50 hover:bg-gray-100 rounded-xl text-secondary-600 font-medium transition-colors hover:shadow-sm hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
<i class="fas fa-plus-circle mr-2"></i>
|
||||
新增快捷日程
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
|
||||
@@ -182,7 +141,6 @@ const handleDragEnd = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.common-event {
|
||||
transition: all 0.3s ease;
|
||||
@@ -191,5 +149,11 @@ const handleDragEnd = () => {
|
||||
|
||||
.common-event:hover {
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-white rounded-2xl shadow-soft p-6 h-full"
|
||||
class="bg-gradient-to-br from-white to-gray-50 rounded-2xl shadow-soft p-6 h-full"
|
||||
@dragover.prevent="handleDragOver"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop"
|
||||
:class="{'drag-over': isDragOver}"
|
||||
data-aos="fade-left"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="300"
|
||||
>
|
||||
<div
|
||||
v-if="!selectedDate"
|
||||
@@ -12,29 +15,20 @@
|
||||
data-aos="fade-in"
|
||||
>
|
||||
<div
|
||||
class="bg-gradient-to-br from-gray-200 to-gray-300 w-24 h-24 rounded-full flex items-center justify-center mx-auto mb-5">
|
||||
<i class="fas fa-calendar-star text-4xl text-gray-600"></i>
|
||||
|
||||
class="bg-gradient-to-br from-gray-100 to-gray-200 w-24 h-24 rounded-full flex items-center justify-center mx-auto mb-5">
|
||||
<i class="fas fa-calendar-star text-4xl text-gray-400"></i>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-bold text-gray-800 mb-2">选择日期查看详情</h3>
|
||||
|
||||
<p class="text-gray-600 mb-6">点击日历中的日期查看该日的日程详情</p>
|
||||
|
||||
<div class="flex">
|
||||
<span class="event-dot bg-primary-600"></span>
|
||||
|
||||
<span class="event-dot bg-secondary-500 ml-1"></span>
|
||||
|
||||
<span class="event-dot bg-accent-500 ml-1"></span>
|
||||
|
||||
</div>
|
||||
|
||||
<p class="text-gray-500 mt-4">将快捷日程拖到此处添加到选中日期</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="selectedDate" class="h-full flex flex-col">
|
||||
<div class="pb-4 border-b border-gray-100" data-aos="fade-in">
|
||||
<div class="flex justify-between">
|
||||
@@ -43,46 +37,36 @@
|
||||
<span class="text-lg text-primary-600 ml-2">
|
||||
{{ dayjs(selectedDate).format('dddd') }}
|
||||
</span>
|
||||
|
||||
</h2>
|
||||
|
||||
<button
|
||||
@click="$emit('add-event')"
|
||||
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-md hover:shadow-lg"
|
||||
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-md hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
添加日程
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<span v-if="getFestival(selectedDate)" class="px-3 py-1 bg-red-100 text-red-600 rounded-full text-sm">
|
||||
<i class="fas fa-gift mr-1"></i>
|
||||
{{ getFestival(selectedDate) }}
|
||||
<i class="fas fa-gift mr-1"></i> {{ getFestival(selectedDate) }}
|
||||
</span>
|
||||
|
||||
<span v-if="getSolarTerm(selectedDate)" class="px-3 py-1 bg-green-100 text-green-600 rounded-full text-sm">
|
||||
<i class="fas fa-leaf mr-1"></i>
|
||||
{{ getSolarTerm(selectedDate) }}
|
||||
<i class="fas fa-leaf mr-1"></i> {{ getSolarTerm(selectedDate) }}
|
||||
</span>
|
||||
|
||||
<span v-if="isHoliday(selectedDate)" class="px-3 py-1 bg-yellow-100 text-yellow-600 rounded-full text-sm">
|
||||
<i class="fas fa-umbrella-beach mr-1"></i>
|
||||
休息日
|
||||
<i class="fas fa-umbrella-beach mr-1"></i> 休息日
|
||||
</span>
|
||||
|
||||
<span v-else class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm">
|
||||
<i class="fas fa-briefcase mr-1"></i>
|
||||
工作日
|
||||
<i class="fas fa-briefcase mr-1"></i> 工作日
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-4 flex-1 overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h3 class="font-semibold text-gray-800 flex items-center">
|
||||
@@ -93,16 +77,14 @@
|
||||
<span class="text-sm text-gray-500">
|
||||
共 {{ getEventsForDate(selectedDate).length }} 个日程
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
v-if="getEventsForDate(selectedDate).length > 0"
|
||||
class="space-y-3"
|
||||
>
|
||||
<div
|
||||
v-for="event in getEventsForDate(selectedDate)"
|
||||
v-for="(event, index) in getEventsForDate(selectedDate)"
|
||||
:key="event.id"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, event)"
|
||||
@@ -119,31 +101,23 @@
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between">
|
||||
<h4 class="font-semibold text-gray-800">{{ event.title }}</h4>
|
||||
|
||||
<div>
|
||||
<button
|
||||
@click.stop="$emit('edit-event', event)"
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full bg-white hover:bg-gray-100 text-gray-500 hover:text-primary-600"
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full bg-white hover:bg-gray-100 text-gray-500 hover:text-primary-600 hover:scale-110 active:scale-95"
|
||||
>
|
||||
<i class="fas fa-edit"></i>
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center text-xs px-3 py-1 rounded-full bg-white shadow-sm">
|
||||
<i class="fas fa-clock mr-1" :style="{ color: event.color }"></i>
|
||||
|
||||
<span :style="{ color: event.color }">
|
||||
{{ event.startTime }}
|
||||
<span v-if="event.duration">({{ event.duration }}分钟)</span>
|
||||
|
||||
</span>
|
||||
|
||||
</span>
|
||||
|
||||
<span
|
||||
@@ -151,102 +125,77 @@
|
||||
class="inline-flex items-center text-xs px-3 py-1 rounded-full bg-white shadow-sm"
|
||||
>
|
||||
<i class="fas fa-location-dot mr-1 text-gray-500"></i>
|
||||
|
||||
{{ event.location }}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增字段 -->
|
||||
<div class="mt-4 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">发起人</p>
|
||||
|
||||
<p class="font-medium flex items-center">
|
||||
<i class="fas fa-user-circle mr-2 text-primary-500"></i>
|
||||
|
||||
{{ event.creator?.name || '测试' }}
|
||||
{{ event.creator?.name || '未设置' }}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">状态</p>
|
||||
|
||||
<div>
|
||||
<span v-if="event.completed" class="px-2 py-1 bg-green-100 text-green-800 rounded-full text-xs">
|
||||
已完成
|
||||
</span>
|
||||
|
||||
<span v-else class="px-2 py-1 bg-yellow-100 text-yellow-800 rounded-full text-xs">
|
||||
进行中
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-3">
|
||||
<p class="text-sm text-gray-500">参与人</p>
|
||||
|
||||
<div class="flex flex-wrap mt-1">
|
||||
<div v-for="(participant, index) in event.participants" :key="index"
|
||||
class="flex items-center mr-3 mb-2">
|
||||
<div v-for="(participant, pIndex) in event.participants" :key="pIndex"
|
||||
class="flex items-center mr-3 mb-2 bg-gray-100 px-2 py-1 rounded-full">
|
||||
<i class="fas fa-user mr-1 text-sm text-gray-500"></i>
|
||||
|
||||
<span class="text-sm">{{ participant.name }}</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="!event.participants || event.participants.length === 0" class="text-gray-400 text-xs">
|
||||
暂无参与人
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="event.description" class="mt-3 text-sm text-gray-600">
|
||||
<p class="font-medium text-gray-700 mb-1">描述:</p>
|
||||
<p class="whitespace-pre-line">{{ event.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else class="py-8 text-center text-gray-500" data-aos="fade-in">
|
||||
<div
|
||||
class="bg-gradient-to-br from-gray-100 to-gray-200 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"
|
||||
>
|
||||
<i class="fas fa-calendar-plus text-2xl text-gray-400"></i>
|
||||
|
||||
</div>
|
||||
|
||||
<p>今日暂无日程安排</p>
|
||||
|
||||
<p class="text-sm mt-2 text-gray-500 mb-4">点击"添加日程"或拖放快捷日程创建计划</p>
|
||||
|
||||
<button
|
||||
@click="$emit('add-event')"
|
||||
class="inline-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-md hover:shadow-lg"
|
||||
class="inline-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-md hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
添加新日程
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -331,4 +280,9 @@ const handleDrop = (event) => {
|
||||
box-shadow: inset 0 0 0 0 rgba(100, 94, 255, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,174 +1,216 @@
|
||||
<template>
|
||||
<transition name="modal">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 px-4 py-6 backdrop-blur-sm">
|
||||
<div class="modal-container bg-white rounded-2xl w-full max-w-md mx-auto shadow-soft-lg transition-all duration-300 transform scale-100">
|
||||
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
|
||||
<h3 class="text-lg font-bold text-gray-800">
|
||||
{{ isEditing ? '编辑日程' : '添加新日程' }}
|
||||
</h3>
|
||||
|
||||
<button @click="$emit('close')" class="w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center transition-colors">
|
||||
<i class="fas fa-times text-gray-600"></i>
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-6">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">标题</label>
|
||||
|
||||
<input
|
||||
v-model="eventData.title"
|
||||
type="text"
|
||||
placeholder="日程标题"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
:class="{'border-red-500': !eventData.title}"
|
||||
>
|
||||
<p v-if="!eventData.title" class="text-red-500 text-xs mt-1">标题不能为空</p>
|
||||
<!-- 外层背景动画 -->
|
||||
<transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="isOpen" class="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 px-4 py-6">
|
||||
<!-- 内层模态框动画 -->
|
||||
<transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div class="modal-container bg-white rounded-2xl w-full max-w-md mx-auto shadow-soft-lg overflow-hidden">
|
||||
<!-- 模态框头部 -->
|
||||
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between bg-gradient-to-r from-gray-50 to-white">
|
||||
<h3 class="text-lg font-bold text-gray-800 flex items-center">
|
||||
<i class="fas fa-calendar-plus text-primary-500 mr-2"></i>
|
||||
{{ isEditing ? '编辑日程' : '添加新日程' }}
|
||||
</h3>
|
||||
|
||||
<button @click="$emit('close')" class="w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center transition-colors hover:scale-110 active:scale-95">
|
||||
<i class="fas fa-times text-gray-600"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">日期</label>
|
||||
|
||||
<!-- 模态框主体内容 -->
|
||||
<div class="p-6 overflow-y-auto max-h-[80vh]">
|
||||
<!-- 标题输入 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
标题 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="eventData.date"
|
||||
type="date"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">时间</label>
|
||||
|
||||
<input
|
||||
v-model="eventData.startTime"
|
||||
type="time"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">地点</label>
|
||||
|
||||
<input
|
||||
v-model="eventData.location"
|
||||
v-model="eventData.title"
|
||||
type="text"
|
||||
placeholder="如:会议室"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
placeholder="日程标题"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
:class="{'border-red-500 ring-2 ring-red-200': !eventData.title && touched.title}"
|
||||
@blur="touched.title = true"
|
||||
>
|
||||
<p v-if="!eventData.title && touched.title" class="text-red-500 text-xs mt-1 animate-pulse">
|
||||
<i class="fas fa-exclamation-circle mr-1"></i> 标题不能为空
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">持续时间</label>
|
||||
|
||||
<div class="relative">
|
||||
<!-- 日期和时间 -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
日期 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="eventData.duration"
|
||||
type="number"
|
||||
min="5"
|
||||
max="480"
|
||||
v-model="eventData.date"
|
||||
type="date"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors pr-12"
|
||||
:class="{'border-red-500': !eventData.duration}"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
>
|
||||
<span class="absolute inset-y-0 right-4 flex items-center text-gray-500">分钟</span>
|
||||
|
||||
<p v-if="!eventData.duration" class="text-red-500 text-xs mt-1">请填写持续时间</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
时间 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="eventData.startTime"
|
||||
type="time"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增字段 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">状态</label>
|
||||
|
||||
<div class="flex space-x-4">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" v-model="eventData.completed" :value="false" class="text-primary-600">
|
||||
<span class="ml-2">进行中</span>
|
||||
|
||||
</label>
|
||||
|
||||
<label class="flex items-center">
|
||||
<input type="radio" v-model="eventData.completed" :value="true" class="text-primary-600">
|
||||
<span class="ml-2">已完成</span>
|
||||
|
||||
</label>
|
||||
<!-- 地点和持续时间 -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">地点</label>
|
||||
<input
|
||||
v-model="eventData.location"
|
||||
type="text"
|
||||
placeholder="如:会议室"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
持续时间 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="eventData.duration"
|
||||
type="number"
|
||||
min="5"
|
||||
max="480"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200 pr-12"
|
||||
:class="{'border-red-500 ring-2 ring-red-200': !eventData.duration && touched.duration}"
|
||||
@blur="touched.duration = true"
|
||||
>
|
||||
<span class="absolute inset-y-0 right-4 flex items-center text-gray-500">分钟</span>
|
||||
<p v-if="!eventData.duration && touched.duration" class="text-red-500 text-xs mt-1 animate-pulse">
|
||||
<i class="fas fa-exclamation-circle mr-1"></i> 请填写持续时间
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- 描述 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">描述</label>
|
||||
<textarea
|
||||
v-model="eventData.description"
|
||||
rows="3"
|
||||
placeholder="输入日程详细描述..."
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 参与人 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">参与人</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div v-for="(participant, index) in eventData.participants" :key="index"
|
||||
class="flex items-center bg-gray-100 px-3 py-1 rounded-full">
|
||||
<input
|
||||
v-model="participant.name"
|
||||
type="text"
|
||||
placeholder="姓名"
|
||||
class="bg-transparent outline-none text-sm w-20"
|
||||
>
|
||||
<button @click="removeParticipant(index)" class="ml-1 text-gray-400 hover:text-red-500">
|
||||
<i class="fas fa-times text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@click="addParticipant"
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 text-gray-500 hover:text-primary-600"
|
||||
>
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">选择颜色</label>
|
||||
<!-- 状态 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">状态</label>
|
||||
<div class="flex space-x-4">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" v-model="eventData.completed" :value="false" class="text-primary-600 focus:ring-primary-500">
|
||||
<span class="ml-2">进行中</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" v-model="eventData.completed" :value="true" class="text-primary-600 focus:ring-primary-500">
|
||||
<span class="ml-2">已完成</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<!-- 颜色选择 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">选择颜色</label>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
@click="eventData.color = color"
|
||||
:class="{'ring-2 ring-offset-2 ring-gray-800': eventData.color === color}"
|
||||
class="w-8 h-8 rounded-full focus:outline-none transition-all hover:scale-110 active:scale-95"
|
||||
:style="{ backgroundColor: color }"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-100">
|
||||
<button
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
@click="eventData.color = color"
|
||||
:class="{'ring-2 ring-offset-2 ring-gray-800': eventData.color === color}"
|
||||
class="w-8 h-8 rounded-full focus:outline-none transition-all hover:scale-110"
|
||||
:style="{ backgroundColor: color }"
|
||||
></button>
|
||||
|
||||
@click="$emit('delete')"
|
||||
v-if="isEditing"
|
||||
class="px-4 py-2 text-sm rounded-lg border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 flex items-center"
|
||||
>
|
||||
<i class="fas fa-trash-alt mr-2"></i> 删除
|
||||
</button>
|
||||
<button
|
||||
@click="save"
|
||||
class="px-5 py-2.5 text-sm rounded-lg bg-gradient-to-r from-primary-500 to-primary-700 hover:from-primary-600 hover:to-primary-800 text-white transition-colors font-medium hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 flex items-center"
|
||||
>
|
||||
<i class="fas fa-save mr-2"></i>
|
||||
{{ isEditing ? '更新日程' : '添加日程' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
@click="$emit('delete')"
|
||||
v-if="isEditing"
|
||||
class="px-4 py-2 text-sm rounded-lg border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="save"
|
||||
class="px-5 py-2.5 text-sm rounded-lg bg-gradient-to-r from-primary-500 to-primary-700 hover:from-primary-600 hover:to-primary-800 text-white transition-colors font-medium hover:shadow-md"
|
||||
>
|
||||
{{ isEditing ? '更新日程' : '添加日程' }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
isEditing: Boolean
|
||||
isEditing: Boolean,
|
||||
isOpen: Boolean
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'save', 'delete']);
|
||||
@@ -181,35 +223,125 @@ const colors = [
|
||||
'#8c6bed'
|
||||
];
|
||||
|
||||
const eventData = ref({ ...props.event });
|
||||
const defaultEventData = {
|
||||
title: '',
|
||||
date: '',
|
||||
startTime: '10:00',
|
||||
duration: 30,
|
||||
location: '',
|
||||
description: '',
|
||||
color: colors[0],
|
||||
completed: false,
|
||||
participants: []
|
||||
};
|
||||
|
||||
const eventData = ref({
|
||||
...defaultEventData,
|
||||
...(props.event || {})
|
||||
});
|
||||
|
||||
const touched = ref({
|
||||
title: false,
|
||||
duration: false
|
||||
});
|
||||
|
||||
// 添加参与人
|
||||
const addParticipant = () => {
|
||||
if (!eventData.value.participants) {
|
||||
eventData.value.participants = [];
|
||||
}
|
||||
eventData.value.participants.push({ name: '' });
|
||||
};
|
||||
|
||||
// 移除参与人
|
||||
const removeParticipant = (index) => {
|
||||
eventData.value.participants.splice(index, 1);
|
||||
};
|
||||
|
||||
// 当传入的 event prop 变化时更新本地值
|
||||
watch(() => props.event, (newValue) => {
|
||||
eventData.value = { ...newValue };
|
||||
});
|
||||
eventData.value = {
|
||||
...defaultEventData,
|
||||
...newValue,
|
||||
participants: newValue?.participants ? [...newValue.participants] : []
|
||||
};
|
||||
}, { deep: true });
|
||||
|
||||
// 保存事件
|
||||
const save = () => {
|
||||
if (!eventData.value.title || !eventData.value.duration) {
|
||||
// 标记字段为已触摸
|
||||
touched.value.title = true;
|
||||
touched.value.duration = true;
|
||||
|
||||
// 验证必填字段
|
||||
if (!eventData.value.title.trim() || !eventData.value.duration) {
|
||||
return;
|
||||
}
|
||||
emit('save', eventData.value);
|
||||
|
||||
// 过滤掉空白的参与人
|
||||
const cleanedParticipants = eventData.value.participants?.filter(p => p.name.trim()) || [];
|
||||
|
||||
// 触发保存事件
|
||||
emit('save', {
|
||||
...eventData.value,
|
||||
participants: cleanedParticipants
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
/* 模态框动画 */
|
||||
.modal-enter-active,
|
||||
.modal-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-enter-from,
|
||||
.modal-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 模态框容器动画 */
|
||||
.modal-container {
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 按钮通用样式 */
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 单选按钮样式 */
|
||||
input[type="radio"] {
|
||||
accent-color: #645eff;
|
||||
}
|
||||
|
||||
/* 文本区域样式 */
|
||||
textarea {
|
||||
resize: none;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
/* 背景模糊效果 */
|
||||
.backdrop-blur-sm {
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
/* 通用过渡效果 */
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 动画关键帧 */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
/* 错误提示动画 */
|
||||
.animate-pulse {
|
||||
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,75 +1,64 @@
|
||||
<template>
|
||||
<transition name="modal">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-100 px-4 py-6 backdrop-blur-sm">
|
||||
<transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="showLoginModal" class="fixed inset-0 bg-gray-900 bg-opacity-70 flex items-center justify-center z-50 px-4 py-6 backdrop-blur-md">
|
||||
<div class="modal-container bg-white rounded-2xl w-full max-w-md mx-auto shadow-soft-lg transition-all duration-300 transform scale-100">
|
||||
<div class="p-8">
|
||||
<div class="text-center mb-6">
|
||||
<div class="w-16 h-16 rounded-full bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center mx-auto mb-4">
|
||||
<i class="fas fa-calendar-alt text-white text-2xl"></i>
|
||||
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-bold text-gray-800">欢迎使用智能日程管家</h2>
|
||||
|
||||
<p class="text-gray-600 mt-2">请登录您的账号</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-5">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">用户名</label>
|
||||
|
||||
<input
|
||||
v-model="loginData.username"
|
||||
type="text"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
placeholder="liqi"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">密码</label>
|
||||
|
||||
<input
|
||||
v-model="loginData.password"
|
||||
type="password"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 outline-none transition-colors"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-300 outline-none transition-colors duration-200"
|
||||
placeholder="123456"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">团队</label>
|
||||
|
||||
<div class="px-4 py-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
萧康云医技术开发团队-医生组
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
@click="login"
|
||||
class="w-full py-3 bg-gradient-to-r from-primary-500 to-primary-700 hover:from-primary-600 hover:to-primary-800 text-white rounded-xl font-medium transition-colors shadow-md hover:shadow-lg hover:-translate-y-0.5"
|
||||
class="w-full py-3 bg-gradient-to-r from-primary-500 to-primary-700 hover:from-primary-600 hover:to-primary-800 text-white rounded-xl font-medium transition-all shadow-md hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
@@ -85,34 +74,29 @@ const login = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟登录成功
|
||||
// 登录成功后
|
||||
const user = {
|
||||
id: 'user-001',
|
||||
name: '李齐',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
|
||||
avatar: 'https://api.dicebear.com/7.x/adventurer/svg?seed=Bella',
|
||||
phone: '13800138000',
|
||||
email: 'liqi@example.com',
|
||||
team: '萧康云医技术开发团队-医生组'
|
||||
};
|
||||
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
emit('login', user);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
/* 模态框动画 */
|
||||
.modal-enter-active,
|
||||
.modal-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-enter-from,
|
||||
.modal-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -144,3 +144,18 @@ input:focus, textarea:focus {
|
||||
.error {
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
background: linear-gradient(to bottom, #f0f4ff, #e0e7ff) no-repeat fixed;
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as chineseLunar from 'chinese-lunar';
|
||||
import { getLunar } from 'chinese-lunar-calendar'
|
||||
|
||||
const lunarMonthArray = [
|
||||
'正', '二', '三', '四', '五', '六', '七', '八', '九', '十',
|
||||
'十一', '十二'
|
||||
];
|
||||
|
||||
const lunarArray = [
|
||||
'初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
|
||||
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
|
||||
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 获取农历日期
|
||||
@@ -8,19 +21,16 @@ import * as chineseLunar from 'chinese-lunar';
|
||||
*/
|
||||
export function getLunarDate(date) {
|
||||
try {
|
||||
// 使用chinese-lunar库获取农历信息
|
||||
const lunar = chineseLunar.solarToLunar(new Date(date));
|
||||
|
||||
const month = chineseLunar.format(lunar, 'M');
|
||||
const day = chineseLunar.format(lunar, 'D');
|
||||
const d = dayjs(date);
|
||||
|
||||
console.log(date, chineseLunar.dayName(lunar))
|
||||
// console.log(date, lunar, month + day)
|
||||
if (day === '初一') {
|
||||
return month;
|
||||
} else {
|
||||
return day;
|
||||
}
|
||||
const year = d.year();
|
||||
const month = d.month() + 1;
|
||||
const day = d.date();
|
||||
|
||||
const result = getLunar(year, month, day);
|
||||
console.log(getLunar(year, month, day));
|
||||
return result.dateStr;
|
||||
} catch (error) {
|
||||
console.error('获取农历日期错误:', error);
|
||||
return '';
|
||||
@@ -116,4 +126,4 @@ export function isHoliday(date) {
|
||||
console.error('判断假日错误:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user