• 从零开始搞后台管理系统(1)——shin-admin


      

      shin 的读音是[ʃɪn],谐音就是行,寓意可行的后台管理系统,shin-admin 的特点是:

    • 站在巨人的肩膀上,依托Umi 2Dva 2Ant Design 3React 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 */
  • 相关阅读:
    使用XE7并行库中的TTask(转)
    Delphi xe7并行编程快速入门(转)
    Pre-compile (pre-JIT) your assembly on the fly, or trigger JIT compilation ahead-of-time (转)
    使用RemObjects Pascal Script (转)
    Remobjects SDK 服务器搭建
    toString()方法
    环境变量
    jstl标签学习
    SQL的各种join
    Mybatis的一些配置
  • 原文地址:https://www.cnblogs.com/strick/p/13869476.html
Copyright © 2020-2023  润新知