更换主题
安装插件
bash
npm i css-color-function
npm i rgb-hex
npm i vite-plugin-require-transform
- 然后再 vite.config.js 中配置
js
import { defineConfig } from "vite";
import requireTransform from "vite-plugin-require-transform";
export default defineConfig({
plugins: [
requireTransform({
fileRegex: /.js$|.vue$|.json$/,
}),
],
});
封装换肤组件
components/ColorPicker/index.vue
components/ThemeColor/index.vue
components/ColorPicker/index.vue
vue
<template>
<el-dialog
:modelValue="dialogVisible"
width="500"
align-center
:title="titlemsg"
>
<div style="width: 32px; height: 32px; margin: 0 auto">
<el-color-picker
v-model="color"
show-alpha
:predefine="predefineColors"
@change="changecolor"
/>
</div>
</el-dialog>
</template>
<script setup>
import { generateNewStyle, writeNewStyle } from "@/utils/theme";
import { useZhuTiStore } from "@/stores/ZhuTi.js";
const StoreModules = useZhuTiStore();
const Props = defineProps({
// 弹出框显示
dialogVisible: {
type: Boolean,
},
type: {
type: Number,
required: true,
},
titlemsg: {
type: String,
},
});
watch(
() => Props.type,
(newval, oldval) => {
if (newval != oldval) {
colorinit();
}
}
);
// 结果
onMounted(() => {
colorinit();
});
const color = ref("");
const predefineColors = ref([]);
const colorinit = () => {
let resultcolor = "";
let pickColor = [];
if (Props.type === 1) {
resultcolor = StoreModules.bgcolor;
pickColor = [...StoreModules.bgcolorarr];
} else if (Props.type === 2) {
resultcolor = StoreModules.defaultTextColor;
pickColor = [...StoreModules.defaultTextColorarr];
} else if (Props.type === 3) {
resultcolor = StoreModules.activeTextColor;
pickColor = [...StoreModules.activeTextColorarr];
}
color.value = resultcolor;
predefineColors.value = pickColor;
};
const changecolor = async () => {
if (Props.type === 1) {
// 1.1 获取主题色
const newStyleText = await generateNewStyle(color.value, 1);
// 1.2 写入最新主题色
writeNewStyle(newStyleText);
StoreModules.changebgColor(color.value);
} else if (Props.type === 2) {
// 1.1 获取文字颜色
const newStyleText = await generateNewStyle(color.value, 2);
// 1.2 写入文字主题色
writeNewStyle(newStyleText);
StoreModules.changetextColor(color.value);
} else if (Props.type == 3) {
// 1.1 获取当前颜色
const newStyleText = await generateNewStyle(color.value, 3);
// 1.2 写入最新当前颜色
writeNewStyle(newStyleText);
StoreModules.changeactivetextcolor(color.value);
}
};
</script>
<style lang="scss" scoped></style>
components/ThemeColor/index.vue
- 对应文字
bash
const en = {
message: {
hello: 'hello',
themeChange: 'themeChange',
textChange: 'textChange',
activetextChange: 'activetextChange'
}
};
export default en;
---------中文--------------
const cn = {
message: {
hello: '你好',
themeChange: '主题更换',
textChange: '文字更换',
activetextChange: '选中更换'
}
};
export default cn;
--------------ja.js-------------------
const ja = {
message: {
hello: '日本语hello',
themeChange: 'themeChange',
textChange: 'textChange',
activetextChange: 'activetextChange'
}
};
export default ja;
- 组件代码
vue
<template>
<el-dropdown>
<span class="imgicon">
<SvgIcon icon="change-theme" size="36" color="black"></SvgIcon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="changelangtheme">
{{ $t("message.themeChange") }}
</el-dropdown-item>
<el-dropdown-item @click="changelangText">
{{ $t("message.textChange") }}
</el-dropdown-item>
<el-dropdown-item @click="changelangactiveText">
{{ $t("message.activetextChange") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!--展示弹层开始-->
<ColorPicker
v-model="dialogVisible"
:type="typemodel"
:title="titlemsg"
></ColorPicker>
<!--展示弹层结束-->
</template>
<script setup>
import { useI18n } from "vue-i18n";
const { t } = useI18n();
// 是否显示弹框
const dialogVisible = ref(false);
const typemodel = ref(1); // 1 就是主题 2就是文字
const titlemsg = ref("");
const changelangtheme = () => {
typemodel.value = 1;
titlemsg.value = t("message.themeChange");
// 展示弹框
dialogVisible.value = true;
};
const changelangText = () => {
typemodel.value = 2;
titlemsg.value = t("message.textChange");
dialogVisible.value = true;
};
const changelangactiveText = () => {
typemodel.value = 3;
titlemsg.value = t("message.activetextChange");
dialogVisible.value = true;
};
</script>
<style lang="scss" scoped>
:deep(.el-tooltip__trigger:focus-visible) {
outline: unset;
}
.el-dropdown {
.imgicon {
width: 30px;
height: 30px;
float: right;
display: block;
img {
display: block;
width: 100%;
height: 100%;
}
}
}
</style>
封装 ZhuTi 模块
封装常量
- src/constant/index.js
js
// token
export const TOKEN = "token";
// token 时间戳
export const TIME_STAMP = "timeStamp";
// 超时时长(毫秒) 180天
export const TOKEN_TIMEOUT_VALUE = 24 * 3600 * 1000 * 180;
// 国际化
export const LANG = "language";
// 主题色保存的 key
export const MAIN_COLOR = "mainColor";
// 默认色值
export const DEFAULT_COLOR = "#409eff";
// tags
export const TAGS_VIEW = "tagsView";
export const USERINFO = "userInfo";
export const ISCOLLAPSE = "isCollapse";
export const BGCOLORINDEX = "bgcolorIndex";
export const ACTIVETEXTCOLORINDEX = "activeTextColorindex";
export const DEFAULTTEXTCOLORINDEX = "defaultTextColorindex";
export const BGCOLOR = "bgcolor";
export const TEXTCOLOR = "textcolor";
export const ACTIVETEXTCOLOR = "activeTextcolor";
封装 ColorPicker 初始变量
注意
这里的名称必须是 xxxx.module.scss 否则暴露不出去
- src/scss/zhuti/zhuti.module.scss
js
$bgcolor:#2d3a4b,#FFCE46,#C050C1,#99D9EA,#F08080,#FE8463,#9BCA63,#FC8463,#60C0DD,#D7504B,#C6E579,#F4D5C9,#D5E6F2,#F5E0D4,#F5E0D4,#F5E0D4;
$activecolor: #2d3a4b,#FFCE46,#C050C1,#99D9EA,#F08080,#FE8463,#9BCA63,#FC8463,#60C0DD,#D7504B,#C6E579,#F4D5C9,#D5E6F2,#F5E0D4,#F5E0D4,#F5E0D4;
$defaultcolor:#2d3a4b,#FFCE46,#C050C1,#99D9EA,#F08080,#FE8463,#9BCA63,#FC8463,#60C0DD,#D7504B,#C6E579,#F4D5C9,#D5E6F2,#F5E0D4,#F5E0D4,#F5E0D4,#fff;
:export{
bgcolor:$bgcolor;
activecolor:$activecolor;
defaultcolor:$defaultcolor;
}
封装 store 下面的 ZhuTi.js
- src/stores/ZhuTi.js
js
import { defineStore } from "pinia";
import { setItem, getItem } from "@/utils/storage";
import {
BGCOLORINDEX,
ACTIVETEXTCOLORINDEX,
DEFAULTTEXTCOLORINDEX,
BGCOLOR,
TEXTCOLOR,
ACTIVETEXTCOLOR,
} from "@/constant/index";
import zhutiscss from "@/scss/zhuti/zhuti.module.scss";
export const useZhuTiStore = defineStore("zhuti", () => {
const bgcolorarr = zhutiscss.bgcolor.split(",");
const activeTextColorarr = zhutiscss.activecolor.split(",");
const defaultTextColorarr = zhutiscss.defaultcolor.split(",");
// 修改 背景颜色
const bgcolorIndex = ref(getItem(BGCOLORINDEX) || 0);
const bgcolor = ref(getItem(BGCOLOR) || bgcolorarr[bgcolorIndex.value]);
const changebgColorIndex = (val) => {
bgcolorIndex.value = val;
setItem(BGCOLORINDEX, val);
};
const changebgColor = (val) => {
bgcolor.value = val;
setItem(BGCOLOR, val);
};
// 修改背景颜色结束
// 修改默认文本颜色
const default_text_color_index = ref(getItem(DEFAULTTEXTCOLORINDEX) || 16);
const defaultTextColor = ref(
getItem(TEXTCOLOR) || defaultTextColorarr[default_text_color_index.value]
);
const changedefaultTextColorIndex = (val) => {
default_text_color_index.value = val;
setItem(DEFAULTTEXTCOLORINDEX, val);
};
const changetextColor = (val) => {
defaultTextColor.value = val;
setItem(TEXTCOLOR, val);
};
// 修改默认文本颜色结束
// 修改鼠标放上去颜色
const active_text_color_index = ref(getItem(ACTIVETEXTCOLORINDEX) || 1);
const activeTextColor = ref(
getItem(ACTIVETEXTCOLOR) ||
activeTextColorarr[active_text_color_index.value]
);
const changeactiveTextColorIndex = (val) => {
active_text_color_index.value = val;
setItem(ACTIVETEXTCOLORINDEX, val);
};
const changeactivetextcolor = (val) => {
activeTextColor.value = val;
setItem(ACTIVETEXTCOLOR, val);
};
// 修改鼠标放上去颜色
return {
bgcolorarr,
bgcolor,
changebgColor,
changebgColorIndex,
defaultTextColor,
defaultTextColorarr,
changetextColor,
changedefaultTextColorIndex,
activeTextColor,
activeTextColorarr,
changeactivetextcolor,
changeactiveTextColorIndex,
active_text_color_index,
default_text_color_index,
bgcolorIndex,
};
});
封装要改变的变量名称
- constant/formula.json
js
{
"shade-1": "color(primary shade(10%))",
"light-1": "color(primary tint(10%))",
"light-2": "color(primary tint(20%))",
"light-3": "color(primary tint(30%))",
"light-4": "color(primary tint(40%))",
"light-5": "color(primary tint(50%))",
"light-6": "color(primary tint(60%))",
"light-7": "color(primary tint(70%))",
"light-8": "color(primary tint(80%))",
"light-9": "color(primary tint(90%))",
"light-10": "color(defaulttextcolor tint(90%))",
"light-11": "color(activetextcolor tint(90%))",
"subMenuHover": "color(primary tint(70%))",
"subMenuBg": "color(primary tint(80%))",
"menuHover": "color(primary tint(90%))",
"menuBg": "color(primary)"
}
(重点) 封装 theme.js
- src/utils/theme.js
注意
去 formula.json 添加新的变量名称和对应的变量替换的值
把要替换的颜色,用 formula.json 里面的变量名称替换(colorMap)
获取到最后的颜色,用正则表达式把 primary,defaulttextcolor,activetextcolor 替换成对应的颜色值,生成一个新的(color)
获取到初始样式表,循环 color,把对应的变量名称替换成新的颜色值
重新写入到样式表
js
import color from "css-color-function";
import rgbHex from "rgb-hex";
import formula from "@/constant/formula.json";
import axios from "axios";
/**
*
* 1. 获取当前 element-plus 的默认样式表
*/
const getOriginalStyle = async (type, data) => {
if (!data) {
const version = require("element-plus/package.json").version;
const url = `https://unpkg.com/element-plus@${version}/dist/index.css`;
const { data } = await axios(url);
// 把获取到的数据筛选为原样式模板
return getStyleTemplate(data, type);
} else {
// 如果传了 则直接用传过来的值
return getStyleTemplate(data, type);
}
};
/**
* 2. 把要修改的颜色设置成变量
* 返回 style 的 template 这里添加标记,方便后续替换
* 找到对应的色值,替换为标记
*/
const getStyleTemplate = (data, type) => {
// element-plus 默认色值
let colorMap = {};
if (type == 1) {
colorMap = {
"#3a8ee6": "shade-1",
"#409eff": "primary",
"#53a8ff": "light-1",
"#66b1ff": "light-2",
"#79bbff": "light-3",
"#8cc5ff": "light-4",
"#a0cfff": "light-5",
"#b3d8ff": "light-6",
"#c6e2ff": "light-7",
"#d9ecff": "light-8",
"#ecf5ff": "light-9",
};
} else if (type == 2) {
colorMap = {
" #fff": "light-10",
};
} else if (type == 3) {
colorMap = {
"#FFCE46": "light-11",
};
}
// 根据默认色值为要替换的色值打上标记
Object.keys(colorMap).forEach((key) => {
const value = colorMap[key];
data = data.replace(new RegExp(key, "ig"), value);
});
return data;
};
/**
* 3.根据主色生成色值表,简单来说就是把primary替换为色值,然后生成新的色值表
*
*/
export const generateColors = (primary, type) => {
if (!primary) return;
/* 1. 获取到要改变的颜色 例如 rgba(254, 132, 99, 1)
/* 2. 设置成一个变量color 例如 primary: rgba(254, 132, 99, 1);
/* 3. 遍历 formula.json 里面的色值,把 primary 替换为 rgba(254, 132, 99, 1)
/* 4. 把 formula.json 里面的值 添加 新建的color属性里面
/*
/* 如果type1 primary最后返回的结果例如就是 {
"primary": "rgba(254, 132, 99, 1)",
"shade-1": "#e57759",
"light-1": "#fe9073",
"light-2": "#fe9d82",
"light-3": "#fea992",
"light-4": "#feb5a1",
"light-5": "#ffc2b1",
"light-6": "#ffcec1",
"light-7": "#ffdad0",
"light-8": "#ffe6e0",
"light-9": "#fff3ef",
"light-10": "#fff3ef",
"subMenuHover": "#ffdad0",
"subMenuBg": "#ffe6e0",
"menuHover": "#fff3ef",
"menuBg": "#fe8463"
}
*/
const colors = {};
if (type == 1) {
colors["primary"] = primary;
} else if (type == 2) {
colors["defaulttextcolor"] = primary;
} else if (type == 3) {
colors["activetextcolor"] = primary;
}
console.log(colors);
if (type == 1) {
Object.keys(formula).forEach((key) => {
if (formula[key].indexOf("primary") != -1) {
let value = formula[key].replace(/primary/g, primary);
colors[key] = "#" + rgbHex(color.convert(value));
}
});
} else if (type == 2) {
Object.keys(formula).forEach((key) => {
if (formula[key].indexOf("defaulttextcolor") != -1) {
let value = formula[key].replace(/defaulttextcolor/g, primary);
colors[key] = "#" + rgbHex(color.convert(value));
}
});
} else if (type == 3) {
Object.keys(formula).forEach((key) => {
if (formula[key].indexOf("activetextcolor") != -1) {
let value = formula[key].replace(/activetextcolor/g, primary);
colors[key] = "#" + rgbHex(color.convert(value));
}
});
console.log(colors);
}
return colors;
};
/**
* 4.根据主色值,生成最新的样式表
*/
export const generateNewStyle = async (primaryColor, type, data) => {
const colors = generateColors(primaryColor, type);
let cssText = await getOriginalStyle(type, data);
// 遍历生成的样式表,在 CSS 的原样式中进行全局替换
Object.keys(colors).forEach((key) => {
cssText = cssText.replace(
new RegExp("(:|\\s+)" + key, "g"),
"$1" + colors[key]
);
});
return cssText;
};
/**
* 5.写入新样式到 style
* @param {*} elNewStyle element-plus 的新样式
* @param {*} isNewStyleTag 是否生成新的 style 标签
*/
export const writeNewStyle = (elNewStyle) => {
const style = document.createElement("style");
style.innerText = elNewStyle;
document.head.appendChild(style);
};
持久化
- App.vue
vue
<template>
<div>
<el-config-provider :locale="localLanguage">
<RouterView />
</el-config-provider>
</div>
</template>
<script setup>
import zhCn from "element-plus/es/locale/lang/zh-cn";
import en from "element-plus/es/locale/lang/en";
import { useI18n } from "vue-i18n";
import { generateNewStyle, writeNewStyle } from "@/utils/theme";
import { useZhuTiStore } from "@/stores/ZhuTi.js";
const ZhuTiModule = useZhuTiStore();
// 设置主题色方法
const changeColor = async () => {
// 1.1 获取主题色
const newStyleText = await generateNewStyle(ZhuTiModule.bgcolor, 1);
const newStyleText2 = await generateNewStyle(
ZhuTiModule.defaultTextColor,
2,
newStyleText
);
const newStyleText3 = await generateNewStyle(
ZhuTiModule.activeTextColor,
3,
newStyleText2
);
// 1.2 写入最新主题色
writeNewStyle(newStyleText3);
};
changeColor();
// 切换语言的方法
const i18n = useI18n();
const localLanguage = computed(() => {
const result = i18n.locale.value;
if (result == "cn") {
return zhCn;
} else {
return en;
}
});
// 切换结束
const huanjing = import.meta.env.VITE_APP_ENV;
console.log(huanjing);
</script>
<style lang="scss" scoped></style>
最后再修改 SideBarMenu.vue
vue
<template>
<el-menu
:uniqueOpened="true"
router
:default-active="activeMenu"
:background-color="bgcolor"
:text-color="defaultextcolor"
:active-text-color="activetextcolor"
:collapse="useCollapse().sidebarOpened"
>
<SideBarItem
v-for="item in routes"
:key="item.path"
:route="item"
></SideBarItem>
</el-menu>
</template>
<script setup>
import { filterRouters, generateMenus } from "@/utils/route";
import { useCollapse } from "@/stores/sidebaropen";
import { useZhuTiStore } from "@/stores/ZhuTi";
/* 获取 变量 */
const ZhuTistore = useZhuTiStore();
const bgcolor = ref(ZhuTistore.bgcolor);
const defaultextcolor = ref(ZhuTistore.defaultTextColor);
const activetextcolor = ref(ZhuTistore.activeTextColor);
watch(
() => {
return ZhuTistore.bgcolor;
},
(newval, oldval) => {
bgcolor.value = newval;
}
);
watch(
() => {
return ZhuTistore.defaultTextColor;
},
(newval, old) => {
defaultextcolor.value = newval;
}
);
watch(
() => {
return ZhuTistore.activeTextColor;
},
(newval, oldval) => {
activetextcolor.value = newval;
}
);
/* 获取路由 */
const router = useRouter();
const routes = computed(() => {
const filterRoutes = filterRouters(router.getRoutes());
return generateMenus(filterRoutes);
});
/* 计算高亮 */
const route = useRoute();
const activeMenu = computed(() => {
const { meta, path } = route;
console.log(route);
return path;
});
</script>
<style lang="scss">
.el-menu {
border-right: none;
}
</style>
NavBar.vue 使用
vue
<template>
<div class="navbarContent">
<div class="sidebaropen">
<SideBarOpen></SideBarOpen>
</div>
<div class="breadcrumb">
<BreadCrumb></BreadCrumb>
</div>
<div class="avatar">
<Avater></Avater>
</div>
<div class="language">
<I18n color="black"></I18n>
</div>
<div class="theme">
<ThemeColor></ThemeColor>
</div>
</div>
</template>
<script setup></script>
<style lang="scss" scoped>
.navbarContent {
width: 100%;
height: 60px;
border-bottom: 1px solid #ccc;
.sidebaropen {
float: left;
margin-top: 15px;
margin-left: 20px;
cursor: pointer;
}
.breadcrumb {
float: left;
margin-left: 10px;
margin-top: 1px;
}
.avatar {
width: 80px;
height: 50px;
float: right;
margin-right: 20px;
margin-top: 5px;
}
.language {
float: right;
margin-top: 15px;
margin-right: 20px;
cursor: pointer;
}
.theme {
float: right;
margin-top: 15px;
margin-right: 20px;
cursor: pointer;
}
}
</style>