Vue文件转成Dom方案

问题

开发中我们经常会遇到引入一些成熟开发库的情况,如echarts、高德地图AMap、 leaflet等等,在一些事件响应的触发上,是这些库所提供的api进行的;以高德地图为例,列举了存在的问题:

  1. 在弹窗中,仅支持拼接字符串的方式;
  2. 当需要的在弹窗的dom上绑定事件的时,无法调取当前组件的事件;
  3. 字符串中充斥着大量内联样式,可扩展性、阅读性差;
  4. 对数据的操作不清晰;

如下代码

let content = `<div>
          <div class='popup-content'>
             <div class='content-padding'>北京市</div>
             <div class='content-padding'>设备总数:0</div>
             <div class='content-padding'>工厂总数:0</div>
          </div>
          <div class='info-sharp'></div>
          <a class='adcombo-close' href="javascript: void(0)" @click='closeInfoWindow()'></a>
          </div>`;复制代码

以上代码仅仅为一个简单的例子,如果页面弹窗结构以及内容复杂可想而知开发以及修改的困难成倍增长。

解决方法

  1. 使用Vue的extend()方法创建一个组件构造器
  2. 然后实例化创建的组件构造器
  3. 手动地挂载一个未挂载的实例
  4. 返回 Vue 实例使用的根 DOM 元素。

实例演示

  1. 新建一个通用方法,包含以上四步骤
// vueToDom.js

import Vue from 'vue'
export function CreateDom(obj){   // 创建一个构造函数
  const vm_obj = obj;   // 存储传入的参数对象
  function _getDomItem(){    // 定义一个转换dom方法
    const extendVM = Vue.extend(vm_obj);  // 创建一个vue构造器
    const vue_tmp = new extendVM();    // 实例化构造器
    const dom = vue_tmp.$mount().$el;  // 手动地挂载一个未挂载的实例,并获取跟元素
    return dom;
  }
  return {
    getDomItem: _getDomItem
  }
} 复制代码
  1. 根据需求,构造自己需要的vue文件
//  deviceInfoWindow.vue
<template>
  <div>
    <div class="popup-content">
      <div class="content-padding">北京市</div>
      <div class="content-padding">设备总数:0</div>
      <div class="content-padding">工厂总数:0</div>
    </div>
    <div class="info-sharp"></div>
    <a
      class="adcombo-close"
      href="javascript: void(0)"
      @click="closeInfoWindow()"
    ></a>
  </div>
</template>
<script>
export default {
  components: {},
  props: {
    infoWindow: {
      type: Object,
      default: () => {
        return {};
      }
    },
  },
  data() {
    return {};
  },
  created() {},
  mounted() {},
  methods: {
    closeInfoWindow() {
      this.infoWindow.close();
    },
  },
};
</script>
<style scoped lang="scss">
.popup-content {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  background: rgba(19, 18, 18, 0.7);
  border-radius: 4px;
  text-align: left;
  border: 1px solid transparent;
  // width: 100px;
  height: 100px;
  color: #fff;
}
.content-padding {
  padding: 15px 40px 0px 15px;
}
.info-sharp {
  // bottom: 0;
  left: 50%;
  margin-left: -8px;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-top: 8px solid rgba(19, 18, 18, 0.7);
  position: absolute;
}
.info-sharp::after {
  position: absolute;
  content: "";
  margin-left: -8px;
  margin-top: -7px;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-top: 8px solid rgba(0, 0, 0, 0.3);
  filter: blur(2px);
  z-index: -1;
}
.adcombo-close {
  position: absolute;
  top: 4px;
  right: 4px;
  background: url(https://webapi.amap.com/images/close.png) center center
    no-repeat;
  width: 18px;
  height: 18px;
  text-indent: -9999em;
}
</style>复制代码
  1. 在需要使用以上组件的vue文件中引入
<template>
  <div class="device-map">
    <div class="device-content-total">
      <div class="device-total">
        <span class="content">{{ $t("deviceMap.deviceTotal") }}</span>
        <span class="number">5</span>
      </div>
      <div class="factory-total">
        <span class="content">{{ $t("deviceMap.factoryTotal") }}</span>
        <span class="number">5</span>
      </div>
    </div>
    <div class="map-content" :id="mapId"></div>
  </div>
</template>

<script>
import { CreateDom } from "@/utils/vueToDom";
import DeviceInfoWindow from "./deviceInfoWindow";
export default {
  name: "DeviceMap",
  data() {
    return {
      map: null,
      mapId: "deviceMapId",
      mapPlugin: {},
      provincePolygons: [],
      infoWindow: null,
    };
  },
  created() {
  },
  mounted() {
    this.$nextTick(() => {
      this.initAmapPlugin();
      this.initAmap(this.mapId);
      this.initMapEvent(this.map);
    });
  },
  methods: {
    // 初始化地图
    initAmap(mapId) {
      this.map = new AMap.Map(mapId, {
        viewMode: "2D", // 默认使用 2D 模式,如果希望使用带有俯仰角的 3D 模式,请设置 viewMode: '3D',
        zoom: 4, //初始化地图层级
        center: [116.397428, 39.90923], //初始化地图中心点
        lang: this.$i18n.locale === "zh" ? "zh_cn" : "en",
      });
    },
    // 初始化高德地图插件
    initAmapPlugin() {
      //异步同时加载多个插件
      AMap.plugin(["AMap.Geocoder", "AMap.DistrictSearch"], () => {
        this.mapPlugin["geocoder"] = new AMap.Geocoder({ extensions: "all" });
        this.mapPlugin["districtSearch"] = new AMap.DistrictSearch({
          subdistrict: 0, //获取边界不需要返回下级行政区
          extensions: "all", //返回行政区边界坐标组等具体信息
          level: "province", //查询行政级别为 市
        });
      });
    },
    // 初始化地图事件
    initMapEvent(map) {
      map.on("click", this.getDeviveAndFactoryInfo);
    },
    // 获取当前点击事件的区域内的设备与工厂总数
    getDeviveAndFactoryInfo(e) {
      // 获取点击事件的经纬度
      let lnglat = [e.lnglat.getLng(), e.lnglat.getLat()];
      // 根据经纬度查询当前点所在的省
      this.mapPlugin.geocoder.getAddress(lnglat, (status, result) => {
        if (status === "complete" && result.regeocode) {
          let province = result.regeocode.addressComponent.province;
          this.infoWindow = new AMap.InfoWindow({
            isCustom: true,
            anchor: "bottom-center",
            closeWhenClickMap: true,
          });
          const that = this;
          //  使用组件---------------------------------------------------------------------
          const popupDom = new CreateDom({
            template: "<DeviceInfoWindow :infoWindow='infoWindow'></DeviceInfoWindow>",
            data() {
              return { infoWindow: null };
            },
            created() {
              // 获取当前页面的数据
              this.infoWindow = that.infoWindow;
            },
            components: {
              DeviceInfoWindow,
            },
          });
           //  使用组件---------------------------------------------------------------------
          this.infoWindow.setContent(popupDom.getDomItem());
          this.infoWindow.open(this.map, lnglat);
          // 根据当前省份获得当前省份的边界
          this.mapPlugin.districtSearch.search(province, (status, result) => {
            this.map.remove(this.provincePolygons); //清除上次结果
            this.provincePolygons = [];
            var bounds = result.districtList[0].boundaries;
            console.log("边界------------", bounds);
            if (bounds) {
              for (var i = 0, l = bounds.length; i < l; i++) {
                //生成行政区划polygon
                var polygon = new AMap.Polygon({
                  strokeWeight: 1,
                  path: bounds[i],
                  fillOpacity: 0.4,
                  fillColor: "#80d8ff",
                  strokeColor: "#0091ea",
                });
                this.provincePolygons.push(polygon);
              }
            }
            this.map.add(this.provincePolygons);
            this.map.setFitView(this.provincePolygons); //视口自适应
          });
        } else {
          this.$message.error("根据鼠标点查询省市失败");
        }
      });
    }
  },
};
</script>复制代码
  1. 效果图如下图

Vue文件转Dom_Dom

优缺点

优点
  1. 代码逻辑统一,可阅读性强。
  2. 可拓展性、可复用性强。
缺点
  1. 需要将项目的运行时构建更改为独立构建,性能消耗有所增加。
  2. 修改方案如下: (1)对于Vue cli3.0以上的版本 在vue.config.js文件中增加如下:
module.exports = {
  runtimeCompiler: true,
}复制代码

(2)对于以下版本修改如下代码:

module.exports = {
// ...
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js', // runtime-only版本为'vue/dist/vue.runtime.esm.js'
'@': resolve('src')
}
},复制代码

总结

如果该篇文章对您有帮助,请帮忙点个赞,谢谢~~~~