更换主题
安装插件
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: [
vue(),
requireTransform({
fileRegex: /.js$|.vue$|.json$/,
}),
],
});封装换肤组件
components/ColorPicker/index.vue
components/ThemeColor/index.vue
components/ColorPicker/index.vue
vue
<script setup>
import { useZhuTiStore } from "@/stores/ZhuTi.js";
import { generateNewStyle, writeNewStyle } from "@/utils/theme";
const Props = defineProps({
// 弹出框显示
dialogVisible: {
type: Boolean,
},
type: {
type: Number,
required: true,
},
titlemsg: {
type: String,
},
});
const StoreModules = useZhuTiStore();
watch(
() => Props.type,
(newval, oldval) => {
if (newval !== oldval) {
colorinit();
}
},
);
// 结果
onMounted(() => {
colorinit();
});
const color = ref("");
const predefineColors = ref([]);
function 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;
}
async function changecolor() {
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>
<template>
<el-dialog
:model-value="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>
<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 {
ACTIVETEXTCOLOR,
ACTIVETEXTCOLORINDEX,
BGCOLOR,
BGCOLORINDEX,
DEFAULTTEXTCOLORINDEX,
TEXTCOLOR,
} from '@/constant/index'
import zhutiscss from '@/scss/zhuti.module.scss'
import { getItem, setItem } from '@/utils/storage'
export const useZhuTiStore = defineStore('zhuti', () => {
const bgcolorarr: any = zhutiscss.bgcolor?.split(',')
const activeTextColorarr: any = zhutiscss.activecolor?.split(',')
const defaultTextColorarr: any = zhutiscss.defaultcolor?.split(',')
// 修改 背景颜色
const bgcolorIndex = ref(getItem(BGCOLORINDEX) || 0)
const bgcolor = ref(getItem(BGCOLOR) || bgcolorarr[bgcolorIndex.value])
const changebgColorIndex = (val: any) => {
bgcolorIndex.value = val
setItem(BGCOLORINDEX, val)
}
const changebgColor = (val: any) => {
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: any) => {
default_text_color_index.value = val
setItem(DEFAULTTEXTCOLORINDEX, val)
}
const changetextColor = (val: any) => {
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: any) => {
active_text_color_index.value = val
setItem(ACTIVETEXTCOLORINDEX, val)
}
const changeactivetextcolor = (val: any) => {
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 axios from 'axios'
import color from 'css-color-function'
import { version } from 'element-plus/package.json'
import rgbHex from 'rgb-hex'
import formula from '@/constant/formula.json'
/**
* 2. 把要修改的颜色设置成变量
* 返回 style 的 template 这里添加标记,方便后续替换
* 找到对应的色值,替换为标记
*/
function getStyleTemplate(data: any, type: any) {
// element-plus 默认色值
let colorMap: Record<string, any> = {}
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: any) => {
const value: any = colorMap[key]
data = data.replace(new RegExp(key, 'gi'), value)
})
return data
}
/**
* 3.根据主色生成色值表,简单来说就是把primary替换为色值,然后生成新的色值表
*
*/
export function generateColors(primary: any, type: any) {
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: Record<string, any> = {}
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: any) => {
if (formula[key as keyof typeof formula]?.indexOf('primary') !== -1) {
const value: any = formula[key as keyof typeof formula].replace(/primary/g, primary)
colors[key] = `#${rgbHex(color.convert(value))}`
}
})
} else if (type === 2) {
Object.keys(formula).forEach((key: any) => {
if (formula[key as keyof typeof formula]?.indexOf('defaulttextcolor') !== -1) {
const value: any = formula[key as keyof typeof formula].replace(/defaulttextcolor/g, primary)
colors[key] = `#${rgbHex(color.convert(value))}`
}
})
} else if (type === 3) {
Object.keys(formula).forEach((key: any) => {
if (formula[key as keyof typeof formula]?.indexOf('activetextcolor') !== -1) {
const value: any = formula[key as keyof typeof formula].replace(/activetextcolor/g, primary)
colors[key] = `#${rgbHex(color.convert(value))}`
}
})
console.log(colors)
}
return colors
}
/**
* 4.根据主色值,生成最新的样式表
*/
export async function generateNewStyle(primaryColor: any, type: any, data: any) {
const colors: any = generateColors(primaryColor, type)
let cssText = await getOriginalStyle(type, data)
// 遍历生成的样式表,在 CSS 的原样式中进行全局替换
Object.keys(colors).forEach((key: any) => {
cssText = cssText.replace(new RegExp(`(:|\\s+)${key}`, 'g'), `$1${colors[key]}`)
})
return cssText
}
/**
* 5.写入新样式到 style
* @param {*} elNewStyle element-plus 的新样式
* @param {*} isNewStyleTag 是否生成新的 style 标签
*/
export function writeNewStyle(elNewStyle: any) {
const style = document.createElement('style')
style.textContent = elNewStyle
document.head.appendChild(style)
}
/**
*
* 1. 获取当前 element-plus 的默认样式表
*/
async function getOriginalStyle(type: any, data: any) {
if (!data) {
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)
}
}持久化
- App.vue
vue
<template>
<div>
<el-config-provider :locale="localLanguage">
<RouterView />
</el-config-provider>
</div>
</template>
<script setup>
import { useZhuTiStore } from "@/stores/ZhuTi.js";
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";
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
<script setup>
import { useCollapse } from "@/stores/sidebaropen";
import { useZhuTiStore } from "@/stores/ZhuTi";
import { filterRouters, generateMenus } from "@/utils/route";
/* 获取 变量 */
const ZhuTistore = useZhuTiStore();
const bgcolor = ref(ZhuTistore.bgcolor);
const defaultextcolor = ref(ZhuTistore.defaultTextColor);
const activetextcolor = ref(ZhuTistore.activeTextColor);
watch(
() => {
return ZhuTistore.bgcolor;
},
(newval, _old) => {
bgcolor.value = newval;
},
);
watch(
() => {
return ZhuTistore.defaultTextColor;
},
(newval, _old) => {
defaultextcolor.value = newval;
},
);
watch(
() => {
return ZhuTistore.activeTextColor;
},
(newval, _old) => {
activetextcolor.value = newval;
},
);
/* 获取路由 */
const router = useRouter();
const routes = computed(() => {
const filterRoutes = filterRouters(router.getRoutes());
return generateMenus(filterRoutes);
});
/* 计算高亮 */
const route = useRoute();
const activeMenu = computed(() => {
const { path, meta } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
});
</script>
<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>
<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>