feat: 系统设置添加主题动态切换
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {computed, ref, watch} from "vue";
|
import {computed, onMounted, ref, watch} from "vue";
|
||||||
import {useAppStoreHook} from "@/store/modules/app";
|
import {useAppStoreHook} from "@/store/modules/app";
|
||||||
import {ElConfigProvider} from 'element-plus'
|
import {ElConfigProvider} from 'element-plus'
|
||||||
|
|
||||||
@@ -29,5 +29,9 @@ watch(language, (value) => {
|
|||||||
// 初始化立即执行,
|
// 初始化立即执行,
|
||||||
immediate: true
|
immediate: true
|
||||||
})
|
})
|
||||||
|
onMounted(()=>{
|
||||||
|
const style = localStorage.getItem("style");
|
||||||
|
document.documentElement.style.cssText = style as string;
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,19 +14,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, onBeforeUnmount, onMounted, ref, watchEffect} from "vue";
|
import {computed, onBeforeUnmount, onMounted, ref, watch} from "vue";
|
||||||
|
|
||||||
import {addClass, removeClass} from '@/utils/index'
|
import {addClass, removeClass} from '@/utils/index'
|
||||||
import {useSettingStoreHook} from "@/store/modules/settings";
|
import {useSettingStoreHook} from "@/store/modules/settings";
|
||||||
|
|
||||||
// 图标依赖
|
// 图标依赖
|
||||||
import {Close, Setting} from '@element-plus/icons-vue'
|
import {Close, Setting} from '@element-plus/icons-vue'
|
||||||
|
import {ElColorPicker} from "element-plus";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
clickNotClose: {
|
|
||||||
default: false,
|
|
||||||
type: Boolean
|
|
||||||
},
|
|
||||||
buttonTop: {
|
buttonTop: {
|
||||||
default: 250,
|
default: 250,
|
||||||
type: Number
|
type: Number
|
||||||
@@ -37,11 +34,12 @@ const theme = computed(() => useSettingStoreHook().theme)
|
|||||||
|
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
|
|
||||||
watchEffect(() => {
|
watch(show, (value) => {
|
||||||
if (show.value && !props.clickNotClose) {
|
console.log('show', value)
|
||||||
|
if (value) {
|
||||||
addEventClick()
|
addEventClick()
|
||||||
}
|
}
|
||||||
if (show.value) {
|
if (value) {
|
||||||
addClass(document.body, 'showRightPanel')
|
addClass(document.body, 'showRightPanel')
|
||||||
} else {
|
} else {
|
||||||
removeClass(document.body, 'showRightPanel')
|
removeClass(document.body, 'showRightPanel')
|
||||||
@@ -53,24 +51,29 @@ function addEventClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeSidebar(evt: any) {
|
function closeSidebar(evt: any) {
|
||||||
const parent = evt.target.closest('.rightPanel')
|
|
||||||
|
// 主题选择点击不关闭
|
||||||
|
let parent = evt.target.closest('.theme-picker-dropdown')
|
||||||
|
if (parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = evt.target.closest('.rightPanel')
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
show.value = false
|
show.value = false
|
||||||
window.removeEventListener('click', closeSidebar)
|
window.removeEventListener('click', closeSidebar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rightPanel = ref(null)
|
const rightPanel = ref(ElColorPicker)
|
||||||
|
|
||||||
function insertToBody() {
|
function insertToBody() {
|
||||||
console.log('insertToBody', rightPanel)
|
|
||||||
const elx = rightPanel.value as any
|
const elx = rightPanel.value as any
|
||||||
const body = document.querySelector('body') as any
|
const body = document.querySelector('body') as any
|
||||||
body.insertBefore(elx, body.firstChild)
|
body.insertBefore(elx, body.firstChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('theme', useSettingStoreHook().theme)
|
|
||||||
insertToBody()
|
insertToBody()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
56
src/components/ThemePicker/index.vue
Normal file
56
src/components/ThemePicker/index.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<el-color-picker
|
||||||
|
v-model="theme"
|
||||||
|
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d' ]"
|
||||||
|
class="theme-picker"
|
||||||
|
popper-class="theme-picker-dropdown"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed, nextTick, watch} from "vue";
|
||||||
|
import {useSettingStoreHook} from "@/store/modules/settings";
|
||||||
|
import {useTagsViewStoreHook} from "@/store/modules/tagsView";
|
||||||
|
import {useRoute, useRouter} from "vue-router";
|
||||||
|
|
||||||
|
// 参考连接:https://juejin.cn/post/7024025899813044232#heading-1
|
||||||
|
import {mix} from "@/utils";
|
||||||
|
// 白色混合色
|
||||||
|
const mixWhite = "#ffffff";
|
||||||
|
// 黑色混合色
|
||||||
|
const mixBlack = "#000000";
|
||||||
|
|
||||||
|
const node = document.documentElement;
|
||||||
|
|
||||||
|
const theme = computed(() => useSettingStoreHook().theme)
|
||||||
|
|
||||||
|
watch(theme, (color: string) => {
|
||||||
|
node.style.setProperty("--el-color-primary", color);
|
||||||
|
localStorage.setItem("theme", color)
|
||||||
|
|
||||||
|
for (let i = 1; i < 10; i += 1) {
|
||||||
|
node.style.setProperty(`--el-color-primary-light-${i}`, mix(color, mixWhite, i * 0.1));
|
||||||
|
}
|
||||||
|
node.style.setProperty("--el-color-primary-dark", mix(color, mixBlack, 0.1));
|
||||||
|
|
||||||
|
localStorage.setItem("style", node.style.cssText);
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.theme-message,
|
||||||
|
.theme-picker-dropdown {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-picker .el-color-picker__trigger {
|
||||||
|
height: 26px !important;
|
||||||
|
width: 26px !important;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="drawer-container">
|
<div class="drawer-container">
|
||||||
<div>
|
|
||||||
<h3 class="drawer-title">系统布局配置</h3>
|
<h3 class="drawer-title">系统布局配置</h3>
|
||||||
|
<div class="drawer-item">
|
||||||
|
<span>主题颜色</span>
|
||||||
|
<div style="float: right;height: 26px;margin: -3px 8px 0 0;">
|
||||||
|
<theme-picker @change="themeChange"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
<span>开启 Tags-View</span>
|
<span>开启 Tags-View</span>
|
||||||
<el-switch v-model="tagsView" class="drawer-switch"/>
|
<el-switch v-model="state.tagsView" class="drawer-switch"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
@@ -18,39 +23,37 @@
|
|||||||
<el-switch v-model="sidebarLogo" class="drawer-switch"/>
|
<el-switch v-model="sidebarLogo" class="drawer-switch"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import {defineComponent, reactive, toRefs, watch} from "vue"
|
import {reactive, toRefs, watch} from "vue";
|
||||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
import {useSettingStoreHook} from "@/store/modules/settings";
|
||||||
export default defineComponent({
|
import ThemePicker from '@/components/ThemePicker/index.vue';
|
||||||
setup() {
|
|
||||||
const state = reactive({
|
|
||||||
fixedHeader:useSettingStoreHook().fixedHeader,
|
|
||||||
tagsView:useSettingStoreHook().tagsView,
|
|
||||||
sidebarLogo:useSettingStoreHook().sidebarLogo,
|
|
||||||
themeChange: (val) => {
|
|
||||||
useSettingStoreHook().changeSetting( { key: 'theme', val })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
watch(()=>state.fixedHeader,(value)=>{
|
|
||||||
useSettingStoreHook().changeSetting( { key: 'fixedHeader', value })
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => state.tagsView, (value) => {
|
const state = reactive({
|
||||||
useSettingStoreHook().changeSetting( { key: 'showTagsView', value })
|
fixedHeader: useSettingStoreHook().fixedHeader,
|
||||||
})
|
tagsView: useSettingStoreHook().tagsView,
|
||||||
|
sidebarLogo: useSettingStoreHook().sidebarLogo
|
||||||
watch(() => state.sidebarLogo, (value) => {
|
|
||||||
useSettingStoreHook().changeSetting( { key: 'sidebarLogo', value })
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {fixedHeader, tagsView, sidebarLogo} = toRefs(state)
|
||||||
|
|
||||||
|
function themeChange(val: any) {
|
||||||
|
useSettingStoreHook().changeSetting({key: 'theme', value: val})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => state.fixedHeader, (value) => {
|
||||||
|
useSettingStoreHook().changeSetting({key: 'fixedHeader', value: value})
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => state.tagsView, (value) => {
|
||||||
|
useSettingStoreHook().changeSetting({key: 'showTagsView', value: value})
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => state.sidebarLogo, (value) => {
|
||||||
|
useSettingStoreHook().changeSetting({key: 'sidebarLogo', value: value})
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="classObj" class="app-wrapper">
|
<div :class="classObj" class="app-wrapper">
|
||||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
<div v-if="device==='mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||||
<sidebar class="sidebar-container"/>
|
<sidebar class="sidebar-container"/>
|
||||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||||
<div :class="{'fixed-header':fixedHeader}">
|
<div :class="{'fixed-header':fixedHeader}">
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import {defineStore} from "pinia";
|
|||||||
import {store} from "@/store";
|
import {store} from "@/store";
|
||||||
import {SettingState} from "@/store/interface";
|
import {SettingState} from "@/store/interface";
|
||||||
import defaultSettings from '../../settings'
|
import defaultSettings from '../../settings'
|
||||||
|
|
||||||
const {showSettings, tagsView, fixedHeader, sidebarLogo} = defaultSettings
|
const {showSettings, tagsView, fixedHeader, sidebarLogo} = defaultSettings
|
||||||
import variables from '@/styles/element-variables.module.scss'
|
import variables from '@/styles/element-variables.module.scss'
|
||||||
|
|
||||||
export const useSettingStore = defineStore({
|
export const useSettingStore = defineStore({
|
||||||
id: "setting",
|
id: "setting",
|
||||||
state: (): SettingState => ({
|
state: (): SettingState => ({
|
||||||
theme: variables.theme,
|
theme: localStorage.get("theme") || variables.theme,
|
||||||
showSettings: showSettings,
|
showSettings: showSettings,
|
||||||
tagsView: tagsView,
|
tagsView: tagsView,
|
||||||
fixedHeader: fixedHeader,
|
fixedHeader: fixedHeader,
|
||||||
|
|||||||
@@ -1,5 +1,35 @@
|
|||||||
:root {
|
:root {
|
||||||
--el-color-primary: #409EFF;
|
// 这里可以设置你自定义的颜色变量
|
||||||
|
// 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改
|
||||||
|
--el-color-primary-dark: #0d84ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 核心组件的变量,下面这些样式是必须要写的 */
|
||||||
|
|
||||||
|
.el-link.el-link--primary:hover {
|
||||||
|
color: var(--el-color-primary-light-2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tag {
|
||||||
|
--el-tag-bg-color: var(--el-color-primary-light-9);
|
||||||
|
--el-tag-border-color: var(--el-color-primary-light-8);
|
||||||
|
--el-tag-text-color: var(--el-color-primary);
|
||||||
|
--el-tag-hover-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--default:active {
|
||||||
|
color: var(--el-color-primary-dark) !important;
|
||||||
|
border-color: var(--el-color-primary-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--primary {
|
||||||
|
--el-button-text-color: var(--el-color-white) !important;
|
||||||
|
--el-button-bg-color: var(--el-color-primary) !important;
|
||||||
|
--el-button-border-color: var(--el-color-primary) !important;
|
||||||
|
--el-button-hover-bg-color: var(--el-color-primary-light-2) !important;
|
||||||
|
--el-button-hover-border-color: var(--el-color-primary-light-2) !important;
|
||||||
|
--el-button-active-bg-color: var(--el-color-primary-dark) !important;
|
||||||
|
--el-button-active-border-color: var(--el-color-primary-dark) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 覆盖 element-plus 的样式
|
// 覆盖 element-plus 的样式
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* I think element-ui's default theme color is too light for long-term use.
|
* I think element default theme color is too light for long-term use.
|
||||||
* So I modified the default color and you can modify it to your liking.
|
* So I modified the default color and you can modify it to your liking.
|
||||||
* https://vitejs.cn/guide/features.html#postcss
|
* https://vitejs.cn/guide/features.html#postcss
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -28,3 +28,20 @@ export function removeClass(ele: HTMLElement, cls: string) {
|
|||||||
ele.className = ele.className.replace(reg, ' ')
|
ele.className = ele.className.replace(reg, ' ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mix(color1: string, color2: string, weight: number) {
|
||||||
|
weight = Math.max(Math.min(Number(weight), 1), 0);
|
||||||
|
let r1 = parseInt(color1.substring(1, 3), 16);
|
||||||
|
let g1 = parseInt(color1.substring(3, 5), 16);
|
||||||
|
let b1 = parseInt(color1.substring(5, 7), 16);
|
||||||
|
let r2 = parseInt(color2.substring(1, 3), 16);
|
||||||
|
let g2 = parseInt(color2.substring(3, 5), 16);
|
||||||
|
let b2 = parseInt(color2.substring(5, 7), 16);
|
||||||
|
let r = Math.round(r1 * (1 - weight) + r2 * weight);
|
||||||
|
let g = Math.round(g1 * (1 - weight) + g2 * weight);
|
||||||
|
let b = Math.round(b1 * (1 - weight) + b2 * weight);
|
||||||
|
const rStr = ("0" + (r || 0).toString(16)).slice(-2);
|
||||||
|
const gStr = ("0" + (g || 0).toString(16)).slice(-2);
|
||||||
|
const bStr = ("0" + (b || 0).toString(16)).slice(-2);
|
||||||
|
return "#" + rStr + gStr + bStr;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user