1. 程式人生 > >從零開始搞後臺管理系統(1)——shin-admin

從零開始搞後臺管理系統(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)開發步驟

  1. 在 pages 目錄中建立頁面模組,分別新建 index.js 和 model.js。
  2. 在 api 目錄中宣告路由或在 services 目錄中建立通訊服務。
  3. 如果需要新增選單欄,得需要三步走。
    • 在 src 目錄的 routes.js 路由檔案中宣告路徑。保證 path 唯一性,component以 ”/“ 結尾,預設取該資料夾下 index.js。
    • 在 src 目錄的 authority.js 檔案中配置許可權列表項,routes 屬性的值對應上面的 component 屬性, id 會與後端許可權中介軟體呼叫的關鍵字保持一致。
    • 在使用者管理 -》 角色管理 -》角色列表中,為當前角色增加該選單的訪問許可權,然後退出登入重進。
  4. 重啟專案。

其他

1)MOCK資料

  Umi 框架安裝了第三方的Mock.js模擬請求資料甚至邏輯,能夠讓前端開發獨立自主,不會被服務端的開發所阻塞。

  若要關閉,只要在 .env 檔案中新增 MOCK=none 或者在 start 命令中將其新增即可。

MOCK=none umi dev

2)ESLint

  在 .eslintrc 中修改預設的配置,無法生效,無奈只能在某個檔案頂部顯式地宣告,以此規避ESLint預設的規則。

/* eslint-disable */

&n