大屏ECharts适配完整方案

大屏ECharts适配完整方案

一、适配方案架构

1. 多层级响应式架构

复制代码
┌─────────────────────────────────────────────────────┐
│                  应用场景识别层                        │
│  • 大屏/中屏/移动端识别                               │
│  • 分辨率自适应策略                                   │
└─────────────────────────┬───────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────┐
│                  容器管理层                           │
│  • 尺寸监听 (ResizeObserver + window.resize)        │
│  • 防抖节流优化                                      │
│  • 容器状态管理                                      │
└─────────────────────────┬───────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────┐
│                  配置计算层                           │
│  • 基准设计稿 (1920×1080)                            │
│  • 动态比例计算                                      │
│  • 响应式配置生成                                    │
└─────────────────────────┬───────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────┐
│                  渲染优化层                           │
│  • 按需渲染                                          │
│  • 动画优化                                          │
│  • 性能监控                                          │
└─────────────────────────────────────────────────────┘

二、核心实现代码

1. 响应式适配基类

javascript 复制代码
// utils/ResponsiveChart.js
export class ResponsiveChart {
  constructor(container, options = {}) {
    this.container = container
    this.chartInstance = null
    this.options = {
      designWidth: 1920,      // 设计稿宽度
      designHeight: 1080,     // 设计稿高度
      minWidth: 800,          // 最小宽度
      minHeight: 600,         // 最小高度
      debounceDelay: 150,     // 防抖延迟
      throttleDelay: 100,     // 节流间隔
      ...options
    }
    
    this.currentScale = 1
    this.isInitialized = false
    this.resizeObserver = null
    this.resizeHandlers = []
  }
  
  // 初始化图表
  async initChart() {
    if (!this.container) {
      console.error('Container not found')
      return
    }
    
    // 1. 初始化ECharts实例
    this.chartInstance = echarts.init(this.container)
    
    // 2. 设置初始配置
    await this.updateChartConfig()
    
    // 3. 初始化监听器
    this.initListeners()
    
    // 4. 性能监控初始化
    this.initPerformanceMonitor()
    
    this.isInitialized = true
  }
  
  // 监听器初始化
  initListeners() {
    // 双重监听策略
    this.initResizeObserver()
    this.initWindowResizeListener()
    this.initVisibilityListener()
  }
  
  // ResizeObserver监听
  initResizeObserver() {
    if (typeof ResizeObserver === 'undefined') {
      console.warn('ResizeObserver not supported, fallback to window resize')
      return
    }
    
    this.resizeObserver = new ResizeObserver(
      this.debounce(() => {
        this.handleContainerResize()
      }, this.options.debounceDelay)
    )
    
    this.resizeObserver.observe(this.container)
  }
  
  // 窗口resize监听
  initWindowResizeListener() {
    window.addEventListener('resize', 
      this.throttle(() => {
        this.handleWindowResize()
      }, this.options.throttleDelay)
    )
  }
  
  // 页面可见性监听
  initVisibilityListener() {
    document.addEventListener('visibilitychange', () => {
      if (!document.hidden && this.chartInstance) {
        // 页面重新可见时刷新图表
        setTimeout(() => {
          this.chartInstance.resize()
        }, 300)
      }
    })
  }
  
  // 处理容器尺寸变化
  handleContainerResize() {
    if (!this.chartInstance || !this.isInitialized) return
    
    const { width, height } = this.getContainerSize()
    
    if (width <= 0 || height <= 0) return
    
    // 计算缩放比例
    const scale = this.calculateScale(width, height)
    
    // 只有在比例变化超过阈值或尺寸变化较大时才更新
    if (Math.abs(scale - this.currentScale) > 0.01 || 
        this.shouldForceResize(width, height)) {
      
      this.currentScale = scale
      this.updateChartConfig()
      
      // 使用requestAnimationFrame优化渲染
      requestAnimationFrame(() => {
        this.chartInstance.resize()
        this.triggerResizeEvent(width, height, scale)
      })
    }
  }
  
  // 计算缩放比例
  calculateScale(width, height) {
    const { designWidth, designHeight } = this.options
    
    // 基于最小边界的等比缩放
    const scaleX = width / designWidth
    const scaleY = height / designHeight
    const minScale = Math.min(scaleX, scaleY)
    
    // 限制在合理范围内
    return Math.max(0.3, Math.min(minScale, 2))
  }
  
  // 更新图表配置
  async updateChartConfig() {
    const size = this.getContainerSize()
    const responsiveConfig = await this.generateResponsiveConfig(size)
    
    if (this.chartInstance) {
      // 保持动画连续性
      this.chartInstance.setOption(responsiveConfig, {
        notMerge: false,  // 合并配置
        lazyUpdate: true, // 延迟更新
        silent: true      // 不触发事件
      })
    }
  }
  
  // 生成响应式配置
  async generateResponsiveConfig({ width, height }) {
    const scale = this.currentScale
    
    return {
      // 文字大小自适应
      textStyle: {
        fontSize: Math.max(10, Math.round(12 * scale))
      },
      
      // 标题配置
      title: {
        textStyle: {
          fontSize: Math.max(14, Math.round(18 * scale))
        },
        top: 20 * scale,
        left: 'center'
      },
      
      // 图例配置
      legend: {
        show: width > 400,  // 小屏隐藏图例
        orient: width > 800 ? 'horizontal' : 'vertical',
        top: width > 800 ? 'top' : 'bottom',
        left: 'center',
        textStyle: {
          fontSize: Math.max(10, Math.round(12 * scale))
        },
        itemWidth: 10 * scale,
        itemHeight: 10 * scale,
        itemGap: 8 * scale
      },
      
      // 网格配置
      grid: {
        left: this.calculatePadding('left', width, scale),
        right: this.calculatePadding('right', width, scale),
        top: this.calculatePadding('top', height, scale),
        bottom: this.calculatePadding('bottom', height, scale)
      },
      
      // 提示框配置
      tooltip: {
        confine: width < 600,  // 小屏限制提示框位置
        textStyle: {
          fontSize: Math.max(10, Math.round(12 * scale))
        }
      },
      
      // 动画配置
      animation: width > 1000,  // 大屏启用动画,小屏禁用
      animationDuration: 500,
      animationEasing: 'cubicOut'
    }
  }
  
  // 计算内边距
  calculatePadding(position, size, scale) {
    const basePadding = {
      left: 80,
      right: 40,
      top: 60,
      bottom: 60
    }
    
    let padding = basePadding[position] * scale
    
    // 小屏优化
    if (size < 600) {
      padding = Math.max(padding * 0.5, 10)
    }
    
    return padding
  }
  
  // 防抖函数
  debounce(func, delay) {
    let timer = null
    return (...args) => {
      clearTimeout(timer)
      timer = setTimeout(() => {
        func.apply(this, args)
      }, delay)
    }
  }
  
  // 节流函数
  throttle(func, delay) {
    let lastCall = 0
    return (...args) => {
      const now = Date.now()
      if (now - lastCall >= delay) {
        lastCall = now
        func.apply(this, args)
      }
    }
  }
  
  // 获取容器尺寸
  getContainerSize() {
    if (!this.container) return { width: 0, height: 0 }
    
    const rect = this.container.getBoundingClientRect()
    return {
      width: rect.width,
      height: rect.height
    }
  }
  
  // 是否强制重绘
  shouldForceResize(newWidth, newHeight) {
    const oldSize = this.lastSize || { width: 0, height: 0 }
    const widthDiff = Math.abs(newWidth - oldSize.width)
    const heightDiff = Math.abs(newHeight - oldSize.height)
    
    this.lastSize = { width: newWidth, height: newHeight }
    
    // 尺寸变化超过10%时强制重绘
    return widthDiff > oldSize.width * 0.1 || 
           heightDiff > oldSize.height * 0.1
  }
  
  // 触发自定义resize事件
  triggerResizeEvent(width, height, scale) {
    const event = new CustomEvent('chartResize', {
      detail: { width, height, scale }
    })
    this.container.dispatchEvent(event)
  }
  
  // 性能监控
  initPerformanceMonitor() {
    if (typeof PerformanceObserver === 'undefined') return
    
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.name.includes('chart-render')) {
          console.log(`Chart render time: ${entry.duration.toFixed(2)}ms`)
        }
      }
    })
    
    observer.observe({ entryTypes: ['measure'] })
  }
  
  // 清理资源
  destroy() {
    if (this.resizeObserver) {
      this.resizeObserver.disconnect()
      this.resizeObserver = null
    }
    
    window.removeEventListener('resize', this.handleWindowResize)
    document.removeEventListener('visibilitychange', this.handleVisibilityChange)
    
    if (this.chartInstance) {
      this.chartInstance.dispose()
      this.chartInstance = null
    }
    
    this.isInitialized = false
  }
}

2. 具体图表类型适配器

javascript 复制代码
// adapters/PieChartAdapter.js
export class PieChartAdapter extends ResponsiveChart {
  generateResponsiveConfig({ width, height }) {
    const scale = this.currentScale
    const baseConfig = super.generateResponsiveConfig({ width, height })
    
    return {
      ...baseConfig,
      series: [{
        type: 'pie',
        radius: this.getRadius(width, scale),
        center: this.getCenter(width, height),
        label: {
          show: width > 400,
          position: this.getLabelPosition(width),
          fontSize: Math.max(10, Math.round(12 * scale)),
          formatter: this.getLabelFormatter(width)
        },
        emphasis: {
          scale: width > 600,  // 小屏禁用放大效果
          scaleSize: 10 * scale
        }
      }]
    }
  }
  
  getRadius(width, scale) {
    if (width < 400) return '50%'
    if (width < 800) return ['40%', '60%']
    return ['30%', '70%']
  }
  
  getCenter(width, height) {
    if (width < 600) return ['50%', '50%']
    return ['50%', '48%']  // 稍微偏上,为标签留空间
  }
  
  getLabelPosition(width) {
    if (width < 400) return 'inside'
    if (width < 1000) return 'outside'
    return 'outside'
  }
  
  getLabelFormatter(width) {
    if (width < 600) {
      // 小屏只显示名称
      return '{b}'
    } else if (width < 1000) {
      // 中屏显示名称和百分比
      return '{b}: {d}%'
    } else {
      // 大屏显示完整信息
      return '{b}\n数量: {c}\n占比: {d}%'
    }
  }
}

3. 大屏适配组件

javascript 复制代码
<!-- components/ResponsiveEChart.vue -->
<template>
  <div class="responsive-chart-container" ref="chartContainer">
    <div 
      v-if="loading" 
      class="chart-loading"
      :style="loadingStyle"
    >
      <div class="loading-spinner"></div>
    </div>
    
    <div 
      v-if="error" 
      class="chart-error"
      :style="errorStyle"
    >
      <div class="error-content">
        <span class="error-icon">⚠️</span>
        <p>{{ errorMessage }}</p>
        <button @click="retry">重试</button>
      </div>
    </div>
    
    <!-- 图表容器 -->
    <div 
      class="chart-wrapper"
      :style="wrapperStyle"
      ref="chartWrapper"
    >
      <div class="chart-inner" ref="chartElement"></div>
    </div>
    
    <!-- 工具栏 -->
    <div v-if="showToolbar" class="chart-toolbar">
      <button 
        v-for="action in toolbarActions" 
        :key="action.name"
        @click="handleToolbarAction(action)"
        :title="action.tooltip"
        :disabled="action.disabled"
      >
        {{ action.icon }}
      </button>
    </div>
  </div>
</template>

<script>
import { debounce, throttle } from 'lodash-es'
import { ResponsiveChart } from '@/utils/ResponsiveChart'
import { PieChartAdapter } from '@/adapters/PieChartAdapter'

export default {
  name: 'ResponsiveEChart',
  
  props: {
    // 图表类型
    chartType: {
      type: String,
      default: 'pie',
      validator: value => ['pie', 'line', 'bar', 'scatter'].includes(value)
    },
    
    // 图表数据
    chartData: {
      type: Array,
      default: () => []
    },
    
    // 图表配置
    chartOptions: {
      type: Object,
      default: () => ({})
    },
    
    // 设计稿尺寸
    designSize: {
      type: Object,
      default: () => ({ width: 1920, height: 1080 })
    },
    
    // 响应式配置
    responsiveConfig: {
      type: Object,
      default: () => ({
        // 响应式断点
        breakpoints: {
          xs: 480,
          sm: 768,
          md: 1024,
          lg: 1280,
          xl: 1920
        },
        // 最小尺寸
        minSize: {
          width: 300,
          height: 200
        },
        // 性能配置
        performance: {
          throttleDelay: 100,
          debounceDelay: 150,
          lazyRender: true
        }
      })
    },
    
    // 是否显示工具栏
    showToolbar: {
      type: Boolean,
      default: true
    },
    
    // 是否启用动画
    animation: {
      type: Boolean,
      default: true
    },
    
    // 是否启用主题
    theme: {
      type: [String, Object],
      default: 'light'
    }
  },
  
  data() {
    return {
      loading: false,
      error: false,
      errorMessage: '',
      currentBreakpoint: '',
      chartInstance: null,
      adapter: null,
      containerSize: { width: 0, height: 0 }
    }
  },
  
  computed: {
    // 容器样式
    wrapperStyle() {
      return {
        width: '100%',
        height: '100%',
        minWidth: `${this.responsiveConfig.minSize.width}px`,
        minHeight: `${this.responsiveConfig.minSize.height}px`,
        position: 'relative'
      }
    },
    
    // 加载样式
    loadingStyle() {
      return {
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'rgba(255, 255, 255, 0.8)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        zIndex: 10
      }
    },
    
    // 错误样式
    errorStyle() {
      return {
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'rgba(255, 255, 255, 0.9)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        zIndex: 10
      }
    },
    
    // 工具栏操作
    toolbarActions() {
      return [
        {
          name: 'refresh',
          icon: '🔄',
          tooltip: '刷新图表',
          disabled: this.loading
        },
        {
          name: 'fullscreen',
          icon: '⛶',
          tooltip: '全屏显示',
          disabled: false
        },
        {
          name: 'export',
          icon: '📥',
          tooltip: '导出图片',
          disabled: false
        },
        {
          name: 'reset',
          icon: '↩️',
          tooltip: '重置视图',
          disabled: false
        }
      ]
    },
    
    // 当前断点
    currentBreakpointName() {
      const { width } = this.containerSize
      const { breakpoints } = this.responsiveConfig
      
      if (width < breakpoints.xs) return 'xs'
      if (width < breakpoints.sm) return 'sm'
      if (width < breakpoints.md) return 'md'
      if (width < breakpoints.lg) return 'lg'
      if (width < breakpoints.xl) return 'xl'
      return 'xxl'
    }
  },
  
  watch: {
    // 监听数据变化
    chartData: {
      handler(newData) {
        if (this.chartInstance && this.adapter) {
          this.updateChartData(newData)
        }
      },
      deep: true
    },
    
    // 监听配置变化
    chartOptions: {
      handler(newOptions) {
        if (this.chartInstance) {
          this.updateChartOptions(newOptions)
        }
      },
      deep: true
    },
    
    // 监听容器尺寸变化
    containerSize: {
      handler(newSize) {
        this.handleSizeChange(newSize)
      },
      deep: true
    }
  },
  
  mounted() {
    this.initChart()
  },
  
  beforeUnmount() {
    this.destroyChart()
  },
  
  methods: {
    // 初始化图表
    async initChart() {
      try {
        this.loading = true
        
        // 1. 获取容器元素
        const container = this.$refs.chartElement
        if (!container) {
          throw new Error('图表容器未找到')
        }
        
        // 2. 根据图表类型选择适配器
        this.adapter = this.createAdapter()
        
        // 3. 初始化适配器
        await this.adapter.initChart()
        
        // 4. 设置初始数据
        this.updateChartData(this.chartData)
        
        // 5. 设置初始配置
        this.updateChartOptions(this.chartOptions)
        
        // 6. 初始化容器尺寸监听
        this.initContainerObserver()
        
        this.loading = false
        this.error = false
        
        // 7. 触发初始化完成事件
        this.$emit('chart-initialized', this.chartInstance)
        
      } catch (error) {
        console.error('图表初始化失败:', error)
        this.error = true
        this.errorMessage = error.message
        this.loading = false
      }
    },
    
    // 创建适配器
    createAdapter() {
      const container = this.$refs.chartElement
      
      switch (this.chartType) {
        case 'pie':
          return new PieChartAdapter(container, {
            designWidth: this.designSize.width,
            designHeight: this.designSize.height,
            ...this.responsiveConfig
          })
          
        case 'line':
          // 返回折线图适配器
          // return new LineChartAdapter(...)
          
        case 'bar':
          // 返回柱状图适配器
          // return new BarChartAdapter(...)
          
        default:
          return new ResponsiveChart(container, {
            designWidth: this.designSize.width,
            designHeight: this.designSize.height,
            ...this.responsiveConfig
          })
      }
    },
    
    // 初始化容器观察器
    initContainerObserver() {
      const container = this.$refs.chartWrapper
      
      this.containerObserver = new ResizeObserver(
        debounce(entries => {
          for (const entry of entries) {
            const { width, height } = entry.contentRect
            this.containerSize = { width, height }
          }
        }, this.responsiveConfig.performance.debounceDelay)
      )
      
      this.containerObserver.observe(container)
    },
    
    // 更新图表数据
    updateChartData(data) {
      if (!this.chartInstance || !data || data.length === 0) return
      
      const option = this.chartInstance.getOption()
      option.series[0].data = data
      
      this.chartInstance.setOption(option, {
        notMerge: false,
        lazyUpdate: this.responsiveConfig.performance.lazyRender
      })
    },
    
    // 更新图表配置
    updateChartOptions(options) {
      if (!this.chartInstance) return
      
      this.chartInstance.setOption(options, {
        notMerge: true,
        lazyUpdate: true
      })
    },
    
    // 处理尺寸变化
    handleSizeChange({ width, height }) {
      if (!this.adapter) return
      
      // 更新断点
      const newBreakpoint = this.currentBreakpointName
      if (newBreakpoint !== this.currentBreakpoint) {
        this.currentBreakpoint = newBreakpoint
        this.$emit('breakpoint-change', newBreakpoint)
      }
      
      // 通知适配器处理尺寸变化
      this.adapter.handleContainerResize()
    },
    
    // 处理工具栏操作
    handleToolbarAction(action) {
      switch (action.name) {
        case 'refresh':
          this.refreshChart()
          break
          
        case 'fullscreen':
          this.toggleFullscreen()
          break
          
        case 'export':
          this.exportChart()
          break
          
        case 'reset':
          this.resetChart()
          break
      }
    },
    
    // 刷新图表
    refreshChart() {
      this.destroyChart()
      this.initChart()
    },
    
    // 切换全屏
    toggleFullscreen() {
      const container = this.$refs.chartContainer
      
      if (!document.fullscreenElement) {
        container.requestFullscreen().catch(err => {
          console.error('全屏失败:', err)
        })
      } else {
        document.exitFullscreen()
      }
    },
    
    // 导出图表
    exportChart() {
      if (!this.chartInstance) return
      
      const base64 = this.chartInstance.getDataURL({
        type: 'png',
        pixelRatio: 2,
        backgroundColor: '#fff'
      })
      
      const link = document.createElement('a')
      link.href = base64
      link.download = `chart-${Date.now()}.png`
      link.click()
    },
    
    // 重置图表
    resetChart() {
      if (this.chartInstance) {
        this.chartInstance.dispatchAction({
          type: 'restore'
        })
      }
    },
    
    // 重试
    retry() {
      this.error = false
      this.initChart()
    },
    
    // 销毁图表
    destroyChart() {
      if (this.containerObserver) {
        this.containerObserver.disconnect()
        this.containerObserver = null
      }
      
      if (this.adapter) {
        this.adapter.destroy()
        this.adapter = null
      }
      
      if (this.chartInstance) {
        this.chartInstance.dispose()
        this.chartInstance = null
      }
    },
    
    // 公共方法:获取图表实例
    getChartInstance() {
      return this.chartInstance
    },
    
    // 公共方法:手动刷新
    forceRefresh() {
      this.refreshChart()
    },
    
    // 公共方法:更新主题
    updateTheme(theme) {
      if (this.chartInstance) {
        echarts.dispose(this.chartInstance)
        this.chartInstance = echarts.init(
          this.$refs.chartElement,
          theme
        )
        this.refreshChart()
      }
    }
  }
}
</script>

<style scoped>
.responsive-chart-container {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 200px;
  overflow: hidden;
}

.chart-wrapper {
  position: relative;
  transition: all 0.3s ease;
}

.chart-inner {
  width: 100%;
  height: 100%;
}

.chart-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #f3f3f3;
  border-top: 3px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.chart-error {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 20px;
  text-align: center;
}

.error-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.error-icon {
  font-size: 40px;
  margin-bottom: 10px;
}

.chart-toolbar {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 5px;
  z-index: 5;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.responsive-chart-container:hover .chart-toolbar {
  opacity: 1;
}

.chart-toolbar button {
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 4px;
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  transition: all 0.2s ease;
}

.chart-toolbar button:hover {
  background: #f5f5f5;
  transform: translateY(-1px);
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
}

.chart-toolbar button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}
</style>

4. 使用示例

javascript 复制代码
<!-- 使用响应式图表组件 -->
<template>
  <div class="dashboard">
    <!-- 全屏大屏 -->
    <div class="fullscreen-chart">
      <ResponsiveEChart
        chart-type="pie"
        :chart-data="pieData"
        :design-size="{ width: 3840, height: 2160 }"
        :responsive-config="fullscreenConfig"
        @chart-initialized="onChartInit"
        @breakpoint-change="onBreakpointChange"
      />
    </div>
    
    <!-- 多列布局 -->
    <div class="chart-grid">
      <div class="chart-item">
        <ResponsiveEChart
          chart-type="line"
          :chart-data="lineData"
          :design-size="{ width: 1200, height: 600 }"
        />
      </div>
      
      <div class="chart-item">
        <ResponsiveEChart
          chart-type="bar"
          :chart-data="barData"
          :design-size="{ width: 1200, height: 600 }"
        />
      </div>
    </div>
  </div>
</template>

<script>
import ResponsiveEChart from '@/components/ResponsiveEChart.vue'

export default {
  components: {
    ResponsiveEChart
  },
  
  data() {
    return {
      // 全屏配置
      fullscreenConfig: {
        breakpoints: {
          xs: 640,
          sm: 1024,
          md: 1440,
          lg: 1920,
          xl: 2560
        },
        minSize: {
          width: 800,
          height: 600
        },
        performance: {
          throttleDelay: 200,
          debounceDelay: 300,
          lazyRender: true
        }
      },
      
      // 图表数据
      pieData: [...],
      lineData: [...],
      barData: [...]
    }
  },
  
  methods: {
    onChartInit(chartInstance) {
      console.log('图表初始化完成', chartInstance)
    },
    
    onBreakpointChange(breakpoint) {
      console.log('断点变化:', breakpoint)
      // 可以根据断点调整数据或配置
    }
  }
}
</script>

<style scoped>
.dashboard {
  width: 100%;
  height: 100vh;
  padding: 20px;
  box-sizing: border-box;
}

.fullscreen-chart {
  width: 100%;
  height: 60vh;
  margin-bottom: 20px;
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  overflow: hidden;
}

.chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  gap: 20px;
  height: 35vh;
}

.chart-item {
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  overflow: hidden;
}

@media (max-width: 768px) {
  .chart-grid {
    grid-template-columns: 1fr;
    height: auto;
  }
  
  .chart-item {
    height: 300px;
  }
}
</style>

三、适配方案总结

1. 核心技术点

  1. 双重监听机制:ResizeObserver + window.resize

  2. 防抖节流优化:避免频繁重绘

  3. 基准设计稿缩放:基于1920×1080等比缩放

  4. 智能断点系统:自适应不同屏幕尺寸

  5. 按需渲染策略:小屏简化,大屏增强

2. 性能优化措施

  • 懒渲染:非可视区域延迟加载

  • 动画控制:小屏禁用复杂动画

  • 批量更新:合并配置更新

  • 内存管理:及时清理资源

3. 用户体验保障

  • 加载状态:显示加载动画

  • 错误处理:友好的错误提示

  • 工具栏:提供常用操作

  • 全屏支持:更好的大屏体验

这个方案具有以下优势:

  1. 高度可配置:支持各种大屏场景

  2. 性能优秀:多重优化保证流畅性

  3. 易于维护:模块化设计,扩展性强

  4. 兼容性好:支持现代浏览器和IE11+

相关推荐
伍华聪1 小时前
介绍一个医疗物质数目清点系统的实现过程
前端
逆风局?1 小时前
后端Web实战(部门管理)——日志技术
java·前端
m0_740043731 小时前
父组件 子组件
javascript·vue.js·ecmascript
00后程序员张1 小时前
Fiddler调试工具全面解析 HTTPHTTPS抓包、代理设置与接口测试实战教程
前端·测试工具·ios·小程序·fiddler·uni-app·webview
爱吃无爪鱼1 小时前
05-JavaScript/TypeScript 项目结构完全解析
javascript·react.js·typescript·前端框架·npm·html·sass
k8s-open1 小时前
解决“Argument list too long”错误:curl参数过长的优雅处理方案
linux·前端·chrome·ssh
渴望成为python大神的前端小菜鸟1 小时前
JS宏任务 & 微任务 API 速查表
javascript