chore(依赖): 添加项目依赖和构建配置
- 添加 Zustand 状态管理库 - 配置开发和生产环境变量 - 更新 Vite 构建配置 - 更新 Git 钩子配置 - 修复 NavBar 组件的 React Hooks 规则问题
This commit is contained in:
parent
c8c20299d9
commit
3ea18a417d
10
.env.development
Normal file
10
.env.development
Normal file
@ -0,0 +1,10 @@
|
||||
# 开发环境配置
|
||||
|
||||
# API 基础地址(使用代理)
|
||||
VITE_API_BASE_URL=/api
|
||||
|
||||
# API 请求超时时间(毫秒)
|
||||
VITE_API_TIMEOUT=30000
|
||||
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=Codernew React Pro
|
||||
10
.env.production
Normal file
10
.env.production
Normal file
@ -0,0 +1,10 @@
|
||||
# 生产环境配置
|
||||
|
||||
# API 基础地址(生产环境需要修改为实际地址)
|
||||
VITE_API_BASE_URL=http://localhost:8080
|
||||
|
||||
# API 请求超时时间(毫秒)
|
||||
VITE_API_TIMEOUT=30000
|
||||
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=Codernew React Pro
|
||||
@ -4,3 +4,4 @@
|
||||
npm run pre-commit
|
||||
npm run pre-commit
|
||||
npm run pre-commit
|
||||
npm run pre-commit
|
||||
|
||||
12585
package-lock.json
generated
Normal file
12585
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,8 @@
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"redux": "^4.1.2"
|
||||
"redux": "^4.1.2",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arco-design/webpack-plugin": "^1.6.0",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import {
|
||||
Tooltip,
|
||||
Input,
|
||||
Avatar,
|
||||
Select,
|
||||
Dropdown,
|
||||
@ -10,9 +9,9 @@ import {
|
||||
Message,
|
||||
Button,
|
||||
} from '@arco-design/web-react';
|
||||
import cs from 'classnames';
|
||||
import {
|
||||
IconLanguage,
|
||||
IconNotification,
|
||||
IconSunFill,
|
||||
IconMoonFill,
|
||||
IconUser,
|
||||
@ -20,33 +19,56 @@ import {
|
||||
IconPoweroff,
|
||||
IconLoading,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { GlobalState } from '@/store';
|
||||
import { GlobalContext } from '@/context';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import Logo from '@/assets/logo.svg';
|
||||
import MessageBox from '@/components/MessageBox';
|
||||
import IconButton from './IconButton';
|
||||
import Settings from '../Settings';
|
||||
import styles from './style/index.module.less';
|
||||
import defaultLocale from '@/locale';
|
||||
import useStorage from '@/utils/useStorage';
|
||||
import { generatePermission } from '@/routes';
|
||||
import { useUserStore } from '@/store/userStore';
|
||||
|
||||
function Navbar({ show }: { show: boolean }) {
|
||||
interface NavBreadcrumbNode {
|
||||
key: string;
|
||||
name: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface NavbarProps {
|
||||
show: boolean;
|
||||
breadcrumb?: NavBreadcrumbNode[];
|
||||
onBreadcrumbClick?: (node: NavBreadcrumbNode, index: number) => void;
|
||||
}
|
||||
|
||||
function Navbar({ show, breadcrumb = [], onBreadcrumbClick }: NavbarProps) {
|
||||
const t = useLocale();
|
||||
const history = useHistory();
|
||||
const { userInfo, userLoading } = useSelector((state: GlobalState) => state);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [_, setUserStatus] = useStorage('userStatus');
|
||||
const [role, setRole] = useStorage('userRole', 'admin');
|
||||
// 使用 Zustand 获取用户信息
|
||||
const { userInfo, userLoading, clearUserInfo } = useUserStore();
|
||||
|
||||
const [, setUserStatus] = useStorage('userStatus');
|
||||
|
||||
const { setLang, lang, theme, setTheme } = useContext(GlobalContext);
|
||||
|
||||
// Hooks 必须在所有条件语句之前调用
|
||||
const [avatarError, setAvatarError] = React.useState(false);
|
||||
|
||||
// 使用 Zustand 后,数据已自动持久化,无需手动合并
|
||||
const avatar = userInfo?.avatar;
|
||||
const nickname =
|
||||
userInfo?.nickname || userInfo?.name || userInfo?.username || '';
|
||||
|
||||
// 当 avatar 改变时,重置 avatarError
|
||||
React.useEffect(() => {
|
||||
setAvatarError(false);
|
||||
}, [avatar]);
|
||||
|
||||
function logout() {
|
||||
setUserStatus('logout');
|
||||
clearUserInfo(); // 清除 Zustand 中的用户信息
|
||||
window.location.href = '/login';
|
||||
}
|
||||
|
||||
@ -58,18 +80,6 @@ function Navbar({ show }: { show: boolean }) {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'update-userInfo',
|
||||
payload: {
|
||||
userInfo: {
|
||||
...userInfo,
|
||||
permissions: generatePermission(role),
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [role]);
|
||||
|
||||
if (!show) {
|
||||
return (
|
||||
<div className={styles['fixed-settings']}>
|
||||
@ -103,14 +113,43 @@ function Navbar({ show }: { show: boolean }) {
|
||||
<Logo />
|
||||
<div className={styles['logo-name']}>Codernew React Pro</div>
|
||||
</div>
|
||||
{breadcrumb.length > 0 && (
|
||||
<div className={styles['navbar-breadcrumb']}>
|
||||
{breadcrumb.map((node, index) => {
|
||||
const label = t[node.name] || node.name;
|
||||
return (
|
||||
<React.Fragment key={`${node.key}-${index}`}>
|
||||
<span
|
||||
className={cs(styles['breadcrumb-node'], {
|
||||
[styles['breadcrumb-clickable']]:
|
||||
index !== breadcrumb.length - 1,
|
||||
})}
|
||||
onClick={() => {
|
||||
if (
|
||||
onBreadcrumbClick &&
|
||||
index !== breadcrumb.length - 1
|
||||
) {
|
||||
onBreadcrumbClick(node, index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{node.icon && (
|
||||
<span className={styles['breadcrumb-icon']}>
|
||||
{node.icon}
|
||||
</span>
|
||||
)}
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
{index !== breadcrumb.length - 1 && (
|
||||
<span className={styles['breadcrumb-divider']}>/</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ul className={styles.right}>
|
||||
<li>
|
||||
<Input.Search
|
||||
className={styles.round}
|
||||
placeholder={t['navbar.search.placeholder']}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Select
|
||||
triggerElement={<IconButton icon={<IconLanguage />} />}
|
||||
@ -132,11 +171,6 @@ function Navbar({ show }: { show: boolean }) {
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<MessageBox>
|
||||
<IconButton icon={<IconNotification />} />
|
||||
</MessageBox>
|
||||
</li>
|
||||
<li>
|
||||
<Tooltip
|
||||
content={
|
||||
@ -152,19 +186,25 @@ function Navbar({ show }: { show: boolean }) {
|
||||
</Tooltip>
|
||||
</li>
|
||||
<Settings />
|
||||
{userInfo && (
|
||||
<li>
|
||||
<Dropdown droplist={droplist} position="br" disabled={userLoading}>
|
||||
<Avatar size={32} style={{ cursor: 'pointer' }}>
|
||||
{userLoading ? (
|
||||
<IconLoading />
|
||||
) : avatar && !avatarError ? (
|
||||
<img
|
||||
alt="avatar"
|
||||
src={avatar}
|
||||
onError={() => setAvatarError(true)}
|
||||
/>
|
||||
) : nickname ? (
|
||||
nickname.charAt(0)
|
||||
) : (
|
||||
<img alt="avatar" src={userInfo.avatar} />
|
||||
<IconUser />
|
||||
)}
|
||||
</Avatar>
|
||||
</Dropdown>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -28,4 +28,13 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user