找回密码
 立即注册
首页 业界区 业界 vue3实现模拟地图上,站点名称按需显示的功能 ...

vue3实现模拟地图上,站点名称按需显示的功能

颓哀 2025-6-6 15:25:33
很久很久没有更新博客了,因为实在是太忙了,每天都有公司的事情忙不完.......
最近在做车辆模拟地图,在实现控制站点名称按需显示时,折腾了好一段时间,特此记录一下。最终界面如下图所示:
1.png

2.png

站点显示需求:首末站必须显示,从第一个站开始,如果站点名称能显示下,则显示,如果站点名称会重叠则隐藏,以此类推。当界面宽度变化时,车辆模拟地图自动变化,保证站点名称能够最大限度的显示。
最开始我用的比例换算法,算法复杂度是O,结果总是不准。尽管一开始我就觉得算法的复杂度应该是O2。我之前却一直想着只遍历一次就算出来,我也尝试过把需求描述得很详细去问chatgpt,可是它就像个傻子一样输出各种算法错误代码。
需要注意的地方:由于站点的名称内容是千奇百怪的,可以有空格,各种特殊图标,所以站点文字的长度计算是一个问题,这里是通过canvas来计算的。还有,这里我添加了一个限制,站点文字内容我最大显示120px,超出隐藏并显示省略号,站点名称上添加了title显示全称。
  1. /**
  2. * 获取站点名称
  3. * @param item
  4. * @param showFullName 是否总是显示站点全名
  5. */
  6. /** */
  7. export const getSiteName = (item: any,showFullName?:boolean=false) => {
  8.   const { siteSign } = simulatedMapConf.value;
  9.   let name = '';
  10.   if (siteSign == 'firstWord') {
  11.     name = getSubStrByPreNum(item.stationName);
  12.   } else if (siteSign == 'order') {
  13.     name = item.stationSeq + '';
  14.   } else {
  15.     name=showFullName?item.stationName:(item.show? item.stationName:''); //show控制站点名称是否显示
  16.   }
  17.   return name || '';
  18. }
  19. /**
  20. * 获取站点名称宽度
  21. * @param item 站点对象
  22. * @param showFullName 是否总是显示站点全名
  23. * @returns
  24. */
  25. export const getSiteNameWidth = (item: any,showFullName?:boolean=false) => {
  26.   const name =showFullName?item.stationName: getSiteName(item,showFullName);
  27.   const width= calculateStringWidth(name);
  28.   return width>siteMaxWidth?siteMaxWidth:width;
  29. }
  30. /**
  31. * 根据字符串计算出界面渲染的宽度
  32. * @param str
  33. * @returns
  34. */
  35. function calculateStringWidth(str:string) {
  36.   // 创建一个虚拟的 <canvas> 元素
  37.   const canvas = document.createElement('canvas');
  38.   const ctx = canvas.getContext('2d');
  39.   // 设置字体样式
  40.   ctx.font = '12px sans-serif';
  41.   // 使用 canvas 的 measureText 方法测量字符串的宽度
  42.   const width = ctx.measureText(str).width;
  43.   // 返回计算出的宽度
  44.   return width;
  45. }
复制代码
最核心的算法:
  1.     //计算上行站点,控制站点是否显示在模拟地图上
  2.     function calcSite(station: any, lineWidth: number) {
  3.         if (station.length < 1) return [];
  4.         station.forEach((f: any, index) => {
  5.             f.show=false;
  6.         });
  7.         const lastSiteLength = getSiteNameWidth(station[station.length - 1], true) / 2;//站点文字半宽
  8.         let lastLeft = getSiteCx(station[station.length - 1], station.length - 1);//最后一个站点left
  9.         lastLeft = toDecimal(lastLeft - lastSiteLength);
  10.         station.forEach((f: any, index) => {
  11.             let siteLength = getSiteNameWidth(f, true);//站点文字宽度
  12.             let bigHalf = siteLength / 2;//获取当前的半宽
  13.             f.left = getSiteCx(f, index);
  14.             if (index == 0 || index == station.length - 1) { //第一项和最后一项必须显示
  15.                 f.show = true;
  16.             } else {
  17.                 const preShowIndex = getLastTrueIndex(station); //获取前面最近一个显示站点的索引
  18.                 const preEndLeft = toDecimal(station[preShowIndex].left + getSiteNameWidth(station[preShowIndex], true) / 2);//上一项显示的站点名称结束left位置
  19.                 f.show = toDecimal(f.left - bigHalf) >=preEndLeft && preEndLeft < lastLeft; //如果上一个显示站点文字的结尾位置 小于等于 当前站点文字的开始位置  并且小于最后一个站点文字的开始位置<br>                if (f.show && toDecimal(f.left + bigHalf) > lastLeft) {<br>                 f.show = false;<br><em id="__mceDel"><em id="__mceDel">                }<br></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel">           }</em></em></em></em>
复制代码
  1. <em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel">        })
  2.     }</em></em></em></em>
复制代码
获取前面最近一个显示站点的索引:
  1.     //获取list集合中最后一项show的index位置
  2.     function getLastTrueIndex(dataList: any) {
  3.         // 从数组末尾第2项开始向前遍历
  4.         for (let i = dataList.length - 2; i >= 0; i--) {
  5.             if (dataList[i].show === true) {
  6.                 return i;  // 返回第一个找到的最后一个为true的索引
  7.             }
  8.         }
  9.         return -1;  // 如果未找到符合条件的对象,返回-1
  10.     }
复制代码
下行站点的计算有些差别,因为下行站点是从右至左,所以left基本上是反着的:
  1.     //计算下行站点,控制站点是否显示在模拟地图上 getDownSiteCx
  2.     function calcDownSite(station: any, lineWidth: number) {
  3.         if (station.length < 1) return [];
  4.         station.forEach((f: any, index) => {
  5.             f.show=false;
  6.         });
  7.         const lastSiteLength = getSiteNameWidth(station[station.length - 1], true) / 2;//站点文字半宽
  8.         let lastLeft = getDownSiteCx(station[station.length - 1], station.length - 1);//最后一个站点left
  9.         lastLeft = toDecimal(lastLeft + lastSiteLength);
  10.         station.forEach((f: any, index) => {
  11.             let siteLength = getSiteNameWidth(f, true);//站点文字宽度
  12.             let bigHalf = siteLength / 2;//获取当前的半宽
  13.             f.left = getDownSiteCx(f, index);
  14.             if (index == 0 || index == station.length - 1) { //第一项和最后一项必须显示
  15.                 f.show = true;
  16.             } else {
  17.                 const preShowIndex = getLastTrueIndex(station); //获取前面最近一个显示站点的索引
  18.                 const preEndLeft = toDecimal(station[preShowIndex].left - getSiteNameWidth(station[preShowIndex], true) / 2);//上一项显示站的的结束left位置
  19.                 f.show = toDecimal(f.left + bigHalf) <=preEndLeft && preEndLeft > lastLeft;
  20.                 if (f.show && toDecimal(f.left - bigHalf) < lastLeft) {
  21.                     f.show = false;
  22.                 }
  23.             }
  24.         })
  25.     }
复制代码
另外获取站点中心点位置的方法
  1.     //获取上行站点水平x位置
  2.     const getSiteCx = (item: any, index: number) => {
  3.         return startleft.value + dLayout.lineWidth * index;
  4.     }
  5.     //获取下行站点水平x位置
  6.     const getDownSiteCx = (item: any, index: number) => {
  7.         return downStartleft.value - layout.endLine - dLayout.downLineWidth * index;
  8.     }
复制代码
说明:站点的布局采用css绝对定位。第一个版本这块我是采用的svg画的,后来发现扩展起来越来越麻烦,周末就在家花了半天时间全部改造为html实现了。
我最开始的有问题代码是上下行站点共用的,最大的问题是会出现跳站点显示的情况,代码如下的:
  1.     //计算站点,控制站点是否显示在模拟地图上
  2.     function calcSite(station: any, lineWidth: number) {
  3.         let availableWidth = (station.length - 1) * lineWidth; //总长度
  4.         //记录显示站点的长度
  5.         let totalLength = 0;
  6.         station.forEach((f: any, index) => {
  7.             let siteLength = getSiteNameWidth(f, true);
  8.             let bigHalf =siteLength / 2;//获取比较大的半宽
  9.             let bigHalfPre = 0;
  10.             //计算上一项的文字半宽
  11.             if (index >= 1) {
  12.                 let siteLengthPre = getSiteNameWidth(station[index - 1], true);
  13.                 bigHalfPre =siteLengthPre / 2;
  14.             }
  15.             f.left = toDecimal(lineWidth * index);
  16.             f.show =index==0?true: f.left >=toDecimal(totalLength);
  17.             if(index >= 1&&station[index-1].show&&bigHalf+bigHalfPre>lineWidth){
  18.                 f.show=false;
  19.             }
  20.             if (f.show) {
  21.                 let times = getDivisor(siteLength, lineWidth);
  22.                 totalLength += times * lineWidth;
  23.             }
  24.         })
  25.     }
复制代码
  1. /**
  2. * 两个数相除有余数时结果加1
  3. * @param all 被除数 站点宽度
  4. * @param num 除数  线宽
  5. * @returns
  6. */
复制代码
export const getDivisor=( all:number,item:number)=>{    if(all

相关推荐

您需要登录后才可以回帖 登录 | 立即注册