前一段时间, 写了强盛集团管理系统(基于 BPMN 引擎的工作流系统), 打算使用 qiankun 改造下项目架构, 迈向微前端, 今天开始第五章主题: 配置中心。
这个比较简单, 仅展示用户名和头像, 可以增加修改资料、修改密码、退出登录等操作
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/config.jsclass Config { constructor() { this._config = {}; } regist(type, value) { this._config[type] = value; } get(type) { return this._config[type]; } } export default new Config();
简单实现了 config 类, 目前有两个方法, regist为注册, get为获取。
config/theme.jsimport config from "./config"; export const init = () => { config.regist("theme", { light: { layoutLeftBg: "#ffffff", layoutLeftText: "#303133", layoutLeftActiveText: "#409eff", }, dark: { layoutLeftBg: "#2c3e50", layoutLeftText: "#ecf0f1", layoutLeftActiveText: "#1890ff", }, }); };
注册一个皮肤theme, 将需要的样式变量注册进去。
config/index.jsimport { init as themeInit } from "./config/theme"; themeInit();
main.jsimport "./config/index";
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
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>
Layout/index.vue中引入组件, 新增以下部分<template>
<config-panel></config-panel>
</template>
<script>
import ConfigPanel from "@/components/Config.vue";
export default {
components: {
ConfigPanel,
},
};
</script>
此时配置面板的样式已经出来了, 风格设置背景是利用伪类实现的。
要做状态管理必然需要用到 Vuex, 增加一个 app 模块
store/modules/app.jsexport 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
store/index.js注册 appimport Vue from "vue"; import Vuex from "vuex"; import app from "./modules/app"; Vue.use(Vuex); export default new Vuex.Store({ modules: { app, }, });
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
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 就仅在左侧栏展示时才会有
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.vue 和 LayoutMain.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>
至此, 本项目已经实现了左侧栏的折叠和面包屑、个人中心、设置面板,之后会正式接入原有项目。
有问题可以私聊我哈。