298 lines
8.9 KiB
Vue
298 lines
8.9 KiB
Vue
<script setup lang="ts">
|
||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||
import { ref, unref, computed, watch } from 'vue'
|
||
import { useI18n } from '@/hooks/web/useI18n'
|
||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||
import { useCssVar } from '@vueuse/core'
|
||
import { useAppStore } from '@/store/modules/app'
|
||
import { trim, setCssVar } from '@/utils'
|
||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||
import { useStorage } from '@/hooks/web/useStorage'
|
||
import { useClipboard } from '@vueuse/core'
|
||
import { useDesign } from '@/hooks/web/useDesign'
|
||
|
||
const { clear: storageClear } = useStorage('localStorage')
|
||
|
||
const { getPrefixCls } = useDesign()
|
||
|
||
const prefixCls = getPrefixCls('setting')
|
||
|
||
const appStore = useAppStore()
|
||
|
||
const { t } = useI18n()
|
||
|
||
const layout = computed(() => appStore.getLayout)
|
||
|
||
const drawer = ref(false)
|
||
|
||
// 主题色相关
|
||
const systemTheme = ref(appStore.getTheme.elColorPrimary)
|
||
|
||
const setSystemTheme = (color: string) => {
|
||
setCssVar('--el-color-primary', color)
|
||
appStore.setTheme({ elColorPrimary: color })
|
||
const leftMenuBgColor = useCssVar('--left-menu-bg-color', document.documentElement)
|
||
setMenuTheme(trim(unref(leftMenuBgColor)))
|
||
}
|
||
|
||
// 头部主题相关
|
||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||
|
||
const setHeaderTheme = (color: string) => {
|
||
const isDarkColor = colorIsDark(color)
|
||
const textColor = isDarkColor ? '#fff' : 'inherit'
|
||
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
||
const topToolBorderColor = isDarkColor ? color : '#eee'
|
||
setCssVar('--top-header-bg-color', color)
|
||
setCssVar('--top-header-text-color', textColor)
|
||
setCssVar('--top-header-hover-color', textHoverColor)
|
||
appStore.setTheme({
|
||
topHeaderBgColor: color,
|
||
topHeaderTextColor: textColor,
|
||
topHeaderHoverColor: textHoverColor,
|
||
topToolBorderColor
|
||
})
|
||
if (unref(layout) === 'top') {
|
||
setMenuTheme(color)
|
||
}
|
||
}
|
||
|
||
// 菜单主题相关
|
||
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
||
|
||
const setMenuTheme = (color: string) => {
|
||
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
||
const isDarkColor = colorIsDark(color)
|
||
const theme: Recordable = {
|
||
// 左侧菜单边框颜色
|
||
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
|
||
// 左侧菜单背景颜色
|
||
leftMenuBgColor: color,
|
||
// 左侧菜单浅色背景颜色
|
||
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
|
||
// 左侧菜单选中背景颜色
|
||
leftMenuBgActiveColor: isDarkColor
|
||
? 'var(--el-color-primary)'
|
||
: hexToRGB(unref(primaryColor), 0.1),
|
||
// 左侧菜单收起选中背景颜色
|
||
leftMenuCollapseBgActiveColor: isDarkColor
|
||
? 'var(--el-color-primary)'
|
||
: hexToRGB(unref(primaryColor), 0.1),
|
||
// 左侧菜单字体颜色
|
||
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
|
||
// 左侧菜单选中字体颜色
|
||
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
|
||
// logo字体颜色
|
||
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
||
// logo边框颜色
|
||
logoBorderColor: isDarkColor ? color : '#eee'
|
||
}
|
||
appStore.setTheme(theme)
|
||
appStore.setCssVarTheme()
|
||
}
|
||
|
||
// 监听layout变化,重置一些主题色
|
||
watch(
|
||
() => layout.value,
|
||
(n) => {
|
||
if (n === 'top' && !appStore.getIsDark) {
|
||
headerTheme.value = '#fff'
|
||
setHeaderTheme('#fff')
|
||
} else {
|
||
setMenuTheme(unref(menuTheme))
|
||
}
|
||
}
|
||
)
|
||
|
||
// 拷贝
|
||
const copyConfig = async () => {
|
||
const { copy, copied, isSupported } = useClipboard({
|
||
source: `
|
||
// 面包屑
|
||
breadcrumb: ${appStore.getBreadcrumb},
|
||
// 面包屑图标
|
||
breadcrumbIcon: ${appStore.getBreadcrumbIcon},
|
||
// 折叠图标
|
||
hamburger: ${appStore.getHamburger},
|
||
// 全屏图标
|
||
screenfull: ${appStore.getScreenfull},
|
||
// 尺寸图标
|
||
size: ${appStore.getSize},
|
||
// 多语言图标
|
||
locale: ${appStore.getLocale},
|
||
// 标签页
|
||
tagsView: ${appStore.getTagsView},
|
||
// 标签页图标
|
||
getTagsViewIcon: ${appStore.getTagsViewIcon},
|
||
// logo
|
||
logo: ${appStore.getLogo},
|
||
// 菜单手风琴
|
||
uniqueOpened: ${appStore.getUniqueOpened},
|
||
// 固定header
|
||
fixedHeader: ${appStore.getFixedHeader},
|
||
// 页脚
|
||
footer: ${appStore.getFooter},
|
||
// 灰色模式
|
||
greyMode: ${appStore.getGreyMode},
|
||
// layout布局
|
||
layout: '${appStore.getLayout}',
|
||
// 暗黑模式
|
||
isDark: ${appStore.getIsDark},
|
||
// 组件尺寸
|
||
currentSize: '${appStore.getCurrentSize}',
|
||
// 主题相关
|
||
theme: {
|
||
// 主题色
|
||
elColorPrimary: '${appStore.getTheme.elColorPrimary}',
|
||
// 左侧菜单边框颜色
|
||
leftMenuBorderColor: '${appStore.getTheme.leftMenuBorderColor}',
|
||
// 左侧菜单背景颜色
|
||
leftMenuBgColor: '${appStore.getTheme.leftMenuBgColor}',
|
||
// 左侧菜单浅色背景颜色
|
||
leftMenuBgLightColor: '${appStore.getTheme.leftMenuBgLightColor}',
|
||
// 左侧菜单选中背景颜色
|
||
leftMenuBgActiveColor: '${appStore.getTheme.leftMenuBgActiveColor}',
|
||
// 左侧菜单收起选中背景颜色
|
||
leftMenuCollapseBgActiveColor: '${appStore.getTheme.leftMenuCollapseBgActiveColor}',
|
||
// 左侧菜单字体颜色
|
||
leftMenuTextColor: '${appStore.getTheme.leftMenuTextColor}',
|
||
// 左侧菜单选中字体颜色
|
||
leftMenuTextActiveColor: '${appStore.getTheme.leftMenuTextActiveColor}',
|
||
// logo字体颜色
|
||
logoTitleTextColor: '${appStore.getTheme.logoTitleTextColor}',
|
||
// logo边框颜色
|
||
logoBorderColor: '${appStore.getTheme.logoBorderColor}',
|
||
// 头部背景颜色
|
||
topHeaderBgColor: '${appStore.getTheme.topHeaderBgColor}',
|
||
// 头部字体颜色
|
||
topHeaderTextColor: '${appStore.getTheme.topHeaderTextColor}',
|
||
// 头部悬停颜色
|
||
topHeaderHoverColor: '${appStore.getTheme.topHeaderHoverColor}',
|
||
// 头部边框颜色
|
||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||
}
|
||
`
|
||
})
|
||
if (!isSupported) {
|
||
ElMessage.error(t('setting.copyFailed'))
|
||
} else {
|
||
await copy()
|
||
if (unref(copied)) {
|
||
ElMessage.success(t('setting.copySuccess'))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清空缓存
|
||
const clear = () => {
|
||
storageClear()
|
||
window.location.reload()
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
:class="prefixCls"
|
||
class="fixed top-[45%] right-0 w-40px h-40px flex items-center justify-center bg-[var(--el-color-primary)] cursor-pointer z-10"
|
||
@click="drawer = true"
|
||
>
|
||
<Icon icon="ant-design:setting-outlined" color="#fff" />
|
||
</div>
|
||
|
||
<ElDrawer v-model="drawer" direction="rtl" size="350px" :z-index="4000">
|
||
<template #header>
|
||
<span class="text-16px font-700">{{ t('setting.projectSetting') }}</span>
|
||
</template>
|
||
|
||
<div class="text-center">
|
||
<!-- 主题 -->
|
||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||
<ThemeSwitch />
|
||
|
||
<!-- 布局 -->
|
||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||
<LayoutRadioPicker />
|
||
|
||
<!-- 系统主题 -->
|
||
<ElDivider>{{ t('setting.systemTheme') }}</ElDivider>
|
||
<ColorRadioPicker
|
||
v-model="systemTheme"
|
||
:schema="[
|
||
'#409eff',
|
||
'#009688',
|
||
'#536dfe',
|
||
'#ff5c93',
|
||
'#ee4f12',
|
||
'#0096c7',
|
||
'#9c27b0',
|
||
'#ff9800'
|
||
]"
|
||
@change="setSystemTheme"
|
||
/>
|
||
|
||
<!-- 头部主题 -->
|
||
<ElDivider>{{ t('setting.headerTheme') }}</ElDivider>
|
||
<ColorRadioPicker
|
||
v-model="headerTheme"
|
||
:schema="[
|
||
'#fff',
|
||
'#151515',
|
||
'#5172dc',
|
||
'#e74c3c',
|
||
'#24292e',
|
||
'#394664',
|
||
'#009688',
|
||
'#383f45'
|
||
]"
|
||
@change="setHeaderTheme"
|
||
/>
|
||
|
||
<!-- 菜单主题 -->
|
||
<template v-if="layout !== 'top'">
|
||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||
<ColorRadioPicker
|
||
v-model="menuTheme"
|
||
:schema="[
|
||
'#fff',
|
||
'#001529',
|
||
'#212121',
|
||
'#273352',
|
||
'#191b24',
|
||
'#383f45',
|
||
'#001628',
|
||
'#344058'
|
||
]"
|
||
@change="setMenuTheme"
|
||
/>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 界面显示 -->
|
||
<ElDivider>{{ t('setting.interfaceDisplay') }}</ElDivider>
|
||
<InterfaceDisplay />
|
||
|
||
<ElDivider />
|
||
<div>
|
||
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
||
t('setting.copy')
|
||
}}</BaseButton>
|
||
</div>
|
||
<div class="mt-5px">
|
||
<BaseButton type="danger" class="w-full" @click="clear">
|
||
{{ t('setting.clearAndReset') }}
|
||
</BaseButton>
|
||
</div>
|
||
</ElDrawer>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
@prefix-cls: ~'@{namespace}-setting';
|
||
|
||
.@{prefix-cls} {
|
||
border-radius: 6px 0 0 6px;
|
||
}
|
||
</style>
|