今天在vue-element-admin學習筆記--Setting保存到cookie筆記里有網友留言關於切換主題后,在刷新頁面主題樣式就失效的問題。之前沒太注意,只記得是聯網切換(只替換顏色)和自定義主題切換兩種。這里說的是只替換顏色的形式。
按照vue-element-admin的文檔中所述:
element-ui 2.0 版本之后所有的樣式都是基於 SCSS 編寫的,所有的顏色都是基於幾個基礎顏色變量來設置的,所以就不難實現動態換膚了,只要找到那幾個顏色變量修改它就可以了。 首先我們需要拿到通過 package.json 拿到 element-ui 的版本號,根據該版本號去請求相應的樣式。拿到樣式之后將樣色,通過正則匹配和替換,將顏色變量替換成你需要的,之后動態添加 style 標簽來覆蓋原有的 css 樣式。
於是仔細看了下ThemePicker組件的換膚方法,於是考慮臨時的實現思路如下:
- 主題顏色從布局layoout的index開始加載(定義一個方法然后mount階段執行),這樣刷新頁面后就可以加載主題顏色
- 從cookie中取主題沒有的話就取默認的
- 在布局layout的index中,根據ThemePicker中的換膚的代碼,在線獲取換膚的主題顏色並替換,可以參考源碼中的getThemeCluster()方法和getCSSString()
- 這樣每次刷新頁面就可以獲取保存的皮膚了
根據這樣的思路,於是修改自己demo中的src\layouts\index.vue文件,這里的demo使用的是vue-element-admin-i18n版本的源碼為基准,部分內容有改動,所以目錄結構是一樣的。代碼如下:
<template>
<div :class="classObj"
class="app-wrapper">
<div v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView }"
class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<right-panel v-if="true">
<settings />
</right-panel>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
import RightPanel from "@/components/RightPanel";
import Settings from "./components/Settings";
import AppMain from "./components/AppMain";
import Navbar from "./components/NavBar";
import Sidebar from "./components/Sidebar";
import TagsView from "./components/TagsView";
// import ResizeMixin from "./mixin/ResizeHandler";
import Cookies from "js-cookie";
//加載皮膚用
const version = require("element-ui/package.json").version; // element-ui version from node_modules
const ORIGINAL_THEME = "#409EFF"; // default color
export default {
name: "Layout",
components: { AppMain, Navbar, Sidebar, TagsView, RightPanel, Settings },
// mixins: [ResizeMixin],
computed: {
...mapState({
sidebar: state => state.app.sidebar,
device: state => state.app.device,
showSettings: state => state.settings.showSettings,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
}),
classObj () {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === "mobile"
};
}
},
mounted () {
//取cooike中的換膚
this.handleUserTheme();
},
methods: {
handleClickOutside () {
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
},
//換皮膚 防止F5后皮膚丟失
async handleUserTheme () {
let val = Cookies.get("theme");
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME;
if (typeof val !== "string") return;
const themeCluster = this.getThemeCluster(val.replace("#", ""));
const originalCluster = this.getThemeCluster(oldVal.replace("#", ""));
console.log(themeCluster, originalCluster);
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(
ORIGINAL_THEME.replace("#", "")
);
const newStyle = this.updateStyle(
this[variable],
originalCluster,
themeCluster
);
let styleTag = document.getElementById(id);
if (!styleTag) {
styleTag = document.createElement("style");
styleTag.setAttribute("id", id);
document.head.appendChild(styleTag);
}
styleTag.innerText = newStyle;
};
};
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`;
await this.getCSSString(url, "chalk");
}
const chalkHandler = getHandler("chalk", "chalk-style");
chalkHandler();
const styles = [].slice
.call(document.querySelectorAll("style"))
.filter(style => {
const text = style.innerText;
return (
new RegExp(oldVal, "i").test(text) && !/Chalk Variables/.test(text)
);
});
styles.forEach(style => {
const { innerText } = style;
if (typeof innerText !== "string") return;
style.innerText = this.updateStyle(
innerText,
originalCluster,
themeCluster
);
});
},
getThemeCluster (theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16);
let green = parseInt(color.slice(2, 4), 16);
let blue = parseInt(color.slice(4, 6), 16);
if (tint === 0) {
// when primary color is in its rgb space
return [red, green, blue].join(",");
} else {
red += Math.round(tint * (255 - red));
green += Math.round(tint * (255 - green));
blue += Math.round(tint * (255 - blue));
red = red.toString(16);
green = green.toString(16);
blue = blue.toString(16);
return `#${red}${green}${blue}`;
}
};
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16);
let green = parseInt(color.slice(2, 4), 16);
let blue = parseInt(color.slice(4, 6), 16);
red = Math.round((1 - shade) * red);
green = Math.round((1 - shade) * green);
blue = Math.round((1 - shade) * blue);
red = red.toString(16);
green = green.toString(16);
blue = blue.toString(16);
return `#${red}${green}${blue}`;
};
const clusters = [theme];
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))));
}
clusters.push(shadeColor(theme, 0.1));
return clusters;
},
updateStyle (style, oldCluster, newCluster) {
let newStyle = style;
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, "ig"), newCluster[index]);
});
return newStyle;
},
getCSSString (url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, "");
resolve();
}
};
xhr.open("GET", url);
xhr.send();
});
},
}
};
</script>
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.mobile .fixed-header {
width: 100%;
}
</style>
代碼可以對比vue-element-admin的源碼來看,這里將ThemePicker中更換主題顏色的方法復制過來並修改,可以臨時滿足刷新頁面能夠保存主體顏色的功能。但有以下幾個問題,可以進行優化:
- 在加載布局前先獲取主題然后緩存起來,這樣就不必每次刷新都請求網絡了
- 把樣式相關的緩存都從cookie改到localstorage里存儲
- 參考ThemePicker中的async theme(val)方法,感覺可以改成定義成單獨的組件,混入的方式加載到頁面里。或者有其他更好的方法,因為我對於vue還處於簡單的使用階段,挺多功能不太熟練。
- 因為現在是要在線獲取主題然后在替換,所以頁面會閃一下原來的主題。如果改成提前加載或者提前從localstorage里取,然后在替換就可以解決這個問題了
暫時只想到這些,歡迎補充討論。
