mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 10:29:32 +08:00
大图查看页面增加日历展示,支持显示节假日信息,提高实用性
This commit is contained in:
7
webapp/package-lock.json
generated
7
webapp/package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
|
"lunar-javascript": "^1.7.7",
|
||||||
"reka-ui": "^2.7.0",
|
"reka-ui": "^2.7.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
@@ -2022,6 +2023,12 @@
|
|||||||
"vue": ">=3.0.1"
|
"vue": ">=3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lunar-javascript": {
|
||||||
|
"version": "1.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/lunar-javascript/-/lunar-javascript-1.7.7.tgz",
|
||||||
|
"integrity": "sha512-u/KYiwPIBo/0bT+WWfU7qO1d+aqeB90Tuy4ErXenr2Gam0QcWeezUvtiOIyXR7HbVnW2I1DKfU0NBvzMZhbVQw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
|
"lunar-javascript": "^1.7.7",
|
||||||
"reka-ui": "^2.7.0",
|
"reka-ui": "^2.7.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
|
|||||||
593
webapp/src/components/ui/calendar/Calendar.vue
Normal file
593
webapp/src/components/ui/calendar/Calendar.vue
Normal file
@@ -0,0 +1,593 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fixed inset-0 z-40">
|
||||||
|
<div
|
||||||
|
ref="calendarPanel"
|
||||||
|
class="fixed bg-gradient-to-br from-black/30 via-black/20 to-black/30 backdrop-blur-xl rounded-3xl p-3 sm:p-4 w-[calc(100%-1rem)] sm:w-full max-w-[95vw] sm:max-w-[420px] shadow-2xl border border-white/10 cursor-move select-none"
|
||||||
|
:style="{ left: panelPos.x + 'px', top: panelPos.y + 'px' }"
|
||||||
|
@mousedown="startDrag"
|
||||||
|
@touchstart="startDrag"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<!-- 拖动手柄指示器 -->
|
||||||
|
<div class="absolute top-2 left-1/2 -translate-x-1/2 w-12 h-1 bg-white/20 rounded-full"></div>
|
||||||
|
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="flex items-center justify-between mb-3 sm:mb-4 mt-2">
|
||||||
|
<div class="flex items-center gap-1.5 sm:gap-2 flex-1">
|
||||||
|
<button
|
||||||
|
@click.stop="previousMonth"
|
||||||
|
:disabled="!canGoPrevious"
|
||||||
|
class="p-1 sm:p-1.5 hover:bg-white/20 rounded-lg transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 sm:w-4 sm:h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="text-center flex-1">
|
||||||
|
<!-- 年月选择器 -->
|
||||||
|
<div class="flex items-center justify-center gap-1 sm:gap-1.5 mb-0.5">
|
||||||
|
<!-- 年份选择 -->
|
||||||
|
<Select v-model="currentYearString" @update:modelValue="onYearChange">
|
||||||
|
<SelectTrigger
|
||||||
|
class="w-[90px] sm:w-[105px] h-6 sm:h-7 bg-white/10 text-white border-white/20 hover:bg-white/20 backdrop-blur-md font-bold text-xs sm:text-sm px-1.5 sm:px-2"
|
||||||
|
@click.stop
|
||||||
|
@mousedown.stop
|
||||||
|
>
|
||||||
|
<SelectValue>{{ currentYear }}年</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent class="max-h-[300px] bg-gray-900/95 backdrop-blur-xl border-white/20">
|
||||||
|
<SelectItem
|
||||||
|
v-for="year in yearOptions"
|
||||||
|
:key="year"
|
||||||
|
:value="String(year)"
|
||||||
|
class="text-white hover:bg-white/20 focus:bg-white/20 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ year }}年
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<!-- 月份选择 -->
|
||||||
|
<Select v-model="currentMonthString" @update:modelValue="onMonthChange">
|
||||||
|
<SelectTrigger
|
||||||
|
class="w-[65px] sm:w-[75px] h-6 sm:h-7 bg-white/10 text-white border-white/20 hover:bg-white/20 backdrop-blur-md font-bold text-xs sm:text-sm px-1.5 sm:px-2"
|
||||||
|
@click.stop
|
||||||
|
@mousedown.stop
|
||||||
|
>
|
||||||
|
<SelectValue>{{ currentMonth + 1 }}月</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent class="bg-gray-900/95 backdrop-blur-xl border-white/20">
|
||||||
|
<SelectItem
|
||||||
|
v-for="month in 12"
|
||||||
|
:key="month"
|
||||||
|
:value="String(month - 1)"
|
||||||
|
class="text-white hover:bg-white/20 focus:bg-white/20 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ month }}月
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-[10px] sm:text-xs text-white/60 drop-shadow-md font-['Microsoft_YaHei_UI','Microsoft_YaHei',sans-serif] leading-relaxed">
|
||||||
|
{{ lunarMonthYear }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click.stop="nextMonth"
|
||||||
|
:disabled="!canGoNext"
|
||||||
|
class="p-1 sm:p-1.5 hover:bg-white/20 rounded-lg transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 sm:w-4 sm:h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click.stop="$emit('close')"
|
||||||
|
class="p-1 sm:p-1.5 hover:bg-white/20 rounded-lg transition-colors ml-1.5 sm:ml-2"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 sm:w-4 sm:h-4 text-white drop-shadow-lg" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 星期标题 -->
|
||||||
|
<div class="grid grid-cols-7 gap-1 sm:gap-1.5 mb-1.5 sm:mb-2 pointer-events-none">
|
||||||
|
<div
|
||||||
|
v-for="(day, idx) in weekDays"
|
||||||
|
:key="day"
|
||||||
|
class="text-center text-[11px] sm:text-[13px] font-medium py-1 sm:py-1.5 drop-shadow-md leading-none"
|
||||||
|
:class="idx === 0 || idx === 6 ? 'text-red-300/80' : 'text-white/70'"
|
||||||
|
>
|
||||||
|
{{ day }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 日期格子 -->
|
||||||
|
<div class="grid grid-cols-7 gap-1 sm:gap-1.5">
|
||||||
|
<div
|
||||||
|
v-for="(day, index) in calendarDays"
|
||||||
|
:key="index"
|
||||||
|
class="relative aspect-square flex flex-col items-center justify-center rounded-lg transition-opacity pointer-events-none py-0.5 sm:py-1"
|
||||||
|
:class="[
|
||||||
|
day.isCurrentMonth && !day.isFuture ? 'text-white' : 'text-white/25',
|
||||||
|
day.isToday ? 'bg-blue-400/40 ring-2 ring-blue-300/50' : '',
|
||||||
|
day.isSelected ? 'bg-white/30 ring-1 ring-white/40' : '',
|
||||||
|
day.isFuture ? 'opacity-40' : '',
|
||||||
|
day.isWeekend && day.isCurrentMonth ? 'text-red-200/90' : '',
|
||||||
|
(day.apiHoliday?.isOffDay || (!day.apiHoliday && day.isWeekend)) ? 'text-red-300' : ''
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- 休息/上班标记 (API优先,其次周末) - 使用圆形SVG -->
|
||||||
|
<div
|
||||||
|
v-if="day.isCurrentMonth && (day.apiHoliday || day.isWeekend)"
|
||||||
|
class="absolute top-0 right-0 w-[14px] h-[14px] sm:w-4 sm:h-4"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 20 20" class="w-full h-full drop-shadow-md">
|
||||||
|
<circle
|
||||||
|
cx="10"
|
||||||
|
cy="10"
|
||||||
|
r="9"
|
||||||
|
:fill="day.apiHoliday ? (day.apiHoliday.isOffDay ? '#ef4444' : '#3b82f6') : '#ef4444'"
|
||||||
|
opacity="0.65"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
x="9.8"
|
||||||
|
y="10.5"
|
||||||
|
text-anchor="middle"
|
||||||
|
dominant-baseline="middle"
|
||||||
|
fill="white"
|
||||||
|
font-size="11"
|
||||||
|
font-weight="bold"
|
||||||
|
font-family="'Microsoft YaHei UI','Microsoft YaHei','PingFang SC','Hiragino Sans GB',sans-serif"
|
||||||
|
>
|
||||||
|
{{ day.apiHoliday ? (day.apiHoliday.isOffDay ? '休' : '班') : '休' }}
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 公历日期 -->
|
||||||
|
<div
|
||||||
|
class="text-[13px] sm:text-[15px] font-medium drop-shadow-md font-['Helvetica','Arial',sans-serif] leading-none mb-0.5 sm:mb-1"
|
||||||
|
:class="(day.apiHoliday?.isOffDay || (!day.apiHoliday && day.isWeekend)) ? 'text-red-300 font-bold' : ''"
|
||||||
|
>
|
||||||
|
{{ day.day }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 农历/节日/节气 (不显示API节假日名称) -->
|
||||||
|
<div
|
||||||
|
class="text-[9px] sm:text-[10px] leading-tight drop-shadow-sm font-['Microsoft_YaHei_UI','Microsoft_YaHei',sans-serif] text-center px-0.5"
|
||||||
|
:class="[
|
||||||
|
day.festival || day.solarTerm || day.lunarFestival ? 'text-red-300 font-semibold' : 'text-white/60'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ day.festival || day.solarTerm || day.lunarFestival || day.lunarDay }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 今日按钮 -->
|
||||||
|
<div class="mt-3 sm:mt-4 flex justify-center">
|
||||||
|
<button
|
||||||
|
@click.stop="goToToday"
|
||||||
|
class="px-4 sm:px-5 py-1 sm:py-1.5 bg-white/15 hover:bg-white/30 text-white rounded-lg text-[11px] sm:text-xs font-medium transition-all hover:scale-105 active:scale-95 drop-shadow-lg"
|
||||||
|
>
|
||||||
|
回到今天
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
import { Solar } from 'lunar-javascript'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from '@/components/ui/select'
|
||||||
|
import { getHolidaysByYear, getHolidayByDate, type Holidays, type HolidayDay } from '@/lib/holiday-service'
|
||||||
|
|
||||||
|
interface CalendarDay {
|
||||||
|
day: number
|
||||||
|
isCurrentMonth: boolean
|
||||||
|
isToday: boolean
|
||||||
|
isSelected: boolean
|
||||||
|
isFuture: boolean
|
||||||
|
isWeekend: boolean
|
||||||
|
isHoliday: boolean
|
||||||
|
holidayName: string
|
||||||
|
apiHoliday: HolidayDay | null // API返回的假期信息
|
||||||
|
lunarDay: string
|
||||||
|
festival: string
|
||||||
|
lunarFestival: string
|
||||||
|
solarTerm: string
|
||||||
|
date: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
selectedDate: string // YYYY-MM-DD
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
|
||||||
|
|
||||||
|
// 日历面板位置
|
||||||
|
const calendarPanel = ref<HTMLElement | null>(null)
|
||||||
|
const panelPos = ref({ x: 0, y: 0 })
|
||||||
|
const isDragging = ref(false)
|
||||||
|
const dragStart = ref({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
// 初始化面板位置(移动端居中,桌面端右上角)
|
||||||
|
const initPanelPosition = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const windowWidth = window.innerWidth
|
||||||
|
const windowHeight = window.innerHeight
|
||||||
|
const isMobile = windowWidth < 640 // sm breakpoint
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
// 移动端:居中显示
|
||||||
|
const panelWidth = windowWidth - 16 // 左右各8px边距
|
||||||
|
const panelHeight = 580 // 估计高度
|
||||||
|
panelPos.value = {
|
||||||
|
x: 8,
|
||||||
|
y: Math.max(8, (windowHeight - panelHeight) / 2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 桌面端:右上角
|
||||||
|
const panelWidth = Math.min(420, windowWidth * 0.9)
|
||||||
|
const panelHeight = 600
|
||||||
|
panelPos.value = {
|
||||||
|
x: windowWidth - panelWidth - 40,
|
||||||
|
y: Math.min(80, (windowHeight - panelHeight) / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentYear = ref(new Date().getFullYear())
|
||||||
|
const currentMonth = ref(new Date().getMonth())
|
||||||
|
const isChangingMonth = ref(false)
|
||||||
|
|
||||||
|
// 假期数据
|
||||||
|
const holidaysData = ref<Map<number, Holidays | null>>(new Map())
|
||||||
|
const loadingHolidays = ref(false)
|
||||||
|
|
||||||
|
// 字符串版本的年月(用于Select组件)
|
||||||
|
const currentYearString = computed({
|
||||||
|
get: () => String(currentYear.value),
|
||||||
|
set: (val: string) => {
|
||||||
|
currentYear.value = Number(val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentMonthString = computed({
|
||||||
|
get: () => String(currentMonth.value),
|
||||||
|
set: (val: string) => {
|
||||||
|
currentMonth.value = Number(val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 年份改变处理
|
||||||
|
const onYearChange = (value: any) => {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
currentYear.value = Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 月份改变处理
|
||||||
|
const onMonthChange = (value: any) => {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
currentMonth.value = Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成年份选项(从2009年到当前年份+10年)
|
||||||
|
const yearOptions = computed(() => {
|
||||||
|
const currentYearValue = new Date().getFullYear()
|
||||||
|
const years: number[] = []
|
||||||
|
for (let year = currentYearValue - 30; year <= currentYearValue + 10; year++) {
|
||||||
|
years.push(year)
|
||||||
|
}
|
||||||
|
return years
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算是否可以切换月份(不限制)
|
||||||
|
const canGoPrevious = computed(() => {
|
||||||
|
return !isChangingMonth.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const canGoNext = computed(() => {
|
||||||
|
return !isChangingMonth.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化为选中的日期
|
||||||
|
watch(() => props.selectedDate, (newDate) => {
|
||||||
|
if (newDate) {
|
||||||
|
const date = new Date(newDate)
|
||||||
|
currentYear.value = date.getFullYear()
|
||||||
|
currentMonth.value = date.getMonth()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 初始化位置
|
||||||
|
initPanelPosition()
|
||||||
|
|
||||||
|
// 加载假期数据
|
||||||
|
const loadHolidaysForYear = async (year: number) => {
|
||||||
|
if (holidaysData.value.has(year)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingHolidays.value = true
|
||||||
|
try {
|
||||||
|
const data = await getHolidaysByYear(year)
|
||||||
|
holidaysData.value.set(year, data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`加载${year}年假期数据失败:`, error)
|
||||||
|
holidaysData.value.set(year, null)
|
||||||
|
} finally {
|
||||||
|
loadingHolidays.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载当前年份的假期数据
|
||||||
|
onMounted(() => {
|
||||||
|
const currentYearValue = currentYear.value
|
||||||
|
loadHolidaysForYear(currentYearValue)
|
||||||
|
// 预加载前后一年的数据
|
||||||
|
loadHolidaysForYear(currentYearValue - 1)
|
||||||
|
loadHolidaysForYear(currentYearValue + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听年份变化,加载对应的假期数据
|
||||||
|
watch(currentYear, (newYear) => {
|
||||||
|
loadHolidaysForYear(newYear)
|
||||||
|
// 预加载前后一年
|
||||||
|
loadHolidaysForYear(newYear - 1)
|
||||||
|
loadHolidaysForYear(newYear + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 开始拖动
|
||||||
|
const startDrag = (e: MouseEvent | TouchEvent) => {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
// 如果点击的是按钮或其子元素,不触发拖拽
|
||||||
|
if (target.closest('button') || target.closest('[class*="grid"]')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
isDragging.value = true
|
||||||
|
|
||||||
|
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
|
||||||
|
const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
|
||||||
|
|
||||||
|
dragStart.value = {
|
||||||
|
x: clientX - panelPos.value.x,
|
||||||
|
y: clientY - panelPos.value.y
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', onDrag)
|
||||||
|
document.addEventListener('mouseup', stopDrag)
|
||||||
|
document.addEventListener('touchmove', onDrag, { passive: false })
|
||||||
|
document.addEventListener('touchend', stopDrag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖动中
|
||||||
|
const onDrag = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
|
||||||
|
if (e instanceof TouchEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
|
||||||
|
const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
|
||||||
|
|
||||||
|
const newX = clientX - dragStart.value.x
|
||||||
|
const newY = clientY - dragStart.value.y
|
||||||
|
|
||||||
|
// 限制在视口内
|
||||||
|
if (calendarPanel.value) {
|
||||||
|
const rect = calendarPanel.value.getBoundingClientRect()
|
||||||
|
const maxX = window.innerWidth - rect.width
|
||||||
|
const maxY = window.innerHeight - rect.height
|
||||||
|
|
||||||
|
panelPos.value = {
|
||||||
|
x: Math.max(0, Math.min(newX, maxX)),
|
||||||
|
y: Math.max(0, Math.min(newY, maxY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止拖动
|
||||||
|
const stopDrag = () => {
|
||||||
|
isDragging.value = false
|
||||||
|
document.removeEventListener('mousemove', onDrag)
|
||||||
|
document.removeEventListener('mouseup', stopDrag)
|
||||||
|
document.removeEventListener('touchmove', onDrag)
|
||||||
|
document.removeEventListener('touchend', stopDrag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 农历月份年份
|
||||||
|
const lunarMonthYear = computed(() => {
|
||||||
|
const solar = Solar.fromDate(new Date(currentYear.value, currentMonth.value, 15))
|
||||||
|
const lunar = solar.getLunar()
|
||||||
|
return `${lunar.getYearInChinese()}年${lunar.getMonthInChinese()}月`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取日历天数
|
||||||
|
const calendarDays = computed<CalendarDay[]>(() => {
|
||||||
|
const year = currentYear.value
|
||||||
|
const month = currentMonth.value
|
||||||
|
|
||||||
|
// 当月第一天
|
||||||
|
const firstDay = new Date(year, month, 1)
|
||||||
|
const firstDayWeek = firstDay.getDay()
|
||||||
|
|
||||||
|
// 当月最后一天
|
||||||
|
const lastDay = new Date(year, month + 1, 0)
|
||||||
|
const lastDate = lastDay.getDate()
|
||||||
|
|
||||||
|
// 上月最后几天
|
||||||
|
const prevLastDay = new Date(year, month, 0)
|
||||||
|
const prevLastDate = prevLastDay.getDate()
|
||||||
|
|
||||||
|
const days: CalendarDay[] = []
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
// 填充上月日期
|
||||||
|
for (let i = firstDayWeek - 1; i >= 0; i--) {
|
||||||
|
const day = prevLastDate - i
|
||||||
|
const date = new Date(year, month - 1, day)
|
||||||
|
days.push(createDayObject(date, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充当月日期
|
||||||
|
for (let day = 1; day <= lastDate; day++) {
|
||||||
|
const date = new Date(year, month, day)
|
||||||
|
days.push(createDayObject(date, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充下月日期
|
||||||
|
const remainingDays = 42 - days.length // 6行7列
|
||||||
|
for (let day = 1; day <= remainingDays; day++) {
|
||||||
|
const date = new Date(year, month + 1, day)
|
||||||
|
days.push(createDayObject(date, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
return days
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建日期对象
|
||||||
|
const createDayObject = (date: Date, isCurrentMonth: boolean): CalendarDay => {
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
const selectedDate = new Date(props.selectedDate)
|
||||||
|
selectedDate.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
// 转换为农历
|
||||||
|
const solar = Solar.fromDate(date)
|
||||||
|
const lunar = solar.getLunar()
|
||||||
|
|
||||||
|
// 获取节日
|
||||||
|
const festivals = solar.getFestivals()
|
||||||
|
const festival = festivals.length > 0 ? festivals[0] : ''
|
||||||
|
|
||||||
|
// 获取农历节日
|
||||||
|
const lunarFestivals = lunar.getFestivals()
|
||||||
|
const lunarFestival = lunarFestivals.length > 0 ? lunarFestivals[0] : ''
|
||||||
|
|
||||||
|
// 获取节气
|
||||||
|
const solarTerm = lunar.getJieQi()
|
||||||
|
|
||||||
|
// 获取API假期数据 - 使用本地时间避免时区偏移
|
||||||
|
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
||||||
|
const yearHolidays = holidaysData.value.get(date.getFullYear())
|
||||||
|
const apiHoliday = getHolidayByDate(yearHolidays || null, dateStr)
|
||||||
|
|
||||||
|
// 检查是否为假期(使用lunar-javascript的节日信息)
|
||||||
|
let isHoliday = false
|
||||||
|
let holidayName = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (festival || lunarFestival) {
|
||||||
|
// 常见法定节假日
|
||||||
|
const legalHolidays = ['元旦', '春节', '清明', '劳动节', '端午', '中秋', '国庆']
|
||||||
|
const holidayNames = [festival, lunarFestival].filter(Boolean)
|
||||||
|
|
||||||
|
for (const name of holidayNames) {
|
||||||
|
if (legalHolidays.some(legal => name.includes(legal))) {
|
||||||
|
isHoliday = true
|
||||||
|
holidayName = name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('假期信息获取失败:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为周末(周六或周日)
|
||||||
|
const isWeekend = date.getDay() === 0 || date.getDay() === 6
|
||||||
|
|
||||||
|
// 农历日期显示
|
||||||
|
let lunarDay = lunar.getDayInChinese()
|
||||||
|
if (lunar.getDay() === 1) {
|
||||||
|
lunarDay = lunar.getMonthInChinese() + '月'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
day: date.getDate(),
|
||||||
|
isCurrentMonth,
|
||||||
|
isToday: date.getTime() === today.getTime(),
|
||||||
|
isSelected: date.getTime() === selectedDate.getTime(),
|
||||||
|
isFuture: date > today,
|
||||||
|
isWeekend,
|
||||||
|
isHoliday,
|
||||||
|
holidayName,
|
||||||
|
apiHoliday,
|
||||||
|
lunarDay,
|
||||||
|
festival,
|
||||||
|
lunarFestival,
|
||||||
|
solarTerm,
|
||||||
|
date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上一月
|
||||||
|
const previousMonth = () => {
|
||||||
|
if (!canGoPrevious.value) return
|
||||||
|
|
||||||
|
if (currentMonth.value === 0) {
|
||||||
|
currentMonth.value = 11
|
||||||
|
currentYear.value--
|
||||||
|
} else {
|
||||||
|
currentMonth.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一月
|
||||||
|
const nextMonth = () => {
|
||||||
|
if (!canGoNext.value) return
|
||||||
|
|
||||||
|
if (currentMonth.value === 11) {
|
||||||
|
currentMonth.value = 0
|
||||||
|
currentYear.value++
|
||||||
|
} else {
|
||||||
|
currentMonth.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回到今天
|
||||||
|
const goToToday = () => {
|
||||||
|
const today = new Date()
|
||||||
|
currentYear.value = today.getFullYear()
|
||||||
|
currentMonth.value = today.getMonth()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不再支持点击日期选择
|
||||||
|
// 日历仅作为台历展示功能
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
import { onUnmounted } from 'vue'
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('mousemove', onDrag)
|
||||||
|
document.removeEventListener('mouseup', stopDrag)
|
||||||
|
document.removeEventListener('touchmove', onDrag)
|
||||||
|
document.removeEventListener('touchend', stopDrag)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
65
webapp/src/lib/holiday-service.ts
Normal file
65
webapp/src/lib/holiday-service.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// 假期API类型定义
|
||||||
|
export interface HolidayDay {
|
||||||
|
/** 节日名称 */
|
||||||
|
name: string;
|
||||||
|
/** 日期, ISO 8601 格式 */
|
||||||
|
date: string;
|
||||||
|
/** 是否为休息日 */
|
||||||
|
isOffDay: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Holidays {
|
||||||
|
/** 完整年份, 整数。*/
|
||||||
|
year: number;
|
||||||
|
/** 所用国务院文件网址列表 */
|
||||||
|
papers: string[];
|
||||||
|
days: HolidayDay[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 假期数据缓存
|
||||||
|
const holidayCache = new Map<number, Holidays>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定年份的假期数据
|
||||||
|
*/
|
||||||
|
export async function getHolidaysByYear(year: number): Promise<Holidays | null> {
|
||||||
|
// 检查缓存
|
||||||
|
if (holidayCache.has(year)) {
|
||||||
|
return holidayCache.get(year)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://api.coding.icu/cnholiday/${year}.json`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.warn(`获取${year}年假期数据失败: ${response.status}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: Holidays = await response.json();
|
||||||
|
|
||||||
|
// 缓存数据
|
||||||
|
holidayCache.set(year, data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`获取${year}年假期数据出错:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定日期的假期信息
|
||||||
|
*/
|
||||||
|
export function getHolidayByDate(holidays: Holidays | null, dateStr: string): HolidayDay | null {
|
||||||
|
if (!holidays) return null;
|
||||||
|
|
||||||
|
return holidays.days.find(day => day.date === dateStr) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除假期缓存
|
||||||
|
*/
|
||||||
|
export function clearHolidayCache() {
|
||||||
|
holidayCache.clear();
|
||||||
|
}
|
||||||
@@ -17,11 +17,20 @@ const router = createRouter({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/image/:date',
|
path: '/image/:date?',
|
||||||
name: 'ImageView',
|
name: 'ImageView',
|
||||||
component: ImageView,
|
component: ImageView,
|
||||||
meta: {
|
meta: {
|
||||||
title: '图片详情'
|
title: '图片详情'
|
||||||
|
},
|
||||||
|
beforeEnter: (to, _from, next) => {
|
||||||
|
// 如果没有提供日期参数,重定向到今天的日期
|
||||||
|
if (!to.params.date) {
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
next({ path: `/image/${today}`, replace: true })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
20
webapp/src/types/lunar-javascript.d.ts
vendored
Normal file
20
webapp/src/types/lunar-javascript.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
declare module 'lunar-javascript' {
|
||||||
|
export class Solar {
|
||||||
|
static fromDate(date: Date): Solar
|
||||||
|
getLunar(): Lunar
|
||||||
|
getFestivals(): string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Lunar {
|
||||||
|
getYearInChinese(): string
|
||||||
|
getMonthInChinese(): string
|
||||||
|
getDayInChinese(): string
|
||||||
|
getDay(): number
|
||||||
|
getJieQi(): string
|
||||||
|
getFestivals(): string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HolidayUtil {
|
||||||
|
// Add methods if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 顶部工具栏 -->
|
<!-- 顶部工具栏 -->
|
||||||
<div class="absolute top-0 left-0 right-0 bg-gradient-to-b from-black/80 to-transparent p-6 z-10">
|
<div
|
||||||
|
v-show="!showCalendar"
|
||||||
|
class="absolute top-0 left-0 right-0 bg-gradient-to-b from-black/80 to-transparent p-6 z-10 transition-opacity duration-300"
|
||||||
|
:class="{ 'opacity-0 pointer-events-none': showCalendar }"
|
||||||
|
>
|
||||||
<div class="flex items-center justify-between max-w-7xl mx-auto">
|
<div class="flex items-center justify-between max-w-7xl mx-auto">
|
||||||
<button
|
<button
|
||||||
@click="goBack"
|
@click="goBack"
|
||||||
@@ -37,11 +41,11 @@
|
|||||||
|
|
||||||
<!-- 信息悬浮层(类似 Windows 聚焦) -->
|
<!-- 信息悬浮层(类似 Windows 聚焦) -->
|
||||||
<div
|
<div
|
||||||
v-if="showInfo"
|
v-if="showInfo && !showCalendar"
|
||||||
ref="infoPanel"
|
ref="infoPanel"
|
||||||
class="fixed w-[90%] max-w-md bg-black/40 backdrop-blur-lg rounded-xl p-4 transform transition-opacity duration-300 z-10 select-none"
|
class="fixed w-[90%] max-w-md bg-black/40 backdrop-blur-lg rounded-xl p-4 transform transition-all duration-300 z-10 select-none"
|
||||||
:style="{ left: infoPanelPos.x + 'px', top: infoPanelPos.y + 'px' }"
|
:style="{ left: infoPanelPos.x + 'px', top: infoPanelPos.y + 'px' }"
|
||||||
:class="{ 'opacity-100': showInfo, 'opacity-0': !showInfo }"
|
:class="{ 'opacity-100': showInfo && !showCalendar, 'opacity-0 pointer-events-none': showCalendar }"
|
||||||
>
|
>
|
||||||
<!-- 拖动手柄 -->
|
<!-- 拖动手柄 -->
|
||||||
<div
|
<div
|
||||||
@@ -83,7 +87,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部控制栏 -->
|
<!-- 底部控制栏 -->
|
||||||
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6 z-10">
|
<div
|
||||||
|
v-show="!showCalendar"
|
||||||
|
class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6 z-10 transition-opacity duration-300"
|
||||||
|
:class="{ 'opacity-0 pointer-events-none': showCalendar }"
|
||||||
|
>
|
||||||
<div class="flex items-center justify-between max-w-7xl mx-auto">
|
<div class="flex items-center justify-between max-w-7xl mx-auto">
|
||||||
<!-- 日期切换按钮 -->
|
<!-- 日期切换按钮 -->
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
@@ -110,6 +118,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧按钮组 -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<!-- 日历按钮 -->
|
||||||
|
<button
|
||||||
|
@click="toggleCalendar(true)"
|
||||||
|
class="flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md text-white rounded-lg hover:bg-white/20 transition-all"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="hidden sm:inline">日历</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- 信息按钮 -->
|
<!-- 信息按钮 -->
|
||||||
<button
|
<button
|
||||||
v-if="!showInfo"
|
v-if="!showInfo"
|
||||||
@@ -124,6 +145,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 日历弹窗 -->
|
||||||
|
<Calendar
|
||||||
|
v-if="showCalendar"
|
||||||
|
:selected-date="currentDate"
|
||||||
|
@close="toggleCalendar(false)"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 错误状态 -->
|
<!-- 错误状态 -->
|
||||||
<div v-else-if="error" class="absolute inset-0 flex items-center justify-center">
|
<div v-else-if="error" class="absolute inset-0 flex items-center justify-center">
|
||||||
@@ -141,16 +170,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useImageByDate } from '@/composables/useImages'
|
import { useImageByDate } from '@/composables/useImages'
|
||||||
import { bingPaperApi } from '@/lib/api-service'
|
import { bingPaperApi } from '@/lib/api-service'
|
||||||
|
import Calendar from '@/components/ui/calendar/Calendar.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 从 localStorage 读取日历状态,默认关闭
|
||||||
|
const CALENDAR_STATE_KEY = 'imageView_showCalendar'
|
||||||
|
|
||||||
|
// 获取初始日历状态
|
||||||
|
const getInitialCalendarState = (): boolean => {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(CALENDAR_STATE_KEY)
|
||||||
|
return stored === 'true'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to read calendar state from localStorage:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const currentDate = ref(route.params.date as string)
|
const currentDate = ref(route.params.date as string)
|
||||||
const showInfo = ref(true)
|
const showInfo = ref(true)
|
||||||
|
const showCalendar = ref(getInitialCalendarState())
|
||||||
const navigating = ref(false)
|
const navigating = ref(false)
|
||||||
|
|
||||||
// 前后日期可用性
|
// 前后日期可用性
|
||||||
@@ -273,8 +318,16 @@ const checkAdjacentDates = async () => {
|
|||||||
// 初始化位置
|
// 初始化位置
|
||||||
initPanelPosition()
|
initPanelPosition()
|
||||||
|
|
||||||
|
// 监听showCalendar变化并自动保存到localStorage
|
||||||
|
watch(showCalendar, (newValue) => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(CALENDAR_STATE_KEY, String(newValue))
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to save calendar state:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 监听日期变化,检测前后日期可用性
|
// 监听日期变化,检测前后日期可用性
|
||||||
import { watch } from 'vue'
|
|
||||||
watch(currentDate, () => {
|
watch(currentDate, () => {
|
||||||
checkAdjacentDates()
|
checkAdjacentDates()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
@@ -337,6 +390,11 @@ const nextDay = () => {
|
|||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换日历状态(watch会自动保存)
|
||||||
|
const toggleCalendar = (state: boolean) => {
|
||||||
|
showCalendar.value = state
|
||||||
|
}
|
||||||
|
|
||||||
// 键盘导航
|
// 键盘导航
|
||||||
const handleKeydown = (e: KeyboardEvent) => {
|
const handleKeydown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'ArrowLeft' && hasPreviousDay.value) {
|
if (e.key === 'ArrowLeft' && hasPreviousDay.value) {
|
||||||
@@ -344,9 +402,15 @@ const handleKeydown = (e: KeyboardEvent) => {
|
|||||||
} else if (e.key === 'ArrowRight' && hasNextDay.value) {
|
} else if (e.key === 'ArrowRight' && hasNextDay.value) {
|
||||||
nextDay()
|
nextDay()
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
|
if (showCalendar.value) {
|
||||||
|
toggleCalendar(false)
|
||||||
|
} else {
|
||||||
goBack()
|
goBack()
|
||||||
|
}
|
||||||
} else if (e.key === 'i' || e.key === 'I') {
|
} else if (e.key === 'i' || e.key === 'I') {
|
||||||
showInfo.value = !showInfo.value
|
showInfo.value = !showInfo.value
|
||||||
|
} else if (e.key === 'c' || e.key === 'C') {
|
||||||
|
toggleCalendar(!showCalendar.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,15 +37,34 @@ export default defineConfig(({ mode }) => {
|
|||||||
// 入口文件名
|
// 入口文件名
|
||||||
entryFileNames: 'assets/[name]-[hash].js',
|
entryFileNames: 'assets/[name]-[hash].js',
|
||||||
// 手动分割代码
|
// 手动分割代码
|
||||||
manualChunks: {
|
manualChunks: (id) => {
|
||||||
// 将 Vue 相关代码单独打包
|
// 将 node_modules 中的依赖分割成不同的 chunk
|
||||||
'vue-vendor': ['vue', 'vue-router'],
|
if (id.includes('node_modules')) {
|
||||||
// 将 UI 组件库单独打包(如果有的话)
|
// Vue 核心库
|
||||||
// 'ui-vendor': ['其他UI库']
|
if (id.includes('vue') || id.includes('vue-router')) {
|
||||||
|
return 'vue-vendor'
|
||||||
|
}
|
||||||
|
// Radix UI / Reka UI 组件库
|
||||||
|
if (id.includes('reka-ui') || id.includes('@vueuse')) {
|
||||||
|
return 'ui-vendor'
|
||||||
|
}
|
||||||
|
// Lucide 图标库
|
||||||
|
if (id.includes('lucide-vue-next')) {
|
||||||
|
return 'icons'
|
||||||
|
}
|
||||||
|
// lunar-javascript 农历库
|
||||||
|
if (id.includes('lunar-javascript')) {
|
||||||
|
return 'lunar'
|
||||||
|
}
|
||||||
|
// 其他 node_modules 依赖
|
||||||
|
return 'vendor'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 增加 chunk 大小警告限制
|
||||||
|
chunkSizeWarningLimit: 1000
|
||||||
|
},
|
||||||
// 开发服务器配置
|
// 开发服务器配置
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
|||||||
Reference in New Issue
Block a user