今回VueでAPI使う場合の勉強を兼ねてOpenWeatherMapを使って天気アプリを作ってNetlifyにデプロイしたので記録しておきます。
#APIキーを取得する
元々楽天会員なのでRakuten RapidAPIから取得しました。
#dockerを導入する
dockerを勉強中の身なので導入してみました。
FROM node:14.15.1
WORKDIR /app
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
RUN apt-get update -y && \
apt-get upgrade -y && \
yarn global add @vue/cli@4.5.9
ADD . /app
version: "3"
services:
web:
container_name: web
build: ./web/
image: web
volumes:
- ./web:/app
ports:
- 8080:8080
privileged: true
command: "yarn run serve"
#vue-cliでvueをインストールします。
webフォルダを作成してその中にインストールしました。
天気情報を取得する
<template>
<section id="weatherHome">
<input
v-model="getWeatherQuery"
placeholder="調べたい場所を入力してください"
/>
<button @click="getWeather">天気を調べる</button>
<div v-if="hasGetWeatherData" id="weatherCard">
<h1 class="weatherCardHeader">{{ getWeatherResult.name }}</h1>
<div class="weatherCardDescription">
{{ getWeatherInfo.description }}
</div>
<div class="weatherCardWeatherData">
<img :src="getWeatherImageUrl" class="weatherCardImage" />
<p class="weatherCardTemp">
<v-icon name="thermometer-half" scale="2" />
{{ getWeatherResult.main.temp }} ℃
</p>
</div>
<div class="weatherCardWeatherOtherData">
<div class="weatherCardWindSpeed">
<v-icon name="wind" scale="2" />
{{ getWeatherResult.wind.speed + "m/s" }}
</div>
<div class="weatherCardHumidity">
<v-icon name="tint" scale="2" />
{{ getWeatherResult.main.humidity + "%" }}
</div>
</div>
風速:{{ getWeatherResult.wind.speed }} 湿度:{{
getWeatherResult.main.humidity
}}
</div>
</section>
</template>
<script>
import axios from "axios";
export default {
name: "WeatherHome",
data() {
return {
getWeatherQuery: "",
getWeatherResult: {},
getWeatherInfo: {},
getWeatherImageUrl: "",
hasGetWeatherData: false,
};
},
methods: {
getWeather() {
let apiURL = "https://community-open-weather-map.p.rapidapi.com/weather";
axios
.get(apiURL, {
headers: {
"content-type": "application/octet-stream",
"x-rapidapi-host": process.env.VUE_APP_OPEN_WEATHER_MAP_API_HOST,
"x-rapidapi-key": process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
useQueryString: true,
},
params: {
id: "2172797",
lang: "ja",
units: "metric",
q: this.getWeatherQuery,
},
})
.then((response) => {
this.getWeatherResult = response.data;
this.getWeatherInfo = Object.assign(...this.getWeatherResult.weather);
this.getWeatherImageUrl =
"http://openweathermap.org/img/wn/" +
this.getWeatherInfo.icon +
"@4x.png";
this.hasGetWeatherData = true;
console.log(response);
})
.catch((error) => {
console.log(error);
});
},
},
};
</script>
<style>
#weatherCard {
background-color: #fff;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
color: #212121;
text-decoration: none;
height: 400px;
width: 400px;
margin: auto;
}
.weatherCardWeatherData {
height: 50%;
display: flex;
align-items: center;
}
.weatherCardHeader {
height: 10%;
text-align: center;
padding-top: 2%;
}
.weatherCardDescription {
height: 5%;
font-size: 150%;
text-align: center;
padding: 0.5% 1%;
}
.weatherCardImage {
margin-left: auto;
}
.weatherCardTemp {
font-size: 120%;
margin-right: 15px;
padding: 1% 1%;
}
.weatherCardWeatherOtherData {
height: 15%;
position: relative;
}
.weatherCardWindSpeed {
font-size: 150%;
position: absolute;
bottom: 0;
left: 70px;
}
.weatherCardHumidity {
font-size: 150%;
position: absolute;
bottom: 0;
right: 70px;
}
</style>
env.localのAPIキーを使いaxiosで情報を取得して各情報を表示しvue-awesomeを使って少し見栄えを良くしています。
Vuexを導入する
雨が降っていたら雨のアニメーションを表示するなど少し凝って見たくなったのでWeatherAnime.vueを作成したのですが、
そういえばVuexを使ったことがなかったと思い現在コンポーネントが2つあるので導入してみました。
*今思うとpropsとか使ったほうが良かったと思います。
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
apiKey: process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
apiHost: "https://community-open-weather-map.p.rapidapi.com/weather",
getWeatherQuery: "",
getWeatherResult: {},
hasGetWeatherData: false,
},
mutations: {
setGetWeatherQuery(state, val) {
state.getWeatherQuery = val;
},
setGetWeatherResult(state, data) {
state.getWeatherResult = data;
},
setHasGetWeatherData(state, data) {
state.hasGetWeatherData = data;
},
},
getters: {
getWeatherResult: (state) => {
return String(state.getWeatherResult);
},
hasGetWeatherData: (state) => {
return Boolean(state.hasGetWeatherData);
},
},
actions: {
async getWeather({ commit, state }, getWeatherQuery) {
try {
commit("setGetWeatherQuery", getWeatherQuery);
const response = await axios.get(state.apiHost, {
headers: {
"content-type": "application/octet-stream",
"x-rapidapi-host": process.env.VUE_APP_OPEN_WEATHER_MAP_API_HOST,
"x-rapidapi-key": process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
useQueryString: true,
},
params: {
lang: "ja",
units: "metric",
q: state.getWeatherQuery,
},
});
const newWeatherData = {
name: response.data.name,
temp: response.data.main.temp,
description: response.data.weather[0].description,
icon: response.data.weather[0].icon,
info: response.data.weather[0].main,
wind: response.data.wind.speed,
humidity: response.data.main.humidity,
};
commit("setGetWeatherResult", newWeatherData);
commit("setHasGetWeatherData", true);
} catch (error) {
console.log(error);
}
},
},
});
export default store;
<template>
<section id="weatherHome">
<input v-model.trim="search" placeholder="調べたい場所を入力してください" />
<button @click="getData">天気を調べる</button>
<div v-if="this.hasGetWeatherData" id="weatherCard">
<h1 class="weatherCardHeader">
{{ this.getWeatherResult.name }}
</h1>
<div class="weatherCardDescription">
{{ this.getWeatherResult.description }}
</div>
<div class="weatherCardWeatherData">
<img
:src="
`http://openweathermap.org/img/wn/${this.getWeatherResult.icon}@4x.png`
"
class="weatherCardImage"
/>
<p class="weatherCardTemp">
<v-icon name="thermometer-half" scale="2" />
{{ this.getWeatherResult.temp }} ℃
</p>
</div>
<div class="weatherCardWeatherOtherData">
<div class="weatherCardWindSpeed">
<v-icon name="wind" scale="2" />
{{ this.getWeatherResult.wind + "m/s" }}
</div>
<div class="weatherCardHumidity">
<v-icon name="tint" scale="2" />
{{ this.getWeatherResult.humidity + "%" }}
</div>
</div>
</div>
</section>
</template>
<script>
import { mapActions, mapState } from "vuex";
export default {
name: "WeatherHome",
data() {
return {
search: "",
};
},
computed: {
weatherQuery: {
get() {
return this.$store.state.getWeatherQuery;
},
set(value) {
this.$store.commit("setGetWeatherQuery", value);
},
},
...mapState(["getWeatherResult", "hasGetWeatherData"]),
},
methods: {
...mapActions(["getWeather"]),
getData() {
this.getWeather(this.search);
},
},
};
</script>
<style>
#weatherCard {
background-color: #fff;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
color: #212121;
text-decoration: none;
height: 400px;
width: 400px;
margin: auto;
}
.weatherCardWeatherData {
height: 50%;
display: flex;
align-items: center;
}
.weatherCardHeader {
height: 10%;
text-align: center;
padding-top: 2%;
}
.weatherCardDescription {
height: 5%;
font-size: 150%;
text-align: center;
padding: 0.5% 1%;
}
.weatherCardImage {
margin-left: auto;
}
.weatherCardTemp {
font-size: 120%;
margin-right: 15px;
padding: 1% 1%;
}
.weatherCardWeatherOtherData {
height: 15%;
position: relative;
}
.weatherCardWindSpeed {
font-size: 150%;
position: absolute;
bottom: 0;
left: 70px;
}
.weatherCardHumidity {
font-size: 150%;
position: absolute;
bottom: 0;
right: 70px;
}
</style>
背景画像を変更する
アニメーションを作りたかったのですがgithubで色々なソースを読んでググってもわからなかったので取り急ぎ背景画像を変更する仕様にしました。
*今思うとちゃんと分かるまで調べたほうが良かったと思います。
<template>
<div
class="weather-anime"
:class="[
{ 'weather-rain': getWeatherResult.info === 'Rain' },
{ 'weather-snow': getWeatherResult.info == 'Snow' },
{ 'weather-clear': getWeatherResult.info == 'Clear' },
{ 'weather-clouds': getWeatherResult.info == 'Clouds' },
]"
></div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(["getWeatherResult"]),
},
};
</script>
<style scoped>
.weather-anime {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
}
.weather-rain {
background-image: url("../assets/images/rain.jpg");
}
.weather-clouds {
background-image: url("../assets/images/clouds.jpg");
}
.weather-clear {
background-image: url("../assets/images/clear.jpg");
}
</style>
少し修正する
ボタンのデザイン変更と初期データに東京を指定しました。
<div class="weatherSearch">
<input
v-model.trim="search"
placeholder="調べたい場所を入力してください"
/>
<button @click="getData" class="weatherCardButton">天気を調べる</button>
</div>
<script>
created: function() {
this.getWeather("Tokyo");
},
</script>
<style>
.weatherCardButton {
border: 2px solid transparent;
}
.weatherCardButton:hover {
background: transparent;
border-color: white;
}
</style>
Netlifyにデプロイする
[build]
base = "/web"
[build.environment]
YARN_VERSION = "1.22.5"
YARN_FLAGS = "--no-ignore-optional"
NODE_VERSION = "14.15.3"
終わりに
一応完成はしましたがGithub上にあるOpenWeatherMapを使ったコードを読んでみるといかに自分のコードのレベルが低いか痛感させられます。
コードはGithubに保存してあるので見たい方がいらしたらどうぞ。
改善するために最近はコードリーディングや写経して作り方のパターンを覚えたほうがいいのではと思案中です。