feat: can navigate to TC

This commit is contained in:
Dustella 2025-02-08 21:25:28 +08:00
parent 52d1caa7de
commit e8bbb7e7c6
Signed by: Dustella
GPG Key ID: 35AA0AA3DC402D5C
19 changed files with 543 additions and 207 deletions

View File

@ -44,6 +44,7 @@
"devDependencies": {
"@antfu/eslint-config": "^3.13.0",
"@iconify-json/carbon": "^1.2.5",
"@iconify/json": "^2.2.304",
"@intlify/unplugin-vue-i18n": "^6.0.3",
"@shikijs/markdown-it": "^1.26.1",
"@types/markdown-it-link-attributes": "^3.0.5",

11
pnpm-lock.yaml generated
View File

@ -89,6 +89,9 @@ importers:
'@iconify-json/carbon':
specifier: ^1.2.5
version: 1.2.5
'@iconify/json':
specifier: ^2.2.304
version: 2.2.304
'@intlify/unplugin-vue-i18n':
specifier: ^6.0.3
version: 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.18.0(jiti@2.4.0))(rollup@4.30.1)(typescript@5.7.3)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
@ -1413,6 +1416,9 @@ packages:
'@iconify-json/carbon@1.2.5':
resolution: {integrity: sha512-aI3TEzOrUDGhs74zIT3ym/ZQBUEziyu8JifntX2Hb4siVzsP5sQ/QEfVdmcCUj37kQUYT3TYBSeAw2vTfCJx9w==}
'@iconify/json@2.2.304':
resolution: {integrity: sha512-8eO5m27lIfYLRFCxNgZQD/AMcDYw9NzPT6ViSYJDvX+Vevuj/smaQ/SfHVTNFzj5JcISDh5AV6n2nllQthceog==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@ -7606,6 +7612,11 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify/json@2.2.304':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
'@iconify/types@2.0.0': {}
'@iconify/utils@2.2.1':

12
src/components.d.ts vendored
View File

@ -34,11 +34,14 @@ declare module 'vue' {
DrawerHeader: typeof import('./components/ui/drawer/DrawerHeader.vue')['default']
DrawerOverlay: typeof import('./components/ui/drawer/DrawerOverlay.vue')['default']
DrawerTitle: typeof import('./components/ui/drawer/DrawerTitle.vue')['default']
InfoPan: typeof import('./components/InfoPan.vue')['default']
Input: typeof import('./components/ui/input/Input.vue')['default']
Label: typeof import('./components/ui/label/Label.vue')['default']
OptForm: typeof import('./components/OptForm.vue')['default']
Popover: typeof import('./components/ui/popover/Popover.vue')['default']
PopoverContent: typeof import('./components/ui/popover/PopoverContent.vue')['default']
PopoverTrigger: typeof import('./components/ui/popover/PopoverTrigger.vue')['default']
PosForm: typeof import('./components/PosForm.vue')['default']
README: typeof import('./components/README.md')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
@ -53,6 +56,15 @@ declare module 'vue' {
SelectSeparator: typeof import('./components/ui/select/SelectSeparator.vue')['default']
SelectTrigger: typeof import('./components/ui/select/SelectTrigger.vue')['default']
SelectValue: typeof import('./components/ui/select/SelectValue.vue')['default']
Table: typeof import('./components/ui/table/Table.vue')['default']
TableBody: typeof import('./components/ui/table/TableBody.vue')['default']
TableCaption: typeof import('./components/ui/table/TableCaption.vue')['default']
TableCell: typeof import('./components/ui/table/TableCell.vue')['default']
TableEmpty: typeof import('./components/ui/table/TableEmpty.vue')['default']
TableFooter: typeof import('./components/ui/table/TableFooter.vue')['default']
TableHead: typeof import('./components/ui/table/TableHead.vue')['default']
TableHeader: typeof import('./components/ui/table/TableHeader.vue')['default']
TableRow: typeof import('./components/ui/table/TableRow.vue')['default']
Textarea: typeof import('./components/ui/textarea/Textarea.vue')['default']
TheCounter: typeof import('./components/TheCounter.vue')['default']
TheFooter: typeof import('./components/TheFooter.vue')['default']

View File

172
src/components/OptForm.vue Normal file
View File

@ -0,0 +1,172 @@
<script setup lang="ts">
import { useOptions } from '@/composables/dateOpt'
import { cn } from '@/lib/utils'
import {
DateFormatter,
type DateValue,
getLocalTimeZone,
isSameDay,
parseDate,
} from '@internationalized/date'
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const {
options,
castBeginDate,
castTargetDate,
} = useOptions()
const mapping = ref < Record<string, string>>({})
onMounted(async () => {
const resp = await baseFetch('/tc/metadata').json()
const data = resp.data.value.data
mapping.value = data
options.mapping = data
})
const isDateDisabled = computed(() => {
const availableDates = Object.keys(mapping.value)
.map((date: string) => {
// date is like 20020202, we need to first trans to 2002-02-02
try {
const newDateStr = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`
const newDate = parseDate(newDateStr)
return newDate
}
catch {
return null
}
})
.filter(date => date !== null) as DateValue[]
function _isDateDisabled(date: DateValue): boolean {
const hasMatch = availableDates.some((thisDate) => {
return isSameDay(date, thisDate)
})
return !hasMatch
}
return _isDateDisabled
})
const isTargetDateDisabled = computed(() => {
const castBegin = castBeginDate.value
function _functionIsTargetDateDisabled(date: DateValue): boolean {
if (!castBegin) {
return true
}
const castEnd = castBegin.add({ days: 29 })
return date.compare(castBegin) < 0 || date.compare(castEnd) > 0
}
return _functionIsTargetDateDisabled
})
</script>
<template>
<form class="grid w-full items-start gap-6">
<fieldset class="grid gap-6 border rounded-lg p-4">
<legend class="px-1 text-sm font-medium -ml-1">
日期设置
</legend>
<div class="grid gap-3">
<Label for="model">渲染模式</Label>
<Select v-model="options.renderMode">
<SelectTrigger
id="model"
class="items-start [&_[data-description]]:hidden"
>
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent class="z-400">
<SelectItem value="heatmap">
<div class="flex items-start gap-3 text-muted-foreground">
<span class="i-tabler-sun size-5" />
<div class="grid gap-0.5">
<p>
热图
<span class="text-foreground font-medium">
使用点状热力图渲染
</span>
</p>
<p class="text-xs" data-description>
放大缩小可能不准确
</p>
</div>
</div>
</SelectItem>
<SelectItem value="image">
<div class="flex items-start gap-3 text-muted-foreground">
<span class="i-material-symbols-image size-5" />
<div class="grid gap-0.5">
<p>
图片
<span class="text-foreground font-medium">
使用图片叠加层渲染
</span>
</p>
<p class="text-xs" data-description>
相对准确
</p>
</div>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<Label for="temperature">发布日期</Label>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(
'w-full justify-start text-left font-normal',
!castBeginDate && 'text-muted-foreground',
)"
>
<span class="i-uil-calender mr-2 h-4 w-4" />
{{ castBeginDate ? df.format(castBeginDate.toDate(getLocalTimeZone())) : "Pick a date" }}
</Button>
</PopoverTrigger>
<PopoverContent class="z-300 w-auto p-0">
<Calendar v-model="castBeginDate" class="z-300" initial-focus :is-date-disabled="isDateDisabled" />
</PopoverContent>
</Popover>
</div>
<div>
<Label for="top-p">目标预报日期</Label>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(
'w-full justify-start text-left font-normal',
!castTargetDate && 'text-muted-foreground',
)"
>
<span class="i-uil-calender mr-2 h-4 w-4" />
{{ castTargetDate ? df.format(castTargetDate.toDate(getLocalTimeZone())) : "Pick a date" }}
</Button>
</PopoverTrigger>
<PopoverContent class="z-300 w-auto p-0">
<Calendar
v-model="castTargetDate"
class="z-300" initial-focus
:is-date-disabled="isTargetDateDisabled"
/>
</PopoverContent>
</Popover>
</div>
</div>
<div class="grid gap-3" />
</fieldset>
</form>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
const {
currentTcLoc,
currentFocus,
} = useOptions()
function jump(postion: [number, number]) {
const getter = () => postion
currentFocus.value = getter
}
</script>
<template>
<fieldset class="grid gap-6 border rounded-lg p-4">
<legend class="px-1 text-sm font-medium -ml-1">
预报台风
</legend>
<Table v-if="currentTcLoc">
<TableCaption>预报台风</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Lat </TableHead>
<TableHead>Lon</TableHead>
<TableHead class="text-right">
操作
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="postion in currentTcLoc" :key="postion[0]">
<TableCell>{{ postion[0].toFixed(2) }}</TableCell>
<TableCell>{{ postion[1].toFixed(2) }}</TableCell>
<TableCell class="text-right">
<Button
class="h-6 text-[13.2px]" variant="outline"
@click.prevent="jump(postion)"
>
跳转
</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</fieldset>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div class="relative w-full overflow-auto">
<table :class="cn('w-full caption-bottom text-sm', props.class)">
<slot />
</table>
</div>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<tbody :class="cn('[&_tr:last-child]:border-0', props.class)">
<slot />
</tbody>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<caption :class="cn('mt-4 text-sm text-muted-foreground', props.class)">
<slot />
</caption>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<td
:class="
cn(
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
props.class,
)
"
>
<slot />
</td>
</template>

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { computed, type HTMLAttributes } from 'vue'
import TableCell from './TableCell.vue'
import TableRow from './TableRow.vue'
const props = withDefaults(defineProps<{
class?: HTMLAttributes['class']
colspan?: number
}>(), {
colspan: 1,
})
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<TableRow>
<TableCell
:class="
cn(
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
props.class,
)
"
v-bind="delegatedProps"
>
<div class="flex items-center justify-center py-10">
<slot />
</div>
</TableCell>
</TableRow>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<tfoot :class="cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', props.class)">
<slot />
</tfoot>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<th :class="cn('h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', props.class)">
<slot />
</th>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<thead :class="cn('[&_tr]:border-b', props.class)">
<slot />
</thead>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<tr :class="cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', props.class)">
<slot />
</tr>
</template>

View File

@ -0,0 +1,9 @@
export { default as Table } from './Table.vue'
export { default as TableBody } from './TableBody.vue'
export { default as TableCaption } from './TableCaption.vue'
export { default as TableCell } from './TableCell.vue'
export { default as TableEmpty } from './TableEmpty.vue'
export { default as TableFooter } from './TableFooter.vue'
export { default as TableHead } from './TableHead.vue'
export { default as TableHeader } from './TableHeader.vue'
export { default as TableRow } from './TableRow.vue'

View File

@ -7,7 +7,25 @@ function _useOptions() {
renderMode: 'heatmap' as 'heatmap' | 'image',
isPlaying: false,
mapping: {} as Record<string, string>,
})
const castBeginDate = ref<DateValue>()
const castTargetDate = ref<DateValue>()
const currentTcLoc = ref<null | [number, number][]>(null)
type PosGetter = () => [number, number]
const currentFocus = ref<null | PosGetter>(null)
function getISOcastBeginDate() {
const castBeginDateStr = options.castBeginDate.toString()
if (castBeginDateStr.length !== 8) {
console.error(castBeginDateStr)
throw new Error('castBeginDate is invalid')
}
const newDateStr = `${castBeginDateStr.slice(0, 4)}-${castBeginDateStr.slice(4, 6)}-${castBeginDateStr.slice(6, 8)}`
const castBeginDate = parseDate(newDateStr)
return castBeginDate
}
function setCastBeginDate(date: DateValue) {
const datestr = date.toString()
@ -27,10 +45,40 @@ function _useOptions() {
return dayNo
}
async function refreshCurrentTcLoc() {
currentTcLoc.value = null
const q = new URLSearchParams()
q.set('day', options.castDayNo.toString())
const path = options.mapping[options.castBeginDate]
q.set('path', path)
const base = import.meta.env.VITE_BACKEND_URL
const url = `${base}/tc/metadata/centroids?${q}`
const resp = await baseFetch(url).json()
const data = resp.data.value!
currentTcLoc.value = data as [number, number][]
}
watch([castBeginDate, castTargetDate], () => {
if (!castBeginDate.value || !castTargetDate.value) {
return
}
setCastBeginDate(castBeginDate.value)
setTargetDate(castTargetDate.value)
refreshCurrentTcLoc()
})
return {
options,
setCastBeginDate,
setTargetDate,
getISOcastBeginDate,
currentFocus,
castBeginDate,
castTargetDate,
currentTcLoc,
}
}

View File

@ -1,219 +1,24 @@
<script setup lang="ts">
import { useOptions } from '@/composables/dateOpt'
import { cn } from '@/lib/utils'
import {
DateFormatter,
type DateValue,
getLocalTimeZone,
isSameDay,
parseDate,
} from '@internationalized/date'
import { DrawerTrigger } from 'vaul-vue'
const [DefineFormTemplate, ReuseFormTemplate] = createReusableTemplate()
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const castBeginDate = ref<DateValue>()
const castTargetDate = ref<DateValue>()
const { options, setCastBeginDate, setTargetDate } = useOptions()
const mapping = ref < Record<string, string>>({})
onMounted(async () => {
const resp = await baseFetch('/tc/metadata').json()
const data = resp.data.value.data
mapping.value = data
options.mapping = data
})
const isDateDisabled = computed(() => {
const availableDates = Object.keys(mapping.value)
.map((date: string) => {
// date is like 20020202, we need to first trans to 2002-02-02
try {
const newDateStr = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`
const newDate = parseDate(newDateStr)
return newDate
}
catch {
return null
}
})
.filter(date => date !== null) as DateValue[]
function _isDateDisabled(date: DateValue): boolean {
const hasMatch = availableDates.some((thisDate) => {
return isSameDay(date, thisDate)
})
return !hasMatch
}
return _isDateDisabled
})
const isTargetDateDisabled = computed(() => {
const castBegin = castBeginDate.value
function _functionIsTargetDateDisabled(date: DateValue): boolean {
if (!castBegin) {
return true
}
const castEnd = castBegin.add({ days: 29 })
return date.compare(castBegin) < 0 || date.compare(castEnd) > 0
}
return _functionIsTargetDateDisabled
})
watch([castBeginDate, castTargetDate], () => {
if (castBeginDate.value && castTargetDate.value) {
setCastBeginDate(castBeginDate.value)
setTargetDate(castTargetDate.value)
}
})
import { DrawerClose, DrawerTrigger } from 'vaul-vue'
</script>
<template>
<div class="grid h-screen w-full">
<DefineFormTemplate>
<form class="grid w-full items-start gap-6">
<fieldset class="grid gap-6 border rounded-lg p-4">
<legend class="px-1 text-sm font-medium -ml-1">
日期设置
</legend>
{{ options }}
<div class="grid gap-3">
<Label for="model">渲染模式</Label>
<Select v-model="options.renderMode">
<SelectTrigger
id="model"
class="items-start [&_[data-description]]:hidden"
>
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="heatmap">
<div class="flex items-start gap-3 text-muted-foreground">
<Rabbit class="size-5" />
<div class="grid gap-0.5">
<p>
热图
<span class="text-foreground font-medium">
使用点状热力图渲染
</span>
</p>
<p class="text-xs" data-description>
放大缩小可能不准确
</p>
</div>
</div>
</SelectItem>
<SelectItem value="image">
<div class="flex items-start gap-3 text-muted-foreground">
<Bird class="size-5" />
<div class="grid gap-0.5">
<p>
图片
<span class="text-foreground font-medium">
使用图片叠加层渲染
</span>
</p>
<p class="text-xs" data-description>
相对准确
</p>
</div>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<Label for="temperature">发布日期</Label>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(
'w-full justify-start text-left font-normal',
!castBeginDate && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
{{ castBeginDate ? df.format(castBeginDate.toDate(getLocalTimeZone())) : "Pick a date" }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="castBeginDate" initial-focus :is-date-disabled="isDateDisabled" />
</PopoverContent>
</Popover>
</div>
<div>
<Label for="top-p">目标预报日期</Label>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(
'w-full justify-start text-left font-normal',
!castTargetDate && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
{{ castTargetDate ? df.format(castTargetDate.toDate(getLocalTimeZone())) : "Pick a date" }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar
v-model="castTargetDate" initial-focus
:is-date-disabled="isTargetDateDisabled"
/>
</PopoverContent>
</Popover>
</div>
</div>
<div class="grid gap-3" />
</fieldset>
<fieldset class="grid gap-6 border rounded-lg p-4">
<legend class="px-1 text-sm font-medium -ml-1">
渲染
</legend>
<Button>
开始自动播放
</Button>
</fieldset>
</form>
</DefineFormTemplate>
<div class="flex flex-col">
<header
class="sticky top-0 z-10 h-[53px] flex items-center gap-1 border-b bg-background px-4"
>
<span class="i-mingcute-typhoon-fill size-6 px-5" />
<h1 class="text-xl font-semibold">
次季节台风预报系统
</h1>
<Drawer>
<DrawerTrigger as-child>
<Button variant="ghost" size="icon" class="md:hidden">
<Settings class="size-4" />
<span class="sr-only">地图设置</span>
</Button>
</DrawerTrigger>
<DrawerContent class="max-h-[80vh]">
<DrawerHeader>
<DrawerTitle>地图设置</DrawerTitle>
<DrawerDescription>
选择日期和渲染方式
</DrawerDescription>
</DrawerHeader>
<ReuseFormTemplate />
</DrawerContent>
</Drawer>
</header>
<main
class="grid flex-1 gap-4 overflow-auto p-4 lg:grid-cols-3 md:grid-cols-2"
>
<div class="relative hidden flex-col items-start gap-8 md:flex">
<ReuseFormTemplate />
<div class="relative hidden flex-col items-stretch gap-8 md:flex">
<OptForm />
<PosForm />
</div>
<div
class="relative h-full min-h-[50vh] flex flex-col rounded-xl bg-muted/50 p-4 lg:col-span-2"
@ -223,18 +28,79 @@ watch([castBeginDate, castTargetDate], () => {
</Badge>
<div class="flex-1" />
<RouterView />
<form
class="relative overflow-hidden border rounded-lg bg-background focus-within:ring-1 focus-within:ring-ring"
<div
class="relative border rounded-lg bg-background"
>
<Label for="message" class="sr-only"> Message </Label>
<Card
id="message"
placeholder="Type your message here..."
class="min-h-22 resize-none border-0 p-3 shadow-none focus-visible:ring-0"
flex="~ col items-stretch gap-4"
class="border-0 p-3 shadow-none focus-visible:ring-0"
>
something here
<!-- <div class="pb-3">
<div v-if="options.castDayNo !== -1">
这一天是 {{ currentDate }}
<br>
{{ options.castBeginDate }} 发布预报的第 {{ options.castDayNo }}
</div>
<div v-if="currentTcLoc">
{{ currentTcLoc.length }} 个区域预报出台风
</div>
</div> -->
<Button w-full variant="outline">
开始自动播放
</Button>
<Drawer class="z-100">
<DrawerTrigger as-child>
<Button size="icon" variant="outline" class="w-full md:hidden">
<span class="i-mingcute-typhoon-fill size-4" />
<span class="">台风位置</span>
</Button>
</DrawerTrigger>
<DrawerContent class="z-200 max-h-[80vh] p-5">
<DrawerHeader>
<DrawerTitle>台风位置</DrawerTitle>
<DrawerDescription>
点击可以跳转
</DrawerDescription>
</DrawerHeader>
<PosForm />
<DrawerClose as-child>
<div class="py-3">
<Button class="w-full">
取消
</Button>
</div>
</DrawerClose>
</DrawerContent>
</Drawer>
<Drawer class="z-100">
<DrawerTrigger as-child>
<Button size="icon" class="w-full md:hidden">
<span class="i-material-symbols-settings size-4" />
<span class="">地图设置</span>
</Button>
</DrawerTrigger>
<DrawerContent class="z-200 max-h-[80vh] p-5">
<DrawerHeader>
<DrawerTitle>地图设置</DrawerTitle>
<DrawerDescription>
选择日期和渲染方式
</DrawerDescription>
</DrawerHeader>
<OptForm />
<DrawerClose as-child>
<div class="py-3">
<Button class="w-full">
应用
</Button>
</div>
</DrawerClose>
</DrawerContent>
</Drawer>
</Card>
</form>
</div>
</div>
</main>
</div>

View File

@ -14,7 +14,7 @@ const previousLayer = ref<null | AMap.ImageLayer>(null)
const apiKey = import.meta.env.VITE_AMAP_API_KEY
const { options } = useOptions()
const { options, currentFocus } = useOptions()
watch(options, async () => {
if (!map.value || !amapInstance.value) {
@ -26,6 +26,16 @@ watch(options, async () => {
updateMap()
})
watch(currentFocus, (newFocus) => {
if (!newFocus) {
return
}
const [lat, lon] = newFocus()
// debugger
map.value?.setCenter([lat, lon - 35])
map.value?.setZoom(4)
})
function updateMap() {
if (!map.value || !amapInstance.value) {
return