refactor: 标准化项目结构 frontend/ + backend/ + SQLite + run.sh
- 目录重构: app/ -> frontend/,与 backend/ 并列 - 数据库: 默认 SQLite(零配置),修复 DSN() bug - run.sh: 一键启动前后端,支持 start/stop/restart/status - Makefile: install/dev/build/lint/clean 快捷命令 - vite.config.ts: 配置 dev server 端口 5173 + API 代理到 8080 - config.yaml: SQLite 默认配置,开箱即用 - docs/: 完整开发文档(项目结构/API/数据库/规范/部署) - 修复: GORM foreign key 关联声明 - 修复: go get gorm.io/driver/sqlite 依赖
This commit is contained in:
1 parent
7d6674bddd
commit
97c4207ff8
132 files changed
+1572
-34
No files matched your search
@@ -0,0 +1,159 @@
|
||||
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;
|
||||
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,
|
||||
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),
|
||||
})),
|
||||
}));
|
||||
Reference in new issue
Block a user