feat: Add base project structure with API and web components
This commit is contained in:
45
web/src/store/locale.ts
Normal file
45
web/src/store/locale.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { create } from 'zustand'
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import type { Locale } from 'antd/es/locale';
|
||||
import dayjs from 'dayjs'
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import i18n from '@/i18n';
|
||||
import { timezoneToAntdLocaleMap } from '@/utils/timezones';
|
||||
|
||||
// 扩展dayjs插件
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
|
||||
interface I18nState {
|
||||
language: string;
|
||||
locale: Locale;
|
||||
timeZone: string;
|
||||
changeLanguage: (language: string) => void;
|
||||
changeTimeZone: (timeZone: string) => void;
|
||||
}
|
||||
|
||||
const initialTimeZone = localStorage.getItem('timeZone') || 'Asia/Shanghai'
|
||||
const initialLanguage = localStorage.getItem('language') || 'en'
|
||||
const initialLocale = initialLanguage === 'en' ? enUS : zhCN
|
||||
i18n.changeLanguage(initialLanguage)
|
||||
|
||||
export const useI18n = create<I18nState>((set, get) => ({
|
||||
language: initialLanguage,
|
||||
locale: initialLocale,
|
||||
timeZone: initialTimeZone,
|
||||
changeLanguage: (language: string) => {
|
||||
i18n.changeLanguage(language)
|
||||
const localeName = timezoneToAntdLocaleMap[language] || enUS;
|
||||
set({ language: language, locale: localeName })
|
||||
},
|
||||
changeTimeZone: (timeZone: string) => {
|
||||
const { timeZone: lastTimeZone } = get()
|
||||
set({ timeZone })
|
||||
if (lastTimeZone !== timeZone) {
|
||||
window.location.reload()
|
||||
}
|
||||
},
|
||||
}))
|
||||
76
web/src/store/menu.ts
Normal file
76
web/src/store/menu.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { create } from 'zustand'
|
||||
import AllMenus from './menu.json'
|
||||
|
||||
export interface MenuItem {
|
||||
id: number;
|
||||
parent: number;
|
||||
code: string | null;
|
||||
label: string;
|
||||
i18nKey: string | null;
|
||||
path: string | null;
|
||||
enable: boolean;
|
||||
display: boolean;
|
||||
level: number;
|
||||
sort: number;
|
||||
icon: string | null;
|
||||
iconActive: string | null;
|
||||
menuDesc: string | null;
|
||||
deleted: string | null;
|
||||
updateTime: number;
|
||||
new_: string | null;
|
||||
keepAlive: boolean;
|
||||
master: string | null;
|
||||
disposable: boolean;
|
||||
appSystem: string | null;
|
||||
subs: MenuItem[];
|
||||
}
|
||||
interface MenuState {
|
||||
collapsed: boolean;
|
||||
toggleSider: () => void;
|
||||
allMenus: Record<'space' | 'manage', MenuItem[]>;
|
||||
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
|
||||
loadMenus: (source: 'space' | 'manage') => void;
|
||||
updateBreadcrumbs: (keyPath: string[], source: 'space' | 'manage') => void;
|
||||
}
|
||||
|
||||
const initBreadcrumbs = localStorage.getItem('breadcrumbs') || '[]'
|
||||
export const useMenu = create<MenuState>((set, get) => ({
|
||||
collapsed: localStorage.getItem('collapsed') === 'true',
|
||||
allMenus: {
|
||||
manage: [],
|
||||
space: []
|
||||
},
|
||||
allBreadcrumbs: JSON.parse(initBreadcrumbs),
|
||||
loadMenus: async () => {
|
||||
set({ allMenus: AllMenus })
|
||||
},
|
||||
toggleSider: () => {
|
||||
set((state) => {
|
||||
const newCollapsed = !state.collapsed
|
||||
localStorage.setItem('collapsed', JSON.stringify(newCollapsed))
|
||||
return { collapsed: newCollapsed }
|
||||
})
|
||||
},
|
||||
updateBreadcrumbs: (paths, source) => {
|
||||
const { allMenus } = get()
|
||||
const menus = allMenus[source] || []
|
||||
let result: MenuItem[] = []
|
||||
const matchedMenu: MenuItem | undefined = menus.find(menu => menu.path === paths[paths.length - 1] || `${menu.id}` === paths[1]);
|
||||
|
||||
if (matchedMenu) {
|
||||
let matchedSubMenu: MenuItem | undefined = undefined;
|
||||
if (paths.length > 1 && matchedMenu?.subs?.length) {
|
||||
matchedSubMenu = matchedMenu.subs.find(menu => menu.path === paths[0]);
|
||||
}
|
||||
result = [
|
||||
{ ...matchedMenu, subs: null },
|
||||
matchedSubMenu
|
||||
].filter(item => item !== undefined) as MenuItem[]
|
||||
} else {
|
||||
result = [] as MenuItem[]
|
||||
}
|
||||
const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: result }
|
||||
set({ allBreadcrumbs })
|
||||
localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs))
|
||||
},
|
||||
}))
|
||||
89
web/src/store/user.ts
Normal file
89
web/src/store/user.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { create } from 'zustand'
|
||||
import { clearAuthData } from '@/utils/auth';
|
||||
import type { User } from '@/views/UserManagement/types'
|
||||
import { getUsers, refreshToken, logout } from '@/api/user'
|
||||
import { getWorkspaceStorageType } from '@/api/workspaces';
|
||||
|
||||
export interface LoginInfo {
|
||||
access_token: string;
|
||||
expires_at: string;
|
||||
refresh_expires_at: string;
|
||||
refresh_token: string;
|
||||
token_type: 'bearer'
|
||||
}
|
||||
export interface UserState {
|
||||
user: User;
|
||||
loginInfo: LoginInfo;
|
||||
storageType: string | null;
|
||||
updateLoginInfo: (values: LoginInfo) => void;
|
||||
getUserInfo: (flag?: boolean) => void;
|
||||
clearUserInfo: () => void;
|
||||
logout: () => void;
|
||||
getStorageType: () => void;
|
||||
}
|
||||
export const useUser = create<UserState>((set, get) => ({
|
||||
user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || '{}') as User : {} as User,
|
||||
loginInfo: {} as LoginInfo,
|
||||
storageType: null,
|
||||
updateLoginInfo: (values: LoginInfo) => {
|
||||
localStorage.setItem('token', values.access_token);
|
||||
localStorage.setItem('refresh_token', values.refresh_token);
|
||||
set({ loginInfo: values });
|
||||
},
|
||||
getUserInfo: async (flag?: boolean) => {
|
||||
if (!localStorage.getItem('token')) {
|
||||
return
|
||||
}
|
||||
const localUser = JSON.parse(localStorage.getItem('user') || '{}') as User;
|
||||
if (localUser.id) {
|
||||
return
|
||||
}
|
||||
getUsers()
|
||||
.then((res) => {
|
||||
const response = res as User;
|
||||
set({ user: response })
|
||||
if (flag) {
|
||||
window.location.href = response.role && response.current_workspace_id ? '/#/' : '/#/space'
|
||||
}
|
||||
localStorage.setItem('user', JSON.stringify(response))
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch user info:', err)
|
||||
})
|
||||
},
|
||||
clearUserInfo: () => {
|
||||
set({ user: {} as User })
|
||||
clearAuthData();
|
||||
},
|
||||
logout: () => {
|
||||
logout()
|
||||
.then(() => {
|
||||
const { clearUserInfo } = get()
|
||||
clearUserInfo()
|
||||
window.location.href = '/#/login'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to logout:', err)
|
||||
})
|
||||
},
|
||||
refreshToken: () => {
|
||||
refreshToken()
|
||||
.then((res) => {
|
||||
const response = res as { refresh_token: string }
|
||||
localStorage.setItem('token', response.refresh_token);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh token:', err)
|
||||
})
|
||||
},
|
||||
getStorageType: () => {
|
||||
getWorkspaceStorageType()
|
||||
.then((res) => {
|
||||
const response = res as { storage_type: string };
|
||||
set({ storageType: response.storage_type || 'neo4j' });
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('Failed to load storage type');
|
||||
})
|
||||
}
|
||||
}))
|
||||
Reference in New Issue
Block a user