2
This commit is contained in:
parent
63bb246e17
commit
663270d9af
25
components.d.ts
vendored
25
components.d.ts
vendored
@ -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
18
components.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -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
1464
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
11
src/App.vue
11
src/App.vue
@ -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
1
src/CONSTANT.ts
Normal file
@ -0,0 +1 @@
|
||||
export const API_BASE_URL = 'http://localhost:5000'
|
||||
78
src/assets/index.css
Normal file
78
src/assets/index.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
139
src/components/TheHeader.vue
Normal file
139
src/components/TheHeader.vue
Normal 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>
|
||||
@ -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>
|
||||
33
src/components/ui/navigation-menu/NavigationMenu.vue
Normal file
33
src/components/ui/navigation-menu/NavigationMenu.vue
Normal 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>
|
||||
34
src/components/ui/navigation-menu/NavigationMenuContent.vue
Normal file
34
src/components/ui/navigation-menu/NavigationMenuContent.vue
Normal 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>
|
||||
@ -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>
|
||||
11
src/components/ui/navigation-menu/NavigationMenuItem.vue
Normal file
11
src/components/ui/navigation-menu/NavigationMenuItem.vue
Normal 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>
|
||||
19
src/components/ui/navigation-menu/NavigationMenuLink.vue
Normal file
19
src/components/ui/navigation-menu/NavigationMenuLink.vue
Normal 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>
|
||||
29
src/components/ui/navigation-menu/NavigationMenuList.vue
Normal file
29
src/components/ui/navigation-menu/NavigationMenuList.vue
Normal 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>
|
||||
34
src/components/ui/navigation-menu/NavigationMenuTrigger.vue
Normal file
34
src/components/ui/navigation-menu/NavigationMenuTrigger.vue
Normal 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>
|
||||
33
src/components/ui/navigation-menu/NavigationMenuViewport.vue
Normal file
33
src/components/ui/navigation-menu/NavigationMenuViewport.vue
Normal 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>
|
||||
13
src/components/ui/navigation-menu/index.ts
Normal file
13
src/components/ui/navigation-menu/index.ts
Normal 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',
|
||||
)
|
||||
23
src/components/ui/number-field/NumberField.vue
Normal file
23
src/components/ui/number-field/NumberField.vue
Normal 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>
|
||||
14
src/components/ui/number-field/NumberFieldContent.vue
Normal file
14
src/components/ui/number-field/NumberFieldContent.vue
Normal 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>
|
||||
25
src/components/ui/number-field/NumberFieldDecrement.vue
Normal file
25
src/components/ui/number-field/NumberFieldDecrement.vue
Normal 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>
|
||||
25
src/components/ui/number-field/NumberFieldIncrement.vue
Normal file
25
src/components/ui/number-field/NumberFieldIncrement.vue
Normal 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>
|
||||
16
src/components/ui/number-field/NumberFieldInput.vue
Normal file
16
src/components/ui/number-field/NumberFieldInput.vue
Normal 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>
|
||||
5
src/components/ui/number-field/index.ts
Normal file
5
src/components/ui/number-field/index.ts
Normal 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'
|
||||
15
src/components/ui/select/Select.vue
Normal file
15
src/components/ui/select/Select.vue
Normal 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>
|
||||
53
src/components/ui/select/SelectContent.vue
Normal file
53
src/components/ui/select/SelectContent.vue
Normal 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>
|
||||
19
src/components/ui/select/SelectGroup.vue
Normal file
19
src/components/ui/select/SelectGroup.vue
Normal 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>
|
||||
44
src/components/ui/select/SelectItem.vue
Normal file
44
src/components/ui/select/SelectItem.vue
Normal 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>
|
||||
11
src/components/ui/select/SelectItemText.vue
Normal file
11
src/components/ui/select/SelectItemText.vue
Normal 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>
|
||||
13
src/components/ui/select/SelectLabel.vue
Normal file
13
src/components/ui/select/SelectLabel.vue
Normal 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>
|
||||
24
src/components/ui/select/SelectScrollDownButton.vue
Normal file
24
src/components/ui/select/SelectScrollDownButton.vue
Normal 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>
|
||||
24
src/components/ui/select/SelectScrollUpButton.vue
Normal file
24
src/components/ui/select/SelectScrollUpButton.vue
Normal 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>
|
||||
17
src/components/ui/select/SelectSeparator.vue
Normal file
17
src/components/ui/select/SelectSeparator.vue
Normal 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>
|
||||
31
src/components/ui/select/SelectTrigger.vue
Normal file
31
src/components/ui/select/SelectTrigger.vue
Normal 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>
|
||||
11
src/components/ui/select/SelectValue.vue
Normal file
11
src/components/ui/select/SelectValue.vue
Normal 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>
|
||||
11
src/components/ui/select/index.ts
Normal file
11
src/components/ui/select/index.ts
Normal 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
6
src/lib/utils.ts
Normal 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))
|
||||
}
|
||||
@ -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)
|
||||
|
||||
94
src/pages/balloon/single.vue
Normal file
94
src/pages/balloon/single.vue
Normal 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
115
src/pages/balloon/year.vue
Normal 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>
|
||||
@ -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
129
src/pages/radar/v1.vue
Normal 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
13
src/pages/radar/v2.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
a
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
5
typed-router.d.ts
vendored
5
typed-router.d.ts
vendored
@ -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>>,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}',
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user