민초맵 개발일지 Day.2~3

하늘·2024년 4월 8일
0

2일차 일지를 분명 업로드했던거같은데 글목록에서 보이지않는다.....

어쩔수없지 후다닥 요약해서 써보겠습니다!

<진행 상황>

db연결해서 마커, 오버레이띄우고 디테일 라우터 완료


💚1. 카카오맵 마저 하기

처음 기획땐 카카오맵 검색 기능을 사용하려 했는데 '민트초코 프라페' 같이 결과가 잘 나오지 않는 키워드에 대처하기 어려워 db에 가게 정보를 저장하고 데이터의 좌표대로 마커와 오버레이를 출력하는게 낫다고 판단했다.

그래서 진행한 마커, 오버레이, db 연결!

1-1. 이미지 마커

끝이 뾰족한게 마커같아서 뾰족한 음식을 고민하다가 아이스크림 콘으로 낙찰
쓱싹 그려보았당(마우스 수제)

1-2. 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
}

1-3. vue에서 데이터 불러오기

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; // 에러 처리
    }
};

1-4. store

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: {}
})

1-5. store 사용하기

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();
});

1-6. 커스텀 오버레이, 마커 코드 수정

마커 이미지는 경로와 사이즈 수정으로 간단하게 변경 가능하다.

// 이미지 마커와 커스텀 오버레이
    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
        });
    })

💙2. 라우터 작성

팝업을 띄울까 고민했는데 라우터 개념을 잡기 위해 라우터를 쓴다!
(그리고 1도 잡지 못했다)

2-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

2-2. Detail.vue

스니펫이 있음에도 기본 포맷 찾는 사람 나야나...

Detail.vue를 디테일 패스의 컴포넌트로 지정했으니 해당 페이지에서 컴포넌트들을 불러와준다.

<template>
    <DetailComp />
</template>

<script>
import DetailComp from '@/components/DetailComp.vue'

export default {
    name: 'Detail',
    components: {
        DetailComp
    }
}
</script>

작동 테스트 후 디테일에 정보 뿌리기!


🤎3. 디테일 데이터 가져오기

이 파트에서 진짜 엄청 헤맸다.
이유는 일단 vue 사용방법을 아직도 모르겠다는 것과... 되야하는거 아님?? 왜 안되냐 하고 또 뒤적뒤적

3-1. 파라미터 아이디 가져오기

useRoute를 활용해 id를 가져와준다.

<script setup lang="ts">

import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id)

3-2. store 데이터 가져오기

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[]);
    ...
    

3-3. find

일치하는 아이디값을 가진 객체를 찾는다!
else는 url값을 임의로 조정하여 디테일에 접근하는 경우를 생각해 작성했다.

const filteredData = await data.value.find(item => item.id === Number(route.params.id));
    if(filteredData){
        detailData.value = filteredData;
    } else{
        window.alert('잘못된 접근입니다.')
    }

3-4. template

데이터가 있을때 출력되도록 조건문을 달아준당

<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>

3-5. 별점

부트스트랩에 별점 rate range가 없어서 ant-design-vue를 사용했다. 사용한 이유는 사용법이 간단하고 디자인이 이쁘고 다양하며 드래그도 지원해주기때문이당💖

우선 설치 후 main에서 import해준다.

main.ts

// 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')

컴포넌트로 별점 부분을 분리해준다.

DetailComp.vue

// src/components/DetailComp.vue
<StarRating :rate="averageRate"/>

demo의 코드를 복사해오고 props를 받아왔다.
=> 부모 컴포넌트(DetailComp)에서 평균 별점(averageRate)을 받아 해당 숫자를 value로 써야하기 때문에 ㅇㅅㅇ

defineProps()로 rate를 받아온당!

부모 컴포넌트에서 넘겨줄 숫자는

반복문(토탈 += 리뷰.별점) 
averageRateNumber = 토탈 / 리뷰.length 

로 계산해야할거같은데 아직 리뷰 작업을 못해서 상수로 넣어봤다.

DetailComp.vue

// 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으로 설정했다.

그럼 요렇게 적당히 모서리가 동그랗고 이쁜 별이 나온당

StarRating.vue

// 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>

▼ 지금까지의 폴더 구조!

내일은 디테일 마크업, 스타일 적용하고
리뷰 데이터 어떻게 할건지 고민을 해야겠다.

profile
새싹 프론트엔드 개발자

0개의 댓글

관련 채용 정보