前言

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

最终效果

在线网址

micro.gif

个人中心

image.png

这个比较简单, 仅展示用户名和头像, 可以增加修改资料、修改密码、退出登录等操作

  1. 在顶部栏LayoutHeader.vue增加
<div class="right-menu">
  <el-dropdown>
    <div class="avator-name-wrap">
      <img
        src="https://p3-passport.byteimg.com/img/user-avatar/0a00ccea3ae4f53e16afea907c9f1342~180x180.awebp"
      />
      <div class="name">前端小溪</div>
    </div>
    <el-dropdown-menu slot="dropdown">
      <el-dropdown-item @click.native="logout">
        <span>退出登录</span>
      </el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</div>

仅写了 html 部分的代码, 有兴趣的可以移步 gitee

配置中心

配置中心是为了控制系统布局、主题色等,也可以增加一些自定义的配置项。

配置 config 类

  1. 新增 config/config.js
class Config {
  constructor() {
    this._config = {};
  }
  regist(type, value) {
    this._config[type] = value;
  }
  get(type) {
    return this._config[type];
  }
}

export default new Config();

简单实现了 config 类, 目前有两个方法, regist为注册, get为获取。

  1. 新增皮肤配置 config/theme.js
import config from "./config";

export const init = () => {
  config.regist("theme", {
    light: {
      layoutLeftBg: "#ffffff",
      layoutLeftText: "#303133",
      layoutLeftActiveText: "#409eff",
    },
    dark: {
      layoutLeftBg: "#2c3e50",
      layoutLeftText: "#ecf0f1",
      layoutLeftActiveText: "#1890ff",
    },
  });
};

注册一个皮肤theme, 将需要的样式变量注册进去。

  1. 将皮肤配置统一注册, 新增config/index.js
import { init as themeInit } from "./config/theme";
themeInit();
  1. 在项目入口引入main.js
import "./config/index";

配置面板

  1. 创建面板components/template/panel.vue
<!-- 面板 -->
<template>
  <div class="panel-page" :class="isOpen ? 'panel-open' : ''">
    <div class="panel-bg"></div>
    <div class="panel">
      <slot name="btn">
        <div class="panel-btn" @click="toggle">
          <i :class="isOpen ? 'el-icon-close' : 'el-icon-setting'"></i>
        </div>
      </slot>
      <div class="panel-container">
        <slot> </slot>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Panel",
  data() {
    return {
      isOpen: false,
    };
  },
  methods: {
    toggle() {
      this.isOpen = !this.isOpen;
    },
  },
};
</script>

样式代码较多, 感兴趣移步 gitee

  1. 新建配置面板 components/Config.vue
<template>
  <div class="config-panel">
    <panel>
      <el-col>
        <h3 class="config-item-title">整体风格设置</h3>
        <div class="theme-list">
          <div
            class="theme-item"
            v-for="item in themeList"
            :key="item.key"
            :title="item.title"
            :class="[item.key]"
            @click="handleChange('themeType', item.key)"
          >
            <icon
              name="check"
              class="select-icon"
              scale="0.6"
              :style="{ display: themeType === item.key ? 'block' : 'none' }"
            ></icon>
          </div>
        </div>
      </el-col>
      <el-col>
        <h3 class="config-item-title">面包屑位置</h3>
        <el-radio-group
          v-model="breadcrumb"
          @change="(val) => handleChange('breadcrumbPosition', val)"
        >
          <el-radio
            label="header"
            :disabled="themeType === 'realDark' || themeType === 'realLight'"
            >顶部栏</el-radio
          >
          <el-radio label="main">内容区</el-radio>
        </el-radio-group>
      </el-col>
      <el-col>
        <h3 class="config-item-title">侧边栏 Logo</h3>
        <el-switch
          v-model="logo_visible"
          active-text="展示"
          inactive-text="隐藏"
          @change="(val) => handleChange('logoVisible', val)"
        >
        </el-switch>
      </el-col>
    </panel>
  </div>
</template>

<script>
import { mapGetters, mapActions } from "vuex";
import Panel from "./template/Panel.vue";
import config from "@/config/config";
export default {
  name: "ConfigPanel",
  components: { Panel },
  computed: {
    ...mapGetters("app", [
      "sidebar",
      "themeType",
      "breadcrumbPosition",
      "logoVisible",
    ]),
  },
  data() {
    return {
      themeList: [
        {
          key: "dark",
          title: "暗色风格",
        },
        {
          key: "light",
          title: "亮色风格",
        },
        {
          key: "realDark",
          title: "暗黑风格",
        },
        {
          key: "realLight",
          title: "亮白风格",
        },
      ],
      breadcrumb: this.$store.getters["app/breadcrumbPosition"],
      logo_visible: this.$store.getters["app/logoVisible"],
    };
  },
  methods: {
    handleChange(type, value) {
      if (type === "themeType") {
        this.$store.commit(`app/set_theme_type`, value);
        if (value === "realDark" || value === "realLight") {
          this.breadcrumb = "main";
          this.$store.commit(`app/set_breadcrumb_position`, "main");
        }
      } else if (type === "breadcrumbPosition") {
        this.$store.commit(`app/set_breadcrumb_position`, value);
      } else if (type === "logoVisible") {
        this.$store.commit(`app/set_logo_visible`, value);
      }
    },
  },
};
</script>

<style lang="less" scoped>
@import url("@/assets/css/common-variables.less");
.theme-item {
  &::before {
    position: absolute;
    top: 0;
    left: 0;
    width: 33%;
    height: 100%;
    background-color: @layout-header-bg;
    content: "";
  }
  &::after {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 25%;
    background-color: @layout-header-bg;
    content: "";
  }
  &.light {
    &::before {
      background-color: @layout-header-bg;
      content: "";
    }
    &::after {
      background-color: @layout-header-bg;
    }
  }
  &.dark {
    &::before {
      z-index: 1;
      background-color: @layout-left-bg;
      content: "";
    }
    &::after {
      background-color: @layout-header-bg;
    }
  }
}
</style>
  1. Layout/index.vue中引入组件, 新增以下部分
<template>
  <config-panel></config-panel>
</template>

<script>
import ConfigPanel from "@/components/Config.vue";
export default {
  components: {
    ConfigPanel,
  },
};
</script>

此时配置面板的样式已经出来了, 风格设置背景是利用伪类实现的。

控制样式

要做状态管理必然需要用到 Vuex, 增加一个 app 模块

  1. 新增 store/modules/app.js
export default {
  namespaced: true,
  state: {
    sidebar: {
      opened: true,
      themeType: "dark", // dark 暗黑模式  light  亮光模式
      position: "left", // left 左侧栏  header 头部
    },
    breadcrumbPosition: "header", // header 顶部栏  main 内容区
    themeType: "dark", // light 亮色 dark 暗色 realDark 暗黑 realLight 亮白
    theme: "", // 主题色
    logoVisible: true, // 是否显示logo
  },
  mutations: {
    set_sidebar(state, { key, value }) {
      state.sidebar[key] = value;
    },
    set_breadcrumb_position(state, value) {
      state.breadcrumbPosition = value;
    },
    set_logo_visible(state, value) {
      state.logoVisible = value;
    },
    set_theme_type(state, value) {
      state.themeType = value;
      if (value === "light") {
        state.sidebar.themeType = value;
        state.sidebar.position = "left";
      } else if (value === "dark") {
        state.sidebar.themeType = value;
        state.sidebar.position = "left";
      } else if (value === "realDark") {
        state.sidebar.themeType = "dark";
        state.sidebar.position = "header";
      } else {
        state.sidebar.themeType = "light";
        state.sidebar.position = "header";
      }
    },
  },
  actions: {
    setSidebar({ commit }, { key, value }) {
      commit("set_sidebar", { key, value });
    },
    toggleCollapse({ commit }, value) {
      commit("set_sidebar", { key: "opened", value });
    },
    setBreadcrumbPosition({ commit }, value) {
      commit("set_breadcrumb_position", value);
    },
  },
  getters: {
    sidebar: (state) => state.sidebar,
    breadcrumbPosition: (state) => state.breadcrumbPosition,
    themeType: (state) => state.themeType,
    logoVisible: (state) => state.logoVisible,
  },
};

sidebar 为左侧栏设置, 包括风格、打开、位置

breadcrumbPosition 面包屑出现位置, 可以在顶部栏或内容区

themeType 为整体风格设置, 默认暗色模式

theme 系统主题色, 需要配合插件, 暂时不用

logoVisible 是否展示左侧栏顶部的 logo

  1. store/index.js注册 app
import Vue from "vue";
import Vuex from "vuex";
import app from "./modules/app";

Vue.use(Vuex);
export default new Vuex.Store({
  modules: {
    app,
  },
});

改造左侧栏

  1. 改造LayoutAside.vue
<!-- 左侧栏 -->
<template>
  <div class="layout-aside" :style="{ backgroundColor: theme.layoutLeftBg }">
    <slot v-if="logoVisible"></slot>
    <el-scrollbar class="layout-aside-scrollbar">
      <el-menu
        :class="[
          'layout-aside-menu',
          isCollapse ? 'collapse' : 'not_collapse',
          mode === 'horizontal' ? 'el-menu-horizontal' : 'el-menu-vertical',
        ]"
        :default-active="defaultActive"
        router
        unique-opened
        :mode="mode"
        :collapse="isCollapse"
        :background-color="theme.layoutLeftBg"
        :text-color="theme.layoutLeftText"
        :active-text-color="theme.layoutLeftActiveText"
      >
        <layout-aside-menu-item
          v-for="item in menuRoutes"
          :item="item"
          :key="item.path"
        />
      </el-menu>
    </el-scrollbar>
  </div>
</template>
<script>
import LayoutAsideMenuItem from "./LayoutAsideMenuItem.vue";
import { routes } from "@/router/index";
import { mapGetters } from "vuex";
import config from "@/config/config";

export default {
  name: "LayoutAside",
  components: { LayoutAsideMenuItem },
  props: {
    mode: {
      type: String,
      default: () => "vertical",
    },
  },
  computed: {
    ...mapGetters("app", ["sidebar", "logoVisible"]),
    defaultActive() {
      let matched = this.$route.matched;
      return matched[matched.length - 1].path;
    },
    isCollapse() {
      return !this.sidebar.opened;
    },
    theme() {
      return config.get("theme")[this.sidebar.themeType];
    },
  },
  data() {
    return {
      menuRoutes: routes,
    };
  },
};
</script>

使用计算属性, 引入配置选项的状态

改动一: 根节点添加样式 :style="{ backgroundColor: theme.layoutLeftBg }"

改动二: el-menu 标签增加了 mode 属性, 用以顶部栏水平展示, 左侧栏垂直展示

改动三: el-menu 标签颜色设置采用对应主题风格下的颜色

改动四: 将 logo 部分设置为插槽, 因为水平展示时, 不需要 logo

  1. 改造Layout/index.vue, 新增以下部分, 用以左侧栏垂直展示
<template>
  <layout-aside v-if="sidebar.position === 'left'">
    <template>
      <div class="layout-aside-logo" :style="{ color: theme.layoutLeftText }">
        <icon name="oa-logo" scale="1"></icon>
        <span class="name" v-if="!isCollapse">强盛集团</span>
      </div>
    </template>
  </layout-aside>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
import config from "@/config/config";

export default {
  name: "Layout",
  computed: {
    ...mapGetters("app", ["sidebar", "breadcrumbPosition"]),
    isCollapse() {
      return !this.sidebar.opened;
    },
    theme() {
      return config.get("theme")[this.sidebar.themeType];
    },
  },
};
</script>

将左侧栏的 logo 部分用插槽的形式进行插入, 这样 logo 就仅在左侧栏展示时才会有

  1. 改造LayoutHeader.vue, 新增以下代码, 用以左侧栏水平展示
<!-- 头部栏 -->
<template>
 <div
    class="layout-header"
    :style="{
      backgroundColor:
        sidebar.position === 'header' ? theme.layoutLeftBg : null,
    }"
  >
        <layout-aside mode="horizontal" v-if="sidebar.position === 'header'">
    </div>
</template>

<script>
import config from "@/config/config";
export default {
    name: "LayoutHeader",
    computed: {
        ...mapGetters("app", ["sidebar", "breadcrumbPosition"]),
        theme() {
        console.log(this.sidebar, "theme");
        return config.get("theme")[this.sidebar.themeType];
        },
    },
    methods: {
        ...mapActions("app", ["toggleCollapse"]),
        toggle() {
        this.toggleCollapse(!this.sidebar.opened);
        },
    },
}
</script>

样式部分比较简单, 且较多, 不方便全部放入, 感兴趣的可以移步 gitee

此时整体风格设置就实现了, 比较简易, 但也算完成了初步 qiankun 基座的布局。

面包屑位置控制

前面在 Vuex 中已经设定了 breadcrumbPosition 字段来判别面包屑顺序, 所以只需要在LayoutHeader.vueLayoutMain.vue 中添加字段判断

<!--  LayoutHeader.vue -->
<template>
  <breadcrumb v-if="breadcrumbPosition === 'header'" />
</template>

<script>
import Breadcrumb from "@/components/Breadcrumb.vue";
export default {
  name: "LayoutHeader",
  components: { Breadcrumb },
};
</script>
<!--  LayoutMain.vue -->
<template>
  <breadcrumb v-if="breadcrumbPosition === 'main'" />
</template>

<script>
import Breadcrumb from "@/components/Breadcrumb.vue";
export default {
  name: "LayoutMain",
  components: { Breadcrumb },
};
</script>

总结

至此, 本项目已经实现了左侧栏的折叠和面包屑、个人中心、设置面板,之后会正式接入原有项目。

有问题可以私聊我哈。