【项目个人笔记记录 !!!】

个人项目笔记记录
基于高德官方开放api进行修改 : https://lbs.amap.com/demo/list/js-api-v2

项目功能(需求点):

1、form表单地图选址功能
2、基于form表单查询的数据 对地图进行海量标点、画标点画圈辐射范围
3、基于标点 进行点击 实现模拟态弹窗展示客户信息
4、对客户拟态弹窗 进行选中、取消选中操作 进行数据处理 从而替换icon来实现选中态和未选中态

1、地图选址功能 :

交互操作:

1、点击form表单字段弹出 地图选址拟态弹窗
2、进行地图点击选点(或 select组件搜索地点 并点击数据行) 从而获取经纬度以及地点名称 同时将地址名称数据绑定到selected组件上 而后点击确认将数据回传到form表单字段上

效果图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

地图选址功能模块代码

因代码涉及其他业务需求 全部粘贴偏长 为了简洁这边就只展示关键代码部分

在这里插入图片描述

<script setup lang="ts">
import { ref } from 'vue'

interface form {
  [key: string]: any
}
const form = ref<form>({
  addr: '',
  locationLat: null,
  locationLon: null
})
const formRef = ref()

// #region 地图选址模块 start
//地图选址弹窗ref
const SelectlocationModalRef = ref()
//formitem地址字段点击事件
const addAddress = () => {
//弹窗defineExpose 暴露出的open方法 从而打开拟态窗口
  SelectlocationModalRef.value.open()
}

/*
* 拟态窗确定操作后的 emits回调
* val 子组件抛出给父组件的数据
*/
const handelLocationSuccess = (val) => {
  form.value.addr = val.addr
  form.value.locationLat = val.lat
  form.value.locationLon = val.lng
}
//#endregion 地图选址模块 end

</script>
<template>
    <el-form
      ref="formRef"
      :model="form">
            <el-form-item label="外访地点">
               <el-button type="primary" @click="addAddress" link>{{`${form.addr ? '更换' : '添加'}地点`}}
               </el-button>
               {{ form.addr }}
            </el-form-item>
    </el-form>
  <SelectlocationModal ref="SelectlocationModalRef" @confirm="handelLocationSuccess" />
</template>

SelectlocationModal.vue组件代码

需要装依赖!!!
包管理器 install 一下 ‘@amap/amap-jsapi-loader’
在这里插入图片描述

<script setup lang="ts">
import AMapLoader from '@amap/amap-jsapi-loader'
import { ref, onMounted, onUnmounted } from 'vue'
import { assign } from 'lodash-es'
import { ElMessage } from 'element-plus'

window._AMapSecurityConfig = {
  // 安全密钥
  securityJsCode: '申请的安全密钥'
}
//自定义事件传递数据
const emits = defineEmits(['confirm'])
//弹窗打开状态
const visible: any = ref(false)
//搜索地点数据
const areaList: any = ref([])
//select搜索关键词数据
const areaValue = ref('')
let map: any = null
const loading: any = ref(false)
//抛出的form数据
const checkedForm: any = ref({
  addr: '',
  lat: '',
  lng: ''
})


// #region 实例对象 start
let AutoComplete: any = null
let aMap: any = null
let geoCoder: any = null
//#endregion 实例对象 end

const initMap = () => {
  AMapLoader.load({
    key: '申请的webkey', // 申请好的Web端开发者Key,首次调用 load 时必填
    version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    plugins: ['AMap.Geocoder', 'AMap.AutoComplete'] // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  })
    .then((AMap: any) => {
      aMap = AMap
      //地图容器id id需确保唯一性 否则 当一个页面存在两个地图插件时 id容器需命名切勿重复
      map = new AMap.Map('container', {
        // 设置地图容器id
        viewMode: '3D', // 是否为3D地图模式
        zoom: 11, // 初始化地图级别
        center: [119.330221107, 26.0471254966] // 初始化地图中心点位 福州市
        // center: [116.397428, 39.90923] // 初始化地图中心点位置置  北京市
      })
      AutoComplete = new AMap.AutoComplete({
        city: '全国'
      })
      geoCoder = new AMap.Geocoder({
        // city: '010', //城市设为北京,默认:“全国”
        city: '0591', //城市设为福州市
        radius: 1000 //范围,默认:500
      })
     //地图点击事件
      map.on('click', async (e: any) => {
        try {
        //处理点击数据 
          const result: any = await new Promise((resolve, reject) => {
            geoCoder.getAddress(e.lnglat, function (status: string, res: any) {
              if (status === 'complete') {
                resolve(res)
              } else {
                reject(new Error(`获取失败: ${status}`))
              }
            })
          })

          checkedForm.value.addr = result.regeocode.formattedAddress
          areaValue.value = result.regeocode.formattedAddress
          addmark(e.lnglat.getLng(), e.lnglat.getLat(), AMap)
        } catch (error) {
          console.error('获取失败', error)
        }
      })
    })
    .catch((e) => {
      console.log(e)
    })
}
let marker: any = null
const addmark = (lat: any, lng: any, AMap: any) => {
  marker && removeMarker()
  marker = new AMap.Marker({
    position: new AMap.LngLat(lat, lng),
    title: checkedForm.value.addr,
    zoom: 13
  })
  checkedForm.value.lat = lng
  checkedForm.value.lng = lat
  map.add(marker)
  map.setCenter([lat, lng], '', 500)
}

const removeMarker = () => {
  map.remove(marker)
}

watch(
  () => checkedForm.value,
  (val) => {
    console.log('val', val)
  },
  {
    immediate: true,
    deep: true
  }
)

const remoteMethod = (searchValue: any) => {
  if (searchValue !== '') {
    setTimeout(() => {
      AutoComplete.search(searchValue, (status: any, result: any) => {
        console.log(result, status)
        if (result.tips?.length) {
          areaList.value = result?.tips
        }
      })
    }, 200)
  } 
}

const currentSelect = (val: any) => {
  checkedForm.value.lat = val.location?.lat
  checkedForm.value.lng = val.location?.lng
  checkedForm.value.addr = val.name
  console.log(val)
  areaValue.value = val.name
  addmark(val.location?.lng, val.location?.lat, aMap)
  map.setCenter([val.location?.lng, val.location?.lat], '', 500)
}

const confirmData = () => {
  if (!checkedForm.value?.lat || !checkedForm.value?.lng) {
    return ElMessage.error('请选择地址')
  }
  emits('confirm', checkedForm.value)
  visible.value = false
  areaValue.value = ''
  map?.destroy()
}
const open = () => {
  initMap()
  visible.value = true
}

defineExpose({
  open
})
onMounted(() => {})
onUnmounted(() => {
  map?.destroy()
})
</script>

<template>
  <el-dialog
    class="dialog companygoodsLog"
    v-model="visible"
    :close-on-click-modal="false"
    :draggable="true"
    :show-close="false"
    title="位置选择"
    destroy-on-close
    style="border-radius: 4px; background-color: #ffffff"
    top="100px"
    width="80%"
  >
    <div style="height: 40px; width: 100%; display: flex; align-items: center">
      <el-select
        v-model="areaValue"
        filterable
        style="width: 350px"
        remote
        reserve-keyword
        placeholder="请输入关键词"
        :remote-method="remoteMethod"
        :loading="loading"
        @change="currentSelect"
      >
        <el-option
          v-for="item in areaList"
          :key="item.id"
          :label="item.name"
          :value="item"
          class="one-text"
        >
          <span style="float: left">{{ item.name }}</span>
          <span style="float: right; color: #8492a6; font-size: 13px">{{ item.district }}</span>
        </el-option>
      </el-select>
    </div>
    <div id="container" class="map"></div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="visible = false"><span style="color: #5e5e5e">关闭</span></el-button>
        <el-button color="#68964C" @click="confirmData"> 确定 </el-button>
      </span>
    </template>
  </el-dialog>
</template>
<style scoped lang="scss">
.map {
  width: 100%;
  height: calc(70vh);
}
</style>

2、海量标点功能:

交互操作:

1、点击查询客户后返回客户list 内包含客户信息以及客户的经纬度定位(业务需求所以返回这些信息 如果只撒点就只需要 经纬度信息lat 、lng)
2、查询后将直接在map中展示

效果图

在这里插入图片描述

海量标点功能模块代码

因代码涉及其他业务需求 全部粘贴偏长 为了简洁这边就只展示关键代码部分

<script setup lang="ts">
import { ref } from 'vue'
/**
*父组件index调用webMap组件
*/

interface form {
  [key: string]: any
}


//此数据就是地图选址组件 回传回来的数据 
const form = ref<form>({
  addr: '',//地址名
  addrTypes: [1, 2, 3], 
  locationLat: null, //纬度
  locationLon: null  //经度
})

// #region 列表勾选 start
//具体列表table的代码就不展示了 这里只是为了说明selectValue的数据来源
const selectValue = ref([])

const handleSelectionChange = (val) => {
  selectValue.value = val
}

//因个人二封了table组件(具体问题图片请看 pic1 )
//rowkey 如果为row的话 操作完回显勾选状态回异常 所以只能用id 使用id回显没问题
// 但是使用id 无法拿到row数据(具体问题图片请看 issue1图、issue2图)
//所以使用以下方法进行数据匹配 筛出id选中项的row数据
//匹配最终list数据
const lonAndLatData = ref([])
watch(
  [() => customerList.value, () => selectValue.value],
  ([list, select]) => {
    const arr = []
    if (select.length) {
      list?.map((item) => {
        select?.map((item2) => {
          if (item.id === item2) {
            arr.push(item)
          }
        })
      })
      lonAndLatData.value = arr
    } else {
      list?.map((item) => {
        arr.push(item)
      })
      lonAndLatData.value = arr
    }
    console.log('lonAndLatData.value ', lonAndLatData.value)
  },
  {
    immediate: true,
    deep: false
  }
)

//#endregion 列表勾选 end
</script>

<template>
//...省略了其他业务代码
		 <WebMapLocation
            :selectValue="selectValue" //选中的id数据 为了实现列表勾选数据和地图的切换 (业务需求列表的选点直接在地图上展示)
            :formData="form" //中心点数据
            :list="customerList"  //查询的客户直接展示在地图上 
            :selectionData="lonAndLatData" // 选中的id数据与list数据匹配最后筛选出匹配项进行传递
            @chose-data="setSelectValue"  //选中数据的回调
            @delete-data="deleteSelectValue" // 取消选中数据的回调
          />
 //...省略了其他业务代码
</template>


pic1

在这里插入图片描述

issue1图:

在这里插入图片描述

issue2图:

在这里插入图片描述

WebMapLocation.vue组件代码

<script setup lang="ts">
import AMapLoader from '@amap/amap-jsapi-loader'
import { ref, onMounted, onUnmounted } from 'vue'
import { assign, cloneDeep } from 'lodash-es'
import { ElMessage } from 'element-plus'
import defaultCustomer from '@/assets/imgs/defaultCustomer.png'
import checkCustomer from '@/assets/imgs/checkCustomer.png'

const props = defineProps({
  formData: {
    type: Object,
    default: () => {}
  },
  list: {
    type: Array,
    default: () => []
  },
  selectValue: {
    type: Array,
    default: () => []
  },
  selectionData: {
    type: Array,
    default: () => []
  }
})

window._AMapSecurityConfig = {
  // 安全密钥
  securityJsCode: '申请的密钥'
}
const emits = defineEmits(['confirm', 'choseData', 'deleteData'])
let map: any = null
const info = ref({
  lat: 0,
  lng: 0,
  radius: 0,
  title: ''
})
const checkedForm: any = ref({
  addr: '',
  lat: '',
  lng: ''
})
let AutoComplete: any = null
let aMap: any = null
let geoCoder: any = null

let massData = ref([])
const initMap = () => {
  AMapLoader.load({
    key: '申请的key', // 申请好的Web端开发者Key
    version: '2.0', //加载的 JSAPI 的版本
    plugins: ['AMap.Geocoder', 'AMap.AutoComplete'] // 需要的插件
  })
    .then((AMap: any) => {
      aMap = AMap
      map = new AMap.Map('localMap', {
        // 设置地图容器id
        viewMode: '2D', // 是否为3D地图模式
        zoom: 11, // 初始化地图级别
        center: [119.330221107, 26.0471254966] // 初始化地图中心点位 福州市
        // center: [116.397428, 39.90923] // 初始化地图中心点位置置  北京市
      })
      AutoComplete = new AMap.AutoComplete({
        city: '全国'
      })
      geoCoder = new AMap.Geocoder({
        // city: '010', //城市设为北京,默认:“全国”
        city: '0591', //城市设为福州市
        radius: 1000 //范围,默认:500
      })

      addmark(info.value.lng, info.value.lat, AMap)

      massMarks(AMap)

      circleMarker(info.value.lng, info.value.lat, info.value.radius, AMap)
      // map.on('click', (e: any) => {
      //   geoCoder.getAddress(e.lnglat, function (status: any, result: any) {
      //     checkedForm.value.addr = result.regeocode.formattedAddress
      //   })

      //   console.log('checkedForm.value', checkedForm.value)
      //   addmark(e.lnglat.getLng(), e.lnglat.getLat(), AMap)
      // })
    })
    .catch((e) => {
      console.log(e)
    })
}
let marker: any = null
let circle: any = null
let mass: any = null
// //创建圆形点标记 CircleMarker 实例
const circleMarker = (lat: any, lng: any, radius: any, AMap: any) => {
  console.log('lat', lat)
  console.log('radius', radius * 1000 + '米')
  circle = new AMap.Circle({
    center: [lat, lng], //圆心
    radius: radius * 1000, //半径
    borderWeight: 3, //描边的宽度
    strokeColor: '#FF33FF', //轮廓线颜色
    strokeOpacity: 1, //轮廓线透明度
    strokeWeight: 1, //轮廓线宽度
    fillOpacity: 0.4, //圆形填充透明度
    strokeStyle: 'solid', //轮廓线样式
    strokeDasharray: [10, 10],
    fillColor: '#1791fc', //圆形填充颜色
    zIndex: 50 //圆形的叠加顺序
  })
  map.add(circle)
  //将覆盖物调整到合适视野
  map.setFitView([circle])
}
// //创建圆形点标记 CircleMarker 实例
const massMarks = (AMap: any) => {
  const style = [
    {
      url: defaultCustomer, //图标地址
      anchor: new AMap.Pixel(6, 19), //图标显示位置偏移量,基准点为图标左上角
      size: new AMap.Size(30, 30) //图标的尺寸,
    },
    {
      url: checkCustomer, //图标地址
      anchor: new AMap.Pixel(6, 19), //图标显示位置偏移量,基准点为图标左上角
      size: new AMap.Size(30, 30) //图标的尺寸,
    }
  ]
  console.log('massData.value', massData.value)
  mass = new AMap.MassMarks(massData.value, {
    zIndex: 999, //海量点图层叠加的顺序
    zooms: [3, 19], //在指定地图缩放级别范围内展示海量点图层
    cursor: 'pointer', //指定鼠标悬停时的鼠标样式
    style: style //设置样式对象
  })

  mass.on('click', function (e) {
    //移除文档弹窗
    CustomerInfoDataRef.value.close()
    handleMassClick(e)
  })

  mass.setMap(map)
}

const addmark = (lat: any, lng: any, AMap: any) => {
  console.log('  info.value', info.value)
  console.log('lat', lat)
  console.log('lng', lng)
  marker && removeMarker()
  marker = new AMap.Marker({
    icon: new AMap.Icon({
      image: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png',
      size: new AMap.Size(22, 28), //图标所处区域大小
      imageSize: new AMap.Size(22, 28) //图标大小
    }),

    position: new AMap.LngLat(lat, lng),
    offset: new AMap.Pixel(-10, -30),
    title: info.value.title,
    zoom: 13
  })
  checkedForm.value.lat = lng
  checkedForm.value.lng = lat
  map.add(marker)
  // marker.setIcon('//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png')
  map.setCenter([lat, lng], '', 500)
}

const removeMarker = () => {
  map.remove(marker)
}

onMounted(() => {
  initMap()
})
onUnmounted(() => {
  map?.destroy()
  console.log('销毁')
})

watch(
  () => props.formData,
  (val) => {
    if (val) {
      const deepVal = cloneDeep(val)
      info.value.lat = Number(deepVal.locationLat)
      info.value.lng = Number(deepVal.locationLon)
      info.value.radius = Number(deepVal.range)
      info.value.title = deepVal.addr
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.list,
  (val) => {
    if (val.length > 0) {
      initMap()
    }
  },
  {
    immediate: true,
    deep: true
  }
)

watch(
  [() => props.selectionData, () => props.selectValue],
  ([selectionData, selectValue]) => {
    if (selectionData.length > 0) {
      massData.value = []
      selectionData.forEach((item: any) => {
        massData.value.push({
          lnglat: item.lonAndLat,
          name: item.loanCustomerName,
          info: item,
          style: selectValue.includes(item.id) ? 1 : 0
        })
      })
      initMap()
    }
  },
  {
    deep: true,
    immediate: true
  }
)

// #region mass点击模块 start
const CustomerInfoDataRef = ref()
const customerInfo = ref({})
const handleMassClick = (e: any) => {
  const data = e.data
  customerInfo.value = data.info
  CustomerInfoDataRef.value.open()
}

//#endregion mass点击模块 end

// #region 弹窗处理数据模块 start
const choseData = (val) => {
  emits('choseData', val)
}

const deleteData = (val) => {
  emits('deleteData', val)
}

//#endregion 弹窗处理数据模块 end
</script>

<template>
  <div id="localMap" class="localMap"> </div>
  <CustomerInfoData
    ref="CustomerInfoDataRef"
    :selectValue="selectValue"
    :info-data="customerInfo"
    :select-data="selectionData"
    @chose-data="choseData"
    @delete-data="deleteData"
  />
</template>
<style scoped lang="scss">
.localMap {
  width: 100%;
  height: calc(70vh);
}
</style>

标点消息弹窗 选中、取消选中数据操作并替换icon图标 交互操作:

1.点击标点消息 弹出消息弹窗
2.选中、取消数据,在list数据中剔除或添加数据 并替换选中和默认icon图标
3.下发任务 将选中的idlist 数组数据传递给后端 完成任务下发 实现需求

效果图:

在这里插入图片描述

消息弹窗组件 CustomerInfoData.vue

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { debounce } from 'lodash-es'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/**
 * @description: 地图客户弹窗
 *
 */
//传递的props
const props = defineProps({
  infoData: {
    type: Object,
    default: () => {}
  },
  selectValue: {
    type: Array,
    default: () => []
  },
  selectData: {
    type: Array,
    default: () => []
  }
})
const person = ref({})
const api = ref()
//表单ref
const formRef = ref()
//表单数据
const form = ref({
  type: '',
  date: ''
})

// #region 单选模块 start
const closeFlag = ref(0)

//单选取消反选
const radioChange = (e) => {
  e === closeFlag.value ? (closeFlag.value = '') : (closeFlag.value = e)
  if (closeFlag.value) {
    emit('choseData', props.infoData.id)
  } else {
    emit('deleteData', props.infoData.id)
  }
}
//#endregion 单选模块 end

//表单规则
const formRules = reactive({
  type: [{ required: true, message: '请选择结清类型', trigger: 'blur' }],
  date: [{ required: true, message: '请选择结清日期', trigger: 'blur' }]
})
//弹窗展示
const dialogVisible = ref(false)

//切换弹窗
/** 打开弹窗 */
const open = async () => {
  dialogVisible.value = true
}

const close = () => {
  dialogVisible.value = false
  closeFlag.value
}

watch(
  () => dialogVisible.value,
  (val) => {
    if (val) {
      if (props.infoData) {
        if (props.selectValue.includes(props.infoData.id)) {
          closeFlag.value = 1
        } else {
          closeFlag.value = 0
        }
      }
    }
  },
  {
    immediate: true,
    deep: true
  }
)

const emit = defineEmits(['close', 'choseData', 'deleteData'])
defineExpose({ open, close }) // 提供 open 方法,用于打开弹窗
</script>
<template>
  <div :before-close="close" top="5vh" class="infoData-modal" v-if="dialogVisible" :modal="false">
    <header>
      <div class="name">{{ infoData.loanCustomerName }}</div>
      <div class="close">
        <el-radio-group v-model="closeFlag">
          <el-radio :label="1" @click.prevent.stop="radioChange(1)">选中</el-radio>
        </el-radio-group>
        <el-icon class="ml2 close-icon" @click.stop="close"><Close /></el-icon>
      </div>
    </header>
    <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
      <el-form-item class="nameEllipsis" label="关注等级:"
        >{{ infoData.concernLevelCn }}
      </el-form-item>
      <el-form-item class="nameEllipsis" label="客户标签:">{{
        infoData.customerTag
      }}</el-form-item>
      <el-form-item class="nameEllipsis" label="电话号码:">{{ infoData.phone }}</el-form-item>
      <el-form-item class="nameEllipsis" label="身份证:">{{
        infoData.customerIdCard
      }}</el-form-item>
      <el-form-item class="nameEllipsis" label="贷款金额:">{{ infoData.loanAmount }}</el-form-item>
      <el-form-item class="nameEllipsis" label="外访地址:">{{ infoData.addressStr }}</el-form-item>
    </el-form>
  </div>
</template>
<style lang="scss" scoped>
.infoData-modal {
  width: 270px;
  min-height: 20px;
  position: fixed;
  right: 6%;
  bottom: 12%;
  z-index: 1000;
  background: #ffffff;
  padding: 20px;
  :deep(.el-form-item) {
    margin-bottom: 0px !important;
  }
}
header {
  .name {
    font-size: 18px;
    font-weight: bolder;
  }
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.close {
  cursor: pointer;
  display: flex;
  align-items: center;
}
.close-icon:hover {
  color: #409eff;
}
.close-icon :active {
  color: #409eff;
}
</style>

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐