快速打造 react 後臺管理系統
前言
相信很多小夥伴都有可能碰到開發後臺管理系統這樣的需求,那麼我們該如何快速的完成這個需求呢
本文將以react為切入點,記錄打造一個基礎管理系統模板的過程,以此加深對react技術棧以及專案實戰的理解,希望對大家開發一個這樣的專案有所幫助
如果文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過
專案簡介
react-admin 是由 create-react-app 腳手架快速構建,基於 React 生態系統搭建的後臺管理系統模板。實現了登陸/登出、路由懶載入、axios封裝、簡單許可權管理等功能,它可以幫助你快速生成管理系統模板,你只需要新增具體業務程式碼即可
線上預覽地址預覽地址
GitHub程式碼程式碼地址
技術棧
此專案涉及的技術棧主要有 es6、react、react-router、redux、react-redux、Create React App、react-loadable、axios等,所以你可能需要提前瞭解這些知識,這樣會對你瞭解這個專案有很大的幫助
基本功能
- 路由懶載入
- 麵包屑導航
- 常用UI展示
- echarts全屏展示
- 登陸/登出功能
- axios封裝
- 簡單許可權管理
專案結構
├── public # 不參與編譯的資原始檔
├── src # 主程式目錄
│ ├── api # axios 封裝
│ ├── assets # 資原始檔
│ │ ├── font # 字型檔案
│ │ └── images # 圖片資源
│ ├── components # 全域性公共元件
│ │ ├── CustomBreadcrumb # 麵包屑導航
│ │ └── CustomMenu # menu 選單
│ ├── contatiners # 頁面結構元件
│ ├── routes # 路由目錄
│ ├── store # redux 配置
│ ├── style # 樣式目錄
│ ├── utils # 工具 類
│ ├── views # UI 頁面
│ ├── APP.js # App.js
│ └── index.js # index.js
├── .prettierrc.js # 程式碼規範
├── config-overrides.js # antd 樣式按需載入
整體思路
打造一個任何一個專案,除去前期需要考慮的受眾群體(其實就是相容…)之外,再加上技術選型,之後就到了頁面架構層。在這個專案中,前期不在我們的考慮範圍之內(已經確定了有木有),所以就從頁面架構開始
一個後臺管理專案,無論它是否具有許可權校驗功能,但是有一些公共的部分是應該有的。比如登陸頁面、公共的頭部、側邊欄導航、底部以及錯誤頁面等。這些共有的部分以及許可權特有的部分共同組成了這個系統
劃分出這樣的模組之後,一個專案基本的頁面架構也就完成了,因為這一部分關係到我們之後對於頁面路由的定義,所以我認為是很重要的一部分
路由
基於[email protected]
路由功能可以說是一個React專案的關鍵,通過前面對於頁面架構的劃分,我們就可以很流暢的進行路由的註冊
基本
在react-admin這個專案中,所使用的是<HashRouter>
首先,我們需要區分公共頁面和可能的特有頁面
<Router>
<Switch>
<!--精確匹配是不是在首頁-->
<Route path='/' exact render={() => <Redirect to='/index' />} />
<!-- 錯誤頁面 -->
<Route path='/500' component={View500} />
<Route path='/login' component={Login} />
<Route path='/404' component={View404} />
<!-- UI頁面 -->
<Route path='/' component={DefaultLayout} />
</Switch>
</Router>
接下來就可以去註冊路由表,再將它迴圈遍歷到我們的視口頁面上。大家可能也發現了迴圈遍歷這個詞,運算元組就意味著我們可以對它進行一系列的篩選,從而實現 路由許可權 的控制(這個我們後面再說)
懶載入
作為一個SPA級應用,有很多優勢(響應速度更快、良好的前後端分離等等),但是也存在很多缺陷,首次載入耗時過長就是我們不得不面對的問題
其實從webpack4.0開始,它本身已經實現了按需載入元件,但是也有它自己的一些規則(比如檔案大小),所以我們還是需要對頁面的首次載入進行一些處理,而路由就是一個很好的切入點
本專案使用的是react-loadable,它可以用很簡單的方式實現路由的懶載入,設定延遲時間、載入動畫、服務端渲染等功能
import Loadable from 'react-loadable';
import Loading from './my-loading-component'; // 這裡可以放置你的 loading
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
當然,你也可以使用React.lazy和Suspense技術(傳送門),不過,它不支援服務端渲染
登入
登入的邏輯也很簡單:
我們的專案首頁一般情況下是index頁面,這個時候我們就需要先去判斷一個使用者載入進來的時候是不是在登入狀態,如果是那麼就正常顯示,如果不是就應該跳轉到登入頁面
本專案使用的是將使用者的登入資訊儲存在localStorage中,登出的時候清除localStorage
關於token可以直接存在本地,後臺設定一個過期時間就可以了
還有一種情況就是使用者登入之後,但是由於長時間沒有操作導致token過期了,這個時候可能就會出現兩種選擇:
- 讓使用者直接跳轉到登入頁面重新登入
- 檢視本地是否儲存了使用者資訊,如果有就更新使用者的token,讓其繼續操作,反之則跳轉到登入頁面(這個取決於你將使用者資訊儲存在哪裡)
當然,具體要怎麼做,還是取決於產品的需求,這裡只是提供一種思路
廣州品牌設計公司https://www.houdianzi.com
axios 封裝
基本操作
專案使用axios與後臺進行互動,封裝的部分有新增請求攔截器、響應攔截器、設定響應時間以及將token新增到請求頭等功能
import axios from 'axios'
// 這裡取決於登入的時候將 token 儲存在哪裡
const token = localStorage.getItem('token')
const instance = axios.create({
timeout: 5000
})
// 設定post請求頭
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
// 新增請求攔截器
instance.interceptors.request.use(
config => {
// 將 token 新增到請求頭
token && (config.headers.Authorization = token)
return config
},
error => {
return Promise.reject(error)
}
)
// 新增響應攔截器
instance.interceptors.response.use(
response => {
if (response.status === 200) {
return Promise.resolve(response)
} else {
return Promise.reject(response)
}
},
error => {
// 相應錯誤處理
// 比如: token 過期, 無許可權訪問, 路徑不存在, 伺服器問題等
switch (error.response.status) {
case 401:
break
case 403:
break
case 404:
break
case 500:
break
default:
console.log('其他錯誤資訊')
}
return Promise.reject(error)
}
)
export default instance
這裡並沒有對baseUrl進行設定,主要是考慮到專案中可能存在不止一個url,比如圖片這些資源可能存在七牛雲或者阿里雲這樣的伺服器上面,而後臺介面又是另外一個url了。所以添加了一個config檔案,匯出各個url
// 考慮到網站可能有好幾個域名,所以單獨提出來
export const API = 'http://rap2api.taobao.org/app/mock/234047'
export const URLAPI = ''
呼叫介面的時候就可以直接這樣
import {API} from './api/config'
import axios from './api'
axios.get({
url: API + '/login'
})
當然,如果你並沒有這樣的需求,你完全可以取消config這個檔案,將baseUrl一併封裝進去
同樣的,也並沒有對常用的請求比如get、post等進行封裝,因為使用這些方式的時候可能會對資料做一些特定的操作,比如序列化等等,所以個人感覺意義並不是很大
跨域
這裡順便記錄一下跨域問題的解決方式:
如果你沒有使用npmrun eject將webpack的配置暴露出來,可以直接在package.json中配置proxy
"proxy": {
"/api": {
"target": "http://100.100.100.100", //後端地址
"changeOrigin": true
}
}
這樣只需要在baseUrl後面新增/api就可以了
當然,如果你將webpack中的配置暴露出來了,那麼也可以直接在config檔案中進行設定,都是可以實現同樣的效果
許可權
許可權功能基本上是後臺管理專案中不可或缺的部分
一般情況下,許可權的控制體現在頁面級別以及按鈕級別(使用者是否可以訪問某個頁面或者操作某個按鈕,比如新增、刪除),這個許可權在使用者註冊、分配或者後期超級管理員更改的時候確定
專案實現
在這個專案中使用簡單的許可權控制,希望可以給大家一些思路,具體實現方式:
- 使用者登入,從後臺獲取註冊時的角色(許可權標識)
- 通過許可權標識,對註冊的menu選單進行過濾,渲染到頁面
getMenu = menu => {
let newMenu,
auth = JSON.parse(localStorage.getItem('user')).auth // 獲取儲存的使用者許可權標識
if (!auth) {
return menu
} else {
// 過濾註冊的 menu
newMenu = menu.filter(res => res.auth && res.auth.indexOf(auth) !== -1)
return newMenu
}
}
- 通過許可權標識,對註冊的路由陣列進行過濾
{routes.map(item => {
return (
<Route
key={item.path}
path={item.path}
exact={item.exact}
render={props =>
!auth ? (
<item.component {...props} />
) : item.auth && item.auth.indexOf(auth) !== -1 ? (
<item.component {...props} />
) : (
// 這裡也可以跳轉到 403 頁面
<Redirect to='/404' {...props} />
)
}></Route>
)
})}
- 按鈕許可權,直接使用許可權標識判斷可否操作,隱藏或者展示即可
說明:這裡對註冊的路由陣列進行過濾這一步進行說明,一般情況下前端路由都是提前註冊好的,就算沒有menu選單導航,如果我們在位址列直接輸入路徑也是可以訪問的,這裡進行一次過濾之後就可以避免這種情況。當然,我們也可以給每一個許可權設定一個可以訪問的路徑陣列,通過比較跳轉的地址是否存在這個陣列當中來進行相應的展示
後臺控制
這裡也簡單說一下後臺控制權限的案例,對於前端來說要簡單很多
- 使用者登入,拿到需要展示的menu陣列,直接渲染到頁面(對選單的篩選由後臺完成)
- 通過許可權標識,判斷使用者有沒有某個按鈕的操作許可權
至於使用者在位址列直接輸入地址去訪問,這裡有兩種情況:
- 如果使用者沒有訪問某一個頁面的許可權,那麼使用其token請求後臺資料的時候一定是不成功的,我們可以將這一個操作封裝在axios請求中,通過不同的狀態碼進行頁面跳轉
- 我就是訪問了一個沒有請求的頁面(這個頁面還不給沒許可權的人看),那我們就採用過濾許可權陣列的方式對其操作進行阻止
當然,這裡的第二種情況很少見…
其它
專案中還集成了平時可能會遇到的一些功能需求
動畫
動畫使用的是Animate.css動畫庫,使用方式
// 下載
yarn add animate.css
// link標籤引入
<link rel="stylesheet" href="animate.min.css">
// 或者 import 引入
import 'animate.css'
然後在需要的盒子上面新增相應的類名即可,可以設定入場、離場動畫,也可以設定動畫時間、延時等
富文字
富文字編輯器使用的是Antd官方推薦的braft-editor
一個基於draft-js的Web富文字編輯器,適用於React框架,相容主流現代瀏覽器
使用方式:
// 引入
import BraftEditor from 'braft-editor'
// 可以使用 BraftEditor.createEditorState 方法來將 raw 或者 html 格式的資料轉換成 editorState 資料
editorState: BraftEditor.createEditorState('你好,<b>可愛的人! 很幸運在這裡與你相遇!</b>')
更多具體的元件屬性及例項方法,大家可以參考其文件傳送門
echarts
echarts相信大家不會陌生,百度的文件也很清晰,這裡單獨提出來主要記錄開發過程中遇到的一個問題
在頁面視窗變化的情況下我們可以使用
window.addEventListener('resize', function() {
myChart.resize()
})
這種方式保證echarts的正常自適應
可是當我們在點選選單收縮展開按鈕的時候並不會觸發window.resize方法,其實頁面盒子的寬度已經發生了變化,只是echarts的resize事件已經觸發結束了,這個時候我們只需要在componentDidUpdate這個生命週期中註冊一個定時器延時觸發resize事件就解決了,只是別忘了在componentWillUnmount生命週期中清除掉這個定時器
載入進度條
載入進度條使用的是nprogress,使用方式也很簡單
// 下載
yarn add nprogress
// 引入
import NProgress from 'nprogress'
// 開始載入
NProgress.start();
// 載入結束
NProgress.done();
// 移除進度條
NProgress.remove();
全屏外掛
全屏功能使用的是screenfull外掛
使用方式
// 下載
yarn add screenfull
if (screenfull.isEnabled) {
screenfull.request(); // 全屏
}
.exit() // 退出
.toggle() // 可切換
也可以給它 註冊change事件
if (screenfull.isEnabled) {
screenfull.on('change', () => {
console.log('Am I fullscreen?', screenfull.isFullscreen ? 'Yes' : 'No');
});
}
但是別忘了移除掉這個事件
screenfull.off('change', callback);
程式碼格式統一
Create React App提供了一組最常見的錯誤規則,在程式碼執行的時候會有錯誤資訊提示,所以Eslint規則在這裡並不是必須的,如果你也想在程式碼書寫的時候就提示錯誤可以進行下面這個步驟:
下載eslint
yarnadd eslint
新增一個.eslintrc.js檔案或者在package.json檔案中的eslintConfig物件中直接新增你需要使用的規則
更多規則可以參考這裡
專案中並沒有使用eslint,只是添加了pretter為專案統一了編碼風格
至於如何在專案中整合pretter,具體的使用方式可以參考官方文件,這裡就不在敘述了
最後
這個專案都是本人閒暇時間開發,主要是為了熟悉react開發流程以及其周邊生態的使用,專案還是比較簡陋,後期會進行迭代開發,將其打造成一個更加實用的後臺管理模板