This time, I made a weather application using OpenWeatherMap and deployed it to Netlify, so I will record it as a study when using the API with Vue.
I was originally a Rakuten member, so I got it from Rakuten Rapid API.
I'm studying docker, so I introduced it.
Dockerfile
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/[email protected]
ADD . /app
docker-compose.yml
version: "3"
services:
web:
container_name: web
build: ./web/
image: web
volumes:
- ./web:/app
ports:
- 8080:8080
privileged: true
command: "yarn run serve"
I created a web folder and installed it in it.
WeatherHome.vue
<template>
<section id="weatherHome">
<input
v-model="getWeatherQuery"
placeholder="Please enter the location you want to look up"
/>
<button @click="getWeather">Check the weather</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>
wind speed:{{ getWeatherResult.wind.speed }}Humidity:{{
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>
I use the API key of env.local to get information with axios, display each information, and use vue-awesome to make it look a little better.
I created WeatherAnime.vue because I wanted to see it a little more elaborately, such as displaying a rain animation when it was raining. By the way, I thought I had never used Vuex, and now I have two components, so I introduced it.
store.js
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;
WeatherHome.vue
<template>
<section id="weatherHome">
<input v-model.trim="search" placeholder="Please enter the location you want to look up" />
<button @click="getData">Check the weather</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>
I wanted to make an animation, but I couldn't understand it even after reading various sources on github, so I hurriedly changed the background image.
WeatherAnime.vue
<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>
Tokyo was specified for the button design change and initial data.
WeatherHome.vue
<div class="weatherSearch">
<input
v-model.trim="search"
placeholder="Please enter the location you want to look up"
/>
<button @click="getData" class="weatherCardButton">Check the weather</button>
</div>
<script>
created: function() {
this.getWeather("Tokyo");
},
</script>
<style>
.weatherCardButton {
border: 2px solid transparent;
}
.weatherCardButton:hover {
background: transparent;
border-color: white;
}
</style>
netlify.toml
[build]
base = "/web"
[build.environment]
YARN_VERSION = "1.22.5"
YARN_FLAGS = "--no-ignore-optional"
NODE_VERSION = "14.15.3"
It was completed, but when I read the code using OpenWeatherMap on Github, I am keenly aware of how low my code level is. The code is saved on Github, so if you want to see it, please. Recently, I'm thinking that it is better to learn the pattern of how to make by code reading and copying sutras in order to improve.