后端改进: - 修复 dashboard/recent-alerts 的 limit 参数解析 (原代码错误地将数字解析为日期) - 为 dashboard/recent-releases 添加 limit 参数解析支持 - 新增 generateAppReleases 种子数据生成函数 (10条应用发布记录) - 修复 AlertEvent 的 rule_name/rule_id/severity 数据一致性 - 添加 go-ldap/ldap/v3 依赖并集成 LDAP 认证处理器 - 添加 seed.go 自动种子数据初始化 (admin用户+LDAP配置) - User model 添加 AuthSource/LDAPConfig 字段 前端改进: - 修复 User 类型定义以匹配后端 API (real_name/is_admin/auth_source等) - 修复 Header.tsx 的 real_name 属性引用导致 TypeScript 编译错误 - 统一 API 服务层、暗色主题样式、组件联调优化 验证: - go build ./... 编译通过 - npm run build 构建通过 - 仪表盘发布历史、告警消息等区域数据正常展示 - 健康检查 /health 正常
162 lines
4.2 KiB
TypeScript
162 lines
4.2 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import type { User } from '@/types';
|
|
|
|
// 主题状态
|
|
interface ThemeState {
|
|
sidebarCollapsed: boolean;
|
|
toggleSidebar: () => void;
|
|
}
|
|
|
|
export const useThemeStore = create<ThemeState>((set) => ({
|
|
sidebarCollapsed: false,
|
|
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
|
|
}));
|
|
|
|
// 用户状态
|
|
interface UserState {
|
|
user: User | null;
|
|
token: string | null;
|
|
isAuthenticated: boolean;
|
|
theme?: string;
|
|
setUser: (user: User | null) => void;
|
|
setToken: (token: string | null) => void;
|
|
login: (user: User, token: string) => void;
|
|
logout: () => void;
|
|
}
|
|
|
|
export const useUserStore = create<UserState>()(
|
|
persist(
|
|
(set) => ({
|
|
user: null,
|
|
token: null,
|
|
isAuthenticated: false,
|
|
theme: 'ocean-deep',
|
|
setUser: (user) => set({ user }),
|
|
setToken: (token) => set({ token }),
|
|
login: (user, token) => set({ user, token, isAuthenticated: true }),
|
|
logout: () => set({ user: null, token: null, isAuthenticated: false }),
|
|
}),
|
|
{
|
|
name: 'user-storage',
|
|
}
|
|
)
|
|
);
|
|
|
|
// 菜单状态
|
|
interface MenuState {
|
|
activeMenu: string;
|
|
openMenus: string[];
|
|
setActiveMenu: (menu: string) => void;
|
|
toggleMenu: (menu: string) => void;
|
|
}
|
|
|
|
export const useMenuStore = create<MenuState>((set) => ({
|
|
activeMenu: 'dashboard',
|
|
openMenus: [],
|
|
setActiveMenu: (menu) => set({ activeMenu: menu }),
|
|
toggleMenu: (menu) =>
|
|
set((state) => ({
|
|
openMenus: state.openMenus.includes(menu)
|
|
? state.openMenus.filter((m) => m !== menu)
|
|
: [...state.openMenus, menu],
|
|
})),
|
|
}));
|
|
|
|
// 标签页状态
|
|
interface TabItem {
|
|
id: string;
|
|
title: string;
|
|
path: string;
|
|
closable?: boolean;
|
|
}
|
|
|
|
interface TabState {
|
|
tabs: TabItem[];
|
|
activeTab: string;
|
|
addTab: (tab: TabItem) => void;
|
|
removeTab: (tabId: string) => void;
|
|
setActiveTab: (tabId: string) => void;
|
|
}
|
|
|
|
export const useTabStore = create<TabState>((set) => ({
|
|
tabs: [{ id: 'dashboard', title: '仪表盘', path: '/', closable: false }],
|
|
activeTab: 'dashboard',
|
|
addTab: (tab) =>
|
|
set((state) => {
|
|
const exists = state.tabs.find((t) => t.id === tab.id);
|
|
if (exists) {
|
|
return { activeTab: tab.id };
|
|
}
|
|
return { tabs: [...state.tabs, tab], activeTab: tab.id };
|
|
}),
|
|
removeTab: (tabId) =>
|
|
set((state) => {
|
|
const newTabs = state.tabs.filter((t) => t.id !== tabId);
|
|
if (state.activeTab === tabId && newTabs.length > 0) {
|
|
return { tabs: newTabs, activeTab: newTabs[newTabs.length - 1].id };
|
|
}
|
|
return { tabs: newTabs };
|
|
}),
|
|
setActiveTab: (tabId) => set({ activeTab: tabId }),
|
|
}));
|
|
|
|
// 通知状态
|
|
interface Notification {
|
|
id: string;
|
|
type: 'info' | 'success' | 'warning' | 'error';
|
|
title: string;
|
|
message: string;
|
|
timestamp: number;
|
|
read: boolean;
|
|
}
|
|
|
|
interface NotificationState {
|
|
notifications: Notification[];
|
|
unreadCount: number;
|
|
addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void;
|
|
markAsRead: (id: string) => void;
|
|
markAllAsRead: () => void;
|
|
removeNotification: (id: string) => void;
|
|
}
|
|
|
|
export const useNotificationStore = create<NotificationState>((set) => ({
|
|
notifications: [],
|
|
unreadCount: 0,
|
|
addNotification: (notification) =>
|
|
set((state) => ({
|
|
notifications: [
|
|
{
|
|
...notification,
|
|
id: Date.now().toString(),
|
|
timestamp: Date.now(),
|
|
read: false,
|
|
},
|
|
...state.notifications,
|
|
].slice(0, 100),
|
|
unreadCount: state.unreadCount + 1,
|
|
})),
|
|
markAsRead: (id) =>
|
|
set((state) => {
|
|
const notification = state.notifications.find((n) => n.id === id);
|
|
if (notification && !notification.read) {
|
|
return {
|
|
notifications: state.notifications.map((n) =>
|
|
n.id === id ? { ...n, read: true } : n
|
|
),
|
|
unreadCount: Math.max(0, state.unreadCount - 1),
|
|
};
|
|
}
|
|
return state;
|
|
}),
|
|
markAllAsRead: () =>
|
|
set((state) => ({
|
|
notifications: state.notifications.map((n) => ({ ...n, read: true })),
|
|
unreadCount: 0,
|
|
})),
|
|
removeNotification: (id) =>
|
|
set((state) => ({
|
|
notifications: state.notifications.filter((n) => n.id !== id),
|
|
})),
|
|
}));
|