前端桥接模式对接腾讯LBS和高德开放平台(以vue + vite为例)

桥(PATH/index.js)


let module;
// vite中标注依赖关系,底层默认是懒加载的
const modules=import.meta.glob('./vendors/*.js'),
init=async ()=>{
  if(!module){
    const res=await request_LBS_config(); // 伪代码,这个需求用桥接模式是为了延迟绑定提升灵活性,配置参数应当存在服务端,否则用桥接的意义不大
    module=await modules['./vendors/'+res.vendor+'.js'](); // 如前面所述,vite的glob方法底层默认是异步加载的,需要await
    await module.init(res.config);
  }
  return module;
};

/**
 * 
 * @param {HTMLElement} dom 
 * @param {?Object} center 
 * @returns {Promise}
 */
export async function initMap(dom,center=null){
  if(!center){
    const location=await getLocation(); // 伪代码,确保当不传入地图中心时能定位到当前位置,非必要
    center={
      longitude:location.latitude,
      longitude:location.longitude
    };
  }
  const {MapComponent}=await init();
  await MapComponent.init();
  const map=new MapComponent(dom,center);

  return map;
};

// 统一样式参数
export class MapLabel {
  id;
  style;
  position;
  rank;
  text;
  constructor(id,style,position,text,{rank=0}={}){
    Object.assign(this,{id,style,position,text,rank});
  }
};
// 统一标记参数
export class MapMarker {
  id;
  style;
  position;
  rank;
  text;
  constructor(id,style,position,text,{rank=0}={}){
    Object.assign(this,{id,style,position,text,rank});
  }
};

// 对外提供注释,对内约束实现
export class MapComponent {
  /**
   * 
   * @param {Object} position 
   * @param {Number} [position.latitude] 
   * @param {Number} [position.longitude] 
   * @returns {MapComponent}
   */
  setCenter(position){ throw new Error('setCenter 方法未实现'); }

  addLabelStyle(id,offset,font,extra={}){ throw new Error('addLabelStyle 方法未实现'); }
  /**
   * 
   * @param {MapLabel[]} labels 
   * @returns {MapComponent}
   */
  addLabels(labels){ throw new Error('addLabels 方法未实现'); }
  
  /**
   * 
   * @param {String} id 
   * @param {Object} icon 
   * @param {String} [icon.src] 
   * @param {Number} [icon.width] 
   * @param {Number} [icon.height] 
   * @param {Object} [icon.anchor=null] 锚点位置,默认在icon中心
   * @param {String} [icon.anchor.h] 锚点横向位置,left/center/right
   * @param {String} [icon.anchor.v] 锚点纵向位置,top/middle/bottom
   * @param {Object} [icon.offset={x:0,y:0}] 图标相对锚点的偏移
   * @param {Object} label 
   */
  addMarkerStyle(id,icon,label={}){ throw new Error('addMarkerStyle 方法未实现'); }
  /**
   * 
   * @param {MapMarker[]} labels 
   * @returns {MapComponent}
   */
  addMarkers(markers){ throw new Error('addMarkers 方法未实现'); }

  clear(){ throw new Error('clear 方法未实现'); }

  static async init(){ throw new Error('init 方法未实现'); }
};

/**
 * 
 * @param {HTMLIFrameElement} iframe 
 * @param {Function} callback
 * @param {Object} options 
 * @param {Object} [options.center] 起始中心坐标 
 * @param {Number} [options.radius] 显示中心多大范围内的POI 
 * @returns 
 */
export async function chooseLocationByIFrame(iframe,callback,options={}){
  const {initLocationChooser}=await init(),
  listener=initLocationChooser(iframe,callback,options);
  window.addEventListener('message',listener);
  return ()=>{
    window.removeEventListener('message',listener);
  };
};

腾讯LBS(PATH/vendors/Tencent.js)

let config;
// 将配置缓存到前端内存,新手注意config结构自行设计,这里只提供参考方案
export function init(cfg){
  config=cfg;
}

import {MapComponent as MapInterface} from '../index';
export class MapComponent extends MapInterface {
  #map;
  constructor(dom,center){
    super();
    this.#map=new window.TMap.Map(dom,{
      center:new window.TMap.LatLng(center.latitude,center.longitude),
      zoom:20,
      viewMode:'2D'
    });
  }

  setCenter(position){
    this.#map.setCenter(new window.TMap.LatLng(position.latitude,position.longitude));
    return this;
  }

  #labelStyles={};
  addLabelStyle(id,offset,font,{padding='',backgroundColor=null,border=null,align=null}={}){
    this.#labelStyles[id]=new window.TMap.LabelStyle({
      offset,
      size:font.size*0.8-3,
      color:font.color,
      padding,
      backgroundColor,
    });
    if(border){
      Object.assign(this.#labelStyles[id],{
        borderWidth:border.width,
        borderColor:border.color,
      });
    }
    if(align){
      Object.assign(this.#labelStyles[id],{
        verticalAlignment:align.v,
        alignment:align.h,
      });
    }
    return this;
  }
  #labelLayer;
  #labels=new Set();
  addLabels(labels){
    const geometries=labels.map(l=>{
      this.#labels.add(l.id);
      return {
        id:l.id,
        styleId:l.style,
        position:new window.TMap.LatLng(l.position.latitude,l.position.longitude),
        rank:l.rank,
        content:l.text,
      };
    });
    if(!this.#labelLayer){
      this.#labelLayer=new window.TMap.MultiLabel({
        styles:this.#labelStyles,
        map:this.#map,
        geometries,
      });
    }else{
      this.#labelLayer.add(geometries);
    }
  }

  #markerStyles={};
  addMarkerStyle(id,icon,label={}){
    const options={
      src:icon.src,
      width:icon.width,
      height:icon.height,
      color:label.color,
      padding:label.padding,
      backgroundColor:label.backgroundColor,
      direction:label.direction||'bottom',
      fontSize:label.size||12,
    };
    if(icon.anchor){
      options.anchor={
        x:{
          left:0,
          center:icon.width/2,
          right:icon.width,
        }[icon.anchor.h],
        y:{
          top:0,
          middle:icon.height/2,
          bottom:icon.height,
        }[icon.anchor.v],
      };
    }else{
      options.anchor={
        x:icon.width/2,
        y:icon.height/2,
      };
    }
    if(icon.offset){
      options.anchor.x-=icon.offset.x;
      options.anchor.y-=icon.offset.y;
    }
    if(label.border){
      Object.assign(this.#labelStyles[id],{
        backgroundBorderWidth:label.border.width,
        backgroundBorderColor:label.border.color,
      });
    }
    this.#markerStyles[id]=new window.TMap.MarkerStyle(options);
    return this;
  }
  #markerLayer;
  #markers=new Set();
  addMarkers(markers){
    const geometries=markers.map(m=>{
      this.#markers.add(m.id);
      return {
        id:m.id,
        styleId:m.style,
        position:new window.TMap.LatLng(m.position.latitude,m.position.longitude),
        rank:m.rank,
        content:m.text,
      };
    });
    if(!this.#markerLayer){
      this.#markerLayer=new window.TMap.MultiMarker({
        styles:this.#markerStyles,
        map:this.#map,
        geometries,
      });
    }else{
      this.#markerLayer.add(geometries);
    }
  }

  clear(){
    this.#labelLayer.remove([...this.#labels]);
    this.#labels.clear();
    this.#markerLayer.remove([...this.#markers]);
    this.#markers.clear();
  }

  static async init(){
    if(!window.TMap){
      // 伪代码,异步加载js库
      await loadScript(`https://map.qq.com/api/gljs?v=1.exp&key=${config.key}&libraries=visualization`);
    }
  }
}

export function initLocationChooser(iframe,callback,{center={},radius=1000,zoom=18,total=50}){
  let src='https://apis.map.qq.com/tools/locpicker?type=1&mapdraggable=1&search=1&key='+config.key
  +'&referer='+config.app
  +'&radius='+radius
  +'&total='+total
  +'&zoom='+zoom;
  if(center.latitude&&center.longitude){
    src+=`&coord=${center.latitude},${center.longitude}`;
  }
  iframe.setAttribute('src',src);
  return e=>{
    if(e.data.module==='locationPicker'&&e.source===iframe.contentWindow){
      callback({
        name:e.data.poiname,
        address:e.data.poiaddress,
        latitude:e.data.latlng.lat,
        longitude:e.data.latlng.lng,
      });
    }
  };
}

高德开放平台(PATH/vendors/AutoNavi.js)

let config;
// 将配置缓存到前端内存,新手注意config结构自行设计,这里只提供参考方案
export function init(cfg){
  config=cfg;
}

import {MapComponent as MapInterface} from '../index';
export class MapComponent extends MapInterface {
  #map;
  constructor(dom,center){
    super();
    this.#map=new window.AMap.Map(dom,{
      center:[center.longitude,center.latitude],
      zoom:20,
      viewMode:'2D'
    });
  }

  setCenter(position){
    this.#map.setCenter([position.longitude,position.latitude]);
    return this;
  }

  #labelStyles={};
  addLabelStyle(id,offset,font,{padding=0,backgroundColor='transparent',border=null,align=null}={}){
    this.#labelStyles[id]={
      offset:new window.AMap.Pixel(offset.x,offset.y),
      style:{
        fontSize:font.size+'px',
        color:font.color,
        padding,
        backgroundColor,
        border:border?border.width+'px solid '+border.color:'none',
      },
      align,
    };
    return this;
  }
  #labels=new Map();
  addLabels(labels){
    labels.forEach(l=>{
      const options={
        extData:{
          id:l.id,
        },
        position:new window.AMap.LngLat(l.position.longitude,l.position.latitude),
        zIndex:l.rank,
        text:l.text,
      };
      if(l.style){
        const style=this.#labelStyles[l.style];
        options.style=style.style;
        options.offset=style.offset;
        options.anchor=style.align?style.align.v+'-'+style.align.h:'center';
      }else{
        options.anchor='center';
      }
      const overlay=new window.AMap.Text(options);
      this.#labels.get(l.id)?.remove();
      this.#labels.set(l.id,overlay);
      this.#map.add(overlay);
    });
  }

  #markerStyles={};
  addMarkerStyle(id,icon,label={}){
    const mapIcon={
      image:icon.src,
      size:[icon.width,icon.height],
      // size:new AMap.Size(icon.width,icon.height), // 有bug,不生效
      anchor:icon.anchor?icon.anchor.v+'-'+icon.anchor.h:'center',
    },
    labelStyle={
      fontSize:label.size||12,
      padding:label.padding||0,
      backgroundColor:label.backgroundColor||'rgba(0,0,0,0)',
      border:label.border?label.border.width+'px solid '+label.border.color:'none',
    };
    if(label.color){
      labelStyle.fillColor=label.color;
    }
    if(icon.offset){
      mapIcon.offset=new window.AMap.Pixel(icon.offset.x,icon.offset.y);
    }
    this.#markerStyles[id]={
      icon:mapIcon,
      label:{
        offset:[0,-labelStyle.fontSize/2],
        // offset:new window.AMap.Pixel(0,-labelStyle.fontSize/2), 有bug,不生效
        direction:label.direction||'bottom',
        style:labelStyle,
      },
    };
    return this;
  }
  #LabelsLayer;
  #markers=new Map();
  addMarkers(markers){
    if(!this.#LabelsLayer){
      this.#LabelsLayer=new window.AMap.LabelsLayer({
        collision: true,
        opacity: 1,
        zIndex: 120,
        allowCollision: true,
      });
      this.#map.add(this.#LabelsLayer);
    }
    markers.forEach(m=>{
      const options={
        name:m.id,
        position:new window.AMap.LngLat(m.position.longitude,m.position.latitude),
        zIndex:m.rank,
        rank:m.rank,
      };
      if(m.style){
        options.icon=this.#markerStyles[m.style].icon;
      }
      if(m.text){
        options.text={content:m.text};
        if(m.style){
          Object.assign(
            options.text,
            this.#markerStyles[m.style].label
          );
        }
      }
      const overlay=new window.AMap.LabelMarker(options);
      this.#markers.get(m.id)?.remove();
      this.#markers.set(m.id,overlay);
      this.#LabelsLayer.add(overlay);
    });
  }

  clear(){
    this.#map.remove([...this.#labels.values()]);
    this.#labels.clear();
    this.#LabelsLayer.remove([...this.#markers.values()]);
    this.#markers.clear();
  }

  static async init(){
    if(!window.AMapLoader){
      window._AMapSecurityConfig = {
        securityJsCode: config.jscode,
      };
      // 伪代码,异步加载js库
      await loadScript('https://webapi.amap.com/loader.js');
      await window.AMapLoader.load({
        key: config.key, //申请好的Web端开发者 Key,调用 load 时必填
        version: '2.0', //指定要加载的 JS API 的版本,缺省时默认为 1.4.15
      });
    }
  }
}

export function initLocationChooser(iframe,callback,{center={},radius=1000,zoom=18,total=50}){
  let src=`https://m.amap.com/picker/?key=`+config.key
  +'&jscode='+config.jscode
  +'&radius='+radius
  +'&total='+total
  +'&zoom='+zoom;
  if(center.latitude&&center.longitude){
    src+=`¢er=${center.longitude},${center.latitude}`;
  }
  iframe.setAttribute('src',src);
  iframe.onload = function(){
    iframe.contentWindow.postMessage('hello','https://m.amap.com/picker/');
  };
  return e=>{
    if(e.source===iframe.contentWindow&&e.data.location){
      const [longitude,latitude]=e.data.location.split(',');
      callback({
        name:e.data.name,
        address:e.data.address,
        latitude:Number(latitude),
        longitude:Number(longitude),
      });
    }
  };
}

示例

const res=await getSignInList(),
labels=[],
markers=[];
res.list.forEach(u=>{
  labels.push(new MapLabel(
    u.id+'_t',
    'time',
    u.signIn,
    this.format(u.signIn.time),
    {rank:u.signIn.time}
  ));
  if(u.student?.number){
    labels.push(new MapLabel(
      u.id+'_n',
      'number_'+u.sex.name,
      u.signIn,
      u.student.number,
      {rank:u.signIn.time+1}
    ))
  }
  markers.push(new MapMarker(
    u.id,
    'student_'+u.sex.name,
    u.signIn,
    u.chinese?.name||u.nickname,
    {rank:u.signIn.time}
  ));
});
let calssRoom;
if(this.lesson.room){
  calssRoom=this.lesson.room.building.position;
  markers.push(new MapMarker('-1','class_room',calssRoom,'教室:'+this.lesson.room.name));
}
let center=calssRoom;
const scheet=this.lesson.sheets.length===1?this.lesson.sheets[0]:
(this.conds.sheet?this.lesson.sheets.find(s=>s.id===this.conds.sheet):null);
if(scheet){
  if(scheet.room){
    center=scheet.room.building.position;
    markers.push(new MapMarker('0','sign_in',center,'签到地点:'+scheet.room.name));
  }else if(scheet.latitude&&scheet.longitude){
    center=scheet;
    markers.push(new MapMarker('0','sign_in',center,'室外签到点'));
  }
}

if(!signInMap){
  signInMap=await initMap(this.$refs.signInMap,center);
  signInMap.addLabelStyle('time',{x:0,y:-25},{size:10,color:'#999999'})
  .addLabelStyle('number_unknown',{x:0,y:20},{size:13,color:'#19a97b'})
  .addLabelStyle('number_boy',{x:0,y:20},{size:13,color:'#1296db'})
  .addLabelStyle('number_girl',{x:0,y:20},{size:13,color:'#d4237a'})
  .addMarkerStyle(
    'class_room',
    {
      width:30,
      height:30,
      src:'/src/img/icons/classRoom.png',
      anchor:{h:'right',v:'middle'},
    },
    {direction:'left'},
  ).addMarkerStyle(
    'sign_in',
    {
      width:30,
      height:30,
      src:'/src/img/icons/signInScheet.png',
      anchor:{h:'left',v:'middle'},
    },
    {direction:'right'},
  ).addMarkerStyle(
    'student_unknown',
    {
      width:20,
      height:20,
      src:'/src/img/icons/student.svg',
      anchor:{h:'center',v:'bottom'},
    },
    {color:'#19a97b',size:16},
  ).addMarkerStyle(
    'student_boy',
    {
      width:20,
      height:20,
      src:'/src/img/icons/boy.svg',
      anchor:{h:'center',v:'bottom'},
    },
    {color:'#1296db',size:16},
  ).addMarkerStyle(
    'student_girl',
    {
      width:20,
      height:20,
      src:'/src/img/icons/girl.svg',
      anchor:{h:'center',v:'bottom'},
    },
    {color:'#d4237a',size:16},
  );
}else if(center) signInMap.setCenter(center);
signInMap.addLabels(labels);
signInMap.addMarkers(markers);

你可能感兴趣的:(前端,桥接模式,vue.js)