接入:强盛集团管理系统

前言

前一段时间, 写了强盛集团管理系统(基于 BPMN 引擎的工作流系统), 打算使用 qiankun 改造下项目架构, 迈向微前端, 今天开始第五章主题: 配置中心。

最终效果

展示强盛集团管理系统(基于 BPMN 引擎的工作流系统)【独立部署】部分页面

基座在线网址访问

接入工作流系统

  1. 基座中添加子应用的入口
js 复制代码
// register-apps.js

registerMicroApps([
  {
    name: "oa",
    entry: process.env.NODE_ENV === "production" ? "/oa/" : "//localhost:8080",
    activeRule: "/oa",
    container: "#Appmicro",
    loader,
    props: { a: 1, util: {}, globalState: {} },
  },
]);

添加子应用的入口, entry 在打生产包后, 访问子应用需要加/oa/

  1. 改造工作流系统
js 复制代码
// main.js

Vue.config.productionTip = false;

let instance = null;
function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector("#app") : "#app");
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log("[vue] oa app bootstraped");
}

export async function mount(props) {
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = "";
  instance = null;
}

主要是需要对外提供三个函数, bootstrapmountunmount

另外也需要通过 qiankun 标识__POWERED_BY_QIANKUN__来区分, 可以独立运行和部署

  1. 工作流系统在 src 目录新增 public-path.js
js 复制代码
/*eslint disable no undef*/
// 上方这一行用于eslint的忽略,因为下方代码的指向其实是不存在的,在有eslint的环境中不加入此行会报错
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

在基座访问子应用时, 调整静态图片等资源路径

  1. main.js引入
js 复制代码
// main.js
import "./public-path.js";
  1. 打包配置修改(vue.config.js)
js 复制代码
const { name } = require("./package");
module.exports = {
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: "umd", // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

在本地需要配置跨域, 及qiankun 需要 umd 格式的应用包

此时重新启动, 理论上是可以看到工作流系统了, 但还需最后一步, 添加路由.

  1. 在基座中添加工作流路由router/modules/oa.js
js 复制代码
// 子应用菜单

import Layout from "@/components/Layouts";
const appRouter = {
  path: "/oa",
  component: Layout,
  redirect: "/oa/dashboard",
  name: "Oa",
  meta: {
    title: "强盛首页",
    icon: "oa-logo",
  },
  children: [
    {
      path: "/oa/dashboard",
      name: "OADashboard",
      meta: { title: "仪表盘", icon: "dashboard" },
    },
    {
      path: "/oa/apply/induction",
      name: "OASystemUser",
      meta: { title: "更新信息", icon: "system-user" },
    },
  ],
};
export default appRouter;
  1. 添加到路由表中router/index
js 复制代码
import OaRoute from "./modules/oa";

export const routes = [OaRoute];

因为工作流系统需要登录才能查看页面, 先在localhost:8080/login 进行登录, 就可以看到下面这样

  1. 接下来, 简单调整下微应用, 通过 qiankun 标识, 判断下在基座访问时, 不展示左侧栏
html 复制代码
<template>
  <section class="layout" :class="{ qiankun: qiankun }">
    <layout-aside v-if="!qiankun"></layout-aside>
    <section class="layout-container">
      <layout-header v-if="!qiankun"></layout-header>
      <layout-main></layout-main>
      <layout-footer v-if="!qiankun"></layout-footer>
    </section>
  </section>
</template>
<script>
  export default {
    data() {
      return {
        qiankun: window.__POWERED_BY_QIANKUN__,
      };
    },
  };
</script>

此时本地刷新基座就可以看到完整的了

基座登录

技术背景

因为工作流系统页面需要登录, 所以想要在基座直接能访问工作流系统, 则需要打通登录鉴权这部分。

基于工作流系统已经初步完版, 并不想做很大的改动, 更不想改掉原有的登录逻辑。

原有工作流已有登录、注册、填写入职信息、完善信息、修改密码等基础功能。

系统大部分基础功能能够复用,而不用重新写一遍。

技术方案

好在工作流系统, 原有的逻辑是点击登录后先获取 token, 在跳转拦截时, 通过 token 去获取详情和动态菜单

那么, 只需要在基座新增登录页面调用登录接口获取 token, 将 token 分发给工作流系统(微应用), 点击跳转时, 就会根据工作流系统原有逻辑进行鉴权, 从而正常使用。

登录实现

  1. 基座新增登录页面
html 复制代码
<template>
  <div class="login-page">
    <div class="login-box">
      <h2>登录</h2>
      <el-form :model="form" :rules="rules" ref="form">
        <div class="user-box">
          <el-form-item prop="phone">
            <el-input v-model="form.phone" placeholder="账号"></el-input>
          </el-form-item>
        </div>
        <div class="user-box">
          <el-form-item prop="password">
            <el-input
              v-model="form.password"
              type="password"
              show-password
              placeholder="密码"
              @keyup.enter.native="handleLogin"
            >
            </el-input>
          </el-form-item>
        </div>
        <el-button class="login-btn" :loading="loading" @click="handleLogin">
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          登录
        </el-button>
      </el-form>
    </div>
    <footer class="page-footer">
      <a href="https://oa.xiaoxi.work/register" target="_blank"> 👉去注册 </a>
    </footer>
  </div>
</template>
<script>
  export default {
    name: "BaseLogin",
    ...
    methods: {
      handleLogin() {
        this.$refs.form.validate((valid) => {
          if (valid) {
            this.loading = true;
            this.$store
              .dispatch("user/customLogin", this.form)
              .then((res) => {
                this.loading = false;
                // this.$router.replace("/");
                this.$router.replace({ path: this.redirect || "/" });
              })
              .catch((err) => {
                this.loading = false;
              });
          } else {
            return false;
          }
        });
      },
    },
  };
</script>
  1. 新增缓存文件utils/auth.js, 便于刷新不影响访问微应用
js 复制代码
export function getCache(TokenKey = "access_token") {
  return JSON.parse(localStorage.getItem(TokenKey));
}

export function setCache(token, TokenKey = "access_token") {
  return localStorage.setItem(TokenKey, JSON.stringify(token));
}

export function removeCache(TokenKey = "access_token") {
  return localStorage.removeItem(TokenKey);
}

export function clearCache() {
  return localStorage.clear();
}

默认新增access_token, 也可以缓存其他值

  1. 新增用户请求api/user.js
js 复制代码
// 封装请求 baseURL为工作流后端服务地址
import request from "@/utils/request";

// 获取oa系统的token
export const login = (params) => {
  return request({
    url: "/user/login",
    method: "post",
    data: params,
  });
};

request 是封装的请求函数, 但又涉及到baseURL的不同环境, 请求体与响应体拦截内容较多, 不便于过多贴代码。详细代码会放到《部署:微前端初步完成》一章。

  1. 在基座创建自定义登录, 新增用户模块store/modules/user.js
js 复制代码
import { login } from "@/api/user";
import { setCache, removeCache } from "@/utils/auth";

export default {
  namespaced: true,
  state: {
    userInfo: null,
    tokens: {},
  },
  mutations: {
    setUserInfo(state, value) {
      let userInfo = { ...state["userInfo"], ...value };
      setCache(userInfo, `user_info`);
      state["userInfo"] = userInfo;
    },
    setTokens(state, { key, token }) {
      setCache(token);
      state.tokens[key] = token;
    },
  },
  actions: {
    /**
     * 登录系统
     */
    customLogin({ commit }, params) {
      return new Promise((resolve, reject) => {
        login(params)
          .then((res) => {
            if (res.status === 200) {
              commit("setTokens", { key: "oa-token", token: res.data.token });
              let userInfo = {
                isInduction: res.data.isInduction,
                oa_token: res.data.token,
              };
              commit("setUserInfo", userInfo);
              resolve(res);
            } else {
              reject(res);
            }
          })
          .catch((err) => {
            reject(err);
          });
      });
    },

    logout({ commit }) {
      removeCache();
      removeCache("user_info");
      location.href = "/login";
    },
  },
  getters: {
    userInfo: (state) => state.userInfo,
  },
};

userInfo存放用户信息, 目前为是否入职和各微应用的 token 字段, 及后续头像和用户名

此时可以点击登录, 正常跳转到首页了。

localStorage里也缓存了两个字段

基座与子应用通信

在登录实现部分,已经拿到了工作流系统的 token,现在要做的是将 token 分发给工作流系统

因为后续应用间通信会比较多, 有必要将通信部分单独抽离出来

  1. 初始化, 基座 src 下新增qiankun/globalState.js
js 复制代码
import { initGlobalState } from "qiankun";

// 定义全局下发的数据
export const initialState = {
  // 当前登录用户
  userInfo: null,
  // 全局配置
  globalConfig: null,
  // 路由数据
  routers: null,
};

// 初始化全局下发的数据
export const qiankunActions = initGlobalState(initialState);

qiankunActions.onGlobalStateChange((state, oldVal) => {
  console.log("基座接受应用 change事件", state);
  for (const key in state) {
    if (Object.prototype.hasOwnProperty.call(state, key)) {
      const item = state[key];
      initialState[key] = item;
    }
  }
  console.log(initialState);
});

initGlobalState(state): 定义全局状态, 并且会返回一个MicroAppStateActions 实例, 后续会用到这个实例, 需要将qiankunActions这个实例暴露出去。

简单说下 qiankun 官方 Actions 通信的几个 API

initialState: 全局需要定义的数据

initGlobalState: 定义全局状态, 并且会返回一个实例

onGlobalStateChange: 监听全局状态改变

setGlobalState: 设置全局状态

offGlobalStateChange: 取消监听状态改变

  1. 将初始化数据分发到工作流系统(微应用), 改造register-apps.js
js 复制代码
import { initialState } from "@/qiankun/globalState";

registerMicroApps([
  {
    name: "oa",
    entry: process.env.NODE_ENV === "production" ? "/oa/" : "//localhost:8080",
    activeRule: "/oa",
    container: "#Appmicro",
    loader,
    props: { a: 1, util: {}, globalState: initialState },
  },
]);
  1. 将 token 分发下去, 改造基座的Layout/index
html 复制代码
<script>
  import { qiankunActions } from "@/qiankun/globalState.js"
  mounted() {
    // 初始化全局下发的数据
    qiankunActions.setGlobalState({
      userInfo: getCache('user_info'),
      routers: routes,
    });
  }
</script>

放置在Layout/index, 是因为登录跳转必先加载布局, 刷新也是会加载布局。

基座中调用setGlobalState函数也会触发基座的onGlobalStateChange, onGlobalStateChange监听到变化会将新数据重新赋值到initialState。微应用在加载时就可以拿掉token

  1. 工作流系统(微应用), 改造main.js, 将 token 缓存到本地
js 复制代码
// main.js
export async function mount(props) {
  if (props.globalState) {
    let userInfo = props.globalState.userInfo;
    setToken(userInfo.oa_token, 1);
  }
  Vue.prototype.$qiankunActions = props;
  render(props);
}

setToken 为原工作流系统登录成功设置token函数。工作流系统可以正常访问啦。

基座鉴权

目前基座是直接进入到基座首页的, 这种情况下, 如果直接跳转微应用页面就会报错, 则需要对基座跳转进行简单路由拦截。

  1. 新增plugins/permission.js
js 复制代码
import router from "@/router/index";
import { getCache } from "@/utils/auth";

// 登录白名单
const whiteList = ["/login"];

router.beforeEach((to, from, next) => {
  if (getCache()) {
    // to.meta.title && store.dispatch('settings/setTitle', to.meta.title);
    if (to.path === "/login") {
      next({ path: "/" });
    }
     else {
      next();
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next();
    } else {
      const redirect = encodeURIComponent(to.fullPath); // 编码 URI,保证参数跳转回去后,可以继续带上
      next(`/login?redirect=${redirect}`); // 否则全部重定向到登录页
    }
  }
});

简单路由拦截, 通过access_token字段判断是否已登录,

  1. 注册plugins/index.js
js 复制代码
...
import "./permission";
  1. 在退出登录及request请求401时, 清除token即可。

这样即实现了简单鉴权,访问基座需要先登录,会将token分发到微应用,微应用携带token去进行后续的逻辑处理,可以正常访问基座和微应用了。

总结

至此,强盛集团管理系统已经可以在本地正常访问。微前端项目初步完成,下一章会介绍下部署微前端项目。

目前仍存在一些bug,例如微应用间跳转不成功等,仍需要较长时间去解决,对此bug有兴趣也可以看看github

有问题可以私聊我哈。

相关推荐
TU^1 分钟前
C语言习题~day16
c语言·前端·算法
计算机学姐16 分钟前
基于微信小程序的调查问卷管理系统
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
弥琉撒到我2 小时前
微服务swagger解析部署使用全流程
java·微服务·架构·swagger
学习使我快乐013 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19953 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈4 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水5 小时前
简洁之道 - React Hook Form
前端
正小安7 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch9 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j