Compare commits
28 Commits
dev-cosmic
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 11666081f9 | |||
| 24e686dafa | |||
| 6aeb429b3c | |||
| 7b6e55e67a | |||
| 0909d5a32c | |||
| 6f72a1a611 | |||
| 70298d740b | |||
| 3b3294d39f | |||
| 27e35c3665 | |||
| 94746d9a85 | |||
| 1e27f4ab9c | |||
| 1d4892a076 | |||
| 4fc70895fa | |||
| 1092eb3706 | |||
| 2f77ce8500 | |||
| 044a4b56e2 | |||
| 6d7d6cde37 | |||
| 023258c9b9 | |||
| c2151b929a | |||
| a0569a41d0 | |||
| 9c53ec252c | |||
| de6a6ac0a5 | |||
| 949ebf420a | |||
| 69df9232e0 | |||
| 247d7e37db | |||
| b3eeef29a7 | |||
| 52deafb19f | |||
| f80f93a9b3 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ dist-ssr
|
|||||||
node_modules
|
node_modules
|
||||||
.idea/
|
.idea/
|
||||||
*.log
|
*.log
|
||||||
|
*.7z
|
||||||
|
|||||||
9
auto-imports.d.ts
vendored
9
auto-imports.d.ts
vendored
@ -8,7 +8,9 @@ export {}
|
|||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
|
const authCode: typeof import('./src/composables/fetch')['authCode']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
|
const baseFetch: typeof import('./src/composables/fetch')['baseFetch']
|
||||||
const computed: typeof import('vue')['computed']
|
const computed: typeof import('vue')['computed']
|
||||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
@ -32,12 +34,14 @@ declare global {
|
|||||||
const defineComponent: typeof import('vue')['defineComponent']
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
const defineLoader: typeof import('vue-router/auto')['defineLoader']
|
const defineLoader: typeof import('vue-router/auto')['defineLoader']
|
||||||
const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
|
const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
|
||||||
|
const doCheckOnline: typeof import('./src/composables/fetch')['doCheckOnline']
|
||||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
const h: typeof import('vue')['h']
|
const h: typeof import('vue')['h']
|
||||||
|
const hasConnection: typeof import('./src/composables/online')['hasConnection']
|
||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
const inject: typeof import('vue')['inject']
|
const inject: typeof import('vue')['inject']
|
||||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||||
@ -112,6 +116,7 @@ declare global {
|
|||||||
const unref: typeof import('vue')['unref']
|
const unref: typeof import('vue')['unref']
|
||||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||||
const until: typeof import('@vueuse/core')['until']
|
const until: typeof import('@vueuse/core')['until']
|
||||||
|
const useAPIOnline: typeof import('./src/composables/online')['useAPIOnline']
|
||||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||||
@ -129,6 +134,7 @@ declare global {
|
|||||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||||
const useAttrs: typeof import('vue')['useAttrs']
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useBackendOnline: typeof import('./src/composables/fetch')['useBackendOnline']
|
||||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||||
@ -307,7 +313,9 @@ declare module 'vue' {
|
|||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||||
|
readonly authCode: UnwrapRef<typeof import('./src/composables/fetch')['authCode']>
|
||||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||||
|
readonly baseFetch: UnwrapRef<typeof import('./src/composables/fetch')['baseFetch']>
|
||||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
||||||
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
||||||
@ -409,6 +417,7 @@ declare module 'vue' {
|
|||||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||||
|
readonly useAPIOnline: UnwrapRef<typeof import('./src/composables/online')['useAPIOnline']>
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
||||||
|
|||||||
20
components.d.ts
vendored
20
components.d.ts
vendored
@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
Alert: typeof import('./src/components/ui/alert/Alert.vue')['default']
|
Alert: typeof import('./src/components/ui/alert/Alert.vue')['default']
|
||||||
AlertDescription: typeof import('./src/components/ui/alert/AlertDescription.vue')['default']
|
AlertDescription: typeof import('./src/components/ui/alert/AlertDescription.vue')['default']
|
||||||
AlertTitle: typeof import('./src/components/ui/alert/AlertTitle.vue')['default']
|
AlertTitle: typeof import('./src/components/ui/alert/AlertTitle.vue')['default']
|
||||||
|
AuthBlock: typeof import('./src/components/AuthBlock.vue')['default']
|
||||||
AutoForm: typeof import('./src/components/ui/auto-form/AutoForm.vue')['default']
|
AutoForm: typeof import('./src/components/ui/auto-form/AutoForm.vue')['default']
|
||||||
AutoFormField: typeof import('./src/components/ui/auto-form/AutoFormField.vue')['default']
|
AutoFormField: typeof import('./src/components/ui/auto-form/AutoFormField.vue')['default']
|
||||||
AutoFormFieldArray: typeof import('./src/components/ui/auto-form/AutoFormFieldArray.vue')['default']
|
AutoFormFieldArray: typeof import('./src/components/ui/auto-form/AutoFormFieldArray.vue')['default']
|
||||||
@ -53,8 +54,17 @@ declare module 'vue' {
|
|||||||
Collapsible: typeof import('./src/components/ui/collapsible/Collapsible.vue')['default']
|
Collapsible: typeof import('./src/components/ui/collapsible/Collapsible.vue')['default']
|
||||||
CollapsibleContent: typeof import('./src/components/ui/collapsible/CollapsibleContent.vue')['default']
|
CollapsibleContent: typeof import('./src/components/ui/collapsible/CollapsibleContent.vue')['default']
|
||||||
CollapsibleTrigger: typeof import('./src/components/ui/collapsible/CollapsibleTrigger.vue')['default']
|
CollapsibleTrigger: typeof import('./src/components/ui/collapsible/CollapsibleTrigger.vue')['default']
|
||||||
CoolBack: typeof import('./src/components/CoolBack.vue')['default']
|
Day_cycle_power_wave_plot: typeof import('./src/components/dense/saber/day_cycle_power_wave_plot.vue')['default']
|
||||||
|
Day_fft_ifft_plot: typeof import('./src/components/dense/saber/day_fft_ifft_plot.vue')['default']
|
||||||
|
DefaultLayout: typeof import('./src/components/DefaultLayout.vue')['default']
|
||||||
DenseFramework: typeof import('./src/components/DenseFramework.vue')['default']
|
DenseFramework: typeof import('./src/components/DenseFramework.vue')['default']
|
||||||
|
Drawer: typeof import('./src/components/ui/drawer/Drawer.vue')['default']
|
||||||
|
DrawerContent: typeof import('./src/components/ui/drawer/DrawerContent.vue')['default']
|
||||||
|
DrawerDescription: typeof import('./src/components/ui/drawer/DrawerDescription.vue')['default']
|
||||||
|
DrawerFooter: typeof import('./src/components/ui/drawer/DrawerFooter.vue')['default']
|
||||||
|
DrawerHeader: typeof import('./src/components/ui/drawer/DrawerHeader.vue')['default']
|
||||||
|
DrawerOverlay: typeof import('./src/components/ui/drawer/DrawerOverlay.vue')['default']
|
||||||
|
DrawerTitle: typeof import('./src/components/ui/drawer/DrawerTitle.vue')['default']
|
||||||
DropdownMenu: typeof import('./src/components/ui/dropdown-menu/DropdownMenu.vue')['default']
|
DropdownMenu: typeof import('./src/components/ui/dropdown-menu/DropdownMenu.vue')['default']
|
||||||
DropdownMenuCheckboxItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue')['default']
|
DropdownMenuCheckboxItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue')['default']
|
||||||
DropdownMenuContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuContent.vue')['default']
|
DropdownMenuContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuContent.vue')['default']
|
||||||
@ -94,6 +104,7 @@ declare module 'vue' {
|
|||||||
MenubarSubContent: typeof import('./src/components/ui/menubar/MenubarSubContent.vue')['default']
|
MenubarSubContent: typeof import('./src/components/ui/menubar/MenubarSubContent.vue')['default']
|
||||||
MenubarSubTrigger: typeof import('./src/components/ui/menubar/MenubarSubTrigger.vue')['default']
|
MenubarSubTrigger: typeof import('./src/components/ui/menubar/MenubarSubTrigger.vue')['default']
|
||||||
MenubarTrigger: typeof import('./src/components/ui/menubar/MenubarTrigger.vue')['default']
|
MenubarTrigger: typeof import('./src/components/ui/menubar/MenubarTrigger.vue')['default']
|
||||||
|
Month_year_power_wave_plot: typeof import('./src/components/dense/saber/month_year_power_wave_plot.vue')['default']
|
||||||
NavigationMenu: typeof import('./src/components/ui/navigation-menu/NavigationMenu.vue')['default']
|
NavigationMenu: typeof import('./src/components/ui/navigation-menu/NavigationMenu.vue')['default']
|
||||||
NavigationMenuContent: typeof import('./src/components/ui/navigation-menu/NavigationMenuContent.vue')['default']
|
NavigationMenuContent: typeof import('./src/components/ui/navigation-menu/NavigationMenuContent.vue')['default']
|
||||||
NavigationMenuIndicator: typeof import('./src/components/ui/navigation-menu/NavigationMenuIndicator.vue')['default']
|
NavigationMenuIndicator: typeof import('./src/components/ui/navigation-menu/NavigationMenuIndicator.vue')['default']
|
||||||
@ -108,11 +119,12 @@ declare module 'vue' {
|
|||||||
NumberFieldDecrement: typeof import('./src/components/ui/number-field/NumberFieldDecrement.vue')['default']
|
NumberFieldDecrement: typeof import('./src/components/ui/number-field/NumberFieldDecrement.vue')['default']
|
||||||
NumberFieldIncrement: typeof import('./src/components/ui/number-field/NumberFieldIncrement.vue')['default']
|
NumberFieldIncrement: typeof import('./src/components/ui/number-field/NumberFieldIncrement.vue')['default']
|
||||||
NumberFieldInput: typeof import('./src/components/ui/number-field/NumberFieldInput.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']
|
ParamsCard: typeof import('./src/components/ParamsCard.vue')['default']
|
||||||
|
Plot_wave_fitting: typeof import('./src/components/dense/saber/plot_wave_fitting.vue')['default']
|
||||||
Popover: typeof import('./src/components/ui/popover/Popover.vue')['default']
|
Popover: typeof import('./src/components/ui/popover/Popover.vue')['default']
|
||||||
PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default']
|
PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default']
|
||||||
PopoverTrigger: typeof import('./src/components/ui/popover/PopoverTrigger.vue')['default']
|
PopoverTrigger: typeof import('./src/components/ui/popover/PopoverTrigger.vue')['default']
|
||||||
|
RadarSingle: typeof import('./src/components/dense/RadarSingle.vue')['default']
|
||||||
RadioGroup: typeof import('./src/components/ui/radio-group/RadioGroup.vue')['default']
|
RadioGroup: typeof import('./src/components/ui/radio-group/RadioGroup.vue')['default']
|
||||||
RadioGroupItem: typeof import('./src/components/ui/radio-group/RadioGroupItem.vue')['default']
|
RadioGroupItem: typeof import('./src/components/ui/radio-group/RadioGroupItem.vue')['default']
|
||||||
ResizableHandle: typeof import('./src/components/ui/resizable/ResizableHandle.vue')['default']
|
ResizableHandle: typeof import('./src/components/ui/resizable/ResizableHandle.vue')['default']
|
||||||
@ -169,14 +181,10 @@ declare module 'vue' {
|
|||||||
TabsContent: typeof import('./src/components/ui/tabs/TabsContent.vue')['default']
|
TabsContent: typeof import('./src/components/ui/tabs/TabsContent.vue')['default']
|
||||||
TabsList: typeof import('./src/components/ui/tabs/TabsList.vue')['default']
|
TabsList: typeof import('./src/components/ui/tabs/TabsList.vue')['default']
|
||||||
TabsTrigger: typeof import('./src/components/ui/tabs/TabsTrigger.vue')['default']
|
TabsTrigger: typeof import('./src/components/ui/tabs/TabsTrigger.vue')['default']
|
||||||
TestHeader: typeof import('./src/components/TestHeader.vue')['default']
|
|
||||||
Textarea: typeof import('./src/components/ui/textarea/Textarea.vue')['default']
|
Textarea: typeof import('./src/components/ui/textarea/Textarea.vue')['default']
|
||||||
TheFooter: typeof import('./src/components/TheFooter.vue')['default']
|
|
||||||
TheHeader: typeof import('./src/components/TheHeader.vue')['default']
|
|
||||||
Tooltip: typeof import('./src/components/ui/tooltip/Tooltip.vue')['default']
|
Tooltip: typeof import('./src/components/ui/tooltip/Tooltip.vue')['default']
|
||||||
TooltipContent: typeof import('./src/components/ui/tooltip/TooltipContent.vue')['default']
|
TooltipContent: typeof import('./src/components/ui/tooltip/TooltipContent.vue')['default']
|
||||||
TooltipProvider: typeof import('./src/components/ui/tooltip/TooltipProvider.vue')['default']
|
TooltipProvider: typeof import('./src/components/ui/tooltip/TooltipProvider.vue')['default']
|
||||||
TooltipTrigger: typeof import('./src/components/ui/tooltip/TooltipTrigger.vue')['default']
|
TooltipTrigger: typeof import('./src/components/ui/tooltip/TooltipTrigger.vue')['default']
|
||||||
TopHeader: typeof import('./src/components/TopHeader.vue')['default']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<meta name="description" content="Opinionated Vite Starter Template" />
|
<meta name="description" content="Opinionated Vite Starter Template" />
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans dark:text-white dark:bg-hex-121212">
|
<body class="font-sans dark:text-white dark:bg-hex-121212">
|
||||||
<div id="app"></div>
|
<div id="app" class="w-full h-full"></div>
|
||||||
<noscript>
|
<noscript>
|
||||||
<div>Please enable JavaScript to use this application.</div>
|
<div>Please enable JavaScript to use this application.</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"packageManager": "pnpm@9.14.4",
|
"packageManager": "pnpm@9.14.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"dev": "vite --port 3333 --open",
|
"dev": "vite --port 10514 --host",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"typecheck": "vue-tsc --noEmit",
|
"typecheck": "vue-tsc --noEmit",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
@ -25,6 +25,7 @@
|
|||||||
"shadcn-vue": "^0.11.3",
|
"shadcn-vue": "^0.11.3",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vaul-vue": "^0.2.0",
|
||||||
"vee-validate": "^4.15.0",
|
"vee-validate": "^4.15.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -48,6 +48,9 @@ importers:
|
|||||||
tailwindcss-animate:
|
tailwindcss-animate:
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7
|
version: 1.0.7
|
||||||
|
vaul-vue:
|
||||||
|
specifier: ^0.2.0
|
||||||
|
version: 0.2.0(radix-vue@1.9.11(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))
|
||||||
vee-validate:
|
vee-validate:
|
||||||
specifier: ^4.15.0
|
specifier: ^4.15.0
|
||||||
version: 4.15.0(vue@3.5.13(typescript@5.6.3))
|
version: 4.15.0(vue@3.5.13(typescript@5.6.3))
|
||||||
@ -3899,6 +3902,12 @@ packages:
|
|||||||
validate-npm-package-license@3.0.4:
|
validate-npm-package-license@3.0.4:
|
||||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||||
|
|
||||||
|
vaul-vue@0.2.0:
|
||||||
|
resolution: {integrity: sha512-YV0zqxc8NiVzr1z/Awwbaty0UDDchxj5BfhFbLiYu+Uz0rCfSaDK2zwmuXZvejBJKLGbWw9I5GLHJRse14lQew==}
|
||||||
|
peerDependencies:
|
||||||
|
radix-vue: ^1.4.0
|
||||||
|
vue: ^3.3.0
|
||||||
|
|
||||||
vee-validate@4.15.0:
|
vee-validate@4.15.0:
|
||||||
resolution: {integrity: sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==}
|
resolution: {integrity: sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -8610,6 +8619,14 @@ snapshots:
|
|||||||
spdx-correct: 3.2.0
|
spdx-correct: 3.2.0
|
||||||
spdx-expression-parse: 3.0.1
|
spdx-expression-parse: 3.0.1
|
||||||
|
|
||||||
|
vaul-vue@0.2.0(radix-vue@1.9.11(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3)):
|
||||||
|
dependencies:
|
||||||
|
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.6.3))
|
||||||
|
radix-vue: 1.9.11(vue@3.5.13(typescript@5.6.3))
|
||||||
|
vue: 3.5.13(typescript@5.6.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@vue/composition-api'
|
||||||
|
|
||||||
vee-validate@4.15.0(vue@3.5.13(typescript@5.6.3)):
|
vee-validate@4.15.0(vue@3.5.13(typescript@5.6.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-api': 7.7.0
|
'@vue/devtools-api': 7.7.0
|
||||||
|
|||||||
254
src/App.vue
254
src/App.vue
@ -1,242 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from '@iconify/vue'
|
// import { useBackendOnline } from './composables'
|
||||||
|
import { useAPIOnline } from './composables/online'
|
||||||
|
|
||||||
import { ChevronRight, ChevronsUpDown } from 'lucide-vue-next'
|
const online = useAPIOnline()
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '~/components/ui/breadcrumb'
|
|
||||||
|
|
||||||
import {
|
onMounted(() => {
|
||||||
Collapsible,
|
online.connect()
|
||||||
CollapsibleContent,
|
})
|
||||||
CollapsibleTrigger,
|
onUnmounted(() => {
|
||||||
} from '~/components/ui/collapsible'
|
online.disconnect()
|
||||||
import { Separator } from '~/components/ui/separator'
|
})
|
||||||
|
|
||||||
import {
|
|
||||||
Sidebar,
|
|
||||||
SidebarContent,
|
|
||||||
SidebarFooter,
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupLabel,
|
|
||||||
SidebarInset,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
SidebarMenuSub,
|
|
||||||
SidebarMenuSubButton,
|
|
||||||
SidebarMenuSubItem,
|
|
||||||
SidebarProvider,
|
|
||||||
SidebarRail,
|
|
||||||
} from '~/components/ui/sidebar'
|
|
||||||
|
|
||||||
// This is sample data.
|
|
||||||
const data = {
|
|
||||||
user: {
|
|
||||||
name: 'shadcn',
|
|
||||||
email: 'm@example.com',
|
|
||||||
avatar: '/avatars/shadcn.jpg',
|
|
||||||
},
|
|
||||||
navMain: [
|
|
||||||
{
|
|
||||||
title: '流星雷达',
|
|
||||||
url: '#',
|
|
||||||
icon: 'ri-radar-fill',
|
|
||||||
isActive: true,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: '潮汐波强度',
|
|
||||||
url: '/radar/v1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '潮汐波时空变化',
|
|
||||||
url: '/radar/v2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Saber',
|
|
||||||
url: '#',
|
|
||||||
icon: 'game-icons:cracked-saber',
|
|
||||||
isActive: true,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: '波动拟合图',
|
|
||||||
url: '/saber/plot_wave_fitting',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '日数据傅里叶变换分析',
|
|
||||||
url: '/saber/day_fft_ifft_plot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '日周期波动能量分析',
|
|
||||||
url: '/saber/day_cycle_power_wave_plot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '月度波动能量分析',
|
|
||||||
url: '/saber/month_power_wave_plot',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '探空气球',
|
|
||||||
url: '#',
|
|
||||||
icon: 'bxs:balloon',
|
|
||||||
isActive: true,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: '重力波单次',
|
|
||||||
url: '/balloon/single',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '重力波统计',
|
|
||||||
url: '/balloon/year',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
关于: [
|
|
||||||
{
|
|
||||||
name: 'Design Engineering',
|
|
||||||
url: '#',
|
|
||||||
icon: 'Frame',
|
|
||||||
},
|
|
||||||
|
|
||||||
],
|
|
||||||
} as const
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div h-full w-full>
|
||||||
<div>
|
<div v-if="!online.isOnline.value" class="absolute z-1000000 h-full w-full bg-gray/4 backdrop-blur-md">
|
||||||
<!-- <TopHeader /> -->
|
<div class="h-full w-full flex flex-col items-center justify-center gap-3">
|
||||||
</div>
|
<h1 class="flex flex-row items-center gap-3 text-3xl">
|
||||||
<div>
|
<div class="i-tabler-network" />
|
||||||
<SidebarProvider>
|
后端服务不在线
|
||||||
<Sidebar collapsible="icon">
|
</h1>
|
||||||
<SidebarHeader>
|
<div>
|
||||||
<Alert>
|
请检查后端是否运行, 或者服务器是否在维护
|
||||||
<Terminal class="h-4 w-4" />
|
</div>
|
||||||
<AlertTitle>服务状态正常</AlertTitle>
|
</div>
|
||||||
</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"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</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>
|
||||||
|
<DefaultLayout />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export const API_BASE_URL = 'http://localhost:5000'
|
// export const API_BASE_URL = 'http://localhost:5000'
|
||||||
|
export const API_BASE_URL = `/api`
|
||||||
// export const API_BASE_URL = 'https://gca-api.dustella.net:8443'
|
// export const API_BASE_URL = 'https://gca-api.dustella.net:8443'
|
||||||
|
|||||||
BIN
src/assets/map.avif
Normal file
BIN
src/assets/map.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
80
src/components/AuthBlock.vue
Normal file
80
src/components/AuthBlock.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '~/components/ui/button'
|
||||||
|
import { Input } from '~/components/ui/input'
|
||||||
|
import { Label } from '~/components/ui/label'
|
||||||
|
import { authCode } from '~/composables'
|
||||||
|
import manba from '../../public/pack.png'
|
||||||
|
|
||||||
|
const code = ref('')
|
||||||
|
const router = useRouter()
|
||||||
|
const remember = ref(false)
|
||||||
|
|
||||||
|
function auth() {
|
||||||
|
authCode.value = code.value
|
||||||
|
const resp = baseFetch(`/ping`, {
|
||||||
|
})
|
||||||
|
if (!resp.error.value) {
|
||||||
|
if (remember.value) {
|
||||||
|
localStorage.setItem('authCode', code.value)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sessionStorage.setItem('authCode', code.value)
|
||||||
|
}
|
||||||
|
router.push('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
alert('认证失败')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full lg:grid lg:grid-cols-2 lg:min-h-[600px] xl:min-h-[800px]">
|
||||||
|
<div class="hidden p-30 lg:block">
|
||||||
|
<img
|
||||||
|
:src="manba"
|
||||||
|
alt="Image"
|
||||||
|
class="h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center bg-muted py-12">
|
||||||
|
<div class="grid mx-auto w-[350px] gap-6">
|
||||||
|
<div class="grid gap-2 text-center">
|
||||||
|
<h1 class="text-3xl font-bold">
|
||||||
|
系统认证
|
||||||
|
</h1>
|
||||||
|
<p class="text-balance text-muted-foreground">
|
||||||
|
本系统仅供内部使用
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Label for="password">口令</Label>
|
||||||
|
</div>
|
||||||
|
<Input id="password" v-model="code" type="password" required />
|
||||||
|
</div>
|
||||||
|
<div flex="~ items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id="remember" :checked="remember" @update:checked="(state) => {
|
||||||
|
remember = state
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<Label for="remember" class="flex items-center">
|
||||||
|
记住我
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" class="w-full" @click.prevent="auth">
|
||||||
|
认证
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 text-center text-sm">
|
||||||
|
丢失密码?请前往后端重置
|
||||||
|
<!-- <a href="#" class="underline">
|
||||||
|
Sign up
|
||||||
|
</a> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
236
src/components/DefaultLayout.vue
Normal file
236
src/components/DefaultLayout.vue
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
|
||||||
|
import { ChevronRight, ChevronsUpDown } from 'lucide-vue-next'
|
||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from '~/components/ui/breadcrumb'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from '~/components/ui/collapsible'
|
||||||
|
import { Separator } from '~/components/ui/separator'
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarInset,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
|
SidebarProvider,
|
||||||
|
SidebarRail,
|
||||||
|
} from '~/components/ui/sidebar'
|
||||||
|
|
||||||
|
import { authCode } from '../composables'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
localStorage.clear()
|
||||||
|
sessionStorage.clear()
|
||||||
|
authCode.value = ''
|
||||||
|
router.push('/auth')
|
||||||
|
}
|
||||||
|
const IconMapping = {
|
||||||
|
探空气球: 'bxs:balloon',
|
||||||
|
SABER: 'game-icons:cracked-saber',
|
||||||
|
流星雷达: 'ri-radar-fill',
|
||||||
|
TIDI: 'mdi:telescope',
|
||||||
|
COSMIC: 'mdi:telescope',
|
||||||
|
} as Record<string, string>
|
||||||
|
|
||||||
|
function generateNavigationFromRoutes() {
|
||||||
|
const allRoutesWithMeta = router.getRoutes().filter(
|
||||||
|
(r) => {
|
||||||
|
// check if r.meta is {}
|
||||||
|
return Object.keys(r.meta).length > 0 && !!r.meta.group
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// @ts-expect-error i expect grpup
|
||||||
|
const groupedItems = Object.groupBy(allRoutesWithMeta, r => r.meta.group)
|
||||||
|
return Object.entries(groupedItems).map(([group, routes]) => {
|
||||||
|
return {
|
||||||
|
title: group,
|
||||||
|
url: '#',
|
||||||
|
icon: IconMapping[group] ?? 'ri-radar-fill',
|
||||||
|
isActive: true,
|
||||||
|
items: routes!.map((r) => {
|
||||||
|
return {
|
||||||
|
title: r.meta.item_name as string,
|
||||||
|
url: r.path,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
user: {
|
||||||
|
name: 'shadcn',
|
||||||
|
email: 'm@example.com',
|
||||||
|
avatar: '/avatars/shadcn.jpg',
|
||||||
|
},
|
||||||
|
navMain: generateNavigationFromRoutes(),
|
||||||
|
|
||||||
|
关于: [
|
||||||
|
{
|
||||||
|
name: 'Design Engineering',
|
||||||
|
url: '#',
|
||||||
|
icon: 'Frame',
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
} as const
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!authCode.value) {
|
||||||
|
router.push('/auth')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="$route.path === '/auth'">
|
||||||
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<SidebarProvider>
|
||||||
|
<Sidebar collapsible="icon">
|
||||||
|
<SidebarHeader>
|
||||||
|
<Alert>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<span> </span>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<div @click="logout">
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Icon icon="heroicons-solid:logout" class="h-4 w-4" />
|
||||||
|
退出系统
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
@ -6,11 +6,69 @@ import {
|
|||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from '~/components/ui/resizable'
|
} from '~/components/ui/resizable'
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
imageResult: ImageResult
|
imageQuery: string
|
||||||
|
extraErrorHandle?: (resp: Response) => {
|
||||||
|
type: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
extraResponseHandle?: (resp: Response) => Promise<ImageResult>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits(['submit'])
|
defineEmits(['submit'])
|
||||||
|
const imageResult = reactive<ImageResult>({
|
||||||
|
result: 'idle',
|
||||||
|
resourceId: '',
|
||||||
|
message: '请你选择一个模式和日期',
|
||||||
|
})
|
||||||
|
|
||||||
|
const urlRef = computed(() => {
|
||||||
|
return props.imageQuery
|
||||||
|
})
|
||||||
|
|
||||||
|
const { onFetchResponse, isFetching, execute } = baseFetch(
|
||||||
|
urlRef,
|
||||||
|
{ immediate: false, updateDataOnError: true, onFetchError: async ({ error, data, response }) => {
|
||||||
|
// same as afterFetch
|
||||||
|
imageResult.result = 'error'
|
||||||
|
const rawErrorMsg = (await response?.json()).error.message as string ?? '未知错误'
|
||||||
|
if (rawErrorMsg.includes('Improper input')) {
|
||||||
|
imageResult.message = '数据异常,无法拟合. 请使用其他时间段再试'
|
||||||
|
return { error, data }
|
||||||
|
}
|
||||||
|
imageResult.message = rawErrorMsg ?? '未知错误'
|
||||||
|
return { error, data }
|
||||||
|
} },
|
||||||
|
)
|
||||||
|
watch(isFetching, (fetching) => {
|
||||||
|
if (fetching) {
|
||||||
|
imageResult.result = 'pending'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onFetchResponse(async (resp) => {
|
||||||
|
if (props.extraResponseHandle) {
|
||||||
|
const newResult = await props.extraResponseHandle(resp)
|
||||||
|
imageResult.result = newResult.result
|
||||||
|
imageResult.resourceId = newResult.resourceId
|
||||||
|
imageResult.message = newResult.message
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const blob = await resp.blob()
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
imageResult.result = 'success'
|
||||||
|
imageResult.resourceId = url
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function download() {
|
||||||
|
if (imageResult.result === 'success') {
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = imageResult.resourceId
|
||||||
|
a.download = 'image.png'
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -22,14 +80,20 @@ defineEmits(['submit'])
|
|||||||
>
|
>
|
||||||
<ResizablePanel id="demo-panel-1" :default-size="200" py-5>
|
<ResizablePanel id="demo-panel-1" :default-size="200" py-5>
|
||||||
<ParamsCard
|
<ParamsCard
|
||||||
@submit="$emit('submit')"
|
@submit="() => {
|
||||||
|
$emit('submit')
|
||||||
|
execute()
|
||||||
|
}"
|
||||||
|
@download="download"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</ParamsCard>
|
</ParamsCard>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle id="demo-handle-1" />
|
<ResizableHandle id="demo-handle-1" />
|
||||||
<ResizablePanel id="demo-panel-2" :default-size="500">
|
<ResizablePanel id="demo-panel-2" :default-size="350">
|
||||||
<ImageContainer :image-result="imageResult" />
|
<ImageContainer v-slot="metadata" :image-result="imageResult">
|
||||||
|
<slot name="extra-meta" :metadata="metadata.metadata" />
|
||||||
|
</ImageContainer>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import { Icon } from '@iconify/vue/dist/iconify.js'
|
|||||||
|
|
||||||
export interface ImageResult {
|
export interface ImageResult {
|
||||||
result: 'success' | 'error' | 'pending' | 'idle'
|
result: 'success' | 'error' | 'pending' | 'idle'
|
||||||
imageUrl: string
|
resourceId: string
|
||||||
message?: string
|
message?: string
|
||||||
|
extraMetadata?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -17,18 +18,14 @@ defineProps<{
|
|||||||
<Badge variant="outline" class="absolute left-3 top-3">
|
<Badge variant="outline" class="absolute left-3 top-3">
|
||||||
图片输出
|
图片输出
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<slot :metadata="imageResult.extraMetadata" />
|
||||||
<div v-if="imageResult.result === 'pending'" class="flex flex-1 items-center justify-center text-xl">
|
<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" />
|
<Icon icon="akar-icons:loading" class="mr-2 animate-spin" />
|
||||||
<span>正在加载图片</span>
|
<span>正在加载图片</span>
|
||||||
</div>
|
</div>
|
||||||
<Image v-else-if="imageResult.result === 'success'" class="flex flex-1 items-center justify-center text-xl" :image-url="imageResult.imageUrl" />
|
<Image v-else-if="imageResult.result === 'success'" class="flex flex-1 items-center justify-center text-xl" :image-url="imageResult.resourceId" />
|
||||||
<div v-else class="flex flex-1 items-center justify-center text-xl">
|
<div v-else class="flex flex-1 items-center justify-center text-xl">
|
||||||
{{ imageResult.message }}
|
{{ imageResult.message }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button type="submit" size="sm" class="ml-auto gap-1.5">
|
|
||||||
下载图片
|
|
||||||
<CornerDownLeft class="size-3.5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
<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,32 +1,34 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineEmits(['submit'])
|
defineEmits(['submit', 'download'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Drawer>
|
<!-- <Drawer>
|
||||||
<DrawerTrigger as-child class="min-w-[20rem]">
|
<DrawerTrigger as-child class="min-w-[20rem]">
|
||||||
<Button variant="ghost" size="icon" class="md:hidden">
|
<Button variant="ghost" size="icon" class="md:hidden">
|
||||||
<Settings class="size-4" />
|
|
||||||
<span class="sr-only">参数设置</span>
|
<span class="sr-only">参数设置</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerTrigger>
|
</DrawerTrigger>
|
||||||
<DrawerContent class="max-h-[80vh]">
|
<DrawerContent class="max-h-[80vh]">
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
<DrawerTitle />
|
<DrawerTitle />
|
||||||
</DrawerHeader>
|
</DrawerHeader> -->
|
||||||
<form class="grid min-w-[20rem] w-full items-start gap-6 overflow-auto p-4 pt-0">
|
<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">
|
<fieldset class="grid gap-6 border rounded-lg p-4">
|
||||||
<legend class="px-1 text-sm font-medium -ml-1">
|
<legend class="px-1 text-sm font-medium -ml-1">
|
||||||
参数设置
|
参数设置
|
||||||
</legend>
|
</legend>
|
||||||
<div class="grid gap-3">
|
<div class="grid gap-3">
|
||||||
<slot />
|
<slot />
|
||||||
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('submit')">
|
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('submit')">
|
||||||
绘制
|
绘制
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
<Button type="submit" class="mt-5 w-full" @click.prevent="$emit('download')">
|
||||||
</fieldset>
|
下载
|
||||||
</form>
|
</Button>
|
||||||
</DrawerContent>
|
</div>
|
||||||
</Drawer>
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<!-- </DrawerContent>
|
||||||
|
</Drawer> -->
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,150 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
NavigationMenuContent,
|
|
||||||
NavigationMenuIndicator,
|
|
||||||
NavigationMenuItem,
|
|
||||||
NavigationMenuLink,
|
|
||||||
NavigationMenuList,
|
|
||||||
NavigationMenuRoot,
|
|
||||||
NavigationMenuTrigger,
|
|
||||||
NavigationMenuViewport,
|
|
||||||
} from 'radix-vue'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import NavigationMenuListItem from './NavigationMenuListItem.vue'
|
|
||||||
|
|
||||||
const currentTrigger = ref('')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NavigationMenuRoot
|
|
||||||
v-model="currentTrigger"
|
|
||||||
class="relative z-[1] mx-auto flex justify-center"
|
|
||||||
>
|
|
||||||
<NavigationMenuList class="center shadow-blackA7 m-0 flex list-none rounded-[6px] bg-white p-1 shadow-[0_2px_10px]">
|
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuTrigger
|
|
||||||
class="group text-grass11 flex select-none items-center justify-between gap-[2px] rounded-[4px] px-3 py-2 text-[15px] font-medium leading-none outline-none hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-green7"
|
|
||||||
>
|
|
||||||
Learn
|
|
||||||
<Icon
|
|
||||||
icon="radix-icons:caret-down"
|
|
||||||
class="text-green10 relative top-[1px] transition-transform duration-[250ms] ease-in group-data-[state=open]:-rotate-180"
|
|
||||||
/>
|
|
||||||
</NavigationMenuTrigger>
|
|
||||||
<NavigationMenuContent
|
|
||||||
class="data-[motion=from-start]:animate-enterFromLeft data-[motion=from-end]:animate-enterFromRight data-[motion=to-start]:animate-exitToLeft data-[motion=to-end]:animate-exitToRight absolute left-0 top-0 w-full sm:w-auto"
|
|
||||||
>
|
|
||||||
<ul class="one grid m-0 list-none gap-x-[10px] p-[22px] sm:grid-cols-[0.75fr_1fr] sm:w-[500px]">
|
|
||||||
<li class="grid row-span-3">
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<a
|
|
||||||
class="h-full w-full flex flex-col select-none justify-end rounded-[6px] from-green9 to-teal9 bg-gradient-to-b p-[25px] no-underline outline-none focus:shadow-[0_0_0_2px] focus:shadow-green7"
|
|
||||||
href="/"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="w-16"
|
|
||||||
src="https://www.radix-vue.com/logo.svg"
|
|
||||||
>
|
|
||||||
<div class="mb-[7px] mt-4 text-[18px] text-white font-medium leading-[1.2]">Radix Primitives</div>
|
|
||||||
<p class="text-mauve4 text-[14px] leading-[1.3]">Unstyled, accessible components for Vue.</p>
|
|
||||||
</a>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<NavigationMenuListItem
|
|
||||||
href="https://stitches.dev/"
|
|
||||||
title="Stitches"
|
|
||||||
>
|
|
||||||
CSS-in-JS with best-in-class developer experience.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
href="/colors"
|
|
||||||
title="Colors"
|
|
||||||
>
|
|
||||||
Beautiful, thought-out palettes with auto dark mode.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
href="https://icons.radix-ui.com/"
|
|
||||||
title="Icons"
|
|
||||||
>
|
|
||||||
A crisp set of 15x15 icons, balanced and consistent.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
</ul>
|
|
||||||
</NavigationMenuContent>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
|
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuTrigger
|
|
||||||
class="text-grass11 group flex select-none items-center justify-between gap-[2px] rounded-[4px] px-3 py-2 text-[15px] font-medium leading-none outline-none hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-green7"
|
|
||||||
>
|
|
||||||
Overview
|
|
||||||
<Icon
|
|
||||||
icon="radix-icons:caret-down"
|
|
||||||
class="text-green10 relative top-[1px] transition-transform duration-[250ms] ease-in group-data-[state=open]:-rotate-180"
|
|
||||||
/>
|
|
||||||
</NavigationMenuTrigger>
|
|
||||||
<NavigationMenuContent class="data-[motion=from-start]:animate-enterFromLeft data-[motion=from-end]:animate-enterFromRight data-[motion=to-start]:animate-exitToLeft data-[motion=to-end]:animate-exitToRight absolute left-0 top-0 w-full sm:w-auto">
|
|
||||||
<ul class="grid m-0 list-none gap-x-[10px] p-[22px] sm:grid-flow-col sm:grid-rows-3 sm:w-[600px]">
|
|
||||||
<NavigationMenuListItem
|
|
||||||
title="Introduction"
|
|
||||||
href="/docs/primitives/overview/introduction"
|
|
||||||
>
|
|
||||||
Build high-quality, accessible design systems and web apps.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
title="Getting started"
|
|
||||||
href="/docs/primitives/overview/getting-started"
|
|
||||||
>
|
|
||||||
A quick tutorial to get you up and running with Radix Primitives.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
title="Styling"
|
|
||||||
href="/docs/primitives/guides/styling"
|
|
||||||
>
|
|
||||||
Unstyled and compatible with any styling solution.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
title="Animation"
|
|
||||||
href="/docs/primitives/guides/animation"
|
|
||||||
>
|
|
||||||
Use CSS keyframes or any animation library of your choice.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
title="Accessibility"
|
|
||||||
href="/docs/primitives/overview/accessibility"
|
|
||||||
>
|
|
||||||
Tested in a range of browsers and assistive technologies.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
<NavigationMenuListItem
|
|
||||||
title="Releases"
|
|
||||||
href="/docs/primitives/overview/releases"
|
|
||||||
>
|
|
||||||
Radix Primitives releases and their changelogs.
|
|
||||||
</NavigationMenuListItem>
|
|
||||||
</ul>
|
|
||||||
</NavigationMenuContent>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
|
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuLink
|
|
||||||
class="text-grass11 block select-none rounded-[4px] px-3 py-2 text-[15px] font-medium leading-none no-underline outline-none hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-green7"
|
|
||||||
href="https://github.com/unovue/radix-vue"
|
|
||||||
>
|
|
||||||
Github
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
|
|
||||||
<NavigationMenuIndicator
|
|
||||||
class="transition-[all,transform_250ms_ease] data-[state=visible]:animate-fadeIn data-[state=hidden]:animate-fadeOut top-full z-[1] h-[10px] flex items-end justify-center overflow-hidden duration-200 data-[state=hidden]:opacity-0"
|
|
||||||
>
|
|
||||||
<div class="relative top-[70%] h-[10px] w-[10px] rotate-[45deg] rounded-tl-[2px] bg-white" />
|
|
||||||
</NavigationMenuIndicator>
|
|
||||||
</NavigationMenuList>
|
|
||||||
|
|
||||||
<div class="absolute left-0 top-full w-full flex perspective-[2000px] justify-center">
|
|
||||||
<NavigationMenuViewport
|
|
||||||
class="transition-[width,_height] data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative mt-[10px] h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[10px] bg-white duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</NavigationMenuRoot>
|
|
||||||
</template>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div />
|
|
||||||
</template>
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
NavigationMenu,
|
|
||||||
NavigationMenuContent,
|
|
||||||
NavigationMenuItem,
|
|
||||||
NavigationMenuLink,
|
|
||||||
NavigationMenuList,
|
|
||||||
NavigationMenuTrigger,
|
|
||||||
navigationMenuTriggerStyle,
|
|
||||||
} from './ui/navigation-menu'
|
|
||||||
|
|
||||||
const header_data: Record<string, { title: string, href: string }[]> = {
|
|
||||||
探空气球: [{
|
|
||||||
title: '重力波单次',
|
|
||||||
href: '/balloon/single',
|
|
||||||
}, {
|
|
||||||
title: '重力波统计',
|
|
||||||
href: '/balloon/year',
|
|
||||||
}],
|
|
||||||
流星雷达: [{
|
|
||||||
title: '潮汐波强度',
|
|
||||||
href: '/radar/v1',
|
|
||||||
}, {
|
|
||||||
title: '潮汐波时空变化',
|
|
||||||
href: '/radar/v2',
|
|
||||||
}],
|
|
||||||
Saber: [
|
|
||||||
{
|
|
||||||
title: '波动拟合图',
|
|
||||||
href: '/saber/plot_wave_fitting',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '日数据傅里叶变换分析',
|
|
||||||
href: '/saber/day_fft_ifft_plot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '日周期波动能量分析',
|
|
||||||
href: '/saber/day_cycle_power_wave_plot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// month_power_wave_plot
|
|
||||||
title: ' 月度波动能量分析',
|
|
||||||
href: '/saber/month_power_wave_plot',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NavigationMenu class="mx-auto">
|
|
||||||
<NavigationMenuList>
|
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuLink href="/" :class="navigationMenuTriggerStyle()">
|
|
||||||
主页
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
<NavigationMenuItem v-for="(thisHeader, headers) in header_data" :key="headers">
|
|
||||||
<NavigationMenuTrigger>{{ headers }}</NavigationMenuTrigger>
|
|
||||||
<NavigationMenuContent>
|
|
||||||
<ul class="w-60 flex flex-col gap-3 p-6">
|
|
||||||
<li v-for="header in thisHeader" :key="header.href" class="row-span-3">
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<a
|
|
||||||
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground"
|
|
||||||
:href="header.href"
|
|
||||||
>
|
|
||||||
<div class="text-sm font-medium leading-none">
|
|
||||||
{{ header.title }}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</NavigationMenuContent>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
</NavigationMenuList>
|
|
||||||
</NavigationMenu>
|
|
||||||
</template>
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
<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>
|
|
||||||
233
src/components/dense/RadarSingle.vue
Normal file
233
src/components/dense/RadarSingle.vue
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
waveType: '潮汐波' | '行星波'
|
||||||
|
isDay?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const selectedWave = computed(() => props.waveType)
|
||||||
|
|
||||||
|
const selectedMode = ref('2日行星波')
|
||||||
|
const selectedDateType = ref('month')
|
||||||
|
const selectedStation = ref('武汉左岭镇站')
|
||||||
|
const selectedYear = ref('2017')
|
||||||
|
const selectedWindType = ref('uwind')
|
||||||
|
const selectedH = ref(94)
|
||||||
|
const selectedDate = ref('20170316')
|
||||||
|
const selectedMonth = ref('1')
|
||||||
|
|
||||||
|
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 dates = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const station = selectedStation.value
|
||||||
|
const year = encodeURIComponent(selectedYear.value)
|
||||||
|
const mode = selectedWave.value === '潮汐波'
|
||||||
|
? '潮汐波'
|
||||||
|
: selectedMode.value
|
||||||
|
const windType = selectedWindType.value
|
||||||
|
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('station', station)
|
||||||
|
query.set('year', year)
|
||||||
|
query.set('model_name', mode)
|
||||||
|
query.set('wind_type', windType)
|
||||||
|
query.set('H', (selectedH.value * 1000).toString())
|
||||||
|
query.set('mode', props.isDay ? 'day' : selectedDateType.value)
|
||||||
|
if (props.isDay) {
|
||||||
|
const queryDay = `${selectedDate.value.slice(0, 4)}-${selectedDate.value.slice(4, 6)}-${selectedDate.value.slice(6, 8)}`
|
||||||
|
query.set('day', queryDay)
|
||||||
|
}
|
||||||
|
if (!props.isDay && selectedDateType.value === 'month') {
|
||||||
|
query.set('month', selectedMonth.value)
|
||||||
|
}
|
||||||
|
// const query = `?station=${station}&year=${year}&model_name=${mode}&wind_type=${windType}&H=${selectedH.value}`
|
||||||
|
const path = `/radar/render/heatmap?${query}`
|
||||||
|
return path
|
||||||
|
})
|
||||||
|
|
||||||
|
const urlModel = defineModel<string>()
|
||||||
|
|
||||||
|
syncRef(urlModel, queryUrl, { direction: 'rtl' })
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const resp = await baseFetch(`/radar/metadata`).json()
|
||||||
|
const data = await resp.data.value
|
||||||
|
// use regex to extract the year from the path,
|
||||||
|
// ./radar/data\\武汉左岭镇站\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
|
||||||
|
const station_pattern = /data\/radar\/(.*?)\//
|
||||||
|
const year_pattern = /(\d{4})\/ZLT_/
|
||||||
|
const date_pattern = /01D_(\d{8})\.txt/
|
||||||
|
const pairs = data.map((source_text: string) => {
|
||||||
|
const station = source_text.match(station_pattern)?.[1]
|
||||||
|
const year = source_text.match(year_pattern)?.[1]
|
||||||
|
const date = source_text.match(date_pattern)?.[1]
|
||||||
|
return { station, year, date }
|
||||||
|
})
|
||||||
|
pairs.forEach(({ station, year, date }: {
|
||||||
|
station: string
|
||||||
|
year: string
|
||||||
|
date: string
|
||||||
|
}) => {
|
||||||
|
stations.value.add(station)
|
||||||
|
years.value.add(year)
|
||||||
|
dates.value.add(date)
|
||||||
|
})
|
||||||
|
|
||||||
|
paths.value = data
|
||||||
|
if (props.isDay) {
|
||||||
|
selectedDate.value = Array.from(dates.value).filter(
|
||||||
|
d => d.startsWith(`${selectedYear.value}${selectedMonth.value.padStart(2, '0')}`),
|
||||||
|
)[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([selectedYear, selectedMonth], () => {
|
||||||
|
selectedDate.value = Array.from(dates.value).filter(
|
||||||
|
d => d.startsWith(`${selectedYear.value}${selectedMonth.value.padStart(2, '0')}`),
|
||||||
|
)[0]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<div>
|
||||||
|
<div flex="~ col gap-3" py-3>
|
||||||
|
<div v-if="selectedWave === '行星波'">
|
||||||
|
<Label>行星波周期</Label>
|
||||||
|
<Tabs v-model="selectedMode" default-value="2日行星波">
|
||||||
|
<TabsList class="grid grid-cols-4 w-full">
|
||||||
|
<TabsTrigger v-for="m in modes" :key="m" :value="m">
|
||||||
|
{{ m.replace("行星波", "") }}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
<Label for="wind_type">风类型</Label>
|
||||||
|
<Tabs id="wind_type" v-model="selectedWindType" default-value="uwind">
|
||||||
|
<TabsList class="grid grid-cols-2 w-full">
|
||||||
|
<TabsTrigger value="uwind">
|
||||||
|
纬向风
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="vwind">
|
||||||
|
经向风
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<div v-if="!props.isDay">
|
||||||
|
<Label for="H">日期类型</Label>
|
||||||
|
<Tabs id="date_type" v-model="selectedDateType" default-value="month">
|
||||||
|
<TabsList class="grid grid-cols-2 w-full">
|
||||||
|
<!-- <TabsTrigger value="day">
|
||||||
|
日
|
||||||
|
</TabsTrigger> -->
|
||||||
|
<TabsTrigger value="month">
|
||||||
|
月
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="year">
|
||||||
|
年
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
<Label>观测站</Label>
|
||||||
|
<Select v-model="selectedStation">
|
||||||
|
<SelectTrigger>
|
||||||
|
<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>
|
||||||
|
<Label>高度(km)</Label>
|
||||||
|
<NumberField
|
||||||
|
id="start"
|
||||||
|
v-model:model-value="selectedH" :format-options="{
|
||||||
|
useGrouping: false,
|
||||||
|
}" :default-value="94"
|
||||||
|
:step="2"
|
||||||
|
:max="110"
|
||||||
|
:min="70"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selectedYear">
|
||||||
|
<SelectTrigger>
|
||||||
|
<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 v-if="selectedDateType === 'month'">
|
||||||
|
<Label>月份</Label>
|
||||||
|
<Select v-model="selectedMonth">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="month in [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
||||||
|
]" :key="month" :value="month.toString()"
|
||||||
|
>
|
||||||
|
{{ month }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div v-if="props.isDay ">
|
||||||
|
<Label>日期</Label>
|
||||||
|
<Select v-model="selectedDate">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>天</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="date in Array.from(dates).filter(
|
||||||
|
(d) => d.startsWith(`${selectedYear}${selectedMonth.padStart(2, '0')}`),
|
||||||
|
)" :key="date" :value="date"
|
||||||
|
>
|
||||||
|
{{ date }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Denseframework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
157
src/components/dense/saber/day_cycle_power_wave_plot.vue
Normal file
157
src/components/dense/saber/day_cycle_power_wave_plot.vue
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"Saber日周期功率波图",
|
||||||
|
"description":"Saber日周期功率波图",
|
||||||
|
"group":"Saber",
|
||||||
|
"item_name":"日周期功率波图"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { currentSaberDays, parseDayOfYear, refreshCurrentSaberDays, refreshPath, renderPath, saberPaths } from './utils'
|
||||||
|
|
||||||
|
const lat_ranges = [
|
||||||
|
'0,10',
|
||||||
|
'10,20',
|
||||||
|
'20,30',
|
||||||
|
'30,40',
|
||||||
|
'40,50',
|
||||||
|
'50,60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const lon_ranges = [
|
||||||
|
'0 ~ 24',
|
||||||
|
'24 ~ 48',
|
||||||
|
'48 ~ 72',
|
||||||
|
'72 ~ 96',
|
||||||
|
'96 ~ 120',
|
||||||
|
'120 ~ 144',
|
||||||
|
'144 ~ 168',
|
||||||
|
'168 ~ 192',
|
||||||
|
'192 ~ 216',
|
||||||
|
'216 ~ 240',
|
||||||
|
'240 ~ 264',
|
||||||
|
'264 ~ 288',
|
||||||
|
'288 ~ 312',
|
||||||
|
'312 ~ 336',
|
||||||
|
'336 ~ 360',
|
||||||
|
]
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
path: '',
|
||||||
|
day: '',
|
||||||
|
cycle_no: '1',
|
||||||
|
lat_range: '0,10',
|
||||||
|
})
|
||||||
|
|
||||||
|
const urll = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('path', selected.path)
|
||||||
|
query.set('day', selected.day)
|
||||||
|
query.set('cycle_no', selected.cycle_no.toString())
|
||||||
|
query.set('lat_range', selected.lat_range)
|
||||||
|
return `/saber/render/gravity_wave/per_day/power_wave_plot?${query}`
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshPath()
|
||||||
|
selected.path = saberPaths.value[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => selected.path, async () => {
|
||||||
|
if (selected.path === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await refreshCurrentSaberDays(selected.path)
|
||||||
|
if (selected.day === '' && currentSaberDays.value.length > 0) {
|
||||||
|
selected.day = currentSaberDays.value[1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="urll">
|
||||||
|
<div>
|
||||||
|
<div flex="~ col items-stretch gap-3" py-3>
|
||||||
|
<slot />
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range.replace(",", " ~ ") }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="day">年月</Label>
|
||||||
|
<Select v-model="selected.path">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月份</SelectLabel>
|
||||||
|
<SelectItem v-for="path in saberPaths.sort((a, b) => renderPath(a).localeCompare(renderPath(b)))" :key="path" :value="path">
|
||||||
|
{{ renderPath(path) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="day">日期</Label>
|
||||||
|
<Select id="day" v-model="selected.day">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Day</SelectLabel>
|
||||||
|
<SelectItem v-for="day in currentSaberDays.slice(1)" :key="day" :value="day">
|
||||||
|
{{ parseDayOfYear (day.toString()).toLocaleDateString("zh", {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
}) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="age">经度范围(°)</Label>
|
||||||
|
<Select v-model="selected.cycle_no">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择范围" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>经度范围(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="i in 15" :key="i" :value="i.toString()">
|
||||||
|
{{ lon_ranges[i - 1] }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<!-- <NumberField
|
||||||
|
id="age"
|
||||||
|
v-model:model-value="selected.cycle_no"
|
||||||
|
:default-value="1" :min="0"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
146
src/components/dense/saber/day_fft_ifft_plot.vue
Normal file
146
src/components/dense/saber/day_fft_ifft_plot.vue
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"title": "Saber日周期功率波图",
|
||||||
|
"description": "Saber日周期功率波图",
|
||||||
|
"group": "Saber",
|
||||||
|
"item_name": "日周期功率波图"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { currentSaberDays, parseDayOfYear, refreshCurrentSaberDays, refreshPath, renderPath, saberPaths } from './utils'
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
path: './saber/data/2012/SABER_Temp_O3_April2012_v2.0.nc',
|
||||||
|
day: '',
|
||||||
|
cycle_no: '1',
|
||||||
|
lat_range: '0,10',
|
||||||
|
})
|
||||||
|
|
||||||
|
const lat_ranges = [
|
||||||
|
'0,10',
|
||||||
|
'10,20',
|
||||||
|
'20,30',
|
||||||
|
'30,40',
|
||||||
|
'40,50',
|
||||||
|
'50,60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const lon_ranges = [
|
||||||
|
'0 ~ 24',
|
||||||
|
'24 ~ 48',
|
||||||
|
'48 ~ 72',
|
||||||
|
'72 ~ 96',
|
||||||
|
'96 ~ 120',
|
||||||
|
'120 ~ 144',
|
||||||
|
'144 ~ 168',
|
||||||
|
'168 ~ 192',
|
||||||
|
'192 ~ 216',
|
||||||
|
'216 ~ 240',
|
||||||
|
'240 ~ 264',
|
||||||
|
'264 ~ 288',
|
||||||
|
'288 ~ 312',
|
||||||
|
'312 ~ 336',
|
||||||
|
'336 ~ 360',
|
||||||
|
]
|
||||||
|
|
||||||
|
const urll = computed(() => {
|
||||||
|
// const path = encodeURIComponent(selected.path)
|
||||||
|
// const day = encodeURIComponent(selected.day)
|
||||||
|
// const query = `path=${path}&day=${day}&cycle_no=${selected.cycle_no}`
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('path', selected.path)
|
||||||
|
query.set('day', selected.day)
|
||||||
|
query.set('cycle_no', selected.cycle_no.toString())
|
||||||
|
query.set('lat_range', selected.lat_range)
|
||||||
|
return `/saber/render/gravity_wave/per_day/fft_ifft?${query}`
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshPath()
|
||||||
|
selected.path = saberPaths.value[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => selected.path, async () => {
|
||||||
|
await refreshCurrentSaberDays(selected.path)
|
||||||
|
if (selected.day === '' && currentSaberDays.value.length > 0) {
|
||||||
|
selected.day = currentSaberDays.value[1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="urll">
|
||||||
|
<div>
|
||||||
|
<div flex="~ col items-stretch gap-3" py-3>
|
||||||
|
<slot />
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model:model-value="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range.replace(",", " ~ ") }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="day">年月</Label>
|
||||||
|
<Select v-model="selected.path">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月份</SelectLabel>
|
||||||
|
<SelectItem v-for="path in saberPaths.sort((a, b) => renderPath(a).localeCompare(renderPath(b)))" :key="path" :value="path">
|
||||||
|
{{ renderPath(path) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="day">日期</Label>
|
||||||
|
<Select id="day" v-model="selected.day">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Day</SelectLabel>
|
||||||
|
<SelectItem v-for="day in currentSaberDays.slice(1)" :key="day" :value="day">
|
||||||
|
{{ parseDayOfYear(day.toString()).toLocaleDateString("zh", {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
}) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="age">经度范围(°)</Label>
|
||||||
|
<Select v-model="selected.cycle_no">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择范围" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>经度范围(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="i in 15" :key="i" :value="i.toString()">
|
||||||
|
{{ lon_ranges[i - 1] }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
144
src/components/dense/saber/month_year_power_wave_plot.vue
Normal file
144
src/components/dense/saber/month_year_power_wave_plot.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"title": "Saber月周期功率波图",
|
||||||
|
"description": "Saber月周期功率波图",
|
||||||
|
"group": "Saber",
|
||||||
|
"item_name": "月周期功率波图"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { refreshPath, renderPath, saberPaths } from './utils'
|
||||||
|
|
||||||
|
const currentMode = ref('月统计图')
|
||||||
|
const allYears = ref([] as string[])
|
||||||
|
const lat_ranges = [
|
||||||
|
'-60,-50',
|
||||||
|
'-50,-40',
|
||||||
|
'-40,-30',
|
||||||
|
'-30,-20',
|
||||||
|
'-20,-10',
|
||||||
|
'-10,0',
|
||||||
|
'0,10',
|
||||||
|
'10,20',
|
||||||
|
'20,30',
|
||||||
|
'30,40',
|
||||||
|
'40,50',
|
||||||
|
'50,60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
path: '',
|
||||||
|
lat_range: '0,10',
|
||||||
|
year: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const urll = computed(() => {
|
||||||
|
if (currentMode.value === '月统计图') {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('path', selected.path)
|
||||||
|
query.set('lat_range', selected.lat_range)
|
||||||
|
return `/saber/render/gravity_wave/per_month/power_wave_plot?${query}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('year', selected.year)
|
||||||
|
query.set('lat_range', selected.lat_range)
|
||||||
|
return `/saber/render/gravity_wave/per_year/power_wave_plot?${query}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshPath()
|
||||||
|
selected.path = saberPaths.value[0]
|
||||||
|
// traverse all paths to get all years,
|
||||||
|
// in each item, str is like ../data/saber/2017/SABER_Temp_O3_June2017_v2.0.nc
|
||||||
|
const years = Array.from(new Set(saberPaths.value.map((str: string) => str.match(/(\d{4})/)?.[1])))
|
||||||
|
allYears.value = years.filter(year => year !== null).map(year => year!)
|
||||||
|
selected.year = allYears.value[0]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="urll">
|
||||||
|
<div>
|
||||||
|
<Label>模式</Label>
|
||||||
|
<Tabs v-model="currentMode">
|
||||||
|
<TabsList class="grid grid-cols-2 w-full">
|
||||||
|
<TabsTrigger value="月统计图">
|
||||||
|
月统计图
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="年统计图">
|
||||||
|
年统计图
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<div v-if="currentMode === '月统计图'" flex="~ col items-stretch gap-3" py-3>
|
||||||
|
<Label for="day">年月</Label>
|
||||||
|
<Select v-model="selected.path">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月份</SelectLabel>
|
||||||
|
<SelectItem v-for="path in saberPaths.sort((a, b) => renderPath(a).localeCompare(renderPath(b)))" :key="path" :value="path">
|
||||||
|
{{ renderPath(path) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range.replace(",", " ~ ") }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col gap-3 py-2">
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selected.year">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in allYears" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range.replace(",", " ~ ") }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
142
src/components/dense/saber/plot_wave_fitting.vue
Normal file
142
src/components/dense/saber/plot_wave_fitting.vue
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"title": "Saber拟合",
|
||||||
|
"description": "Saber拟合",
|
||||||
|
"group": "Saber",
|
||||||
|
"item_name": "拟合"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { currentSaberDays, parseDayOfYear, refreshCurrentSaberDays, refreshPath, renderPath, saberPaths } from './utils'
|
||||||
|
|
||||||
|
const lat_ranges = [
|
||||||
|
'0,10',
|
||||||
|
'10,20',
|
||||||
|
'20,30',
|
||||||
|
'30,40',
|
||||||
|
'40,50',
|
||||||
|
'50,60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
path: './saber/data\\2012\\SABER_Temp_O3_April2012_v2.0.nc',
|
||||||
|
day: '',
|
||||||
|
height_no: '1',
|
||||||
|
lat_ranges: '0,10',
|
||||||
|
})
|
||||||
|
|
||||||
|
const urll = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('path', selected.path)
|
||||||
|
query.set('day', selected.day)
|
||||||
|
query.set('height_no', selected.height_no.toString())
|
||||||
|
query.set('lat_range', selected.lat_ranges)
|
||||||
|
return `/saber/render/gravity_wave/per_day/wave_fitting?${query}`
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshPath()
|
||||||
|
selected.path = saberPaths.value[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => selected.path, async () => {
|
||||||
|
await refreshCurrentSaberDays(selected.path)
|
||||||
|
if (saberPaths.value.length > 0) {
|
||||||
|
selected.path = saberPaths.value[1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function mapHeightValue(input: number) {
|
||||||
|
// Input range: 1 to 157
|
||||||
|
// Output range: 30 to 90
|
||||||
|
const inputMin = 1
|
||||||
|
const inputMax = 157
|
||||||
|
const outputMin = 30
|
||||||
|
const outputMax = 90
|
||||||
|
|
||||||
|
// Calculate slope
|
||||||
|
const m = (outputMax - outputMin) / (inputMax - inputMin)
|
||||||
|
// Calculate y-intercept
|
||||||
|
const b = outputMin - m * inputMin
|
||||||
|
|
||||||
|
// Apply linear mapping
|
||||||
|
return m * input + b
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="urll">
|
||||||
|
<div>
|
||||||
|
<div flex="~ col justify-stretch gap-3" py-3>
|
||||||
|
<slot />
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_ranges">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range.replace(",", " ~ ") }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="day">年月</Label>
|
||||||
|
<Select v-model="selected.path">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月份</SelectLabel>
|
||||||
|
<SelectItem v-for="path in saberPaths.sort((a, b) => renderPath(a).localeCompare(renderPath(b)))" :key="path" :value="path">
|
||||||
|
{{ renderPath(path) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="day">日期</Label>
|
||||||
|
<Select id="day" v-model="selected.day">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Day</SelectLabel>
|
||||||
|
<SelectItem v-for="day in currentSaberDays.slice(1)" :key="day" :value="day">
|
||||||
|
{{ parseDayOfYear(day.toString()).toLocaleDateString("zh", {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
}) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="age">高度</Label>
|
||||||
|
<Select v-model="selected.height_no">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择高度" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>高度</SelectLabel>
|
||||||
|
<SelectItem v-for="height_no in 157" :key="height_no" :value="height_no.toString()">
|
||||||
|
{{ mapHeightValue(height_no).toFixed(2) }} km
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
47
src/components/dense/saber/utils.ts
Normal file
47
src/components/dense/saber/utils.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const saberPaths = ref<string[]>([])
|
||||||
|
const currentSaberDays = ref<string>('')
|
||||||
|
|
||||||
|
async function refreshPath() {
|
||||||
|
if (saberPaths.value.length)
|
||||||
|
return
|
||||||
|
const resp = await baseFetch<string[]>(`/saber/metadata`).json()
|
||||||
|
const data = resp.data.value
|
||||||
|
saberPaths.value = data!
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCurrentSaberDays(path: string) {
|
||||||
|
const resp = await baseFetch<string>(`/saber/metadata/list_days?path=${path}`).json()
|
||||||
|
const data = resp.data.value
|
||||||
|
currentSaberDays.value = data!
|
||||||
|
}
|
||||||
|
function renderPath(path: string) {
|
||||||
|
const yearPattern = /\/data\/saber\/(\d{4})/
|
||||||
|
const year = path.match(yearPattern)?.[1]
|
||||||
|
const monthPattern = /Temp_O3_(.*)(\d{4})/
|
||||||
|
const month_mapping = {
|
||||||
|
January: '01月',
|
||||||
|
February: '02月',
|
||||||
|
March: '03月',
|
||||||
|
April: '04月',
|
||||||
|
May: '05月',
|
||||||
|
June: '06月',
|
||||||
|
July: '07月',
|
||||||
|
August: '08月',
|
||||||
|
September: '09月',
|
||||||
|
October: '10月',
|
||||||
|
November: '11月',
|
||||||
|
December: '12月',
|
||||||
|
} as const
|
||||||
|
const month = path.match(monthPattern)?.[1] as keyof typeof month_mapping
|
||||||
|
return `${year}年${month_mapping[month]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDayOfYear(dateString: string): Date {
|
||||||
|
const year = Number.parseInt(dateString.substring(0, 4))
|
||||||
|
const dayOfYear = Number.parseInt(dateString.substring(4)) - 1 // subtract 1 because JS dates are 0-based
|
||||||
|
const date = new Date(year, 0) // Start with January 1st of the year
|
||||||
|
date.setDate(dayOfYear + 1) // Add the days
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
export { currentSaberDays, parseDayOfYear, refreshCurrentSaberDays, refreshPath, renderPath, saberPaths }
|
||||||
19
src/components/ui/drawer/Drawer.vue
Normal file
19
src/components/ui/drawer/Drawer.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
|
||||||
|
import { useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { DrawerRoot } from 'vaul-vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DrawerRootProps>(), {
|
||||||
|
shouldScaleBackground: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits<DrawerRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</DrawerRoot>
|
||||||
|
</template>
|
||||||
28
src/components/ui/drawer/DrawerContent.vue
Normal file
28
src/components/ui/drawer/DrawerContent.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
|
||||||
|
import type { HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { DrawerContent, DrawerPortal } from 'vaul-vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
import DrawerOverlay from './DrawerOverlay.vue'
|
||||||
|
|
||||||
|
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
const emits = defineEmits<DialogContentEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerContent
|
||||||
|
v-bind="forwarded" :class="cn(
|
||||||
|
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
<slot />
|
||||||
|
</DrawerContent>
|
||||||
|
</DrawerPortal>
|
||||||
|
</template>
|
||||||
20
src/components/ui/drawer/DrawerDescription.vue
Normal file
20
src/components/ui/drawer/DrawerDescription.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||||
|
import { DrawerDescription } from 'vaul-vue'
|
||||||
|
import { computed, type HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
|
||||||
|
<slot />
|
||||||
|
</DrawerDescription>
|
||||||
|
</template>
|
||||||
14
src/components/ui/drawer/DrawerFooter.vue
Normal file
14
src/components/ui/drawer/DrawerFooter.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HtmlHTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
14
src/components/ui/drawer/DrawerHeader.vue
Normal file
14
src/components/ui/drawer/DrawerHeader.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HtmlHTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
src/components/ui/drawer/DrawerOverlay.vue
Normal file
18
src/components/ui/drawer/DrawerOverlay.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DialogOverlayProps } from 'radix-vue'
|
||||||
|
import { DrawerOverlay } from 'vaul-vue'
|
||||||
|
import { computed, type HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerOverlay v-bind="delegatedProps" :class="cn('fixed inset-0 z-50 bg-black/80', props.class)" />
|
||||||
|
</template>
|
||||||
20
src/components/ui/drawer/DrawerTitle.vue
Normal file
20
src/components/ui/drawer/DrawerTitle.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DrawerTitleProps } from 'vaul-vue'
|
||||||
|
import { DrawerTitle } from 'vaul-vue'
|
||||||
|
import { computed, type HtmlHTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DrawerTitle v-bind="delegatedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
|
||||||
|
<slot />
|
||||||
|
</DrawerTitle>
|
||||||
|
</template>
|
||||||
8
src/components/ui/drawer/index.ts
Normal file
8
src/components/ui/drawer/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export { default as Drawer } from './Drawer.vue'
|
||||||
|
export { default as DrawerContent } from './DrawerContent.vue'
|
||||||
|
export { default as DrawerDescription } from './DrawerDescription.vue'
|
||||||
|
export { default as DrawerFooter } from './DrawerFooter.vue'
|
||||||
|
export { default as DrawerHeader } from './DrawerHeader.vue'
|
||||||
|
export { default as DrawerOverlay } from './DrawerOverlay.vue'
|
||||||
|
export { default as DrawerTitle } from './DrawerTitle.vue'
|
||||||
|
export { DrawerClose, DrawerPortal, DrawerTrigger } from 'vaul-vue'
|
||||||
74
src/composables/fetch.ts
Normal file
74
src/composables/fetch.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { createFetch } from '@vueuse/core'
|
||||||
|
import { API_BASE_URL } from '~/CONSTANT'
|
||||||
|
|
||||||
|
export const authCode = ref(
|
||||||
|
localStorage.getItem('authCode')
|
||||||
|
|| sessionStorage.getItem('authCode')
|
||||||
|
|| '',
|
||||||
|
)
|
||||||
|
|
||||||
|
export const baseFetch = createFetch({
|
||||||
|
baseUrl: API_BASE_URL,
|
||||||
|
options: {
|
||||||
|
async beforeFetch({ options }) {
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
Authorization: `${authCode.value}`,
|
||||||
|
}
|
||||||
|
return { options }
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
fetchOptions: {
|
||||||
|
mode: 'cors',
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// export async function doCheckOnline() {
|
||||||
|
// let resp = null
|
||||||
|
// try {
|
||||||
|
// resp = await baseFetch('/ping', {
|
||||||
|
// timeout: 2000,
|
||||||
|
// })
|
||||||
|
// const { error } = resp
|
||||||
|
// if (error.value)
|
||||||
|
// throw new Error(error.value)
|
||||||
|
// const status = resp.response.value?.status
|
||||||
|
// if (!status) {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// if (status === 200) {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// else if (status >= 400 && status < 500) {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// catch (err) {
|
||||||
|
// if (resp?.response.value?.status === 401) {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// console.error(err)
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const useBackendOnline = createSharedComposable(() => {
|
||||||
|
// const isOnline = ref(true)
|
||||||
|
|
||||||
|
// const useCheckInterval = useIntervalFn(() => {
|
||||||
|
// doCheckOnline().then((online) => {
|
||||||
|
// isOnline.value = online
|
||||||
|
// })
|
||||||
|
// }, 5000, {
|
||||||
|
// immediate: true,
|
||||||
|
// immediateCallback: true,
|
||||||
|
// })
|
||||||
|
// useCheckInterval.resume()
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// isOnline,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
0
src/composables/header.ts
Normal file
0
src/composables/header.ts
Normal file
@ -1 +1,2 @@
|
|||||||
export * from './dark'
|
export * from './dark'
|
||||||
|
export * from './fetch'
|
||||||
|
|||||||
@ -1,3 +1,163 @@
|
|||||||
export const hasConnection = ref(false)
|
import { useIntervalFn } from '@vueuse/core'
|
||||||
|
import { API_BASE_URL } from '~/CONSTANT'
|
||||||
|
|
||||||
// get `https://`
|
interface UseAPIOnlineOptions {
|
||||||
|
/**
|
||||||
|
* WebSocket endpoint URL
|
||||||
|
*/
|
||||||
|
url?: string
|
||||||
|
/**
|
||||||
|
* 心跳间隔(ms)
|
||||||
|
* @default 30000
|
||||||
|
*/
|
||||||
|
heartbeatInterval?: number
|
||||||
|
/**
|
||||||
|
* 重连间隔(ms)
|
||||||
|
* @default 5000
|
||||||
|
*/
|
||||||
|
reconnectDelay?: number
|
||||||
|
/**
|
||||||
|
* 连接成功回调
|
||||||
|
*/
|
||||||
|
onConnected?: () => void
|
||||||
|
/**
|
||||||
|
* 断开连接回调
|
||||||
|
*/
|
||||||
|
onDisconnected?: () => void
|
||||||
|
/**
|
||||||
|
* 连接错误回调
|
||||||
|
*/
|
||||||
|
onError?: (error: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAPIOnline(options: UseAPIOnlineOptions = {}) {
|
||||||
|
// check current protocol, if is https, use wss, if is http, use ws
|
||||||
|
const {
|
||||||
|
wsProtocol,
|
||||||
|
wsBaseURL,
|
||||||
|
} = (() => {
|
||||||
|
let wsProtocol = API_BASE_URL.startsWith('https') ? 'wss' : 'ws'
|
||||||
|
if (!API_BASE_URL.startsWith('http')) {
|
||||||
|
wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||||
|
}
|
||||||
|
let wsBaseURL = API_BASE_URL.replace('https://', '').replace('http://', '')
|
||||||
|
if (!API_BASE_URL.startsWith('http')) {
|
||||||
|
wsBaseURL = location.host + API_BASE_URL
|
||||||
|
}
|
||||||
|
return { wsProtocol, wsBaseURL }
|
||||||
|
})()
|
||||||
|
|
||||||
|
const {
|
||||||
|
url = `${wsProtocol}://${wsBaseURL}/ping/ws`,
|
||||||
|
heartbeatInterval = 30000,
|
||||||
|
reconnectDelay = 5000,
|
||||||
|
onConnected,
|
||||||
|
onDisconnected,
|
||||||
|
onError,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const ws = ref<WebSocket | null>(null)
|
||||||
|
const isOnline = ref(false)
|
||||||
|
const isConnecting = ref(false)
|
||||||
|
|
||||||
|
// 最后一次收到pong的时间戳
|
||||||
|
const lastPongTime = ref(0)
|
||||||
|
|
||||||
|
// 检查是否需要重连的定时器
|
||||||
|
const { pause: pauseHealthCheck, resume: resumeHealthCheck } = useIntervalFn(() => {
|
||||||
|
const now = Date.now()
|
||||||
|
// 如果45秒没有收到pong,认为连接断开
|
||||||
|
if (now - lastPongTime.value > 45000) {
|
||||||
|
isOnline.value = false
|
||||||
|
reconnect()
|
||||||
|
}
|
||||||
|
}, 15000)
|
||||||
|
|
||||||
|
// 心跳定时器
|
||||||
|
const { pause: pauseHeartbeat, resume: resumeHeartbeat } = useIntervalFn(() => {
|
||||||
|
if (ws.value?.readyState === WebSocket.OPEN) {
|
||||||
|
ws.value.send(JSON.stringify({ type: 'ping' }))
|
||||||
|
}
|
||||||
|
}, heartbeatInterval)
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
if (isConnecting.value || (ws.value?.readyState === WebSocket.CONNECTING)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnecting.value = true
|
||||||
|
|
||||||
|
// 清理旧的连接
|
||||||
|
if (ws.value) {
|
||||||
|
ws.value.close()
|
||||||
|
ws.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = new WebSocket(url)
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
isOnline.value = true
|
||||||
|
isConnecting.value = false
|
||||||
|
lastPongTime.value = Date.now()
|
||||||
|
resumeHeartbeat()
|
||||||
|
resumeHealthCheck()
|
||||||
|
onConnected?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = () => {
|
||||||
|
isOnline.value = false
|
||||||
|
isConnecting.value = false
|
||||||
|
pauseHeartbeat()
|
||||||
|
pauseHealthCheck()
|
||||||
|
onDisconnected?.()
|
||||||
|
setTimeout(reconnect, reconnectDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = (error) => {
|
||||||
|
isOnline.value = false
|
||||||
|
isConnecting.value = false
|
||||||
|
pauseHeartbeat()
|
||||||
|
pauseHealthCheck()
|
||||||
|
onError?.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
if (data.type === 'pong') {
|
||||||
|
lastPongTime.value = Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('Failed to parse websocket message:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.value = socket
|
||||||
|
}
|
||||||
|
|
||||||
|
function reconnect() {
|
||||||
|
if (!isOnline.value && !isConnecting.value) {
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disconnect = () => {
|
||||||
|
if (ws.value) {
|
||||||
|
ws.value.close()
|
||||||
|
ws.value = null
|
||||||
|
}
|
||||||
|
isOnline.value = false
|
||||||
|
isConnecting.value = false
|
||||||
|
pauseHeartbeat()
|
||||||
|
pauseHealthCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOnline,
|
||||||
|
isConnecting,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
reconnect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { routes } from 'vue-router/auto-routes'
|
import { handleHotUpdate, routes } from 'vue-router/auto-routes'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
import '@unocss/reset/tailwind.css'
|
import '@unocss/reset/tailwind.css'
|
||||||
@ -13,5 +13,9 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
})
|
})
|
||||||
|
if (import.meta.hot) {
|
||||||
|
handleHotUpdate(router)
|
||||||
|
}
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
12
src/pages/auth.vue
Normal file
12
src/pages/auth.vue
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AuthBlock />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
156
src/pages/balloon/gravity_wave/0-perday.vue
Normal file
156
src/pages/balloon/gravity_wave/0-perday.vue
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
title: 探空气球-重力波单次
|
||||||
|
description: 探空气球-重力波单次
|
||||||
|
group: 探空气球
|
||||||
|
item_name: 重力波提取
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ImageResult } from '~/components/ImageContainer.vue'
|
||||||
|
|
||||||
|
const modes = [
|
||||||
|
'观测的二阶多项式拟合',
|
||||||
|
'扰动分量的正弦波拟合',
|
||||||
|
'径向风-纬向风矢量图',
|
||||||
|
'温度-水平风矢量图',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const nameModeMapping: Record<string, string> = {
|
||||||
|
'径向风-纬向风矢量图': '经向风-纬向风矢量图',
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPaths = ref([] as string[])
|
||||||
|
const allStations = ref([] as string[])
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
selectedMode: '观测的二阶多项式拟合',
|
||||||
|
selectedPath: '',
|
||||||
|
station: 'LIN',
|
||||||
|
})
|
||||||
|
const currentPathesList = computed(() => {
|
||||||
|
const station = selected.station
|
||||||
|
return allPaths.value.filter((a: string) => a.includes(station))
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await baseFetch<string []>(`/balloon/metadata`).json().then(({ data }) => {
|
||||||
|
const das = data.value!
|
||||||
|
allPaths.value = das
|
||||||
|
selected.selectedPath = das[0]
|
||||||
|
|
||||||
|
const _allStations = das.map((a: string) => {
|
||||||
|
const stationPattern = /data\/balloon\/(.+)\//
|
||||||
|
return a.match(stationPattern)?.[1].split('/')[0] ?? ''
|
||||||
|
}) as string[]
|
||||||
|
|
||||||
|
const stations = Array.from(new Set(_allStations))
|
||||||
|
allStations.value = stations
|
||||||
|
selected.station = stations[0]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function renderDate(str: string) {
|
||||||
|
const datePattern = /_\d{8}T\d{6}_/
|
||||||
|
const date = str.match(datePattern)![0]
|
||||||
|
const utcstr = date.replaceAll('_', '')
|
||||||
|
const realTime = new Date(utcstr.replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/, '$1-$2-$3T$4:$5:$6'))
|
||||||
|
return realTime.toLocaleString('zh', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const selectedMode = selected.selectedMode
|
||||||
|
const selectedDate = selected.selectedPath
|
||||||
|
return `/balloon/render/single?mode=${encodeURIComponent(selectedMode)}&path=${encodeURIComponent(selectedDate)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const metadata = ref({} as Record<string, string>)
|
||||||
|
|
||||||
|
async function customHandle(resp: Response) {
|
||||||
|
if (resp.status === 204) {
|
||||||
|
const res: ImageResult = {
|
||||||
|
message: '这时刻没有重力波',
|
||||||
|
result: 'error',
|
||||||
|
resourceId: '',
|
||||||
|
}
|
||||||
|
metadata.value = {}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const data = await resp.json()
|
||||||
|
const blob = await fetch(`data:image/png;base64,${data.image}`).then(r => r.blob())
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
metadata.value = data.metadata
|
||||||
|
const res: ImageResult = {
|
||||||
|
message: '成功',
|
||||||
|
result: 'success',
|
||||||
|
resourceId: url,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
message: '未知错误',
|
||||||
|
result: 'error',
|
||||||
|
resourceId: '',
|
||||||
|
} as ImageResult
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl" :extra-response-handle="customHandle">
|
||||||
|
<template #extra-meta>
|
||||||
|
<div v-if="!(Object.keys(metadata).length === 0)" class="absolute right-3 top-3 rounded-md bg-white p-3 drop-shadow">
|
||||||
|
<span v-for="(v, k) in metadata" :key="k">{{ k }}: {{ v }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<Label>选择台站</Label>
|
||||||
|
<Select v-model="selected.station">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择台站" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>台站</SelectLabel>
|
||||||
|
<SelectItem v-for="s in allStations" :key="s" :value="s">
|
||||||
|
{{ s }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>选择日期</Label>
|
||||||
|
<Select v-model="selected.selectedPath">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择日期" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>日期</SelectLabel>
|
||||||
|
<SelectItem v-for="path in currentPathesList" :key="path" :value="path">
|
||||||
|
{{ renderDate(path) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>选择模式</Label>
|
||||||
|
<Tabs v-model="selected.selectedMode" default-value="观测的二阶多项式拟合">
|
||||||
|
<TabsList class="grid grid-cols-1 w-full">
|
||||||
|
<TabsTrigger v-for="m in modes" :key="m" :value="m">
|
||||||
|
{{ nameModeMapping[m] ?? m }}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
223
src/pages/balloon/gravity_wave/1-year.vue
Normal file
223
src/pages/balloon/gravity_wave/1-year.vue
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
title: 探空气球-重力波统计
|
||||||
|
description: 探空气球-重力波统计
|
||||||
|
group: 探空气球
|
||||||
|
item_name: 重力波统计
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const selectedMode = ref('w/f值统计结果')
|
||||||
|
const selectedStation = ref('LIN')
|
||||||
|
const allStations = ref([] as string[])
|
||||||
|
const allPaths = ref([] as string[])
|
||||||
|
|
||||||
|
const isIllegal = ref(false)
|
||||||
|
|
||||||
|
const startYear = ref(2017)
|
||||||
|
const endYear = ref(2024)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await baseFetch<string []>(`/balloon/metadata`).json().then(({ data }) => {
|
||||||
|
const das = data.value!
|
||||||
|
allPaths.value = das
|
||||||
|
|
||||||
|
const _allStations = das.map((a: string) => {
|
||||||
|
const stationPattern = /data\/balloon\/(.+)\//
|
||||||
|
return a.match(stationPattern)?.[1].split('/')[0] ?? ''
|
||||||
|
}) as string[]
|
||||||
|
|
||||||
|
const stations = Array.from(new Set(_allStations))
|
||||||
|
allStations.value = stations
|
||||||
|
selectedStation.value = stations[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
// set default start year and end year
|
||||||
|
const station = selectedStation.value
|
||||||
|
// filter out the data that belong to the selected station
|
||||||
|
const stationData = allPaths.value.filter(a => a.includes(station))
|
||||||
|
const stationYears = new Set(stationData.map((a) => {
|
||||||
|
const yearPattern = /-(\d{4})/
|
||||||
|
return Number.parseInt(a.match(yearPattern)?.[1] ?? '-1')
|
||||||
|
}).filter(a => a !== -1))
|
||||||
|
const minYear = Math.min(...Array.from(stationYears))
|
||||||
|
const maxYear = Math.max(...Array.from(stationYears))
|
||||||
|
startYear.value = minYear
|
||||||
|
endYear.value = maxYear
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentMinBeginYear = computed(() => {
|
||||||
|
const station = selectedStation.value
|
||||||
|
// filter out the data that belong to the selected station
|
||||||
|
const stationData = allPaths.value.filter(a => a.includes(station))
|
||||||
|
const stationYears = new Set(stationData.map((a) => {
|
||||||
|
const yearPattern = /-(\d{4})/
|
||||||
|
return Number.parseInt(a.match(yearPattern)?.[1] ?? '-1')
|
||||||
|
}).filter(a => a !== -1))
|
||||||
|
return Math.min(...Array.from(stationYears))
|
||||||
|
})
|
||||||
|
const currentMaxEndYear = computed(() => {
|
||||||
|
const station = selectedStation.value
|
||||||
|
// filter out the data that belong to the selected station
|
||||||
|
const stationData = allPaths.value.filter(a => a.includes(station))
|
||||||
|
const stationYears = new Set(stationData.map((a) => {
|
||||||
|
const yearPattern = /-(\d{4})/
|
||||||
|
return Number.parseInt(a.match(yearPattern)?.[1] ?? '-1')
|
||||||
|
}).filter(a => a !== -1))
|
||||||
|
const result = Math.max(...Array.from(stationYears))
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([currentMinBeginYear, currentMaxEndYear], () => {
|
||||||
|
if (startYear.value < currentMinBeginYear.value) {
|
||||||
|
startYear.value = currentMinBeginYear.value
|
||||||
|
}
|
||||||
|
if (endYear.value > currentMaxEndYear.value) {
|
||||||
|
endYear.value = currentMaxEndYear.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, if start year is greater than end year, set end year to start year
|
||||||
|
if (startYear.value > endYear.value) {
|
||||||
|
endYear.value = startYear.value
|
||||||
|
}
|
||||||
|
// or if
|
||||||
|
})
|
||||||
|
|
||||||
|
const modes = [
|
||||||
|
|
||||||
|
'w/f值统计结果',
|
||||||
|
'周期统计结果',
|
||||||
|
'垂直波长分布',
|
||||||
|
'水平波长分布',
|
||||||
|
'纬向本征相速度',
|
||||||
|
'经向本征相速度',
|
||||||
|
'垂直本征相速度',
|
||||||
|
'Zonal wind amplitude (m/s)',
|
||||||
|
'扰动振幅统计结果',
|
||||||
|
'Temperature amplitude (K)',
|
||||||
|
'纬向动量通量统计结果',
|
||||||
|
'经向动量通量统计结果',
|
||||||
|
'horizontal propagation',
|
||||||
|
'每月上传/下传重力波占比',
|
||||||
|
'动能和势能分布情况',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const mode_display_mapping = {
|
||||||
|
'w/f值统计结果': '本征频率与惯性频率的比值',
|
||||||
|
'周期统计结果': '周期统计',
|
||||||
|
'垂直波长分布': '垂直波长',
|
||||||
|
'水平波长分布': '水平波长',
|
||||||
|
'纬向本征相速度': '纬向本征相速度',
|
||||||
|
'经向本征相速度': '经向本征相速度',
|
||||||
|
'垂直本征相速度': '垂直本征相速度',
|
||||||
|
'Zonal wind amplitude (m/s)': '纬向风扰动幅度',
|
||||||
|
'扰动振幅统计结果': '经向风扰动幅度',
|
||||||
|
'Temperature amplitude (K)': '温度扰动振幅',
|
||||||
|
'纬向动量通量统计结果': '纬向动量通量统计',
|
||||||
|
'经向动量通量统计结果': '经向动量通量统计',
|
||||||
|
'horizontal propagation': '水平传播',
|
||||||
|
'每月上传/下传重力波占比': '垂直传播',
|
||||||
|
'动能和势能分布情况': '动能和势能分布情况',
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
mode: selectedMode.value,
|
||||||
|
start_year: startYear.value.toString(),
|
||||||
|
end_year: endYear.value.toString(),
|
||||||
|
station: selectedStation.value,
|
||||||
|
})
|
||||||
|
return `/balloon/render/year?${params.toString()}`
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([selectedMode, startYear, endYear], () => {
|
||||||
|
if (startYear.value > endYear.value) {
|
||||||
|
isIllegal.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isIllegal.value = false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<div flex="col gap-5 ~ justify-center">
|
||||||
|
<div>
|
||||||
|
<Label>选择台站</Label>
|
||||||
|
<Select v-model="selectedStation">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择台站" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>台站</SelectLabel>
|
||||||
|
<SelectItem v-for="s in allStations" :key="s" :value="s">
|
||||||
|
{{ s }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- {{ currentMaxEndYear }}{{ currentMinBeginYear }} -->
|
||||||
|
台站 {{ selectedStation }} 可选的数据范围为 {{ currentMinBeginYear }} 年 - {{ currentMaxEndYear }} 年
|
||||||
|
</div>
|
||||||
|
<NumberField
|
||||||
|
id="start"
|
||||||
|
v-model:model-value="startYear" :format-options="{
|
||||||
|
useGrouping: false,
|
||||||
|
}" :default-value="2017"
|
||||||
|
:min="currentMinBeginYear"
|
||||||
|
:max="endYear"
|
||||||
|
>
|
||||||
|
<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="startYear" :max="currentMaxEndYear"
|
||||||
|
>
|
||||||
|
<Label for="end">终止年</Label>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
<div>
|
||||||
|
<Label>统计参数</Label>
|
||||||
|
<Select v-model="selectedMode">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>模式</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="mode in Array.from(modes).sort(((a, b) => {
|
||||||
|
return mode_display_mapping[a].length - mode_display_mapping[b].length
|
||||||
|
}))"
|
||||||
|
:key="mode" :value="mode"
|
||||||
|
>
|
||||||
|
{{ mode_display_mapping[mode] ?? mode }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,101 +0,0 @@
|
|||||||
<route lang="yaml">
|
|
||||||
meta:
|
|
||||||
title: 探空气球-重力波单次
|
|
||||||
description: 探空气球-重力波单次
|
|
||||||
group: 探空气球
|
|
||||||
item_name: 重力波单次
|
|
||||||
</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 imageResult = reactive<ImageResult>({
|
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const modes = [
|
|
||||||
'观测的二阶多项式拟合',
|
|
||||||
'扰动分量的正弦波拟合',
|
|
||||||
'径向风-纬向风矢量图',
|
|
||||||
'温度-水平风矢量图',
|
|
||||||
] as const
|
|
||||||
|
|
||||||
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).describe('选择一个模式'),
|
|
||||||
selectedDate: z.enum(data.map((d: string) => {
|
|
||||||
const datePattern = /_\d{8}T\d{6}/
|
|
||||||
if (!datePattern.test(d)) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
const capture = datePattern.exec(d)
|
|
||||||
if (!capture) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
return capture[0]
|
|
||||||
})),
|
|
||||||
}).describe('选择一个日期')
|
|
||||||
form.setFieldValue('selectedDate', data[0])
|
|
||||||
form.setFieldValue('selectedMode', modes[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
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) {
|
|
||||||
imageResult.result = 'error' as const
|
|
||||||
imageResult.message = '这一天没有数据'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
imageResult.result = 'success' as const
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
<DenseFramework :image-result="imageResult" @submit="submit">
|
|
||||||
<AutoForm
|
|
||||||
:form="form"
|
|
||||||
:field-config="{
|
|
||||||
selectedMode: {
|
|
||||||
component: 'radio',
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
:schema="formSchema"
|
|
||||||
/>
|
|
||||||
</DenseFramework>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
<route lang="yaml">
|
|
||||||
meta:
|
|
||||||
title: 探空气球-重力波统计
|
|
||||||
description: 探空气球-重力波统计
|
|
||||||
group: 探空气球
|
|
||||||
item_name: 重力波统计
|
|
||||||
</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 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 = `${API_BASE_URL}/balloon/render/year?mode=${encodeURIComponent(selectedMode.value)}&start_year=${startYear.value}&end_year=${endYear.value}`
|
|
||||||
const resp = await fetch(url)
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const u = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = u
|
|
||||||
}
|
|
||||||
|
|
||||||
watch([selectedMode, startYear, endYear], () => {
|
|
||||||
if (startYear.value > endYear.value) {
|
|
||||||
isIllegal.value = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isIllegal.value = false
|
|
||||||
refreshImage()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DenseFramework :image-result="imageResult" @submit="refreshImage">
|
|
||||||
<div flex="col gap-5 ~ 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>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
147
src/pages/cosmic/gravity_wave/0-perday.vue
Normal file
147
src/pages/cosmic/gravity_wave/0-perday.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<route lang='json'>
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"COSMIC单次",
|
||||||
|
"description":"COSMIC重力波单次",
|
||||||
|
"group":"COSMIC",
|
||||||
|
"item_name":"重力波提取"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
CalendarDate,
|
||||||
|
createCalendar,
|
||||||
|
DateFormatter,
|
||||||
|
type DateValue,
|
||||||
|
getLocalTimeZone,
|
||||||
|
toCalendar,
|
||||||
|
} from '@internationalized/date'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
function getDayOfYear(date: Date) {
|
||||||
|
const start = new Date(date.getFullYear(), 0, 0)
|
||||||
|
// @ts-expect-error date - start
|
||||||
|
const diff = date - start
|
||||||
|
const oneDay = 1000 * 60 * 60 * 24
|
||||||
|
return Math.floor(diff / oneDay)
|
||||||
|
}
|
||||||
|
const df = new DateFormatter('zh-CN', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const lat_ranges = [
|
||||||
|
'-60 ~ -50',
|
||||||
|
'-50 ~ -40',
|
||||||
|
'-40 ~ -30',
|
||||||
|
'-30 ~ -20',
|
||||||
|
'-20 ~ -10',
|
||||||
|
'-10 ~ 0',
|
||||||
|
'0 ~ 10',
|
||||||
|
'10 ~ 20',
|
||||||
|
'20 ~ 30',
|
||||||
|
'30 ~ 40',
|
||||||
|
'40 ~ 50',
|
||||||
|
'50 ~ 60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
year: '2008',
|
||||||
|
day: '1',
|
||||||
|
mode: 'mean_ktemp_Nz',
|
||||||
|
lat_range: '0 ~ 10',
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = ref<DateValue>(
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
value.value = toCalendar(new CalendarDate(2008, 1, 1), createCalendar('zh-CN'))
|
||||||
|
})
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('year', selected.year)
|
||||||
|
const theDate = value.value?.toDate(getLocalTimeZone()) ?? new Date(2008, 1, 1)
|
||||||
|
const day_No = getDayOfYear(theDate)
|
||||||
|
query.set('day', day_No.toString())
|
||||||
|
query.set('mode', selected.mode)
|
||||||
|
query.set('lat_range', selected.lat_range)
|
||||||
|
return `/cosmic/render/gravity_wave/perday?${query}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<Label>统计参数</Label>
|
||||||
|
<Tabs v-model:model-value="selected.mode">
|
||||||
|
<TabsList class="grid grid-cols-1 w-full">
|
||||||
|
<TabsTrigger value="mean_ktemp_Nz">
|
||||||
|
平均浮力频率随高度变化
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="mean_ktemp_Ptz">
|
||||||
|
平均势能随高度变化
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="residual_mean">
|
||||||
|
残差温度值曲线图
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="background_temp">
|
||||||
|
背景温度曲线图
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selected.year">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a year" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem value="2008">
|
||||||
|
2008
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>日期</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
:class="cn(
|
||||||
|
'w-full justify-start text-left font-normal',
|
||||||
|
!value && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "选择日期" }}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<!-- @ts-expect-error dsa -->
|
||||||
|
<Calendar v-model="value " initial-focus />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
100
src/pages/cosmic/gravity_wave/1-multiday.vue
Normal file
100
src/pages/cosmic/gravity_wave/1-multiday.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
title: Cosmic 重力波多日
|
||||||
|
description: Cosmic 重力波多日
|
||||||
|
group: COSMIC
|
||||||
|
item_name: 重力波统计
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
if mode == "布伦特-维萨拉频率分布":
|
||||||
|
await run_sync(p.plot_heatmap_tempNz)()
|
||||||
|
elif mode == "不同高度下的逐日统计分析":
|
||||||
|
await run_sync(p.plot_heatmap_tempPtz)()
|
||||||
|
elif mode == "每月浮力频率变化趋势":
|
||||||
|
await run_sync(p.plot_floatage_trend)()
|
||||||
|
elif mode == "每月平均重力势能的折线图":
|
||||||
|
await run_sync(p.plot_monthly_energy)()
|
||||||
|
*/
|
||||||
|
const MODES = [
|
||||||
|
'不同高度下的逐日统计分析',
|
||||||
|
// '每月浮力频率变化趋势',
|
||||||
|
'每月平均重力势能的折线图',
|
||||||
|
'浮力频率均值',
|
||||||
|
'每月平均N^2的折线图',
|
||||||
|
]
|
||||||
|
const lat_ranges = [
|
||||||
|
'-60 ~ -50',
|
||||||
|
'-50 ~ -40',
|
||||||
|
'-40 ~ -30',
|
||||||
|
'-30 ~ -20',
|
||||||
|
'-20 ~ -10',
|
||||||
|
'-10 ~ 0',
|
||||||
|
'0 ~ 10',
|
||||||
|
'10 ~ 20',
|
||||||
|
'20 ~ 30',
|
||||||
|
'30 ~ 40',
|
||||||
|
'40 ~ 50',
|
||||||
|
'50 ~ 60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const selected = reactive({
|
||||||
|
year: '2008',
|
||||||
|
// begin_day
|
||||||
|
mode: '不同高度下的逐日统计分析',
|
||||||
|
lat_range: '0 ~ 10',
|
||||||
|
})
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const q = new URLSearchParams()
|
||||||
|
q.set('year', selected.year)
|
||||||
|
q.set('mode', selected.mode)
|
||||||
|
q.set('lat_range', selected.lat_range)
|
||||||
|
return `/cosmic/render/gravity_wave/multiday?${q}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selected.year">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in ['2008']" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>统计参数</Label>
|
||||||
|
<Tabs v-model="selected.mode">
|
||||||
|
<TabsList class="grid grid-cols-1 w-full">
|
||||||
|
<TabsTrigger v-for="mode in MODES" :key="mode" :value="mode">
|
||||||
|
{{ mode }}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="lat_range in lat_ranges" :key="lat_range" :value="lat_range">
|
||||||
|
{{ lat_range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
170
src/pages/cosmic/planet_wave/0-perday.vue
Normal file
170
src/pages/cosmic/planet_wave/0-perday.vue
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{"meta":{
|
||||||
|
"title":"COSMIC",
|
||||||
|
"description":"行星波提取",
|
||||||
|
"group":"COSMIC",
|
||||||
|
"item_name":"行星波提取"
|
||||||
|
}}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
CalendarDate,
|
||||||
|
createCalendar,
|
||||||
|
DateFormatter,
|
||||||
|
type DateValue,
|
||||||
|
getLocalTimeZone,
|
||||||
|
toCalendar,
|
||||||
|
} from '@internationalized/date'
|
||||||
|
import DenseFramework from '~/components/DenseFramework.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const selectedT = ref<'5' | '10' | '16'>('5')
|
||||||
|
const selectedH = ref(50)
|
||||||
|
const selectedRange = ref('-30')
|
||||||
|
const selectedYear = ref('2008')
|
||||||
|
const ranges = ['-30', '-60', '30', '60']
|
||||||
|
const df = new DateFormatter('zh-CN', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const ca = createCalendar('zh-CN')
|
||||||
|
const targetDate = ref<DateValue>()
|
||||||
|
|
||||||
|
function getDayOfYear(date: Date) {
|
||||||
|
const start = new Date(date.getFullYear(), 0, 0)
|
||||||
|
// @ts-expect-error date - start
|
||||||
|
const diff = date - start
|
||||||
|
const oneDay = 1000 * 60 * 60 * 24
|
||||||
|
return Math.floor(diff / oneDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(selectedYear, () => {
|
||||||
|
targetDate.value = toCalendar(
|
||||||
|
new CalendarDate(Number.parseInt(selectedYear.value), 1, 1),
|
||||||
|
ca,
|
||||||
|
).add({ days: Math.floor(Number.parseInt(selectedT.value) * 1.5) })
|
||||||
|
})
|
||||||
|
|
||||||
|
const minDateValue = computed(() => {
|
||||||
|
const currentYearBeginDate = new CalendarDate(Number.parseInt(selectedYear.value), 1, 1)
|
||||||
|
return toCalendar(currentYearBeginDate, ca).add({ days: Math.floor(Number.parseInt(selectedT.value) * 1.5) })
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxDateValue = computed(() => {
|
||||||
|
const currentYearBeginDate = new CalendarDate(Number.parseInt(selectedYear.value) + 1, 1, 1)
|
||||||
|
return toCalendar(currentYearBeginDate, ca).subtract({ days: Math.floor(Number.parseInt(selectedT.value) * 1.5) })
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
targetDate.value = toCalendar(
|
||||||
|
new CalendarDate(Number.parseInt(selectedYear.value), 1, 1),
|
||||||
|
ca,
|
||||||
|
).add({ days: Math.floor(Number.parseInt(selectedT.value) * 1.5) })
|
||||||
|
})
|
||||||
|
const fetchUrl = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
if (!targetDate.value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const targetDay = getDayOfYear(targetDate.value.toDate(getLocalTimeZone()))
|
||||||
|
const startDay = targetDay - Math.floor(1.5 * Number.parseInt(selectedT.value))
|
||||||
|
query.set('T', selectedT.value)
|
||||||
|
query.set('target_h', selectedH.value.toString())
|
||||||
|
query.set('target_lat', selectedRange.value)
|
||||||
|
query.set('year', selectedYear.value)
|
||||||
|
query.set('start_day', startDay.toString())
|
||||||
|
return `/cosmic/render/planet_wave/perday?${query}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="fetchUrl">
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selectedYear">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in ['2008']" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="T_range">行星波周期</Label>
|
||||||
|
<Tabs id="T_range" v-model="selectedT" default-value="5">
|
||||||
|
<TabsList class="grid grid-cols-3 w-full">
|
||||||
|
<TabsTrigger value="5">
|
||||||
|
5日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="10">
|
||||||
|
10日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="16">
|
||||||
|
16日
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<!-- TODO: not implemented -->
|
||||||
|
<Label>高度(km)</Label>
|
||||||
|
<NumberField
|
||||||
|
id="start"
|
||||||
|
v-model:model-value="selectedH" :format-options="{
|
||||||
|
useGrouping: false,
|
||||||
|
}" :default-value="60"
|
||||||
|
:step="10"
|
||||||
|
:max="60"
|
||||||
|
:min="0"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selectedRange">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="range in ranges" :key="range" :value="range">
|
||||||
|
{{ range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>日期</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
:class="cn(
|
||||||
|
'w-full justify-start text-left font-normal',
|
||||||
|
!targetDate && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span i-md-calendar />
|
||||||
|
{{ targetDate ? df.format(targetDate.toDate(getLocalTimeZone())) : "选择日期" }}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<!-- @ts-expect-error dsa -->
|
||||||
|
<Calendar
|
||||||
|
v-model="targetDate " initial-focus
|
||||||
|
:is-date-disabled="(date:DateValue) => date < minDateValue || date > maxDateValue"
|
||||||
|
:prevent-deselect="true"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<div>
|
||||||
|
拟合起始日期 {{ targetDate?.subtract({ days: Math.floor(1.5 * Number.parseInt(selectedT)) }) }}
|
||||||
|
<br>
|
||||||
|
拟合结束日期 {{ targetDate?.add({ days: Math.floor(1.5 * Number.parseInt(selectedT)) }) }}
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
104
src/pages/cosmic/planet_wave/1-daily.vue
Normal file
104
src/pages/cosmic/planet_wave/1-daily.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{"meta":{
|
||||||
|
"title":"COSMIC",
|
||||||
|
"description":"行星波统计",
|
||||||
|
"group":"COSMIC",
|
||||||
|
"item_name":"行星波统计"
|
||||||
|
}}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import DenseFramework from '~/components/DenseFramework.vue'
|
||||||
|
|
||||||
|
const selectedT = ref<'5' | '10' | '16'>('5')
|
||||||
|
const selectedH = ref(50)
|
||||||
|
const selectedRange = ref('-30')
|
||||||
|
const selectedK = ref(0)
|
||||||
|
const selectedYear = ref('2008')
|
||||||
|
const ranges = ['-30', '-60', '30', '60']
|
||||||
|
|
||||||
|
const fetchUrl = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('T', selectedT.value)
|
||||||
|
query.set('target_h', selectedH.value.toString())
|
||||||
|
query.set('target_lat', selectedRange.value)
|
||||||
|
query.set('k', selectedK.value.toString())
|
||||||
|
return `/cosmic/render/planet_wave/daily?${query}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="fetchUrl">
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selectedYear">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in ['2008']" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label for="T_range">行星波周期</Label>
|
||||||
|
<Tabs id="T_range" v-model="selectedT" default-value="5">
|
||||||
|
<TabsList class="grid grid-cols-3 w-full">
|
||||||
|
<TabsTrigger value="5">
|
||||||
|
5日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="10">
|
||||||
|
10日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="16">
|
||||||
|
16日
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<!-- TODO: not implemented -->
|
||||||
|
<Label>高度(km)</Label>
|
||||||
|
<NumberField
|
||||||
|
id="start"
|
||||||
|
v-model:model-value="selectedH" :format-options="{
|
||||||
|
useGrouping: false,
|
||||||
|
}" :default-value="50"
|
||||||
|
:step="10"
|
||||||
|
:max="60"
|
||||||
|
:min="0"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selectedRange">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="range in ranges" :key="range" :value="range">
|
||||||
|
{{ range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>波数 k</Label>
|
||||||
|
<NumberField
|
||||||
|
v-model="selectedK"
|
||||||
|
:default-value="0"
|
||||||
|
:max="4" :min="-4" :step="1"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
14
src/pages/debug.vue
Normal file
14
src/pages/debug.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="grid grid-cols-3">
|
||||||
|
<pre>
|
||||||
|
{{ JSON.stringify($router.getRoutes(), null, 4) }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
24
src/pages/radar/planet_wave/0-single.vue
Normal file
24
src/pages/radar/planet_wave/0-single.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"流星雷达 行星波单次",
|
||||||
|
"description":"行星波提取",
|
||||||
|
"group":"流星雷达",
|
||||||
|
"item_name":"行星波提取"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const url = ref('')
|
||||||
|
|
||||||
|
const mode = '行星波' as const
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RadarSingle is-day :model-value="url" :wave-type="mode" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
14
src/pages/radar/planet_wave/1-stats.vue
Normal file
14
src/pages/radar/planet_wave/1-stats.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"title": "流星雷达",
|
||||||
|
"description": "行星波统计",
|
||||||
|
"group": "流星雷达",
|
||||||
|
"item_name": "行星波统计"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RadarSingle wave-type="行星波" />
|
||||||
|
</template>
|
||||||
24
src/pages/radar/tidal_wave/0-single.vue
Normal file
24
src/pages/radar/tidal_wave/0-single.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"潮汐波单次",
|
||||||
|
"description":"潮汐波提取",
|
||||||
|
"group":"流星雷达",
|
||||||
|
"item_name":"潮汐波提取"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const url = ref('')
|
||||||
|
|
||||||
|
const mode = '潮汐波' as const
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RadarSingle :model-value="url" :wave-type="mode" :is-day="true" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
14
src/pages/radar/tidal_wave/1-stats.vue
Normal file
14
src/pages/radar/tidal_wave/1-stats.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"title": "流星雷达",
|
||||||
|
"description": "潮汐波统计",
|
||||||
|
"group": "流星雷达",
|
||||||
|
"item_name": "潮汐波统计"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RadarSingle wave-type="潮汐波" />
|
||||||
|
</template>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
<route lang="json">
|
<!-- <route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "流星雷达-热力图",
|
"title": "流星雷达-热力图",
|
||||||
@ -7,28 +7,19 @@
|
|||||||
"item_name": "热力图"
|
"item_name": "热力图"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</route>
|
</route> -->
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
const selectedMode = ref('2日行星波')
|
||||||
import { API_BASE_URL } from '~/CONSTANT'
|
|
||||||
|
|
||||||
const selectedMode = ref('潮汐波')
|
|
||||||
const selectedWave = ref('潮汐波')
|
const selectedWave = ref('潮汐波')
|
||||||
const selectedDateType = ref('day')
|
const selectedDateType = ref('day')
|
||||||
const selectedStation = ref('武汉左岭镇站')
|
const selectedStation = ref('武汉左岭镇站')
|
||||||
const selectedYear = ref('2017')
|
const selectedYear = ref('2017')
|
||||||
const selectedWindType = ref('uwind')
|
const selectedWindType = ref('uwind')
|
||||||
const selectedH = ref(90000)
|
const selectedH = ref(94000)
|
||||||
const selectedDate = ref('20170316')
|
const selectedDate = ref('20170316')
|
||||||
const selectedMonth = ref('1')
|
const selectedMonth = ref('1')
|
||||||
|
|
||||||
const imageResult = reactive<ImageResult>({
|
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const modes = [
|
const modes = [
|
||||||
'2日行星波',
|
'2日行星波',
|
||||||
'5日行星波',
|
'5日行星波',
|
||||||
@ -55,6 +46,7 @@ const queryUrl = computed(() => {
|
|||||||
query.set('model_name', mode)
|
query.set('model_name', mode)
|
||||||
query.set('wind_type', windType)
|
query.set('wind_type', windType)
|
||||||
query.set('H', selectedH.value.toString())
|
query.set('H', selectedH.value.toString())
|
||||||
|
query.set('mode', selectedDateType.value)
|
||||||
if (selectedDateType.value === 'day') {
|
if (selectedDateType.value === 'day') {
|
||||||
const queryDay = `${selectedDate.value.slice(0, 4)}-${selectedDate.value.slice(4, 6)}-${selectedDate.value.slice(6, 8)}`
|
const queryDay = `${selectedDate.value.slice(0, 4)}-${selectedDate.value.slice(4, 6)}-${selectedDate.value.slice(6, 8)}`
|
||||||
query.set('day', queryDay)
|
query.set('day', queryDay)
|
||||||
@ -63,32 +55,17 @@ const queryUrl = computed(() => {
|
|||||||
query.set('month', selectedMonth.value)
|
query.set('month', selectedMonth.value)
|
||||||
}
|
}
|
||||||
// const query = `?station=${station}&year=${year}&model_name=${mode}&wind_type=${windType}&H=${selectedH.value}`
|
// const query = `?station=${station}&year=${year}&model_name=${mode}&wind_type=${windType}&H=${selectedH.value}`
|
||||||
const path = `${API_BASE_URL}/radar/render/heatmap?${query}`
|
const path = `/radar/render/heatmap?${query}`
|
||||||
return path
|
return path
|
||||||
})
|
})
|
||||||
|
|
||||||
const { onFetchResponse, isFetching, execute } = useFetch(queryUrl, { immediate: false })
|
|
||||||
|
|
||||||
onFetchResponse(async (resp) => {
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(isFetching, (fetching) => {
|
|
||||||
if (fetching) {
|
|
||||||
imageResult.result = 'pending'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const resp = await fetch(`${API_BASE_URL}/radar/metadata`)
|
const resp = await baseFetch(`/radar/metadata`).json()
|
||||||
const data = await resp.json()
|
const data = await resp.data.value
|
||||||
// use regex to extract the year from the path,
|
// use regex to extract the year from the path,
|
||||||
// ./radar/data\\武汉左岭镇站\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
|
// ./radar/data\\武汉左岭镇站\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
|
||||||
const station_pattern = /data\\(.*?)\\/
|
const station_pattern = /data\/(.*?)\//
|
||||||
const year_pattern = /(\d{4})\\ZLT_/
|
const year_pattern = /(\d{4})\/ZLT_/
|
||||||
const date_pattern = /01D_(\d{8})\.txt/
|
const date_pattern = /01D_(\d{8})\.txt/
|
||||||
const pairs = data.map((source_text: string) => {
|
const pairs = data.map((source_text: string) => {
|
||||||
const station = source_text.match(station_pattern)?.[1]
|
const station = source_text.match(station_pattern)?.[1]
|
||||||
@ -108,10 +85,16 @@ onMounted(async () => {
|
|||||||
|
|
||||||
paths.value = data
|
paths.value = data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(selectedYear, (newV) => {
|
||||||
|
selectedDate.value = Array.from(dates.value).filter(
|
||||||
|
d => d.startsWith(newV),
|
||||||
|
)[0]
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DenseFramework :image-result="imageResult" @submit="execute">
|
<DenseFramework :image-query="queryUrl">
|
||||||
<div>
|
<div>
|
||||||
<div flex="~ col gap-3" py-3>
|
<div flex="~ col gap-3" py-3>
|
||||||
<Label for="waves">波类型</Label>
|
<Label for="waves">波类型</Label>
|
||||||
@ -127,7 +110,7 @@ onMounted(async () => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
<div v-if="selectedWave === '行星波'">
|
<div v-if="selectedWave === '行星波'">
|
||||||
<Label>行星波类型</Label>
|
<Label>行星波类型</Label>
|
||||||
<Tabs v-model="selectedMode" default-value="uwind">
|
<Tabs v-model="selectedMode" default-value="2日行星波">
|
||||||
<TabsList class="grid grid-cols-4 w-full">
|
<TabsList class="grid grid-cols-4 w-full">
|
||||||
<TabsTrigger v-for="m in modes" :key="m" :value="m">
|
<TabsTrigger v-for="m in modes" :key="m" :value="m">
|
||||||
{{ m.replace("行星波", "") }}
|
{{ m.replace("行星波", "") }}
|
||||||
@ -179,7 +162,7 @@ onMounted(async () => {
|
|||||||
id="start"
|
id="start"
|
||||||
v-model:model-value="selectedH" :format-options="{
|
v-model:model-value="selectedH" :format-options="{
|
||||||
useGrouping: false,
|
useGrouping: false,
|
||||||
}" :default-value="90000"
|
}" :default-value="94000"
|
||||||
>
|
>
|
||||||
<NumberFieldContent>
|
<NumberFieldContent>
|
||||||
<NumberFieldDecrement />
|
<NumberFieldDecrement />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<route lang="json">
|
<!-- <route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "流星雷达-潮汐波时空变化",
|
"title": "流星雷达-潮汐波时空变化",
|
||||||
@ -7,23 +7,16 @@
|
|||||||
"item_name": "潮汐波时空变化"
|
"item_name": "潮汐波时空变化"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</route>
|
</route> -->
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
const selectedMode = ref('2日行星波')
|
||||||
import { API_BASE_URL } from '~/CONSTANT'
|
const selectedWave = ref('潮汐波')
|
||||||
|
|
||||||
const selectedMode = ref('潮汐波')
|
|
||||||
const selectedStation = ref('武汉左岭镇站')
|
const selectedStation = ref('武汉左岭镇站')
|
||||||
const selectedYear = ref('2017')
|
const selectedYear = ref('2017')
|
||||||
const imageResult = reactive<ImageResult>({
|
const selectedMonthRange = reactive({ start: '1', end: '12' })
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const modes = [
|
const modes = [
|
||||||
'潮汐波',
|
|
||||||
'2日行星波',
|
'2日行星波',
|
||||||
'5日行星波',
|
'5日行星波',
|
||||||
'10日行星波',
|
'10日行星波',
|
||||||
@ -35,36 +28,24 @@ const stations = ref<Set<string>>(new Set())
|
|||||||
const years = ref<Set<string>>(new Set())
|
const years = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
const queryUrl = computed(() => {
|
const queryUrl = computed(() => {
|
||||||
const station = encodeURIComponent(selectedStation.value)
|
// const query = `?station=${station}&year=${year}&model_name=${mode}`
|
||||||
const year = encodeURIComponent(selectedYear.value)
|
const query = new URLSearchParams()
|
||||||
const mode = encodeURIComponent(selectedMode.value)
|
query.set('station', selectedStation.value)
|
||||||
const query = `?station=${station}&year=${year}&model_name=${mode}`
|
query.set('year', selectedYear.value)
|
||||||
const path = `${API_BASE_URL}/radar/render/v2${query}`
|
query.set('model_name', selectedWave.value === '潮汐波' ? '潮汐波' : selectedMode.value)
|
||||||
|
query.set('start_month', selectedMonthRange.start)
|
||||||
|
query.set('end_month', selectedMonthRange.end)
|
||||||
|
const path = `/radar/render/changes?${query}`
|
||||||
return path
|
return path
|
||||||
})
|
})
|
||||||
|
|
||||||
const { onFetchResponse, isFetching, execute } = useFetch(queryUrl, { refetch: true })
|
|
||||||
|
|
||||||
onFetchResponse(async (resp) => {
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(isFetching, (fetching) => {
|
|
||||||
if (fetching) {
|
|
||||||
imageResult.result = 'pending'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const resp = await fetch(`${API_BASE_URL}/radar/metadata`)
|
const resp = await baseFetch(`/radar/metadata`).json()
|
||||||
const data = await resp.json()
|
const data = await resp.data.value!
|
||||||
// use regex to extract the year from the path,
|
// use regex to extract the year from the path,
|
||||||
// ./radar/data\\武汉左岭镇站\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
|
// ./radar/data\\武汉左岭镇站\\2017\\ZLT_MET01_DLL_L21_01D_20170316.txt
|
||||||
const station_pattern = /data\\(.*?)\\/
|
const station_pattern = /data\/(.*?)\//
|
||||||
const year_pattern = /(\d{4})\\ZLT_/
|
const year_pattern = /(\d{4})\/ZLT_/
|
||||||
const pairs = data.map((source_text: string) => {
|
const pairs = data.map((source_text: string) => {
|
||||||
const station = source_text.match(station_pattern)?.[1]
|
const station = source_text.match(station_pattern)?.[1]
|
||||||
const year = source_text.match(year_pattern)?.[1]
|
const year = source_text.match(year_pattern)?.[1]
|
||||||
@ -83,22 +64,31 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DenseFramework :image-result="imageResult" @submit="execute">
|
<DenseFramework :image-query="queryUrl">
|
||||||
<div>
|
<div>
|
||||||
<div flex="~ col items-stretch gap-3" py-3>
|
<div flex="~ col items-stretch gap-3" py-3>
|
||||||
<Select v-model="selectedMode">
|
<Label>计算模式</Label>
|
||||||
<SelectTrigger>
|
<Tabs id="waves" v-model="selectedWave" default-value="潮汐波">
|
||||||
<SelectValue placeholder="Select a fruit" />
|
<TabsList class="grid grid-cols-2 w-full">
|
||||||
</SelectTrigger>
|
<TabsTrigger value="潮汐波">
|
||||||
<SelectContent>
|
潮汐波
|
||||||
<SelectGroup>
|
</TabsTrigger>
|
||||||
<SelectLabel>计算模式</SelectLabel>
|
<TabsTrigger value="行星波">
|
||||||
<SelectItem v-for="mode in modes" :key="mode" :value="mode">
|
行星波
|
||||||
{{ mode }}
|
</TabsTrigger>
|
||||||
</SelectItem>
|
</TabsList>
|
||||||
</SelectGroup>
|
</Tabs>
|
||||||
</SelectContent>
|
<div v-if="selectedWave === '行星波'">
|
||||||
</Select>
|
<Label>行星波类型</Label>
|
||||||
|
<Tabs v-model="selectedMode" default-value="uwind">
|
||||||
|
<TabsList class="grid grid-cols-4 w-full">
|
||||||
|
<TabsTrigger v-for="m in modes" :key="m" :value="m">
|
||||||
|
{{ m.replace("行星波", "") }}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
<Label>观测站</Label>
|
||||||
<Select v-model="selectedStation">
|
<Select v-model="selectedStation">
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a fruit" />
|
<SelectValue placeholder="Select a fruit" />
|
||||||
@ -112,19 +102,56 @@ onMounted(async () => {
|
|||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
<Label>年份</Label>
|
||||||
<Select v-model="selectedYear">
|
<Select v-model="selectedYear">
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a fruit" />
|
<SelectValue placeholder="Select a fruit" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>天</SelectLabel>
|
<SelectLabel>年</SelectLabel>
|
||||||
<SelectItem v-for="year in years" :key="year" :value="year">
|
<SelectItem v-for="year in years" :key="year" :value="year">
|
||||||
{{ year }}
|
{{ year }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
<Label>起始月份</Label>
|
||||||
|
<Select v-model="selectedMonthRange.start">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择起始月" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="month in [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
||||||
|
]" :key="month" :value="month.toString()"
|
||||||
|
>
|
||||||
|
{{ month }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>终止月份</Label>
|
||||||
|
<Select v-model="selectedMonthRange.end">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择终止月" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>月</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="month in [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
||||||
|
]" :key="month" :value="month.toString()"
|
||||||
|
>
|
||||||
|
{{ month }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DenseFramework>
|
</DenseFramework>
|
||||||
|
|||||||
@ -1,121 +0,0 @@
|
|||||||
<route lang="json">
|
|
||||||
{
|
|
||||||
"meta":{
|
|
||||||
"title":"Saber日周期功率波图",
|
|
||||||
"description":"Saber日周期功率波图",
|
|
||||||
"group":"Saber",
|
|
||||||
"item_name":"日周期功率波图"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</route>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
|
||||||
import { API_BASE_URL } from '~/CONSTANT'
|
|
||||||
import { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths } from './utils'
|
|
||||||
|
|
||||||
const selected = reactive({
|
|
||||||
path: '',
|
|
||||||
day: '',
|
|
||||||
cycle_no: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const imageResult = reactive<ImageResult>({
|
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const urll = computed(() => {
|
|
||||||
const path = encodeURIComponent(selected.path)
|
|
||||||
const day = encodeURIComponent(selected.day)
|
|
||||||
const query = `path=${path}&day=${day}&cycle_no=${selected.cycle_no}`
|
|
||||||
return `${API_BASE_URL}/saber/render/day_cycle_power_wave_plot?${query}`
|
|
||||||
})
|
|
||||||
const { onFetchResponse, isFetching, execute } = useFetch(
|
|
||||||
urll,
|
|
||||||
{ immediate: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(isFetching, (fetching) => {
|
|
||||||
if (fetching) {
|
|
||||||
imageResult.result = 'pending'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onFetchResponse(async (resp) => {
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refreshPath()
|
|
||||||
selected.path = saberPaths.value[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => selected.path, () => {
|
|
||||||
refreshCurrentSaberDays(selected.path)
|
|
||||||
if (selected.day === '') {
|
|
||||||
selected.day = currentSaberDays.value[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function renderPath(path: string) {
|
|
||||||
const yearPattern = /\/data\\(\d{4})/
|
|
||||||
const year = path.match(yearPattern)?.[1]
|
|
||||||
const monthPattern = /Temp_O3_(.*)(\d{4})/
|
|
||||||
const month = path.match(monthPattern)?.[1]
|
|
||||||
return `${year}年${month}`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DenseFramework :image-result="imageResult" @submit="execute">
|
|
||||||
<div>
|
|
||||||
<div flex="~ col items-stretch gap-3" py-3>
|
|
||||||
<Label for="day">年月</Label>
|
|
||||||
<Select v-model="selected.path">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>月份</SelectLabel>
|
|
||||||
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
|
|
||||||
{{ renderPath(path) }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label for="day">天数</Label>
|
|
||||||
<Select id="day" v-model="selected.day">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Day</SelectLabel>
|
|
||||||
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
|
|
||||||
{{ day }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label for="age">Cycle No.</Label>
|
|
||||||
<NumberField id="age" v-model:model-value="selected.cycle_no" :default-value="1" :min="0">
|
|
||||||
<NumberFieldContent>
|
|
||||||
<NumberFieldDecrement />
|
|
||||||
<NumberFieldInput />
|
|
||||||
<NumberFieldIncrement />
|
|
||||||
</NumberFieldContent>
|
|
||||||
</NumberField>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DenseFramework>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
<route lang="json">
|
|
||||||
{
|
|
||||||
"meta": {
|
|
||||||
"title": "Saber日周期功率波图",
|
|
||||||
"description": "Saber日周期功率波图",
|
|
||||||
"group": "Saber",
|
|
||||||
"item_name": "日周期功率波图"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</route>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
|
||||||
import { API_BASE_URL } from '~/CONSTANT'
|
|
||||||
import { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths } from './utils'
|
|
||||||
|
|
||||||
const selected = reactive({
|
|
||||||
path: './saber/data\\2012\\SABER_Temp_O3_April2012_v2.0.nc',
|
|
||||||
day: '',
|
|
||||||
cycle_no: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const imageResult = reactive<ImageResult>({
|
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const urll = computed(() => {
|
|
||||||
const path = encodeURIComponent(selected.path)
|
|
||||||
const day = encodeURIComponent(selected.day)
|
|
||||||
const query = `path=${path}&day=${day}&cycle_no=${selected.cycle_no}`
|
|
||||||
return `${API_BASE_URL}/saber/render/day_fft_ifft_plot?${query}`
|
|
||||||
})
|
|
||||||
const { onFetchResponse, isFetching, execute } = useFetch(
|
|
||||||
urll,
|
|
||||||
{ immediate: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(isFetching, (fetching) => {
|
|
||||||
if (fetching) {
|
|
||||||
imageResult.result = 'pending'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onFetchResponse(async (resp) => {
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refreshPath()
|
|
||||||
selected.path = saberPaths.value[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => selected.path, () => {
|
|
||||||
refreshCurrentSaberDays(selected.path)
|
|
||||||
if (selected.day === '') {
|
|
||||||
selected.day = currentSaberDays.value[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function renderPath(path: string) {
|
|
||||||
const yearPattern = /\/data\\(\d{4})/
|
|
||||||
const year = path.match(yearPattern)?.[1]
|
|
||||||
const monthPattern = /Temp_O3_(.*)(\d{4})/
|
|
||||||
const month = path.match(monthPattern)?.[1]
|
|
||||||
return `${year}年${month}`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DenseFramework :image-result="imageResult" @submit="execute">
|
|
||||||
<div>
|
|
||||||
<div flex="~ col items-stretch gap-3" py-3>
|
|
||||||
<Label for="day">年月</Label>
|
|
||||||
<Select v-model="selected.path">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>月份</SelectLabel>
|
|
||||||
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
|
|
||||||
{{ renderPath(path) }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label for="day">天数</Label>
|
|
||||||
<Select id="day" v-model="selected.day">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Day</SelectLabel>
|
|
||||||
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
|
|
||||||
{{ day }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label for="age">Cycle No.</Label>
|
|
||||||
<NumberField id="age" v-model:model-value="selected.cycle_no" :default-value="1" :min="0">
|
|
||||||
<NumberFieldContent>
|
|
||||||
<NumberFieldDecrement />
|
|
||||||
<NumberFieldInput />
|
|
||||||
<NumberFieldIncrement />
|
|
||||||
</NumberFieldContent>
|
|
||||||
</NumberField>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DenseFramework>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
48
src/pages/saber/gravity_wave/0-perday.vue
Normal file
48
src/pages/saber/gravity_wave/0-perday.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
title: Saber 重力波单日
|
||||||
|
description: Saber 重力波单日
|
||||||
|
group: Saber
|
||||||
|
item_name: 重力波提取
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Day_cycle_power_wave_plot from '~/components/dense/saber/day_cycle_power_wave_plot.vue'
|
||||||
|
import Day_fft_ifft_plot from '~/components/dense/saber/day_fft_ifft_plot.vue'
|
||||||
|
import Plot_wave_fitting from '~/components/dense/saber/plot_wave_fitting.vue'
|
||||||
|
|
||||||
|
const component_mapping = {
|
||||||
|
day_cycle_power_wave_plot: Day_cycle_power_wave_plot,
|
||||||
|
day_fft_ifft_plot: Day_fft_ifft_plot,
|
||||||
|
plot_wave_fitting: Plot_wave_fitting,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const currentMode = ref<keyof typeof component_mapping>('day_cycle_power_wave_plot')
|
||||||
|
|
||||||
|
const currentComponent = computed(() => {
|
||||||
|
return component_mapping[currentMode.value]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component :is="currentComponent">
|
||||||
|
<div>
|
||||||
|
<Label>选择模式</Label>
|
||||||
|
<Tabs v-model="currentMode" class="w-full" default-value="day_cycle_power_wave_plot">
|
||||||
|
<TabsList class="grid grid-cols-1 w-full">
|
||||||
|
<TabsTrigger v-for="key in Object.keys(component_mapping)" :key="key" class="w-full" :value="key">
|
||||||
|
{{ {
|
||||||
|
day_cycle_power_wave_plot: '平均浮力频率和平均势能随高度变化',
|
||||||
|
day_fft_ifft_plot: '傅里叶变化滤波处理',
|
||||||
|
plot_wave_fitting: '逐日波形拟合',
|
||||||
|
}[key] }}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
11
src/pages/saber/gravity_wave/1-monthly.vue
Normal file
11
src/pages/saber/gravity_wave/1-monthly.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
title: Saber 重力波月统计
|
||||||
|
description: Saber 重力波月统计
|
||||||
|
group: Saber
|
||||||
|
item_name: 重力波统计
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Month_year_power_wave_plot />
|
||||||
|
</template>
|
||||||
@ -1,89 +0,0 @@
|
|||||||
<route lang="json">
|
|
||||||
{
|
|
||||||
"meta": {
|
|
||||||
"title": "Saber月周期功率波图",
|
|
||||||
"description": "Saber月周期功率波图",
|
|
||||||
"group": "Saber",
|
|
||||||
"item_name": "月周期功率波图"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</route>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
|
||||||
import { API_BASE_URL } from '~/CONSTANT'
|
|
||||||
import { refreshPath, saberPaths } from './utils'
|
|
||||||
|
|
||||||
const selected = reactive({
|
|
||||||
path: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const imageResult = reactive<ImageResult>({
|
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const urll = computed(() => {
|
|
||||||
const path = encodeURIComponent(selected.path)
|
|
||||||
const query = `path=${path}`
|
|
||||||
return `${API_BASE_URL}/saber/render/month_power_wave_plot?${query}`
|
|
||||||
})
|
|
||||||
const { onFetchResponse, execute, isFetching } = useFetch(
|
|
||||||
urll,
|
|
||||||
{ immediate: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(isFetching, (fetching) => {
|
|
||||||
if (fetching) {
|
|
||||||
imageResult.result = 'pending'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onFetchResponse(async (resp) => {
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refreshPath()
|
|
||||||
selected.path = saberPaths.value[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
function renderPath(path: string) {
|
|
||||||
const yearPattern = /\/data\\(\d{4})/
|
|
||||||
const year = path.match(yearPattern)?.[1]
|
|
||||||
const monthPattern = /Temp_O3_(.*)(\d{4})/
|
|
||||||
const month = path.match(monthPattern)?.[1]
|
|
||||||
return `${year}年${month}`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DenseFramework :image-result="imageResult" @submit="execute">
|
|
||||||
<div>
|
|
||||||
<div flex="~ row items-center gap-3" py-3>
|
|
||||||
<Label for="day">年月</Label>
|
|
||||||
<Select v-model="selected.path">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>月份</SelectLabel>
|
|
||||||
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
|
|
||||||
{{ renderPath(path) }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DenseFramework>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
188
src/pages/saber/planet_wave/0-perday.vue
Normal file
188
src/pages/saber/planet_wave/0-perday.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"Saber 行星波月统计",
|
||||||
|
"description":"Saber 行星波月统计",
|
||||||
|
"group":"Saber",
|
||||||
|
"item_name":"行星波提取"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
CalendarDate,
|
||||||
|
createCalendar,
|
||||||
|
DateFormatter,
|
||||||
|
type DateValue,
|
||||||
|
getLocalTimeZone,
|
||||||
|
toCalendar,
|
||||||
|
} from '@internationalized/date'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('zh-CN', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const ca = createCalendar('zh-CN')
|
||||||
|
const selected = reactive({
|
||||||
|
year: '2018',
|
||||||
|
T: '16',
|
||||||
|
k: 0,
|
||||||
|
H: 90,
|
||||||
|
range: '60',
|
||||||
|
|
||||||
|
})
|
||||||
|
const targetDate = ref<DateValue>()
|
||||||
|
|
||||||
|
function getDayOfYear(date: Date) {
|
||||||
|
const start = new Date(date.getFullYear(), 0, 0)
|
||||||
|
// @ts-expect-error date - start
|
||||||
|
const diff = date - start
|
||||||
|
const oneDay = 1000 * 60 * 60 * 24
|
||||||
|
return Math.floor(diff / oneDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => selected.year, () => {
|
||||||
|
targetDate.value = toCalendar(
|
||||||
|
new CalendarDate(Number.parseInt(selected.year), 1, 1),
|
||||||
|
ca,
|
||||||
|
).add({ days: Number.parseInt(selected.T) * 1.5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
const minDateValue = computed(() => {
|
||||||
|
const currentYearBeginDate = new CalendarDate(Number.parseInt(selected.year), 1, 1)
|
||||||
|
return toCalendar(currentYearBeginDate, ca).add({ days: Math.floor(Number.parseInt(selected.T) * 1.5) })
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxDateValue = computed(() => {
|
||||||
|
const currentYearBeginDate = new CalendarDate(Number.parseInt(selected.year) + 1, 1, 1)
|
||||||
|
return toCalendar(currentYearBeginDate, ca).subtract({ days: Math.floor(Number.parseInt(selected.T) * 1.5) })
|
||||||
|
})
|
||||||
|
|
||||||
|
const ranges = [
|
||||||
|
'-30',
|
||||||
|
'30',
|
||||||
|
'-60',
|
||||||
|
'60',
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
targetDate.value = toCalendar(
|
||||||
|
new CalendarDate(Number.parseInt(selected.year), 1, 1),
|
||||||
|
ca,
|
||||||
|
).add({ days: Math.floor(Number.parseInt(selected.T) * 1.5) })
|
||||||
|
})
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const q = new URLSearchParams()
|
||||||
|
if (!targetDate.value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const targetDay = getDayOfYear(targetDate.value.toDate(getLocalTimeZone()))
|
||||||
|
const startDay = targetDay - Math.floor(1.5 * Number.parseInt(selected.T))
|
||||||
|
q.set('year', selected.year)
|
||||||
|
q.set('T', selected.T.toString())
|
||||||
|
q.set('k', selected.k.toString())
|
||||||
|
q.set('H', selected.H.toString())
|
||||||
|
q.set('lat_target', selected.range)
|
||||||
|
q.set('start_day', startDay.toString())
|
||||||
|
return `/saber/render/planet_wave/per_day?${q}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selected.year">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in ['2018', '2015']" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>行星波周期</Label>
|
||||||
|
<!-- can be 5,10,16 -->
|
||||||
|
<!-- use Tabs -->
|
||||||
|
<Tabs v-model="selected.T" default-value="16">
|
||||||
|
<TabsList class="grid grid-cols-3 w-full">
|
||||||
|
<TabsTrigger value="5">
|
||||||
|
5日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="10">
|
||||||
|
10日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="16">
|
||||||
|
16日
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Label>高度(km)</Label>
|
||||||
|
<NumberField
|
||||||
|
id="start"
|
||||||
|
v-model:model-value="selected.H" :format-options="{
|
||||||
|
useGrouping: false,
|
||||||
|
}" :default-value="94"
|
||||||
|
:step="20"
|
||||||
|
:max="110"
|
||||||
|
:min="30"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="range in ranges" :key="range" :value="range">
|
||||||
|
{{ range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>日期</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
:class="cn(
|
||||||
|
'w-full justify-start text-left font-normal',
|
||||||
|
!targetDate && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span i-md-calendar />
|
||||||
|
{{ targetDate ? df.format(targetDate.toDate(getLocalTimeZone())) : "选择日期" }}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<!-- @ts-expect-error dsa -->
|
||||||
|
<Calendar
|
||||||
|
v-model="targetDate " initial-focus
|
||||||
|
:is-date-disabled="(date:DateValue) => date < minDateValue || date > maxDateValue"
|
||||||
|
:prevent-deselect="true"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<div>
|
||||||
|
拟合起始日期 {{ targetDate?.subtract({ days: Math.floor(1.5 * Number.parseInt(selected.T)) }) }}
|
||||||
|
<br>
|
||||||
|
拟合结束日期 {{ targetDate?.add({ days: Math.floor(1.5 * Number.parseInt(selected.T)) }) }}
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
118
src/pages/saber/planet_wave/1-monthly.vue
Normal file
118
src/pages/saber/planet_wave/1-monthly.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"Saber 行星波月统计",
|
||||||
|
"description":"Saber 行星波月统计",
|
||||||
|
"group":"Saber",
|
||||||
|
"item_name":"行星波统计"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const selected = reactive({
|
||||||
|
year: '2018',
|
||||||
|
T: '16',
|
||||||
|
k: 0,
|
||||||
|
H: 90,
|
||||||
|
range: '60',
|
||||||
|
})
|
||||||
|
|
||||||
|
const ranges = [
|
||||||
|
'-30',
|
||||||
|
'30',
|
||||||
|
'-60',
|
||||||
|
'60',
|
||||||
|
]
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const q = new URLSearchParams()
|
||||||
|
q.set('year', selected.year)
|
||||||
|
q.set('T', selected.T.toString())
|
||||||
|
q.set('k', selected.k.toString())
|
||||||
|
q.set('H', selected.H.toString())
|
||||||
|
q.set('lat_target', selected.range)
|
||||||
|
return `/saber/render/planet_wave/per_year/energy_plot?${q}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selected.year">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in ['2018', '2015']" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>行星波周期</Label>
|
||||||
|
<!-- can be 5,10,16 -->
|
||||||
|
<!-- use Tabs -->
|
||||||
|
<Tabs v-model="selected.T" default-value="16">
|
||||||
|
<TabsList class="grid grid-cols-3 w-full">
|
||||||
|
<TabsTrigger value="5">
|
||||||
|
5日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="10">
|
||||||
|
10日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="16">
|
||||||
|
16日
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Label>高度(km)</Label>
|
||||||
|
<NumberField
|
||||||
|
id="start"
|
||||||
|
v-model:model-value="selected.H" :format-options="{
|
||||||
|
useGrouping: false,
|
||||||
|
}" :default-value="94"
|
||||||
|
:step="20"
|
||||||
|
:max="110"
|
||||||
|
:min="30"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem v-for="range in ranges" :key="range" :value="range">
|
||||||
|
{{ range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>波数 k</Label>
|
||||||
|
<NumberField
|
||||||
|
v-model="selected.k"
|
||||||
|
:default-value="0"
|
||||||
|
:max="4" :min="-4" :step="1"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,121 +0,0 @@
|
|||||||
<route lang="json">
|
|
||||||
{
|
|
||||||
"meta": {
|
|
||||||
"title": "Saber拟合",
|
|
||||||
"description": "Saber拟合",
|
|
||||||
"group": "Saber",
|
|
||||||
"item_name": "拟合"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</route>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { ImageResult } from '~/components/ImageContainer.vue'
|
|
||||||
import { API_BASE_URL } from '~/CONSTANT'
|
|
||||||
import { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths } from './utils'
|
|
||||||
|
|
||||||
const selected = reactive({
|
|
||||||
path: './saber/data\\2012\\SABER_Temp_O3_April2012_v2.0.nc',
|
|
||||||
day: '',
|
|
||||||
height_no: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const imageResult = reactive<ImageResult>({
|
|
||||||
result: 'idle',
|
|
||||||
imageUrl: '',
|
|
||||||
message: '请你选择一个模式和日期',
|
|
||||||
})
|
|
||||||
|
|
||||||
const urll = computed(() => {
|
|
||||||
const path = encodeURIComponent(selected.path)
|
|
||||||
const day = encodeURIComponent(selected.day)
|
|
||||||
const query = `path=${path}&day=${day}&height=${selected.height_no}`
|
|
||||||
return `${API_BASE_URL}/saber/render/plot_wave_fitting?${query}`
|
|
||||||
})
|
|
||||||
const { onFetchResponse, execute, isFetching } = useFetch(
|
|
||||||
urll,
|
|
||||||
{ immediate: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(isFetching, (fetching) => {
|
|
||||||
if (fetching) {
|
|
||||||
imageResult.result = 'pending'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onFetchResponse(async (resp) => {
|
|
||||||
const blob = await resp.blob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
imageResult.result = 'success'
|
|
||||||
imageResult.imageUrl = url
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refreshPath()
|
|
||||||
selected.path = saberPaths.value[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => selected.path, () => {
|
|
||||||
refreshCurrentSaberDays(selected.path)
|
|
||||||
if (saberPaths.value.length > 0) {
|
|
||||||
selected.path = saberPaths.value[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function renderPath(path: string) {
|
|
||||||
const yearPattern = /\/data\\(\d{4})/
|
|
||||||
const year = path.match(yearPattern)?.[1]
|
|
||||||
const monthPattern = /Temp_O3_(.*)(\d{4})/
|
|
||||||
const month = path.match(monthPattern)?.[1]
|
|
||||||
return `${year}年${month}`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DenseFramework :image-result="imageResult" @submit="execute">
|
|
||||||
<div>
|
|
||||||
<div flex="~ col justify-stretch gap-3" py-3>
|
|
||||||
<Label for="day">年月</Label>
|
|
||||||
<Select v-model="selected.path">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>月份</SelectLabel>
|
|
||||||
<SelectItem v-for="path in saberPaths" :key="path" :value="path">
|
|
||||||
{{ renderPath(path) }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label for="day">天数</Label>
|
|
||||||
<Select id="day" v-model="selected.day">
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择日期" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Day</SelectLabel>
|
|
||||||
<SelectItem v-for="day in currentSaberDays" :key="day" :value="day">
|
|
||||||
{{ day }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label for="age">高度</Label>
|
|
||||||
<NumberField id="age" :default-value="18" :min="0">
|
|
||||||
<NumberFieldContent>
|
|
||||||
<NumberFieldDecrement />
|
|
||||||
<NumberFieldInput />
|
|
||||||
<NumberFieldIncrement />
|
|
||||||
</NumberFieldContent>
|
|
||||||
</NumberField>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DenseFramework>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { API_BASE_URL } from '~/CONSTANT'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const saberPaths = ref<string[]>([])
|
const saberPaths = ref<string[]>([])
|
||||||
const currentSaberDays = ref<string>('')
|
const currentSaberDays = ref<string>('')
|
||||||
@ -6,15 +6,44 @@ const currentSaberDays = ref<string>('')
|
|||||||
async function refreshPath() {
|
async function refreshPath() {
|
||||||
if (saberPaths.value.length)
|
if (saberPaths.value.length)
|
||||||
return
|
return
|
||||||
const resp = await fetch(`${API_BASE_URL}/saber/metadata`)
|
const resp = await baseFetch<string[]>(`/saber/metadata`).json()
|
||||||
const data = await resp.json()
|
const data = resp.data.value
|
||||||
saberPaths.value = data
|
saberPaths.value = data!
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCurrentSaberDays(path: string) {
|
async function refreshCurrentSaberDays(path: string) {
|
||||||
const resp = await fetch(`${API_BASE_URL}/saber/metadata/list_days?path=${path}`)
|
const resp = await baseFetch<string>(`/saber/metadata/list_days?path=${path}`).json()
|
||||||
const data = await resp.json()
|
const data = resp.data.value
|
||||||
currentSaberDays.value = data
|
currentSaberDays.value = data!
|
||||||
|
}
|
||||||
|
function renderPath(path: string) {
|
||||||
|
const yearPattern = /\/data\/(\d{4})/
|
||||||
|
const year = path.match(yearPattern)?.[1]
|
||||||
|
const monthPattern = /Temp_O3_(.*)(\d{4})/
|
||||||
|
const month_mapping = {
|
||||||
|
January: '01月',
|
||||||
|
February: '02月',
|
||||||
|
March: '03月',
|
||||||
|
April: '04月',
|
||||||
|
May: '05月',
|
||||||
|
June: '06月',
|
||||||
|
July: '07月',
|
||||||
|
August: '08月',
|
||||||
|
September: '09月',
|
||||||
|
October: '10月',
|
||||||
|
November: '11月',
|
||||||
|
December: '12月',
|
||||||
|
} as const
|
||||||
|
const month = path.match(monthPattern)?.[1] as keyof typeof month_mapping
|
||||||
|
return `${year}年${month_mapping[month]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export { currentSaberDays, refreshCurrentSaberDays, refreshPath, saberPaths }
|
function parseDayOfYear(dateString: string): Date {
|
||||||
|
const year = Number.parseInt(dateString.substring(0, 4))
|
||||||
|
const dayOfYear = Number.parseInt(dateString.substring(4)) - 1 // subtract 1 because JS dates are 0-based
|
||||||
|
const date = new Date(year, 0) // Start with January 1st of the year
|
||||||
|
date.setDate(dayOfYear + 1) // Add the days
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
export { currentSaberDays, parseDayOfYear, refreshCurrentSaberDays, refreshPath, renderPath, saberPaths }
|
||||||
|
|||||||
92
src/pages/tidi/gravity_wave/monthly.vue
Normal file
92
src/pages/tidi/gravity_wave/monthly.vue
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"TIDI 重力波统计",
|
||||||
|
"description":"TIDI 重力波统计",
|
||||||
|
"group":"TIDI",
|
||||||
|
"item_name":"重力波统计"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const selected = reactive({
|
||||||
|
year: '2015',
|
||||||
|
mode: 'monthly_height',
|
||||||
|
lat_range: '0 ~ 20',
|
||||||
|
})
|
||||||
|
|
||||||
|
const allYears = ref([] as string[])
|
||||||
|
const lat_ranges = ['0 ~ 20']
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('year', selected.year)
|
||||||
|
const mode = selected.mode
|
||||||
|
return `/tidi/render/gravity_wave/${mode}?${query}`
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const resp = await baseFetch(`/tidi/metadata`).json()
|
||||||
|
allYears.value = Array.from(new Set(resp.data.value.path.map((a: string) => {
|
||||||
|
const year_pattern = /data\/tidi\/(\d{4})\//
|
||||||
|
return a.match(year_pattern)?.[1]
|
||||||
|
})))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<Label>选择模式</Label>
|
||||||
|
<Tabs v-model="selected.mode" default-value="v1">
|
||||||
|
<TabsList class="grid grid-cols-1 w-full">
|
||||||
|
<TabsTrigger value="monthly_height">
|
||||||
|
重力波动能(取log)随高度变化热力图
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="monthly_energy">
|
||||||
|
重力波动能(取log)变化折线图
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Label>纬度带(°)</Label>
|
||||||
|
<Select v-model="selected.lat_range">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度带" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度带(°)</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="lat_range in lat_ranges"
|
||||||
|
:key="lat_range"
|
||||||
|
:value="lat_range"
|
||||||
|
>
|
||||||
|
{{ lat_range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selected.year">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem
|
||||||
|
v-for="y in allYears"
|
||||||
|
:key="y"
|
||||||
|
:value="y"
|
||||||
|
>
|
||||||
|
{{ y }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</select>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
137
src/pages/tidi/planet_wave/daily.vue
Normal file
137
src/pages/tidi/planet_wave/daily.vue
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta":{
|
||||||
|
"title":"TIDI 行星波振幅",
|
||||||
|
"icon":"mdi:telescope",
|
||||||
|
"group":"TIDI",
|
||||||
|
"item_name":"行星波提取"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const years = ref([] as string[])
|
||||||
|
const ranges = ['0 ~ 5', '5 ~ 10', '10 ~ 15', '15 ~ 20']
|
||||||
|
const heights = [70, 72.5, 75, 77.5, 80, 82.5, 85, 87.5, 90, 92.5, 95, 97.5, 100, 102.5, 105, 107.5, 110, 112.5, 115, 117.5, 120].map(String)
|
||||||
|
|
||||||
|
const selectedMode = ref('V_Meridional')
|
||||||
|
const selectedYear = ref('2017')
|
||||||
|
|
||||||
|
// const k = [ -4,-3,-2,-1,0,1,2,3,4]
|
||||||
|
const selectedK = ref(0)
|
||||||
|
const selectedT = ref('5')
|
||||||
|
const selectedRange = ref('0 ~ 5')
|
||||||
|
const selectedHeight = ref('85')
|
||||||
|
|
||||||
|
const queryUrl = computed(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
query.set('mode', selectedMode.value)
|
||||||
|
query.set('year', selectedYear.value)
|
||||||
|
query.set('k', selectedK.value.toString())
|
||||||
|
query.set('T', selectedT.value.toString())
|
||||||
|
query.set('lat_range', selectedRange.value)
|
||||||
|
query.set('height', selectedHeight.value)
|
||||||
|
return `/tidi/render/planet_wave/daily?${query}`
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const metas = await baseFetch<{ path: string [] }>(`/tidi/metadata`).json()
|
||||||
|
const _years = metas.data.value.path.map((a: string) => {
|
||||||
|
const year_pattern = /data\/tidi\/(\d{4})\//
|
||||||
|
return a.match(year_pattern)?.[1]
|
||||||
|
})
|
||||||
|
_years.sort()
|
||||||
|
years.value = Array.from(new Set(_years))
|
||||||
|
selectedYear.value = years.value[0]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DenseFramework :image-query="queryUrl">
|
||||||
|
<div flex="col gap-3 ~ justify-stretch">
|
||||||
|
<Label>计算模式</Label>
|
||||||
|
<Tabs id="modes" v-model="selectedMode" default-value="V_Meridional">
|
||||||
|
<TabsList class="grid grid-cols-2 w-full">
|
||||||
|
<TabsTrigger value="V_Meridional">
|
||||||
|
纬向风
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="V_Zonal">
|
||||||
|
经向风
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Label>年份</Label>
|
||||||
|
<Select v-model="selectedYear">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择年份" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>年份</SelectLabel>
|
||||||
|
<SelectItem v-for="year in years" :key="year" :value="year">
|
||||||
|
{{ year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>行星波周期</Label>
|
||||||
|
<Tabs v-model:model-value="selectedT" default-value="15">
|
||||||
|
<TabsList class="grid grid-cols-3 w-full">
|
||||||
|
<TabsTrigger value="5">
|
||||||
|
5日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="10">
|
||||||
|
10日
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="16">
|
||||||
|
16日
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Label>高度</Label>
|
||||||
|
<Select v-model="selectedHeight">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择高度" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>高度</SelectLabel>
|
||||||
|
<SelectItem v-for="height in heights" :key="height" :value="height">
|
||||||
|
{{ height }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>纬度范围</Label>
|
||||||
|
<Select v-model="selectedRange">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择纬度范围" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>纬度范围</SelectLabel>
|
||||||
|
<SelectItem v-for="range in ranges" :key="range" :value="range">
|
||||||
|
{{ range }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>波数 k</Label>
|
||||||
|
<NumberField
|
||||||
|
v-model="selectedK"
|
||||||
|
:max="4" :min="-4" :step="1"
|
||||||
|
>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
</div>
|
||||||
|
</DenseFramework>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
24
typed-router.d.ts
vendored
24
typed-router.d.ts
vendored
@ -20,13 +20,25 @@ declare module 'vue-router/auto-routes' {
|
|||||||
export interface RouteNamedMap {
|
export interface RouteNamedMap {
|
||||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||||
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
|
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
|
||||||
'/balloon/single': RouteRecordInfo<'/balloon/single', '/balloon/single', Record<never, never>, Record<never, never>>,
|
'/auth': RouteRecordInfo<'/auth', '/auth', Record<never, never>, Record<never, never>>,
|
||||||
'/balloon/year': RouteRecordInfo<'/balloon/year', '/balloon/year', Record<never, never>, Record<never, never>>,
|
'/balloon/gravity_wave/0-perday': RouteRecordInfo<'/balloon/gravity_wave/0-perday', '/balloon/gravity_wave/0-perday', Record<never, never>, Record<never, never>>,
|
||||||
|
'/balloon/gravity_wave/1-year': RouteRecordInfo<'/balloon/gravity_wave/1-year', '/balloon/gravity_wave/1-year', Record<never, never>, Record<never, never>>,
|
||||||
|
'/cosmic/gravity_wave/0-perday': RouteRecordInfo<'/cosmic/gravity_wave/0-perday', '/cosmic/gravity_wave/0-perday', Record<never, never>, Record<never, never>>,
|
||||||
|
'/cosmic/gravity_wave/1-multiday': RouteRecordInfo<'/cosmic/gravity_wave/1-multiday', '/cosmic/gravity_wave/1-multiday', Record<never, never>, Record<never, never>>,
|
||||||
|
'/cosmic/planet_wave/0-perday': RouteRecordInfo<'/cosmic/planet_wave/0-perday', '/cosmic/planet_wave/0-perday', Record<never, never>, Record<never, never>>,
|
||||||
|
'/cosmic/planet_wave/1-daily': RouteRecordInfo<'/cosmic/planet_wave/1-daily', '/cosmic/planet_wave/1-daily', Record<never, never>, Record<never, never>>,
|
||||||
|
'/debug': RouteRecordInfo<'/debug', '/debug', Record<never, never>, Record<never, never>>,
|
||||||
|
'/radar/planet_wave/0-single': RouteRecordInfo<'/radar/planet_wave/0-single', '/radar/planet_wave/0-single', Record<never, never>, Record<never, never>>,
|
||||||
|
'/radar/planet_wave/1-stats': RouteRecordInfo<'/radar/planet_wave/1-stats', '/radar/planet_wave/1-stats', Record<never, never>, Record<never, never>>,
|
||||||
|
'/radar/tidal_wave/0-single': RouteRecordInfo<'/radar/tidal_wave/0-single', '/radar/tidal_wave/0-single', Record<never, never>, Record<never, never>>,
|
||||||
|
'/radar/tidal_wave/1-stats': RouteRecordInfo<'/radar/tidal_wave/1-stats', '/radar/tidal_wave/1-stats', Record<never, never>, Record<never, never>>,
|
||||||
'/radar/v1': RouteRecordInfo<'/radar/v1', '/radar/v1', 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>>,
|
'/radar/v2': RouteRecordInfo<'/radar/v2', '/radar/v2', Record<never, never>, Record<never, never>>,
|
||||||
'/saber/day_cycle_power_wave_plot': RouteRecordInfo<'/saber/day_cycle_power_wave_plot', '/saber/day_cycle_power_wave_plot', Record<never, never>, Record<never, never>>,
|
'/saber/gravity_wave/0-perday': RouteRecordInfo<'/saber/gravity_wave/0-perday', '/saber/gravity_wave/0-perday', Record<never, never>, Record<never, never>>,
|
||||||
'/saber/day_fft_ifft_plot': RouteRecordInfo<'/saber/day_fft_ifft_plot', '/saber/day_fft_ifft_plot', Record<never, never>, Record<never, never>>,
|
'/saber/gravity_wave/1-monthly': RouteRecordInfo<'/saber/gravity_wave/1-monthly', '/saber/gravity_wave/1-monthly', Record<never, never>, Record<never, never>>,
|
||||||
'/saber/month_power_wave_plot': RouteRecordInfo<'/saber/month_power_wave_plot', '/saber/month_power_wave_plot', Record<never, never>, Record<never, never>>,
|
'/saber/planet_wave/0-perday': RouteRecordInfo<'/saber/planet_wave/0-perday', '/saber/planet_wave/0-perday', Record<never, never>, Record<never, never>>,
|
||||||
'/saber/plot_wave_fitting': RouteRecordInfo<'/saber/plot_wave_fitting', '/saber/plot_wave_fitting', Record<never, never>, Record<never, never>>,
|
'/saber/planet_wave/1-monthly': RouteRecordInfo<'/saber/planet_wave/1-monthly', '/saber/planet_wave/1-monthly', Record<never, never>, Record<never, never>>,
|
||||||
|
'/tidi/gravity_wave/monthly': RouteRecordInfo<'/tidi/gravity_wave/monthly', '/tidi/gravity_wave/monthly', Record<never, never>, Record<never, never>>,
|
||||||
|
'/tidi/planet_wave/daily': RouteRecordInfo<'/tidi/planet_wave/daily', '/tidi/planet_wave/daily', Record<never, never>, Record<never, never>>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,18 @@ import { defineConfig } from 'vite'
|
|||||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:58620',
|
||||||
|
},
|
||||||
|
'/api/ping': {
|
||||||
|
target: 'ws://localhost:58620',
|
||||||
|
ws: true,
|
||||||
|
rewriteWsOrigin: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~/': `${path.resolve(__dirname, 'src')}/`,
|
'~/': `${path.resolve(__dirname, 'src')}/`,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user