mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 16:59:32 +08:00
增加多地区每日图片抓取能力
This commit is contained in:
@@ -102,6 +102,16 @@
|
||||
local: 直接返回图片流; redirect: 重定向到存储位置
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Label for="api-fallback">启用地区不存在时兜底</Label>
|
||||
<Switch
|
||||
id="api-fallback"
|
||||
v-model="config.API.EnableMktFallback"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
如果请求的地区无数据,自动回退到默认地区
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -372,6 +382,31 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 抓取配置 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>抓取配置</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>抓取地区</Label>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mt-2">
|
||||
<div v-for="region in allRegions" :key="region.value" class="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
:id="'region-'+region.value"
|
||||
:checked="config.Fetcher.Regions.includes(region.value)"
|
||||
@update:checked="(checked: any) => toggleRegion(region.value, !!checked)"
|
||||
/>
|
||||
<Label :for="'region-'+region.value" class="text-sm font-normal cursor-pointer">{{ region.label }}</Label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
勾选需要定期抓取壁纸的地区。如果不勾选任何地区,默认将只抓取 zh-CN。
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 功能特性配置 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -414,6 +449,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { apiService } from '@/lib/api-service'
|
||||
import type { Config } from '@/lib/api-types'
|
||||
@@ -424,9 +460,12 @@ const loadError = ref('')
|
||||
const saveLoading = ref(false)
|
||||
const dsnError = ref('')
|
||||
|
||||
// 所有可选地区列表
|
||||
const allRegions = ref<any[]>([])
|
||||
|
||||
const config = ref<Config>({
|
||||
Admin: { PasswordBcrypt: '' },
|
||||
API: { Mode: 'local' },
|
||||
API: { Mode: 'local', EnableMktFallback: true },
|
||||
Cron: { Enabled: true, DailySpec: '0 9 * * *' },
|
||||
DB: { Type: 'sqlite', DSN: '' },
|
||||
Feature: { WriteDailyFiles: true },
|
||||
@@ -464,12 +503,37 @@ const config = ref<Config>({
|
||||
}
|
||||
},
|
||||
Token: { DefaultTTL: '168h' },
|
||||
Web: { Path: './webapp/dist' }
|
||||
Web: { Path: './webapp/dist' },
|
||||
Fetcher: { Regions: [] }
|
||||
})
|
||||
|
||||
const configJson = ref('')
|
||||
const jsonError = ref('')
|
||||
|
||||
// 获取所有地区
|
||||
const fetchRegions = async () => {
|
||||
try {
|
||||
const data = await apiService.getRegions()
|
||||
allRegions.value = data
|
||||
} catch (err) {
|
||||
console.error('获取地区列表失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleRegion = (regionValue: string, checked: boolean) => {
|
||||
if (!config.value.Fetcher.Regions) {
|
||||
config.value.Fetcher.Regions = []
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
if (!config.value.Fetcher.Regions.includes(regionValue)) {
|
||||
config.value.Fetcher.Regions.push(regionValue)
|
||||
}
|
||||
} else {
|
||||
config.value.Fetcher.Regions = config.value.Fetcher.Regions.filter(r => r !== regionValue)
|
||||
}
|
||||
}
|
||||
|
||||
// DSN 示例
|
||||
const dsnExamples = computed(() => {
|
||||
switch (config.value.DB.Type) {
|
||||
@@ -602,6 +666,7 @@ const handleSaveConfig = async () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRegions()
|
||||
fetchConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -103,6 +103,10 @@
|
||||
<code class="text-yellow-400 min-w-24">format</code>
|
||||
<span class="text-white/50">格式: jpg (默认: jpg)</span>
|
||||
</div>
|
||||
<div class="flex gap-4 text-sm">
|
||||
<code class="text-yellow-400 min-w-24">mkt</code>
|
||||
<span class="text-white/50">地区编码 (如 zh-CN, en-US, ja-JP),默认由服务器自动探测</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -182,6 +186,10 @@
|
||||
<code class="text-yellow-400 min-w-24">format</code>
|
||||
<span class="text-white/50">格式 (默认: jpg)</span>
|
||||
</div>
|
||||
<div class="flex gap-4 text-sm">
|
||||
<code class="text-yellow-400 min-w-24">mkt</code>
|
||||
<span class="text-white/50">地区编码 (如 zh-CN, en-US, ja-JP)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -254,6 +262,10 @@
|
||||
<code class="text-yellow-400 min-w-24">format</code>
|
||||
<span class="text-white/50">格式 (默认: jpg)</span>
|
||||
</div>
|
||||
<div class="flex gap-4 text-sm">
|
||||
<code class="text-yellow-400 min-w-24">mkt</code>
|
||||
<span class="text-white/50">地区编码 (如 zh-CN, en-US, ja-JP)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -334,6 +346,10 @@
|
||||
<code class="text-yellow-400 min-w-32">date</code>
|
||||
<span class="text-white/60">图片日期(格式:YYYY-MM-DD)</span>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<code class="text-yellow-400 min-w-32">mkt</code>
|
||||
<span class="text-white/60">地区编码(如 zh-CN, en-US)</span>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<code class="text-yellow-400 min-w-32">title</code>
|
||||
<span class="text-white/60">图片标题</span>
|
||||
@@ -458,24 +474,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { API_BASE_URL } from '@/lib/api-config'
|
||||
import { getDefaultMkt } from '@/lib/mkt-utils'
|
||||
|
||||
const baseURL = ref(API_BASE_URL)
|
||||
const previewImage = ref<string | null>(null)
|
||||
const defaultMkt = getDefaultMkt()
|
||||
|
||||
// 获取今日图片示例
|
||||
const getTodayImageExample = () => {
|
||||
return `${baseURL.value}/image/today?variant=UHD&format=jpg`
|
||||
return `${baseURL.value}/image/today?variant=UHD&format=jpg&mkt=${defaultMkt}`
|
||||
}
|
||||
|
||||
// 获取指定日期图片示例
|
||||
const getDateImageExample = () => {
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
return `${baseURL.value}/image/date/${today}?variant=1920x1080&format=jpg`
|
||||
return `${baseURL.value}/image/date/${today}?variant=1920x1080&format=jpg&mkt=${defaultMkt}`
|
||||
}
|
||||
|
||||
// 获取随机图片示例
|
||||
const getRandomImageExample = () => {
|
||||
return `${baseURL.value}/image/random?variant=UHD&format=jpg`
|
||||
return `${baseURL.value}/image/random?variant=UHD&format=jpg&mkt=${defaultMkt}`
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
|
||||
@@ -79,6 +79,23 @@
|
||||
|
||||
<!-- 筛选器 -->
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<!-- 地区选择 -->
|
||||
<Select v-model="selectedMkt" @update:model-value="onMktChange">
|
||||
<SelectTrigger class="w-[180px] bg-white/10 backdrop-blur-md text-white border-white/20 hover:bg-white/15 hover:border-white/30 focus:ring-white/50 shadow-lg">
|
||||
<SelectValue placeholder="选择地区" />
|
||||
</SelectTrigger>
|
||||
<SelectContent class="bg-gray-900/95 backdrop-blur-xl border-white/20 text-white">
|
||||
<SelectItem
|
||||
v-for="region in regions"
|
||||
:key="region.value"
|
||||
:value="region.value"
|
||||
class="focus:bg-white/10 focus:text-white cursor-pointer"
|
||||
>
|
||||
{{ region.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<!-- 年份选择 -->
|
||||
<Select v-model="selectedYear" @update:model-value="onYearChange">
|
||||
<SelectTrigger class="w-[180px] bg-white/10 backdrop-blur-md text-white border-white/20 hover:bg-white/15 hover:border-white/30 focus:ring-white/50 shadow-lg">
|
||||
@@ -153,7 +170,7 @@
|
||||
</div>
|
||||
<img
|
||||
v-else
|
||||
:src="getImageUrl(image.date!)"
|
||||
:src="getImageUrl(image)"
|
||||
:alt="image.title || 'Bing Image'"
|
||||
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
loading="lazy"
|
||||
@@ -248,6 +265,7 @@ import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useImageList } from '@/composables/useImages'
|
||||
import { bingPaperApi } from '@/lib/api-service'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getDefaultMkt, setSavedMkt, SUPPORTED_REGIONS, setSupportedRegions } from '@/lib/mkt-utils'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -258,18 +276,21 @@ import {
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 地区列表
|
||||
const regions = ref(SUPPORTED_REGIONS)
|
||||
|
||||
// 顶部最新图片(独立加载,不受筛选影响)
|
||||
const latestImage = ref<any>(null)
|
||||
const todayLoading = ref(false)
|
||||
|
||||
// 历史图片列表(使用服务端分页和筛选,每页15张)
|
||||
const { images, loading, hasMore, loadMore, filterByMonth } = useImageList(15)
|
||||
const { images, loading, hasMore, loadMore, filterByMonth, filterByMkt } = useImageList(15)
|
||||
|
||||
// 加载顶部最新图片
|
||||
const loadLatestImage = async () => {
|
||||
todayLoading.value = true
|
||||
try {
|
||||
const params = { page: 1, page_size: 1 }
|
||||
const params: any = { page: 1, page_size: 1, mkt: selectedMkt.value }
|
||||
const result = await bingPaperApi.getImages(params)
|
||||
if (result.length > 0) {
|
||||
latestImage.value = result[0]
|
||||
@@ -281,8 +302,17 @@ const loadLatestImage = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载顶部图片
|
||||
onMounted(() => {
|
||||
// 初始化加载
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const backendRegions = await bingPaperApi.getRegions()
|
||||
if (backendRegions && backendRegions.length > 0) {
|
||||
regions.value = backendRegions
|
||||
setSupportedRegions(backendRegions)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch regions:', error)
|
||||
}
|
||||
loadLatestImage()
|
||||
})
|
||||
|
||||
@@ -315,9 +345,22 @@ const nextUpdateTime = computed(() => {
|
||||
})
|
||||
|
||||
// 筛选相关状态
|
||||
const selectedMkt = ref(getDefaultMkt())
|
||||
const selectedYear = ref('')
|
||||
const selectedMonth = ref('')
|
||||
|
||||
const onMktChange = () => {
|
||||
setSavedMkt(selectedMkt.value)
|
||||
filterByMkt(selectedMkt.value)
|
||||
loadLatestImage()
|
||||
|
||||
// 重置懒加载状态
|
||||
imageVisibility.value = []
|
||||
setTimeout(() => {
|
||||
setupObserver()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 懒加载相关
|
||||
const imageRefs = ref<(HTMLElement | null)[]>([])
|
||||
const imageVisibility = ref<boolean[]>([])
|
||||
@@ -374,11 +417,14 @@ const onFilterChange = () => {
|
||||
|
||||
// 重置筛选
|
||||
const resetFilters = () => {
|
||||
selectedMkt.value = getDefaultMkt()
|
||||
selectedYear.value = ''
|
||||
selectedMonth.value = ''
|
||||
|
||||
// 重置为加载默认数据
|
||||
filterByMkt(selectedMkt.value)
|
||||
filterByMonth(undefined)
|
||||
loadLatestImage()
|
||||
|
||||
// 重置懒加载状态
|
||||
imageVisibility.value = []
|
||||
@@ -521,12 +567,12 @@ const formatDate = (dateStr?: string) => {
|
||||
// 获取最新图片 URL(顶部大图使用UHD高清)
|
||||
const getLatestImageUrl = () => {
|
||||
if (!latestImage.value?.date) return ''
|
||||
return bingPaperApi.getImageUrlByDate(latestImage.value.date, 'UHD', 'jpg')
|
||||
return bingPaperApi.getImageUrlByDate(latestImage.value.date, 'UHD', 'jpg', latestImage.value.mkt)
|
||||
}
|
||||
|
||||
// 获取图片 URL(缩略图 - 使用较小分辨率节省流量)
|
||||
const getImageUrl = (date: string) => {
|
||||
return bingPaperApi.getImageUrlByDate(date, '640x480', 'jpg')
|
||||
const getImageUrl = (image: any) => {
|
||||
return bingPaperApi.getImageUrlByDate(image.date!, '640x480', 'jpg', image.mkt)
|
||||
}
|
||||
|
||||
// 查看图片详情
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<!-- 拖动手柄 -->
|
||||
<div
|
||||
@mousedown="startDrag"
|
||||
@touchstart="startDrag"
|
||||
@touchstart.passive="startDrag"
|
||||
class="absolute top-2 left-1/2 -translate-x-1/2 w-12 h-1 bg-white/30 rounded-full cursor-move hover:bg-white/50 transition-colors touch-none"
|
||||
></div>
|
||||
|
||||
@@ -178,10 +178,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useImageByDate } from '@/composables/useImages'
|
||||
import { bingPaperApi } from '@/lib/api-service'
|
||||
import { getDefaultMkt } from '@/lib/mkt-utils'
|
||||
import Calendar from '@/components/ui/calendar/Calendar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -202,12 +203,17 @@ const getInitialCalendarState = (): boolean => {
|
||||
}
|
||||
|
||||
const currentDate = ref(route.params.date as string)
|
||||
const currentMkt = ref(route.query.mkt as string || getDefaultMkt())
|
||||
const showInfo = ref(true)
|
||||
const showCalendar = ref(getInitialCalendarState())
|
||||
const navigating = ref(false)
|
||||
const imageOpacity = ref(1)
|
||||
const imageTransitioning = ref(false)
|
||||
|
||||
// 响应式窗口大小
|
||||
const windowSize = ref({ width: window.innerWidth, height: window.innerHeight })
|
||||
const isMobile = computed(() => windowSize.value.width < 768)
|
||||
|
||||
// 前后日期可用性
|
||||
const hasPreviousDay = ref(true)
|
||||
const hasNextDay = ref(true)
|
||||
@@ -222,11 +228,10 @@ let animationFrameId: number | null = null
|
||||
|
||||
// 计算图片实际显示区域(考虑图片宽高比和object-contain)
|
||||
const getImageDisplayBounds = () => {
|
||||
const windowWidth = window.innerWidth
|
||||
const windowHeight = window.innerHeight
|
||||
const windowWidth = windowSize.value.width
|
||||
const windowHeight = windowSize.value.height
|
||||
|
||||
// 必应图片通常是16:9或类似宽高比
|
||||
// 使用UHD分辨率: 1920x1080 (16:9)
|
||||
// 必应图片通常是16:9
|
||||
const imageAspectRatio = 16 / 9
|
||||
const windowAspectRatio = windowWidth / windowHeight
|
||||
|
||||
@@ -259,22 +264,35 @@ const getImageDisplayBounds = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化浮窗位置(居中偏下,限制在图片显示区域内)
|
||||
// 初始化浮窗位置(限制在图片显示区域内,移动端默认展示在底部)
|
||||
const initPanelPosition = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const bounds = getImageDisplayBounds()
|
||||
const panelWidth = Math.min(bounds.width * 0.9, 448) // max-w-md = 448px
|
||||
|
||||
infoPanelPos.value = {
|
||||
x: bounds.left + (bounds.width - panelWidth) / 2,
|
||||
y: Math.max(bounds.top, bounds.bottom - 280) // 距底部280px,避免与控制栏重叠
|
||||
if (isMobile.value) {
|
||||
// 移动端:默认居中靠下,不严格限制在图片内(因为要求可以不限制)
|
||||
// 但为了好看,我们还是给它一个默认位置
|
||||
const panelWidth = windowSize.value.width * 0.9
|
||||
infoPanelPos.value = {
|
||||
x: (windowSize.value.width - panelWidth) / 2,
|
||||
y: windowSize.value.height - 240 // 靠下
|
||||
}
|
||||
} else {
|
||||
// 桌面端:限制在图片区域内
|
||||
const panelWidth = Math.min(bounds.width * 0.9, 448) // max-w-md = 448px
|
||||
infoPanelPos.value = {
|
||||
x: bounds.left + (bounds.width - panelWidth) / 2,
|
||||
y: Math.max(bounds.top, bounds.bottom - 280) // 距底部280px,避免与控制栏重叠
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始拖动
|
||||
const startDrag = (e: MouseEvent | TouchEvent) => {
|
||||
e.preventDefault()
|
||||
if (e instanceof MouseEvent) {
|
||||
e.preventDefault()
|
||||
}
|
||||
isDragging.value = true
|
||||
|
||||
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
|
||||
@@ -287,7 +305,7 @@ const startDrag = (e: MouseEvent | TouchEvent) => {
|
||||
|
||||
document.addEventListener('mousemove', onDrag, { passive: false })
|
||||
document.addEventListener('mouseup', stopDrag)
|
||||
document.addEventListener('touchmove', onDrag, { passive: false })
|
||||
document.addEventListener('touchmove', onDrag, { passive: true })
|
||||
document.addEventListener('touchend', stopDrag)
|
||||
}
|
||||
|
||||
@@ -295,7 +313,9 @@ const startDrag = (e: MouseEvent | TouchEvent) => {
|
||||
const onDrag = (e: MouseEvent | TouchEvent) => {
|
||||
if (!isDragging.value) return
|
||||
|
||||
e.preventDefault()
|
||||
if (e instanceof MouseEvent) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// 取消之前的动画帧
|
||||
if (animationFrameId !== null) {
|
||||
@@ -310,15 +330,26 @@ const onDrag = (e: MouseEvent | TouchEvent) => {
|
||||
const newX = clientX - dragStart.value.x
|
||||
const newY = clientY - dragStart.value.y
|
||||
|
||||
// 限制在图片实际显示区域内,考虑底部控制栏高度(约80px)
|
||||
// 限制在有效区域内
|
||||
if (infoPanel.value) {
|
||||
const rect = infoPanel.value.getBoundingClientRect()
|
||||
const bounds = getImageDisplayBounds()
|
||||
|
||||
const minX = bounds.left
|
||||
const maxX = bounds.right - rect.width
|
||||
const minY = bounds.top
|
||||
const maxY = bounds.bottom - rect.height - 80 // 预留底部控制栏空间
|
||||
let minX, maxX, minY, maxY
|
||||
|
||||
if (isMobile.value) {
|
||||
// 移动端:不限制区域,限制在视口内即可
|
||||
minX = 0
|
||||
maxX = windowSize.value.width - rect.width
|
||||
minY = 0
|
||||
maxY = windowSize.value.height - rect.height
|
||||
} else {
|
||||
// 桌面端:限制在图片实际显示区域内,考虑底部控制栏高度(约80px)
|
||||
const bounds = getImageDisplayBounds()
|
||||
minX = bounds.left
|
||||
maxX = bounds.right - rect.width
|
||||
minY = bounds.top
|
||||
maxY = bounds.bottom - rect.height - 80 // 预留底部控制栏空间
|
||||
}
|
||||
|
||||
infoPanelPos.value = {
|
||||
x: Math.max(minX, Math.min(newX, maxX)),
|
||||
@@ -347,12 +378,12 @@ const stopDrag = () => {
|
||||
}
|
||||
|
||||
// 使用 composable 获取图片数据(传递 ref,自动响应日期变化)
|
||||
const { image, loading, error } = useImageByDate(currentDate)
|
||||
const { image, loading, error } = useImageByDate(currentDate, currentMkt)
|
||||
|
||||
// 检测指定日期是否有数据
|
||||
const checkDateAvailability = async (dateStr: string): Promise<boolean> => {
|
||||
try {
|
||||
await bingPaperApi.getImageMetaByDate(dateStr)
|
||||
await bingPaperApi.getImageMetaByDate(dateStr, currentMkt.value)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
@@ -384,9 +415,6 @@ const checkAdjacentDates = async () => {
|
||||
checkingDates.value = false
|
||||
}
|
||||
|
||||
// 初始化位置
|
||||
initPanelPosition()
|
||||
|
||||
// 监听showCalendar变化并自动保存到localStorage
|
||||
watch(showCalendar, (newValue) => {
|
||||
try {
|
||||
@@ -401,6 +429,20 @@ watch(currentDate, () => {
|
||||
checkAdjacentDates()
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听路由变化,支持前进后退
|
||||
watch(() => route.params.date, (newDate) => {
|
||||
if (newDate && newDate !== currentDate.value) {
|
||||
currentDate.value = newDate as string
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => route.query.mkt, (newMkt) => {
|
||||
const mkt = (newMkt as string) || getDefaultMkt()
|
||||
if (mkt !== currentMkt.value) {
|
||||
currentMkt.value = mkt
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr?: string) => {
|
||||
if (!dateStr) return ''
|
||||
@@ -415,7 +457,7 @@ const formatDate = (dateStr?: string) => {
|
||||
|
||||
// 获取完整图片 URL
|
||||
const getFullImageUrl = () => {
|
||||
return bingPaperApi.getImageUrlByDate(currentDate.value, 'UHD', 'jpg')
|
||||
return bingPaperApi.getImageUrlByDate(currentDate.value, 'UHD', 'jpg', currentMkt.value)
|
||||
}
|
||||
|
||||
// 预加载图片
|
||||
@@ -432,10 +474,10 @@ const preloadImage = (url: string): Promise<void> => {
|
||||
const preloadImageAndData = async (date: string): Promise<void> => {
|
||||
try {
|
||||
// 并行预加载图片和数据
|
||||
const imageUrl = bingPaperApi.getImageUrlByDate(date, 'UHD', 'jpg')
|
||||
const imageUrl = bingPaperApi.getImageUrlByDate(date, 'UHD', 'jpg', currentMkt.value)
|
||||
await Promise.all([
|
||||
preloadImage(imageUrl),
|
||||
bingPaperApi.getImageMetaByDate(date)
|
||||
bingPaperApi.getImageMetaByDate(date, currentMkt.value)
|
||||
])
|
||||
} catch (error) {
|
||||
console.warn('Failed to preload image or data:', error)
|
||||
@@ -461,7 +503,7 @@ const switchToDate = async (newDate: string) => {
|
||||
|
||||
// 3. 更新日期(此时图片和数据已经预加载完成)
|
||||
currentDate.value = newDate
|
||||
router.replace(`/image/${newDate}`)
|
||||
router.replace(`/image/${newDate}?mkt=${currentMkt.value}`)
|
||||
|
||||
// 4. 等待一个微任务,确保 DOM 更新
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
@@ -534,18 +576,29 @@ const handleKeydown = (e: KeyboardEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加键盘事件监听
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
window.addEventListener('resize', initPanelPosition)
|
||||
// 窗口缩放处理
|
||||
const handleResize = () => {
|
||||
windowSize.value = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
}
|
||||
initPanelPosition()
|
||||
}
|
||||
|
||||
// 清理
|
||||
import { onUnmounted } from 'vue'
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
window.addEventListener('resize', handleResize)
|
||||
}
|
||||
// 初始化浮窗位置
|
||||
initPanelPosition()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('keydown', handleKeydown)
|
||||
window.removeEventListener('resize', initPanelPosition)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
document.removeEventListener('mousemove', onDrag)
|
||||
document.removeEventListener('mouseup', stopDrag)
|
||||
document.removeEventListener('touchmove', onDrag)
|
||||
|
||||
Reference in New Issue
Block a user