Skip to content

Latest commit

 

History

History
778 lines (658 loc) · 16.1 KB

File metadata and controls

778 lines (658 loc) · 16.1 KB

LayoutSwitch.vue 布局切换组件文档

概述

LayoutSwitch.vue是布局切换组件,提供用户切换不同布局模式的功能,支持管理布局、混合布局、顶部布局等多种布局模式。

文件位置: src/components/common/LayoutSwitch.vue

组件功能

1. 布局切换功能

  • 支持多种布局模式切换
  • 实时预览布局效果
  • 状态持久化保存
  • 响应式布局适配
  • 布局配置管理

2. 组件结构

<template>
  <div class="layout-switch">
    <el-dropdown 
      @command="handleCommand" 
      trigger="click"
      placement="bottom-end"
    >
      <div class="layout-trigger">
        <el-icon class="layout-icon">
          <component :is="currentLayoutIcon" />
        </el-icon>
        <span class="layout-text">{{ currentLayoutText }}</span>
        <el-icon class="dropdown-icon">
          <ArrowDown />
        </el-icon>
      </div>
      
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item 
            v-for="layout in layoutOptions" 
            :key="layout.value"
            :command="layout.value"
            :class="{ 'is-active': currentLayout === layout.value }"
          >
            <div class="layout-option">
              <div class="layout-preview">
                <div :class="['preview-container', `preview-${layout.value}`]">
                  <div class="preview-header"></div>
                  <div class="preview-body">
                    <div v-if="layout.showSidebar" class="preview-sidebar"></div>
                    <div class="preview-main"></div>
                  </div>
                </div>
              </div>
              <div class="layout-info">
                <span class="layout-name">{{ layout.label }}</span>
                <small class="layout-desc">{{ layout.description }}</small>
              </div>
              <el-icon v-if="currentLayout === layout.value" class="check-icon">
                <Check />
              </el-icon>
            </div>
          </el-dropdown-item>
        </el-dropdown-menu>
      </template>
    </el-dropdown>
  </div>
</template>

3. 脚本逻辑

<script setup>
import { ref, computed, onMounted } from 'vue'
import { useLayoutStore } from '@/stores/layout'
import { ElMessage } from 'element-plus'
import {
  ArrowDown,
  Check,
  Grid,
  Menu,
  TopRight
} from '@element-plus/icons-vue'

// 状态管理
const layoutStore = useLayoutStore()

// 布局选项配置
const layoutOptions = ref([
  {
    value: 'admin',
    label: '管理布局',
    description: '经典后台管理布局',
    icon: 'Menu',
    showSidebar: true,
    showHeader: true,
    features: ['侧边栏导航', '顶部工具栏', '面包屑导航']
  },
  {
    value: 'mix',
    label: '混合布局',
    description: '顶部+侧边混合布局',
    icon: 'Grid',
    showSidebar: true,
    showHeader: true,
    features: ['顶部主导航', '侧边子导航', '灵活切换']
  },
  {
    value: 'top',
    label: '顶部布局',
    description: '顶部水平导航布局',
    icon: 'TopRight',
    showSidebar: false,
    showHeader: true,
    features: ['水平导航', '简洁界面', '移动友好']
  }
])

// 当前布局
const currentLayout = computed(() => layoutStore.layoutMode)

// 当前布局信息
const currentLayoutInfo = computed(() => {
  return layoutOptions.value.find(layout => layout.value === currentLayout.value) || layoutOptions.value[0]
})

// 当前布局图标
const currentLayoutIcon = computed(() => currentLayoutInfo.value.icon)

// 当前布局文本
const currentLayoutText = computed(() => currentLayoutInfo.value.label)
</script>

布局切换功能

1. 布局模式切换

// 处理命令
const handleCommand = (layoutMode) => {
  if (layoutMode === currentLayout.value) {
    return
  }
  
  setLayoutMode(layoutMode)
}

// 设置布局模式
const setLayoutMode = (mode) => {
  // 更新布局状态
  layoutStore.setLayoutMode(mode)
  
  // 应用布局变化
  applyLayoutChanges(mode)
  
  // 显示切换提示
  showLayoutChangeNotification(mode)
  
  // 触发布局变化事件
  emitLayoutChange(mode)
}

// 应用布局变化
const applyLayoutChanges = (mode) => {
  const layoutInfo = layoutOptions.value.find(l => l.value === mode)
  if (!layoutInfo) return
  
  // 根据布局模式调整相关设置
  switch (mode) {
    case 'admin':
      layoutStore.setShowSidebar(true)
      layoutStore.setShowHeader(true)
      break
    case 'mix':
      layoutStore.setShowSidebar(true)
      layoutStore.setShowHeader(true)
      break
    case 'top':
      layoutStore.setShowSidebar(false)
      layoutStore.setShowHeader(true)
      break
  }
  
  // 移动端适配
  if (layoutStore.isMobile) {
    adaptMobileLayout(mode)
  }
  
  // 更新CSS类
  updateLayoutClasses(mode)
}

// 移动端布局适配
const adaptMobileLayout = (mode) => {
  if (mode === 'admin' || mode === 'mix') {
    // 移动端自动折叠侧边栏
    layoutStore.setSidebarCollapse(true)
  }
}

// 更新布局CSS类
const updateLayoutClasses = (mode) => {
  const body = document.body
  
  // 移除旧的布局类
  body.classList.remove('layout-admin', 'layout-mix', 'layout-top')
  
  // 添加新的布局类
  body.classList.add(`layout-${mode}`)
  
  // 更新根元素属性
  document.documentElement.setAttribute('data-layout', mode)
}

// 显示布局切换通知
const showLayoutChangeNotification = (mode) => {
  const layoutInfo = layoutOptions.value.find(l => l.value === mode)
  if (!layoutInfo) return
  
  ElMessage({
    message: `已切换到${layoutInfo.label}`,
    type: 'success',
    duration: 2000,
    showClose: false
  })
}

// 触发布局变化事件
const emitLayoutChange = (mode) => {
  window.dispatchEvent(new CustomEvent('layout-change', {
    detail: { 
      mode, 
      layoutInfo: layoutOptions.value.find(l => l.value === mode) 
    }
  }))
}

2. 布局预览功能

// 布局预览配置
const previewConfig = {
  admin: {
    headerHeight: '12px',
    sidebarWidth: '20px',
    mainPadding: '4px'
  },
  mix: {
    headerHeight: '12px',
    sidebarWidth: '16px',
    mainPadding: '4px'
  },
  top: {
    headerHeight: '12px',
    sidebarWidth: '0px',
    mainPadding: '4px'
  }
}

// 生成预览样式
const generatePreviewStyle = (layoutType) => {
  const config = previewConfig[layoutType]
  if (!config) return {}
  
  return {
    '--preview-header-height': config.headerHeight,
    '--preview-sidebar-width': config.sidebarWidth,
    '--preview-main-padding': config.mainPadding
  }
}

3. 布局配置管理

// 获取布局配置
const getLayoutConfig = (mode) => {
  const configs = {
    admin: {
      showSidebar: true,
      showHeader: true,
      sidebarPosition: 'left',
      headerPosition: 'top',
      menuMode: 'vertical',
      contentPadding: '20px'
    },
    mix: {
      showSidebar: true,
      showHeader: true,
      sidebarPosition: 'left',
      headerPosition: 'top',
      menuMode: 'horizontal-vertical',
      contentPadding: '20px'
    },
    top: {
      showSidebar: false,
      showHeader: true,
      sidebarPosition: 'none',
      headerPosition: 'top',
      menuMode: 'horizontal',
      contentPadding: '20px'
    }
  }
  
  return configs[mode] || configs.admin
}

// 应用布局配置
const applyLayoutConfig = (mode) => {
  const config = getLayoutConfig(mode)
  
  // 应用配置到布局store
  Object.keys(config).forEach(key => {
    if (typeof layoutStore[`set${capitalize(key)}`] === 'function') {
      layoutStore[`set${capitalize(key)}`](config[key])
    }
  })
}

// 首字母大写
const capitalize = (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

4. 响应式布局处理

// 监听设备变化
const handleDeviceChange = () => {
  const device = layoutStore.device
  
  // 根据设备类型调整布局
  if (device === 'mobile') {
    handleMobileLayout()
  } else if (device === 'tablet') {
    handleTabletLayout()
  } else {
    handleDesktopLayout()
  }
}

// 移动端布局处理
const handleMobileLayout = () => {
  // 移动端推荐使用顶部布局
  if (currentLayout.value === 'admin' || currentLayout.value === 'mix') {
    // 自动折叠侧边栏
    layoutStore.setSidebarCollapse(true)
  }
  
  // 调整布局参数
  layoutStore.setSidebarWidth(200) // 移动端侧边栏更窄
}

// 平板端布局处理
const handleTabletLayout = () => {
  // 平板端适中的布局参数
  layoutStore.setSidebarWidth(220)
}

// 桌面端布局处理
const handleDesktopLayout = () => {
  // 桌面端完整的布局参数
  layoutStore.setSidebarWidth(240)
  
  // 恢复侧边栏状态
  const savedCollapse = localStorage.getItem('sidebarCollapse')
  if (savedCollapse !== null) {
    layoutStore.setSidebarCollapse(savedCollapse === 'true')
  }
}

// 监听设备变化
onMounted(() => {
  // 初始化设备处理
  handleDeviceChange()
  
  // 监听设备变化事件
  window.addEventListener('resize', handleDeviceChange)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleDeviceChange)
})

5. 布局动画效果

// 布局切换动画
const animateLayoutChange = (fromMode, toMode) => {
  const duration = 300
  
  // 添加切换动画类
  document.body.classList.add('layout-switching')
  
  // 动画完成后移除类
  setTimeout(() => {
    document.body.classList.remove('layout-switching')
  }, duration)
  
  // 触发重新计算布局
  setTimeout(() => {
    window.dispatchEvent(new Event('resize'))
  }, duration / 2)
}

样式定义

1. 组件基础样式

.layout-switch {
  display: inline-block;
}

.layout-trigger {
  display: flex;
  align-items: center;
  padding: 8px 12px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s ease;
  user-select: none;
  
  &:hover {
    background: var(--el-fill-color-light);
  }
}

.layout-icon {
  margin-right: 6px;
  font-size: 16px;
  color: var(--el-text-color-regular);
}

.layout-text {
  font-size: 13px;
  color: var(--el-text-color-regular);
  margin-right: 4px;
}

.dropdown-icon {
  font-size: 12px;
  color: var(--el-text-color-placeholder);
  transition: transform 0.2s ease;
}

.layout-trigger:hover .dropdown-icon {
  transform: rotate(180deg);
}

2. 下拉菜单样式

.layout-option {
  display: flex;
  align-items: center;
  width: 280px;
  padding: 12px;
  
  &:hover {
    background: var(--el-fill-color-light);
  }
  
  &.is-active {
    background: var(--el-color-primary-light-9);
    
    .layout-name {
      color: var(--el-color-primary);
      font-weight: 500;
    }
  }
}

.layout-preview {
  margin-right: 12px;
  flex-shrink: 0;
}

.preview-container {
  width: 60px;
  height: 40px;
  border: 1px solid var(--el-border-color);
  border-radius: 4px;
  overflow: hidden;
  background: var(--el-bg-color);
}

.preview-header {
  height: var(--preview-header-height, 12px);
  background: var(--el-color-primary-light-7);
}

.preview-body {
  display: flex;
  height: calc(100% - var(--preview-header-height, 12px));
}

.preview-sidebar {
  width: var(--preview-sidebar-width, 20px);
  background: var(--el-color-primary-light-8);
}

.preview-main {
  flex: 1;
  background: var(--el-fill-color-lighter);
  margin: var(--preview-main-padding, 4px);
  border-radius: 2px;
}

/* 不同布局的预览样式 */
.preview-admin {
  --preview-header-height: 12px;
  --preview-sidebar-width: 20px;
  --preview-main-padding: 4px;
}

.preview-mix {
  --preview-header-height: 12px;
  --preview-sidebar-width: 16px;
  --preview-main-padding: 4px;
}

.preview-top {
  --preview-header-height: 12px;
  --preview-sidebar-width: 0px;
  --preview-main-padding: 4px;
}

3. 布局信息样式

.layout-info {
  flex: 1;
  min-width: 0;
}

.layout-name {
  display: block;
  font-size: 14px;
  color: var(--el-text-color-primary);
  margin-bottom: 4px;
}

.layout-desc {
  display: block;
  font-size: 12px;
  color: var(--el-text-color-secondary);
  line-height: 1.4;
}

.check-icon {
  margin-left: 8px;
  font-size: 16px;
  color: var(--el-color-primary);
  flex-shrink: 0;
}

4. 布局切换动画

/* 布局切换动画 */
.layout-switching {
  .layout-container {
    transition: all 0.3s ease;
  }
  
  .sidebar {
    transition: width 0.3s ease, transform 0.3s ease;
  }
  
  .main-content {
    transition: margin-left 0.3s ease, padding 0.3s ease;
  }
}

/* 不同布局模式的样式 */
.layout-admin {
  .sidebar {
    position: fixed;
    left: 0;
    top: 60px;
  }
  
  .main-content {
    margin-left: var(--sidebar-width);
  }
}

.layout-mix {
  .sidebar {
    position: fixed;
    left: 0;
    top: 60px;
  }
  
  .main-content {
    margin-left: var(--sidebar-width);
  }
  
  .header-menu {
    display: flex;
  }
}

.layout-top {
  .sidebar {
    display: none;
  }
  
  .main-content {
    margin-left: 0;
  }
  
  .header-menu {
    display: flex;
    justify-content: center;
  }
}

高级功能

1. 布局模板保存

// 保存布局模板
const saveLayoutTemplate = (name, config) => {
  const templates = getLayoutTemplates()
  templates[name] = {
    ...config,
    createTime: Date.now(),
    updateTime: Date.now()
  }
  
  localStorage.setItem('layoutTemplates', JSON.stringify(templates))
  ElMessage.success('布局模板保存成功')
}

// 获取布局模板
const getLayoutTemplates = () => {
  try {
    const templates = localStorage.getItem('layoutTemplates')
    return templates ? JSON.parse(templates) : {}
  } catch (error) {
    console.error('获取布局模板失败:', error)
    return {}
  }
}

// 应用布局模板
const applyLayoutTemplate = (templateName) => {
  const templates = getLayoutTemplates()
  const template = templates[templateName]
  
  if (template) {
    Object.keys(template).forEach(key => {
      if (key !== 'createTime' && key !== 'updateTime') {
        layoutStore[`set${capitalize(key)}`]?.(template[key])
      }
    })
    
    ElMessage.success(`已应用布局模板:${templateName}`)
  }
}

2. 布局快捷键

// 布局快捷键处理
const handleKeyboard = (event) => {
  // Ctrl + 1: 管理布局
  if (event.ctrlKey && event.key === '1') {
    event.preventDefault()
    setLayoutMode('admin')
  }
  
  // Ctrl + 2: 混合布局
  if (event.ctrlKey && event.key === '2') {
    event.preventDefault()
    setLayoutMode('mix')
  }
  
  // Ctrl + 3: 顶部布局
  if (event.ctrlKey && event.key === '3') {
    event.preventDefault()
    setLayoutMode('top')
  }
}

// 绑定快捷键
onMounted(() => {
  document.addEventListener('keydown', handleKeyboard)
})

onUnmounted(() => {
  document.removeEventListener('keydown', handleKeyboard)
})

使用示例

1. 基本使用

<template>
  <div class="header-tools">
    <LayoutSwitch />
  </div>
</template>

<script setup>
import LayoutSwitch from '@/components/common/LayoutSwitch.vue'
</script>

2. 监听布局变化

<script setup>
import { onMounted, onUnmounted } from 'vue'

const handleLayoutChange = (event) => {
  const { mode, layoutInfo } = event.detail
  console.log(`布局已切换到: ${layoutInfo.label}`)
  
  // 执行相关操作
  handleLayoutChangeEffect(mode)
}

const handleLayoutChangeEffect = (mode) => {
  // 重新计算图表大小
  nextTick(() => {
    window.dispatchEvent(new Event('resize'))
  })
}

onMounted(() => {
  window.addEventListener('layout-change', handleLayoutChange)
})

onUnmounted(() => {
  window.removeEventListener('layout-change', handleLayoutChange)
})
</script>

3. 自定义布局选项

<script setup>
import { ref } from 'vue'

// 自定义布局选项
const customLayoutOptions = ref([
  {
    value: 'custom',
    label: '自定义布局',
    description: '个性化布局配置',
    icon: 'Setting',
    showSidebar: true,
    showHeader: true
  }
])
</script>

注意事项

  1. 响应式: 确保在不同设备上的布局切换效果
  2. 性能: 布局切换时避免频繁的DOM操作
  3. 兼容性: 考虑不同浏览器的CSS支持
  4. 用户体验: 提供平滑的切换动画
  5. 状态管理: 正确保存和恢复布局状态

相关文档


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