mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
refactor: 优化主界面布局与通知系统
重构App.vue,首页独立无侧边栏,其他页面采用统一侧边栏布局。新增右下角固定通知区,集成返回顶部、队列通知、外交通知和敌方警报。移除新手引导组件,替换为弱引导提示系统。支持星球重命名弹窗。优化NPC成长与行为定时器逻辑,提升性能和可维护性。删除issue模板及相关文档描述。
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-2xl',
|
||||
'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-[60] 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
|
||||
)
|
||||
"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[60] bg-black/80',
|
||||
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 sm:min-w-[764px] 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-[60] 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
|
||||
)
|
||||
"
|
||||
|
||||
93
src/components/ui/pagination/FixedPagination.vue
Normal file
93
src/components/ui/pagination/FixedPagination.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div v-if="totalPages > 1" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-40">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 上一页按钮 - 圆形胶囊 -->
|
||||
<button
|
||||
v-if="currentPage > 1"
|
||||
@click="emit('update:page', currentPage - 1)"
|
||||
class="h-10 w-10 rounded-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border shadow-lg flex items-center justify-center hover:bg-accent transition-colors"
|
||||
>
|
||||
<ChevronLeft class="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<!-- 页码 - 椭圆形胶囊 -->
|
||||
<div class="bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border rounded-full py-2 px-3 shadow-lg flex items-center gap-1">
|
||||
<button
|
||||
v-for="pageNum in pageNumbers"
|
||||
:key="pageNum"
|
||||
@click="emit('update:page', pageNum)"
|
||||
class="h-8 min-w-8 px-2 rounded-full text-sm font-medium transition-colors"
|
||||
:class="
|
||||
pageNum === currentPage
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'hover:bg-accent'
|
||||
"
|
||||
>
|
||||
{{ pageNum }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 下一页按钮 - 圆形胶囊 -->
|
||||
<button
|
||||
v-if="currentPage < totalPages"
|
||||
@click="emit('update:page', currentPage + 1)"
|
||||
class="h-10 w-10 rounded-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border shadow-lg flex items-center justify-center hover:bg-accent transition-colors"
|
||||
>
|
||||
<ChevronRight class="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||
|
||||
interface Props {
|
||||
page: number
|
||||
totalPages: number
|
||||
maxVisible?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxVisible: 3
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:page': [page: number]
|
||||
}>()
|
||||
|
||||
const currentPage = computed(() => props.page)
|
||||
|
||||
// 生成页码列表 - 最多显示指定数量页码,不含省略号
|
||||
const pageNumbers = computed(() => {
|
||||
const pages: number[] = []
|
||||
const { totalPages, maxVisible } = props
|
||||
const current = currentPage.value
|
||||
|
||||
if (totalPages <= maxVisible) {
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
} else {
|
||||
let start = current - Math.floor(maxVisible / 2)
|
||||
let end = current + Math.floor(maxVisible / 2)
|
||||
|
||||
// 边界调整
|
||||
if (start < 1) {
|
||||
start = 1
|
||||
end = maxVisible
|
||||
}
|
||||
if (end > totalPages) {
|
||||
end = totalPages
|
||||
start = totalPages - maxVisible + 1
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
return pages
|
||||
})
|
||||
</script>
|
||||
@@ -6,3 +6,4 @@ export { default as PaginationItem } from './PaginationItem.vue'
|
||||
export { default as PaginationLast } from './PaginationLast.vue'
|
||||
export { default as PaginationNext } from './PaginationNext.vue'
|
||||
export { default as PaginationPrevious } from './PaginationPrevious.vue'
|
||||
export { default as FixedPagination } from './FixedPagination.vue'
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
import SheetHeader from '@/components/ui/sheet/SheetHeader.vue'
|
||||
import SheetTitle from '@/components/ui/sheet/SheetTitle.vue'
|
||||
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from './utils'
|
||||
import { useTutorial } from '@/composables/useTutorial'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({
|
||||
@@ -101,47 +100,17 @@
|
||||
|
||||
const router = useRouter()
|
||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
||||
const { tutorialState, currentStep, nextStep } = useTutorial()
|
||||
|
||||
// 包装setOpenMobile以拦截教程期间的关闭操作
|
||||
// 处理移动端侧边栏打开/关闭
|
||||
const handleOpenMobileChange = (open: boolean) => {
|
||||
// 如果是移动端且在教程的菜单相关步骤,阻止关闭侧边栏
|
||||
if (tutorialState.value.isActive && currentStep.value) {
|
||||
// 只在第3步期间阻止关闭侧边栏,让玩家必须手动打开
|
||||
if (currentStep.value.id === 'menu_intro_mobile') {
|
||||
// 只允许打开,不允许关闭
|
||||
if (open) {
|
||||
setOpenMobile(true)
|
||||
}
|
||||
// 如果试图关闭,忽略该操作,保持打开状态
|
||||
return
|
||||
}
|
||||
}
|
||||
// 其他情况正常更新
|
||||
setOpenMobile(open)
|
||||
}
|
||||
|
||||
// 监听openMobile变化,在移动端教程第3步时,侧边栏打开后自动推进到第4步
|
||||
watch(
|
||||
() => openMobile.value,
|
||||
(isOpen) => {
|
||||
if (isMobile.value && tutorialState.value.isActive && currentStep.value) {
|
||||
// 如果在第3步且侧边栏刚打开,自动推进到第4步
|
||||
if (currentStep.value.id === 'menu_intro_mobile' && isOpen) {
|
||||
setTimeout(() => {
|
||||
nextStep()
|
||||
}, 300) // 延迟300ms让侧边栏动画完成
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 监听路由变化,在移动端关闭侧边栏
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
() => {
|
||||
if (isMobile.value && openMobile.value) {
|
||||
// 路由变化时关闭移动端侧边栏
|
||||
setOpenMobile(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes, Ref } from 'vue'
|
||||
import { defaultDocument, useEventListener, useMediaQuery, useVModel } from '@vueuse/core'
|
||||
import { useMediaQuery, useVModel } from '@vueuse/core'
|
||||
import { TooltipProvider } from 'reka-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
provideSidebarContext,
|
||||
SIDEBAR_COOKIE_MAX_AGE,
|
||||
SIDEBAR_COOKIE_NAME,
|
||||
SIDEBAR_KEYBOARD_SHORTCUT,
|
||||
SIDEBAR_WIDTH,
|
||||
SIDEBAR_WIDTH_ICON
|
||||
} from './utils'
|
||||
import { provideSidebarContext, SIDEBAR_WIDTH, SIDEBAR_WIDTH_ICON } from './utils'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -36,7 +29,7 @@
|
||||
class?: HTMLAttributes['class']
|
||||
}>(),
|
||||
{
|
||||
defaultOpen: !defaultDocument?.cookie.includes(`${SIDEBAR_COOKIE_NAME}=false`),
|
||||
defaultOpen: true,
|
||||
open: undefined
|
||||
}
|
||||
)
|
||||
@@ -54,30 +47,17 @@
|
||||
}) as Ref<boolean>
|
||||
|
||||
const setOpen = (value: boolean) => {
|
||||
open.value = value // emits('update:open', value)
|
||||
|
||||
// This sets the cookie to keep the sidebar state.
|
||||
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open.value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
|
||||
open.value = value
|
||||
}
|
||||
|
||||
const setOpenMobile = (value: boolean) => {
|
||||
openMobile.value = value
|
||||
}
|
||||
|
||||
// Helper to toggle the sidebar.
|
||||
const toggleSidebar = () => {
|
||||
return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value)
|
||||
}
|
||||
|
||||
useEventListener('keydown', (event: KeyboardEvent) => {
|
||||
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault()
|
||||
toggleSidebar()
|
||||
}
|
||||
})
|
||||
|
||||
// We add a state so that we can do data-state="expanded" or "collapsed".
|
||||
// This makes it easier to style the sidebar with Tailwind classes.
|
||||
const state = computed(() => (open.value ? 'expanded' : 'collapsed'))
|
||||
|
||||
provideSidebarContext({
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import { createContext } from 'reka-ui'
|
||||
|
||||
export const SIDEBAR_COOKIE_NAME = 'sidebar_state'
|
||||
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
||||
export const SIDEBAR_WIDTH = '16rem'
|
||||
export const SIDEBAR_WIDTH_MOBILE = '18rem'
|
||||
export const SIDEBAR_WIDTH_ICON = '3rem'
|
||||
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
|
||||
|
||||
export const [useSidebar, provideSidebarContext] = createContext<{
|
||||
state: ComputedRef<'expanded' | 'collapsed'>
|
||||
|
||||
Reference in New Issue
Block a user