dinist

[Laravel + tailwindcss + Vue.js] 날씨 페이지 만들기 - 2 본문

Web/PHP

[Laravel + tailwindcss + Vue.js] 날씨 페이지 만들기 - 2

dinist 2021. 8. 29. 22:36

이미 페이지를 완성한 후 게시글을 작성하는 것이므로 자세한 사진설명이 생략되어 있습니다.

 

이 포스팅에서 사용하는 tailwindcss 버전은 v2 버전입니다. 만약 IE를 지원해야 한다면

es6-promise 와 tailwindcss 버전 v1을 사용해야합니다.

v1와 v2간의 기능 차이가 있을 수 있습니다.

[Laravel + tailwindcss + Vue.js] 날씨 페이지 만들기 - 1

https://dinist.tistory.com/36?category=1223053 

 

[Laravel + tailwindcss + Vue.js] 날씨 페이지 만들기 - 1

라라벨, tailwindcss, Vue.js를 모두 사용해보기위해 다음 영상을 참고 하여 제작합니다. 링크 : https://www.youtube.com/watch?v=_nSzMDx79Ao npm, composer와 같은 기본적인 도구들은 사전에 설치되어있음을 가..

dinist.tistory.com

 

지난 1편 작성 후 2주가 지난 뒤에야 글을 씁니다. 바쁜때도 있었고, 귀찮아서 미루다보니 이렇게 2주가 지났습니다.

반성해야겠어요

 

이제 본론으로 들어가봅시다.

 

지난 1편에는기본적인 설치를 다루었고, 2편에서는 디자인과 실행을 다룰 예정입니다.

 

디자인은 대략적인 설명만 진행할 예정입니다.

 

/resources/js/app.js 파일을 보시면

// Vue.component('example-component', require('./components/ExampleComponent.vue').default);
Vue.component('vue-weather', require('./components/VueWeather.vue').default);

example-component를 vue-weather 로 변경해줍니다.

 

이후 /resources/js/components/ExampleComponent.vue 파일을 /resources/js/components/VueWeather.vue 파일로 이름을 변경합니다.

 

이후 /resources/view/welcome.blade.php 파일에서 <Example-component>를 <vue-weather>로 변경해줍니다.

 

이후 localhost:3000 또는 8000으로 접속해서 페이지가 정상적으로 표시된다면 변경사항 적용이 완료된 것입니다.

 

우선 VueWeather.vue 파일의 <template> 부분을 다음과같이 변경합니다.

<template>
    <div class="weather-container">
        <div id="menu_wrap" class="bg-transparent text-black w-128 px-1 py-2">
            <div class="option">
                <div>
                    <form id="search" @submit="searchPlaces" class="flex justify-end items-center px-0.5">
                        <input type="text" id="keyword" size="20" class="w-full px-2 py-1 rounded-lg outline-none" placeholder="장소명 입력">
                        <button type="submit" class="absolute mr-2">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
                            </svg>
                        </button>
                    </form>
                </div>
            </div>
        </div>
        <div id="list" class="absolute w-128 rounded-t-md">
              <ul id="placesList" class="hidden bg-yellow-50 rounded-t-lg min-h-640"></ul>
              <div id="pagination" class="hidden py-2 bg-yellow-50 rounded-b-lg"></div>
            </div>
        <div class="top-wrapper w-128 flex justify-between px-6 py-8  bg-gray-800 rounded-t-lg">
            <div class="temperature-side">
                <div class="live-temperature text-6xl font-bold">{{weatherdata.temp}}°C</div>
                <div class="fells-like-temperature my-2">체감 기온 {{weatherdata.fells_like}}°C</div>
            </div>
            <div class="weather-stat-side flex justify-center flex-col w-1/2">
                <div>{{weatherdata.weather}}</div>
                <div>{{location.name}}</div>
            </div>
            <div class="weather-icon-side flex items-center">
                <img :src="weatherdata.icon ? 'http://openweathermap.org/img/wn/'+weatherdata.icon+'.png' : '' ">
            </div>
        </div>
        <div class="middle w-128 px-6 py-2 bg-gray-600 rounded-b-lg h-30r">
            <div class="middle-inside flex justify-between items-center px-6 my-2" v-for="(day,idx) in daily_weather_data" :key="day.dt">
                <div class="day">
                  {{new Date(day.dt * 1000).getDate()+" "+days[new Date(day.dt * 1000).getDay()]}}
                </div>
                <div class="icon">
                  <img :src="day.weather[0].icon ? 'http://openweathermap.org/img/wn/'+day.weather[0].icon+'.png' : '' ">
                </div>
                <div>
                  {{new Date(day.sunrise * 1000).getHours()+" : "}}{{new Date(day.sunrise * 1000).getMinutes() < 10 ? '0' : ''}}{{new Date(day.sunrise * 1000).getMinutes()}}
                </div>
                <div>
                  {{new Date(day.sunset * 1000).getHours()+" : "}}{{new Date(day.sunset * 1000).getMinutes() < 10 ? '0' : ''}}{{new Date(day.sunset * 1000).getMinutes()}}
                </div>
                <div class="temperature">
                  <div>
                      {{ Math.round(day.temp.min) }} °C
                  </div>
                  <div>
                      {{ Math.round(day.temp.max) }} °C
                  </div>
                </div>
            </div>
        </div>
    </div>
</template>

 

이후 페이지를 보면 정상작동 하지 않을것입니다.

당연합니다. 스크립트 코드는 작성하지 않았기 때문입니다.

 

그리고 1편에 링크한 유튜브 영상에서는 장소검색을 위해

Algolia Places를 이용했는데 이 서비스가 2022년 5월말에 서비스 종료 예정이랍니다.

 

관련링크 : https://www.algolia.com/blog/product/sunsetting-our-places-feature/

 

Sunsetting our Places feature | Algolia Blog

We have important news to share about Algolia Places. After careful consideration, we have come to the decision to sunset our Places feature. Our customers

www.algolia.com

그래서 대안으로 여러개를 찾아봤지만 국내한정으로 이용한다는 가정으로 카카오맵 API를 이용하기로 결정했습니다.

이에따라 카카오맵 API를 이용하기 위해 KAKAO JS KEY 가 필요해졌네요 

 

카카오계정으로 로그인 후 

https://developers.kakao.com/console/app

 

카카오계정 로그인

여기를 눌러 링크를 확인하세요.

accounts.kakao.com

이곳에서 애플리케이션을 하나 추가해줍시다.

이후 애플리케이션 페이지로 접속해 요약정보를 보면 JavaScript 키가 있습니다.

이것을 복사해주시고!

 

프로젝트 루트폴더에 보시면 .env라는 파일이 있습니다.

하단에 다음과 같이 추가해줍니다.

MIX_KAKAO_JS_KEY=여러분의 KAKAO JS KEY

왜 앞에 MIX_를 붙였는지 설명 해드리겠습니다. 우리는 Laravel Mix를 사용중인 상태인데 이 Laravel Mix를 사용하면 Vue 파일에서 Laravel의 .env 변수에 접근할 수 있습니다. 단 접두어가 MIX_로 시작해야 합니다. 이러한 MIX_ 로 시작하는 변수는 Vue의 script 코드에서 process.env.MIX_변수이름 을 통해 해당 값에 접근 할 수 있습니다.

 

이번 프로젝트에서는 카카오맵 API 활용을 위해 해당 MIX 변수를 사용하게 됩니다.

 

날씨정보는 openweathermap 을 이용합니다. 이 서비스도 마찬가지로 API KEY 가 필요하며 .env파일에 변수로 추가해야합니다.

 

openweathermap 사용법을 알려드립니다.

 

https://openweathermap.org/api

 

Weather API - OpenWeatherMap

Please, sign up to use our fast and easy-to-work weather APIs for free. In case your requirements go beyond our freemium account conditions, you may check the entire list of our subscription plans. You can read the How to Start guide and enjoy using our po

openweathermap.org

이 사이트에 우선 가입을 하고, One Call API 의 Free로 API 신청을 합니다.

이후 https://home.openweathermap.org/api_keys 에서 API키를 확인하실 수 있습니다.

 

API키가 발급되었다 해서 바로 사용이 가능한 것은 아닌것 같습니다. 20분정도 이후에 서비스 이용이 가능한것으로 보입니다.

API키를 복사해서 .env 파일에 다음과같이 추가해줍니다.

OPENWEATHER_KEY=여러분의 OPENWEATHER KEY

그리고 /config/services.php 파일의 리턴 배열 내에 다음 내용을 추가해 줍니다.

'weather' => [
        'key' => env('OPENWEATHER_KEY'),
    ],

추가하면 이런식이 됩니다.

'mailgun' => [
        'domain' => env('MAILGUN_DOMAIN'),
        'secret' => env('MAILGUN_SECRET'),
        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    ],

    'postmark' => [
        'token' => env('POSTMARK_TOKEN'),
    ],

    'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],

    'weather' => [
        'key' => env('OPENWEATHER_KEY'),
    ],

 

이제 날씨 정보를 가져올 API 페이지를 만들어봅시다.

/routes/api.php 파일을 열어봅시다. 그리고 하단에 다음 내용을 추가합니다.

 

Route::get('weather', function () {
    $service_key = config('services.weather.key');
    $lat = request('lat');
    $lng = request('lng');
    $url = "https://api.openweathermap.org/data/2.5/onecall?lang=kr&lat=$lat&lon=$lng&exclude=minutely,hourly&units=metric&appid=$service_key";
    $res = Http::get($url);
    return $res->json();
});

서비스키는 방금 /config/services.php 파일에 추가한 .env 변수를 가져오는 값이 되며,

lat(위도), lng(경도)를 변수로 저장 후 Http:get 요청 후 이를 json으로 반환하는 방식입니다.

 

Route::get('weather' 라고 했으니 /api/weather 페이지로 get요청시 위 함수가 실행될 것입니다.

분단위, 1시간단위는 제외하고 온도단위는 섭씨단위로 지정했습니다.

 

혹시 여러분들은 다른기준 및 추가 데이터를 가져오기 원하신다면 아래 API Doc 페이지 주소를 안내해드리니 해당 페이지를 참고하셔서 Request URL을 구성하시면 되겠습니다.

 

https://openweathermap.org/api/one-call-api

 

One Call API: weather data for any geographical coordinate - OpenWeatherMap

To learn about how get access to historical weather data for the previous 5 days, please use this section of the documentation. How to make an API call https://api.openweathermap.org/data/2.5/onecall/timemachine?lat={lat}&lon={lon}&dt={time}&appid={API key

openweathermap.org

그리고 로딩 효과도 추가해주면 좋겠지요?

로딩 효과도 추가해봅시다. 로딩효과 이미지는 아래 사이트에서 만들었습니다.

https://loading.io/

 

svg로 다운 받고 코드를 복사하여 vue 컴포넌트를 하나 더 만들었습니다.

/resources/js/components/LoadImg.vue 파일을 만듭니다.

<template>
    <!-- [ldio] generated by https://loading.io/ -->
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto; animation-play-state: running; animation-delay: 0s;" width="6.2rem" height="6.2rem" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
        <circle cx="84" cy="50" r="10" fill="#e15b64" style="animation-play-state: running; animation-delay: 0s;">
            <animate attributeName="r" repeatCount="indefinite" dur="0.43859649122807015s" calcMode="spline" keyTimes="0;1" values="10;0" keySplines="0 0.5 0.5 1" begin="0s" style="animation-play-state: running; animation-delay: 0s;"></animate>
            <animate attributeName="fill" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="discrete" keyTimes="0;0.25;0.5;0.75;1" values="#e15b64;#abbd81;#f8b26a;#f47e60;#e15b64" begin="0s" style="animation-play-state: running; animation-delay: 0s;"></animate>
        </circle>
        <circle cx="16" cy="50" r="10" fill="#e15b64" style="animation-play-state: running; animation-delay: 0s;">
          <animate attributeName="r" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="0s" style="animation-play-state: running; animation-delay: 0s;"></animate>
          <animate attributeName="cx" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="0s" style="animation-play-state: running; animation-delay: 0s;"></animate>
        </circle>
        <circle cx="50" cy="50" r="10" fill="#f47e60" style="animation-play-state: running; animation-delay: 0s;">
          <animate attributeName="r" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.43859649122807015s" style="animation-play-state: running; animation-delay: 0s;"></animate>
          <animate attributeName="cx" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.43859649122807015s" style="animation-play-state: running; animation-delay: 0s;"></animate>
        </circle>
        <circle cx="84" cy="50" r="10" fill="#f8b26a" style="animation-play-state: running; animation-delay: 0s;">
          <animate attributeName="r" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.8771929824561403s" style="animation-play-state: running; animation-delay: 0s;"></animate>
          <animate attributeName="cx" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.8771929824561403s" style="animation-play-state: running; animation-delay: 0s;"></animate>
        </circle>
        <circle cx="16" cy="50" r="10" fill="#abbd81" style="animation-play-state: running; animation-delay: 0s;">
          <animate attributeName="r" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.3157894736842104s" style="animation-play-state: running; animation-delay: 0s;"></animate>
          <animate attributeName="cx" repeatCount="indefinite" dur="1.7543859649122806s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.3157894736842104s" style="animation-play-state: running; animation-delay: 0s;"></animate>
        </circle>
    </svg>
</template>

저장 후 VueWeather.vue 파일에 방금 생성한 LoadImg.vue 파일을 가져옵니다.

import LoadImg from './LoadImg.vue';

export default{
	components : {
    	LoadImg
    }
}

components에 LoadImg를 추가해줍니다.

 

스크립트 코드는 은근히 길어서 이곳에 첨부하기엔 너무 포스팅이 길어질것 같습니다.

 

완성된 프로젝트의 github 링크를 달아드릴테니 코드를 확인해주세요!

 

https://github.com/devdinist/Laravel_Vue_Weather

궁금하신점이 있으면 댓글 달아주세요

하지만 답글을 언제 달지는 장담할 수 없습니다.. ㅠ