feat(project): base smatec's frontend

This commit is contained in:
Tran Anh Tuan
2026-01-21 11:48:57 +07:00
commit 5c2a909bed
138 changed files with 43666 additions and 0 deletions

153
src/app.tsx Normal file
View File

@@ -0,0 +1,153 @@
// 运行时配置
import { getLocale, history, Link, RunTimeLayoutConfig } from '@umijs/max';
import { ConfigProvider } from 'antd';
import dayjs from 'dayjs';
import { handleRequestConfig as devRequestConfig } from '../config/request_dev';
import { handleRequestConfig as prodRequestConfig } from '../config/request_prod';
import { AvatarDropdown } from './components/Avatar/AvatarDropdown';
import IconFont from './components/IconFont';
import LanguageSwitcher from './components/Lang/LanguageSwitcher';
import ThemeProvider from './components/Theme/ThemeProvider';
import ThemeSwitcher from './components/Theme/ThemeSwitcher';
import { THEME_KEY } from './constants';
import { ROUTE_LOGIN } from './constants/routes';
import NotFoundPage from './pages/Exception/NotFound';
import UnAccessPage from './pages/Exception/UnAccess';
import { apiQueryProfile } from './services/master/AuthController';
import { checkTokenExpired } from './utils/jwt';
import { getLogoImage } from './utils/logo';
import {
clearAllData,
clearSessionData,
getToken,
removeToken,
} from './utils/storage';
const isProdBuild = process.env.NODE_ENV === 'production';
export type InitialStateResponse = {
getUserProfile?: () => Promise<MasterModel.ProfileResponse | undefined>;
currentUserProfile?: MasterModel.ProfileResponse;
theme?: 'light' | 'dark';
};
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
// 更多信息见文档https://umijs.org/docs/api/runtime-config#getinitialstate
export async function getInitialState(): Promise<InitialStateResponse> {
const userToken: string = getToken();
const { pathname } = history.location;
dayjs.locale(getLocale() === 'en-US' ? 'en' : 'vi');
if (!userToken) {
if (pathname !== ROUTE_LOGIN) {
history.push(`${ROUTE_LOGIN}?redirect=${pathname}`);
} else {
history.push(ROUTE_LOGIN);
}
return {};
}
const isTokenExpried = checkTokenExpired(userToken);
if (isTokenExpried) {
removeToken();
clearAllData();
clearSessionData();
window.location.href = ROUTE_LOGIN;
return {};
}
const getUserProfile = async () => {
try {
const resp = await apiQueryProfile();
return resp;
} catch (error) {
removeToken();
clearAllData();
clearSessionData();
window.location.href = ROUTE_LOGIN;
}
};
const resp = await getUserProfile();
const currentTheme =
(localStorage.getItem(THEME_KEY) as 'light' | 'dark') || 'light';
return {
getUserProfile: getUserProfile!,
currentUserProfile: resp,
theme: currentTheme,
};
}
export const layout: RunTimeLayoutConfig = ({ initialState }) => {
const isDark = initialState?.theme === 'dark';
return {
logo: getLogoImage(),
menu: {
locale: true,
},
fixedHeader: true,
contentWidth: 'Fluid',
navTheme: isDark ? 'realDark' : 'light',
splitMenus: true,
iconfontUrl: '//at.alicdn.com/t/c/font_5096559_y5mqyqd86f.js',
contentStyle: {
padding: 0,
margin: 0,
paddingInline: 0,
},
actionsRender: () => [
<ThemeSwitcher key="theme-switcher" />,
<LanguageSwitcher key="lang-switcher" type="dropdown" />,
],
avatarProps: {
size: 'small',
src: '/avatar.svg',
render: () => (
<AvatarDropdown currentUserProfile={initialState?.currentUserProfile} />
),
},
childrenRender: (children) => {
return (
<ThemeProvider>
<ConfigProvider
theme={{
components: {
Collapse: {
contentPadding: '0px',
borderlessContentPadding: '0px',
headerPadding: '0px',
},
},
}}
>
{children}
</ConfigProvider>
</ThemeProvider>
);
},
layout: 'top',
menuHeaderRender: undefined,
menuItemRender: (item, dom) => {
if (item.path) {
// Coerce values to string to satisfy TypeScript expectations
const to = String(item.path ?? '');
const iconType = String(item.icon ?? '');
const label = String(item.name ?? '');
return (
<Link to={to}>
<IconFont type={iconType} />
<span>{label}</span>
</Link>
);
}
return dom;
},
token: {
header: {
colorBgMenuItemSelected: '#EEF7FF', // background khi chọn
colorTextMenuSelected: isDark ? '#fff' : '#1A2130', // màu chữ khi chọn
},
},
unAccessible: <UnAccessPage />,
noFound: <NotFoundPage />,
};
};
export const request = isProdBuild ? prodRequestConfig : devRequestConfig;