东海县护送服务网

Vue进行大屏开发的全流程及技术细节详解

2026-03-31 10:56:04 浏览次数:1
详细信息

Vue大屏开发全流程与技术细节详解

一、整体开发流程

1. 前期准备阶段

// 项目初始化
vue create large-screen-project
// 选择配置:Vue 3 + TypeScript + Router + Pinia + SCSS

2. 架构设计

项目结构:
src/
├── components/         # 公共组件
│   ├── charts/        # 图表组件
│   ├── widgets/       # 小部件
│   └── layout/        # 布局组件
├── views/             # 页面视图
├── store/             # 状态管理
├── utils/             # 工具函数
│   ├── echarts-config/ # ECharts配置
│   ├── data-mock/     # 模拟数据
│   └── screen-adapt/  # 屏幕适配
├── assets/            # 静态资源
│   ├── styles/        # 样式文件
│   └── images/        # 图片资源
└── types/             # TypeScript类型定义

二、核心技术实现

1. 屏幕适配方案

<!-- 基于CSS变量和scale的适配方案 -->
<template>
  <div class="screen-container" ref="screenRef">
    <div class="screen-content" :style="transformStyle">
      <!-- 大屏内容 -->
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'

const screenRef = ref<HTMLElement>()
const scale = ref(1)

// 设计稿尺寸
const designWidth = 3840
const designHeight = 2160

const transformStyle = computed(() => {
  return {
    transform: `scale(${scale.value})`,
    transformOrigin: 'left top'
  }
})

// 适配函数
const updateScale = () => {
  if (!screenRef.value) return

  const { clientWidth, clientHeight } = screenRef.value
  const widthScale = clientWidth / designWidth
  const heightScale = clientHeight / designHeight

  // 选择较小的缩放比例保证完全显示
  scale.value = Math.min(widthScale, heightScale)
}

onMounted(() => {
  updateScale()
  window.addEventListener('resize', updateScale)
})

onUnmounted(() => {
  window.removeEventListener('resize', updateScale)
})
</script>

<style scoped>
.screen-container {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  position: relative;
}

.screen-content {
  width: 3840px;
  height: 2160px;
  transition: transform 0.3s;
}
</style>

2. 响应式布局组件

<!-- GridLayout组件 -->
<template>
  <div class="grid-layout" :style="gridStyle">
    <div 
      v-for="item in layoutItems" 
      :key="item.id"
      class="grid-item"
      :style="getItemStyle(item)"
    >
      <component :is="item.component" :data="item.data" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

interface LayoutItem {
  id: string
  x: number
  y: number
  w: number
  h: number
  component: any
  data?: any
}

const props = defineProps<{
  cols: number
  rowHeight: number
  layout: LayoutItem[]
}>()

const gridStyle = computed(() => ({
  display: 'grid',
  gridTemplateColumns: `repeat(${props.cols}, 1fr)`,
  gridTemplateRows: `repeat(auto-fit, ${props.rowHeight}px)`,
  gap: '20px'
}))

const getItemStyle = (item: LayoutItem) => ({
  gridColumn: `span ${item.w}`,
  gridRow: `span ${item.h}`,
  gridArea: `${item.y} / ${item.x} / ${item.y + item.h} / ${item.x + item.w}`
})
</script>

三、图表集成与优化

1. ECharts高级封装

// src/components/charts/BaseChart.vue
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as echarts from 'echarts'
import { EChartsType } from 'echarts'

interface Props {
  option: echarts.EChartsOption
  theme?: string
  autoResize?: boolean
  loading?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  autoResize: true,
  loading: false
})

const chartRef = ref<HTMLDivElement>()
let chartInstance: EChartsType | null = null

// 初始化图表
const initChart = () => {
  if (!chartRef.value) return

  chartInstance = echarts.init(chartRef.value, props.theme)
  chartInstance.setOption(props.option)

  // 性能优化:防抖重绘
  let resizeTimer: NodeJS.Timeout
  if (props.autoResize) {
    const resizeHandler = () => {
      clearTimeout(resizeTimer)
      resizeTimer = setTimeout(() => {
        chartInstance?.resize()
      }, 300)
    }
    window.addEventListener('resize', resizeHandler)

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

// 数据更新监听
watch(() => props.option, (newOption) => {
  if (chartInstance) {
    // 使用notMerge确保完全更新
    chartInstance.setOption(newOption, true)
  }
}, { deep: true })

// 加载状态
watch(() => props.loading, (loading) => {
  if (chartInstance) {
    loading ? 
      chartInstance.showLoading() : 
      chartInstance.hideLoading()
  }
})

onMounted(() => {
  initChart()
})

onUnmounted(() => {
  if (chartInstance) {
    chartInstance.dispose()
    chartInstance = null
  }
})

2. WebSocket实时数据更新

// src/utils/websocket-manager.ts
class WebSocketManager {
  private ws: WebSocket | null = null
  private reconnectTimer: NodeJS.Timeout | null = null
  private maxReconnectAttempts = 5
  private reconnectAttempts = 0

  constructor(private url: string) {}

  connect(onMessage: (data: any) => void) {
    this.ws = new WebSocket(this.url)

    this.ws.onopen = () => {
      console.log('WebSocket连接成功')
      this.reconnectAttempts = 0
    }

    this.ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data)
        onMessage(data)
      } catch (error) {
        console.error('数据解析失败:', error)
      }
    }

    this.ws.onclose = () => {
      console.log('WebSocket连接关闭')
      this.reconnect()
    }

    this.ws.onerror = (error) => {
      console.error('WebSocket错误:', error)
    }
  }

  private reconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('达到最大重连次数')
      return
    }

    this.reconnectTimer = setTimeout(() => {
      this.reconnectAttempts++
      console.log(`第${this.reconnectAttempts}次重连...`)
      this.connect(this.url)
    }, 3000 * this.reconnectAttempts)
  }

  send(data: any) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data))
    }
  }

  disconnect() {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
    }
    this.ws?.close()
  }
}

四、性能优化策略

1. 虚拟滚动列表

<!-- 大数据列表展示 -->
<template>
  <div class="virtual-list" :style="containerStyle" @scroll="handleScroll">
    <div class="list-phantom" :style="phantomStyle"></div>
    <div class="list-content" :style="contentStyle">
      <div 
        v-for="item in visibleData" 
        :key="item.id"
        class="list-item"
        :style="getItemStyle(item)"
      >
        {{ item.content }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'

interface ListItem {
  id: number
  content: string
}

const props = defineProps<{
  data: ListItem[]
  itemHeight: number
  bufferSize?: number
}>()

const containerHeight = 600
const startIndex = ref(0)
const scrollTop = ref(0)

// 可见区域计算
const visibleCount = computed(() => 
  Math.ceil(containerHeight / props.itemHeight)
)

const visibleData = computed(() => {
  const start = Math.max(0, startIndex.value - (props.bufferSize || 5))
  const end = Math.min(
    props.data.length, 
    startIndex.value + visibleCount.value + (props.bufferSize || 5)
  )
  return props.data.slice(start, end)
})

const phantomStyle = computed(() => ({
  height: `${props.data.length * props.itemHeight}px`
}))

const contentStyle = computed(() => ({
  transform: `translateY(${startIndex.value * props.itemHeight}px)`
}))

const handleScroll = (e: Event) => {
  const target = e.target as HTMLElement
  scrollTop.value = target.scrollTop
  startIndex.value = Math.floor(scrollTop.value / props.itemHeight)
}
</script>

2. 图表懒加载

<!-- 按需加载图表组件 -->
<template>
  <div class="chart-container" ref="containerRef">
    <BaseChart 
      v-if="isVisible" 
      :option="chartOption" 
      :theme="theme"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'

const containerRef = ref<HTMLElement>()
const isVisible = ref(false)

const { stop } = useIntersectionObserver(
  containerRef,
  ([{ isIntersecting }]) => {
    if (isIntersecting) {
      isVisible.value = true
      stop() // 加载后停止观察
    }
  },
  {
    threshold: 0.1, // 10%可见时触发
    rootMargin: '50px' // 提前50px加载
  }
)
</script>

五、状态管理设计

// src/store/dashboard.ts
import { defineStore } from 'pinia'
import { reactive, ref } from 'vue'
import { mockDataService } from '@/services/mockData'

export interface DashboardState {
  realTimeData: Record<string, any>
  historicalData: any[]
  loading: boolean
  lastUpdate: Date | null
}

export const useDashboardStore = defineStore('dashboard', () => {
  const state = reactive<DashboardState>({
    realTimeData: {},
    historicalData: [],
    loading: false,
    lastUpdate: null
  })

  // 实时数据更新
  const updateRealTimeData = (data: any) => {
    Object.assign(state.realTimeData, data)
    state.lastUpdate = new Date()
  }

  // 批量更新
  const batchUpdate = async (updates: any[]) => {
    state.loading = true
    try {
      const promises = updates.map(update => 
        mockDataService.fetchData(update)
      )
      const results = await Promise.all(promises)
      results.forEach(result => {
        updateRealTimeData(result)
      })
    } finally {
      state.loading = false
    }
  }

  // 数据缓存
  const cachedData = ref(new Map())

  const getCachedData = (key: string) => {
    const item = cachedData.value.get(key)
    if (item && Date.now() - item.timestamp < 60000) {
      return item.data // 1分钟内有效
    }
    return null
  }

  const setCachedData = (key: string, data: any) => {
    cachedData.value.set(key, {
      data,
      timestamp: Date.now()
    })
  }

  return {
    state,
    updateRealTimeData,
    batchUpdate,
    getCachedData,
    setCachedData
  }
})

六、监控与调试

1. 性能监控

// src/utils/performance-monitor.js
export class PerformanceMonitor {
  constructor() {
    this.metrics = new Map()
  }

  startMeasure(name) {
    const startTime = performance.now()
    this.metrics.set(name, { startTime })
  }

  endMeasure(name) {
    const metric = this.metrics.get(name)
    if (metric) {
      const duration = performance.now() - metric.startTime
      console.log(`[性能监控] ${name}: ${duration.toFixed(2)}ms`)

      // 发送到监控平台
      this.reportMetric(name, duration)
    }
  }

  reportMetric(name, duration) {
    // 可以发送到监控系统如Sentry、自建监控
    if (window.gtag) {
      gtag('event', 'performance_metric', {
        event_category: 'Performance',
        event_label: name,
        value: Math.round(duration)
      })
    }
  }

  // 监控内存使用
  monitorMemory() {
    if (performance.memory) {
      setInterval(() => {
        const memory = performance.memory
        console.log(`内存使用: ${memory.usedJSHeapSize / 1024 / 1024} MB`)
      }, 60000)
    }
  }
}

七、部署优化

1. 构建配置

// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const CompressionWebpackPlugin = require('compression-webpack-plugin')

module.exports = defineConfig({
  transpileDependencies: true,

  // 生产环境配置
  chainWebpack: config => {
    // 代码分割
    config.optimization.splitChunks({
      chunks: 'all',
      cacheGroups: {
        libs: {
          name: 'chunk-libs',
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: 'initial'
        },
        echarts: {
          name: 'chunk-echarts',
          test: /[\\/]node_modules[\\/]echarts[\\/]/,
          priority: 20
        }
      }
    })

    // 图片压缩
    config.module
      .rule('images')
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({
        mozjpeg: { progressive: true, quality: 65 },
        optipng: { enabled: false },
        pngquant: { quality: [0.65, 0.9], speed: 4 },
        gifsicle: { interlaced: false }
      })
  },

  configureWebpack: {
    plugins: [
      // Gzip压缩
      new CompressionWebpackPlugin({
        test: /\.(js|css)$/,
        threshold: 10240,
        minRatio: 0.8
      })
    ],

    // 外部依赖
    externals: {
      'vue': 'Vue',
      'echarts': 'echarts'
    }
  },

  // 资源优化
  pluginOptions: {
    // 预加载关键资源
    preload: {
      rel: 'preload',
      include: 'initial',
      fileBlacklist: [/\.map$/, /hot-update\.js$/]
    }
  }
})

八、最佳实践总结

1. 开发规范

2. 性能要点

3. 维护建议

4. 扩展性考虑

通过以上完整的流程和技术细节,可以构建出高性能、可维护、体验优秀的大屏数据可视化应用。

相关推荐