接入:强盛集团管理系统

前言

前一段时间, 写了强盛集团管理系统(基于 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

有问题可以私聊我哈。

相关推荐
乐闻x4 分钟前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚6 分钟前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Amd79421 分钟前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You30 分钟前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生41 分钟前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
baiduopenmap1 小时前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish1 小时前
小程序webview我爱死你了 小程序webview和H5通讯
前端
菜牙买菜1 小时前
让安卓也能玩出Element-Plus的表格效果
前端
请叫我欧皇i1 小时前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_1 小时前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js