location.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <template>
  2. <v-dialog v-model="props.isLatlngModal" persistent width="50rem">
  3. <div class="v-common-dialog-wrapper custom-dialog">
  4. <div class="modal-tit">
  5. <strong>지도로 검색</strong>
  6. <button class="btn-close" @click="$emit('closeModal')"></button>
  7. </div>
  8. <div class="v-common-dialog-content">
  9. <div class="map-area" id="map2"></div>
  10. <div class="map-address">
  11. <strong>주소 :</strong>
  12. <p class="color-blue2" v-if="!objSetLatlng.address">
  13. ※ 마우스로 위치를 지정하세요
  14. </p>
  15. <p style="" v-else>{{ objSetLatlng.address }}</p>
  16. </div>
  17. </div>
  18. <div class="btn-wrap">
  19. <v-btn class="custom-btn btn-white mini" @click="$emit('closeModal')">
  20. 취소
  21. </v-btn>
  22. <v-btn class="custom-btn btn-blue mini" @click="fnSaveLatlng"> 저장 </v-btn>
  23. </div>
  24. </div>
  25. </v-dialog>
  26. </template>
  27. <script setup>
  28. /***********************
  29. * import
  30. ************************/
  31. import { useI18n } from "vue-i18n";
  32. import useUtil from "@/composables/useUtil";
  33. import useValid from "@/composables/useValid";
  34. import useEnumCode from "@/composables/useEnumCode";
  35. /***********************
  36. * plugins inject
  37. ************************/
  38. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
  39. // props
  40. const props = defineProps({
  41. isLatlngModal: Boolean,
  42. address: String,
  43. centerPosition: Object,
  44. });
  45. // 참조가능 데이터 설정
  46. defineExpose({
  47. fnInit,
  48. fnReset,
  49. });
  50. // 발신 이벤트 선언
  51. const emit = defineEmits(["closeModal", "setLatlng"]);
  52. const i18n = useI18n();
  53. /***********************
  54. * data & created
  55. ************************/
  56. const map = ref(null); // 카카오 맵 객체
  57. const marker = ref(null); // 카카오맵 마커(핀) 객체
  58. const geocoder = ref(null);
  59. // 주소위치설정값
  60. const objSetLatlng = ref({
  61. lat: "", // 위도
  62. lng: "", // 경도
  63. address: "", // 주소
  64. regionalCode: "", // 지역명 (코드로 변환필요)
  65. });
  66. const sidoCode = ref([]);
  67. watchEffect(() => {
  68. fnGetEnumCode(useLangStore().getLang);
  69. });
  70. /**
  71. * ENUM 업데이트
  72. * @param lang
  73. */
  74. function fnGetEnumCode(lang) {
  75. let objEnum = useEnumCode.getEnumCode(lang);
  76. // ...objEnum.sidoCode
  77. sidoCode.value = _cloneDeep(objEnum.sidoCode);
  78. }
  79. /***********************
  80. * Methods
  81. ************************/
  82. function fnInit() {
  83. if (!useUtil.isNull(props.address)) {
  84. objSetLatlng.value.address = _cloneDeep(props.address);
  85. }
  86. nextTick().then(() => {
  87. if (window.kakao && window.kakao.maps) {
  88. loadMap();
  89. } else {
  90. loadScript();
  91. }
  92. });
  93. }
  94. function fnReset() {
  95. objSetLatlng.value = {
  96. lat: "",
  97. lng: "",
  98. address: "",
  99. };
  100. }
  101. /**
  102. * kakao 스크립트 로드
  103. */
  104. function loadScript() {
  105. const script = document.createElement("script");
  106. script.async = true;
  107. script.onload = () => {
  108. window.kakao.maps.load(loadMap);
  109. };
  110. script.src = `//dapi.kakao.com/v2/maps/sdk.js?autoload=false&libraries=services,clusterer,drawing&appkey=${
  111. import.meta.env.VITE_APP_KAKAO_APP_KEY
  112. }`;
  113. document.head.appendChild(script);
  114. }
  115. /**
  116. * kakao 지도 로드
  117. */
  118. async function loadMap() {
  119. const mapContainer = document.getElementById("map2");
  120. let mapOption = {
  121. center: new kakao.maps.LatLng(props.centerPosition.lat, props.centerPosition.lng), // 지도의 중심좌표
  122. level: 3, // 지도의 확대 레벨
  123. };
  124. map.value = new kakao.maps.Map(mapContainer, mapOption);
  125. // 주소-좌표 변환 객체를 생성합니다
  126. geocoder.value = new kakao.maps.services.Geocoder();
  127. // 지도를 클릭한 위치에 표출할 마커입니다
  128. marker.value = new kakao.maps.Marker({
  129. // 지도 중심좌표에 마커를 생성합니다
  130. position: map.value.getCenter(),
  131. });
  132. // 지도에 마커를 표시합니다
  133. marker.value.setMap(map.value);
  134. let zoomControl = new kakao.maps.ZoomControl();
  135. // map.value.addControl(zoomControl, kakao.maps.ControlPosition.BOTTOMRIGHT)
  136. fnSetEventListener(); // 지도 이벤트 등록
  137. }
  138. /**
  139. * 지도 클릭 이벤트
  140. */
  141. function fnSetEventListener() {
  142. kakao.maps.event.addListener(map.value, "click", function (mouseEvent) {
  143. // 클릭한 위도, 경도 정보
  144. let latlng = mouseEvent.latLng;
  145. marker.value.setPosition(latlng);
  146. // 위/경도값으로 주소 조회
  147. fnSearchAddrDetail(latlng);
  148. // 위치값 설정
  149. objSetLatlng.value.lat = latlng.getLat();
  150. objSetLatlng.value.lng = latlng.getLng();
  151. });
  152. }
  153. /**
  154. * 위/경도 값으로 주소 구하기
  155. */
  156. function fnSearchAddrDetail(coords) {
  157. return new Promise((resolve, reject) => {
  158. fnSearchDetailAddrFromCoords(coords, function (result, status) {
  159. if (status === kakao.maps.services.Status.OK) {
  160. var detailAddr = !!result[0].road_address
  161. ? result[0].road_address.address_name
  162. : "";
  163. detailAddr += `(${result[0].address.address_name})`;
  164. objSetLatlng.value.address = detailAddr;
  165. objSetLatlng.value.regionalCode = fnGetRegionCode(
  166. result[0].address.region_1depth_name
  167. );
  168. resolve();
  169. } else {
  170. reject(status);
  171. }
  172. });
  173. });
  174. }
  175. function fnSearchDetailAddrFromCoords(coords, callback) {
  176. geocoder.value.coord2Address(coords.getLng(), coords.getLat(), callback);
  177. }
  178. // 지역코드 변환
  179. function fnGetRegionCode(regionName) {
  180. let regionCode = "";
  181. sidoCode.value.forEach((item) => {
  182. if (item.title.includes(regionName)) {
  183. regionCode = item.value;
  184. }
  185. });
  186. return regionCode;
  187. }
  188. /**
  189. * 저장
  190. */
  191. async function fnSaveLatlng() {
  192. if (useUtil.isNull(objSetLatlng.value.lat) || useUtil.isNull(objSetLatlng.value.lng)) {
  193. let latlng = new kakao.maps.LatLng(
  194. props.centerPosition.lat,
  195. props.centerPosition.lng
  196. );
  197. try {
  198. await fnSearchAddrDetail(latlng);
  199. objSetLatlng.value.lat = latlng.getLat();
  200. objSetLatlng.value.lng = latlng.getLng();
  201. } catch (error) {
  202. $log.debug("[주소위치설정 실패][error]", JSON.stringify(error));
  203. }
  204. }
  205. let params = {
  206. lat: objSetLatlng.value.lat.toFixed(8),
  207. lng: objSetLatlng.value.lng.toFixed(8),
  208. address: objSetLatlng.value.address,
  209. regionalCode: objSetLatlng.value.regionalCode,
  210. };
  211. emit("setLatlng", params);
  212. }
  213. </script>