mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
feat: 重构战报弹窗与模拟器视图,优化UI与逻辑
重构BattleReportDialog和BattleSimulatorView相关静态资源,替换旧版JS/CSS文件,提升界面一致性和交互体验。新增和优化空状态、滚动区域等通用UI组件,移除部分冗余composable,完善多语言内容。引入导弹逻辑,补充版本检测工具,提升整体代码结构和可维护性。
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-2xl',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 w-[calc(100vw-3rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200 sm:w-auto flex flex-col p-0',
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 w-[calc(100vw-3rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200 sm:w-auto sm:min-w-[764px] flex flex-col p-0',
|
||||
containerClass
|
||||
)
|
||||
"
|
||||
|
||||
22
src/components/ui/empty/Empty.vue
Normal file
22
src/components/ui/empty/Empty.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div
|
||||
data-slot="empty"
|
||||
:class="
|
||||
cn(
|
||||
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/empty/EmptyContent.vue
Normal file
14
src/components/ui/empty/EmptyContent.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="empty-content" :class="cn('flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
19
src/components/ui/empty/EmptyDescription.vue
Normal file
19
src/components/ui/empty/EmptyDescription.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<p
|
||||
data-slot="empty-description"
|
||||
:class="
|
||||
cn('text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', $attrs.class ?? '')
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/empty/EmptyHeader.vue
Normal file
14
src/components/ui/empty/EmptyHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="empty-header" :class="cn('flex max-w-sm flex-col items-center gap-2 text-center', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
17
src/components/ui/empty/EmptyMedia.vue
Normal file
17
src/components/ui/empty/EmptyMedia.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div data-slot="empty-icon" :data-variant="variant" :class="cn(emptyMediaVariants({ variant }), props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { EmptyMediaVariants } from '.'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { emptyMediaVariants } from '.'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
variant?: EmptyMediaVariants['variant']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/empty/EmptyTitle.vue
Normal file
14
src/components/ui/empty/EmptyTitle.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="empty-title" :class="cn('text-lg font-medium tracking-tight', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
23
src/components/ui/empty/index.ts
Normal file
23
src/components/ui/empty/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Empty } from './Empty.vue'
|
||||
export { default as EmptyContent } from './EmptyContent.vue'
|
||||
export { default as EmptyDescription } from './EmptyDescription.vue'
|
||||
export { default as EmptyHeader } from './EmptyHeader.vue'
|
||||
export { default as EmptyMedia } from './EmptyMedia.vue'
|
||||
export { default as EmptyTitle } from './EmptyTitle.vue'
|
||||
|
||||
export const emptyMediaVariants = cva('mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
})
|
||||
|
||||
export type EmptyMediaVariants = VariantProps<typeof emptyMediaVariants>
|
||||
25
src/components/ui/scroll-area/ScrollArea.vue
Normal file
25
src/components/ui/scroll-area/ScrollArea.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ScrollAreaRoot data-slot="scroll-area" v-bind="delegatedProps" :class="cn('relative', props.class)">
|
||||
<ScrollAreaViewport
|
||||
data-slot="scroll-area-viewport"
|
||||
class="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||
>
|
||||
<slot />
|
||||
</ScrollAreaViewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaCorner />
|
||||
</ScrollAreaRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ScrollAreaRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ScrollAreaCorner, ScrollAreaRoot, ScrollAreaViewport } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import ScrollBar from './ScrollBar.vue'
|
||||
|
||||
const props = defineProps<ScrollAreaRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
30
src/components/ui/scroll-area/ScrollBar.vue
Normal file
30
src/components/ui/scroll-area/ScrollBar.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<ScrollAreaScrollbar
|
||||
data-slot="scroll-area-scrollbar"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex touch-none p-px transition-colors select-none',
|
||||
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent',
|
||||
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<ScrollAreaThumb data-slot="scroll-area-thumb" class="bg-border relative flex-1 rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ScrollAreaScrollbarProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ScrollAreaScrollbar, ScrollAreaThumb } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<ScrollAreaScrollbarProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
orientation: 'vertical'
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
2
src/components/ui/scroll-area/index.ts
Normal file
2
src/components/ui/scroll-area/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as ScrollArea } from './ScrollArea.vue'
|
||||
export { default as ScrollBar } from './ScrollBar.vue'
|
||||
@@ -37,6 +37,7 @@
|
||||
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from 'lucide-vue-next'
|
||||
import { Toaster as Sonner } from 'vue-sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
import 'vue-sonner/style.css'
|
||||
|
||||
const props = defineProps<ToasterProps>()
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user