快速打造 react 後臺管理系統

項目預覽地址

前言

相信很多小夥伴都有可能碰到開發後臺管理系統這樣的需求,那麼我們該如何快速的完成這個需求呢

本文將以 react 爲切入點,記錄打造一個基礎管理系統模板的過程,以此加深對 react 技術棧以及項目實戰的理解,希望對大家開發一個這樣的項目有所幫助

如果文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過

以下↓

項目簡介

react-admin 是由 create-react-app 腳手架快速構建,基於 React 生態系統搭建的後臺管理系統模板。實現了登陸/註銷、路由懶加載、axios封裝、簡單權限管理等功能,它可以幫助你快速生成管理系統模板,你只需要添加具體業務代碼即可

線上預覽地址 預覽地址

GitHub 代碼 代碼地址

技術棧

此項目涉及的技術棧主要有 es6reactreact-routerreduxreact-reduxCreate React Appreact-loadableaxios等,所以你可能需要提前瞭解這些知識,這樣會對你瞭解這個項目有很大的幫助

基本功能

  • 路由懶加載
  • 麪包屑導航
  • 常用 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.lazySuspense 技術(傳送門),不過,它不支持服務端渲染

登錄

登錄的邏輯也很簡單:

我們的項目首頁一般情況下是 index 頁面,這個時候我們就需要先去判斷一個用戶加載進來的時候是不是在登錄狀態,如果是那麼就正常顯示,如果不是就應該跳轉到登錄頁面

本項目使用的是將用戶的登錄信息存儲在 localStorage中,註銷的時候清除 localStorage

關於 token 可以直接存在本地,後臺設定一個過期時間就可以了

還有一種情況就是用戶登錄之後,但是由於長時間沒有操作導致 token 過期了,這個時候可能就會出現兩種選擇:

  • 讓用戶直接跳轉到登錄頁面重新登錄
  • 查看本地是否存儲了用戶信息,如果有就更新用戶的 token ,讓其繼續操作,反之則跳轉到登錄頁面(這個取決於你將用戶信息存儲在哪裏)

當然,具體要怎麼做,還是取決於產品的需求,這裏只是提供一種思路

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 一併封裝進去

同樣的,也並沒有對常用的請求比如 getpost 等進行封裝,因爲使用這些方式的時候可能會對數據做一些特定的操作,比如序列化等等,所以個人感覺意義並不是很大

跨域

這裏順便記錄一下跨域問題的解決方式:

如果你沒有使用 npm run ejectwebpack 的配置暴露出來,可以直接在 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-jsWeb富文本編輯器,適用於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 方法,其實頁面盒子的寬度已經發生了變化,只是 echartsresize 事件已經觸發結束了,這個時候我們只需要在 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
yarn add eslint
添加一個 .eslintrc.js 文件或者在 package.json 文件中的 eslintConfig 對象中直接添加你需要使用的規則

更多規則可以參考這裏

項目中並沒有使用 eslint,只是添加了 pretter 爲項目統一了編碼風格

至於如何在項目中集成 pretter ,具體的使用方式可以參考 官方文檔,這裏就不在敘述了

最後

這個項目都是本人閒暇時間開發,主要是爲了熟悉 react 開發流程以及其周邊生態的使用,項目還是比較簡陋,後期會進行迭代開發,將其打造成一個更加實用的後臺管理模板

如果覺得不錯或者對你有些許的幫助,歡迎 star,或者你有更好的實現方式、有趣的 idea,也歡迎留言交流

最後,推薦一下本人的前端學習歷程文檔,裏面分享了許多前端知識的小片段 點擊這裏 進行查看, 歡迎 star 關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章