newui: ballon
This commit is contained in:
parent
522cf7dfb9
commit
41a78347d1
24
components.d.ts
vendored
24
components.d.ts
vendored
@ -11,6 +11,9 @@ declare module 'vue' {
|
||||
AccordionContent: typeof import('./src/components/ui/accordion/AccordionContent.vue')['default']
|
||||
AccordionItem: typeof import('./src/components/ui/accordion/AccordionItem.vue')['default']
|
||||
AccordionTrigger: typeof import('./src/components/ui/accordion/AccordionTrigger.vue')['default']
|
||||
Alert: typeof import('./src/components/ui/alert/Alert.vue')['default']
|
||||
AlertDescription: typeof import('./src/components/ui/alert/AlertDescription.vue')['default']
|
||||
AlertTitle: typeof import('./src/components/ui/alert/AlertTitle.vue')['default']
|
||||
AutoForm: typeof import('./src/components/ui/auto-form/AutoForm.vue')['default']
|
||||
AutoFormField: typeof import('./src/components/ui/auto-form/AutoFormField.vue')['default']
|
||||
AutoFormFieldArray: typeof import('./src/components/ui/auto-form/AutoFormFieldArray.vue')['default']
|
||||
@ -50,6 +53,8 @@ declare module 'vue' {
|
||||
Collapsible: typeof import('./src/components/ui/collapsible/Collapsible.vue')['default']
|
||||
CollapsibleContent: typeof import('./src/components/ui/collapsible/CollapsibleContent.vue')['default']
|
||||
CollapsibleTrigger: typeof import('./src/components/ui/collapsible/CollapsibleTrigger.vue')['default']
|
||||
CoolBack: typeof import('./src/components/CoolBack.vue')['default']
|
||||
DenseFramework: typeof import('./src/components/DenseFramework.vue')['default']
|
||||
DropdownMenu: typeof import('./src/components/ui/dropdown-menu/DropdownMenu.vue')['default']
|
||||
DropdownMenuCheckboxItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue')['default']
|
||||
DropdownMenuContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuContent.vue')['default']
|
||||
@ -74,6 +79,21 @@ declare module 'vue' {
|
||||
Input: typeof import('./src/components/ui/input/Input.vue')['default']
|
||||
Label: typeof import('./src/components/ui/label/Label.vue')['default']
|
||||
Loading: typeof import('./src/components/Loading.vue')['default']
|
||||
Menubar: typeof import('./src/components/ui/menubar/Menubar.vue')['default']
|
||||
MenubarCheckboxItem: typeof import('./src/components/ui/menubar/MenubarCheckboxItem.vue')['default']
|
||||
MenubarContent: typeof import('./src/components/ui/menubar/MenubarContent.vue')['default']
|
||||
MenubarGroup: typeof import('./src/components/ui/menubar/MenubarGroup.vue')['default']
|
||||
MenubarItem: typeof import('./src/components/ui/menubar/MenubarItem.vue')['default']
|
||||
MenubarLabel: typeof import('./src/components/ui/menubar/MenubarLabel.vue')['default']
|
||||
MenubarMenu: typeof import('./src/components/ui/menubar/MenubarMenu.vue')['default']
|
||||
MenubarRadioGroup: typeof import('./src/components/ui/menubar/MenubarRadioGroup.vue')['default']
|
||||
MenubarRadioItem: typeof import('./src/components/ui/menubar/MenubarRadioItem.vue')['default']
|
||||
MenubarSeparator: typeof import('./src/components/ui/menubar/MenubarSeparator.vue')['default']
|
||||
MenubarShortcut: typeof import('./src/components/ui/menubar/MenubarShortcut.vue')['default']
|
||||
MenubarSub: typeof import('./src/components/ui/menubar/MenubarSub.vue')['default']
|
||||
MenubarSubContent: typeof import('./src/components/ui/menubar/MenubarSubContent.vue')['default']
|
||||
MenubarSubTrigger: typeof import('./src/components/ui/menubar/MenubarSubTrigger.vue')['default']
|
||||
MenubarTrigger: typeof import('./src/components/ui/menubar/MenubarTrigger.vue')['default']
|
||||
NavigationMenu: typeof import('./src/components/ui/navigation-menu/NavigationMenu.vue')['default']
|
||||
NavigationMenuContent: typeof import('./src/components/ui/navigation-menu/NavigationMenuContent.vue')['default']
|
||||
NavigationMenuIndicator: typeof import('./src/components/ui/navigation-menu/NavigationMenuIndicator.vue')['default']
|
||||
@ -88,12 +108,15 @@ declare module 'vue' {
|
||||
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']
|
||||
Paper: typeof import('./src/components/Paper.vue')['default']
|
||||
ParamsCard: typeof import('./src/components/ParamsCard.vue')['default']
|
||||
Popover: typeof import('./src/components/ui/popover/Popover.vue')['default']
|
||||
PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default']
|
||||
PopoverTrigger: typeof import('./src/components/ui/popover/PopoverTrigger.vue')['default']
|
||||
RadioGroup: typeof import('./src/components/ui/radio-group/RadioGroup.vue')['default']
|
||||
RadioGroupItem: typeof import('./src/components/ui/radio-group/RadioGroupItem.vue')['default']
|
||||
ResizableHandle: typeof import('./src/components/ui/resizable/ResizableHandle.vue')['default']
|
||||
ResizablePanelGroup: typeof import('./src/components/ui/resizable/ResizablePanelGroup.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Select: typeof import('./src/components/ui/select/Select.vue')['default']
|
||||
@ -150,5 +173,6 @@ declare module 'vue' {
|
||||
TooltipContent: typeof import('./src/components/ui/tooltip/TooltipContent.vue')['default']
|
||||
TooltipProvider: typeof import('./src/components/ui/tooltip/TooltipProvider.vue')['default']
|
||||
TooltipTrigger: typeof import('./src/components/ui/tooltip/TooltipTrigger.vue')['default']
|
||||
TopHeader: typeof import('./src/components/TopHeader.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,11 @@
|
||||
"@unocss/preset-icons": "^0.65.4",
|
||||
"@vee-validate/zod": "^4.15.0",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/motion": "^2.2.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"p5i": "^0.6.0",
|
||||
"radix-vue": "^1.9.11",
|
||||
"shadcn-vue": "^0.11.3",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
|
||||
624
pnpm-lock.yaml
generated
624
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
202
src/App.vue
202
src/App.vue
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
import { ChevronRight, ChevronsUpDown } from 'lucide-vue-next'
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@ -9,13 +10,14 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '~/components/ui/breadcrumb'
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '~/components/ui/collapsible'
|
||||
|
||||
import { Separator } from '~/components/ui/separator'
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@ -31,7 +33,6 @@ import {
|
||||
SidebarMenuSubItem,
|
||||
SidebarProvider,
|
||||
SidebarRail,
|
||||
SidebarTrigger,
|
||||
} from '~/components/ui/sidebar'
|
||||
|
||||
// This is sample data.
|
||||
@ -108,83 +109,134 @@ const data = {
|
||||
},
|
||||
|
||||
],
|
||||
}
|
||||
} as const
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SidebarProvider>
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>模型</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
<Collapsible
|
||||
v-for="item in data.navMain"
|
||||
:key="item.title"
|
||||
as-child
|
||||
:default-open="item.isActive"
|
||||
class="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger as-child>
|
||||
<SidebarMenuButton :tooltip="item.title">
|
||||
<Icon :icon="item.icon" />
|
||||
<span>{{ item.title }}</span>
|
||||
<ChevronRight class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<div>
|
||||
<div>
|
||||
<!-- <TopHeader /> -->
|
||||
</div>
|
||||
<div>
|
||||
<SidebarProvider>
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<Alert>
|
||||
<Terminal class="h-4 w-4" />
|
||||
<AlertTitle>服务状态正常</AlertTitle>
|
||||
</Alert>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>模型</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
<Collapsible
|
||||
v-for="item in data.navMain"
|
||||
:key="item.title"
|
||||
as-child
|
||||
:default-open="item.isActive"
|
||||
class="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger as-child>
|
||||
<SidebarMenuButton :tooltip="item.title">
|
||||
<Icon :icon="item.icon" />
|
||||
<span>{{ item.title }}</span>
|
||||
<ChevronRight class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
<SidebarMenuSubItem
|
||||
v-for="subItem in item.items"
|
||||
:key="subItem.title"
|
||||
class="hover:bg-gray-100"
|
||||
>
|
||||
<SidebarMenuSubButton as-child>
|
||||
<RouterLink
|
||||
:to="subItem.url"
|
||||
active-class="bg-accent-foreground text-accent"
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
<SidebarMenuSubItem
|
||||
v-for="subItem in item.items"
|
||||
:key="subItem.title"
|
||||
class="hover:bg-gray-100"
|
||||
>
|
||||
<span>{{ subItem.title }}</span>
|
||||
</RouterLink>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
<SidebarMenuSubButton as-child>
|
||||
<RouterLink
|
||||
:to="subItem.url"
|
||||
active-class="bg-accent-foreground text-accent"
|
||||
>
|
||||
<span>{{ subItem.title }}</span>
|
||||
</RouterLink>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem flex="~ row items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">操作员</span>
|
||||
<span class="truncate text-xs">已授权</span>
|
||||
</div>
|
||||
<ChevronsUpDown class="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="min-w-56 w-[--radix-dropdown-menu-trigger-width] rounded-lg" side="bottom" align="end" :side-offset="4">
|
||||
<DropdownMenuItem>
|
||||
<Icon icon="heroicons-solid:logout" class="h-4 w-4" />
|
||||
退出系统
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button variant="outline" size="icon" class="ml-auto h-8 w-8">
|
||||
<Icon icon="icon-park-outline:setting" class="h-4 w-4" />
|
||||
<span class="sr-only">设置</span>
|
||||
</Button>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem />
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
<SidebarInset>
|
||||
<header class="group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 h-16 flex shrink-0 items-center gap-2 transition-[width,height] ease-linear">
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger class="-ml-1" />
|
||||
<Separator orientation="vertical" class="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem class="hidden md:block">
|
||||
<BreadcrumbLink href="#">
|
||||
{{ $route.meta.group }}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator class="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{{ $route.meta.item_name }}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</header>
|
||||
<RouterView />
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
<SidebarInset>
|
||||
<header class="group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 h-16 flex shrink-0 items-center justify-between gap-2 transition-[width,height] ease-linear">
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<Icon icon="heroicons-solid:home" class="h-4 w-4" />
|
||||
<Separator orientation="vertical" class="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">
|
||||
中高层大气波动解析识别技术系统
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator class="hidden md:block" />
|
||||
|
||||
<BreadcrumbItem class="hidden md:block">
|
||||
<BreadcrumbLink href="#">
|
||||
{{ $route.meta.group }}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator class="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{{ $route.meta.item_name }}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div px-7>
|
||||
<Alert variant="default">
|
||||
<Icon icon="el:ok-circle" class="h-4 w-4 text-green!" />
|
||||
<AlertTitle>服务状态正常</AlertTitle>
|
||||
</Alert>
|
||||
</div>
|
||||
</header>
|
||||
<RouterView class="h-full overflow-hidden" />
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
36
src/components/DenseFramework.vue
Normal file
36
src/components/DenseFramework.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ImageResult } from './ImageContainer.vue'
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '~/components/ui/resizable'
|
||||
|
||||
defineProps<{
|
||||
imageResult: ImageResult
|
||||
}>()
|
||||
|
||||
defineEmits(['submit'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex="~ row h-full items-begin justify-stretch gap-3 p-8 w-full">
|
||||
<ResizablePanelGroup
|
||||
id="demo-group-1"
|
||||
direction="horizontal"
|
||||
class="w-full border rounded-lg"
|
||||
>
|
||||
<ResizablePanel id="demo-panel-1" :default-size="200" py-5>
|
||||
<ParamsCard
|
||||
@submit="$emit('submit')"
|
||||
>
|
||||
<slot />
|
||||
</ParamsCard>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="demo-handle-1" />
|
||||
<ResizablePanel id="demo-panel-2" :default-size="500">
|
||||
<ImageContainer :image-result="imageResult" />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</template>
|
||||
@ -6,7 +6,7 @@ defineProps<{
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img id="image_" class="w-400" :src="imageUrl">
|
||||
<img id="image_" :src="imageUrl">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,16 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js'
|
||||
|
||||
export interface ImageResult {
|
||||
result: 'success' | 'error' | 'pending' | 'idle'
|
||||
imageUrl: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
imageResult: ImageResult
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full min-h-[50vh] w-full flex flex-col rounded-xl bg-muted/50 p-4 lg:col-span-2">
|
||||
<Badge variant="outline" class="absolute right-3 top-3">
|
||||
Output
|
||||
<Badge variant="outline" class="absolute left-3 top-3">
|
||||
图片输出
|
||||
</Badge>
|
||||
<div class="flex-1" />
|
||||
<Image :image-url="imageUrl" />
|
||||
<div v-if="imageResult.result === 'pending'" class="flex flex-1 items-center justify-center text-xl">
|
||||
<Icon icon="akar-icons:loading" class="mr-2 animate-spin" />
|
||||
</div>
|
||||
<Image v-else-if="imageResult.result === 'success'" :image-url="imageResult.imageUrl" />
|
||||
<div v-else class="flex flex-1 items-center justify-center text-xl">
|
||||
{{ imageResult.message }}
|
||||
</div>
|
||||
|
||||
<Button type="submit" size="sm" class="ml-auto gap-1.5">
|
||||
下载图片
|
||||
|
||||
79
src/components/Paper.vue
Normal file
79
src/components/Paper.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script setup lang='ts'>
|
||||
import { useEventListener, useFullscreen } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const fullscreen = useFullscreen(ref(document.querySelector('html')))
|
||||
|
||||
// const isDark = useDark()
|
||||
|
||||
useEventListener('keydown', (e) => {
|
||||
if (document.activeElement === document.body) {
|
||||
if (e.key === 'f') {
|
||||
if (fullscreen.isFullscreen.value)
|
||||
fullscreen.exit()
|
||||
else
|
||||
fullscreen.enter()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let no = route.path.slice(1)
|
||||
if (no.startsWith('x'))
|
||||
no = no.slice(1)
|
||||
|
||||
const shot = Boolean(route.query.shot)
|
||||
const hideFrame = Boolean(route.query.hideFrame !== undefined || route.query.full !== undefined)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="paper" :class="{ shot }">
|
||||
<div v-if="!shot && !hideFrame" class="nav font-mono">
|
||||
<router-link to="/" class="link block pt-1 text-xl">
|
||||
<carbon-chevron-left />
|
||||
</router-link>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.shot .nav {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.shot .bottom-nav {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.nav-links .next,
|
||||
.nav-links .prev {
|
||||
opacity: 0;
|
||||
transition: 0.3s all ease-in-out;
|
||||
margin-top: -1.5em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-links:hover .next,
|
||||
.nav-links:hover .prev {
|
||||
opacity: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,15 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ZodObject } from 'zod'
|
||||
|
||||
defineProps<{
|
||||
schema: ZodObject<any, any, any> | null
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
submit: [value: {
|
||||
[x: string]: any
|
||||
}]
|
||||
}>()
|
||||
defineEmits(['submit'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -17,7 +7,7 @@ defineEmits<{
|
||||
<DrawerTrigger as-child class="min-w-[20rem]">
|
||||
<Button variant="ghost" size="icon" class="md:hidden">
|
||||
<Settings class="size-4" />
|
||||
<span class="sr-only">Settings</span>
|
||||
<span class="sr-only">参数设置</span>
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent class="max-h-[80vh]">
|
||||
@ -27,14 +17,13 @@ defineEmits<{
|
||||
<form class="grid min-w-[20rem] w-full items-start gap-6 overflow-auto p-4 pt-0">
|
||||
<fieldset class="grid gap-6 border rounded-lg p-4">
|
||||
<legend class="px-1 text-sm font-medium -ml-1">
|
||||
Settings
|
||||
参数设置
|
||||
</legend>
|
||||
<div class="grid gap-3">
|
||||
<AutoForm v-if="schema" :schema="schema" @submit="(e) => $emit('submit', e)">
|
||||
<Button type="submit" class="w-full">
|
||||
绘制
|
||||
</Button>
|
||||
</AutoForm>
|
||||
<slot />
|
||||
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('submit')">
|
||||
绘制
|
||||
</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
123
src/components/TopHeader.vue
Normal file
123
src/components/TopHeader.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from '~/components/ui/menubar'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Menubar>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
New Window <MenubarShortcut>⌘N</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled>
|
||||
New Incognito Window
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Email link</MenubarItem>
|
||||
<MenubarItem>Messages</MenubarItem>
|
||||
<MenubarItem>Notes</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>
|
||||
Print... <MenubarShortcut>⌘P</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Edit</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Find</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Search the web</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Find...</MenubarItem>
|
||||
<MenubarItem>Find Next</MenubarItem>
|
||||
<MenubarItem>Find Previous</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Cut</MenubarItem>
|
||||
<MenubarItem>Copy</MenubarItem>
|
||||
<MenubarItem>Paste</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>
|
||||
Always Show Full URLs
|
||||
</MenubarCheckboxItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Reload <MenubarShortcut>⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled inset>
|
||||
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Toggle Fullscreen
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Hide Sidebar
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Profiles</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarRadioGroup value="benoit">
|
||||
<MenubarRadioItem value="andy">
|
||||
Andy
|
||||
</MenubarRadioItem>
|
||||
<MenubarRadioItem value="benoit">
|
||||
Benoit
|
||||
</MenubarRadioItem>
|
||||
<MenubarRadioItem value="Luis">
|
||||
Luis
|
||||
</MenubarRadioItem>
|
||||
</MenubarRadioGroup>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Edit...
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Add Profile...
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
</template>
|
||||
16
src/components/ui/alert/Alert.vue
Normal file
16
src/components/ui/alert/Alert.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
import { type AlertVariants, alertVariants } from '.'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
variant?: AlertVariants['variant']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(alertVariants({ variant }), props.class)" role="alert">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
14
src/components/ui/alert/AlertDescription.vue
Normal file
14
src/components/ui/alert/AlertDescription.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('text-sm [&_p]:leading-relaxed', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
14
src/components/ui/alert/AlertTitle.vue
Normal file
14
src/components/ui/alert/AlertTitle.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>
|
||||
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</h5>
|
||||
</template>
|
||||
23
src/components/ui/alert/index.ts
Normal file
23
src/components/ui/alert/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
export { default as Alert } from './Alert.vue'
|
||||
export { default as AlertDescription } from './AlertDescription.vue'
|
||||
export { default as AlertTitle } from './AlertTitle.vue'
|
||||
|
||||
export const alertVariants = cva(
|
||||
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-background text-foreground',
|
||||
destructive:
|
||||
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type AlertVariants = VariantProps<typeof alertVariants>
|
||||
35
src/components/ui/menubar/Menubar.vue
Normal file
35
src/components/ui/menubar/Menubar.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MenubarRoot,
|
||||
type MenubarRootEmits,
|
||||
type MenubarRootProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarRootEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarRoot
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-10 items-center gap-x-1 rounded-md border bg-background p-1',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarRoot>
|
||||
</template>
|
||||
40
src/components/ui/menubar/MenubarCheckboxItem.vue
Normal file
40
src/components/ui/menubar/MenubarCheckboxItem.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import {
|
||||
MenubarCheckboxItem,
|
||||
type MenubarCheckboxItemEmits,
|
||||
type MenubarCheckboxItemProps,
|
||||
MenubarItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarCheckboxItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarCheckboxItem
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'relative flex 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">
|
||||
<MenubarItemIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</MenubarItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</MenubarCheckboxItem>
|
||||
</template>
|
||||
43
src/components/ui/menubar/MenubarContent.vue
Normal file
43
src/components/ui/menubar/MenubarContent.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MenubarContent,
|
||||
type MenubarContentProps,
|
||||
MenubarPortal,
|
||||
useForwardProps,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<MenubarContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
align: 'start',
|
||||
alignOffset: -4,
|
||||
sideOffset: 8,
|
||||
},
|
||||
)
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarPortal>
|
||||
<MenubarContent
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 min-w-48 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in 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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarContent>
|
||||
</MenubarPortal>
|
||||
</template>
|
||||
11
src/components/ui/menubar/MenubarGroup.vue
Normal file
11
src/components/ui/menubar/MenubarGroup.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { MenubarGroup, type MenubarGroupProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<MenubarGroupProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarGroup v-bind="props">
|
||||
<slot />
|
||||
</MenubarGroup>
|
||||
</template>
|
||||
35
src/components/ui/menubar/MenubarItem.vue
Normal file
35
src/components/ui/menubar/MenubarItem.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MenubarItem,
|
||||
type MenubarItemEmits,
|
||||
type MenubarItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const emits = defineEmits<MenubarItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarItem
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</MenubarItem>
|
||||
</template>
|
||||
13
src/components/ui/menubar/MenubarLabel.vue
Normal file
13
src/components/ui/menubar/MenubarLabel.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { MenubarLabel, type MenubarLabelProps } from 'radix-vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarLabel :class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)">
|
||||
<slot />
|
||||
</MenubarLabel>
|
||||
</template>
|
||||
11
src/components/ui/menubar/MenubarMenu.vue
Normal file
11
src/components/ui/menubar/MenubarMenu.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { MenubarMenu, type MenubarMenuProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<MenubarMenuProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarMenu v-bind="props">
|
||||
<slot />
|
||||
</MenubarMenu>
|
||||
</template>
|
||||
20
src/components/ui/menubar/MenubarRadioGroup.vue
Normal file
20
src/components/ui/menubar/MenubarRadioGroup.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MenubarRadioGroup,
|
||||
type MenubarRadioGroupEmits,
|
||||
type MenubarRadioGroupProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
|
||||
const props = defineProps<MenubarRadioGroupProps>()
|
||||
|
||||
const emits = defineEmits<MenubarRadioGroupEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarRadioGroup v-bind="forwarded">
|
||||
<slot />
|
||||
</MenubarRadioGroup>
|
||||
</template>
|
||||
40
src/components/ui/menubar/MenubarRadioItem.vue
Normal file
40
src/components/ui/menubar/MenubarRadioItem.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { Circle } from 'lucide-vue-next'
|
||||
import {
|
||||
MenubarItemIndicator,
|
||||
MenubarRadioItem,
|
||||
type MenubarRadioItemEmits,
|
||||
type MenubarRadioItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarRadioItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarRadioItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarRadioItem
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'relative flex 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">
|
||||
<MenubarItemIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</MenubarItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</MenubarRadioItem>
|
||||
</template>
|
||||
19
src/components/ui/menubar/MenubarSeparator.vue
Normal file
19
src/components/ui/menubar/MenubarSeparator.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { MenubarSeparator, type MenubarSeparatorProps, useForwardProps } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarSeparator :class=" cn('-mx-1 my-1 h-px bg-muted', props.class)" v-bind="forwardedProps" />
|
||||
</template>
|
||||
14
src/components/ui/menubar/MenubarShortcut.vue
Normal file
14
src/components/ui/menubar/MenubarShortcut.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>
|
||||
<span :class="cn('ml-auto text-xs tracking-widest text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
19
src/components/ui/menubar/MenubarSub.vue
Normal file
19
src/components/ui/menubar/MenubarSub.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { MenubarSub, type MenubarSubEmits, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
interface MenubarSubRootProps {
|
||||
defaultOpen?: boolean
|
||||
open?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<MenubarSubRootProps>()
|
||||
const emits = defineEmits<MenubarSubEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarSub v-bind="forwarded">
|
||||
<slot />
|
||||
</MenubarSub>
|
||||
</template>
|
||||
39
src/components/ui/menubar/MenubarSubContent.vue
Normal file
39
src/components/ui/menubar/MenubarSubContent.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MenubarPortal,
|
||||
MenubarSubContent,
|
||||
type MenubarSubContentEmits,
|
||||
type MenubarSubContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarSubContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const emits = defineEmits<MenubarSubContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarPortal>
|
||||
<MenubarSubContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground 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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarSubContent>
|
||||
</MenubarPortal>
|
||||
</template>
|
||||
30
src/components/ui/menubar/MenubarSubTrigger.vue
Normal file
30
src/components/ui/menubar/MenubarSubTrigger.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { MenubarSubTrigger, type MenubarSubTriggerProps, useForwardProps } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarSubTriggerProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarSubTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</MenubarSubTrigger>
|
||||
</template>
|
||||
29
src/components/ui/menubar/MenubarTrigger.vue
Normal file
29
src/components/ui/menubar/MenubarTrigger.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { MenubarTrigger, type MenubarTriggerProps, useForwardProps } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarTrigger>
|
||||
</template>
|
||||
15
src/components/ui/menubar/index.ts
Normal file
15
src/components/ui/menubar/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export { default as Menubar } from './Menubar.vue'
|
||||
export { default as MenubarCheckboxItem } from './MenubarCheckboxItem.vue'
|
||||
export { default as MenubarContent } from './MenubarContent.vue'
|
||||
export { default as MenubarGroup } from './MenubarGroup.vue'
|
||||
export { default as MenubarItem } from './MenubarItem.vue'
|
||||
export { default as MenubarLabel } from './MenubarLabel.vue'
|
||||
export { default as MenubarMenu } from './MenubarMenu.vue'
|
||||
export { default as MenubarRadioGroup } from './MenubarRadioGroup.vue'
|
||||
export { default as MenubarRadioItem } from './MenubarRadioItem.vue'
|
||||
export { default as MenubarSeparator } from './MenubarSeparator.vue'
|
||||
export { default as MenubarShortcut } from './MenubarShortcut.vue'
|
||||
export { default as MenubarSub } from './MenubarSub.vue'
|
||||
export { default as MenubarSubContent } from './MenubarSubContent.vue'
|
||||
export { default as MenubarSubTrigger } from './MenubarSubTrigger.vue'
|
||||
export { default as MenubarTrigger } from './MenubarTrigger.vue'
|
||||
26
src/components/ui/resizable/ResizableHandle.vue
Normal file
26
src/components/ui/resizable/ResizableHandle.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { GripVertical } from 'lucide-vue-next'
|
||||
import { SplitterResizeHandle, type SplitterResizeHandleEmits, type SplitterResizeHandleProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<SplitterResizeHandleProps & { class?: HTMLAttributes['class'], withHandle?: boolean }>()
|
||||
const emits = defineEmits<SplitterResizeHandleEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SplitterResizeHandle v-bind="forwarded" :class="cn('relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 [&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full [&[data-orientation=vertical]]:after:left-0 [&[data-orientation=vertical]]:after:h-1 [&[data-orientation=vertical]]:after:w-full [&[data-orientation=vertical]]:after:-translate-y-1/2 [&[data-orientation=vertical]]:after:translate-x-0 [&[data-orientation=vertical]>div]:rotate-90', props.class)">
|
||||
<template v-if="props.withHandle">
|
||||
<div class="z-10 h-4 w-3 flex items-center justify-center border rounded-sm bg-border">
|
||||
<GripVertical class="h-2.5 w-2.5" />
|
||||
</div>
|
||||
</template>
|
||||
</SplitterResizeHandle>
|
||||
</template>
|
||||
21
src/components/ui/resizable/ResizablePanelGroup.vue
Normal file
21
src/components/ui/resizable/ResizablePanelGroup.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { SplitterGroup, type SplitterGroupEmits, type SplitterGroupProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<SplitterGroupProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<SplitterGroupEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SplitterGroup v-bind="forwarded" :class="cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', props.class)">
|
||||
<slot />
|
||||
</SplitterGroup>
|
||||
</template>
|
||||
3
src/components/ui/resizable/index.ts
Normal file
3
src/components/ui/resizable/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as ResizableHandle } from './ResizableHandle.vue'
|
||||
export { default as ResizablePanelGroup } from './ResizablePanelGroup.vue'
|
||||
export { SplitterPanel as ResizablePanel } from 'radix-vue'
|
||||
@ -7,13 +7,16 @@ meta:
|
||||
</route>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { z } from 'zod'
|
||||
import { API_BASE_URL } from '~/CONSTANT'
|
||||
|
||||
const selectedMode = ref('观测的二阶多项式拟合')
|
||||
|
||||
const imageUrl = ref('')
|
||||
const hasWaveThisDay = ref(false)
|
||||
const imageResult = reactive<ImageResult>({
|
||||
result: 'idle',
|
||||
imageUrl: '',
|
||||
message: '请你选择一个模式和日期',
|
||||
})
|
||||
|
||||
const modes = [
|
||||
'观测的二阶多项式拟合',
|
||||
@ -21,16 +24,21 @@ const modes = [
|
||||
'径向风-纬向风矢量图',
|
||||
'温度-水平风矢量图',
|
||||
] as const
|
||||
const formSchema = shallowRef<z.ZodObject< any, any, any > | null>(null)
|
||||
const stagedDates = ref<string[]>([])
|
||||
|
||||
const selectedDate = ref('2022-01-01')
|
||||
const formSchema = shallowRef<z.ZodObject< any, any, any >>(z.object({
|
||||
selectedMode: z.enum(modes).describe('选择一个模式'),
|
||||
selectedDate: z.enum(['no date']).describe('选择一个日期'),
|
||||
}))
|
||||
|
||||
const form = useForm()
|
||||
|
||||
const stagedDates = ref<string[]>([])
|
||||
|
||||
onMounted(async () => {
|
||||
await fetch(`${API_BASE_URL}/balloon/metadata`).then(resp => resp.json()).then((data) => {
|
||||
stagedDates.value = data
|
||||
formSchema.value = z.object({
|
||||
selectedMode: z.enum(modes),
|
||||
selectedMode: z.enum(modes).describe('选择一个模式'),
|
||||
selectedDate: z.enum(data.map((d: string) => {
|
||||
const datePattern = /_\d{8}T\d{6}/
|
||||
if (!datePattern.test(d)) {
|
||||
@ -42,50 +50,50 @@ onMounted(async () => {
|
||||
}
|
||||
return capture[0]
|
||||
})),
|
||||
})
|
||||
}).describe('选择一个日期')
|
||||
form.setFieldValue('selectedDate', data[0])
|
||||
form.setFieldValue('selectedMode', modes[0])
|
||||
})
|
||||
})
|
||||
|
||||
async function get_image() {
|
||||
const resp = await fetch(`${API_BASE_URL}/balloon/render/single?mode=${encodeURIComponent(selectedMode.value)}&path=${encodeURIComponent(selectedDate.value)}`)
|
||||
async function get_image(
|
||||
selectedMode: string,
|
||||
selectedDate: string,
|
||||
) {
|
||||
const resp = await fetch(`${API_BASE_URL}/balloon/render/single?mode=${encodeURIComponent(selectedMode)}&path=${encodeURIComponent(selectedDate)}`)
|
||||
// check for MIME Type, check if is png
|
||||
const isPng = resp.headers.get('Content-Type') === 'image/png'
|
||||
if (!isPng) {
|
||||
hasWaveThisDay.value = false
|
||||
imageResult.result = 'error' as const
|
||||
imageResult.message = '这一天没有数据'
|
||||
return
|
||||
// return alert('No image available for this mode and date')
|
||||
}
|
||||
hasWaveThisDay.value = true
|
||||
imageResult.result = 'success' as const
|
||||
const blob = await resp.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
imageUrl.value = url
|
||||
imageResult.imageUrl = url
|
||||
}
|
||||
|
||||
watch([selectedMode, selectedDate], () => {
|
||||
get_image()
|
||||
})
|
||||
|
||||
function submit(value: Record<string, any>) {
|
||||
if (!(value.selectedMode && value.selectedDate)) {
|
||||
return
|
||||
}
|
||||
selectedMode.value = value.selectedMode
|
||||
// search for the date in stagedDates
|
||||
for (const d of stagedDates.value) {
|
||||
if (d.includes(value.selectedDate)) {
|
||||
selectedDate.value = d
|
||||
break
|
||||
}
|
||||
}
|
||||
function submit() {
|
||||
const value = form.values
|
||||
const selectedMode = value.selectedMode
|
||||
const selectedDate = stagedDates.value.find(d => d.includes(value.selectedDate))
|
||||
get_image(selectedMode, selectedDate!)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex="~ row items-begin justify-stretch gap-3 w-full">
|
||||
<ParamsCard :schema="formSchema" @submit="(e) => submit(e)" />
|
||||
|
||||
<ImageContainer :image-url="imageUrl" />
|
||||
</div>
|
||||
<DenseFramework :image-result="imageResult" @submit="submit">
|
||||
<AutoForm
|
||||
:form="form"
|
||||
:field-config="{
|
||||
selectedMode: {
|
||||
component: 'radio',
|
||||
},
|
||||
}"
|
||||
:schema="formSchema"
|
||||
/>
|
||||
</DenseFramework>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -7,12 +7,18 @@ meta:
|
||||
</route>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
||||
import { API_BASE_URL } from '~/CONSTANT'
|
||||
|
||||
const selectedMode = ref('w/f值统计结果')
|
||||
|
||||
const imageResult = reactive<ImageResult>({
|
||||
result: 'idle',
|
||||
imageUrl: '',
|
||||
message: '请你选择一个模式和日期',
|
||||
})
|
||||
|
||||
const isIllegal = ref(false)
|
||||
const imageUrl = ref('')
|
||||
|
||||
const startYear = ref(2017)
|
||||
const endYear = ref(2024)
|
||||
@ -41,7 +47,8 @@ async function refreshImage() {
|
||||
const resp = await fetch(url)
|
||||
const blob = await resp.blob()
|
||||
const u = URL.createObjectURL(blob)
|
||||
imageUrl.value = u
|
||||
imageResult.result = 'success'
|
||||
imageResult.imageUrl = u
|
||||
}
|
||||
|
||||
watch([selectedMode, startYear, endYear], () => {
|
||||
@ -55,69 +62,54 @@ watch([selectedMode, startYear, endYear], () => {
|
||||
</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>
|
||||
<DenseFramework :image-result="imageResult" @submit="refreshImage">
|
||||
<div flex="col gap-5 ~ justify-center">
|
||||
<div>
|
||||
<div v-if="!isIllegal && imageUrl">
|
||||
<Image :image-url="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>
|
||||
<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>
|
||||
</DenseFramework>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -4,6 +4,7 @@ import manba from '../../public/pack.png'
|
||||
|
||||
<template>
|
||||
<div flex="~ col items-center">
|
||||
<!-- <CoolBack /> -->
|
||||
<img :src="manba">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user