feat: 炫酷的登录页

This commit is contained in:
Blizzard
2026-04-28 16:43:34 +08:00
parent 3cade8e7ef
commit ccb36fa59c
34 changed files with 2390 additions and 253 deletions
+23 -6
View File
@@ -1,15 +1,32 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface AppState {
sidebarOpen: boolean
mobileMenuOpen: boolean
themeHue: string
cmdKOpen: boolean
toggleSidebar: () => void
setMobileMenu: (open: boolean) => void
setThemeHue: (hue: string) => void
setCmdKOpen: (open: boolean) => void
}
export const useAppStore = create<AppState>((set) => ({
sidebarOpen: true,
mobileMenuOpen: false,
toggleSidebar: () => set(s => ({ sidebarOpen: !s.sidebarOpen })),
setMobileMenu: (open) => set({ mobileMenuOpen: open }),
}))
export const useAppStore = create<AppState>()(
persist(
(set) => ({
sidebarOpen: true,
mobileMenuOpen: false,
themeHue: '145', // Default emerald
cmdKOpen: false,
toggleSidebar: () => set(s => ({ sidebarOpen: !s.sidebarOpen })),
setMobileMenu: (open) => set({ mobileMenuOpen: open }),
setThemeHue: (hue) => set({ themeHue: hue }),
setCmdKOpen: (open) => set({ cmdKOpen: open }),
}),
{
name: 'app-storage',
partialize: (state) => ({ themeHue: state.themeHue, sidebarOpen: state.sidebarOpen }),
}
)
)
+82
View File
@@ -0,0 +1,82 @@
import { create } from 'zustand'
export interface TabItem {
path: string
title: string
closable: boolean // Dashboard tab is not closable
}
interface TabsState {
tabs: TabItem[]
activeTab: string
addTab: (tab: TabItem) => void
removeTab: (path: string) => string // returns next active path
setActiveTab: (path: string) => void
closeOthers: (path: string) => void
closeAll: () => string // returns home path
closeRight: (path: string) => void
closeLeft: (path: string) => void
}
const HOME_TAB: TabItem = { path: '/dashboard', title: '仪表盘', closable: false }
export const useTabsStore = create<TabsState>((set, get) => ({
tabs: [HOME_TAB],
activeTab: '/dashboard',
addTab: (tab) => {
const { tabs } = get()
if (!tabs.find(t => t.path === tab.path)) {
set({ tabs: [...tabs, tab], activeTab: tab.path })
} else {
set({ activeTab: tab.path })
}
},
removeTab: (path) => {
const { tabs, activeTab } = get()
const target = tabs.find(t => t.path === path)
if (!target || !target.closable) return activeTab
const newTabs = tabs.filter(t => t.path !== path)
let nextActive = activeTab
if (activeTab === path) {
const idx = tabs.findIndex(t => t.path === path)
nextActive = newTabs[Math.min(idx, newTabs.length - 1)]?.path || '/dashboard'
}
set({ tabs: newTabs, activeTab: nextActive })
return nextActive
},
setActiveTab: (path) => set({ activeTab: path }),
closeOthers: (path) => {
const { tabs } = get()
set({
tabs: tabs.filter(t => !t.closable || t.path === path),
activeTab: path,
})
},
closeAll: () => {
set({ tabs: [HOME_TAB], activeTab: '/dashboard' })
return '/dashboard'
},
closeRight: (path) => {
const { tabs, activeTab } = get()
const idx = tabs.findIndex(t => t.path === path)
const newTabs = tabs.filter((t, i) => i <= idx || !t.closable)
const stillHasActive = newTabs.find(t => t.path === activeTab)
set({ tabs: newTabs, activeTab: stillHasActive ? activeTab : path })
},
closeLeft: (path) => {
const { tabs, activeTab } = get()
const idx = tabs.findIndex(t => t.path === path)
const newTabs = tabs.filter((t, i) => i >= idx || !t.closable)
const stillHasActive = newTabs.find(t => t.path === activeTab)
set({ tabs: newTabs, activeTab: stillHasActive ? activeTab : path })
},
}))