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 */