layout.js是布局状态管理模块,使用Pinia管理应用的布局配置、设备信息、主题设置等状态。
文件位置: src/stores/layout.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useLayoutStore = defineStore('layout', () => {
// 布局模式
const layoutMode = ref('admin') // admin, mix, top
// 侧边栏状态
const sidebarCollapse = ref(false)
const sidebarWidth = ref(240)
const collapsedWidth = ref(64)
// 设备信息
const device = ref('desktop') // desktop, tablet, mobile
const screenWidth = ref(window.innerWidth)
const screenHeight = ref(window.innerHeight)
// 显示控制
const showHeader = ref(true)
const showSidebar = ref(true)
const showTabs = ref(true)
const showBreadcrumb = ref(true)
const showFooter = ref(false)
// 主题设置
const theme = ref('default') // default, dark, blue, green
const primaryColor = ref('#409EFF')
const isDark = ref(false)
// 字体大小
const fontSize = ref('medium') // small, medium, large
// 固定设置
const fixedHeader = ref(true)
const fixedSidebar = ref(true)
// 动画设置
const enableTransition = ref(true)
const transitionName = ref('fade-transform')
// 水印设置
const showWatermark = ref(false)
const watermarkText = ref('')
// 计算属性
const isMobile = computed(() => device.value === 'mobile')
const isTablet = computed(() => device.value === 'tablet')
const isDesktop = computed(() => device.value === 'desktop')
const currentSidebarWidth = computed(() =>
sidebarCollapse.value ? collapsedWidth.value : sidebarWidth.value
)
const showMobileSidebar = computed(() =>
isMobile.value && !sidebarCollapse.value
)
return {
// 状态
layoutMode,
sidebarCollapse,
sidebarWidth,
collapsedWidth,
device,
screenWidth,
screenHeight,
showHeader,
showSidebar,
showTabs,
showBreadcrumb,
showFooter,
theme,
primaryColor,
isDark,
fontSize,
fixedHeader,
fixedSidebar,
enableTransition,
transitionName,
showWatermark,
watermarkText,
// 计算属性
isMobile,
isTablet,
isDesktop,
currentSidebarWidth,
showMobileSidebar
}
})// 切换布局模式
const setLayoutMode = (mode) => {
layoutMode.value = mode
// 根据布局模式调整其他设置
switch (mode) {
case 'admin':
showSidebar.value = true
showHeader.value = true
break
case 'mix':
showSidebar.value = true
showHeader.value = true
break
case 'top':
showSidebar.value = false
showHeader.value = true
break
}
// 保存到本地存储
localStorage.setItem('layoutMode', mode)
// 应用布局变化
applyLayoutChanges()
}
// 应用布局变化
const applyLayoutChanges = () => {
// 更新CSS变量
const root = document.documentElement
root.style.setProperty('--sidebar-width', `${currentSidebarWidth.value}px`)
root.style.setProperty('--header-height', showHeader.value ? '60px' : '0px')
root.style.setProperty('--tabs-height', showTabs.value ? '40px' : '0px')
// 触发窗口resize事件,让图表等组件重新计算尺寸
window.dispatchEvent(new Event('resize'))
}// 切换侧边栏折叠状态
const toggleSidebar = () => {
sidebarCollapse.value = !sidebarCollapse.value
// 移动端自动关闭侧边栏
if (isMobile.value) {
sidebarCollapse.value = true
}
// 保存状态
localStorage.setItem('sidebarCollapse', sidebarCollapse.value.toString())
// 应用变化
applyLayoutChanges()
}
// 设置侧边栏状态
const setSidebarCollapse = (collapse) => {
sidebarCollapse.value = collapse
localStorage.setItem('sidebarCollapse', collapse.toString())
applyLayoutChanges()
}
// 设置侧边栏宽度
const setSidebarWidth = (width) => {
sidebarWidth.value = width
localStorage.setItem('sidebarWidth', width.toString())
applyLayoutChanges()
}
// 移动端侧边栏处理
const handleMobileSidebar = () => {
if (isMobile.value) {
// 移动端默认折叠侧边栏
sidebarCollapse.value = true
} else {
// 桌面端恢复之前的状态
const saved = localStorage.getItem('sidebarCollapse')
if (saved !== null) {
sidebarCollapse.value = saved === 'true'
}
}
}// 检测设备类型
const detectDevice = () => {
const width = window.innerWidth
if (width < 768) {
device.value = 'mobile'
} else if (width < 1200) {
device.value = 'tablet'
} else {
device.value = 'desktop'
}
screenWidth.value = width
screenHeight.value = window.innerHeight
// 处理移动端侧边栏
handleMobileSidebar()
// 应用布局变化
applyLayoutChanges()
}
// 监听窗口大小变化
const handleResize = () => {
detectDevice()
}
// 初始化设备检测
const initDeviceDetection = () => {
detectDevice()
window.addEventListener('resize', handleResize)
}
// 清理设备检测
const cleanupDeviceDetection = () => {
window.removeEventListener('resize', handleResize)
}// 设置主题
const setTheme = (themeName) => {
theme.value = themeName
// 应用主题到DOM
document.documentElement.setAttribute('data-theme', themeName)
// 保存到本地存储
localStorage.setItem('theme', themeName)
// 更新主题色
updateThemeColors(themeName)
}
// 切换暗色模式
const toggleDark = () => {
isDark.value = !isDark.value
// 应用暗色模式
if (isDark.value) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
// 保存状态
localStorage.setItem('isDark', isDark.value.toString())
}
// 设置主色调
const setPrimaryColor = (color) => {
primaryColor.value = color
// 更新CSS变量
document.documentElement.style.setProperty('--el-color-primary', color)
// 生成相关颜色
generateThemeColors(color)
// 保存到本地存储
localStorage.setItem('primaryColor', color)
}
// 生成主题相关颜色
const generateThemeColors = (primaryColor) => {
const colors = generateColorPalette(primaryColor)
colors.forEach((color, index) => {
document.documentElement.style.setProperty(
`--el-color-primary-light-${index + 1}`,
color
)
})
}
// 更新主题颜色
const updateThemeColors = (themeName) => {
const themeColors = {
default: '#409EFF',
dark: '#409EFF',
blue: '#1890ff',
green: '#52c41a',
purple: '#722ed1',
red: '#f5222d'
}
const color = themeColors[themeName] || themeColors.default
setPrimaryColor(color)
}// 设置字体大小
const setFontSize = (size) => {
fontSize.value = size
// 应用字体大小到DOM
document.documentElement.setAttribute('data-font-size', size)
// 更新CSS变量
const fontSizes = {
small: '12px',
medium: '14px',
large: '16px'
}
document.documentElement.style.setProperty(
'--base-font-size',
fontSizes[size] || fontSizes.medium
)
// 保存到本地存储
localStorage.setItem('fontSize', size)
}
// 增大字体
const increaseFontSize = () => {
const sizes = ['small', 'medium', 'large']
const currentIndex = sizes.indexOf(fontSize.value)
const nextIndex = Math.min(currentIndex + 1, sizes.length - 1)
setFontSize(sizes[nextIndex])
}
// 减小字体
const decreaseFontSize = () => {
const sizes = ['small', 'medium', 'large']
const currentIndex = sizes.indexOf(fontSize.value)
const prevIndex = Math.max(currentIndex - 1, 0)
setFontSize(sizes[prevIndex])
}// 设置组件显示状态
const setShowHeader = (show) => {
showHeader.value = show
localStorage.setItem('showHeader', show.toString())
applyLayoutChanges()
}
const setShowSidebar = (show) => {
showSidebar.value = show
localStorage.setItem('showSidebar', show.toString())
applyLayoutChanges()
}
const setShowTabs = (show) => {
showTabs.value = show
localStorage.setItem('showTabs', show.toString())
applyLayoutChanges()
}
const setShowBreadcrumb = (show) => {
showBreadcrumb.value = show
localStorage.setItem('showBreadcrumb', show.toString())
}
const setShowFooter = (show) => {
showFooter.value = show
localStorage.setItem('showFooter', show.toString())
applyLayoutChanges()
}
// 批量设置显示状态
const setDisplaySettings = (settings) => {
Object.keys(settings).forEach(key => {
if (key in this) {
this[key] = settings[key]
localStorage.setItem(key, settings[key].toString())
}
})
applyLayoutChanges()
}// 设置固定头部
const setFixedHeader = (fixed) => {
fixedHeader.value = fixed
// 应用固定样式
const header = document.querySelector('.layout-header')
if (header) {
if (fixed) {
header.classList.add('fixed')
} else {
header.classList.remove('fixed')
}
}
localStorage.setItem('fixedHeader', fixed.toString())
}
// 设置固定侧边栏
const setFixedSidebar = (fixed) => {
fixedSidebar.value = fixed
// 应用固定样式
const sidebar = document.querySelector('.layout-sidebar')
if (sidebar) {
if (fixed) {
sidebar.classList.add('fixed')
} else {
sidebar.classList.remove('fixed')
}
}
localStorage.setItem('fixedSidebar', fixed.toString())
}// 设置过渡动画
const setEnableTransition = (enable) => {
enableTransition.value = enable
// 应用动画设置
if (enable) {
document.documentElement.classList.add('enable-transition')
} else {
document.documentElement.classList.remove('enable-transition')
}
localStorage.setItem('enableTransition', enable.toString())
}
// 设置过渡动画名称
const setTransitionName = (name) => {
transitionName.value = name
localStorage.setItem('transitionName', name)
}// 设置水印显示
const setShowWatermark = (show) => {
showWatermark.value = show
localStorage.setItem('showWatermark', show.toString())
// 触发水印组件更新
window.dispatchEvent(new CustomEvent('watermark-change', {
detail: { show, text: watermarkText.value }
}))
}
// 设置水印文本
const setWatermarkText = (text) => {
watermarkText.value = text
localStorage.setItem('watermarkText', text)
// 触发水印组件更新
if (showWatermark.value) {
window.dispatchEvent(new CustomEvent('watermark-change', {
detail: { show: true, text }
}))
}
}// 从本地存储恢复状态
const restoreFromStorage = () => {
try {
// 恢复布局模式
const savedLayoutMode = localStorage.getItem('layoutMode')
if (savedLayoutMode) {
layoutMode.value = savedLayoutMode
}
// 恢复侧边栏状态
const savedSidebarCollapse = localStorage.getItem('sidebarCollapse')
if (savedSidebarCollapse !== null) {
sidebarCollapse.value = savedSidebarCollapse === 'true'
}
const savedSidebarWidth = localStorage.getItem('sidebarWidth')
if (savedSidebarWidth) {
sidebarWidth.value = parseInt(savedSidebarWidth)
}
// 恢复显示设置
const displaySettings = [
'showHeader', 'showSidebar', 'showTabs',
'showBreadcrumb', 'showFooter'
]
displaySettings.forEach(setting => {
const saved = localStorage.getItem(setting)
if (saved !== null) {
this[setting] = saved === 'true'
}
})
// 恢复主题设置
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
theme.value = savedTheme
}
const savedIsDark = localStorage.getItem('isDark')
if (savedIsDark !== null) {
isDark.value = savedIsDark === 'true'
}
const savedPrimaryColor = localStorage.getItem('primaryColor')
if (savedPrimaryColor) {
primaryColor.value = savedPrimaryColor
}
// 恢复字体大小
const savedFontSize = localStorage.getItem('fontSize')
if (savedFontSize) {
fontSize.value = savedFontSize
}
// 恢复固定设置
const savedFixedHeader = localStorage.getItem('fixedHeader')
if (savedFixedHeader !== null) {
fixedHeader.value = savedFixedHeader === 'true'
}
const savedFixedSidebar = localStorage.getItem('fixedSidebar')
if (savedFixedSidebar !== null) {
fixedSidebar.value = savedFixedSidebar === 'true'
}
// 恢复动画设置
const savedEnableTransition = localStorage.getItem('enableTransition')
if (savedEnableTransition !== null) {
enableTransition.value = savedEnableTransition === 'true'
}
const savedTransitionName = localStorage.getItem('transitionName')
if (savedTransitionName) {
transitionName.value = savedTransitionName
}
// 恢复水印设置
const savedShowWatermark = localStorage.getItem('showWatermark')
if (savedShowWatermark !== null) {
showWatermark.value = savedShowWatermark === 'true'
}
const savedWatermarkText = localStorage.getItem('watermarkText')
if (savedWatermarkText) {
watermarkText.value = savedWatermarkText
}
} catch (error) {
console.error('恢复布局状态失败:', error)
}
}
// 重置所有设置
const resetSettings = () => {
// 重置为默认值
layoutMode.value = 'admin'
sidebarCollapse.value = false
sidebarWidth.value = 240
device.value = 'desktop'
showHeader.value = true
showSidebar.value = true
showTabs.value = true
showBreadcrumb.value = true
showFooter.value = false
theme.value = 'default'
primaryColor.value = '#409EFF'
isDark.value = false
fontSize.value = 'medium'
fixedHeader.value = true
fixedSidebar.value = true
enableTransition.value = true
transitionName.value = 'fade-transform'
showWatermark.value = false
watermarkText.value = ''
// 清除本地存储
const keys = [
'layoutMode', 'sidebarCollapse', 'sidebarWidth',
'showHeader', 'showSidebar', 'showTabs', 'showBreadcrumb', 'showFooter',
'theme', 'isDark', 'primaryColor', 'fontSize',
'fixedHeader', 'fixedSidebar', 'enableTransition', 'transitionName',
'showWatermark', 'watermarkText'
]
keys.forEach(key => {
localStorage.removeItem(key)
})
// 应用变化
applyLayoutChanges()
}// 布局相关的组合式API
export const useLayout = () => {
const layoutStore = useLayoutStore()
return {
// 状态
layoutMode: computed(() => layoutStore.layoutMode),
sidebarCollapse: computed(() => layoutStore.sidebarCollapse),
device: computed(() => layoutStore.device),
isMobile: computed(() => layoutStore.isMobile),
isTablet: computed(() => layoutStore.isTablet),
isDesktop: computed(() => layoutStore.isDesktop),
// 方法
setLayoutMode: layoutStore.setLayoutMode,
toggleSidebar: layoutStore.toggleSidebar,
setSidebarCollapse: layoutStore.setSidebarCollapse,
setTheme: layoutStore.setTheme,
toggleDark: layoutStore.toggleDark,
setFontSize: layoutStore.setFontSize
}
}// 响应式相关的组合式API
export const useResponsive = () => {
const layoutStore = useLayoutStore()
const breakpoints = {
xs: 480,
sm: 768,
md: 1024,
lg: 1200,
xl: 1920
}
const isXs = computed(() => layoutStore.screenWidth < breakpoints.xs)
const isSm = computed(() => layoutStore.screenWidth >= breakpoints.xs && layoutStore.screenWidth < breakpoints.sm)
const isMd = computed(() => layoutStore.screenWidth >= breakpoints.sm && layoutStore.screenWidth < breakpoints.md)
const isLg = computed(() => layoutStore.screenWidth >= breakpoints.md && layoutStore.screenWidth < breakpoints.lg)
const isXl = computed(() => layoutStore.screenWidth >= breakpoints.lg)
return {
screenWidth: computed(() => layoutStore.screenWidth),
screenHeight: computed(() => layoutStore.screenHeight),
device: computed(() => layoutStore.device),
isMobile: computed(() => layoutStore.isMobile),
isTablet: computed(() => layoutStore.isTablet),
isDesktop: computed(() => layoutStore.isDesktop),
isXs,
isSm,
isMd,
isLg,
isXl,
breakpoints
}
}<template>
<div :class="layoutClasses">
<div v-if="showHeader" class="header">
<!-- 头部内容 -->
</div>
<div v-if="showSidebar" class="sidebar">
<!-- 侧边栏内容 -->
</div>
<div class="main">
<!-- 主要内容 -->
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useLayout } from '@/stores/layout'
const {
layoutMode,
sidebarCollapse,
isMobile,
showHeader,
showSidebar,
toggleSidebar
} = useLayout()
const layoutClasses = computed(() => [
'layout',
`layout-${layoutMode.value}`,
{
'sidebar-collapse': sidebarCollapse.value,
'is-mobile': isMobile.value
}
])
</script><script setup>
import { useResponsive } from '@/stores/layout'
const { isMobile, isTablet, isDesktop } = useResponsive()
// 根据设备类型调整行为
const columns = computed(() => {
if (isMobile.value) return 1
if (isTablet.value) return 2
return 3
})
</script>- 性能: 避免频繁的DOM操作和样式计算
- 响应式: 合理处理不同设备的布局适配
- 持久化: 重要设置需要持久化到本地存储
- 兼容性: 确保在不同浏览器中的兼容性
- 用户体验: 提供平滑的过渡动画
最后更新时间:2025-09-19