diff --git a/package.json b/package.json index 3c82eaa..f1c9d45 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,21 @@ "preview": "vite preview" }, "dependencies": { - "tailwindcss": "^4.1.4", - "vue": "^3.5.13", "@ant-design-vue/pro-layout": "^3.2.5", "@ant-design/icons-vue": "^7.0.1", "@fortawesome/fontawesome-free": "^6.7.2", "@tailwindcss/vite": "^4.1.4", "@vueuse/head": "^2.0.0", "ant-design-vue": "^4.1.1", + "aos": "^2.3.4", "axios": "^1.6.2", + "chinese-lunar": "^0.1.4", + "dayjs": "^1.11.13", "lucide-vue-next": "^0.507.0", "pinia": "^2.1.7", "swiper": "^11.0.5", + "tailwindcss": "^4.1.4", + "vue": "^3.5.13", "vue-router": "^4.2.5" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 061cee9..3191141 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@ant-design/icons-vue': specifier: ^7.0.1 version: 7.0.1(vue@3.5.13) + '@fortawesome/fontawesome-free': + specifier: ^6.7.2 + version: 6.7.2 '@tailwindcss/vite': specifier: ^4.1.4 version: 4.1.4(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.88.0)) @@ -29,6 +32,12 @@ importers: axios: specifier: ^1.6.2 version: 1.9.0 + chinese-lunar: + specifier: ^0.1.4 + version: 0.1.4 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 lucide-vue-next: specifier: ^0.507.0 version: 0.507.0(vue@3.5.13) @@ -263,6 +272,10 @@ packages: cpu: [x64] os: [win32] + '@fortawesome/fontawesome-free@6.7.2': + resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==} + engines: {node: '>=6'} + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} @@ -295,42 +308,36 @@ 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==} @@ -388,67 +395,56 @@ 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==} @@ -506,28 +502,24 @@ 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==} @@ -655,6 +647,9 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + chinese-lunar@0.1.4: + resolution: {integrity: sha512-E4seb9UZkSJtkm65Ry2ZLWTw5Z87DOyGcCePdg3ft7da342hnEliMtcDAPSn+YSo/o0xl1Egmhh671kxEJgY+g==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -851,28 +846,24 @@ 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==} @@ -1229,6 +1220,8 @@ snapshots: '@esbuild/win32-x64@0.25.3': optional: true + '@fortawesome/fontawesome-free@6.7.2': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@parcel/watcher-android-arm64@2.5.1': @@ -1577,6 +1570,8 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + chinese-lunar@0.1.4: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 diff --git a/src/App.vue b/src/App.vue index 3602e60..7f10cc3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,481 @@ - - - +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); + } +}); + + + \ No newline at end of file diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue new file mode 100644 index 0000000..ca603bd --- /dev/null +++ b/src/components/CalendarView.vue @@ -0,0 +1,403 @@ + + + + + \ No newline at end of file diff --git a/src/components/CommonEventModal.vue b/src/components/CommonEventModal.vue new file mode 100644 index 0000000..1a68cc6 --- /dev/null +++ b/src/components/CommonEventModal.vue @@ -0,0 +1,139 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/CommonEvents.vue b/src/components/CommonEvents.vue new file mode 100644 index 0000000..870245f --- /dev/null +++ b/src/components/CommonEvents.vue @@ -0,0 +1,195 @@ + + + + + + + diff --git a/src/components/EventDetail.vue b/src/components/EventDetail.vue new file mode 100644 index 0000000..df61e54 --- /dev/null +++ b/src/components/EventDetail.vue @@ -0,0 +1,334 @@ + + + + + + \ No newline at end of file diff --git a/src/components/EventModal.vue b/src/components/EventModal.vue new file mode 100644 index 0000000..085c408 --- /dev/null +++ b/src/components/EventModal.vue @@ -0,0 +1,215 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/LoginModal.vue b/src/components/LoginModal.vue new file mode 100644 index 0000000..7bfb419 --- /dev/null +++ b/src/components/LoginModal.vue @@ -0,0 +1,118 @@ + + + + + + + diff --git a/src/main.js b/src/main.js index efadd61..5474505 100644 --- a/src/main.js +++ b/src/main.js @@ -5,6 +5,9 @@ import './style.css' import App from './App.vue' // 注册路由 import router from './router' +import '@fortawesome/fontawesome-free/css/all.min.css'; +import AOS from 'aos' +import 'aos/dist/aos.css' const app = createApp(App) @@ -13,4 +16,10 @@ const app = createApp(App) app.use(createPinia()) app.use(router) app.use(Antd) +app.use(AOS.init({ + duration: 800, + offset: 100, + easing: 'ease-in-out', + once: true +})) app.mount('#app') diff --git a/src/style.css b/src/style.css index a461c50..b7ac51a 100644 --- a/src/style.css +++ b/src/style.css @@ -1 +1,146 @@ -@import "tailwindcss"; \ No newline at end of file +@import "tailwindcss"; + + +:root { + --primary-50: #f5f7ff; + --primary-100: #eef1ff; + --primary-200: #dee4ff; + --primary-300: #c3ceff; + --primary-400: #a1abff; + --primary-500: #7f88ff; + --primary-600: #645eff; + --primary-700: #4f47e9; + --primary-800: #423cc4; + --primary-900: #39369e; + + --secondary-50: #f0fdfa; + --secondary-100: #cffaf0; + --secondary-200: #9af4e3; + --secondary-300: #5de8d4; + --secondary-400: #2bd4c0; + --secondary-500: #0fb8a9; + --secondary-600: #07938c; + --secondary-700: #0b746f; + --secondary-800: #0f5c59; + --secondary-900: #114c4a; + + --accent-500: #ff7b54; +} + +.bg-primary-50 { + background-color: var(--primary-50); +} +.bg-primary-100 { + background-color: var(--primary-100); +} +.bg-primary-500 { + background-color: var(--primary-500); +} +.bg-primary-600 { + background-color: var(--primary-600); +} +.bg-primary-700 { + background-color: var(--primary-700); +} +.bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); +} +.from-primary-500 { + --tw-gradient-from: var(--primary-500); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} +.to-primary-700 { + --tw-gradient-to: var(--primary-700); +} +.from-secondary-500 { + --tw-gradient-from: var(--secondary-500); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} +.to-secondary-700 { + --tw-gradient-to: var(--secondary-700); +} +.text-primary-500 { + color: var(--primary-500); +} +.text-primary-600 { + color: var(--primary-600); +} +.bg-secondary-500 { + background-color: var(--secondary-500); +} +.text-white { + color: #fff; +} +.text-gray-800 { + color: #1f2937; +} +.text-gray-700 { + color: #374151; +} +.text-gray-600 { + color: #4b5563; +} +.text-gray-500 { + color: #6b7280; +} +.text-gray-400 { + color: #9ca3af; +} +.bg-white { + background-color: #fff; +} +.bg-gray-50 { + background-color: #f9fafb; +} +.bg-gray-100 { + background-color: #f3f4f6; +} +.bg-red-50 { + background-color: #fef2f2; +} +.text-red-500 { + color: #ef4444; +} +.bg-green-50 { + background-color: #f0fdf4; +} +.text-green-600 { + color: #16a34a; +} +.text-amber-600 { + color: #d97706; +} + +.shadow-soft { + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08); +} + +.shadow-soft-lg { + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12); +} + +/* 按钮悬停效果 */ +button, .btn { + transition: all 0.3s ease; + cursor: pointer; +} + +button:hover, .btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +button:active, .btn:active { + transform: translateY(1px); +} + +/* 表单焦点样式 */ +input:focus, textarea:focus { + border-color: var(--primary-500); + box-shadow: 0 0 0 3px rgba(100, 94, 255, 0.1); +} + +/* 错误状态 */ +.error { + border-color: #ef4444; +} diff --git a/src/utils/calendarUtils.js b/src/utils/calendarUtils.js new file mode 100644 index 0000000..29587e8 --- /dev/null +++ b/src/utils/calendarUtils.js @@ -0,0 +1,119 @@ +import dayjs from 'dayjs'; +import * as chineseLunar from 'chinese-lunar'; + +/** + * 获取农历日期 + * @param {string} date - YYYY-MM-DD格式的日期 + * @returns {string} 农历日期(初一显示月份) + */ +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'); + + console.log(date, chineseLunar.dayName(lunar)) + // console.log(date, lunar, month + day) + if (day === '初一') { + return month; + } else { + return day; + } + } catch (error) { + console.error('获取农历日期错误:', error); + return ''; + } +} + +/** + * 获取节假日信息 + * @param {string} date - YYYY-MM-DD格式的日期 + * @returns {string|null} 节假日名称(如果没有则为null) + */ +export function getFestival(date) { + try { + return ''; + // 使用dayjs解析日期 + const d = dayjs(date); + const month = d.month() + 1; + const day = d.date(); + + // 公历节日 + if (month === 1 && day === 1) return '元旦'; + if (month === 5 && day === 1) return '劳动节'; + if (month === 10 && day === 1) return '国庆节'; + + // 农历节日 - 使用chinese-lunar获取农历日期 + const year = d.year(); + const lunar = chineseLunar.solarToLunar(new Date(date)); + + // 农历节日 + if (lunar.lunarMonth === 1 && lunar.lunarDay === 1) return '春节'; + if (lunar.lunarMonth === 1 && lunar.lunarDay === 15) return '元宵节'; + if (lunar.lunarMonth === 5 && lunar.lunarDay === 5) return '端午节'; + if (lunar.lunarMonth === 8 && lunar.lunarDay === 15) return '中秋节'; + if (lunar.lunarMonth === 9 && lunar.lunarDay === 9) return '重阳节'; + + return null; + } catch (error) { + console.error('获取节日错误:', error); + return null; + } +} + +/** + * 获取节气信息 + * @param {string} date - YYYY-MM-DD格式的日期 + * @returns {string|null} 节气名称(如果没有则为null) + */ +export function getSolarTerm(date) { + return '大雪'; + const d = dayjs(date); + const month = d.month() + 1; + const day = d.date(); + +// 24节气(简化版) + const terms = [ + // 每个月的两个节气 + [1, 5, '小寒'], [1, 20, '大寒'], + [2, 4, '立春'], [2, 19, '雨水'], + [3, 5, '惊蛰'], [3, 20, '春分'], + [4, 5, '清明'], [4, 20, '谷雨'], + [5, 5, '立夏'], [5, 21, '小满'], + [6, 6, '芒种'], [6, 21, '夏至'], + [7, 7, '小暑'], [7, 23, '大暑'], + [8, 7, '立秋'], [8, 23, '处暑'], + [9, 8, '白露'], [9, 23, '秋分'], + [10, 8, '寒露'], [10, 23, '霜降'], + [11, 7, '立冬'], [11, 22, '小雪'], + [12, 7, '大雪'], [12, 22, '冬至'] + ]; + +} + +/** + * 是否为法定假日 + * @param {string} date - YYYY-MM-DD格式的日期 + * @returns {boolean} 是否为法定假日 + */ +export function isHoliday(date) { + try { + // 使用dayjs解析日期 + const d = dayjs(date); + const dayOfWeek = d.day(); + + // 周六或周日为休息日 + if (dayOfWeek === 0 || dayOfWeek === 6) { + return true; + } + + // 检查是否为法定节假日 + const festival = getFestival(date); + return !!festival; + } catch (error) { + console.error('判断假日错误:', error); + return false; + } +} \ No newline at end of file diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 0000000..e69de29 diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..1ddeb4c --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,43 @@ +module.exports = { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + primary: { + 50: '#f5f7ff', + 100: '#eef1ff', + 200: '#dee4ff', + 300: '#c3ceff', + 400: '#a1abff', + 500: '#7f88ff', + 600: '#645eff', + 700: '#4f47e9', + 800: '#423cc4', + 900: '#39369e', + 950: '#201f59', + }, + secondary: { + 50: '#f0fdfa', + 100: '#cffaf0', + 200: '#9af4e3', + 300: '#5de8d4', + 400: '#2bd4c0', + 500: '#0fb8a9', + 600: '#07938c', + 700: '#0b746f', + 800: '#0f5c59', + 900: '#114c4a', + 950: '#022c2c', + } + }, + boxShadow: { + 'soft': '0 8px 32px rgba(31, 38, 135, 0.1)', + 'soft-lg': '0 12px 48px rgba(31, 38, 135, 0.15)', + } + } + }, + plugins: [], +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index df31b24..0fc4669 100644 --- a/vite.config.js +++ b/vite.config.js @@ -15,7 +15,7 @@ export default defineConfig({ } }, server: { - port: 8888, + port: 17000, proxy: { '/api': { // target: 'http://127.0.0.1:18007/api/',