從零開始搞後臺管理系統(1)——shin-admin
shin 的讀音是[ʃɪn],諧音就是行,寓意可行的後臺管理系統,shin-admin 的特點是:
- 站在巨人的肩膀上,依託Umi 2、Dva 2、Ant Design 3和React 16.8搭建的定製化後臺。
- 介於半成品和成品之間,有很強的可塑性,短期內你就能把控全域性。
- 藉助模板元件可快速交付90%以上的頁面。
- 多樣的許可權粒度,大到選單,小到介面。
- 容易擴充套件,例如引入統計用的圖表或富文字編輯器等。
當然它還有一些不友好的地方:
- 大流程絕對能跑起來,但仍潛伏著很多細節BUG有待解決。
- 有一定的學習成本,需要學習Umi配置,Dva資料流方案,Ant Design元件以及React、ES6+等語法。
準備工作
1)安裝
在將專案下載下來後,來到其根目錄,執行安裝命令,自動將依賴包下載到本地。
$ npm install
2)啟動
啟動開發伺服器,預設會進入登入頁(下圖),由於會呼叫本地的Mock資料,所以即使沒有後端伺服器,專案也能執行。若要與後端配合,可參考 shin-server。
$ npm start
使用者名稱密碼可以隨意輸入,提交後進入系統主頁,目前是空白的,可自定義。
3)構建
在開發完成後呼叫構建命令,可自動生成dist目錄,將該目錄上傳到伺服器上用於部署。
$ npm run build
在 package.json 的 scripts 欄位中,還提供了其他命令,例如 lint、test 等。
4)執行流程
管理系統執行的大致流程,如下圖所示,其中賬號的登入態認證,基於JWT的方式。
目錄
├── shin-admin │ ├── docs ----------------------------------- 說明文件 │ ├── mock ----------------------------------- MOCK 資料 │ ├── src ------------------------------------ 原始碼 │ ├───└─── api ------------------------------- 介面API宣告 │ ├───└─── assets ---------------------------- 靜態資源 │ ├───└─── components ------------------------ 全域性通用元件 │ └───└────└──── Common ---------------------- 功能元件 │ └───└────└──── Layout ---------------------- 結構元件 │ └───└──── layouts -------------------------- 頁面整體結構 │ └───└──── models --------------------------- 全域性 model(資料處理) │ └───└──── pages ---------------------------- 頁面 │ └───└────└──── path ------------------------ 頁面路徑(任意名稱) │ └───└────└────└──── index.js --------------- 檢視邏輯 │ └───└────└────└──── model.js --------------- 頁面 model │ └───└──── services ------------------------- 與後端通訊的服務(可選) │ └───└──── utils ---------------------------- 各類工具輔助程式碼 │ └───└──── app.js --------------------------- 執行時配置,處理40X狀態碼 │ └───└──── routes.js ------------------------ 路由 │ └───└──── authority.js --------------------- 許可權 │ └───└──── global.less ---------------------- 全域性樣式 │ ├── .env ----------------------------------- 環境變數 │ ├── .umirc.js ------------------------------ umi 配置 └───└── package.json --------------------------- 命令和依賴包
1)api
api目錄下可包含多個檔案,預設只有一個 index.js,聲明瞭與後端通訊的 API 地址,例如。
export default { templateCreate: "template/create", //模板示例中的建立和編輯 templateQuery: "template/query", //模板示例中的查詢 templateHandle: "template/handle", //模板示例中的資料處理 }
2)components
功能元件包括重置密碼、拖動列表、上傳按鈕和模板元件,具體用法可參考此處。
結構元件包括頂部導航、側邊選單欄、麵包屑導航和快速搜尋,在上面的主頁圖中已體現。
3)models
model 檔案是 Dva 中的概念,用於處理元件中的資料(下面是資料流向圖),典型事例參考此處。
app.model({ namespace: 'app', //名稱空間,同時也是他在全域性 state 上的屬性 state: {}, //初始值 //處理同步操作,唯一可以修改 state 的地方,由 action 觸發 reducers: { add(state, { payload: todo }) { return [...state, todo]; // 儲存資料到 state }, }, //處理非同步操作和業務邏輯(和伺服器互動),不直接修改 state,由 action 觸發 effects: { *save({ payload: todo }, { put, call }) { // 呼叫 saveTodoToServer,成功後觸發 `add` action 儲存到 state yield call(saveTodoToServer, todo); yield put({ type: 'add', payload: todo }); }, }, //用於訂閱一個數據源,然後根據需要 dispatch 相應的 action subscriptions: { setup({ history, dispatch }) { // 監聽 history 變化,當進入 `/` 時觸發 `load` action return history.listen(({ pathname }) => { if (pathname === '/') { dispatch({ type: 'load' }); } }); }, }, });
4)pages
所有頁面的邏輯都放在此目錄下,例如訪問 http://localhost:8000/template/list ,那麼就需要先建立 template 目錄,然後建立其子目錄 list,即路徑為 pages/template/list。
在子目錄中會包含 index.js 和 model.js,偶爾也會建立 less 樣式檔案。
由於採用了 Dva 資料流方案,因此在 index.js 中就不能直接修改內部狀態(state),只能 dispatch 相應的 action,然後在 model.js 檔案中更新狀態。
下面是 index.js 的一個示例,App 元件中的 id 引數是 model 檔案中的狀態,dispatch()函式是Dva的庫函式,用於觸發 action。
底部的 connect() 函式用於連線 model 和 component。app 是 model.js 檔案中的名稱空間,App 是元件名稱。
import React from 'react'; import { connect } from 'dva'; import { Button } from 'antd'; const App = ({ id, dispatch }) => { const onCreate = () => { dispatch({ type: 'app/save', payload: { id }, }); }; return <Button type="primary" onClick={onCreate}>新建</Button>; }; export default connect(data => data.app)(App);
5)services
用來與後端通訊,但在使用過程中發現經常只是做一層中轉,內部並沒有很多特定的邏輯,例如下面的登入函式。
其實就是宣告一個請求地址,要傳遞的資料以及請求方法。
import request from '../utils/request'; export async function login(data) { return request('/api/user/login', { method: 'POST', data, }); }
完全可以提煉出來,直接在 model.js 檔案中直接發起請求,例如先在 api 處宣告好地址(程式碼中的 url 引數),redirect、get 和 post 是封裝的三種請求方式。
import { redirect, get, post } from 'utils/request'; export default { namespace: 'template', state: {}, effects: { //查詢 *query({ payload }, { call, put }) { const { url, params } = payload; const { data } = yield call(get, url, params); }, //Excel匯出 *export({ payload }, { call, put }) { yield call(redirect, payload.url, payload.params); }, //處理資料,增刪改 *handle({ payload }, { call, put, select }) { const { url, params } = payload; const { data } = yield call(post, url, params); }, }, };
6)utils
utils 目錄中的檔案如下:
- config.js:全域性配置引數
- constants.js:全域性常量
- menu.js:選單處理
- request.js:基於 axios 封裝的通訊庫
- tools.js:雜七雜八的工具函式
7)app.js
app.js 在處理各種異常響應時會給出不同的提示,在401時會跳轉到登入頁。
export const dva = { config: { onError(error) { if (error.status) { switch (error.status) { case 401: window.location = '/login'; break; case 403: message.error('403 : 沒有許可權'); break; case 404: message.error('404 : 物件不存在'); break; case 409: message.error('409 : 服務升級,請重新登入'); break; case 504: message.error('504 : 網路有點問題'); break; default: Modal.error({ content: `${error.status} : ${error.response.data.error}` }); } } else { Modal.error({ content: error.message }); } }, }, };
8)routes.js
routes.js會宣告元件和路由之間的對映關係,其實 pages 目錄下的各個頁面就是一個個的元件。
module.exports = [ { path: '/', component: '../layouts/index', //component 相對於 src/pages 目錄 routes: [ { path: '/', component: 'index' }, { path: '/login', component: 'login/', exact: true }, { path: '/template/list', component: 'template/list/', exact: true }, { path: '/*', component: '404', exact: true }, ] } ];
9)authority.js
authority.js 中的許可權會形成一棵樹形結構,當 type 為 1 時,會在左側選單欄中展示,為 2 時就僅做一個介面許可權。圖示的選擇可參考此處。
/** * 許可權列表 * @param id {string} 許可權id * @param pid {string} 父級許可權id * @param status {number} 是否開啟 1 開啟 2 關閉 * @param type {string} 許可權型別 1 選單 2 介面 * @param name {string} 許可權名稱 * @param desc {string} 許可權描述 * @param routers {string} 許可權相關路由 * @param icon {string} 選單圖示 */ export default [ { id: 'backend', pid: '', status: 1, type: 1, name: '管理後臺', desc: '', routers: '/', icon: 'desktop', }, { id: 'backend.template', pid: 'backend', status: 1, type: 1, name: '全域性模板', desc: '', routers: '/template', icon: 'file-text', }, { id: 'backend.template.list', pid: 'backend.template', status: 1, type: 1, name: '列表模板', desc: '', routers: '/template/list', } ]
10).umirc.js
在 .umirc.js 中可引入路由資訊,配置路徑別名,開啟代理伺服器。
當配置了路由別名時,就不需要寫相對路徑了,但是無法使用IDE工具的程式碼導航了。
import request from 'utils/request';
搭建
1)常規流程
在 pages 下的 login 和 user 兩個目錄中,採用了常規的搭建流程。
- 在 index.js 檔案中編寫檢視的各類邏輯,將幾個特定元件抽象到當前的 components 子目錄中。
- 在 model.js 檔案中處理各類元件狀態,並且引用 serveices 中宣告的函式。
其實很多後臺頁面所需的狀態(例如Loading、列表、數量等)和幾個特定元件都差不多,例如過濾條件、列表、模態視窗等,沒必要每次寫頁面都重新宣告一下。
在此背景下,提煉出了通用的模板元件(用法文件),位於 components/Common 的 Template 和 Upload 兩個目錄中,效果如下面兩張圖所示。
2)高速流程
模板元件(用法文件)就是將一些頁面互動和資料處理封裝起來,呼叫的時候只需要定義各類引數,就能快速搭建出一套完整的邏輯,並且能大大減少BUG數量。
以往搭建下面這樣的一張頁面(包括列表、分頁、建立、查詢、模態視窗等部分),熟練的話也得兩三個小時以上,而採用模板元件的話,最多半小時就能完成。
在 template 目錄中演示了三種類型的模板頁面:列表、表單和照片牆。
在 tool 目錄中完成了對模板元件的實踐。
3)開發步驟
- 在 pages 目錄中建立頁面模組,分別新建 index.js 和 model.js。
- 在 api 目錄中宣告路由或在 services 目錄中建立通訊服務。
- 如果需要新增選單欄,得需要三步走。
- 在 src 目錄的 routes.js 路由檔案中宣告路徑。保證 path 唯一性,component以 ”/“ 結尾,預設取該資料夾下 index.js。
- 在 src 目錄的 authority.js 檔案中配置許可權列表項,routes 屬性的值對應上面的 component 屬性, id 會與後端許可權中介軟體呼叫的關鍵字保持一致。
- 在使用者管理 -》 角色管理 -》角色列表中,為當前角色增加該選單的訪問許可權,然後退出登入重進。
- 重啟專案。
其他
1)MOCK資料
Umi 框架安裝了第三方的Mock.js模擬請求資料甚至邏輯,能夠讓前端開發獨立自主,不會被服務端的開發所阻塞。
若要關閉,只要在 .env 檔案中新增 MOCK=none 或者在 start 命令中將其新增即可。
MOCK=none umi dev
2)ESLint
在 .eslintrc 中修改預設的配置,無法生效,無奈只能在某個檔案頂部顯式地宣告,以此規避ESLint預設的規則。
/* eslint-disable */
&n