feat: new tidi stuff

This commit is contained in:
Dustella 2025-01-24 12:36:02 +08:00
parent a0569a41d0
commit c2151b929a
20 changed files with 344 additions and 40 deletions

7
components.d.ts vendored
View File

@ -56,6 +56,13 @@ declare module 'vue' {
CollapsibleTrigger: typeof import('./src/components/ui/collapsible/CollapsibleTrigger.vue')['default']
CoolBack: typeof import('./src/components/CoolBack.vue')['default']
DenseFramework: typeof import('./src/components/DenseFramework.vue')['default']
Drawer: typeof import('./src/components/ui/drawer/Drawer.vue')['default']
DrawerContent: typeof import('./src/components/ui/drawer/DrawerContent.vue')['default']
DrawerDescription: typeof import('./src/components/ui/drawer/DrawerDescription.vue')['default']
DrawerFooter: typeof import('./src/components/ui/drawer/DrawerFooter.vue')['default']
DrawerHeader: typeof import('./src/components/ui/drawer/DrawerHeader.vue')['default']
DrawerOverlay: typeof import('./src/components/ui/drawer/DrawerOverlay.vue')['default']
DrawerTitle: typeof import('./src/components/ui/drawer/DrawerTitle.vue')['default']
DropdownMenu: typeof import('./src/components/ui/dropdown-menu/DropdownMenu.vue')['default']
DropdownMenuCheckboxItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue')['default']
DropdownMenuContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuContent.vue')['default']

View File

@ -25,6 +25,7 @@
"shadcn-vue": "^0.11.3",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"vaul-vue": "^0.2.0",
"vee-validate": "^4.15.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0",

17
pnpm-lock.yaml generated
View File

@ -48,6 +48,9 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7
vaul-vue:
specifier: ^0.2.0
version: 0.2.0(radix-vue@1.9.11(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))
vee-validate:
specifier: ^4.15.0
version: 4.15.0(vue@3.5.13(typescript@5.6.3))
@ -3899,6 +3902,12 @@ packages:
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
vaul-vue@0.2.0:
resolution: {integrity: sha512-YV0zqxc8NiVzr1z/Awwbaty0UDDchxj5BfhFbLiYu+Uz0rCfSaDK2zwmuXZvejBJKLGbWw9I5GLHJRse14lQew==}
peerDependencies:
radix-vue: ^1.4.0
vue: ^3.3.0
vee-validate@4.15.0:
resolution: {integrity: sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==}
peerDependencies:
@ -8610,6 +8619,14 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
vaul-vue@0.2.0(radix-vue@1.9.11(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3)):
dependencies:
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.6.3))
radix-vue: 1.9.11(vue@3.5.13(typescript@5.6.3))
vue: 3.5.13(typescript@5.6.3)
transitivePeerDependencies:
- '@vue/composition-api'
vee-validate@4.15.0(vue@3.5.13(typescript@5.6.3)):
dependencies:
'@vue/devtools-api': 7.7.0

View File

@ -122,9 +122,13 @@ const data = {
isActive: true,
items: [
{
title: '行星波月统计',
title: '行星波振幅',
url: '/tidi/waves',
},
{
title: '行星波月统计',
url: '/tidi/month_stats',
},
],
},
{

View File

@ -3,33 +3,32 @@ defineEmits(['submit', 'download'])
</script>
<template>
<Drawer>
<!-- <Drawer>
<DrawerTrigger as-child class="min-w-[20rem]">
<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 />
</DrawerHeader>
<form class="grid min-w-[20rem] w-full items-start gap-6 overflow-auto p-4 pt-0">
<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">
<slot />
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('submit')">
绘制
</Button>
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('download')">
下载
</Button>
</div>
</fieldset>
</form>
</DrawerContent>
</Drawer>
</DrawerHeader> -->
<form class="grid min-w-[20rem] w-full items-start gap-6 overflow-auto p-4 pt-0">
<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">
<slot />
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('submit')">
绘制
</Button>
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('download')">
下载
</Button>
</div>
</fieldset>
</form>
<!-- </DrawerContent>
</Drawer> -->
</template>

View File

@ -0,0 +1,19 @@
<script lang="ts" setup>
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
import { useForwardPropsEmits } from 'radix-vue'
import { DrawerRoot } from 'vaul-vue'
const props = withDefaults(defineProps<DrawerRootProps>(), {
shouldScaleBackground: true,
})
const emits = defineEmits<DrawerRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DrawerRoot v-bind="forwarded">
<slot />
</DrawerRoot>
</template>

View File

@ -0,0 +1,28 @@
<script lang="ts" setup>
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
import type { HtmlHTMLAttributes } from 'vue'
import { useForwardPropsEmits } from 'radix-vue'
import { DrawerContent, DrawerPortal } from 'vaul-vue'
import { cn } from '~/lib/utils'
import DrawerOverlay from './DrawerOverlay.vue'
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
const emits = defineEmits<DialogContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent
v-bind="forwarded" :class="cn(
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
props.class,
)"
>
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
<slot />
</DrawerContent>
</DrawerPortal>
</template>

View File

@ -0,0 +1,20 @@
<script lang="ts" setup>
import type { DrawerDescriptionProps } from 'vaul-vue'
import { DrawerDescription } from 'vaul-vue'
import { computed, type HtmlHTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
<slot />
</DrawerDescription>
</template>

View File

@ -0,0 +1,14 @@
<script lang="ts" setup>
import type { HtmlHTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<{
class?: HtmlHTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,14 @@
<script lang="ts" setup>
import type { HtmlHTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<{
class?: HtmlHTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,18 @@
<script lang="ts" setup>
import type { DialogOverlayProps } from 'radix-vue'
import { DrawerOverlay } from 'vaul-vue'
import { computed, type HtmlHTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<DrawerOverlay v-bind="delegatedProps" :class="cn('fixed inset-0 z-50 bg-black/80', props.class)" />
</template>

View File

@ -0,0 +1,20 @@
<script lang="ts" setup>
import type { DrawerTitleProps } from 'vaul-vue'
import { DrawerTitle } from 'vaul-vue'
import { computed, type HtmlHTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<DrawerTitle v-bind="delegatedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
<slot />
</DrawerTitle>
</template>

View File

@ -0,0 +1,8 @@
export { default as Drawer } from './Drawer.vue'
export { default as DrawerContent } from './DrawerContent.vue'
export { default as DrawerDescription } from './DrawerDescription.vue'
export { default as DrawerFooter } from './DrawerFooter.vue'
export { default as DrawerHeader } from './DrawerHeader.vue'
export { default as DrawerOverlay } from './DrawerOverlay.vue'
export { default as DrawerTitle } from './DrawerTitle.vue'
export { DrawerClose, DrawerPortal, DrawerTrigger } from 'vaul-vue'

View File

@ -21,12 +21,14 @@ const allPaths = ref([] as string[])
const selected = reactive({
selectedMode: '观测的二阶多项式拟合',
selectedPath: '',
station: 'LIN',
})
onMounted(async () => {
await baseFetch<string []>(`${API_BASE_URL}/balloon/metadata`).json().then(({ data }) => {
const das = data.value!
allPaths.value = das
selected.selectedPath = das[0]
})
})
@ -89,6 +91,20 @@ async function customHandle(resp: Response) {
</TabsTrigger>
</TabsList>
</Tabs>
<Label>选择台站</Label>
<Select v-model="selected.station">
<SelectTrigger>
<SelectValue placeholder="选择台站" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>台站</SelectLabel>
<SelectItem value="LIN">
LIN
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label>选择日期</Label>
<Select v-model="selected.selectedPath">
<SelectTrigger>

View File

@ -40,7 +40,7 @@ const urll = computed(() => {
onMounted(async () => {
await refreshPath()
selected.path = saberPaths.value[0]
selected.path = saberPaths.value[1]
})
watch(() => selected.path, async () => {
@ -49,7 +49,7 @@ watch(() => selected.path, async () => {
}
await refreshCurrentSaberDays(selected.path)
if (selected.day === '' && currentSaberDays.value.length > 0) {
selected.day = currentSaberDays.value[0]
selected.day = currentSaberDays.value[1]
}
})
</script>
@ -94,7 +94,7 @@ watch(() => selected.path, async () => {
<SelectContent>
<SelectGroup>
<SelectLabel>Day</SelectLabel>
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
<SelectItem v-for="day in currentSaberDays.slice(1)" :key="day" :value="day">
{{ parseDayOfYear (day.toString()).toLocaleDateString("zh", {
year: 'numeric',
month: 'long',

View File

@ -43,11 +43,14 @@ const urll = computed(() => {
onMounted(async () => {
await refreshPath()
selected.path = saberPaths.value[0]
selected.path = saberPaths.value[1]
})
watch(() => selected.path, async () => {
await refreshCurrentSaberDays(selected.path)
if (selected.day === '' && currentSaberDays.value.length > 0) {
selected.day = currentSaberDays.value[1]
}
})
</script>
@ -91,7 +94,7 @@ watch(() => selected.path, async () => {
<SelectContent>
<SelectGroup>
<SelectLabel>Day</SelectLabel>
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
<SelectItem v-for="day in currentSaberDays.slice(1)" :key="day" :value="day">
{{ parseDayOfYear(day.toString()).toLocaleDateString("zh", {
year: 'numeric',
month: 'long',

View File

@ -25,7 +25,7 @@ const lat_ranges = [
const selected = reactive({
path: './saber/data\\2012\\SABER_Temp_O3_April2012_v2.0.nc',
day: '',
height_no: 1,
height_no: '1',
lat_ranges: '0,10',
})
@ -40,15 +40,32 @@ const urll = computed(() => {
onMounted(async () => {
await refreshPath()
selected.path = saberPaths.value[0]
selected.path = saberPaths.value[1]
})
watch(() => selected.path, async () => {
await refreshCurrentSaberDays(selected.path)
if (saberPaths.value.length > 0) {
selected.path = saberPaths.value[0]
selected.path = saberPaths.value[1]
}
})
function mapHeightValue(input: number) {
// Input range: 1 to 157
// Output range: 30 to 90
const inputMin = 1
const inputMax = 157
const outputMin = 30
const outputMax = 90
// Calculate slope
const m = (outputMax - outputMin) / (inputMax - inputMin)
// Calculate y-intercept
const b = outputMin - m * inputMin
// Apply linear mapping
return m * input + b
}
</script>
<template>
@ -91,7 +108,7 @@ watch(() => selected.path, async () => {
<SelectContent>
<SelectGroup>
<SelectLabel>Day</SelectLabel>
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
<SelectItem v-for="day in currentSaberDays.slice(1)" :key="day" :value="day">
{{ parseDayOfYear(day.toString()).toLocaleDateString("zh", {
year: 'numeric',
month: 'long',
@ -102,13 +119,19 @@ watch(() => selected.path, async () => {
</SelectContent>
</Select>
<Label for="age">高度</Label>
<NumberField id="age" :default-value="18" :min="0">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
<Select v-model="selected.height_no">
<SelectTrigger>
<SelectValue placeholder="选择高度" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>高度</SelectLabel>
<SelectItem v-for="height_no in 157" :key="height_no" :value="height_no.toString()">
{{ mapHeightValue(height_no).toFixed(2) }} km
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
</DenseFramework>

View File

@ -0,0 +1,92 @@
<route lang="json">
{
"meta":{
"title":"TIDI月统计",
"description":"TIDI月统计",
"group":"TIDI",
"item_name":"月统计"
}
}
</route>
<script setup lang="ts">
const selected = reactive({
year: '2015',
mode: 'v1',
lat_range: '0 ~ 20',
})
const allYears = ref([] as string[])
const lat_ranges = ['0 ~ 20']
const queryUrl = computed(() => {
const query = new URLSearchParams()
query.set('year', selected.year)
const mode = selected.mode
return `/tidi/render/month_stats_${mode}?${query}`
})
onMounted(async () => {
const resp = await baseFetch(`/tidi/metadata`).json()
allYears.value = Array.from(new Set(resp.data.value.path.map((a: string) => {
const year_pattern = /data\/(\d{4})\//
return a.match(year_pattern)?.[1]
})))
})
</script>
<template>
<DenseFramework :image-query="queryUrl">
<Label>选择模式</Label>
<Tabs v-model="selected.mode" default-value="v1">
<TabsList class="grid grid-cols-1 w-full">
<TabsTrigger value="v1">
重力波势能(取log)随高度变化热力图
</TabsTrigger>
<TabsTrigger value="v2">
重力波势能(取log)变化折线图
</TabsTrigger>
</TabsList>
</Tabs>
<Label>纬度带</Label>
<Select v-model="selected.lat_range">
<SelectTrigger>
<SelectValue placeholder="选择纬度带" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>纬度带</SelectLabel>
<SelectItem
v-for="lat_range in lat_ranges"
:key="lat_range"
:value="lat_range"
>
{{ lat_range }}
</SelectItem>
</SelectGroup>
</SelectContent>
<Label>年份</Label>
<Select v-model="selected.year">
<SelectTrigger>
<SelectValue placeholder="选择年份" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>年份</SelectLabel>
<SelectItem
v-for="y in allYears"
:key="y"
:value="y"
>
{{ y }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</select>
</DenseFramework>
</template>
<style scoped>
</style>

View File

@ -1,10 +1,10 @@
<route lang="json">
{
"meta":{
"title":"TIDI 行星波月统计",
"title":"TIDI 行星波振幅",
"icon":"mdi:telescope",
"group":"TIDI",
"item_name":"行星波月统计"
"item_name":"行星波振幅"
}
}

1
typed-router.d.ts vendored
View File

@ -30,6 +30,7 @@ declare module 'vue-router/auto-routes' {
'/saber/day_fft_ifft_plot': RouteRecordInfo<'/saber/day_fft_ifft_plot', '/saber/day_fft_ifft_plot', Record<never, never>, Record<never, never>>,
'/saber/month_power_wave_plot': RouteRecordInfo<'/saber/month_power_wave_plot', '/saber/month_power_wave_plot', Record<never, never>, Record<never, never>>,
'/saber/plot_wave_fitting': RouteRecordInfo<'/saber/plot_wave_fitting', '/saber/plot_wave_fitting', Record<never, never>, Record<never, never>>,
'/tidi/month_stats': RouteRecordInfo<'/tidi/month_stats', '/tidi/month_stats', Record<never, never>, Record<never, never>>,
'/tidi/waves': RouteRecordInfo<'/tidi/waves', '/tidi/waves', Record<never, never>, Record<never, never>>,
}
}