vue+openlayers+nodejs+postgis实现轨迹运动效果

 更新时间:2024年05月31日 10:29:58   作者:小白舒_SC  
使用postgres(postgis)数据库以及nodejs作为后台,vue和openlayers做前端,openlayers使用http请求通过nodejs从postgres数据库获取数据,这篇文章主要介绍了vue+openlayers+nodejs+postgis实现轨迹运动,需要的朋友可以参考下

概要

使用openlayers实现轨迹运动

整体架构流程

使用postgres(postgis)数据库以及nodejs作为后台,vue和openlayers做前端,openlayers使用http请求通过nodejs从postgres数据库获取数据。

技术名词解释

postgis:postgis是postgres的一个扩展,提供空间对象的相关操作。

技术细节

nodejs直连数据库,openlayers使用http服务通过nodejs转为数据库的查询语句。

实现思路如下:每条数据表示一条船,每个船的轨迹关键点在数据库存为MultiPointM的Geometry数据,其中M分量为时间戳,然后前端传入一个空间范围和时间戳,空间范围主要为了过滤范围外要素加速渲染,时间戳则用来查询所有船的轨迹点小于该时间戳的所有关键点,将其连成线,然后在时间戳所在的区间,使用线性插值插值出小船当前位置,线和插值出的点有相同的fid,在前端通过fid将线和插值点连接并显示,就是船的实时轨迹。

效果如下:

前端代码如下:

<template>
  <div id="map" class="map"></div>
</template>
<script>
import * as ol from 'ol';
import 'ol/ol.css';
import proj from 'ol/proj'
import { fromLonLat } from 'ol/proj';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';
import WKB from 'ol/format/WKB';
import Icon from 'ol/style/Icon';
import { transformExtent } from 'ol/proj';
export default {
  name: 'OpenLayersMap',
  data() {
    return {
      map: null,
      pointLayer: null,
      lineLayer: null,
      linesData: [],
      pointsData: [],
      iconImagePath: '../../board.png',
      lastPoint: {}
    };
  },
  mounted() {
    this.initializeMap();
    this.timeRange();
  },
  methods: {
    initializeMap() {
      this.map = new Map({
        target: 'map',
        layers: [
          new TileLayer({
            source: new XYZ({
              url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
            }),
          }),
        ],
        view: new View({
          center: [0, 0],
          zoom: 2,
        }),
      });
      this.lineLayer = new VectorLayer({
        source: new VectorSource(),
      });
      this.map.addLayer(this.lineLayer);
      this.pointLayer = new VectorLayer({
        source: new VectorSource(),
        style: new Style({
          image: new CircleStyle({
            radius: 6,
            fill: new Fill({ color: 'red' }),
            stroke: new Stroke({ color: 'white', width: 2 }),
          }),
        }),
      });
      this.map.addLayer(this.pointLayer);
    },
    timeRange() {
      fetch('http://localhost:4325/time-range')
        .then(response => response.json())
        .then(data => {
          const { minTime, maxTime } = data;
          console.log('Time Range:', minTime, maxTime);
          this.fetchDataInRange(minTime, maxTime);
        })
        .catch(error => console.error('Error fetching time range:', error));
    },
    fetchDataInRange(startTime, endTime) {
      let currentTime = startTime;
      const timerId = setInterval(() => {
        if (currentTime >= endTime) {
          this.fetchData(endTime);
          clearInterval(timerId); // Stop the timer when currentTime >= endTime
          return;
        }
        this.fetchData(currentTime);
        currentTime += 5; // Increment currentTime
        //console.log('Current Time:', currentTime);
      }, 200);
    },
    fetchData(currentTime) {
      // 获取地图视图
      const mapView = this.map.getView();
      // 获取地图视图的范围
      const extent = mapView.calculateExtent(this.map.getSize());
      // 将范围转换为EPSG:4326坐标系下的值 
      const bbox = transformExtent(extent, mapView.getProjection(), 'EPSG:4326');
      Promise.all([
        fetch(`http://localhost:4325/line-geometries?timestamp=${currentTime}&bbox=${bbox.join(',')}`).then(response => response.json()),
        fetch(`http://localhost:4325/points?timestamp=${currentTime}&bbox=${bbox.join(',')}`).then(response => response.json())
      ]).then(([linesData, pointsData]) => {
        this.linesData = linesData;
        this.pointsData = pointsData;
        this.processData();
      }).catch(error => console.error('Error fetching data:', error));
    },
    processData() {
      const lineSource = this.lineLayer.getSource();
      const pointSource = this.pointLayer.getSource();
      const existingLineFeatureIds = {};
      const existingPointFeatureIds = {};
      // 处理线要素数据
      //console.log('this.linesData', this.linesData)
      this.linesData.forEach(line => {
        const fid = line.fid;
        let feature = lineSource.getFeatureById(fid);
        if (feature) {
          // 如果已存在具有相同 fid 的要素,则更新要素信息
          // 更新要素信息
          existingLineFeatureIds[fid] = true;
        } else {
          // 否则创建新的要素并添加到图层中
          feature = new Feature({
            // 设置要素信息
          });
          lineSource.addFeature(feature);
          existingLineFeatureIds[fid] = true;
        }
      });
      // 处理点要素数据
      this.pointsData.forEach(point => {
        const fid = point.fid;
        let feature = pointSource.getFeatureById(fid);
        if (feature) {
          // 如果已存在具有相同 fid 的要素,则更新要素信息
          // 更新要素信息
          existingPointFeatureIds[fid] = true;
        } else {
          // 否则创建新的要素并添加到图层中
          feature = new Feature({
            // 设置要素信息
          });
          pointSource.addFeature(feature);
          existingPointFeatureIds[fid] = true;
        }
      });
      // 移除地图上已存在但未在当前数据中出现的线要素
      lineSource.getFeatures().forEach(feature => {
        const fid = feature.getId();
        if (!existingLineFeatureIds[fid]) {
          lineSource.removeFeature(feature);
        }
      });
      // 移除地图上已存在但未在当前数据中出现的点要素
      pointSource.getFeatures().forEach(feature => {
        const fid = feature.getId();
        if (!existingPointFeatureIds[fid]) {
          pointSource.removeFeature(feature);
        }
      });
      // Create a mapping of fid to points
      const pointsMap = {};
      this.pointsData.forEach(point => {
        if (!pointsMap[point.fid]) {
          pointsMap[point.fid] = [];
        }
        pointsMap[point.fid].push(point);
      });
      // Process lines and append points if they exist
      this.linesData.forEach(line => {
        const format = new WKB();
        const feature = format.readFeature(line.line_geom, {
          dataProjection: 'EPSG:4326',
          featureProjection: 'EPSG:3857'
        });
        const geometry = feature.getGeometry();
        if (geometry.getType() === 'LineString' && pointsMap[line.fid]) {
          const coordinates = geometry.getCoordinates();
          pointsMap[line.fid].forEach(point => {
            const coord = fromLonLat([point.interpolated_longitude, point.interpolated_latitude]);
            coordinates.push(coord);
          });
          geometry.setCoordinates(coordinates);
        }
        //feature.setId(line.fid);
        this.lineLayer.getSource().addFeature(feature);
      });
      // Log for debugging
      //console.log('Processed Lines:', this.lineLayer.getSource().getFeatures());
      //console.log('Processed Points:', this.pointLayer.getSource().getFeatures());
      this.processPointLayer();
    },
    processPointLayer() {
      const tempLastPoint = {};
      const lineFeatures = this.lineLayer.getSource().getFeatures();
      lineFeatures.forEach(lineFeature => {
        const lineGeometry = lineFeature.getGeometry();
        const lineCoordinates = lineGeometry.getCoordinates();
        const numCoordinates = lineCoordinates.length;
        //const fid = lineFeature.getId();
        //console.log('fid', fid);
        if (numCoordinates === 1) {
          const defaultAngle = 0;
          const lastPointCoords = lineCoordinates[0];
          tempLastPoint[fid] = lastPointCoords;
          const pointFeature = new Feature({
            geometry: new Point(lineCoordinates[0]),
          });
          //pointFeature.setId(fid);
          const iconStyle = this.createPointStyle(defaultAngle);
          pointFeature.setStyle(iconStyle);
          this.pointLayer.getSource().addFeature(pointFeature);
        } else if (numCoordinates > 1) {
          const lastPointCoords = lineCoordinates[numCoordinates - 1];
          //console.log('lastPointCoords', lastPointCoords);
          const penultimatePointCoords = lineCoordinates[numCoordinates - 2];
          const dx = lastPointCoords[0] - penultimatePointCoords[0];
          const dy = lastPointCoords[1] - penultimatePointCoords[1];
          const angle = Math.atan2(dy, dx);
          const pointFeature = new Feature({
            geometry: new Point(lastPointCoords),
          });
          //pointFeature.setId(fid);
          const iconStyle = this.createPointStyle(angle);
          pointFeature.setStyle(iconStyle);
          this.pointLayer.getSource().addFeature(pointFeature);
          //const tempLastPointCoords = this.lastPoint[fid];
          //console.log('tempLastPointCoords', tempLastPointCoords);
          //if (tempLastPointCoords) {
            //console.log('animate point', lineFeature.getId(), this.lastPoint[lineFeature.getId()], lastPointCoords);
            //this.animatePoint(pointFeature, tempLastPointCoords, lastPointCoords);
          //}
          //tempLastPoint[fid] = lastPointCoords;
        }
      });
      //this.lastPoint = tempLastPoint;
      //console.log('lastPoint', this.lastPoint); 
      //console.log('tempLastPoint', tempLastPoint);
    },
    animatePoint(feature, startCoords, endCoords) {
      const duration = 800; // 动画持续时间,单位毫秒
      const start = performance.now();
    //console.log('startCoords', startCoords);
      const animate = (timestamp) => {
        const elapsed = timestamp - start;
        const progress = Math.min(elapsed / duration, 1); // 进度百分比,范围从0到1
        // 线性插值计算当前位置
        const currentCoords = [
          startCoords[0] + (endCoords[0] - startCoords[0]) * progress,
          startCoords[1] + (endCoords[1] - startCoords[1]) * progress,
        ];
        feature.setGeometry(new Point(currentCoords));
        if (progress < 1) {
          requestAnimationFrame(animate);
        }
      };
      requestAnimationFrame(animate);
    },
    createPointStyle(angle) {
      // 根据朝向创建点的样式
      return new Style({
        image: new Icon({
          src: require('@/assets/board.png'),
          scale: 0.1,
          rotation: -angle + (180 * Math.PI / 180), // 设置点的朝向
          anchor: [0.5, 0.7], // 设置锚点位置
        }),
      });
    }
  },
};
</script>
<style scoped>
.map {
  width: 100%;
  height: 800px;
}
</style>

服务器代码如下:

 1、数据库相关:

// database.js
const { Client } = require('pg');
const axios = require('axios');
const fs = require('fs').promises;
const moment = require('moment-timezone');
// 配置数据库连接
const client = new Client({
    user: 'postgres',
    host: 'loaclhost',
    database: 'postgres',
    password: 'root',
    port: 4321, // 默认PostgreSQL端口
});
async function createTable() {
    const createTableQuery = `
        CREATE TABLE IF NOT EXISTS track_board_test (
            fid BIGINT PRIMARY KEY,
            id VARCHAR(255),
            name VARCHAR(255),
            mmsi VARCHAR(255),
            geom GEOMETRY(MultiPointM)
        );
        CREATE INDEX IF NOT EXISTS geom_index ON track_board_test USING GIST (geom);
    `;
    try {
        await client.query(createTableQuery);
        console.log('Table created successfully');
    } catch (err) {
        console.error('Error creating table:', err.stack);
    }
}
async function insertDataFromFile(filePath, isHttp) {
    try {
        let data;
        if (isHttp) {
            const response = await axios.get(filePath);
            data = response.data;
        } else {
            const rawData = await fs.readFile(filePath);
            data = JSON.parse(rawData);
        }
        for (const item of data.data) {
            const { id, mmsi, name, hisRecord } = item;
            let fid;
            if (id.startsWith("radar")) {
                fid = parseInt(id.substring("radar".length));
            } else {
                fid = parseInt(id);
            }
            const points = hisRecord.map(record => {
                const utcTime = moment.tz(record.updateTime, "Asia/Shanghai").utc().format('YYYY-MM-DD HH:mm:ss');
                return `ST_SetSRID(ST_MakePointM(${record.longitude}, ${record.latitude}, EXTRACT(EPOCH FROM TIMESTAMP '${utcTime}')), 4326)`;
            }).join(', ');
            const geom = `ST_Collect(ARRAY[${points}])`;
            const query = `
                INSERT INTO track_board_test (id, name, mmsi, geom, fid)
                VALUES ($1, $2, $3, ${geom}, $4)
                ON CONFLICT (fid) DO UPDATE
                SET id = EXCLUDED.id, name = EXCLUDED.name, mmsi = EXCLUDED.mmsi, geom = EXCLUDED.geom, fid = EXCLUDED.fid;
            `;
            await client.query(query, [id, name, mmsi, fid]);
        }
        console.log('数据插入成功');
    } catch (err) {
        console.error('插入数据时发生错误:', err);
    }
}
async function insertRandomData() {
    const insertRandomDataQuery = `
    DO $$
    DECLARE
        i INT;
    BEGIN
        FOR i IN 10010000..10015000 LOOP
            EXECUTE format(
                'INSERT INTO track_board_test (id, geom, fid) 
                VALUES (%L, 
                    (SELECT ST_Collect(
                        ARRAY(
                            WITH RECURSIVE points AS (
                                SELECT
                                    random() * 360 - 180 AS lon,
                                    random() * 180 - 90 AS lat,
                                    CAST(1716186468 + random() * 1000 AS INT) AS m,
                                    1 AS iteration,
                                    CEIL(random() * 99 + 1) AS max_iterations -- 随机生成1到100之间的点数
                                UNION ALL
                                SELECT
                                    lon + (0.01 + random() * 0.09) * (CASE WHEN random() < 0.5 THEN 1 ELSE -1 END) AS lon,
                                    lat + (0.01 + random() * 0.09) * (CASE WHEN random() < 0.5 THEN 1 ELSE -1 END) AS lat,
                                    CAST(m + random() * 400 AS INT) AS m,
                                    iteration + 1,
                                    max_iterations
                                FROM points
                                WHERE iteration < max_iterations
                            )
                            SELECT ST_SetSRID(ST_MakePointM(lon, lat, m), 4326)
                            FROM points
                        )
                    )),
                    %L
                ) 
                ON CONFLICT (fid) DO NOTHING',
                'radar_' || i,
                i
            );
        END LOOP;
    END $$;    
    `;
    try {
        await client.query(insertRandomDataQuery);
        console.log('Random data insert successfully');
    } catch (err) {
        console.error('Error inserting random data:', err.stack);
    }
}
async function getAllData() {
    try {
        const query = `
            SELECT fid, id, name, mmsi, ST_X(dp.geom) AS Lng, ST_Y(dp.geom) AS Lat, ST_M(dp.geom) AS time
            FROM track_board_test,
            LATERAL ST_DumpPoints(geom) AS dp;
        `;
        const result = await client.query(query);
        return result.rows;
    } catch (err) {
        console.error('Error fetching data:', err.stack);
        return [];
    }
}
async function getTimeRange() {
    try {
        const query = `
            SELECT 
            MAX(max_time) AS max_time, 
            MIN(min_time) AS min_time
        FROM (
            SELECT 
                (SELECT MAX(ST_M(dp.geom)) FROM LATERAL ST_DumpPoints(track_board_test.geom) AS dp) AS max_time,
                (SELECT MIN(ST_M(dp.geom)) FROM LATERAL ST_DumpPoints(track_board_test.geom) AS dp) AS min_time
            FROM 
                track_board_test
        ) AS subquery;    
        `;
        const result = await client.query(query);
        const { max_time, min_time } = result.rows[0];
        return { minTime: min_time, maxTime: max_time };
    } catch (err) {
        console.error('Error executing query', err.stack);
        throw err;
    }
}
async function getPointsByTimestamp(timestamp, bbox) {
    try {
        const query = `
        WITH extracted_points AS (
            SELECT 
                tbt.fid, 
                (dp).geom AS point,
                ST_M((dp).geom) AS m_value
            FROM 
                track_board_test tbt
                CROSS JOIN LATERAL ST_DumpPoints(tbt.geom) AS dp
            WHERE
                ST_Intersects(tbt.geom, ST_MakeEnvelope($1, $2, $3, $4, 4326)) -- Add bbox filter
            ORDER BY fid
        ),
        min_max_times AS (
            SELECT 
                fid,
                MAX(CASE WHEN m_value <= $5 THEN m_value END) AS min_time,
                MIN(CASE WHEN m_value > $5 THEN m_value END) AS max_time
            FROM 
                extracted_points
            GROUP BY 
                fid
        ),
        min_points AS (
            SELECT 
                ep.fid,
                ep.m_value AS min_time,
                ep.point AS min_point
            FROM 
                extracted_points ep
            JOIN min_max_times mmt ON ep.fid = mmt.fid AND ep.m_value = mmt.min_time
        ),
        max_points AS (
            SELECT 
                ep.fid,
                ep.m_value AS max_time,
                ep.point AS max_point
            FROM 
                extracted_points ep
            JOIN min_max_times mmt ON ep.fid = mmt.fid AND ep.m_value = mmt.max_time
        )
        SELECT 
            mmt.fid,
            ST_X(ST_LineInterpolatePoint(ST_MakeLine(mp.min_point, mx.max_point), ($5 - mmt.min_time) / (mmt.max_time - mmt.min_time))) AS interpolated_longitude,
            ST_Y(ST_LineInterpolatePoint(ST_MakeLine(mp.min_point, mx.max_point), ($5 - mmt.min_time) / (mmt.max_time - mmt.min_time))) AS interpolated_latitude
        FROM 
            min_max_times mmt
            JOIN min_points mp ON mmt.fid = mp.fid
            JOIN max_points mx ON mmt.fid = mx.fid;
        `;
        const result = await client.query(query, [...bbox, timestamp]);
        return result.rows;
    } catch (err) {
        console.error('Error fetching interpolated points:', err.stack);
        return [];
    }
}
async function getLineGeometries(timestamp, bbox) {
    const query = `
        WITH extracted_points AS (
            SELECT
                fid,
                (ST_DumpPoints(geom)).geom AS point
            FROM track_board_test
            WHERE
                ST_Intersects(geom, ST_MakeEnvelope($1, $2, $3, $4, 4326)) -- Add bbox filter
        ),
        filtered_points AS (
            SELECT
                fid,
                point,
                ST_M(point) AS m_value
            FROM extracted_points
            WHERE ST_M(point) <= $5
        ),
        sorted_points AS (
            SELECT
                fid,
                point
            FROM filtered_points
            ORDER BY fid, m_value
        )
        SELECT
            fid,
            ST_MakeLine(point) AS line_geom
        FROM sorted_points
        GROUP BY fid;
    `;
    const result = await client.query(query, [...bbox, timestamp]);
    return result.rows;
}
module.exports = {
    client,
    createTable,
    insertDataFromFile,
    insertRandomData,
    getAllData,
    getTimeRange,
    getPointsByTimestamp,
    getLineGeometries
};

http接口相关:

const express = require('express');
const cors = require('cors');
const {
    client,
    createTable,
    insertDataFromFile,
    insertRandomData,
    getAllData,
    getTimeRange,
    getPointsByTimestamp,
    getLineGeometries } = require('./database');
const app = express();
app.use(cors());
const port = 4325;
client.connect()
    .then(() => console.log('Connected to the database'))
    .catch(err => console.error('Connection error', err.stack));
createTable();
const filePath = './test.json'; // 替换为你的文件路径
insertDataFromFile(filePath, false);
insertRandomData();
app.get('/all-data', async (req, res) => {
    try {
        const data = await getAllData();
        res.json(data);
    } catch (err) {
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
// 创建一个API端点
app.get('/time-range', async (req, res) => {
    try {
        const { minTime, maxTime } = await getTimeRange();
        res.json({ minTime, maxTime });
    } catch (err) {
        console.error('Error fetching time range:', err.stack);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
app.get('/points', async (req, res) => {
    const timestamp = req.query.timestamp;
    const bbox = req.query.bbox.split(',').map(parseFloat); // 解析 bbox 参数为数组
    if (!timestamp) {
      return res.status(400).json({ error: 'Timestamp is required' });
    }
    try {
        const points = await getPointsByTimestamp(timestamp, bbox); // 将 bbox 参数传递给函数
        res.json(points);
    } catch (err) {
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
app.get('/line-geometries', async (req, res) => {
    const timestamp = req.query.timestamp;
    const bbox = req.query.bbox.split(',').map(parseFloat); // 解析 bbox 参数为数组
    if (!timestamp) {
        return res.status(400).json({ error: 'Timestamp is required' });
    }
    try {
        const lineGeometries = await getLineGeometries(timestamp, bbox); // 将 bbox 参数传递给函数
        res.json(lineGeometries);
    } catch (err) {
        console.error('Error fetching line geometries:', err.stack);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
// 启动服务器
app.listen(port, () => {
    console.log(`Server is running on http://localhost:${port}`);
});

小结

当显示全球范围性能会有明显卡顿,可能需要改进算法。

到此这篇关于vue+openlayers+nodejs+postgis实现轨迹运动的文章就介绍到这了,更多相关vue轨迹运动内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue+element下拉列表默认值问题

    vue+element下拉列表默认值问题

    这篇文章主要介绍了vue+element下拉列表默认值问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • Vue+ElementUI 中级联选择器Bug问题的解决

    Vue+ElementUI 中级联选择器Bug问题的解决

    这篇文章主要介绍了Vue+ElementUI 中级联选择器Bug问题的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • Vue3全局属性app.config.globalProperties的实现

    Vue3全局属性app.config.globalProperties的实现

    Vue3中的app.config.globalProperties是一个强大的全局配置功能,允许我们在应用级别设置和访问属性,本文主要介绍了Vue3全局属性app.config.globalProperties的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • vue实现下拉菜单树

    vue实现下拉菜单树

    这篇文章主要为大家详细介绍了vue实现下拉菜单树,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10
  • 使用vue-router切换组件时使组件不销毁问题

    使用vue-router切换组件时使组件不销毁问题

    这篇文章主要介绍了使用vue-router切换组件时使组件不销毁问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • vue单页面打包文件大?首次加载慢?nginx带你飞,从7.5M到1.3M蜕变过程(推荐)

    vue单页面打包文件大?首次加载慢?nginx带你飞,从7.5M到1.3M蜕变过程(推荐)

    这篇文章主要介绍了vue单页面打包文件大?首次加载慢?nginx带你飞,从7.5M到1.3M蜕变过程,需要的朋友可以参考下
    2018-01-01
  • vue3+vite动态加载路由,本地路由和线上路由匹配方式

    vue3+vite动态加载路由,本地路由和线上路由匹配方式

    这篇文章主要介绍了vue3+vite动态加载路由,本地路由和线上路由匹配方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • element-ui 限制日期选择的方法(datepicker)

    element-ui 限制日期选择的方法(datepicker)

    本篇文章主要介绍了element-ui 限制日期选择的方法(datepicker),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • vite+vue3项目中svg图标组件封装的过程详解

    vite+vue3项目中svg图标组件封装的过程详解

    这篇文章主要介绍了vite+vue3项目中svg图标组件封装的过程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-03-03
  • Vue组件间的通信方式详析

    Vue组件间的通信方式详析

    本文介绍Vue组件间通信方式,Vue组件间通信一直是个重要的话题,虽然官方推出的Vuex状态管理方案可以很好的解决组件之间的通信问题,但是在组件库内部使用Vuex往往会比较重,本文将系统的罗列出几种不使用Vuex,比较实用的组件间的通信方式,希望能帮助到大家
    2022-09-09

最新评论