Skip to content

Latest commit

 

History

History
759 lines (633 loc) · 17.9 KB

File metadata and controls

759 lines (633 loc) · 17.9 KB

layout.js 布局状态管理文档

概述

layout.js是布局状态管理模块,使用Pinia管理应用的布局配置、设备信息、主题设置等状态。

文件位置: src/stores/layout.js

状态定义

1. 布局状态结构

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
  }
})

核心方法

1. 布局模式管理

// 切换布局模式
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'))
}

2. 侧边栏管理

// 切换侧边栏折叠状态
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'
    }
  }
}

3. 设备检测

// 检测设备类型
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)
}

4. 主题管理

// 设置主题
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)
}

5. 字体大小管理

// 设置字体大小
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])
}

6. 显示控制

// 设置组件显示状态
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()
}

7. 固定设置

// 设置固定头部
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())
}

8. 动画设置

// 设置过渡动画
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)
}

9. 水印设置

// 设置水印显示
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 }
    }))
  }
}

10. 状态持久化

// 从本地存储恢复状态
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

1. useLayout组合函数

// 布局相关的组合式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
  }
}

2. useResponsive组合函数

// 响应式相关的组合式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
  }
}

使用示例

1. 在组件中使用

<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>

2. 响应式使用

<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>

注意事项

  1. 性能: 避免频繁的DOM操作和样式计算
  2. 响应式: 合理处理不同设备的布局适配
  3. 持久化: 重要设置需要持久化到本地存储
  4. 兼容性: 确保在不同浏览器中的兼容性
  5. 用户体验: 提供平滑的过渡动画

相关文档


最后更新时间:2025-09-19