列表地图模式

概述

用于配置在列表页同时展示列表和地图, 需要下面的地图字段名不为空

功能介绍

  1. 会在列表页同样展示一个地图区域, 可配置为 仅地图、混合模式(混合模式是既有列表也有地图,并且可以动态切换)
  2. 地图区域会将列表的每个坐标点数据渲绘制一个一个的marker
  3. 互动: 选中marker会高亮列表行, 选中列表行会给对应marker添加弹跳动画
  4. 选中marker会出现窗口,带有坐标点基本信息和编辑详情按钮, 按钮功能和列表功能一致, 并且按钮权限跟列表一致

效果预览

  1. 列表展示 列表地图模式展示
  2. 选中marker 列表地图模式展示

地图字段名

  1. 列表每条数据的坐标点字段名称,为空则不启用地图模式

  2. 列表的坐标点数据需要遵循以下格式

  • JSON 格式的值
  • fd_lng 为经度, fd_lat 为纬度
  • fd_text 为地点名称
{
	"fd_lng": 116.397451,
	"fd_lat": 39.909187,
	"fd_text": "北京市东城区东华门街道天安门"
}

展示模式

列默认表展示模式,可切换,选择仅地图后不可切换, 详见上面图片的切换按钮

列表布局

列表和地图的位置, 建议是列表在上, 地图在下. 可配置, 目前只支持上下布局

地图组件路径

自定义列表中地图组件的文件路径,为空时则为默认值 自定义地图组件示例可见项目的src\views\modules\online\cgform\auto\AutoMapCustom.vue文件 自定义地图组件需要遵循以下规则

  • props接收: hoverId(当前鼠标悬停列表的数据ID), mapField(地图数据字段), buttonSwitch(详情编辑按钮)
  • 自定义初始化地图逻辑
  • renderData方法, 参数是列表的数据,可以不使用这个数据, 主要用来做地图marker渲染
  • loadData方法, 参数是列表的查询项参数, 可以用来通过自定义接口获取数据
  • detail和edit 事件, 触发这些事件会打开对应表单弹窗
  • highlight事件, 用来通知列表是否高亮列表某一行

自定义地图组件示例

<template>
  <div id="auto-list-map-container"></div>
</template>

<script>
// 地图标记
import marker_icon from '@/assets/marker.png'
import { getAction } from '@/api/manage'
import { message } from 'ant-design-vue'

export default {
  props: {
    // 接收,当前鼠标悬停列表的数据ID
    hoverId: {
      type: String,
      default: '',
    },

    // 地图字段
    mapField: {
      type: String,
      default: '',
    },

    // 是否展示编辑,详情按钮
    buttonSwitch: {
      type: Object,
      default: () => ({}),
    },
  },

  data() {
    return {
      // 列表数据,可以是从父组件传递过来的,也可以自己根据查询条件查询的
      dataList: [],
    }
  },

  mounted() {
    this.initialVariables()
  },

  watch: {
    hoverId(val) {
      if (val) {
        return this.highlightMapMarker(val)
      }
      this.unHighlightMapMarker()
    },
  },

  methods: {
    // 初始化变量
    initialVariables() {
      this.map = null
      this.infoWindow = null
      this.markers = []
      this.animateMarker = null
      this.markerNormal = new AMap.Icon({
        image: marker_icon,
        size: new AMap.Size(74, 84),
        imageSize: new AMap.Size(37, 42),
      })
    },

    // 初始化地图
    async initMap() {
      if (this.map) return
      const mapInitParams = {
        resizeEnable: true, //是否监控地图容器尺寸变化
        zoom: 12, //初始地图级别
      }
      console.log('地图组件初始化地图')
      this.map = new AMap.Map('auto-list-map-container', mapInitParams)
      this.infoWindow = new AMap.InfoWindow({ offset: new AMap.Pixel(0, -35) })

      return true
    },

    // 渲染数据 -- 根据数据渲染地图标记
    renderData(list = []) {
      // 使用列表的数据
      // console.log('地图组件渲染数据:', [...list])
      // this.dataList = [...list]
      // return this.renderMarker(list)

      // 使用查询条件自定义查询数据
      return this.renderMarker(this.dataList)
    },

    // 获取数据 -- 查询项参数变化,如果使用列表数据,函数体为空即可
    async loadData(searchParams = {}) {
      console.log('地图组件开始获取数据:', { ...searchParams })
      const { pageNo, pageSize, url, ...rest } = searchParams

      return getAction(url, {
        ...rest,
        pageNo: 1,
        pageSize: 10,
      }).then((res = {}) => {
        if (!res.success) {
          return message.error(res.message)
        }

        let list = res.result?.records ?? []
        console.log('list:', list)

        this.dataList = list
      })
    },

    // 渲染marker
    renderMarker(list = []) {
      if (!this.map) return

      const mapFieldName = this.mapField
      this.markers = list
        .filter((v) => v && v[mapFieldName])
        .map((v) => {
          let mapFieldValue
          try {
            mapFieldValue = JSON.parse(v[mapFieldName])
          } catch (error) {
            console.error('解析地图坐点错误', v[mapFieldName])
          }
          if (!mapFieldValue) return

          const { fd_lng, fd_lat, fd_text } = mapFieldValue
          if (!fd_lng || !fd_lat) return null
          console.log('fd_lng,fd_lng', { fd_lng, fd_lat, fd_text })

          let normal_icon = this.markerNormal
          const marker = new AMap.Marker({
            position: [fd_lng, fd_lat],
            cursor: 'pointer',
            extData: v.id,
            icon: normal_icon,
          })

          marker.on('click', () => {
            this.onClickMarker({
              dataId: v.id,
              fd_lng,
              fd_lat,
              fd_text,
            })
          })

          return marker
        })
        .filter((v) => !!v)

      this.map.clearMap()
      if (this.markers.length) {
        this.map.add(this.markers)
        const [firstMarker] = this.markers
        this.map.setCenter(firstMarker.getPosition())
      }
    },

    // 渲染infoWindow
    renderInfoWindow(values) {
      const { dataId, fd_text, fd_lng, fd_lat } = values

      // 点击编辑或者详情
      window.onAutoListMapClick = (type) => {
        const record = this.dataList.find((v) => v.id === dataId)
        if (!record) {
          record = { id: dataId }
        }
        if (type === 'detail') {
          return this.$emit('detail', record)
        }

        return this.$emit('edit', record)
      }

      let actionDOM = ''
      actionDOM += '<div class="actions">'

      if (this.buttonSwitch.detail) {
        actionDOM += `<span class="item " onclick="onAutoListMapClick('detail')" >详情</span>`
      }

      if (this.buttonSwitch.update) {
        actionDOM += `<span class="item " onclick="onAutoListMapClick('edit')" >编辑</span>`
      }

      actionDOM += '</div>'

      const content = `<div class="auto-list-info-window">
          <p style="margin:0;">${fd_text}</p>
          <p style="margin:0;">经度:${fd_lng}</p>
          <p style="margin:0;">纬度:${fd_lat}</p>
          ${actionDOM}
        </div>`
      this.infoWindow.setContent(content)
      this.infoWindow.open(this.map, [fd_lng, fd_lat])
      this.infoWindow.on('close', this.onInfoWindowClose)
      return content
    },

    // 点击点标记
    onClickMarker(values) {
      this.$emit('highlight', values.dataId)
      this.renderInfoWindow(values)
    },

    // 关闭信息窗体
    onInfoWindowClose() {
      this.$emit('highlight', null, true)
    },

    // 停止高亮标记
    unHighlightMapMarker() {
      if (!this.animateMarker) return
      this.animateMarker.dom.classList.remove('marker-animate')
      this.animateMarker = null
    },

    // 标记高亮
    highlightMapMarker(dataId) {
      if (!this.markers) return

      const marker = this.markers.find((v) => v?.getExtData() === dataId)
      if (!marker) return
      marker.dom.classList.add('marker-animate')
      this.animateMarker = marker
    },
  },
}
</script>

<style lang="less">
#auto-list-map-container {
  height: 100%;

  .amap-marker {
    &.marker-animate {
      animation: marker-animate 1s infinite;
    }

    @keyframes marker-animate {
      0% {
        transform: translateY(0);
      }
      100% {
        transform: translateY(-50px);
      }
    }
  }
}
</style>

<style lang="less">
// 地图样式
.auto-list-info-window {
  padding: 10px 15px;

  .actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-top: 8px;
    margin-top: 4px;
    border-top: solid 1px #ccc;
    .item {
      margin-right: 8px;
      padding: 3px 10px;
      color: #fff;
      background: #1890ff;
      border-radius: 3px;
      cursor: pointer;
    }
  }
}
</style>

<style lang="less">
.auto-list-container {
  .ant-table-row.highlight {
    background-color: aquamarine;
  }
}
</style>

感谢您的反馈。如果您有关于如何使用 KubeSphere 的具体问题,请在 Slack 上提问。如果您想报告问题或提出改进建议,请在 GitHub 存储库中打开问题。