This commit is contained in:
Dustella 2024-12-25 16:21:36 +08:00
parent 63bb246e17
commit 663270d9af
Signed by: Dustella
GPG Key ID: 07C9FA0DB5373B8D
48 changed files with 2697 additions and 121 deletions

25
components.d.ts vendored
View File

@ -7,10 +7,35 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
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']
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']
NumberFieldContent: typeof import('./src/components/ui/number-field/NumberFieldContent.vue')['default']
NumberFieldDecrement: typeof import('./src/components/ui/number-field/NumberFieldDecrement.vue')['default']
NumberFieldIncrement: typeof import('./src/components/ui/number-field/NumberFieldIncrement.vue')['default']
NumberFieldInput: typeof import('./src/components/ui/number-field/NumberFieldInput.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Select: typeof import('./src/components/ui/select/Select.vue')['default']
SelectContent: typeof import('./src/components/ui/select/SelectContent.vue')['default']
SelectGroup: typeof import('./src/components/ui/select/SelectGroup.vue')['default']
SelectItem: typeof import('./src/components/ui/select/SelectItem.vue')['default']
SelectItemText: typeof import('./src/components/ui/select/SelectItemText.vue')['default']
SelectLabel: typeof import('./src/components/ui/select/SelectLabel.vue')['default']
SelectScrollDownButton: typeof import('./src/components/ui/select/SelectScrollDownButton.vue')['default']
SelectScrollUpButton: typeof import('./src/components/ui/select/SelectScrollUpButton.vue')['default']
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']
TheFooter: typeof import('./src/components/TheFooter.vue')['default']
TheHeader: typeof import('./src/components/TheHeader.vue')['default']
TheInput: typeof import('./src/components/TheInput.vue')['default']
}
}

18
components.json Normal file
View File

@ -0,0 +1,18 @@
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "default",
"typescript": true,
"tsConfigPath": "./tsconfig.json",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/assets/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"framework": "vite",
"aliases": {
"components": "~/components",
"utils": "~/lib/utils"
}
}

View File

@ -14,6 +14,13 @@
},
"dependencies": {
"@vueuse/core": "^12.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-vue-next": "^0.468.0",
"radix-vue": "^1.9.11",
"shadcn-vue": "^0.11.3",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
@ -36,6 +43,8 @@
"taze": "^0.18.0",
"typescript": "~5.6.3",
"unocss": "^0.65.1",
"unocss-preset-animations": "^1.1.0",
"unocss-preset-shadcn": "^0.3.1",
"unplugin-auto-import": "^0.19.0",
"unplugin-vue-components": "^0.28.0",
"unplugin-vue-macros": "^2.13.6",

1464
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,13 @@
<template>
<div class="w-full flex items-center justify-center">
<div class="max-w-[980px]">
<TheHeader />
</div>
</div>
<main font-sans p="x-4 y-10" text="center gray-700 dark:gray-200">
<RouterView />
<TheFooter />
<div class="mx-auto max-w-[980px]">
<RouterView />
</div>
</main>
<TheFooter />
</template>

1
src/CONSTANT.ts Normal file
View File

@ -0,0 +1 @@
export const API_BASE_URL = 'http://localhost:5000'

78
src/assets/index.css Normal file
View File

@ -0,0 +1,78 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -1,9 +0,0 @@
## Components
Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components).
### Icons
You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/).
It will only bundle the icons you use. Check out [`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details.

View File

@ -1,19 +0,0 @@
<script setup lang="ts">
const props = defineProps<{
initial: number
}>()
const { count, inc, dec } = useCounter(props.initial)
</script>
<template>
<div>
{{ count }}
<button class="inc" @click="inc()">
+
</button>
<button class="dec" @click="dec()">
-
</button>
</div>
</template>

View File

@ -1,15 +1,3 @@
<template>
<nav mt-6 inline-flex gap-2 text-xl>
<button icon-btn @click="toggleDark()">
<div i-carbon-sun dark:i-carbon-moon />
</button>
<a
i-carbon-logo-github icon-btn
rel="noreferrer"
href="https://github.com/antfu-collective/vitesse-lite"
target="_blank"
title="GitHub"
/>
</nav>
<div />
</template>

View File

@ -0,0 +1,139 @@
<script setup lang="ts">
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
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.',
},
]
</script>
<template>
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink href="/" :class="navigationMenuTriggerStyle()">
主页
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>探空气球</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">
<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"
>
<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>
<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

@ -1,18 +0,0 @@
<script setup lang="ts">
const modelValue = defineModel()
</script>
<template>
<input
id="input"
v-model="modelValue"
type="text"
v-bind="$attrs"
p="x-4 y-2"
w="250px"
text="center"
bg="transparent"
border="~ rounded gray-200 dark:gray-700"
outline="none active:none"
>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import {
NavigationMenuRoot,
type NavigationMenuRootEmits,
type NavigationMenuRootProps,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
import NavigationMenuViewport from './NavigationMenuViewport.vue'
const props = defineProps<NavigationMenuRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<NavigationMenuRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<NavigationMenuRoot
v-bind="forwarded"
:class="cn('relative z-10 flex max-w-max flex-1 items-center justify-center', props.class)"
>
<slot />
<NavigationMenuViewport />
</NavigationMenuRoot>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {
NavigationMenuContent,
type NavigationMenuContentEmits,
type NavigationMenuContentProps,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NavigationMenuContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<NavigationMenuContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<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',
props.class,
)"
>
<slot />
</NavigationMenuContent>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { NavigationMenuIndicator, type NavigationMenuIndicatorProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NavigationMenuIndicatorProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuIndicator
v-bind="forwardedProps"
:class="cn('top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in', props.class)"
>
<div class="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuIndicator>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { NavigationMenuItem, type NavigationMenuItemProps } from 'radix-vue'
const props = defineProps<NavigationMenuItemProps>()
</script>
<template>
<NavigationMenuItem v-bind="props">
<slot />
</NavigationMenuItem>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import {
NavigationMenuLink,
type NavigationMenuLinkEmits,
type NavigationMenuLinkProps,
useForwardPropsEmits,
} from 'radix-vue'
const props = defineProps<NavigationMenuLinkProps>()
const emits = defineEmits<NavigationMenuLinkEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<NavigationMenuLink v-bind="forwarded">
<slot />
</NavigationMenuLink>
</template>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import { NavigationMenuList, type NavigationMenuListProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NavigationMenuListProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuList
v-bind="forwardedProps"
:class="
cn(
'group flex flex-1 list-none items-center justify-center gap-x-1',
props.class,
)
"
>
<slot />
</NavigationMenuList>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import { ChevronDown } from 'lucide-vue-next'
import {
NavigationMenuTrigger,
type NavigationMenuTriggerProps,
useForwardProps,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
import { navigationMenuTriggerStyle } from '.'
const props = defineProps<NavigationMenuTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuTrigger
v-bind="forwardedProps"
:class="cn(navigationMenuTriggerStyle(), 'group', props.class)"
>
<slot />
<ChevronDown
class="relative top-px ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuTrigger>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import {
NavigationMenuViewport,
type NavigationMenuViewportProps,
useForwardProps,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NavigationMenuViewportProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<div class="absolute left-0 top-full flex justify-center">
<NavigationMenuViewport
v-bind="forwardedProps"
:class="
cn(
'origin-top-center relative mt-1.5 h-[--radix-navigation-menu-viewport-height] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[--radix-navigation-menu-viewport-width]',
props.class,
)
"
/>
</div>
</template>

View File

@ -0,0 +1,13 @@
import { cva } from 'class-variance-authority'
export { default as NavigationMenu } from './NavigationMenu.vue'
export { default as NavigationMenuContent } from './NavigationMenuContent.vue'
export { default as NavigationMenuItem } from './NavigationMenuItem.vue'
export { default as NavigationMenuLink } from './NavigationMenuLink.vue'
export { default as NavigationMenuList } from './NavigationMenuList.vue'
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',
)

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import type { NumberFieldRootEmits, NumberFieldRootProps } from 'radix-vue'
import { NumberFieldRoot, useForwardPropsEmits } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NumberFieldRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<NumberFieldRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<NumberFieldRoot v-bind="forwarded" :class="cn('grid gap-1.5', props.class)">
<slot />
</NumberFieldRoot>
</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>
<div :class="cn('relative [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-5 [&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-5', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import type { NumberFieldDecrementProps } from 'radix-vue'
import { Minus } from 'lucide-vue-next'
import { NumberFieldDecrement, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NumberFieldDecrementProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
<NumberFieldDecrement data-slot="decrement" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:cursor-not-allowed disabled:opacity-20', props.class)">
<slot>
<Minus class="h-4 w-4" />
</slot>
</NumberFieldDecrement>
</template>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import type { NumberFieldIncrementProps } from 'radix-vue'
import { Plus } from 'lucide-vue-next'
import { NumberFieldIncrement, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<NumberFieldIncrementProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
<NumberFieldIncrement data-slot="increment" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 right-0 disabled:cursor-not-allowed disabled:opacity-20 p-3', props.class)">
<slot>
<Plus class="h-4 w-4" />
</slot>
</NumberFieldIncrement>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { NumberFieldInput } from 'radix-vue'
import { cn } from '~/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<NumberFieldInput
data-slot="input"
:class="cn('flex h-10 w-full rounded-md border border-input bg-background py-2 text-sm text-center ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)"
/>
</template>

View File

@ -0,0 +1,5 @@
export { default as NumberField } from './NumberField.vue'
export { default as NumberFieldContent } from './NumberFieldContent.vue'
export { default as NumberFieldDecrement } from './NumberFieldDecrement.vue'
export { default as NumberFieldIncrement } from './NumberFieldIncrement.vue'
export { default as NumberFieldInput } from './NumberFieldInput.vue'

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import type { SelectRootEmits, SelectRootProps } from 'radix-vue'
import { SelectRoot, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<SelectRootProps>()
const emits = defineEmits<SelectRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<SelectRoot v-bind="forwarded">
<slot />
</SelectRoot>
</template>

View File

@ -0,0 +1,53 @@
<script setup lang="ts">
import {
SelectContent,
type SelectContentEmits,
type SelectContentProps,
SelectPortal,
SelectViewport,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
import { SelectScrollDownButton, SelectScrollUpButton } from '.'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(
defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(),
{
position: 'popper',
},
)
const emits = defineEmits<SelectContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<SelectPortal>
<SelectContent
v-bind="{ ...forwarded, ...$attrs }" :class="cn(
'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper'
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class,
)
"
>
<SelectScrollUpButton />
<SelectViewport :class="cn('p-1', position === 'popper' && 'h-[--radix-select-trigger-height] w-full min-w-[--radix-select-trigger-width]')">
<slot />
</SelectViewport>
<SelectScrollDownButton />
</SelectContent>
</SelectPortal>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { SelectGroup, type SelectGroupProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectGroupProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<SelectGroup :class="cn('p-1 w-full', props.class)" v-bind="delegatedProps">
<slot />
</SelectGroup>
</template>

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import { Check } from 'lucide-vue-next'
import {
SelectItem,
SelectItemIndicator,
type SelectItemProps,
SelectItemText,
useForwardProps,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectItemProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectItem
v-bind="forwardedProps"
:class="
cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
)
"
>
<span class="absolute left-2 h-3.5 w-3.5 flex items-center justify-center">
<SelectItemIndicator>
<Check class="h-4 w-4" />
</SelectItemIndicator>
</span>
<SelectItemText>
<slot />
</SelectItemText>
</SelectItem>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { SelectItemText, type SelectItemTextProps } from 'radix-vue'
const props = defineProps<SelectItemTextProps>()
</script>
<template>
<SelectItemText v-bind="props">
<slot />
</SelectItemText>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { SelectLabel, type SelectLabelProps } from 'radix-vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectLabelProps & { class?: HTMLAttributes['class'] }>()
</script>
<template>
<SelectLabel :class="cn('py-1.5 pl-8 pr-2 text-sm font-semibold', props.class)">
<slot />
</SelectLabel>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { ChevronDown } from 'lucide-vue-next'
import { SelectScrollDownButton, type SelectScrollDownButtonProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectScrollDownButton v-bind="forwardedProps" :class="cn('flex cursor-default items-center justify-center py-1', props.class)">
<slot>
<ChevronDown class="h-4 w-4" />
</slot>
</SelectScrollDownButton>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { ChevronUp } from 'lucide-vue-next'
import { SelectScrollUpButton, type SelectScrollUpButtonProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectScrollUpButton v-bind="forwardedProps" :class="cn('flex cursor-default items-center justify-center py-1', props.class)">
<slot>
<ChevronUp class="h-4 w-4" />
</slot>
</SelectScrollUpButton>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { SelectSeparator, type SelectSeparatorProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<SelectSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
</template>

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import { ChevronDown } from 'lucide-vue-next'
import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
import { cn } from '~/lib/utils'
const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectTrigger
v-bind="forwardedProps"
:class="cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
props.class,
)"
>
<slot />
<SelectIcon as-child>
<ChevronDown class="h-4 w-4 shrink-0 opacity-50" />
</SelectIcon>
</SelectTrigger>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { SelectValue, type SelectValueProps } from 'radix-vue'
const props = defineProps<SelectValueProps>()
</script>
<template>
<SelectValue v-bind="props">
<slot />
</SelectValue>
</template>

View File

@ -0,0 +1,11 @@
export { default as Select } from './Select.vue'
export { default as SelectContent } from './SelectContent.vue'
export { default as SelectGroup } from './SelectGroup.vue'
export { default as SelectItem } from './SelectItem.vue'
export { default as SelectItemText } from './SelectItemText.vue'
export { default as SelectLabel } from './SelectLabel.vue'
export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue'
export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue'
export { default as SelectSeparator } from './SelectSeparator.vue'
export { default as SelectTrigger } from './SelectTrigger.vue'
export { default as SelectValue } from './SelectValue.vue'

6
src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -5,6 +5,7 @@ import App from './App.vue'
import '@unocss/reset/tailwind.css'
import './styles/main.css'
import './assets/index.css'
import 'uno.css'
const app = createApp(App)

View File

@ -0,0 +1,94 @@
<script setup lang="ts">
const selectedMode = ref('观测的二阶多项式拟合')
const imageUrl = ref('')
const hasWaveThisDay = ref(false)
const modes = [
'观测的二阶多项式拟合',
'扰动分量的正弦波拟合',
'径向风-纬向风矢量图',
'温度-水平风矢量图',
]
const selectedDate = ref('2022-01-01')
const dates = ref([] as string[])
onMounted(async () => {
const resp = await fetch('http://localhost:5000/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)}`)
// check for MIME Type, check if is png
const isPng = resp.headers.get('Content-Type') === 'image/png'
if (!isPng) {
hasWaveThisDay.value = false
return
// return alert('No image available for this mode and date')
}
hasWaveThisDay.value = true
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
imageUrl.value = url
}
watch([selectedMode, selectedDate], () => {
get_image()
})
</script>
<template>
<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="selectedDate">
<SelectTrigger class="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel></SelectLabel>
<SelectItem v-for="date in dates" :key="date" :value="date">
{{ date.match(/_(\d{8}T\d{6})/)?.[1] }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div>
{{ selectedMode }}
{{ selectedDate }}
</div>
<div>
<div v-if="hasWaveThisDay">
<img :src="imageUrl">
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
{{ selectedDate.match(/_(\d{8}T\d{6})/)?.[1] }} <br>
这个时刻没有数据
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
</style>

115
src/pages/balloon/year.vue Normal file
View File

@ -0,0 +1,115 @@
<script setup lang="ts">
const selectedMode = ref('w/f值统计结果')
const isIllegal = ref(false)
const imageUrl = ref('')
const startYear = ref(2017)
const endYear = ref(2024)
const modes = [
'w/f值统计结果',
'周期统计结果',
'垂直波长分布',
'水平波长分布',
'纬向本征相速度',
'经向本征相速度',
'垂直本征相速度',
'Zonal wind amplitude (m/s)',
'扰动振幅统计结果',
'Temperature amplitude (K)',
'纬向动量通量统计结果',
'经向动量通量统计结果',
'horizontal propagation',
'每月上传/下传重力波占比',
'动能和势能分布情况',
]
async function refreshImage() {
const url = `http://localhost:5000/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)
imageUrl.value = u
}
watch([selectedMode, startYear, endYear], () => {
if (startYear.value > endYear.value) {
isIllegal.value = true
return
}
isIllegal.value = false
refreshImage()
})
</script>
<template>
<div>
<div>
<div flex="row ~ items-center justify-center">
<div>
<Label>计算模式</Label>
<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>
</div>
<NumberField
id="start"
v-model:model-value="startYear" :format-options="{
useGrouping: false,
}" :default-value="2017" :min="2017" :max="2024"
>
<Label for="start">起始年</Label>
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
<NumberField
id="end"
v-model:model-value="endYear" :format-options="{
style: 'decimal',
notation: 'standard',
useGrouping: false,
}" :default-value="2017" :min="2017" :max="2024"
>
<Label for="end">终止年</Label>
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</div>
<div>
<div v-if="!isIllegal && imageUrl">
<img :src="imageUrl">
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
<div v-if="isIllegal">
<div>Start year should be less than end year</div>
</div>
<div v-else>
<div>There is no data</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,22 +0,0 @@
<script setup lang="ts">
const params = useRoute('/hi/[name]').params
const router = useRouter()
</script>
<template>
<div>
<div i-carbon-pedestrian inline-block text-4xl />
<p>
Hi, {{ params.name }}
</p>
<p text-sm op50>
<em>Dynamic route!</em>
</p>
<div>
<button m-3 mt-8 text-sm btn @click="router.back()">
Back
</button>
</div>
</div>
</template>

129
src/pages/radar/v1.vue Normal file
View File

@ -0,0 +1,129 @@
<script setup lang="ts">
import { API_BASE_URL } from '~/CONSTANT'
const selectedMode = ref('潮汐波')
const selectedStation = ref('武汉左岭镇站')
const selectedYear = ref('2017')
const selectedWindType = ref('uwind')
const selectedH = ref(90000)
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 windType = encodeURIComponent(selectedWindType.value)
const query = `?station=${station}&year=${year}&model_name=${mode}&wind_type=${windType}&H=${selectedH.value}`
const path = `${API_BASE_URL}/radar/render/v1${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('http://localhost:5000/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 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" />
</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 !== ''">
<img :src="imageUrl">
</div>
<div v-else h-40 flex="~ col items-center justify-center" text-xl>
loading...
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
</style>

13
src/pages/radar/v2.vue Normal file
View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div>
a
</div>
</template>
<style scoped>
</style>

5
typed-router.d.ts vendored
View File

@ -20,6 +20,9 @@ declare module 'vue-router/auto-routes' {
export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
'/hi/[name]': RouteRecordInfo<'/hi/[name]', '/hi/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/balloon/single': RouteRecordInfo<'/balloon/single', '/balloon/single', Record<never, never>, Record<never, never>>,
'/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>>,
}
}

View File

@ -5,6 +5,8 @@ import {
presetUno,
presetWebFonts,
} from 'unocss'
import presetAnimations from 'unocss-preset-animations'
import { presetShadcn } from 'unocss-preset-shadcn'
export default defineConfig({
shortcuts: [
@ -25,5 +27,20 @@ export default defineConfig({
mono: 'DM Mono',
},
}),
presetAnimations(),
presetShadcn({
// With default setting for SolidUI, you need to set the darkSelector option.
darkSelector: '[data-kb-theme="dark"]',
}),
],
content: {
pipeline: {
include: [
// the default
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/,
// include js/ts files
'(components|src)/**/*.{js,ts}',
],
},
},
})