1
0

selectMapleaftImages.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <div class="search-layout">
  3. <el-input
  4. v-model="keyword"
  5. :placeholder="$t('sceneHome.nameSearch')"
  6. style="width: 350px"
  7. clearable
  8. @change="onSearch"
  9. >
  10. <template #append>
  11. <el-button @click="onSearch(keyword)" :icon="Search" />
  12. </template>
  13. </el-input>
  14. <div class="rrr">
  15. <div class="search-result" v-show="keyword">
  16. <div
  17. class="search-list"
  18. v-for="(item, index) in keywordList"
  19. @click="hanleItem(item)"
  20. >
  21. {{ item.name||item.formatted_address }}
  22. </div>
  23. <div v-if="issearch && keywordList && keywordList.length == 0" class="noData search-list">{{$t('sys.unSearchData')}}</div>
  24. </div>
  25. <!-- <div class="search-sh" v-show="keyword">
  26. <el-button style="width: 100%" @click="showSearch = !showSearch">
  27. {{ showSearch ? "收起" : "展开" }}搜索结果
  28. </el-button>
  29. </div> -->
  30. </div>
  31. </div>
  32. <div class="def-select-map-layout">
  33. <div class="def-select-map" id="mapEl" ref="mapEl"></div>
  34. </div>
  35. <div class="def-map-info" v-if="searchInfo">
  36. <p><span>{{$t('coord.lat')}}</span>{{ searchInfo.lat }}</p>
  37. <p><span>{{$t('coord.lng')}}</span>{{ searchInfo.lng }}</p>
  38. <!-- <p><span>缩放级别</span>{{ info.zoom }}</p> -->
  39. </div>
  40. </template>
  41. <script setup lang="ts">
  42. import AMapLoader from "@amap/amap-jsapi-loader";
  43. import { Search } from "@element-plus/icons-vue";
  44. import { wgs84_to_gcj02 } from "./map";
  45. import { getTipsList, getGaoDeGaoDeList, getTipsNames, getCaseInfo } from "@/store/case";
  46. import { ref, watchEffect, onMounted, computed } from "vue";
  47. import { QuiskExpose } from "@/helper/mount";
  48. import { ElMessage, ElMessageBox, genFileId } from "element-plus";
  49. import { ui18n } from "@/i18n";
  50. import { debounce } from "@/util";
  51. import html2canvas from "html2canvas";
  52. import L from "leaflet";
  53. import "./leaflet.ChineseTmsProviders.js";
  54. import "leaflet/dist/leaflet.css";
  55. import { router } from "@/router";
  56. export type MapImage = { blob: Blob | null; search: MapInfo | null };
  57. type MapInfo = {
  58. lat: number;
  59. lng: number;
  60. zoom: number;
  61. text: string;
  62. address: string;
  63. id: string;
  64. };
  65. // const layer = L.tileLayer('http://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}')
  66. // const layer = L.tileLayer("http://a.map.jms.gd/tile/osm/{z}/{x}/{y}.png");
  67. // const layer = L.tileLayer('http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}&token=YOUR_API_KEY')
  68. var normalMap = L.tileLayer.chinaProvider("Google.Normal.Map", {
  69. maxZoom: 18,
  70. minZoom: 5,
  71. });
  72. var Gaode = L.tileLayer.chinaProvider("GaoDe.Normal.Map", {
  73. maxZoom: 18,
  74. minZoom: 5,
  75. });
  76. var baseLayers = {
  77. [ui18n.t("coord.edit.gmap")]: Gaode,
  78. };
  79. let map: any = {};
  80. const issearch = ref(false);
  81. let clickMarker;
  82. const keyword = ref("");
  83. const showSearch = ref(true);
  84. const isGoogle = ref(true);
  85. const info = ref<MapInfo>();
  86. const caseInfoData = ref<any>(null);
  87. const searchInfo = ref<MapInfo>();
  88. const mapEl = ref<HTMLDivElement>();
  89. const keywordList = ref([]);
  90. watchEffect(() => {
  91. if (keyword.value) {
  92. showSearch.value = true;
  93. }
  94. });
  95. const caseId = computed(() => {
  96. const caseId = router.currentRoute.value.params.caseId;
  97. if (caseId) {
  98. return Number(caseId);
  99. }
  100. });
  101. function detectProxy() {
  102. return new Promise(resolve => {
  103. const img = new Image();
  104. img.onload = () => resolve(false);
  105. img.onerror = () => resolve(true);
  106. img.src = `https://www.google.com/images/searchbox/desktop_searchbox_sprites318_hr.webp?t=${Date.now()}`;
  107. // 设置超时
  108. setTimeout(() => resolve(true), 1000);
  109. });
  110. }
  111. onMounted(async () => {
  112. isGoogle.value = await detectProxy();
  113. if(!isGoogle.value){
  114. baseLayers[ui18n.t("coord.edit.ggmap")] = normalMap
  115. }
  116. caseInfoData.value = await getCaseInfo(caseId.value);
  117. let center = [22.363947, 113.598948];
  118. if (caseInfoData.value?.latAndLong) {
  119. center = caseInfoData.value.latAndLong.split(",");
  120. }
  121. console.log("caseInfoData", caseInfoData.value.latAndLong, center);
  122. // 'map'为HTML节点id
  123. map = L.map(mapEl.value, {
  124. center: center, //中心坐标
  125. zoom: 14, //缩放级别
  126. zoomControl: true, //缩放组件
  127. attributionControl: false, //去掉右下角logol
  128. layers: [Gaode], //图层
  129. // center: [51.505, -0.09],
  130. // zoom: 13
  131. });
  132. if (caseInfoData.value?.latAndLong) {
  133. clickMarker = L.marker([center[0], center[1]], {
  134. position: [center[0], center[1]],
  135. title: "点击位置",
  136. });
  137. clickMarker.addTo(map);
  138. }
  139. console.log("baseLayers", baseLayers);
  140. L.control.layers(baseLayers, null).addTo(map);
  141. map.on("click", async function (e) {
  142. // 获取点击位置的经纬度坐标
  143. console.log("click", e);
  144. var latitude = e.latlng.lat;
  145. var longitude = e.latlng.lng;
  146. console.log("click", e, [longitude, latitude]);
  147. let ress = await getGaoDeGaoDeList(`${longitude},${latitude}`, 2)
  148. let data = ress.data && JSON.parse(ress.data) || {};
  149. console.log("click",data, ress);
  150. let formatted_address = data.regeocode && data.regeocode.formatted_address || "";
  151. searchInfo.value = {
  152. text: formatted_address.length ? formatted_address : "",
  153. lat: latitude,
  154. lng: longitude,
  155. zoom: 0,
  156. };
  157. clickMarker && clickMarker.remove();
  158. clickMarker = null;
  159. // 在地图上添加标记
  160. clickMarker = L.marker([latitude, longitude], {
  161. position: [latitude, longitude],
  162. title: "点击位置",
  163. });
  164. clickMarker.addTo(map);
  165. map.panTo([latitude, longitude]);
  166. // map.add(clickMarker);
  167. });
  168. });
  169. const resultEl = ref<HTMLDivElement>();
  170. const searchAMap = ref<any>();
  171. watchEffect(async (onCleanup) => {
  172. if (!mapEl.value || !resultEl.value) {
  173. return;
  174. }
  175. //绑定地图移动与缩放事件
  176. map.on("moveend", () => {
  177. info.value = getMapInfo();
  178. });
  179. map.on("zoomend", () => {
  180. info.value = getMapInfo();
  181. });
  182. searchAMap.value = placeSearch;
  183. onCleanup(() => {
  184. searchAMap.value = null;
  185. map.destroy();
  186. });
  187. });
  188. const placeSearch = async () => {
  189. // let res = await
  190. }
  191. const getMapInfo = (): MapInfo => {
  192. var zoom = map.getZoom(); //获取当前地图级别
  193. var center = map.getCenter();
  194. return {
  195. text: "",
  196. zoom,
  197. lat: center.lat,
  198. lng: center.lng,
  199. };
  200. };
  201. const onSearch = (val) => {
  202. issearch.value = true;
  203. getGaoDeGaoDeList(val).then((res) => {
  204. let data = res.data && JSON.parse(res.data);
  205. keywordList.value = data.pois;
  206. keyword.value = val;
  207. console.log("getTipsList",data, keywordList.value, keyword.value);
  208. // if(data && data.geocodes && data.geocodes.length > 0) {
  209. // let item = data.geocodes[0].location.split(",");
  210. // console.log("item", item);
  211. // // 在地图上添加标记
  212. // clickMarker = L.marker([item[1], item[0]], {
  213. // position: [item[1], item[0]],
  214. // title: "点击位置",
  215. // // icon,
  216. // });
  217. // clickMarker.addTo(map);
  218. // map.panTo([item[1], item[0]]);
  219. // }
  220. }).catch((err) => {
  221. ElMessage.error(ui18n.t("err.disconnect"));
  222. });
  223. console.log("onSearch", val, "keyword.value", keyword.value);
  224. };
  225. const hanleItem = async (item) => {
  226. let res = item
  227. console.log("hanleItem", item);
  228. if(item.location) {
  229. res.lng = item.location.split(",")[0];
  230. res.lat = item.location.split(",")[1];
  231. res.name = res.name || item.formatted_address
  232. }else{
  233. let ress = await getTipsNames(item.name);
  234. res = ress.data;
  235. }
  236. keyword.value = "";
  237. searchInfo.value = {
  238. ...res,
  239. text: res.name,
  240. };
  241. clickMarker && clickMarker.remove();
  242. // let icon = L.icon({
  243. // iconUrl: require('./icon.svg'),
  244. // iconSize: [25, 30],
  245. // iconAnchor: [12, 30]
  246. // });
  247. clickMarker = null;
  248. // 在地图上添加标记
  249. clickMarker = L.marker([res.lat, res.lng], {
  250. position: [res.lat, res.lng],
  251. title: "点击位置",
  252. // icon,
  253. });
  254. clickMarker.addTo(map);
  255. map.panTo([res.lat, res.lng]);
  256. // keyword.value = item.name;
  257. // getTipsNames(name).then((ress) => {
  258. // let res = ress.data;
  259. // // longlat = wgs84_to_gcj02(Number(res.lng),Number(res.lat))
  260. // });
  261. // onSearch(item.name);
  262. };
  263. var dataURLtoBlob = function (dataurl) {
  264. var arr = dataurl.split(","),
  265. mime = arr[0].match(/:(.*?);/)[1],
  266. bstr = atob(arr[1]),
  267. n = bstr.length,
  268. u8arr = new Uint8Array(n);
  269. while (n--) {
  270. u8arr[n] = bstr.charCodeAt(n);
  271. }
  272. return new Blob([u8arr], { type: mime });
  273. };
  274. const search = debounce((keyword: string) => {
  275. searchAMap.value.search(keyword);
  276. }, 1000);
  277. watchEffect(() => {
  278. searchAMap.value && search(keyword.value);
  279. });
  280. defineExpose<QuiskExpose>({
  281. submit() {
  282. return new Promise<MapImage>((resolve) => {
  283. console.log("searchInfo", searchInfo.value, mapEl.value);
  284. if (mapEl.value) {
  285. const canvas = mapEl.value.querySelector("canvas") as HTMLCanvasElement;
  286. console.log(canvas, "canvas");
  287. canvas &&
  288. canvas.toBlob((blob) => resolve({ blob, search: searchInfo.value! })); // || resolve({ search: searchInfo.value! });
  289. if (!canvas) {
  290. //div内容生成图片
  291. html2canvas(mapEl.value, {
  292. useCORS: true, // 添加这个选项以解决跨域问题
  293. }).then((canvas) => {
  294. let imgUrl = canvas.toDataURL("image/png");
  295. let blob = dataURLtoBlob(imgUrl);
  296. resolve({ blob, search: searchInfo.value! });
  297. });
  298. }
  299. } else {
  300. resolve({ blob: null, search: null });
  301. }
  302. });
  303. },
  304. });
  305. </script>
  306. <style lang="scss" scoped>
  307. .search-layout {
  308. display: inline-block;
  309. position: relative;
  310. margin-bottom: 15px;
  311. z-index: 2;
  312. }
  313. .rrr {
  314. position: absolute;
  315. left: 0;
  316. right: 0;
  317. z-index: 1;
  318. }
  319. .search-sh,
  320. .search-result {
  321. overflow: hidden;
  322. &.show {
  323. max-height: 450px;
  324. overflow-y: auto;
  325. }
  326. .search-list {
  327. background: #1B1B1C;
  328. padding-left: 10px;
  329. line-height: 36px;
  330. cursor: pointer;
  331. }
  332. }
  333. .def-map-info {
  334. margin-top: 10px;
  335. p {
  336. font-size: 14px;
  337. color: rgba(0, 0, 0, 0.85);
  338. display: inline;
  339. &:not(:last-child)::after {
  340. content: ",";
  341. margin-right: 6px;
  342. }
  343. }
  344. span::after {
  345. content: ":";
  346. }
  347. }
  348. .def-select-map-layout {
  349. --scale: 1.5;
  350. width: 100%;
  351. padding-top: calc((390 / 540) * 100%);
  352. position: relative;
  353. z-index: 1;
  354. }
  355. .def-select-map {
  356. position: absolute;
  357. left: 0;
  358. top: 0;
  359. width: 100%;
  360. height: 100%;
  361. }
  362. </style>