如何同时开发小程序+中后台应用(feat: icejs) - 后台管理系统篇

知乎:使用 React + icejs 开发一个完整的 Todo 应用 - 后台系统篇
语雀:如何同时开发小程序+中后台应用(feat: icejs) - 后台管理系统篇

icejs 主要应用场景为开发中后台应用。但 icejs@1.7.0 版本开始支持小程序开发 。如果你想使用 React 同时开发中后台应用和小程序,那么 icejs 即可满足你。使用同一套技术体系,减少技术切换成本,提高研发效率。

介绍

本文将演示如何使用 icejs 构建 Todo 小程序 + 后台管理系统,同时包括相应服务端。

Todo 应用的功能或需求为:

  • 小程序端:展示 Todo 列表,支持增删改查,以及同步数据到服务端。
  • 后台管理系统:小程序用户信息和增删改查的管理系统。

整体的技术栈设计如下:

  • *小程序 *
    • icejs 框架
    • universal-request 数据请求
  • 后台管理系统
    • icejs 框架
    • icejs build-plugin-ice-auth 插件,权限管理
    • fusion design UI 组件库
  • 服务端 + 数据库
    • eggjs 服务端框架
    • egg-sequelize + mysql2 用于 eggjs 连接 MySQL 数据库
    • MySQL 数据库
    • uuid 唯一 id 生成

因篇幅较长,如何同时开发小程序+中后台应用(feat: icejs)将分为三篇分别介绍。

  • 小程序篇

使用 icejs 开发 Todo 小程序。

  • 后台管理系统篇(本文)

使用 icejs 开发 Todo 小程序后台管理系统。

  • 服务端篇

搭建服务 Todo 小程序及后台管理系统的服务端。

后台管理系统

项目代码见 icejs-miniapp-admin/client
后台管理系统基于 icejs

后台管理系统实现对小程序内容的管理。

具体功能包括:

  • 账户登录/登出
  • 用户列表查看、查看某个用户所有代办事项(Todo)
  • 代办事项列表查看、增删改查

项目初始化

起步

创建文件夹存放后台管理系统代码

$ mkdir icejs-todos/client -p && cd icejs-todos/client

基于 React 模板 Fusion Design Pro - JS 创建项目

后台管理系统绝大部分内容基于该模板,可先尝试熟悉该模板。

$ npm init ice . @alifd/fusion-design-pro-js # 当前目录初始化项目

启动项目

$ npm install && npm run start

# 打开浏览器 http://localhost:3333 可看到项目正常启动

目录结构

.
├── .ice                                // icejs 运行时临时目录
├── public
│   ├── favicon.png                     // favicon
│   └── index.html                      // 应用入口
├── src                                 // 源码目录
│   ├── components                      // 全局组件
│   │   ├── LocaleProvider              // 多语言
│   │   │   └── index.jsx
│   │   └── PageHeader                  // 页首
│   │       ├── index.jsx
│   │       └── index.module.scss
│   ├── layouts                         // 布局组件
│   │   ├── BasicLayout                 // 基本布局
│   │   │   ├── components
│   │   │   ├── index.jsx
│   │   │   └── menuConfig.js           // 侧边栏配置
│   │   └── UserLayout                  // 登录/注册页布局
│   │       ├── index.jsx
│   │       └── index.module.scss
│   ├── locales                         // 多语言
│   │   ├── en-US
│   │   │   └── index.js
│   │   └── zh-CN
│   │       └── index.js
│   ├── models                          // 全局状态
│   │   └── user.js
│   ├── pages                           // 页面组件 包含较多页面,此处省略
│   ├── utils                           // 工具函数
│   │   └── locale.js
│   ├── app.jsx                         // 应用入口
│   ├── global.scss                     // 全局样式
│   └── routes.js                       // 路由配置
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .stylelintignore
├── .stylelintrc.js
├── README.md                            // README
├── build.json                           // icejs 工程配置
├── jsconfig.json
├── package-lock.json
├── package.json
├── screenshot.png
└── tsconfig.json

页面编写

根据 Todo 应用需求对 Fusion Design Pro - JS 模板加以修改,编写相应页面,通过 API 接口与服务端交互。

调整目录结构:

  • 删除 src/pages 未使用页面
  • 添加用户管理页和代办事项页(基于 src/pages/FusionDialogTable)
  • 修改基本页面布局组件 src/layouts/BasicLayout
  • 添加数据请求文件夹 src/services

调整后的页面主要包括:

  • 登录页 src/pages/Login

该页用于账户登录

  • 首页 src/pages/Home

该页用于介绍后台管理系统

  • 用户管理页:src/pages/Users
    该页面实现用户的展示及查看用户所创建的 Todo。
  • 代办事项页:src/pages/Todos
    该页面提供对于 Todo 的增删改查操作。

调整后的目录结构如下:

  .
  ├── .ice
  ├── public
  │   ├── favicon.png
  │   └── index.html
  ├── src
  │   ├── components
  │   │   ├── LocaleProvider
  │   │   └── PageHeader
  │   ├── layouts
  │   │   ├── BasicLayout
  │   │   └── UserLayout
  │   ├── locales
  │   │   ├── en-US
  │   │   └── zh-CN
  │   ├── models
  │   │   └── user.js
  │   ├── pages                  // 移除无用页面,只保留 Home Login Register
  │   │   ├── Home
  │   │   ├── Login
  │   │   ├── Register
+ │   │   ├── Todos              // 代办事项页
+ │   │   └── Users              // 用户页
+ │   ├── services               // 数据请求
+ │   │   ├── auth.js            // 登录登出
+ │   │   ├── todos.js           // 代码事项
+ │   │   └── users.js           // 用户
  │   ├── utils
  │   │   └── locale.js
  │   ├── app.jsx
  │   ├── global.scss
  │   └── routes.js
  ├── .editorconfig
  ├── .eslintcache
  ├── .eslintignore
  ├── .eslintrc.js
  ├── .prettierignore
  ├── .prettierrc.js
  ├── .stylelintignore
  ├── .stylelintrc.js
  ├── README.md
  ├── build.json
  ├── jsconfig.json
  ├── package-lock.json
  ├── package.json
  └── tsconfig.json

UI 主要基于 Fusion Design Pro - JS 模板修改。故不在进行具体介绍。可直接查看项目代码。
可将 Todos、Users、Home 页从此处复制到本项目中,完成页面编写。

页面编写完成后,可配置对应路由和配置侧边栏

路由配置

// route.js
// 引入路由组件
import UserLayout from '@/layouts/UserLayout';
import Login from '@/pages/Login';
import BasicLayout from '@/layouts/BasicLayout';
import Home from '@/pages/Home';
import Users from '@/pages/Users';
import Todos from '@/pages/Todos';

const routerConfig = [
  {
    path: '/user',         // 配置路由
    component: UserLayout, // 配置路由对应组件
    children: [            // 配置子路由
      {
        path: '/login',
        component: Login,
      },
      {
        path: '/',
        redirect: '/user/login',
      },
    ],
  },
  {
    path: '/',
    component: BasicLayout,
    children: [
      {
        path: '/home',
        component: Home,
      },
      {
        path: '/users/:openId',
        component: Todos,
      },
      {
        path: '/users',
        component: Users
      },
      {
        path: '/todos',
        component: Todos
      },
      {
        path: '/',
        redirect: '/home',
      },
    ],
  },
];
export default routerConfig;

侧边栏配置

侧边栏通过 menuConfig.js 进行配置

// src/layouts/BasicLayout/menuConfig.js
const headerMenuConfig = [];
// 侧边栏配置
const asideMenuConfig = [
  {
    name: '首页',
    path: '/home',
    icon: 'chart-pie'
  },
  {
    name: '代办事项',
    path: '/todos',
    icon: 'calendar'
  },
  {
    name: '用户列表',
    path: '/users',
    icon: 'account'
  }
];
export { headerMenuConfig, asideMenuConfig };

到现在为止,页面编写已完成。此时还不能查看页面效果。各页面中有调用数据请求服务,下面需要添加数据请求。

数据请求

数据请求直接采用 icejs 中的数据请求方案。用户只需从 ice 中导出 request 模块即可发起数据请求。
即:

import { request } from 'ice'; // 引入数据请求库

export default {
  // 发起 GET 请求
	async list () {
  	const res = await request.get('/todos');
    return res;
  },
  // 发起 POST 请求
  async del (id) {
  	const res = await request.post('/todos/del', {
      id
    })
    return res;
  }
}

service 编写

本项目中的数据请求主要包括

  • 账户登录、登出
  • 用户列表展示
  • Todo 列表展示及增删改查

具体的逻辑在 src/services 中。

src/services
├── auth.js   // 账户登录登出
├── todos.js  // Todo 列表查询及增删改查
└── users.js  // 用户列表查询

auth 包括获取用户角色,登录,登出

import { request } from 'ice';

export default {
  // 获取用户角色
  async getRoles () {
    let roles = [];
    try {
      const res = await request.get('/auth/roles');
      roles = res.data.roles;
    } catch (err) {
      console.error(err);
    }
    return roles;
  },
  // 登录
  async login (data) {
    return await request.post('/auth/login', data)
  },
  // 登出
  async logout () {
    return await request.get('/auth/logout');
  }
}

users 包括用户列表的获取

import { request } from 'ice';

export default {
  // 获取用户列表,支持分页查询
  async list({ current, pageSize }) {
    let data = {};
    try {
      const res = await request.get(`/users?current=${current}&size=${pageSize}`);
      data = res.data;
    } catch (err) {
      console.error(err);
    }
    return data;
  }
}

todos 包括查询 Todo 列表,及对 Todo 的增删改查

import { request } from 'ice';

export default {
  // 根据分页数据及用户 id 查询 Todo 列表
  async list ({ current, pageSize, openId }) {
    let data = {};
    try {
      let URL = `/todos?current=${current}&size=${pageSize}`
      if (openId) {
        URL = `${URL}&openId=${openId}`
      }
      const res = await request.get(URL);
      data = res.data;
    } catch (err) {
      console.error(err);
    }
    return data;
  },
  // 为用户添加 Todo
  async add ({ content, openId }) {
    let data = {};
    try {
      const res = await request.post('/todos/add', {
        openId,
        content
      })
      data = res.data;
    } catch (err) {
      console.error(err);
    }
    return data;
  },
  // 根据 id 删除 Todo
  async del (id) {
    let data = {};
    try {
      const res = await request.get(`/todos/del/${id}`)
      data = res.data;
    } catch (err) {
      console.error(err);
    }
    return data;
  },
  // 编辑 Todo
  async edit (id, { openId, content }) {
    let data = {};
    try {
      const res = await request.post('/todos/edit', {
        id,
        openId,
        content
      })
      data = res.data;
    } catch (err) {
      console.error(err);
    }
    return data;
  },
  // 查看 Todo
  async view (id) {
    let data = {};
    try {
      const res = await request.get(`/todos/view/${id}`)
      data = res.data;
    } catch (err) {
      console.error(err);
    }
    return data;
  },
}

可对 request 请求进行全局配置,使各请求均以 /api 开头。编辑 src/app.jsx

// src/app.jsx
const appConfig = {
  app: {
    rootId: 'ice-container',
    addProvider: ({ children }) => <LocaleProvider locale={locale}>{children}</LocaleProvider>,
  },
  // 配置 request
  request: {
    baseURL: '/api'
  }
};

此时运行项目查看效果

$ npm run start

icejs8.png

Mock 数据

此时数据均为空,service 发起的请求均为 404 not found。因具体的数据需要服务端支持,暂时可通过 mock 数据查看效果。
编辑 build.json 使得 request 指向 mock 服务 https://easy-mock.bookset.io/mock/5f4f05642ff5d50508b3d21b/todos_mock

该 mock 服务只提供 Todo 及用户列表查询,Todo 增删改查无效。

// build.json
{
	"proxy": {
    "/api": {
      "enable": true,
      "target": "https://easy-mock.bookset.io/mock/5f4f05642ff5d50508b3d21b/todos_mock"
    }
  }
}

查看效果
icejs11.png

权限管理

只有登录用户才能访问用户管理页和 Todo 页,未登录用户应重定向到登录页。要实现该逻辑,需要对各页面设置权限。

权限管理主要通过 icejs 插件 build-plugin-ice-auth 实现。

需要安装该插件

$ npm install build-plugin-ice-auth --save-dev

并配置

// build.json
{
  "plugins": [
    "build-plugin-ice-auth"
  ]
}

项目逻辑

在本项目中权限管理的逻辑主要包括:

  1. 页面加载时从服务端获取权限数据

    // app.jsx
    import authService from '@/services/auth';
    
    const appConfig = {
      app: {
        // 页面初始化时获取权限数据
        getInitialData: async () => {
          const roles = await authService.getRoles();
          return {
            auth: {
              roles
            }
          }
        }
        // ...
      }
    };
  2. 创建权限管理组件,并管理相应页面
    权限管理组件 src/components/Auth

    // src/components/Auth/index.jsx
    import React from 'react';
    import { useAuth, Redirect } from 'ice';
    
    const WrapperPage = (PageComponent) => {
    
      const WrappedPage = (props) => {
        const [auth] = useAuth();
        // 页面鉴权 根据权限进行页面跳转
        const needAuth = !auth.roles.includes('user');
        return (
          needAuth ? <Redirect to="/user/login" /> : <PageComponent {...props} />
        )
      };
      return WrappedPage;
    };
    
    export default WrapperPage;
  3. 使用 Auth 组件管理 BasicLayout

    // src/layouts/BasicLayout/index.jsx
    import Auth from '@/components/Auth';
    
    // 具体布局组件
    function BasicLayout(){
      return (
        // ... 省略逻辑
      )
    }
    
    // 使用 Auth 组件包装 BasicLayout
    export default Auth(BasicLayout)
  4. 添加登录/登出逻辑

后台管理系统代码中复制相应页面
替换 src/pages/Login:接入登录接口
替换 src/layouts/BasicLayout:接入登出接口,更改UI

  1. 运行项目查看效果

icejs15.png

  • 登出->登录页

icejs16.png

小结

本文介绍了后台管理系统的搭建。内容主要包括页面权限管理方案的实现,渲染通过数据请求获取到的数据。

Todo 的增删改查功能需要搭配服务端使用,该部分内容详见服务端篇。

后台管理系统代码见 icejs-miniapp-admin/client,可结合服务端 icejs-miniapp-admin/server 一起运行查看效果。

推荐阅读