지정판매소 주소·지도 연동과 관련 설정을 반영
지정판매소 등록/수정/목록에 카카오 주소 검색 및 지도 연동 컴포넌트를 적용하고, 관련 모델·SQL 스크립트·테스트 설정을 함께 정리해 기능 동작 기반을 맞췄다. Made-with: Cursor
This commit is contained in:
153
app/Views/components/kakao_map_modal.php
Normal file
153
app/Views/components/kakao_map_modal.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$key = trim((string) ($kakaoJavascriptKey ?? ''));
|
||||
?>
|
||||
<div id="kakao-map-modal" class="hidden fixed inset-0 z-[300] flex items-center justify-center p-4" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="kakao-map-modal-title">
|
||||
<div class="absolute inset-0 bg-black/50" id="kakao-map-modal-backdrop"></div>
|
||||
<div class="relative z-[301] w-full max-w-2xl max-h-[90vh] flex flex-col rounded border border-gray-300 bg-white shadow-lg overflow-hidden">
|
||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 bg-gray-50 shrink-0">
|
||||
<span id="kakao-map-modal-title" class="text-sm font-bold text-gray-800">위치</span>
|
||||
<button type="button" id="kakao-map-modal-close" class="text-gray-600 hover:text-gray-900 text-xl leading-none px-1" aria-label="닫기">×</button>
|
||||
</div>
|
||||
<div id="kakao-map-modal-container" class="w-full bg-gray-100" style="min-height: 380px; height: 50vh;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var APP_KEY = <?= json_encode($key, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
var modal = document.getElementById('kakao-map-modal');
|
||||
var backdrop = document.getElementById('kakao-map-modal-backdrop');
|
||||
var btnClose = document.getElementById('kakao-map-modal-close');
|
||||
var mapContainer = document.getElementById('kakao-map-modal-container');
|
||||
var mapInstance = null;
|
||||
var markerInstance = null;
|
||||
var scriptLoading = false;
|
||||
var pendingAfterLoad = [];
|
||||
|
||||
function hideModal() {
|
||||
if (!modal) return;
|
||||
modal.classList.add('hidden');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
if (!modal) return;
|
||||
modal.classList.remove('hidden');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
function runPending() {
|
||||
var q = pendingAfterLoad;
|
||||
pendingAfterLoad = [];
|
||||
q.forEach(function (fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
function ensureScript(cb) {
|
||||
if (!APP_KEY) {
|
||||
window.alert('카카오맵 JavaScript 키가 설정되지 않았습니다. .env에 kakao.javascriptKey를 설정해 주세요. (Kakao Developers → 앱 키 → JavaScript 키)');
|
||||
return;
|
||||
}
|
||||
if (typeof kakao !== 'undefined' && kakao.maps) {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
pendingAfterLoad.push(cb);
|
||||
if (scriptLoading) {
|
||||
return;
|
||||
}
|
||||
scriptLoading = true;
|
||||
var s = document.createElement('script');
|
||||
s.charset = 'UTF-8';
|
||||
s.async = true;
|
||||
// 동적 삽입 시 autoload=false 후 kakao.maps.load() 필수 (카카오 웹 가이드)
|
||||
s.src = 'https://dapi.kakao.com/v2/maps/sdk.js?appkey=' + encodeURIComponent(APP_KEY) + '&libraries=services&autoload=false';
|
||||
s.onload = function () {
|
||||
scriptLoading = false;
|
||||
if (typeof kakao === 'undefined' || !kakao.maps || typeof kakao.maps.load !== 'function') {
|
||||
pendingAfterLoad = [];
|
||||
window.alert(
|
||||
'카카오맵 API를 불러올 수 없습니다.\n\n' +
|
||||
'Kakao Developers → 내 애플리케이션 → 해당 앱 → 「제품 설정」에서 「Kakao Map」(지도) / 로컬 API를 사용 설정으로 켜 주세요.\n' +
|
||||
'(비활성 시 서버에서 OPEN_MAP_AND_LOCAL 오류가 납니다.)\n\n' +
|
||||
'또한 플랫폼(Web)에 이 사이트 주소(예: http://localhost:8080)가 등록되어 있어야 합니다.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
kakao.maps.load(function () {
|
||||
runPending();
|
||||
});
|
||||
};
|
||||
s.onerror = function () {
|
||||
scriptLoading = false;
|
||||
pendingAfterLoad = [];
|
||||
window.alert(
|
||||
'카카오맵 스크립트를 불러오지 못했습니다.\n\n' +
|
||||
'• 네트워크·차단(광고 차단) 확인\n' +
|
||||
'• Kakao Developers → 제품 설정에서 「Kakao Map」활성화\n' +
|
||||
'• 플랫폼(Web)에 접속 중인 URL 등록'
|
||||
);
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
if (btnClose) {
|
||||
btnClose.addEventListener('click', hideModal);
|
||||
}
|
||||
if (backdrop) {
|
||||
backdrop.addEventListener('click', hideModal);
|
||||
}
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape' || !modal || modal.classList.contains('hidden')) {
|
||||
return;
|
||||
}
|
||||
hideModal();
|
||||
});
|
||||
|
||||
window.openDesignatedShopKakaoMap = function (addressQuery) {
|
||||
var q = String(addressQuery || '').trim();
|
||||
if (!q) {
|
||||
window.alert('주소가 없습니다.');
|
||||
return;
|
||||
}
|
||||
ensureScript(function () {
|
||||
if (typeof kakao === 'undefined' || !kakao.maps || !kakao.maps.services) {
|
||||
window.alert('카카오맵을 초기화할 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
var geocoder = new kakao.maps.services.Geocoder();
|
||||
geocoder.addressSearch(q, function (result, status) {
|
||||
if (status !== kakao.maps.services.Status.OK || !result || !result[0]) {
|
||||
window.alert('주소를 지도에서 찾을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
var coords = new kakao.maps.LatLng(result[0].y, result[0].x);
|
||||
showModal();
|
||||
if (!mapInstance) {
|
||||
mapInstance = new kakao.maps.Map(mapContainer, {
|
||||
center: coords,
|
||||
level: 3
|
||||
});
|
||||
} else {
|
||||
mapInstance.setCenter(coords);
|
||||
mapInstance.setLevel(3);
|
||||
}
|
||||
if (markerInstance) {
|
||||
markerInstance.setMap(null);
|
||||
}
|
||||
markerInstance = new kakao.maps.Marker({ position: coords, map: mapInstance });
|
||||
setTimeout(function () {
|
||||
if (mapInstance) {
|
||||
mapInstance.relayout();
|
||||
mapInstance.setCenter(coords);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user