✨✨使用vue3打造一个el-form表单及高德地图的关联组件实例✨

✨1. 实现功能

  1. 🌟表单内显示省市县以及详细地址
    • 点击省市县输入框时,打开对应地图弹窗,进行位置选择
    • 选择位置回显入对应输入框
    • 表单内的省市县以及地址输入框同外嵌表单走相同的校验方式
    • 触发校验后点击reset实现清除校验与清空数据
  2. 🌟地图内展示地址搜索框以及地图坐标图
    • 搜索框展示当前经纬度地址
    • 搜索框可输入自定义地址,下拉菜单展示范围兴趣点和道路信息,点击可进行搜索
  3. 🌟 单独封装每个组件,使form-itemdialog以及amap三个组件可单独使用

✨2. 示例图

  1. 💖示例图1:💖
    在这里插入图片描述

  2. 💖💖示例图2:💖
    在这里插入图片描述

  3. 💖💖💖示例图3:💖
    在这里插入图片描述

  4. 💖💖💖💖示例图4:💖
    在这里插入图片描述

  5. 💖💖💖💖💖示例图5:💖
    在这里插入图片描述

✨3. 组件代码

🌹1. 组件目录结构

在这里插入图片描述

2. 🍗 🍖地图组件AmapContainer.vue
<template>
  <div v-loading="loading">
    <input type="text" class="address" v-model="iMap.address" id="inputAddress" />
    <div id="container"></div>
  </div>
</template>

<script setup lang="ts" name="AmapContainer">
import { ref, reactive, computed, watch, onMounted, onUnmounted } from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";
import { AMAP_MAP_KEY, AMAP_SECRET_KEY } from "@/config";
import { getBrowserLang } from "@/utils";
import { useGlobalStore } from "@/stores/modules/global";
import { IMap } from "../interface/index";

const globalStore = useGlobalStore();
const language = computed(() => {
  if (globalStore.language == "zh") return "zh_cn";
  if (globalStore.language == "en") return "en";
  return getBrowserLang() == "zh" ? "zh_cn" : "en";
});

const loading = ref(true);

interface ExtendsWindow extends Window {
  _AMapSecurityConfig?: {
    securityJsCode: string;
  };
}
let _window: ExtendsWindow = window;

// 定义map实例
let map: any = null;

const iMap = reactive<IMap>({
  province: "",
  city: "",
  district: "",
  address: "",
  lnglat: [114.525918, 38.032612],
  canSubmit: true
});

watch(
  () => iMap.address,
  () => {
    iMap.canSubmit = !iMap.address;
  }
);

onMounted(() => {
  initMap();
});

onUnmounted(() => {
  map?.destroy();
});

// 初始化地图
const initMap = async () => {
  _window._AMapSecurityConfig = {
    securityJsCode: AMAP_SECRET_KEY // ❓高德秘钥👇👇下方会有👇👇
  };
  AMapLoader.load({
    key: AMAP_MAP_KEY, // ❓申请好的Web端开发者Key,首次调用 load 时必填👇👇下方会有👇👇
    version: "2.0", // ❓指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    plugins: ["AMap.ToolBar", "AMap.Scale", "AMap.Marker", "AMap.Geocoder", "AMap.AutoComplete"] //需要使用的的插件列表
  })
    .then(AMap => {
      map = new AMap.Map("container", {
        // 设置地图容器id
        viewMode: "2D", // 是否为3D地图模式
        zoom: 11, // 初始化地图级别
        center: iMap.lnglat // 初始化地图中心点位置
      });
      //创建工具条插件实例
      const toolbar = new AMap.ToolBar({
        position: {
          top: "110px",
          right: "40px"
        }
      });
      map.addControl(toolbar);

      //创建比例尺插件实例
      const Scale = new AMap.Scale();
      map.addControl(Scale);

      //创建标记插件实例
      const Marker = new AMap.Marker({
        position: iMap.lnglat
      });
      map.addControl(Marker);

      //创建地理编码插件实例
      const Geocoder: any = new AMap.Geocoder({
        radius: 1000, //以已知坐标为中心点,radius为半径,返回范围内兴趣点和道路信息
        extensions: "base", //返回地址描述以及附近兴趣点和道路信息,默认“base | all”
        lang: language.value
      });

      //返回地理编码结果
      Geocoder.getAddress(iMap.lnglat, (status, result) => {
        if (status === "complete" && result.info === "OK") {
          iMap.province = result.regeocode.addressComponent.province;
          iMap.city = result.regeocode.addressComponent.city;
          iMap.district = result.regeocode.addressComponent.district;
          iMap.address = result.regeocode.formattedAddress;
          AutoComplete.setCity(iMap.address);
          loading.value = false;
        }
      });
      // 根据输入关键字提示匹配信息
      const AutoComplete = new AMap.AutoComplete({
        input: "inputAddress",
        city: iMap.address,
        datatype: "all",
        lang: language.value
      });

      AutoComplete.on("select", result => {
        iMap.lnglat = [result.poi.location.lng, result.poi.location.lat];
        setPointOrAddress();
      });

      //点击地图事件
      map.on("click", e => {
        iMap.lnglat = [e.lnglat.lng, e.lnglat.lat];
        setPointOrAddress();
      });

      // 设置地图点坐标与位置交互
      const setPointOrAddress = () => {
        Marker.setPosition(iMap.lnglat);
        map.setCenter(iMap.lnglat);
        map.setZoom(12);
        Geocoder.getAddress(iMap.lnglat, (status, result) => {
          if (status === "complete" && result.info === "OK") {
            iMap.province = result.regeocode.addressComponent.province;
            iMap.city = result.regeocode.addressComponent.city;
            iMap.district = result.regeocode.addressComponent.district;
            iMap.address = result.regeocode.formattedAddress;
          }
        });
      };
    })
    .catch(e => {
      console.log(e);
    });
};

defineExpose({
  iMap
});
</script>

<style scoped lang="scss">
@import "../index.scss";
</style>

<style lang="scss">
.amap-sug-result {
  z-index: 10000;
}
</style>

🍀3. 弹窗组件AmapDialog.vue 🍀
<template>
  <el-dialog :model-value="visible" title="请选择" width="800" :before-close="handleClose">
    <AmapContainer ref="amapContainer" />
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleClose">取消</el-button>
        <el-button type="primary" :disabled="amapContainer?.iMap?.canSubmit" @click="handleConfirm">确认</el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script setup lang="ts" name="AmapExplore">
/*🔻
  // 使用方式
  // ❤️amapFlag: 控制弹窗显隐
  // ❤️iMap必须ref定义, 接收选择地址数据
  // 示例:
  // 💥<AmapExplore v-model:visible="amapFlag" v-model:amap="iMap" />💥
*/ 🔺 
import { ref, withDefaults } from "vue";
import { IAddress } from "../interface/index";
import AmapContainer from "./AmapContainer.vue";

withDefaults(
  defineProps<{
    visible: boolean;
    amap: Partial<IAddress>;
  }>(),
  {
    visible: false
  }
);

const amapContainer = ref();

// 定义emits
const emits = defineEmits<{
  "update:amap": [value: IAddress];
  "update:visible": [value: boolean];
}>();

const handleConfirm = () => {
  // delete amapContainer.value?.iMap?.canSubmit;
  emits("update:amap", amapContainer.value?.iMap);
  handleClose();
};

const handleClose = () => {
  emits("update:visible", false);
};
</script>

<style scoped lang="scss"></style>

🌼4. 表单组件AmapExplore/index.vue 🌼
<template>
  <el-row :gutter="gutter" :style="gutterStyle">
    <el-col :span="8">
      <el-form-item prop="province">
        <el-input
          v-model="iMapForm.province"
          ref="provinceRef"
          placeholder="省"
          size="large"
          style="width: 100%"
          @click="handleAmapChange"
        ></el-input>
      </el-form-item>
    </el-col>
    <el-col :span="8">
      <el-form-item prop="city">
        <el-input
          v-model="iMapForm.city"
          ref="cityRef"
          placeholder="市"
          size="large"
          style="width: 100%"
          @click="handleAmapChange"
        ></el-input>
      </el-form-item>
    </el-col>
    <el-col :span="8">
      <el-form-item prop="district">
        <el-input
          v-model="iMapForm.district"
          ref="districtRef"
          placeholder="县"
          size="large"
          style="width: 100%"
          @click="handleAmapChange"
        ></el-input>
      </el-form-item>
    </el-col>
  </el-row>
  <el-col :span="24">
    <el-form-item prop="address">
      <el-input v-model="iMapForm.address" placeholder="请输入详细地址" size="large" style="width: 100%"></el-input>
    </el-form-item>
  </el-col>
  <AmapDialog v-model:visible="amapFlag" v-model:amap="iMapForm" />
</template>

<script setup lang="ts" name="AmapExplore">
import { ref, reactive, watch, inject, watchEffect } from "vue";
import type { FormRules } from "element-plus";
import { IAddress } from "./interface/index";
import AmapDialog from "./components/AmapDialog.vue";

// 栅格间隔与样式
const gutter = 20;
const gutterStyle = {
  width: `calc(100% + ${gutter}px)`,
  "margin-bottom": `${gutter}px`
};

// 接收传入的formData和formRules
const { ruleForm, rules } = inject<{ ruleForm: Object; rules: any }>("aMap", { ruleForm: reactive({}), rules: reactive({}) });

const iMapForm = ref<IAddress>({
  province: "",
  city: "",
  district: "",
  address: "",
  lnglat: []
});

// 若地址有值,则赋予formData
watch(
  () => iMapForm,
  n => {
    // 为防止重复赋值
    if (n.value.province || n.value.city || n.value.district || n.value.address) {
      Object.assign(ruleForm, { ...iMapForm.value });
    }
  },
  {
    deep: true
  }
);

// 另处理经纬度lnglat
watch([() => iMapForm.value.province, () => iMapForm.value.city, () => iMapForm.value.district], n => {
  if (n.some(item => !item)) {
    iMapForm.value.lnglat = [];
  }
});

watch(
  () => iMapForm.value.lnglat,
  n => {
    if (!n.length) {
      Object.assign(ruleForm, iMapForm.value);
    }
  }
);

// 将formData赋值给iMapForm-主要作用为清空重置
watchEffect(() => {
  Object.assign(iMapForm.value, { ...ruleForm });
});

// form校验;
const iMapRules = reactive<FormRules<IAddress>>({
  province: [{ required: true, message: "请选择省", trigger: ["blur", "change"] }],
  city: [{ required: true, message: "请选择市", trigger: ["blur", "change"] }],
  district: [{ required: true, message: "请选择区、县", trigger: ["blur", "change"] }],
  address: [{ required: true, message: "请输入详细地址", trigger: ["blur", "change"] }]
});

// 合并校验数据;
watch(rules, () => Object.assign(rules, { ...iMapRules }), {
  immediate: true,
  deep: true
});

// 地图弹窗
const amapFlag = ref<boolean>(false);
const provinceRef = ref();
const cityRef = ref();
const districtRef = ref();
const handleAmapChange = () => {
  amapFlag.value = true;
  provinceRef.value.blur();
  cityRef.value.blur();
  districtRef.value.blur();
};
</script>

<style scoped lang="scss"></style>

5. 🌿scss文件 🌿
// AmapContainer
.address {
  box-sizing: border-box;
  width: 100%;
  height: 30px;
  padding: 0 12px;
  margin-bottom: 10px;
  line-height: 30px;
  border: 1px solid #ececec;
  border-radius: 4px;
}
#container {
  width: 100%;
  height: 400px;
  padding: 0;
  margin: 0;
}
🌴6. 类型定义interface/index.ts 🌴
export interface IAddress {
  province: string;
  city: string;
  district: string;
  address: string;
  lnglat: number[];
}
export interface IMap extends IAddress {
  canSubmit: boolean;
}

❕ ❕7. 地图组件内使用的高德AMAP_MAP_KEY和秘钥AMAP_SECRET_KEY可以自行设置

// 高德地图 key
export const AMAP_MAP_KEY: string = "****";
// 高德地图 安全密钥
export const AMAP_SECRET_KEY: string = "*****";

✨4. 父组件使用😎

  1. ☝️ 使用组件

<!-- 1. 使用组件 -->
<AmapExplore />
  1. ✌️使用provide向后代传入表单数据formData)和校验规则formRules

// 2. 传入formData和formRules
provide("aMap", { ruleForm, rules });
  1. 👋完整代码示例:
<template>
  <div class="card amap-example">
    <el-form ref="ruleFormRef" :model="ruleForm" :rules label-width="auto" style="max-width: 600px">
      <el-form-item label="Activity name" prop="name">
        <el-input v-model="ruleForm.name" />
      </el-form-item>
      <el-form-item label="地址" required>
        <!-- 1. 使用组件 -->
        <AmapExplore />
      </el-form-item>
      <el-form-item label="备注" prop="remark">
        <el-input v-model="ruleForm.remark" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm(ruleFormRef)"> Create </el-button>
        <el-button @click="resetForm(ruleFormRef)">Reset</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup lang="ts" name="amapExample">
import { reactive, ref, provide } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import AmapExplore from "@/components/AmapExplore/index.vue";

interface RuleForm {
  name: string;
  remark: string;
}
const ruleFormRef = ref<FormInstance>();
let ruleForm = reactive<RuleForm>({
  name: "",
  remark: ""
});

let rules = reactive<FormRules<RuleForm>>({
  name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
  remark: [{ required: true, message: "请输入备注", trigger: "blur" }]
});

// 2. 传入formData和formRules
provide("aMap", { ruleForm, rules });

const submitForm = async (formEl: FormInstance | undefined) => {
  console.log(ruleForm, "s");
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log("submit!");
    } else {
      console.log("error submit!", fields);
    }
  });
};

const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.resetFields();
};
</script>

❗️ 5. 封装实例缺点💦

  1. 当选择地址之后,再次打开地图弹窗,更改地图标记点,地址会实时变更,
  2. 不论点击取消还是确认按钮,都会改变表单内部值
  3. 💢初始不会出现此问题💢
  4. 💪后续会改进😁😁😁😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/615842.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebSocket 详解加入门实操理解加深

WebSocket 介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c; 并进行双向数据传输。 HTTP协议和WebSocket协议对比&#xff1a; HTTP是短连接…

图的顺序存储和链式存储实现

目录 一、顺序存储 有向图&#xff1a; 无向图 代码实现 二、链式存储 有向图 无向图 代码实现 一、顺序存储 主要用到的是一个二维数组&#xff0c;也就是矩阵&#xff0c;直接上栗子&#xff1a; 有向图&#xff1a; 若要储存如下这个有向图&#xff1a; 需要建立一…

前端小程序调用 getLocation 实现地图位置功能,通过 纬度:latitude 经度: longitude 获取当前位置

1、首先登录一下 腾讯的位置服务 有账号就登录没账号就注册&#xff0c; 点击右上角的控制台点击左侧的应用管理 ---> 我的应用 ---->> 创建应用 1、创建应用 2、列表就会显示我们刚刚创建好的 key 3、点击添加 key 4、按照要求填写信息 我们用的是小程序 所以选择…

Spring编程使用DDD的小把戏

场景 现在流行充血领域层&#xff0c;在原本只存储对象的java类中&#xff0c;增加一些方法去替代原本写在service层的crud&#xff0c; 但是例如service这种一般都是托管给spring的&#xff0c;我们使用的ORM也都托管给spring&#xff0c;这样方便在service层调用mybatis的m…

电影院购票管理系统

文章目录 电影院购票管理系统一、项目演示二、项目介绍三、部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 电影院购票管理系统 一、项目演示 电影院售票管理系统 二、项目介绍 基于springbootvue的前后端分离电影院购票管理…

Hive行列转换应用与实现

Hive行列转换应用与实现 1.多行转多列 问题引入 解决方法 2.多行转单列 问题引入 解决方法 3.多列转多行 问题引入 解决方法 4.单列转多行

部署yolov5

1 创建一个yolov5的环境 conda create -n yolov5 python3.8 2 激活环境 conda activate yolov5 3 设置清华源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 4 PyTorch 网站下载pytorch 备注:也可以使用pip install 5 下载 yolov5…

Linux实验三:文件属性及目录操作

目录 一、实验目的二、实验内容三、实验环境四、参考代码五、实验步骤步骤1. 编辑源代码test3.c步骤2. 编译源代码test3.c步骤3. 运行可执行文件test3 六、实验结果七、实验总结 一、实验目的 1、理解Linux中的目录及i节点等基本概念&#xff1b; 2、掌握目录的读写时常用的函…

OpenHarmony 实战开发——轻量带屏解决方案之恒玄芯片移植案例

本文章基于恒玄科技BES2600W芯片的欧智通 Multi-modal V200Z-R开发板 &#xff0c;进行轻量带屏开发板的标准移植&#xff0c;开发了智能开关面板样例&#xff0c;同时实现了ace_engine_lite、arkui_ui_lite、aafwk_lite、appexecfwk_lite、HDF等部件基于OpenHarmony LiteOS-M内…

PCIE协议-2-事务层规范-Message Request Rules

2.2.8 消息请求规则 本文档定义了以下几组消息&#xff1a; INTx 中断信号电源管理错误信号锁定事务支持插槽电源限制支持厂商定义消息延迟容忍度报告&#xff08;LTR&#xff09;消息优化缓冲区冲洗/填充&#xff08;OBFF&#xff09;消息设备就绪状态&#xff08;DRS&#…

函数重载和函数模板

c语言中函数名字不可重复,但是可以写代码实现 普通的函数重载 这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同和返回值没有关系(因为就像我想调用Add(1,2),Add重载的几个函数仅仅返回值不同,编辑器就不知道去找哪一个,就有歧义了) 情况1-数组 int ave(int*pa,i…

计算机毕业设计Python地震预测系统 地震数据分析可视化 地震爬虫 大数据毕业设计 Flink Hadoop 深度学习 机器学习 人工智能 知识图谱

学生信息 姓名&#xff1a;  祁浩 题目&#xff1a; 基于Python的中国地震数据分析与可视化系统的设计与实现 学号&#xff1a; 2020135211 班级&#xff1a; 20大数据本科2班 指导教师&#xff1a; 刘思思 答辩过程 学生开题陈述 为了让学习者更好的了解了解地震…

python数据分析——pandas数据结构2

参考资料&#xff1a;活用pandas库 导入基础数据 # 导入库 import pandas as pd # 读取数据集 dfpd.read_csv(r"..\data\scientists.csv") df.head() 1、DataFrame DataFrame是Pandas中最常见的对象。可以把它看作python存储电子表格式数据的方式。Series数据结构…

手机在网状态多方面重要性

手机在网状态的重要性体现在多个方面&#xff0c;它是现代社会中人们保持联系、获取信息以及进行日常活动不可或缺的一部分。以下是一些关于手机在网状态重要性的详细解释&#xff1a; 通信联系&#xff1a; 手机是在现代社会中进行通信联系的主要工具。当手机处于在网状态时&…

攻防世界-NewsCenter

题目信息 分析过程 题目打开是有个输入框可以用来输入搜索信息&#xff0c;初步判断是个sql注入的题目。接下来判断能否进行sql注入&#xff1a; 输入 hi&#xff0c;有搜索结果&#xff0c;如下图: 输入hi’,无结果&#xff0c;如下图&#xff1a; 初步判定是hi‘后面还有单引…

Android内核之解决报错:error: ISO C90 forbids mixing declarations and code(七十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

璩静是为了薅百度羊毛

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 百度副总裁璩静离职了&#xff0c;网传她的年薪是1500万&#xff0c;而璩静在4月24日注册了一个文化传媒公司&#xff0c;大家都认为璩静是在为离职做准备。但松松我认为不是。 我认为&#xff1a;璩静成立新公司是…

波动性悖论:为何低风险股票长期跑赢高风险对手?

从去年开始&#xff0c;“红利低波”类的产品净值稳步向上&#xff0c;不断新高&#xff0c;让很多人关注到了A股“分红高”、“波动率低”这两类股票。分红高的公司更受投资者青睐&#xff0c;这从基本面的角度很容易理解&#xff0c;那么波动率低的股票明明波动更小&#xff…

前端笔记-day1

文章目录 01-标签的写法02-HTML的基本骨架03-标签的关系04-注释05-标题标签06-段落标签07-换行与水平线标签08-文本格式化标签09-图像的基本使用10-图像的属性12-绝对路径13-超链接14-音频15-视频标签16-招聘案例18-个人简历19-vue简介 01-标签的写法 <strong>文字内容&…

解决NVM 下载node.js慢问题->最新镜像

一、NVM 介绍 nvm是node版本管理工具&#xff0c;可以运行在多种操作系统上。这里主要记录一下在windows系统的安装和使用。 在使用过程中&#xff0c;下载其他版本时会出现下载慢或卡住或下载失败的情况&#xff0c;是因为服务器在国外&#xff0c;网络原因导致&#xff0c;…