staging: v1

This commit is contained in:
Dustella 2025-01-08 14:13:16 +08:00
parent 5f2483c511
commit 383909fe89
34 changed files with 952 additions and 177 deletions

9
components.d.ts vendored
View File

@ -7,12 +7,17 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Button: typeof import('./src/components/ui/button/Button.vue')['default']
Image: typeof import('./src/components/Image.vue')['default']
Label: typeof import('./src/components/ui/label/Label.vue')['default']
Loading: typeof import('./src/components/Loading.vue')['default']
NavigationMenu: typeof import('./src/components/ui/navigation-menu/NavigationMenu.vue')['default']
NavigationMenuContent: typeof import('./src/components/ui/navigation-menu/NavigationMenuContent.vue')['default']
NavigationMenuIndicator: typeof import('./src/components/ui/navigation-menu/NavigationMenuIndicator.vue')['default']
NavigationMenuItem: typeof import('./src/components/ui/navigation-menu/NavigationMenuItem.vue')['default']
NavigationMenuLink: typeof import('./src/components/ui/navigation-menu/NavigationMenuLink.vue')['default']
NavigationMenuList: typeof import('./src/components/ui/navigation-menu/NavigationMenuList.vue')['default']
NavigationMenuListItem: typeof import('./src/components/NavigationMenuListItem.vue')['default']
NavigationMenuTrigger: typeof import('./src/components/ui/navigation-menu/NavigationMenuTrigger.vue')['default']
NavigationMenuViewport: typeof import('./src/components/ui/navigation-menu/NavigationMenuViewport.vue')['default']
NumberField: typeof import('./src/components/ui/number-field/NumberField.vue')['default']
@ -33,9 +38,9 @@ declare module 'vue' {
SelectSeparator: typeof import('./src/components/ui/select/SelectSeparator.vue')['default']
SelectTrigger: typeof import('./src/components/ui/select/SelectTrigger.vue')['default']
SelectValue: typeof import('./src/components/ui/select/SelectValue.vue')['default']
TheCounter: typeof import('./src/components/TheCounter.vue')['default']
Skeleton: typeof import('./src/components/ui/skeleton/Skeleton.vue')['default']
TestHeader: typeof import('./src/components/TestHeader.vue')['default']
TheFooter: typeof import('./src/components/TheFooter.vue')['default']
TheHeader: typeof import('./src/components/TheHeader.vue')['default']
TheInput: typeof import('./src/components/TheInput.vue')['default']
}
}

7
cspell.config.yaml Normal file
View File

@ -0,0 +1,7 @@
version: '0.2'
ignorePaths: []
dictionaryDefinitions: []
dictionaries: []
words: []
ignoreWords: []
import: []

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<title>Vitesse Lite</title>
<title>中高层大气波动解析识别技术系统</title>
<meta name="description" content="Opinionated Vite Starter Template" />
</head>
<body class="font-sans dark:text-white dark:bg-hex-121212">

BIN
public/pack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

View File

@ -1,6 +1,9 @@
<template>
<div class="w-full flex items-center justify-center">
<div class="max-w-[980px]">
<div class="relative w-full flex-col items-center justify-center">
<div p-4 text-center text-2xl font-bold>
中高层大气波动解析识别技术系统
</div>
<TheHeader />
</div>
</div>

View File

@ -1 +1 @@
export const API_BASE_URL = 'http://localhost:5000'
export const API_BASE_URL = 'https://gca-api.dustella.net:8443'

27
src/components/Image.vue Normal file
View File

@ -0,0 +1,27 @@
<script setup lang="ts">
const props = defineProps<{
imageUrl: string
}>()
function download() {
const a = document.createElement('a')
a.href = props.imageUrl
a.download = 'image.png'
a.click()
}
</script>
<template>
<div>
<img id="image_" :src="imageUrl">
<div my-2 w-full>
<Button class="w-2/3" @click="download">
下载
</Button>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div h-full w-full flex="~ col items-center justify-center">
<Skeleton class="h-50 w-3/5" />
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import { NavigationMenuLink } from 'radix-vue'
const props = defineProps({
title: String,
})
</script>
<template>
<li>
<NavigationMenuLink as-child>
<a
v-bind="$attrs"
class="hover:bg-mauve3 block select-none rounded-[6px] p-3 text-[15px] leading-none no-underline outline-none transition-colors focus:shadow-[0_0_0_2px] focus:shadow-green7"
>
<div class="text-green12 mb-[5px] font-medium leading-[1.2]">
{{ props.title }}
</div>
<p class="text-mauve11 my-0 leading-[1.4]">
<slot />
</p>
</a>
</NavigationMenuLink>
</li>
</template>

View File

@ -0,0 +1,150 @@
<script setup lang="ts">
import {
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuRoot,
NavigationMenuTrigger,
NavigationMenuViewport,
} from 'radix-vue'
import { ref } from 'vue'
import NavigationMenuListItem from './NavigationMenuListItem.vue'
const currentTrigger = ref('')
</script>
<template>
<NavigationMenuRoot
v-model="currentTrigger"
class="relative z-[1] mx-auto flex justify-center"
>
<NavigationMenuList class="center shadow-blackA7 m-0 flex list-none rounded-[6px] bg-white p-1 shadow-[0_2px_10px]">
<NavigationMenuItem>
<NavigationMenuTrigger
class="group text-grass11 flex select-none items-center justify-between gap-[2px] rounded-[4px] px-3 py-2 text-[15px] font-medium leading-none outline-none hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-green7"
>
Learn
<Icon
icon="radix-icons:caret-down"
class="text-green10 relative top-[1px] transition-transform duration-[250ms] ease-in group-data-[state=open]:-rotate-180"
/>
</NavigationMenuTrigger>
<NavigationMenuContent
class="data-[motion=from-start]:animate-enterFromLeft data-[motion=from-end]:animate-enterFromRight data-[motion=to-start]:animate-exitToLeft data-[motion=to-end]:animate-exitToRight absolute left-0 top-0 w-full sm:w-auto"
>
<ul class="one grid m-0 list-none gap-x-[10px] p-[22px] sm:grid-cols-[0.75fr_1fr] sm:w-[500px]">
<li class="grid row-span-3">
<NavigationMenuLink as-child>
<a
class="h-full w-full flex flex-col select-none justify-end rounded-[6px] from-green9 to-teal9 bg-gradient-to-b p-[25px] no-underline outline-none focus:shadow-[0_0_0_2px] focus:shadow-green7"
href="/"
>
<img
class="w-16"
src="https://www.radix-vue.com/logo.svg"
>
<div class="mb-[7px] mt-4 text-[18px] text-white font-medium leading-[1.2]">Radix Primitives</div>
<p class="text-mauve4 text-[14px] leading-[1.3]">Unstyled, accessible components for Vue.</p>
</a>
</NavigationMenuLink>
</li>
<NavigationMenuListItem
href="https://stitches.dev/"
title="Stitches"
>
CSS-in-JS with best-in-class developer experience.
</NavigationMenuListItem>
<NavigationMenuListItem
href="/colors"
title="Colors"
>
Beautiful, thought-out palettes with auto dark mode.
</NavigationMenuListItem>
<NavigationMenuListItem
href="https://icons.radix-ui.com/"
title="Icons"
>
A crisp set of 15x15 icons, balanced and consistent.
</NavigationMenuListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger
class="text-grass11 group flex select-none items-center justify-between gap-[2px] rounded-[4px] px-3 py-2 text-[15px] font-medium leading-none outline-none hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-green7"
>
Overview
<Icon
icon="radix-icons:caret-down"
class="text-green10 relative top-[1px] transition-transform duration-[250ms] ease-in group-data-[state=open]:-rotate-180"
/>
</NavigationMenuTrigger>
<NavigationMenuContent class="data-[motion=from-start]:animate-enterFromLeft data-[motion=from-end]:animate-enterFromRight data-[motion=to-start]:animate-exitToLeft data-[motion=to-end]:animate-exitToRight absolute left-0 top-0 w-full sm:w-auto">
<ul class="grid m-0 list-none gap-x-[10px] p-[22px] sm:grid-flow-col sm:grid-rows-3 sm:w-[600px]">
<NavigationMenuListItem
title="Introduction"
href="/docs/primitives/overview/introduction"
>
Build high-quality, accessible design systems and web apps.
</NavigationMenuListItem>
<NavigationMenuListItem
title="Getting started"
href="/docs/primitives/overview/getting-started"
>
A quick tutorial to get you up and running with Radix Primitives.
</NavigationMenuListItem>
<NavigationMenuListItem
title="Styling"
href="/docs/primitives/guides/styling"
>
Unstyled and compatible with any styling solution.
</NavigationMenuListItem>
<NavigationMenuListItem
title="Animation"
href="/docs/primitives/guides/animation"
>
Use CSS keyframes or any animation library of your choice.
</NavigationMenuListItem>
<NavigationMenuListItem
title="Accessibility"
href="/docs/primitives/overview/accessibility"
>
Tested in a range of browsers and assistive technologies.
</NavigationMenuListItem>
<NavigationMenuListItem
title="Releases"
href="/docs/primitives/overview/releases"
>
Radix Primitives releases and their changelogs.
</NavigationMenuListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
class="text-grass11 block select-none rounded-[4px] px-3 py-2 text-[15px] font-medium leading-none no-underline outline-none hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-green7"
href="https://github.com/unovue/radix-vue"
>
Github
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuIndicator
class="transition-[all,transform_250ms_ease] data-[state=visible]:animate-fadeIn data-[state=hidden]:animate-fadeOut top-full z-[1] h-[10px] flex items-end justify-center overflow-hidden duration-200 data-[state=hidden]:opacity-0"
>
<div class="relative top-[70%] h-[10px] w-[10px] rotate-[45deg] rounded-tl-[2px] bg-white" />
</NavigationMenuIndicator>
</NavigationMenuList>
<div class="absolute left-0 top-full w-full flex perspective-[2000px] justify-center">
<NavigationMenuViewport
class="transition-[width,_height] data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative mt-[10px] h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[10px] bg-white duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]"
/>
</div>
</NavigationMenuRoot>
</template>

View File

@ -9,131 +9,70 @@ import {
navigationMenuTriggerStyle,
} from './ui/navigation-menu'
/**
* comboType = [
"探空气球",
"流星雷达",
"Saber",
"TIDI",
"COSMIC",
]
comboMode = [
["重力波单次", "重力波统计"],
["重力波月统计", "潮汐波单次", "潮汐波月统计"],
["行星波月统计", "重力波单次", "重力波月统计"],
["行星波月统计"],
["行星波月统计"],
]
*/
const components: { title: string, href: string, description: string }[] = [
{
title: '探空气球',
href: '/ballon',
description:
'探空气球',
},
{
title: 'Hover Card',
href: '/docs/components/hover-card',
description:
'For sighted users to preview content available behind a link.',
},
{
title: 'Progress',
href: '/docs/components/progress',
description:
'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
},
{
title: 'Scroll-area',
href: '/docs/components/scroll-area',
description: 'Visually or semantically separates content.',
},
{
title: 'Tabs',
href: '/docs/components/tabs',
description:
'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
},
{
title: 'Tooltip',
href: '/docs/components/tooltip',
description:
'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
},
]
const header_data: Record<string, { title: string, href: string }[]> = {
探空气球: [{
title: '重力波单次',
href: '/balloon/single',
}, {
title: '重力波统计',
href: '/balloon/year',
}],
流星雷达: [{
title: '潮汐波强度',
href: '/radar/v1',
}, {
title: '潮汐波时空变化',
href: '/radar/v2',
}],
Saber: [
{
title: '波动拟合图',
href: '/saber/plot_wave_fitting',
},
{
title: '日数据傅里叶变换分析',
href: '/saber/day_fft_ifft_plot',
},
{
title: '日周期波动能量分析',
href: '/saber/day_cycle_power_wave_plot',
},
{
// month_power_wave_plot
title: ' 月度波动能量分析',
href: '/saber/month_power_wave_plot',
},
],
}
</script>
<template>
<NavigationMenu>
<NavigationMenu class="mx-auto">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink href="/" :class="navigationMenuTriggerStyle()">
主页
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>探空气球</NavigationMenuTrigger>
<NavigationMenuItem v-for="(thisHeader, headers) in header_data" :key="headers">
<NavigationMenuTrigger>{{ headers }}</NavigationMenuTrigger>
<NavigationMenuContent>
<ul class="grid gap-3 p-6 lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)] lg:w-[500px] md:w-[400px]">
<li class="row-span-3">
<ul class="w-60 flex flex-col gap-3 p-6">
<li v-for="header in thisHeader" :key="header.href" class="row-span-3">
<NavigationMenuLink as-child>
<a
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground"
href="/balloon/single"
:href="header.href"
>
<div class="text-sm font-medium leading-none">
探空气球 - 重力波单次
{{ header.title }}
</div>
<p class="line-clamp-2 text-sm text-muted-foreground leading-tight">
探空气球 - 重力波单次
</p>
</a>
</NavigationMenuLink>
</li>
<li>
<NavigationMenuLink as-child>
<a
href="/balloon/year"
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground"
>
<div class="text-sm font-medium leading-none">探空气球 - 重力波统计</div>
<p class="line-clamp-2 text-sm text-muted-foreground leading-tight">
探空气球 - 重力波统计
</p>
</a>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul class="grid w-[400px] gap-3 p-4 md:grid-cols-2 lg:w-[600px] md:w-[500px]">
<li v-for="component in components" :key="component.title">
<NavigationMenuLink as-child>
<a
:href="component.href"
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground"
>
<div class="text-sm font-medium leading-none">{{ component.title }}</div>
<p class="line-clamp-2 text-sm text-muted-foreground leading-snug">
{{ component.description }}
</p>
</a>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="/docs/introduction" :class="navigationMenuTriggerStyle()">
Documentation
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Primitive, type PrimitiveProps } from 'radix-vue'
import { cn } from '~/lib/utils'
import { type ButtonVariants, buttonVariants } from '.'
interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>
<template>
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,35 @@
import { cva, type VariantProps } from 'class-variance-authority'
export { default as Button } from './Button.vue'
export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
export type ButtonVariants = VariantProps<typeof buttonVariants>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { Label, type LabelProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<Label
v-bind="delegatedProps"
:class="
cn(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
props.class,
)
"
>
<slot />
</Label>
</template>

View File

@ -0,0 +1 @@
export { default as Label } from './Label.vue'

View File

@ -25,7 +25,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<template>
<NavigationMenuRoot
v-bind="forwarded"
:class="cn('relative z-10 flex max-w-max flex-1 items-center justify-center', props.class)"
:class="cn('relative z-10 flex justify-center', props.class)"
>
<slot />
<NavigationMenuViewport />

View File

@ -25,7 +25,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<NavigationMenuContent
v-bind="forwarded"
:class="cn(
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto',
'absolute w-full left-0 top-0 data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 ',
props.class,
)"
>

View File

@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps)
v-bind="forwardedProps"
:class="
cn(
'group flex flex-1 list-none items-center justify-center gap-x-1',
'center flex list-none gap-x-1',
props.class,
)
"

View File

@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<div class="absolute left-0 top-full flex justify-center">
<div class="absolute left-0 top-full w-full flex perspective-[2000px] justify-center">
<NavigationMenuViewport
v-bind="forwardedProps"
:class="

View File

@ -9,5 +9,5 @@ export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue'
export { default as NavigationMenuViewport } from './NavigationMenuViewport.vue'
export const navigationMenuTriggerStyle = cva(
'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
'group flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
)

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
interface SkeletonProps {
class?: HTMLAttributes['class']
}
const props = defineProps<SkeletonProps>()
</script>
<template>
<div :class="cn('animate-pulse rounded-md bg-muted', props.class)" />
</template>

View File

@ -0,0 +1 @@
export { default as Skeleton } from './Skeleton.vue'

View File

@ -1,4 +1,6 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
const selectedMode = ref('观测的二阶多项式拟合')
const imageUrl = ref('')
@ -16,13 +18,13 @@ const selectedDate = ref('2022-01-01')
const dates = ref([] as string[])
onMounted(async () => {
const resp = await fetch('http://localhost:5000/balloon/metadata')
const resp = await fetch(`${API_BASE_URL}/balloon/metadata`)
const data = await resp.json()
dates.value = data
})
async function get_image() {
const resp = await fetch(`http://localhost:5000/balloon/render/single?mode=${encodeURIComponent(selectedMode.value)}&path=${encodeURIComponent(selectedDate.value)}`)
const resp = await fetch(`${API_BASE_URL}/balloon/render/single?mode=${encodeURIComponent(selectedMode.value)}&path=${encodeURIComponent(selectedDate.value)}`)
// check for MIME Type, check if is png
const isPng = resp.headers.get('Content-Type') === 'image/png'
if (!isPng) {
@ -79,7 +81,7 @@ watch([selectedMode, selectedDate], () => {
</div>
<div>
<div v-if="hasWaveThisDay">
<img :src="imageUrl">
<Image :image-url="imageUrl" />
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
{{ selectedDate.match(/_(\d{8}T\d{6})/)?.[1] }} <br>

View File

@ -1,4 +1,6 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
const selectedMode = ref('w/f值统计结果')
const isIllegal = ref(false)
@ -27,7 +29,7 @@ const modes = [
]
async function refreshImage() {
const url = `http://localhost:5000/balloon/render/year?mode=${encodeURIComponent(selectedMode.value)}&start_year=${startYear.value}&end_year=${endYear.value}`
const url = `${API_BASE_URL}/balloon/render/year?mode=${encodeURIComponent(selectedMode.value)}&start_year=${startYear.value}&end_year=${endYear.value}`
const resp = await fetch(url)
const blob = await resp.blob()
const u = URL.createObjectURL(blob)
@ -95,7 +97,7 @@ watch([selectedMode, startYear, endYear], () => {
</div>
<div>
<div v-if="!isIllegal && imageUrl">
<img :src="imageUrl">
<Image :image-url="imageUrl" />
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
<div v-if="isIllegal">

View File

@ -1,46 +1,9 @@
<script setup lang="ts" generic="T extends any, O extends any">
defineOptions({
name: 'IndexPage',
})
const name = ref('')
const router = useRouter()
function go() {
if (name.value)
router.push(`/hi/${encodeURIComponent(name.value)}`)
}
import manba from '../../public/pack.png'
</script>
<template>
<div>
<div i-carbon-campsite inline-block text-4xl />
<p>
<a rel="noreferrer" href="https://github.com/antfu-collective/vitesse-lite" target="_blank">
Vitesse Lite
</a>
</p>
<p>
<em text-sm op75>Opinionated Vite Starter Template</em>
</p>
<div py-4 />
<TheInput
v-model="name"
placeholder="What's your name?"
autocomplete="false"
@keydown.enter="go"
/>
<div>
<button
class="m-3 text-sm btn"
:disabled="!name"
@click="go"
>
Go
</button>
</div>
<div flex="~ col items-center">
<img :src="manba">
</div>
</template>

View File

@ -39,7 +39,7 @@ onFetchResponse(async (resp) => {
})
onMounted(async () => {
const resp = await fetch('http://localhost:5000/radar/metadata')
const resp = await fetch(`${API_BASE_URL}/radar/metadata`)
const data = await resp.json()
// use regex to extract the year from the path,
// ./radar/data\\\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
@ -66,8 +66,6 @@ onMounted(async () => {
<div flex="~ col items-center">
<div>
<div flex="~ row items-center gap-3" py-3>
{{ stations }}
{{ years }}
<Select v-model="selectedMode">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="Select a fruit" />
@ -115,7 +113,7 @@ onMounted(async () => {
</div>
<div>
<div v-if="!isFetching && imageUrl !== ''">
<img :src="imageUrl">
<Image :image-url="imageUrl" />
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
loading...

View File

@ -1,13 +1,127 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
const selectedMode = ref('潮汐波')
const selectedStation = ref('武汉左岭镇站')
const selectedYear = ref('2017')
const imageUrl = ref('')
const modes = [
'潮汐波',
'2日行星波',
'5日行星波',
'10日行星波',
'16日行星波',
]
const paths = ref([] as string[])
const stations = ref<Set<string>>(new Set())
const years = ref<Set<string>>(new Set())
const queryUrl = computed(() => {
const station = encodeURIComponent(selectedStation.value)
const year = encodeURIComponent(selectedYear.value)
const mode = encodeURIComponent(selectedMode.value)
const query = `?station=${station}&year=${year}&model_name=${mode}`
const path = `${API_BASE_URL}/radar/render/v2${query}`
return path
})
const { onFetchResponse, isFetching } = useFetch(queryUrl, { refetch: true })
onFetchResponse(async (resp) => {
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
imageUrl.value = url
})
onMounted(async () => {
const resp = await fetch(`${API_BASE_URL}/radar/metadata`)
const data = await resp.json()
// use regex to extract the year from the path,
// ./radar/data\\\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
const station_pattern = /data\\(.*?)\\/
const year_pattern = /(\d{4})\\ZLT_/
const pairs = data.map((source_text: string) => {
const station = source_text.match(station_pattern)?.[1]
const year = source_text.match(year_pattern)?.[1]
return { station, year }
})
pairs.forEach(({ station, year }: {
station: string
year: string
}) => {
stations.value.add(station)
years.value.add(year)
})
paths.value = data
})
</script>
<template>
<div>
a
<div flex="~ col items-center">
<div>
<div flex="~ row items-center gap-3" py-3>
<Select v-model="selectedMode">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>计算模式</SelectLabel>
<SelectItem v-for="mode in modes" :key="mode" :value="mode">
{{ mode }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Select v-model="selectedStation">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>观测站</SelectLabel>
<SelectItem v-for="station in stations" :key="station" :value="station">
{{ station }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Select v-model="selectedYear">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel></SelectLabel>
<SelectItem v-for="year in years" :key="year" :value="year">
{{ year }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div>
{{ selectedMode }}
{{ selectedYear }}
</div>
<div>
<div v-if="!isFetching && imageUrl !== ''">
<Image :image-url="imageUrl" />
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
警告由于计算一年的数据需要等待大约1分钟出图<br>
请勿刷新页面或者离开当前页面改动参数<br>
否则需要重新等待 3 分钟
loading...
</div>
</div>
</div>
</template>
<style scoped>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,110 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
import { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths } from './utils'
const selected = reactive({
path: '',
day: '',
cycle_no: 1,
})
const imageUrl = ref('')
const urll = computed(() => {
const path = encodeURIComponent(selected.path)
const day = encodeURIComponent(selected.day)
const query = `path=${path}&day=${day}&cycle_no=${selected.cycle_no}`
return `${API_BASE_URL}/saber/render/day_cycle_power_wave_plot?${query}`
})
const { onFetchResponse, isFetching } = useFetch(
urll,
{ refetch: true },
)
onFetchResponse(async (resp) => {
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
imageUrl.value = url
})
onMounted(() => {
refreshPath()
selected.path = saberPaths.value[0]
})
watch(() => selected.path, () => {
refreshCurrentSaberDays(selected.path)
if (selected.day === '') {
selected.day = currentSaberDays.value[0]
}
})
function renderPath(path: string) {
const yearPattern = /\/data\\(\d{4})/
const year = path.match(yearPattern)?.[1]
const monthPattern = /Temp_O3_(.*)(\d{4})/
const month = path.match(monthPattern)?.[1]
return `${year}${month}`
}
</script>
<template>
<div flex="~ col items-center" w-full>
<div>
<div flex="~ row items-center gap-3" py-3>
<Label for="day">年月</Label>
<Select v-model="selected.path">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>月份</SelectLabel>
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
{{ renderPath(path) }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label for="day">天数</Label>
<Select id="day" v-model="selected.day">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Day</SelectLabel>
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
{{ day }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label for="age">Cycle No.</Label>
<NumberField id="age" v-model:model-value="selected.cycle_no" :default-value="1" :min="0">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</div>
</div>
<div>
{{ selected.day }}
{{ selected.path }}
</div>
<div w-full>
<div v-if="!isFetching && imageUrl !== ''">
<Image :image-url="imageUrl" />
</div>
<div v-else flex="~ col items-center justify-center" h-40 w-full text-xl>
<Loading />
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,110 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
import { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths } from './utils'
const selected = reactive({
path: './saber/data\\2012\\SABER_Temp_O3_April2012_v2.0.nc',
day: '',
cycle_no: 1,
})
const imageUrl = ref('')
const urll = computed(() => {
const path = encodeURIComponent(selected.path)
const day = encodeURIComponent(selected.day)
const query = `path=${path}&day=${day}&cycle_no=${selected.cycle_no}`
return `${API_BASE_URL}/saber/render/day_fft_ifft_plot?${query}`
})
const { onFetchResponse, isFetching } = useFetch(
urll,
{ refetch: true },
)
onFetchResponse(async (resp) => {
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
imageUrl.value = url
})
onMounted(() => {
refreshPath()
selected.path = saberPaths.value[0]
})
watch(() => selected.path, () => {
refreshCurrentSaberDays(selected.path)
if (selected.day === '') {
selected.day = currentSaberDays.value[0]
}
})
function renderPath(path: string) {
const yearPattern = /\/data\\(\d{4})/
const year = path.match(yearPattern)?.[1]
const monthPattern = /Temp_O3_(.*)(\d{4})/
const month = path.match(monthPattern)?.[1]
return `${year}${month}`
}
</script>
<template>
<div flex="~ col items-center" w-full>
<div>
<div flex="~ row items-center gap-3" py-3>
<Label for="day">年月</Label>
<Select v-model="selected.path">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>月份</SelectLabel>
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
{{ renderPath(path) }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label for="day">天数</Label>
<Select id="day" v-model="selected.day">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Day</SelectLabel>
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
{{ day }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label for="age">Cycle No.</Label>
<NumberField id="age" v-model:model-value="selected.cycle_no" :default-value="1" :min="0">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</div>
</div>
<div>
{{ selected.day }}
{{ selected.path }}
</div>
<div w-full>
<div v-if="!isFetching && imageUrl !== ''">
<Image :image-url="imageUrl" />
</div>
<div v-else flex="~ col items-center justify-center" h-40 w-full text-xl>
<Loading />
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,77 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
import { refreshPath, saberPaths } from './utils'
const selected = reactive({
path: '',
})
const imageUrl = ref('')
const urll = computed(() => {
const path = encodeURIComponent(selected.path)
const query = `path=${path}`
return `${API_BASE_URL}/saber/render/month_power_wave_plot?${query}`
})
const { onFetchResponse, isFetching } = useFetch(
urll,
{ refetch: true },
)
onFetchResponse(async (resp) => {
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
imageUrl.value = url
})
onMounted(() => {
refreshPath()
selected.path = saberPaths.value[0]
})
function renderPath(path: string) {
const yearPattern = /\/data\\(\d{4})/
const year = path.match(yearPattern)?.[1]
const monthPattern = /Temp_O3_(.*)(\d{4})/
const month = path.match(monthPattern)?.[1]
return `${year}${month}`
}
</script>
<template>
<div flex="~ col items-center" w-full>
<div>
<div flex="~ row items-center gap-3" py-3>
<Label for="day">年月</Label>
<Select v-model="selected.path">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>月份</SelectLabel>
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
{{ renderPath(path) }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div>
{{ selected.path }}
</div>
<div w-full>
<div v-if="!isFetching && imageUrl !== ''">
<Image :image-url="imageUrl" />
</div>
<div v-else flex="~ col items-center justify-center" h-40 w-full text-xl>
<Loading />
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,110 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
import { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths } from './utils'
const selected = reactive({
path: './saber/data\\2012\\SABER_Temp_O3_April2012_v2.0.nc',
day: '',
height_no: 1,
})
const imageUrl = ref('')
const urll = computed(() => {
const path = encodeURIComponent(selected.path)
const day = encodeURIComponent(selected.day)
const query = `path=${path}&day=${day}&height=${selected.height_no}`
return `${API_BASE_URL}/saber/render/plot_wave_fitting?${query}`
})
const { onFetchResponse, isFetching } = useFetch(
urll,
{ refetch: true },
)
onFetchResponse(async (resp) => {
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
imageUrl.value = url
})
onMounted(() => {
refreshPath()
selected.path = saberPaths.value[0]
})
watch(() => selected.path, () => {
refreshCurrentSaberDays(selected.path)
if (saberPaths.value.length > 0) {
selected.path = saberPaths.value[0]
}
})
function renderPath(path: string) {
const yearPattern = /\/data\\(\d{4})/
const year = path.match(yearPattern)?.[1]
const monthPattern = /Temp_O3_(.*)(\d{4})/
const month = path.match(monthPattern)?.[1]
return `${year}${month}`
}
</script>
<template>
<div flex="~ col items-center" w-full>
<div>
<div flex="~ row items-center gap-3" py-3>
<Label for="day">年月</Label>
<Select v-model="selected.path">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>月份</SelectLabel>
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
{{ renderPath(path) }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label for="day">天数</Label>
<Select id="day" v-model="selected.day">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="选择日期" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Day</SelectLabel>
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
{{ day }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Label for="age">高度</Label>
<NumberField id="age" :default-value="18" :min="0">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</div>
</div>
<div>
{{ selected.day }}
{{ selected.path }}
</div>
<div w-full>
<div v-if="!isFetching && imageUrl !== ''">
<Image :image-url="imageUrl" />
</div>
<div v-else flex="~ col items-center justify-center" h-40 w-full text-xl>
<Loading />
</div>
</div>
</div>
</template>
<style scoped>
</style>

20
src/pages/saber/utils.ts Normal file
View File

@ -0,0 +1,20 @@
import { API_BASE_URL } from '~/CONSTANT'
const saberPaths = ref<string[]>([])
const currentSaberDays = ref<string>('')
async function refreshPath() {
if (saberPaths.value.length)
return
const resp = await fetch(`${API_BASE_URL}/saber/metadata`)
const data = await resp.json()
saberPaths.value = data
}
async function refreshCurrentSaberDays(path: string) {
const resp = await fetch(`${API_BASE_URL}/saber/metadata/list_days?path=${path}`)
const data = await resp.json()
currentSaberDays.value = data
}
export { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths }

4
typed-router.d.ts vendored
View File

@ -24,5 +24,9 @@ declare module 'vue-router/auto-routes' {
'/balloon/year': RouteRecordInfo<'/balloon/year', '/balloon/year', Record<never, never>, Record<never, never>>,
'/radar/v1': RouteRecordInfo<'/radar/v1', '/radar/v1', Record<never, never>, Record<never, never>>,
'/radar/v2': RouteRecordInfo<'/radar/v2', '/radar/v2', Record<never, never>, Record<never, never>>,
'/saber/day_cycle_power_wave_plot': RouteRecordInfo<'/saber/day_cycle_power_wave_plot', '/saber/day_cycle_power_wave_plot', Record<never, never>, Record<never, never>>,
'/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>>,
}
}

View File

@ -5,7 +5,6 @@ import Vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import VueMacros from 'unplugin-vue-macros/vite'
import { VueRouterAutoImports } from 'unplugin-vue-router'
import VueRouter from 'unplugin-vue-router/vite'
import { defineConfig } from 'vite'
@ -17,19 +16,12 @@ export default defineConfig({
},
},
plugins: [
VueMacros({
defineOptions: false,
defineModels: false,
plugins: {
vue: Vue({
script: {
propsDestructure: true,
defineModel: true,
},
}),
Vue({
script: {
propsDestructure: true,
defineModel: true,
},
}),
// https://github.com/posva/unplugin-vue-router
VueRouter(),