33
This commit is contained in:
parent
b14c0beb36
commit
9b948a9bd2
|
|
@ -13,6 +13,13 @@
|
|||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<!-- 主题切换按钮 -->
|
||||
<el-tooltip content="主题设置" placement="bottom">
|
||||
<div class="header-action" @click="themeStore.toggleThemeDrawer">
|
||||
<el-icon><Brush /></el-icon>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="32" :src="userInfo?.avatar || undefined">
|
||||
|
|
@ -36,7 +43,8 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Fold, Expand, ArrowDown } from '@element-plus/icons-vue'
|
||||
import { Fold, Expand, ArrowDown, Brush } from '@element-plus/icons-vue'
|
||||
import { useThemeStore } from '@/store/modules/theme'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
|
|
@ -49,6 +57,7 @@ defineEmits(['toggle-collapse'])
|
|||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const themeStore = useThemeStore()
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
|
|
@ -112,6 +121,24 @@ const handleCommand = async (command: string) => {
|
|||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.header-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--border-radius-base);
|
||||
cursor: pointer;
|
||||
color: var(--text-regular);
|
||||
margin-right: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.header-action:hover {
|
||||
background-color: var(--bg-hover);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,318 @@
|
|||
<template>
|
||||
<el-drawer
|
||||
v-model="themeStore.showThemeDrawer"
|
||||
title="主题设置"
|
||||
direction="rtl"
|
||||
size="300px"
|
||||
:show-close="true"
|
||||
>
|
||||
<div class="theme-drawer">
|
||||
<!-- 预设主题 -->
|
||||
<div class="theme-section">
|
||||
<h4 class="section-title">系统主题</h4>
|
||||
<div class="theme-grid">
|
||||
<div
|
||||
v-for="theme in presetThemes"
|
||||
:key="theme.name"
|
||||
class="theme-item"
|
||||
:class="{ active: themeStore.currentTheme === theme.name }"
|
||||
@click="themeStore.setTheme(theme.name)"
|
||||
>
|
||||
<div
|
||||
class="theme-preview"
|
||||
:style="{ backgroundColor: theme.primaryColor }"
|
||||
>
|
||||
<el-icon v-if="themeStore.currentTheme === theme.name" class="check-icon">
|
||||
<Check />
|
||||
</el-icon>
|
||||
</div>
|
||||
<span class="theme-label">{{ theme.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义主题 -->
|
||||
<div class="theme-section">
|
||||
<h4 class="section-title">自定义主题</h4>
|
||||
<div class="custom-theme">
|
||||
<div class="color-item">
|
||||
<span class="color-label">主色调</span>
|
||||
<el-color-picker v-model="customColors.primaryColor" @change="onCustomColorChange" />
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<span class="color-label">侧边栏背景</span>
|
||||
<el-color-picker v-model="customColors.sidebarBg" @change="onCustomColorChange" />
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<span class="color-label">页面背景</span>
|
||||
<el-color-picker v-model="customColors.bgPage" @change="onCustomColorChange" />
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="apply-btn"
|
||||
@click="applyCustomTheme"
|
||||
>
|
||||
应用自定义主题
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速预览 -->
|
||||
<div class="theme-section">
|
||||
<h4 class="section-title">预览效果</h4>
|
||||
<div class="preview-box">
|
||||
<div class="preview-sidebar" :style="{ backgroundColor: previewColors.sidebarBg }">
|
||||
<div class="preview-logo" :style="{ backgroundColor: previewColors.primaryColor }"></div>
|
||||
<div class="preview-menu">
|
||||
<div class="preview-menu-item"></div>
|
||||
<div class="preview-menu-item active" :style="{ backgroundColor: previewColors.primaryBg }"></div>
|
||||
<div class="preview-menu-item"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-main" :style="{ backgroundColor: previewColors.bgPage }">
|
||||
<div class="preview-header"></div>
|
||||
<div class="preview-content">
|
||||
<div class="preview-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { Check } from '@element-plus/icons-vue'
|
||||
import { useThemeStore, presetThemes } from '@/store/modules/theme'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
|
||||
// 自定义颜色
|
||||
const customColors = reactive({
|
||||
primaryColor: '#4A90D9',
|
||||
sidebarBg: '#F0F7FF',
|
||||
bgPage: '#F5F9FC'
|
||||
})
|
||||
|
||||
// 预览颜色
|
||||
const previewColors = computed(() => {
|
||||
if (themeStore.currentTheme === 'custom' && themeStore.customTheme) {
|
||||
return {
|
||||
primaryColor: themeStore.customTheme.primaryColor || customColors.primaryColor,
|
||||
sidebarBg: themeStore.customTheme.sidebarBg || customColors.sidebarBg,
|
||||
bgPage: themeStore.customTheme.bgPage || customColors.bgPage,
|
||||
primaryBg: themeStore.customTheme.primaryBg || lightenColor(customColors.primaryColor, 0.9)
|
||||
}
|
||||
}
|
||||
const theme = presetThemes.find(t => t.name === themeStore.currentTheme)
|
||||
if (theme) {
|
||||
return {
|
||||
primaryColor: theme.primaryColor,
|
||||
sidebarBg: theme.sidebarBg,
|
||||
bgPage: theme.bgPage,
|
||||
primaryBg: theme.primaryBg
|
||||
}
|
||||
}
|
||||
return {
|
||||
primaryColor: customColors.primaryColor,
|
||||
sidebarBg: customColors.sidebarBg,
|
||||
bgPage: customColors.bgPage,
|
||||
primaryBg: lightenColor(customColors.primaryColor, 0.9)
|
||||
}
|
||||
})
|
||||
|
||||
// 颜色变浅
|
||||
const lightenColor = (hex: string, ratio: number): string => {
|
||||
const color = hex.replace('#', '')
|
||||
const r = parseInt(color.substring(0, 2), 16)
|
||||
const g = parseInt(color.substring(2, 4), 16)
|
||||
const b = parseInt(color.substring(4, 6), 16)
|
||||
const lr = Math.round(r + (255 - r) * ratio)
|
||||
const lg = Math.round(g + (255 - g) * ratio)
|
||||
const lb = Math.round(b + (255 - b) * ratio)
|
||||
return `rgb(${lr}, ${lg}, ${lb})`
|
||||
}
|
||||
|
||||
// 颜色变深
|
||||
const darkenColor = (hex: string, ratio: number): string => {
|
||||
const color = hex.replace('#', '')
|
||||
const r = parseInt(color.substring(0, 2), 16)
|
||||
const g = parseInt(color.substring(2, 4), 16)
|
||||
const b = parseInt(color.substring(4, 6), 16)
|
||||
const dr = Math.round(r * (1 - ratio))
|
||||
const dg = Math.round(g * (1 - ratio))
|
||||
const db = Math.round(b * (1 - ratio))
|
||||
return `rgb(${dr}, ${dg}, ${db})`
|
||||
}
|
||||
|
||||
const onCustomColorChange = () => {
|
||||
// 实时预览可以在这里添加
|
||||
}
|
||||
|
||||
const applyCustomTheme = () => {
|
||||
themeStore.saveCustomTheme({
|
||||
primaryColor: customColors.primaryColor,
|
||||
primaryLight: lightenColor(customColors.primaryColor, 0.3),
|
||||
primaryDark: darkenColor(customColors.primaryColor, 0.2),
|
||||
primaryBg: lightenColor(customColors.primaryColor, 0.9),
|
||||
sidebarBg: customColors.sidebarBg,
|
||||
sidebarLogoBg: customColors.primaryColor,
|
||||
sidebarTextActive: customColors.primaryColor,
|
||||
bgPage: customColors.bgPage,
|
||||
bgLight: lightenColor(customColors.bgPage, 0.5),
|
||||
bgHover: lightenColor(customColors.primaryColor, 0.85),
|
||||
loginBgStart: lightenColor(customColors.primaryColor, 0.2),
|
||||
loginBgEnd: customColors.primaryColor
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化自定义颜色
|
||||
watch(() => themeStore.customTheme, (val) => {
|
||||
if (val) {
|
||||
customColors.primaryColor = val.primaryColor || '#4A90D9'
|
||||
customColors.sidebarBg = val.sidebarBg || '#F0F7FF'
|
||||
customColors.bgPage = val.bgPage || '#F5F9FC'
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-drawer {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.theme-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border-lighter);
|
||||
}
|
||||
|
||||
.theme-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.theme-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius-base);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.theme-item:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.theme-item.active {
|
||||
background-color: var(--primary-bg);
|
||||
}
|
||||
|
||||
.theme-preview {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.theme-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-regular);
|
||||
}
|
||||
|
||||
.custom-theme {
|
||||
background-color: var(--bg-light);
|
||||
padding: 16px;
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
|
||||
.color-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.color-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-regular);
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.preview-box {
|
||||
display: flex;
|
||||
height: 120px;
|
||||
border-radius: var(--border-radius-base);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--box-shadow-light);
|
||||
}
|
||||
|
||||
.preview-sidebar {
|
||||
width: 60px;
|
||||
padding: 8px 6px;
|
||||
}
|
||||
|
||||
.preview-logo {
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.preview-menu-item {
|
||||
height: 12px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.preview-menu-item.active {
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.preview-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
height: 24px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid var(--border-lighter);
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -24,19 +24,30 @@
|
|||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
<!-- 主题设置抽屉 -->
|
||||
<ThemeDrawer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Sidebar from './components/Sidebar.vue'
|
||||
import Header from './components/Header.vue'
|
||||
import ThemeDrawer from './components/ThemeDrawer.vue'
|
||||
import { useThemeStore } from '@/store/modules/theme'
|
||||
|
||||
const isCollapse = ref(false)
|
||||
const themeStore = useThemeStore()
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value
|
||||
}
|
||||
|
||||
// 初始化主题
|
||||
onMounted(() => {
|
||||
themeStore.initTheme()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,14 @@ const router = createRouter({
|
|||
// 白名单路由
|
||||
const whiteList = ['/login', '/404']
|
||||
|
||||
// 标记动态路由是否已加载
|
||||
let dynamicRoutesLoaded = false
|
||||
|
||||
// 重置动态路由状态(供外部调用)
|
||||
export function resetRouter() {
|
||||
dynamicRoutesLoaded = false
|
||||
}
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to, _from, next) => {
|
||||
const token = getToken()
|
||||
|
|
@ -64,12 +72,15 @@ router.beforeEach(async (to, _from, next) => {
|
|||
const userStore = useUserStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
if (userStore.userInfo) {
|
||||
// 检查动态路由是否已加载
|
||||
if (dynamicRoutesLoaded) {
|
||||
next()
|
||||
} else {
|
||||
try {
|
||||
// 获取用户信息
|
||||
await userStore.getUserInfo()
|
||||
// 获取用户信息(如果还没有)
|
||||
if (!userStore.userInfo) {
|
||||
await userStore.getUserInfo()
|
||||
}
|
||||
// 生成动态路由
|
||||
const accessRoutes = await permissionStore.generateRoutes()
|
||||
// 添加动态路由
|
||||
|
|
@ -78,15 +89,20 @@ router.beforeEach(async (to, _from, next) => {
|
|||
})
|
||||
// 添加 404 兜底路由
|
||||
router.addRoute({ path: '/:pathMatch(.*)*', redirect: '/404' })
|
||||
// 标记动态路由已加载
|
||||
dynamicRoutesLoaded = true
|
||||
next({ ...to, replace: true })
|
||||
} catch (error) {
|
||||
// 获取用户信息失败,清除 token 并跳转登录页
|
||||
dynamicRoutesLoaded = false
|
||||
userStore.logout()
|
||||
next(`/login?redirect=${to.path}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 重置动态路由标记
|
||||
dynamicRoutesLoaded = false
|
||||
if (whiteList.includes(to.path)) {
|
||||
next()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ export default pinia
|
|||
|
||||
export * from './modules/user'
|
||||
export * from './modules/permission'
|
||||
export * from './modules/theme'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,262 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 预设主题配置
|
||||
export interface ThemeConfig {
|
||||
name: string
|
||||
label: string
|
||||
primaryColor: string
|
||||
primaryLight: string
|
||||
primaryDark: string
|
||||
primaryBg: string
|
||||
sidebarBg: string
|
||||
sidebarLogoBg: string
|
||||
sidebarTextActive: string
|
||||
bgPage: string
|
||||
bgLight: string
|
||||
bgHover: string
|
||||
loginBgStart: string
|
||||
loginBgEnd: string
|
||||
}
|
||||
|
||||
// 系统预设主题
|
||||
export const presetThemes: ThemeConfig[] = [
|
||||
{
|
||||
name: 'blue',
|
||||
label: '天空蓝',
|
||||
primaryColor: '#4A90D9',
|
||||
primaryLight: '#74B9FF',
|
||||
primaryDark: '#2B7DE9',
|
||||
primaryBg: '#E8F4FD',
|
||||
sidebarBg: '#F0F7FF',
|
||||
sidebarLogoBg: '#4A90D9',
|
||||
sidebarTextActive: '#4A90D9',
|
||||
bgPage: '#F5F9FC',
|
||||
bgLight: '#F0F7FF',
|
||||
bgHover: '#E8F4FD',
|
||||
loginBgStart: '#74B9FF',
|
||||
loginBgEnd: '#4A90D9'
|
||||
},
|
||||
{
|
||||
name: 'green',
|
||||
label: '翠绿',
|
||||
primaryColor: '#52C41A',
|
||||
primaryLight: '#73D13D',
|
||||
primaryDark: '#389E0D',
|
||||
primaryBg: '#F6FFED',
|
||||
sidebarBg: '#F6FFED',
|
||||
sidebarLogoBg: '#52C41A',
|
||||
sidebarTextActive: '#52C41A',
|
||||
bgPage: '#F9FFF6',
|
||||
bgLight: '#F6FFED',
|
||||
bgHover: '#D9F7BE',
|
||||
loginBgStart: '#73D13D',
|
||||
loginBgEnd: '#52C41A'
|
||||
},
|
||||
{
|
||||
name: 'purple',
|
||||
label: '典雅紫',
|
||||
primaryColor: '#722ED1',
|
||||
primaryLight: '#9254DE',
|
||||
primaryDark: '#531DAB',
|
||||
primaryBg: '#F9F0FF',
|
||||
sidebarBg: '#F9F0FF',
|
||||
sidebarLogoBg: '#722ED1',
|
||||
sidebarTextActive: '#722ED1',
|
||||
bgPage: '#FBF5FF',
|
||||
bgLight: '#F9F0FF',
|
||||
bgHover: '#EFDBFF',
|
||||
loginBgStart: '#9254DE',
|
||||
loginBgEnd: '#722ED1'
|
||||
},
|
||||
{
|
||||
name: 'orange',
|
||||
label: '活力橙',
|
||||
primaryColor: '#FA8C16',
|
||||
primaryLight: '#FFA940',
|
||||
primaryDark: '#D46B08',
|
||||
primaryBg: '#FFF7E6',
|
||||
sidebarBg: '#FFF7E6',
|
||||
sidebarLogoBg: '#FA8C16',
|
||||
sidebarTextActive: '#FA8C16',
|
||||
bgPage: '#FFFBF5',
|
||||
bgLight: '#FFF7E6',
|
||||
bgHover: '#FFE7BA',
|
||||
loginBgStart: '#FFA940',
|
||||
loginBgEnd: '#FA8C16'
|
||||
},
|
||||
{
|
||||
name: 'red',
|
||||
label: '中国红',
|
||||
primaryColor: '#F5222D',
|
||||
primaryLight: '#FF4D4F',
|
||||
primaryDark: '#CF1322',
|
||||
primaryBg: '#FFF1F0',
|
||||
sidebarBg: '#FFF1F0',
|
||||
sidebarLogoBg: '#F5222D',
|
||||
sidebarTextActive: '#F5222D',
|
||||
bgPage: '#FFFAFA',
|
||||
bgLight: '#FFF1F0',
|
||||
bgHover: '#FFCCC7',
|
||||
loginBgStart: '#FF4D4F',
|
||||
loginBgEnd: '#F5222D'
|
||||
},
|
||||
{
|
||||
name: 'dark',
|
||||
label: '暗夜黑',
|
||||
primaryColor: '#1890FF',
|
||||
primaryLight: '#40A9FF',
|
||||
primaryDark: '#096DD9',
|
||||
primaryBg: '#111B26',
|
||||
sidebarBg: '#001529',
|
||||
sidebarLogoBg: '#002140',
|
||||
sidebarTextActive: '#1890FF',
|
||||
bgPage: '#0D1117',
|
||||
bgLight: '#161B22',
|
||||
bgHover: '#21262D',
|
||||
loginBgStart: '#001529',
|
||||
loginBgEnd: '#000C17'
|
||||
}
|
||||
]
|
||||
|
||||
const THEME_STORAGE_KEY = 'honeybox-admin-theme'
|
||||
const CUSTOM_THEME_KEY = 'honeybox-admin-custom-theme'
|
||||
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
const currentTheme = ref<string>('blue')
|
||||
const customTheme = ref<Partial<ThemeConfig> | null>(null)
|
||||
const showThemeDrawer = ref(false)
|
||||
|
||||
// 初始化主题
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY)
|
||||
const savedCustom = localStorage.getItem(CUSTOM_THEME_KEY)
|
||||
|
||||
if (savedCustom) {
|
||||
customTheme.value = JSON.parse(savedCustom)
|
||||
}
|
||||
|
||||
if (savedTheme) {
|
||||
currentTheme.value = savedTheme
|
||||
applyTheme(savedTheme)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用主题
|
||||
const applyTheme = (themeName: string) => {
|
||||
let theme: ThemeConfig | Partial<ThemeConfig> | undefined
|
||||
|
||||
if (themeName === 'custom' && customTheme.value) {
|
||||
theme = customTheme.value
|
||||
} else {
|
||||
theme = presetThemes.find(t => t.name === themeName)
|
||||
}
|
||||
|
||||
if (!theme) return
|
||||
|
||||
const root = document.documentElement
|
||||
|
||||
// 应用CSS变量
|
||||
if (theme.primaryColor) {
|
||||
root.style.setProperty('--primary-color', theme.primaryColor)
|
||||
root.style.setProperty('--el-color-primary', theme.primaryColor)
|
||||
}
|
||||
if (theme.primaryLight) {
|
||||
root.style.setProperty('--primary-light', theme.primaryLight)
|
||||
}
|
||||
if (theme.primaryDark) {
|
||||
root.style.setProperty('--primary-dark', theme.primaryDark)
|
||||
}
|
||||
if (theme.primaryBg) {
|
||||
root.style.setProperty('--primary-bg', theme.primaryBg)
|
||||
}
|
||||
if (theme.sidebarBg) {
|
||||
root.style.setProperty('--sidebar-bg', theme.sidebarBg)
|
||||
}
|
||||
if (theme.sidebarLogoBg) {
|
||||
root.style.setProperty('--sidebar-logo-bg', theme.sidebarLogoBg)
|
||||
}
|
||||
if (theme.sidebarTextActive) {
|
||||
root.style.setProperty('--sidebar-text-active', theme.sidebarTextActive)
|
||||
root.style.setProperty('--sidebar-item-active', theme.primaryBg || theme.sidebarBg || '')
|
||||
root.style.setProperty('--sidebar-item-hover', theme.bgHover || '')
|
||||
}
|
||||
if (theme.bgPage) {
|
||||
root.style.setProperty('--bg-page', theme.bgPage)
|
||||
root.style.setProperty('--el-bg-color-page', theme.bgPage)
|
||||
}
|
||||
if (theme.bgLight) {
|
||||
root.style.setProperty('--bg-light', theme.bgLight)
|
||||
}
|
||||
if (theme.bgHover) {
|
||||
root.style.setProperty('--bg-hover', theme.bgHover)
|
||||
}
|
||||
if (theme.loginBgStart) {
|
||||
root.style.setProperty('--login-bg-start', theme.loginBgStart)
|
||||
}
|
||||
if (theme.loginBgEnd) {
|
||||
root.style.setProperty('--login-bg-end', theme.loginBgEnd)
|
||||
}
|
||||
|
||||
// 生成 Element Plus 主色阶
|
||||
if (theme.primaryColor) {
|
||||
generateElPrimaryColors(theme.primaryColor)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 Element Plus 主色阶
|
||||
const generateElPrimaryColors = (primary: string) => {
|
||||
const root = document.documentElement
|
||||
const hex = primary.replace('#', '')
|
||||
const r = parseInt(hex.substring(0, 2), 16)
|
||||
const g = parseInt(hex.substring(2, 4), 16)
|
||||
const b = parseInt(hex.substring(4, 6), 16)
|
||||
|
||||
// 生成浅色
|
||||
const lightLevels = [3, 5, 7, 8, 9]
|
||||
lightLevels.forEach(level => {
|
||||
const ratio = level / 10
|
||||
const lr = Math.round(r + (255 - r) * ratio)
|
||||
const lg = Math.round(g + (255 - g) * ratio)
|
||||
const lb = Math.round(b + (255 - b) * ratio)
|
||||
root.style.setProperty(`--el-color-primary-light-${level}`, `rgb(${lr}, ${lg}, ${lb})`)
|
||||
})
|
||||
|
||||
// 生成深色
|
||||
const darkRatio = 0.2
|
||||
const dr = Math.round(r * (1 - darkRatio))
|
||||
const dg = Math.round(g * (1 - darkRatio))
|
||||
const db = Math.round(b * (1 - darkRatio))
|
||||
root.style.setProperty('--el-color-primary-dark-2', `rgb(${dr}, ${dg}, ${db})`)
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
const setTheme = (themeName: string) => {
|
||||
currentTheme.value = themeName
|
||||
localStorage.setItem(THEME_STORAGE_KEY, themeName)
|
||||
applyTheme(themeName)
|
||||
}
|
||||
|
||||
// 保存自定义主题
|
||||
const saveCustomTheme = (theme: Partial<ThemeConfig>) => {
|
||||
customTheme.value = { ...theme, name: 'custom', label: '自定义' }
|
||||
localStorage.setItem(CUSTOM_THEME_KEY, JSON.stringify(customTheme.value))
|
||||
setTheme('custom')
|
||||
}
|
||||
|
||||
// 切换主题抽屉
|
||||
const toggleThemeDrawer = () => {
|
||||
showThemeDrawer.value = !showThemeDrawer.value
|
||||
}
|
||||
|
||||
return {
|
||||
currentTheme,
|
||||
customTheme,
|
||||
showThemeDrawer,
|
||||
initTheme,
|
||||
setTheme,
|
||||
saveCustomTheme,
|
||||
toggleThemeDrawer,
|
||||
applyTheme
|
||||
}
|
||||
})
|
||||
|
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
|||
import { ref } from 'vue'
|
||||
import { login as loginApi, getUserInfo as getUserInfoApi, logout as logoutApi, type LoginRequest, type UserInfo } from '@/api/auth'
|
||||
import { setToken, removeToken } from '@/utils/auth'
|
||||
import router from '@/router'
|
||||
import router, { resetRouter } from '@/router'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const token = ref<string | null>(null)
|
||||
|
|
@ -34,6 +34,7 @@ export const useUserStore = defineStore('user', () => {
|
|||
token.value = null
|
||||
userInfo.value = null
|
||||
removeToken()
|
||||
resetRouter()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default defineConfig({
|
|||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5000',
|
||||
target: 'http://localhost:61551',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user