前言
一如既往,實戰出真理.
有興趣的可以瞧瞧,沒興趣的大佬請止步於此.
免得浪費您的時間
效果圖
- 基於
antd
的sidebar
元件封裝
折騰記的技術棧選型
Mobx & mobx-react
(用起來感覺良好的狀態管理器)React 16.4.2
(從這個起步,用新不用舊)React Router V4
(如上)antd
(版本追求如上 , 阿里出品的UI框架)styled-components
(不想寫單獨的樣式檔案,用這個是棒棒的,用過都說好)webpack 4.16.5
(版本追求如上)
實現思路
實現思路
- 自行維護一份靜態路由表
- 結合路由的
history
物件的pathanme
- 在元件渲染完畢的情況下,再去遍歷路由表,通過
setState
重新渲染側邊欄 - 為什麼不在元件初始化的時候就設定,那這樣對於404的路由沒法控制
react-router-dom v4
雖然提供了全域性404元件,但是history
裡面沒有代表404的狀態
實現目標
- 點選側邊欄的子選單會改變標題,對應的item也會高亮
- 直接修改路由,初次載入等會自動展開對應的分組,高亮對應的子項
- 不匹配的路由不展開和高亮任何
能學到啥
我儘量註釋,而收穫見仁見智了
我的思路? 我的的程式碼姿勢? 僅供參考
實現程式碼
基礎版
- 靜態路由表一份
export const sidebarData = [
{
key: 'group0',
title: {
icon: 'dashboard',
text: '資料分析'
},
children: [
{
key: '1',
text: '資料監控',
path: '/dashboard/monitor'
},
{
key: '2',
text: '資料分析',
path: '/dashboard/analyze'
}
]
},
{
key: 'group1',
title: {
icon: 'play-circle',
text: '音訊管理'
},
children: [
{
key: '6',
text: '聲兮列表',
path: '/voice/sxlist'
},
{
key: '7',
text: '回聲列表',
path: '/voice/calllist'
}
]
},
{
key: 'group2',
title: {
icon: 'schedule',
text: '活動中心'
},
children: [
{
key: '11',
text: '活動列表',
path: '/active/list'
},
{
key: '12',
text: '新建活動',
path: '/active/add'
}
]
},
{
key: 'group3',
title: {
icon: 'apple-o',
text: 'APP管理'
},
children: [
{
key: '16',
text: '移動互動',
path: '/appmanage/interaction'
},
{
key: '17',
text: '回聲列表',
path: '/test'
},
{
key: '18',
text: '使用者列表',
path: '/user/list'
}
]
},
{
key: 'group4',
title: {
icon: 'safety',
text: '安全中心'
},
children: [
{
key: '21',
text: '舉報處理',
path: '/safety/report'
},
{
key: '22',
text: '廣播中心',
path: '/safety/broadcast'
}
]
},
{
key: 'group5',
title: {
icon: 'user',
text: '系統設定'
},
children: [
{
key: '26',
text: '個人設定',
path: '/user/setting'
},
{
key: '27',
text: '使用者列表',
path: '/user/list'
}
]
},
{
key: 'group6',
title: {
icon: 'info-circle',
text: '平臺設定'
},
children: [
{
key: '31',
text: '使用者協議',
path: '/platform/license'
},
{
key: '32',
text: '幫助中心',
path: '/platform/help'
}
]
}
];
export const groupKey = sidebarData.map(item=>item.key);
複製程式碼
- sidebar
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
// antd
import { Layout, Menu, Icon } from 'antd';
const { Sider } = Layout;
const { SubMenu, Item } = Menu;
import { sidebarData, groupKey } from 'pages/Layout/SidebarData';
// Logo元件
import Logo from 'pages/Layout/Logo';
@withRouter
class Sidebar extends Component {
constructor(props) {
super(props);
// 初始化置空可以在遍歷不到的時候應用預設值
this.state = {
openKeys: [''],
selectedKeys: [''],
rootSubmenuKeys: groupKey,
itemName: ''
};
}
setDefaultActiveItem = ({ location }) => {
const { pathname } = location;
sidebarData.map(item => {
if (item.pathname) {
// 做一些事情,這裡只有二級選單
}
// 因為選單隻有二級,簡單的做個遍歷就可以了
if (item.children && item.children.length > 0) {
item.children.map(childitem => {
// 為什麼要用match是因為 url有可能帶引數等,全等就不可以了
// 若是match不到會返回null
if (pathname.match(childitem.path)) {
this.setState({
openKeys: [item.key],
selectedKeys: [childitem.key]
});
// 設定title
document.title = childitem.text;
}
});
}
});
};
componentDidMount = () => {
// 設定選單的預設值
this.setDefaultActiveItem(this.props);
};
OpenChange = openKeys => {
console.log(openKeys);
const latestOpenKey = openKeys.find(
key => this.state.openKeys.indexOf(key) === -1
);
console.log(latestOpenKey);
if (this.state.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
this.setState({ openKeys });
} else {
this.setState({
openKeys: latestOpenKey ? [latestOpenKey] : [...openKeys]
});
}
};
render() {
const { openKeys, selectedKeys } = this.state;
const { collapsed, onCollapse } = this.props;
const SideTree = sidebarData.map(item => (
<SubMenu
key={item.key}
title={
<span>
<Icon type={item.title.icon} />
<span>{item.title.text}</span>
</span>
}>
{item.children &&
item.children.map(menuItem => (
<Item
key={menuItem.key}
onClick={() => {
// 設定高亮的item
this.setState({ selectedKeys: [menuItem.key] });
// 設定文件標題
document.title = menuItem.text;
}}>
<Link to={menuItem.path}>{menuItem.text}</Link>
</Item>
))}
</SubMenu>
));
return (
<Sider
collapsible
breakpoint="lg"
collapsed={collapsed}
onCollapse={onCollapse}
trigger={collapsed}>
<Logo collapsed={collapsed} />
<Menu
subMenuOpenDelay={0.3}
theme="dark"
openKeys={openKeys}
selectedKeys={selectedKeys}
mode="inline"
onOpenChange={this.OpenChange}>
{SideTree}
</Menu>
</Sider>
);
}
}
export default Sidebar;
複製程式碼
collapsed
,onCollapse
這些是控制側邊欄縮小的,接受的是外部的props
擴充版思路
舉一反三,同樣我們同在可以在靜態路由新增鑑權,比如某個路由僅限某些使用者訪問!
這樣鑑權機制可以做到很細緻化,但是對應的判斷邏輯也會多起來,看業務改了
也可以維護過渡效果,新增對應的欄位,然後每次訪問不同URL的時候更改過渡效果
以上的都需要依賴狀態管理器,來維護,因為涉及到不同元件的通訊,mobx
也可以,redux
也行...蘿蔔青菜各有所愛
答疑
- 小夥伴留言說要看我的目錄如何構建的,其實和常規的搭建差不多,如下
如何生成漂亮的目錄樹
alias gdtree="tree -I 'node_modules|dist|.git|.vscode|.DS_Store|.idea' -L 2 -a"
我直接寫到環境檔案裡了, -L
就是遍歷的層級, -a
是所有檔案(包括隱藏), -I
是正則忽略
├── .babelrc # babel配置
├── .browserslistrc #瀏覽器的相容範圍
├── .editorconfig # 基礎規範
├── .eslintignore # eslint忽略
├── .eslintrc # eslint 配置
├── .gitignore
├── .postcssrc.js # postcss配置
├── .prettierrc # 格式化程式碼的配置檔案
├── README.md
├── build # webpack的構建目錄
│ ├── webpack.base.config.js # 通用的webpack配置,可以理解為common,開發和生產都依賴,比如外掛等
│ ├── webpack.development.js # 開發模式專有,熱更新,反向代理啥的
│ └── webpack.production.js # 儘可能的壓縮切割,抽離樣式為CSS檔案什麼的
├── jsconfig.json # 用來對映webpack alias 的,這樣vscode下才能智慧提示alias的路徑
├── package.json
├── public
│ ├── favicon.png
│ └── index.html
├── src
│ ├── App.css
│ ├── App.js # 根元件
│ ├── PrivateRoute.js # 私有路由,對Route的封裝
│ ├── assets # 靜態資源
│ ├── components # 通用元件
│ ├── index.js # webpack的主入口
│ ├── pages # 頁面元件
│ ├── services # api的封裝,以及彙總地方
│ ├── store # 狀態管理
│ └── utils # 公用的程式碼片段,比如一些函式什麼的
├── tests # 存放jest單元測試的目錄
│ └── union
└── yarn.lock
複製程式碼
總結
公司最近打算重構整個後臺管理系統;把老的兩個系統整合在一起....emmm,
單打獨鬥的好處(bei shang)就是技術選型可以自己把握
說做就做,用最新的webpack4
搭了個架子,開始折騰(因為比較新,更新依賴很容易出問題)....
等專案完畢再把腳手架放出來,估計webpack
5都出來了..
有人肯定會說,官方有現成的antd pro
為嘛不用..我看了跟dva高度結合,不喜歡,那就自己搭架子
之前用vue
和ng
都是整個系統佈局自己寫一遍...這次試試用現成的側邊欄來實現
有不對之處請留言,看到會及時修正,謝謝閱讀.