2일차 일지를 분명 업로드했던거같은데 글목록에서 보이지않는다.....
어쩔수없지 후다닥 요약해서 써보겠습니다!
db연결해서 마커, 오버레이띄우고 디테일 라우터 완료
처음 기획땐 카카오맵 검색 기능을 사용하려 했는데 '민트초코 프라페' 같이 결과가 잘 나오지 않는 키워드에 대처하기 어려워 db에 가게 정보를 저장하고 데이터의 좌표대로 마커와 오버레이를 출력하는게 낫다고 판단했다.
그래서 진행한 마커, 오버레이, db 연결!
끝이 뾰족한게 마커같아서 뾰족한 음식을 고민하다가 아이스크림 콘으로 낙찰
쓱싹 그려보았당(마우스 수제)
이전에 vercel에 mongoDB 배포한 서버를 다시 꺼냈다.
route를 추가하고 모델을 추가하는 것으로 간단하게 완료!
프론트에도 데이터타입을 작성해둔다!
스키마 모델 복사해서 양식 조금만 수정하면 끝
지금 생각하니 이미지도 들어가야하는데 ㅇㅅㅇ... 일단 요렇게 했당!
export type mapDataType = {
id: number,
storeName: string,
tel: string,
address: string,
coordinate: mapDataCoordinateType,
openHour: number,
closeHour: number,
menu: mapDataMenuType
}
export type mapDataMenuType = {
name: string,
price: number
}
export type mapDataCoordinateType = {
x: number,
y: number
}
src에 api 폴더를 생성하고 db.ts
를 새로 생성!
src/api/db.ts
경로에서 axios
로 get했당
vue에서는 어떻게 쓰나 검색하니까 react처럼 앞에 VUE_~
를 붙이길래 했더니 안됨(왜죠)
=> Vite는 import.meta.env.환경변수
식으로 사용해야한다고 한당
// src/api/db.ts
import axios from 'axios';
// 데이터를 가져오는 함수
export const getData = async () => {
try {
const response = await axios.get(`${import.meta.env.VITE_DB_URL}`);
return response.data; // 응답 데이터 반환
} catch (error) {
console.error('데이터를 가져오는 데 실패했습니다:', error);
throw error; // 에러 처리
}
};
data를 빈배열로 정의하고 getters에서 데이터를 불러왔다.
store 너무 헷갈려서(문법) 유튜브에서 vuex 완전정복!! 같은 강의를 라디오처럼 틀어두면서 했던거같다.
실행되는 코드는 간단하고 이거지 ㅇㅇ 인데 시행착오가 왤케 복잡한지~
import axios from 'axios';
import { createStore } from 'vuex'
import { getData } from '../api/db';
export default createStore({
state: {
data: []
},
getters: {
async getData(state) {
state.data = await getData()
}
},
mutations: {},
actions: {},
modules: {}
})
vuex
에서 useStore
를 불러오고,
마운트 후 데이터 패치를 실행하기 위해 onMounted
도 불러온다.
<script setup lang="ts">
import { useStore } from 'vuex';
import { onMounted, computed, ComputedRef } from 'vue';
const store = useStore();
const data: ComputedRef<mapDataType[]> = computed(() => (store.state.data));
async function fetchData() {
await store.getters.getData;
initMap(); // 카카오맵
}
onMounted(() => {
fetchData();
});
마커 이미지는 경로와 사이즈 수정으로 간단하게 변경 가능하다.
// 이미지 마커와 커스텀 오버레이
const imageSrc = '마커 이미지 경로',
imageSize = new kakao.maps.Size(마커 이미지 사이즈x, y),
imageOption = { offset: new kakao.maps.Point(마커 이미지 사이즈x, y) };
이어서 카카오맵 api 마커 뿌리기는 아래 코드로 실행되는데,
인자로 이미지경로, 크기, 옵션을 전달해야한다.
약간 함정은 좌표y부터 넣어야한다는거...!
var markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption),
markerPosition = new kakao.maps.LatLng(마커 좌표 y,x);
var marker = new kakao.maps.Marker({
position: markerPosition,
image: markerImage
});
marker.setMap(map);
위 코드를 반복문돌리면 좌표 정보가 담긴 객체 배열을 쫙 뿌릴 수 있지 않을까...? 하는 생각으로
공식 문서의 라이브러리 예제를 보니 for문을 사용한걸 발견했다.
테스트 데이터 2개를 준비하고 데이터 맵을 돌려봤다.
Composition API
에서 ref
로 지정한 data는 반응형 참조를 생성해 .value
속성으로 값에 접근해야한다.
data.value.forEach(item => {
var markerImage =
...
marker.setMap(map);
// 커스텀 오버레이 생성
var content = `<div class="customoverlay">
<h2 class="title">${item.storeName}</h2>
<a href="/#/detail/${item.id}">
자세히보기
</a>
</div>`;
var position = new kakao.maps.LatLng(y좌표, x좌표);
new kakao.maps.CustomOverlay({
map: map,
position: position,
content: content,
yAnchor: 1
});
})
팝업을 띄울까 고민했는데 라우터 개념을 잡기 위해 라우터를 쓴다!
(그리고 1도 잡지 못했다)
router/index.ts
에서 컴포넌트와 createRouter를 불러온다.
추후 뒤로가기 기능을 구현하기 위해 history를 냅뒀는데 삭제해도 되는진 모르겠다.(=타입 보니까 꼭 있어야하는듯)
오버레이의 자세히보기를 클릭하면 파라미터로 id를 넘긴다.
디테일 페이지에선 해당 파라미터를 잡아서 해당하는 데이터를 가져와 뿌리는 익숙한 read
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/Home.vue'
import DetailView from '../views/Detail.vue'
const routes = [
{
path: '/',
name: 'Home',
component: HomeView
},
{
path: '/detail/:id',
name: 'Detail',
component: DetailView
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
스니펫이 있음에도 기본 포맷 찾는 사람 나야나...
Detail.vue를 디테일 패스의 컴포넌트로 지정했으니 해당 페이지에서 컴포넌트들을 불러와준다.
<template>
<DetailComp />
</template>
<script>
import DetailComp from '@/components/DetailComp.vue'
export default {
name: 'Detail',
components: {
DetailComp
}
}
</script>
작동 테스트 후 디테일에 정보 뿌리기!
이 파트에서 진짜 엄청 헤맸다.
이유는 일단 vue 사용방법을 아직도 모르겠다는 것과... 되야하는거 아님?? 왜 안되냐 하고 또 뒤적뒤적
useRoute를 활용해 id를 가져와준다.
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id)
useStore로 가져오고
store.getters.getData(db.get 함수)를 실행했다.
import { useStore } from 'vuex';
const store = useStore();
onMounted(async () => {
await store.getters.getData;
const data = computed(() => store.state.data as mapDataType[]);
...
일치하는 아이디값을 가진 객체를 찾는다!
else는 url값을 임의로 조정하여 디테일에 접근하는 경우를 생각해 작성했다.
const filteredData = await data.value.find(item => item.id === Number(route.params.id));
if(filteredData){
detailData.value = filteredData;
} else{
window.alert('잘못된 접근입니다.')
}
데이터가 있을때 출력되도록 조건문을 달아준당
<section v-if="detailData" id="DetailPageSection">
뒤로가기 버튼은 router.go(-1)
로 처리했다.
go는 history에 step만큼 적용된다고 함
<header>
<button v-on:click="router.go(-1)"><BackSvg /></button>
<h1>{{ detailData.storeName }}</h1>
<button>⭐</button>
</header>
부트스트랩에 별점 rate range가 없어서 ant-design-vue
를 사용했다. 사용한 이유는 사용법이 간단하고 디자인이 이쁘고 다양하며 드래그도 지원해주기때문이당💖
우선 설치 후 main에서 import해준다.
// main.ts
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
createApp(App).use(store).use(router).use(Antd).mount('#app')
컴포넌트로 별점 부분을 분리해준다.
// src/components/DetailComp.vue
<StarRating :rate="averageRate"/>
demo의 코드를 복사해오고 props를 받아왔다.
=> 부모 컴포넌트(DetailComp)에서 평균 별점(averageRate)을 받아 해당 숫자를 value로 써야하기 때문에 ㅇㅅㅇ
defineProps()
로 rate를 받아온당!
부모 컴포넌트에서 넘겨줄 숫자는
반복문(토탈 += 리뷰.별점)
averageRateNumber = 토탈 / 리뷰.length
로 계산해야할거같은데 아직 리뷰 작업을 못해서 상수로 넣어봤다.
// src/components/DetailComp.vue
const averageRate = ref<Number | null>(0);
onMounted(async () => {
...
if(filteredData){
detailData.value = filteredData;
setAverageRate();
}
}
const setAverageRate = () => {
averageRate.value = 3.3;
}
별점에 들어가는 value값은 ref(0)으로 기본값 0으로 설정했다.
그럼 요렇게 적당히 모서리가 동그랗고 이쁜 별이 나온당
// src/components/star/StarRating.vue
<script setup>
import { ref, onMounted, defineProps } from 'vue';
const props = defineProps({
rate: Number
});
const value = ref(0);
onMounted(() => {
value.value = props.rate;
});
</script>
<template>
<a-rate v-model:value="value" allow-half disabled/>
</template>
▼ 지금까지의 폴더 구조!
내일은 디테일 마크업, 스타일 적용하고
리뷰 데이터 어떻게 할건지 고민을 해야겠다.