Używanie Vue.js do tworzenia interaktywnego pulpitu pogodowego z interfejsami API

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Tworzenie dashboardu z danymi API to często skomplikowana sprawa. Wybór stosu technologicznego, integracja interfejsów API, wybór właściwych wykresów i upiększanie za pomocą stylów CSS może być trudne. Ten samouczek jest przewodnikiem krok po kroku, jak pomóc w tworzeniu pulpitu pogodowego w Vue.js przy użyciu danych API.

(To jest artykuł sponsorowany.) W tym samouczku zbudujesz od podstaw prosty pulpit pogodowy. Będzie to aplikacja kliencka, która nie jest ani przykładem „Hello World”, ani zbyt onieśmielająca pod względem rozmiaru i złożoności.

Cały projekt będzie rozwijany przy użyciu narzędzi z ekosystemu Node.js + npm. W szczególności będziemy w dużym stopniu polegać na API Dark Sky w przypadku danych, Vue.js w przypadku wszystkich ciężkich prac oraz FusionCharts w przypadku wizualizacji danych.

Warunki wstępne

Spodziewamy się, że znasz następujące elementy:

  • HTML5 i CSS3 (będziemy również korzystać z podstawowych funkcji dostarczanych przez Bootstrap;
  • JavaScript (zwłaszcza sposób korzystania z języka ES6);
  • Node.js i npm (podstawy środowiska i zarządzania pakietami są w porządku).

Oprócz tych wymienionych powyżej, byłoby świetnie, gdybyś znał Vue.js lub jakikolwiek inny podobny framework JavaScript. Nie oczekujemy, że będziesz wiedział o FusionCharts — jest tak łatwy w użyciu, że nauczysz się go w locie!

Oczekiwane wnioski

Twoje kluczowe wnioski z tego projektu to:

  1. Jak zaplanować wdrożenie dobrego dashboardu
  2. Jak tworzyć aplikacje z Vue.js
  3. Jak tworzyć aplikacje oparte na danych
  4. Jak wizualizować dane za pomocą FusionCharts

W szczególności każda z sekcji przybliża Cię o krok do celów nauki:

  1. Wprowadzenie do pulpitu pogodowego
    Ten rozdział zawiera przegląd różnych aspektów przedsięwzięcia.
  2. Stwórz projekt
    W tej sekcji dowiesz się, jak tworzyć projekt od podstaw za pomocą narzędzia wiersza poleceń Vue.
  3. Dostosuj domyślną strukturę projektu
    Domyślne rusztowanie projektu, które otrzymałeś w poprzedniej sekcji, nie wystarczy; tutaj dowiesz się dodatkowych rzeczy potrzebnych do projektu ze strukturalnego punktu widzenia.
  4. Pozyskiwanie i przetwarzanie danych
    Ta sekcja jest sednem projektu; Cały kluczowy kod do pozyskiwania i przetwarzania danych z API jest tutaj pokazany. Spodziewaj się, że spędzisz maksymalny czas w tej sekcji.
  5. Wizualizacja danych za pomocą FusionCharts
    Po ustabilizowaniu wszystkich danych i innych ruchomych części projektu, ta sekcja jest poświęcona wizualizacji danych za pomocą FusionCharts i odrobiny CSS.

1. Przepływ pracy pulpitu nawigacyjnego

Zanim zagłębimy się w implementację, ważne jest, aby mieć jasność co do naszego planu. Nasz plan dzielimy na cztery różne aspekty:

Wymagania

Jakie są nasze wymagania dotyczące tego projektu? Innymi słowy, jakie rzeczy chcemy pokazać za pośrednictwem naszego panelu pogodowego? Mając na uwadze, że naszymi docelowymi odbiorcami są prawdopodobnie zwykli śmiertelnicy o prostych gustach, chcielibyśmy pokazać im, co następuje:

  • Szczegóły lokalizacji, dla której chcą zobaczyć pogodę, wraz z podstawowymi informacjami o pogodzie. Ponieważ nie ma rygorystycznych wymagań, nudne szczegóły ustalimy później. Jednak na tym etapie należy zauważyć, że będziemy musieli udostępnić odbiorcom pole wyszukiwania, aby mogli podać dane wejściowe dotyczące interesującej ich lokalizacji.
  • Graficzne informacje o pogodzie ich interesującej lokalizacji, takie jak:
    • Zmiana temperatury w dniu zapytania
    • Najważniejsze informacje o dzisiejszej pogodzie:
      • Prędkość i kierunek wiatru
      • Widoczność
      • Wskaźnik promieniowania ultrafioletowego

Uwaga : dane uzyskane z API dostarczają informacji dotyczących wielu innych aspektów pogody. Zdecydowaliśmy się nie używać ich wszystkich, aby ograniczyć kod do minimum.

Struktura

W oparciu o wymagania możemy ustrukturyzować nasz pulpit nawigacyjny, jak pokazano poniżej:

Struktura deski rozdzielczej
(duży podgląd)

Dane

Nasz dashboard jest tak dobry, jak dane, które otrzymujemy, ponieważ nie będzie ładnych wizualizacji bez odpowiednich danych. Istnieje wiele publicznych interfejsów API udostępniających dane o pogodzie — niektóre z nich są bezpłatne, a niektóre nie. Dla naszego projektu będziemy zbierać dane z Dark Sky API. Jednak nie będziemy mogli odpytywać punktu końcowego interfejsu API bezpośrednio po stronie klienta. Nie martw się, mamy obejście, które zostanie ujawnione we właściwym czasie! Gdy otrzymamy dane dla szukanej lokalizacji, zajmiemy się przetwarzaniem i formatowaniem danych — wiesz, rodzaj technicznych aspektów, które pomagają nam płacić rachunki.

Wyobrażanie sobie

Gdy otrzymamy czyste i sformatowane dane, podłączamy je do FusionCharts. Na świecie jest bardzo niewiele bibliotek JavaScript tak wydajnych jak FusionCharts. Z ogromnej liczby ofert FusionCharts użyjemy tylko kilku — wszystkie napisane w JavaScript, ale działają bezproblemowo po zintegrowaniu z opakowaniem Vue dla FusionCharts.

Uzbrojeni w szerszy obraz, ubrudźmy sobie ręce — czas na konkrety! W następnej sekcji stworzysz podstawowy projekt Vue, na podstawie którego będziemy dalej budować.

2. Tworzenie projektu

Aby stworzyć projekt, wykonaj następujące kroki:

  1. Zainstaluj Node.js + npm
    ( Jeśli masz zainstalowany Node.js na swoim komputerze, pomiń ten krok. )
    Node.js jest dostarczany z npm w pakiecie, więc nie musisz osobno instalować npm. W zależności od systemu operacyjnego pobierz i zainstaluj Node.js zgodnie z instrukcjami podanymi tutaj.

    Po zainstalowaniu prawdopodobnie dobrym pomysłem jest sprawdzenie, czy oprogramowanie działa poprawnie i jakie są jego wersje. Aby to przetestować, otwórz wiersz poleceń/terminal i wykonaj następujące polecenia:
     node --version npm --version
  2. Zainstaluj pakiety za pomocą npm
    Po uruchomieniu programu npm wykonaj następujące polecenie, aby zainstalować podstawowe pakiety niezbędne dla naszego projektu.
     npm install -g vue@2 vue-cli@2
  3. Zainicjuj rusztowanie projektu za pomocą vue-cli
    Zakładając, że poprzedni krok przebiegł dobrze, następnym krokiem jest użycie vue-cli — narzędzia wiersza poleceń z Vue.js, aby zainicjować projekt. Aby to zrobić, wykonaj następujące czynności:
    • Zainicjuj rusztowanie za pomocą prostego szablonu webpack.
       vue init webpack-simple vue_weather_dashboard
      Zostaniesz poproszony o kilka pytań — zaakceptowanie wartości domyślnych dla wszystkich, ale ostatnie pytanie będzie wystarczająco dobre dla tego projektu; odpowiedz N dla ostatniego.
      Zrzut ekranu wiersza poleceń/terminala
      (duży podgląd)
      Należy pamiętać, że chociaż webpack-simple jest doskonały do ​​szybkiego prototypowania i lekkich aplikacji, takich jak nasza, nie jest szczególnie odpowiedni do poważnych aplikacji lub wdrożeń produkcyjnych. Jeśli chcesz użyć innego szablonu (chociaż odradzamy go, jeśli jesteś nowicjuszem) lub chciałbyś nadać swojemu projektowi inną nazwę, składnia jest następująca:
       vue init [template-name] [project-name]
    • Przejdź do katalogu utworzonego przez vue-cli dla projektu.
       cd vue_weather_dashboard
    • Zainstaluj wszystkie pakiety wymienione w package.json , który został utworzony przez narzędzie vue-cli dla szablonu webpack-simple .
       npm install
    • Uruchom serwer deweloperski i zobacz, jak domyślny projekt Vue działa w przeglądarce!
       npm run dev

Jeśli jesteś nowy w Vue.js, poświęć chwilę na delektowanie się swoim najnowszym osiągnięciem — stworzyłeś małą aplikację Vue i działa ona na localhost:8080!

Zrzut ekranu strony Vue.js
(duży podgląd)

Krótkie wyjaśnienie domyślnej struktury projektu

Czas przyjrzeć się strukturze wewnątrz katalogu vue_weather_dashboard , aby zrozumieć podstawy, zanim zaczniemy go modyfikować.

Struktura wygląda mniej więcej tak:

 vue_weather_dashboard |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src | |--- App.vue | |--- assets | | |--- logo.png | |--- main.js

Chociaż pominięcie zapoznania się z domyślnymi plikami i katalogami może być kuszące, jeśli jesteś nowy w Vue, zdecydowanie zalecamy przynajmniej przyjrzenie się zawartości plików. Może to być dobra sesja edukacyjna i wywołać pytania, do których należy odpowiedzieć sobie samemu, zwłaszcza następujące pliki:

  • package.json i tylko rzut oka na jego kuzyna package-lock.json
  • webpack.config.js
  • index.html
  • src/main.js
  • src/App.vue

Poniżej znajduje się krótkie wyjaśnienie każdego z plików i katalogów pokazanych na schemacie drzewa:

  • README.md
    Żadnej nagrody za zgadywanie — to przede wszystkim dla ludzi, aby przeczytać i zrozumieć kroki niezbędne do stworzenia rusztowania projektowego.
  • node_modules/
    Jest to katalog, w którym npm pobiera pakiety niezbędne do rozpoczęcia projektu. Informacje o niezbędnych pakietach są dostępne w pliku package.json .
  • pakiet.json
    Ten plik jest tworzony przez narzędzie vue-cli na podstawie wymagań szablonu webpack-simple i zawiera informacje o pakietach npm (w tym ich wersjach i innych szczegółach), które muszą zostać zainstalowane. Przyjrzyj się dokładnie zawartości tego pliku — tutaj powinieneś odwiedzić i być może edytować, aby dodać/usunąć pakiety niezbędne dla projektu, a następnie uruchomić npm install. Przeczytaj więcej o package.json tutaj.
  • pakiet-lock.json
    Ten plik jest tworzony przez sam npm i służy przede wszystkim do przechowywania dziennika rzeczy, które npm pobrał i zainstalował.
  • webpack.config.js
    Jest to plik JavaScript, który zawiera konfigurację webpacka — narzędzia, które łączy różne aspekty naszego projektu (kod, zasoby statyczne, konfigurację, środowiska, tryb użytkowania itp.) i minifikuje przed udostępnieniem go użytkownikowi. Zaletą jest to, że wszystkie rzeczy są ze sobą powiązane automatycznie, a wrażenia użytkownika znacznie się poprawiają dzięki poprawie wydajności aplikacji (strony są szybko obsługiwane i szybciej ładują się w przeglądarce). Jak możesz się później spotkać, jest to plik, który należy sprawdzić, gdy coś w systemie kompilacji nie działa tak, jak powinno. Ponadto, jeśli chcesz wdrożyć aplikację, jest to jeden z kluczowych plików, który należy edytować (czytaj więcej tutaj).
  • index.html
    Ten plik HTML służy jako matryca (lub można powiedzieć, szablon), w której dane i kod mają być osadzane dynamicznie (to właśnie robi Vue), a następnie podawane użytkownikowi.
  • src/main.js
    Ten plik JavaScript zawiera kod, który przede wszystkim zarządza zależnościami najwyższego poziomu/projektu i definiuje komponent Vue najwyższego poziomu. Krótko mówiąc, organizuje JavaScript dla całego projektu i służy jako punkt wejścia do aplikacji. Edytuj ten plik, gdy musisz zadeklarować zależności całego projektu od niektórych modułów węzłów lub chcesz coś zmienić w najwyższym komponencie Vue w projekcie.
  • src/App.vue
    W poprzednim punkcie, kiedy mówiliśmy o „najwyższym komponencie Vue”, mówiliśmy w zasadzie o tym pliku. Każdy plik .vue w projekcie jest składnikiem, a składniki są powiązane hierarchicznie. Na początku mamy tylko jeden plik .vue , czyli App.vue , jako nasz jedyny komponent. Ale wkrótce dodamy do naszego projektu więcej komponentów (przede wszystkim zgodnie ze strukturą dashboardu) i połączymy je zgodnie z naszą pożądaną hierarchią, a App.vue będzie przodkiem wszystkich. Te pliki .vue będą zawierać kod w formacie, który Vue chce, abyśmy napisali. Nie martw się, są to kod JavaScript napisany z zachowaniem struktury, która może utrzymać nas przy zdrowych zmysłach i uporządkować. Zostałeś ostrzeżony — pod koniec tego projektu, jeśli jesteś nowy w Vue, możesz uzależnić się od template — script — style template — script — style template — script — style sposób organizowania kodu!

Teraz, gdy stworzyliśmy fundament, nadszedł czas, aby:

  • Zmodyfikuj szablony i dostosuj nieco pliki konfiguracyjne, aby projekt zachowywał się dokładnie tak, jak chcemy.
  • Utwórz nowe pliki .vue i zaimplementuj strukturę pulpitu nawigacyjnego za pomocą kodu Vue.

Poznamy je w następnym podrozdziale, który będzie trochę długi i wymaga nieco uwagi. Jeśli potrzebujesz kofeiny lub wody albo chcesz się wyładować — nadszedł czas!

3. Dostosowywanie domyślnej struktury projektu

Czas majstrować przy fundamencie, który dał nam projekt rusztowań. Zanim zaczniesz, upewnij się, że serwer deweloperski dostarczony przez webpack jest uruchomiony. Zaletą ciągłego uruchamiania tego serwera jest to, że wszelkie zmiany, które wprowadzasz w kodzie źródłowym — zapisujesz go i odświeżasz stronę internetową — są natychmiast odzwierciedlane w przeglądarce.

Jeśli chcesz uruchomić serwer deweloperski, po prostu wykonaj następujące polecenie z terminala (zakładając, że bieżący katalog jest katalogiem projektu):

 npm run dev

W kolejnych sekcjach zmodyfikujemy niektóre z istniejących plików i dodamy kilka nowych. Następnie nastąpi krótkie wyjaśnienie zawartości tych plików, abyś miał pojęcie, do czego służą te zmiany.

Modyfikuj istniejące pliki

index.html

Nasza aplikacja jest dosłownie aplikacją jednostronicową, ponieważ w przeglądarce wyświetlana jest tylko jedna strona internetowa. Porozmawiamy o tym później, ale najpierw dokonajmy pierwszej zmiany — zmiany tekstu w tagu <title> .

W tej małej wersji plik HTML wygląda następująco:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <!-- Modify the text of the title tag below --> <title>Vue Weather Dashboard</title> </head> <body> <div></div> <script src="/dist/build.js"></script> </body> </html>

Poświęć chwilę na odświeżenie strony internetowej pod adresem localhost:8080 i zobacz zmianę odzwierciedloną na pasku tytułowym karty w przeglądarce — powinna brzmieć „Vue Weather Dashboard”. Było to jednak tylko po to, aby zademonstrować proces wprowadzania zmian i sprawdzania, czy działa. Mamy więcej rzeczy do zrobienia!

Na tej prostej stronie HTML brakuje wielu rzeczy, których potrzebujemy w naszym projekcie, a zwłaszcza następujących:

  • Niektóre metainformacje
  • Linki CDN do Bootstrap (framework CSS)
  • link do niestandardowego arkusza stylów (do dodania w projekcie)
  • Wskaźniki do interfejsu API geolokalizacji Map Google z tagu <script>

Po dodaniu tych rzeczy, końcowy index.html ma następującą zawartość:

 <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="src/css/style.css"> <title>Weather Dashboard</title> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC-lCjpg1xbw-nsCc11Si8Ldg2LKYizqI4&libraries=places"></script> </head> <body> <div></div> <script src="/dist/build.js"></script> </body> </html>

Zapisz plik i odśwież stronę internetową. Być może zauważyłeś niewielkie uderzenie podczas ładowania strony — wynika to głównie z faktu, że styl strony jest teraz kontrolowany przez Bootstrap, a elementy stylu, takie jak czcionki, odstępy itp., różnią się od domyślnego, który mieliśmy wcześniej (jeśli nie jesteś pewien, przywróć ustawienia domyślne i zobacz różnicę).

Zrzut ekranu po odświeżeniu strony z localhost:8080
(duży podgląd)

Uwaga : Jedna ważna rzecz, zanim przejdziemy dalej — adres URL interfejsu API Map Google zawiera klucz, który jest własnością FusionCharts. Na razie możesz użyć tego klucza do zbudowania projektu, ponieważ nie chcemy, abyś ugrzęzł w tego typu drobnych szczegółach (które mogą rozpraszać Cię, gdy jesteś nowy). Jednak zdecydowanie zachęcamy do wygenerowania i używania własnego klucza API Map Google, gdy poczynisz pewne postępy i poczujesz się komfortowo, aby zwrócić uwagę na te drobne szczegóły.

pakiet.json

W chwili pisania tego tekstu używaliśmy w naszym projekcie pewnych wersji pakietów npm i wiemy na pewno, że te rzeczy działają razem. Jednak w momencie wykonywania projektu jest bardzo możliwe, że najnowsze stabilne wersje pakietów, które pobiera npm dla Ciebie, nie są takie same, jak my używaliśmy, co może spowodować złamanie kodu (lub wykonanie rzeczy, które wykraczają poza naszej kontroli). Dlatego bardzo ważne jest, aby mieć dokładnie ten sam plik package.json , który został użyty do zbudowania tego projektu, aby nasz kod/wyjaśnienia i wyniki były spójne.

Zawartość pliku package.json powinna być:

 { "name": "vue_weather_dashboard", "description": "A Vue.js project", "version": "1.0.0", "author": "FusionCharts", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" }, "dependencies": { "axios": "^0.18.0", "babel": "^6.23.0", "babel-cli": "^6.26.0", "babel-polyfill": "^6.26.0", "fusioncharts": "^3.13.3", "moment": "^2.22.2", "moment-timezone": "^0.5.21", "vue": "^2.5.11", "vue-fusioncharts": "^2.0.4" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.0", "babel-preset-stage-3": "^6.24.1", "cross-env": "^5.0.5", "css-loader": "^0.28.7", "file-loader": "^1.1.4", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1" } }

Zachęcamy do przejrzenia nowego package.json i dowiedzenia się, jakie są funkcje różnych obiektów w json. Możesz zmienić wartość klucza „ author ” na swoje imię i nazwisko. Ponadto pakiety wymienione w zależnościach ujawnią się we właściwym czasie w kodzie. Na razie wystarczy wiedzieć, że:

  • Pakiety związane z babel służą do poprawnej obsługi kodu stylu ES6 przez przeglądarkę;
  • axios żądania HTTP oparte na Promise;
  • moment i strefa czasowa służą do manipulacji datą/godziną;
  • fusioncharts i vue-fusioncharts odpowiadają za renderowanie wykresów:
  • vue , z oczywistych powodów.

webpack.config.js

Podobnie jak w przypadku package.json , sugerujemy utrzymanie pliku webpack.config.js zgodnego z tym, którego użyliśmy do zbudowania projektu. Jednak przed wprowadzeniem jakichkolwiek zmian zalecamy dokładne porównanie domyślnego kodu w webpack.config.js z kodem, który podaliśmy poniżej. Zauważysz sporo różnic — wyszukaj je w Google i zorientuj się, co oznaczają. Ponieważ szczegółowe wyjaśnienie konfiguracji pakietów internetowych wykracza poza zakres tego artykułu, jesteś sam w tym zakresie.

Dostosowany plik webpack.config.js wygląda następująco:

 var path = require('path') var webpack = require('webpack') module.exports = { entry: ['babel-polyfill', './src/main.js'], output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true, host: '0.0.0.0', port: 8080 }, performance: { hints: false }, devtool: '#eval-source-map' } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // https://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }

Po wprowadzeniu zmian w webpack.config.js projektu konieczne jest zatrzymanie działającego serwera deweloperskiego ( Ctrl + C ) i ponowne uruchomienie go za pomocą następującego polecenia wykonanego z katalogu projektu po zainstalowaniu wszystkich pakietów wymienionych w package.json plik package.json :

 npm install npm run dev

Dzięki temu kończy się męka poprawiania konfiguracji i upewniania się, że odpowiednie pakiety są na swoim miejscu. Jest to jednak również droga do modyfikowania i pisania kodu, która jest nieco długa, ale także bardzo satysfakcjonująca!

src/main.js

Ten plik jest kluczem do orkiestracji projektu na najwyższym poziomie — tutaj definiujemy:

  • Jakie są zależności najwyższego poziomu (skąd pobrać najważniejsze potrzebne pakiety npm);
  • Jak rozwiązać zależności, wraz z instrukcjami dla Vue dotyczącymi używania wtyczek/opakowań, jeśli takie istnieją;
  • Instancja Vue, która zarządza najwyższym komponentem w projekcie: src/App.vue (plik .vue ).

Zgodnie z naszymi celami dla pliku src/main.js kod powinien wyglądać następująco:

 // Import the dependencies and necessary modules import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; // Resolve the dependencies Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); // Globally register the components for project-wide use Vue.use(VueFusionCharts, FusionCharts); // Instantiate the Vue instance that controls the application new Vue({ el: '#app', render: h => h(App) })

src/App.vue

Jest to jeden z najważniejszych plików w całym projekcie i reprezentuje najwyższy składnik w hierarchii — całą aplikację jako całość. W naszym projekcie ten komponent wykona wszystkie ciężkie prace, które omówimy później. Na razie chcemy pozbyć się domyślnego schematu i wstawić coś własnego.

Jeśli jesteś nowy w sposobie organizowania kodu w Vue, lepiej byłoby poznać ogólną strukturę plików .vue . Pliki .vue składają się z trzech sekcji:

  • Szablon
    W tym miejscu definiowany jest szablon HTML strony. Oprócz statycznego kodu HTML, ta sekcja zawiera również sposób osadzania dynamicznej zawartości przez Vue przy użyciu podwójnych nawiasów klamrowych {{ }} .
  • Scenariusz
    JavaScript rządzi tą sekcją i jest odpowiedzialny za generowanie dynamicznej zawartości, która jest umieszczana w szablonie HTML w odpowiednich miejscach. Ta sekcja to przede wszystkim obiekt, który jest eksportowany i składa się z:
    • Dane
      Jest to funkcja sama w sobie i zwykle zwraca pewne pożądane dane zawarte w ładnej strukturze danych.
    • Metody
      Obiekt, który składa się z jednej lub więcej funkcji/metod, z których każda zwykle w jakiś sposób manipuluje danymi, a także kontroluje dynamiczną zawartość szablonu HTML.
    • Obliczona
      Podobnie jak w przypadku obiektu metody omówionego powyżej z jednym ważnym rozróżnieniem — podczas gdy wszystkie funkcje w obiekcie metody są wykonywane za każdym razem, gdy którakolwiek z nich jest wywoływana, funkcje w obliczonym obiekcie zachowują się znacznie bardziej rozsądnie i są wykonywane wtedy i tylko wtedy, gdy zostało to nazywa się.
  • Styl
    Ta sekcja dotyczy stylów CSS, które mają zastosowanie do kodu HTML strony (zapisanego w szablonie) — umieść tutaj stary dobry CSS, aby Twoje strony były piękne!

Mając na uwadze powyższy paradygmat, minimalnie spersonalizujmy kod w App.vue :

 <template> <div> <p>This component's code is in {{ filename }}</p> </div> </template> <script> export default { data() { return { filename: 'App.vue' } }, methods: { }, computed: { }, } </script> <style> </style>

Pamiętaj, że powyższy fragment kodu służy po prostu do sprawdzenia, czy App.vue działa z naszym własnym kodem. Później przejdzie wiele zmian, ale najpierw zapisz plik i odśwież stronę w przeglądarce.

Zrzut ekranu przeglądarki z komunikatem „Kod tego komponentu znajduje się w App.vue”
(duży podgląd)

W tym momencie prawdopodobnie dobrym pomysłem jest uzyskanie pomocy w oprzyrządowaniu. Sprawdź narzędzia deweloperskie Vue dla Chrome, a jeśli nie masz większych problemów z używaniem Google Chrome jako domyślnej przeglądarki do programowania, zainstaluj narzędzie i pobaw się nim trochę. Przyda się on do dalszego rozwoju i debugowania, gdy sytuacja stanie się bardziej skomplikowana.

Dodatkowe katalogi i pliki

Kolejnym krokiem byłoby dodanie dodatkowych plików, dzięki czemu struktura naszego projektu stanie się kompletna. Dodamy następujące katalogi i pliki:

  • src/css/style.css
  • src/assets/calendar.svgvlocation.svgsearch.svgwinddirection.svgwindspeed.svg
  • src/components/Content.vueHighlights.vueTempVarChart.vueUVIndex.vueVisibility.vueWindStatus.vue

Uwaga : Zapisz hiperłącza pliki .svg w swoim projekcie.

Utwórz katalogi i pliki wymienione powyżej. Ostateczna struktura projektu powinna wyglądać (pamiętaj, aby usunąć foldery i pliki z domyślnej struktury, które są teraz niepotrzebne):

 vue_weather_dashboard/ |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src/ | |--- App.vue | |--- css/ | | |--- style.css | |--- assets/ | | |--- calendar.svg | | |--- location.svg | | |--- location.svg | | |--- winddirection.svg | | |--- windspeed.svg | |--- main.js | |--- components/ | | |--- Content.vue | | |--- Highlights.vue | | |--- TempVarChart.vue | | |--- UVIndex.vue | | |--- Visibility.vue | | |--- WindStatus.vue

W folderze głównym projektu mogą znajdować się inne pliki, takie jak .babelrc , .editorconfig .gitignore itp. Na razie możesz je bezpiecznie zignorować.

W następnej sekcji dodamy minimalną zawartość do nowo dodanych plików i przetestujemy, czy działają poprawnie.

src/css/style.css

Chociaż nie przyda się to od razu, skopiuj do pliku następujący kod:

 @import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500"); :root { font-size: 62.5%; } body { font-family: Roboto; font-weight: 400; width: 100%; margin: 0; font-size: 1.6rem; } #sidebar { position: relative; display: flex; flex-direction: column; background-image: linear-gradient(-180deg, #80b6db 0%, #7da7e2 100%); } #search { text-align: center; height: 20vh; position: relative; } #location-input { height: 42px; width: 100%; opacity: 1; border: 0; border-radius: 2px; background-color: rgba(255, 255, 255, 0.2); margin-top: 16px; padding-left: 16px; color: #ffffff; font-size: 1.8rem; line-height: 21px; } #location-input:focus { outline: none; } ::placeholder { color: #FFFFFF; opacity: 0.6; } #current-weather { color: #ffffff; font-size: 8rem; line-height: 106px; position: relative; } #current-weather>span { color: #ffffff; font-size: 3.6rem; line-height: 42px; vertical-align: super; opacity: 0.8; top: 15px; position: absolute; } #weather-desc { font-size: 2.0rem; color: #ffffff; font-weight: 500; line-height: 24px; } #possibility { color: #ffffff; font-size: 16px; font-weight: 500; line-height: 19px; } #max-detail, #min-detail { color: #ffffff; font-size: 2.0rem; font-weight: 500; line-height: 24px; } #max-detail>i, #min-detail>i { font-style: normal; height: 13.27px; width: 16.5px; opacity: 0.4; } #max-detail>span, #min-detail>span { color: #ffffff; font-family: Roboto; font-size: 1.2rem; line-height: 10px; vertical-align: super; } #max-summary, #min-summary { opacity: 0.9; color: #ffffff; font-size: 1.4rem; line-height: 16px; margin-top: 2px; opacity: 0.7; } #search-btn { position: absolute; right: 0; top: 16px; padding: 2px; z-index: 999; height: 42px; width: 45px; background-color: rgba(255, 255, 255, 0.2); border: none; } #dashboard-content { text-align: center; height: 100vh; } #date-desc, #location-desc { color: #ffffff; font-size: 1.6rem; font-weight: 500; line-height: 19px; margin-bottom: 15px; } #date-desc>img { top: -3px; position: relative; margin-right: 10px; } #location-desc>img { top: -3px; position: relative; margin-left: 5px; margin-right: 15px; } #location-detail { opacity: 0.7; color: #ffffff; font-size: 1.4rem; line-height: 20px; margin-left: 35px; } .centered { position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%); } .max-desc { width: 80px; float: left; margin-right: 28px; } .temp-max-min { margin-top: 40px } #dashboard-content { background-color: #F7F7F7; } .custom-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 20px !important; } .custom-content-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 0px !important; } .header-card { height: 50vh; } .content-card { height: 43vh; } .card-divider { margin-top: 0; } .content-header { color: #8786A4; font-size: 1.4rem; line-height: 16px; font-weight: 500; padding: 15px 10px 5px 15px; } .highlights-item { min-height: 37vh; max-height: 38vh; background-color: #FFFFFF; } .card-heading { color: rgb(33, 34, 68); font-size: 1.8rem; font-weight: 500; line-height: 21px; text-align: center; } .card-sub-heading { color: #73748C; font-size: 1.6rem; line-height: 19px; } .card-value { color: #000000; font-size: 1.8rem; line-height: 21px; } span text { font-weight: 500 !important; } hr { padding-top: 1.5px; padding-bottom: 1px; margin-bottom: 0; margin-top: 0; line-height: 0.5px; } @media only screen and (min-width: 768px) { #sidebar { height: 100vh; } #info { position: fixed; bottom: 50px; width: 100%; padding-left: 15px; } .wrapper-right { margin-top: 80px; } } @media only screen and (min-width:1440px) { #sidebar { width: 350px; max-width: 350px; flex: auto; } #dashboard-content { width: calc(100% — 350px); max-width: calc(100% — 350px); flex: auto; } }

źródło/zasoby/

W tym katalogu pobierz i zapisz wymienione poniżej pliki .svg :

  • calendar.svg
  • location.svg
  • search.svg
  • winddirection.svg
  • windspeed.svg

src/components/Content.vue

To jest to, co nazywamy „niemądrym komponentem” (tj. symbolem zastępczym), który ma tylko utrzymywać hierarchię i zasadniczo przekazuje dane do swoich komponentów podrzędnych.

Pamiętaj, że w pliku App.vue nie ma żadnego technicznego paska do zapisania całego naszego kodu, ale stosujemy podejście polegające na podzieleniu kodu poprzez zagnieżdżenie komponentów z dwóch powodów:

  • Aby napisać czysty kod, który pomaga w czytelności i utrzymaniu;
  • Aby odtworzyć tę samą strukturę, którą zobaczymy na ekranie, tj. hierarchię.

Zanim zagnieździmy komponent zdefiniowany w Content.vue w głównym komponencie App.vue , napiszmy trochę zabawkowego (ale edukacyjnego) kodu dla Content.vue :

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> </div> </template> <script> export default { data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> </style>

W kodzie uważnie obserwuj i zrozum, co następuje:

  • W tagu <script> (gdzie oczywiście piszemy trochę kodu JavaScript) domyślnie definiujemy obiekt, który jest eksportowany (udostępniany innym plikom). Ten obiekt zawiera funkcję data() , która zwraca obiekt tablicy o nazwie childComponents , którego elementy są nazwami plików składowych, które powinny być dalej zagnieżdżane.
  • W tagu <template> (gdzie piszemy jakiś szablon HTML), interesującą rzeczą jest <ul> .
    • Na liście nieuporządkowanej każdy element listy powinien być nazwami zamierzonych komponentów podrzędnych, zgodnie z definicją w obiekcie tablicy childComponents . Ponadto lista powinna się automatycznie rozszerzać do ostatniego elementu tablicy. Wygląda na to, że powinniśmy napisać pętlę for , prawda? Robimy to za pomocą dyrektywy v-for dostarczanej przez Vue.js. Dyrektywa v-for :
      • Działa jako atrybut znacznika <li> , iteruje po tablicy, renderuje nazwy komponentów potomnych, w których iterator jest wymieniony w nawiasach {{ }} (gdzie piszemy tekst dla elementów listy).

Kod i powyższe wyjaśnienie stanowią podstawę do późniejszego zrozumienia, w jaki sposób skrypt i szablon są ze sobą powiązane i jak możemy korzystać z dyrektyw dostarczonych przez Vue.js.

Nauczyliśmy się całkiem sporo, ale nawet po tym wszystkim pozostało nam do nauczenia się jednej rzeczy o płynnym łączeniu komponentów w hierarchię — przekazywaniu danych z komponentu nadrzędnego do jego dzieci. Na razie musimy nauczyć się, jak przekazać niektóre dane z src/App.vue do src/components/Content.vue , abyśmy mogli użyć tych samych technik dla reszty zagnieżdżania komponentów w tym projekcie.

Dane spływające z komponentów nadrzędnych do komponentów podrzędnych mogą wydawać się proste, ale diabeł tkwi w szczegółach! Jak krótko wyjaśniono poniżej, istnieje wiele kroków, aby to działało:

  • Definiowanie i dane
    Na razie chcemy trochę danych statycznych do zabawy — obiekt zawierający zakodowane na stałe wartości dotyczące różnych aspektów pogody będzie po prostu w porządku! Tworzymy obiekt o nazwie weather_data i zwracamy go z funkcji data() App.vue . Obiekt weather_data jest podany w poniższym fragmencie:
 weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "NE", }, visibility: "12 km", }, },
  • Przekazywanie danych od rodzica
    Aby przekazać dane, potrzebujemy miejsca docelowego, do którego chcemy przesłać dane! W tym przypadku miejscem docelowym jest komponent Content.vue , a sposobem jego implementacji jest:
    • Przypisz obiekt weather_data do niestandardowego atrybutu tagu <Content>
    • Powiąż atrybut z danymi za pomocą dyrektywy v-bind : dostarczonej przez Vue.js, co sprawia, że ​​wartość atrybutu jest dynamiczna (reaguje na zmiany dokonane w oryginalnych danych).
       <Content v-bind:weather_data=“weather_data”></Content>

Definiowanie i przekazywanie danych odbywa się po stronie źródłowej uzgadniania, którym w naszym przypadku jest plik App.vue .

Kod pliku App.vue , w jego aktualnym stanie, jest podany poniżej:

 <template> <div> <p>This component's code is in {{ filename }}</p> <Content v-bind:weather_data="weather_data"></Content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'Content': Content }, data () { return { filename: 'App.vue', weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "NE", }, visibility: "12 km", }, }, } }, methods: { }, computed: { }, } </script> <style> </style> 
Zrzut ekranu przeglądarki z komunikatem „Kod tego komponentu znajduje się w App.vue. Te komponenty podrzędne Content.vue to: TempVarChart.vue, Highlights.vue”
(duży podgląd)

Po zdefiniowaniu danych i przesłaniu ich ze źródła (komponentu nadrzędnego) obowiązkiem dziecka jest teraz otrzymanie danych i ich odpowiednie renderowanie, jak wyjaśniono w kolejnych dwóch krokach.

  • Otrzymanie danych przez dziecko
    Komponent podrzędny, w tym przypadku Content.vue , musi otrzymać obiekt weather_data wysłany do niego przez komponent nadrzędny App.vue . Vue.js zapewnia do tego mechanizm — wystarczy obiekt tablicy o nazwie props , zdefiniowany w domyślnym obiekcie eksportowanym przez Content.vue . Każdy element tablicy props jest nazwą obiektów danych, które chce otrzymać od swojego rodzica. Na razie jedynym obiektem danych, który powinien otrzymać, jest weather_data z App.vue. Tak więc tablica props wygląda tak:
 <template> // HTML template code here </template> <script> export default { props: ["weather_data"], data () { return { // data here } }, } </script> <style> // component specific CSS here </style>
  • Renderowanie danych na stronie
    Teraz, gdy zapewniliśmy otrzymanie danych, ostatnim zadaniem, które musimy wykonać, jest renderowanie danych. W tym przykładzie bezpośrednio zrzucimy otrzymane dane na stronę internetową, aby zilustrować technikę. Jednak w rzeczywistych aplikacjach (takich jak ta, którą zamierzamy zbudować), dane zwykle przechodzą wiele procesów przetwarzania i tylko odpowiednie ich części są wyświetlane w sposób odpowiedni do celu. Na przykład w tym projekcie ostatecznie uzyskamy surowe dane z API pogodowego, wyczyścimy je i sformatujemy, wprowadzimy dane do struktur danych niezbędnych dla wykresów, a następnie je zwizualizujemy. W każdym razie, aby wyświetlić zrzut surowych danych, użyjemy po prostu nawiasów {{ }} zrozumiałych dla Vue, jak pokazano na poniższym fragmencie:
 <template> <div> // other template code here {{ weather_data }} </div> </template>

Teraz nadszedł czas na przyswojenie wszystkich kawałków i kawałków. Kod Content.vue — w obecnym stanie — jest podany poniżej:

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} </div> </template> <script> export default { props: ["weather_data"], data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> #pagecontent { border: 1px solid black; padding: 2px; } </style> 
Zrzut ekranu przeglądarki z wynikiem dostarczonego kodu
(duży podgląd)

Po dokonaniu opisanych powyżej zmian odśwież stronę w przeglądarce i zobacz jak wygląda. Poświęć chwilę, aby docenić złożoność obsługiwaną przez Vue — jeśli zmodyfikujesz obiekt weather_data w App.vue , zostanie on po cichu przesłany do Content.vue , a ostatecznie do przeglądarki wyświetlającej stronę internetową! Spróbuj, zmieniając wartość lokalizacji klucza.

Chociaż poznaliśmy właściwości props i wiązanie danych przy użyciu danych statycznych, będziemy używać w aplikacji danych dynamicznych gromadzonych za pomocą webowych API i odpowiednio zmienimy kod .

Streszczenie

Zanim przejdziemy do pozostałych plików .vue , podsumujmy to, czego nauczyliśmy się podczas pisania kodu dla App.vue i components/Content.vue :

  • Plik App.vue jest tym, co nazywamy komponentem głównym — tym, który znajduje się na szczycie hierarchii komponentów. Pozostałe pliki .vue reprezentują składniki, które są jego bezpośrednim dzieckiem, wnukiem i tak dalej.
  • Plik Content.vue jest fikcyjnym komponentem — jego obowiązkiem jest przekazywanie danych do poziomów poniżej i zachowanie hierarchii strukturalnej, tak aby nasz kod pozostał zgodny z filozofią „*wdrażamy to, co widzimy*”.
  • Relacja rodzic-dziecko komponentu nie powstaje znikąd — musisz zarejestrować komponent (globalnie lub lokalnie, w zależności od zamierzonego użycia komponentu), a następnie zagnieździć go za pomocą niestandardowych tagów HTML (którego pisownia jest dokładna takie same jak nazwy, pod którymi zarejestrowano komponenty).
  • Po zarejestrowaniu i zagnieżdżeniu dane są przekazywane z komponentów nadrzędnych do podrzędnych, a przepływ nigdy nie jest odwrócony (jeśli architektura projektu zezwala na przepływ wsteczny, mogą się zdarzyć złe rzeczy). Komponent nadrzędny jest względnym źródłem danych i przekazuje odpowiednie dane swoim dzieciom za pomocą dyrektywy v-bind dla atrybutów niestandardowych elementów HTML. Dziecko otrzymuje przeznaczone dla niego dane za pomocą rekwizytów, a następnie samodzielnie decyduje, co z tymi danymi zrobić.

W przypadku pozostałych komponentów nie będziemy zagłębiać się w szczegółowe wyjaśnienia — po prostu napiszemy kod w oparciu o wnioski z powyższego podsumowania. Kod będzie oczywisty, a jeśli nie masz pewności co do hierarchii, zapoznaj się z poniższym diagramem:

Schemat wyjaśniający hierarchię kodu
(duży podgląd)

Diagram mówi, że TempVarChart.vue i Highlights.vue są bezpośrednim dzieckiem Content.vue . Dlatego dobrym pomysłem może być przygotowanie Content.vue do wysyłania danych do tych komponentów, co robimy za pomocą poniższego kodu:

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue' import Highlights from './Highlights.vue' export default { props: ["weather_data"], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'], tempVar: this.weather_data.temperature, highlights: this.weather_data.highlights, } }, methods: { }, computed: { }, } </script> <style> </style>

Po zapisaniu tego kodu pojawią się błędy — nie martw się, jest to oczekiwane. Zostanie to naprawione, gdy będziesz mieć gotowe pozostałe pliki składowe. Jeśli przeszkadza Ci to, że nie możesz zobaczyć wyników, skomentuj wiersze zawierające niestandardowe znaczniki elementów <temp-var-chart> i <today-highlights> .

W tej sekcji jest to ostateczny kod Content.vue . W pozostałej części tej sekcji będziemy odwoływać się do tego kodu , a nie do poprzednich, które napisaliśmy do nauki.

src/components/TempVarChart.vue

Z nadrzędnym komponentem Content.vue przekazującym dane, TempVarChart.vue musi być skonfigurowany do odbierania i renderowania danych, jak pokazano w poniższym kodzie:

 <template> <div> <p>Temperature Information:</p> {{ tempVar }} </div> </template> <script> export default { props: ["tempVar"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

src/components/Highlights.vue

Ten komponent otrzyma również dane z App.vue — jego komponentu nadrzędnego. Następnie należy go połączyć z komponentami podrzędnymi i przekazać im odpowiednie dane.

Zobaczmy najpierw kod odbierania danych od rodzica:

 <template> <div> <p>Weather Highlights:</p> {{ highlights }} </div> </template> <script> export default { props: ["highlights"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

W tym momencie strona internetowa wygląda jak na poniższym obrazku:

Wynik kodu wyświetlanego w przeglądarce
(duży podgląd)

Teraz musimy zmodyfikować kod Highlights.vue , aby zarejestrować i zagnieździć jego komponenty podrzędne, a następnie przekazać dane do dzieci. Kod tego jest następujący:

 <template> <div> <p>Weather Highlights:</p> {{ highlights }} <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

Po zapisaniu kodu i wyświetleniu strony internetowej prawdopodobnie pojawią się błędy w narzędziu Developer Console udostępnianym przez przeglądarkę; pojawiają się, ponieważ chociaż Highlights.vue wysyła dane, nikt ich nie odbiera. Jeszcze nie napisaliśmy kodu dla dzieci Highlights.vue .

Zauważ, że nie zrobiliśmy zbyt wiele z przetwarzania danych, tj. nie wyodrębniliśmy poszczególnych czynników danych pogodowych, które znajdują się w sekcji Najważniejsze informacje na desce rozdzielczej. Mogliśmy to zrobić w funkcji data() , ale woleliśmy zachować Highlights.vue głupi komponent, który po prostu przekazuje cały zrzut danych, który otrzymuje, każdemu z dzieci, które następnie posiadają własne ekstrakty, co jest dla nich niezbędne . Zachęcamy jednak do wypróbowania wyodrębniania danych z Highlights.vue i wysyłania odpowiednich danych do każdego komponentu podrzędnego — mimo wszystko jest to dobre ćwiczenie!

src/components/UVIndex.vue

Kod tego komponentu otrzymuje zrzut danych z podświetleniami z Highlights.vue , wyodrębnia dane dla indeksu UV i renderuje je na stronie.

 <template> <div> <p>UV Index: {{ uvindex }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { uvindex: this.highlights.uvindex } }, methods: { }, computed: { }, } </script> <style> </style>

src/components/Visibility.vue

Kod tego komponentu otrzymuje zrzut danych z podświetleniami z Highlights.vue , wyodrębnia dane dla Widoczności i renderuje je na stronie.

 <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility, } }, methods: { }, computed: { }, } </script> <style> </style>

src/components/WindStatus.vue

Kod tego komponentu odbiera zrzut danych z Highlights.vue , wyodrębnia dane dotyczące stanu wiatru (prędkość i kierunek) i renderuje je na stronie.

 <template> <div> <p>Wind Status:</p> <p>Speed — {{ speed }}; Direction — {{ direction }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { speed: this.highlights.windstatus.speed, direction: this.highlights.windstatus.direction } }, methods: { }, computed: { }, } </script> <style> </style>

Po dodaniu kodu dla wszystkich komponentów spójrz na stronę internetową w przeglądarce.

Wynik kodu wyświetlanego w przeglądarce
(duży podgląd)

Nie zniechęcać, ale cała ta harówka polegała tylko na łączeniu komponentów w hierarchię i testowaniu, czy przepływ danych ma miejsce między nimi, czy nie! W następnej sekcji wyrzucimy większość kodu, który do tej pory napisaliśmy , a dodamy dużo więcej odnoszącego się do samego projektu. Z pewnością jednak zachowamy strukturę i zagnieżdżenie komponentów; wnioski z tej sekcji pozwolą nam zbudować przyzwoity dashboard z Vue.js.

4. Pozyskiwanie i przetwarzanie danych

Pamiętasz obiekt weather_data w App.vue ? Zawierał pewne zakodowane dane, których użyliśmy do sprawdzenia, czy wszystkie komponenty działają poprawnie, a także do nauczenia się podstawowych aspektów aplikacji Vue bez zagłębiania się w szczegóły danych ze świata rzeczywistego. Jednak teraz nadszedł czas, abyśmy zrzucili naszą powłokę i wkroczyli do prawdziwego świata, w którym dane z API zdominują większość naszego kodu.

Przygotowywanie komponentów podrzędnych do odbierania i przetwarzania prawdziwych danych

W tej sekcji otrzymasz zrzut kodu dla wszystkich komponentów oprócz App.vue . Kod będzie obsługiwał odbieranie prawdziwych danych z App.vue (w przeciwieństwie do kodu, który napisaliśmy w poprzedniej sekcji, aby odbierać i renderować dane fikcyjne).

Gorąco zachęcamy do uważnego przeczytania kodu każdego komponentu, aby uzyskać wyobrażenie o tym, jakich danych oczekuje każdy z tych komponentów i które zostaną ostatecznie użyte w wizualizacji.

Część kodu i ogólna struktura będą podobne do tych, które widziałeś w poprzedniej strukturze — więc nie spotkasz się z czymś drastycznie innym. Jednak diabeł tkwi w szczegółach! Dlatego dokładnie przeanalizuj kod, a kiedy dobrze go zrozumiesz, skopiuj kod do odpowiednich plików składowych w swoim projekcie.

Uwaga : Wszystkie komponenty w tej sekcji znajdują się w katalogu src/components/ . Dlatego za każdym razem ścieżka nie będzie wymieniona — tylko nazwa pliku .vue zostanie wymieniona w celu identyfikacji komponentu.

Treść.vue

 <template> <div> <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue'; import Highlights from './Highlights.vue'; export default { props: ['highlights', 'tempVar'], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, } </script>

Następujące zmiany zostały wprowadzone z poprzedniego kodu:

  • W <template> , tekst i dane w {{ }} zostały usunięte, ponieważ teraz tylko otrzymujemy dane i przekazujemy je dzieciom, bez renderowania specyficznego dla tego komponentu.
  • W export default {} :
    • props zostały zmienione tak, aby pasowały do ​​obiektów danych, które zostaną wysłane przez rodzica: App.vue . Powodem zmiany rekwizytów jest to, że sama App.vue wyświetli niektóre dane, które pozyska z API pogody i innych zasobów online, w oparciu o zapytanie wyszukiwania użytkownika, a resztę przekaże. W fikcyjnym kodzie, który napisaliśmy wcześniej, App.vue przekazywał cały fikcyjny zrzut danych bez żadnej dyskryminacji, a rekwizyty Content.vue zostały odpowiednio skonfigurowane.
    • Funkcja data() teraz nic nie zwraca, ponieważ nie wykonujemy żadnej manipulacji danymi w tym komponencie.

TempVarChart.vue

Ten komponent ma otrzymywać szczegółowe prognozy temperatury na resztę bieżącego dnia i ostatecznie wyświetlać je za pomocą FusionCharts. Ale na razie wyświetlamy je tylko jako tekst na stronie.

 <template> <div> {{ tempVar.tempToday }} </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { }; }, methods: { }, }; </script> <style> </style>

Highlights.vue

 <template> <div> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

Zmiany wprowadzone z poprzedniego kodu to:

  • W <template> , tekst i dane w {{ }} zostały usunięte, ponieważ jest to głupi składnik, podobnie jak Content.vue , którego jedynym zadaniem jest przekazywanie danych dzieciom przy zachowaniu hierarchii strukturalnej. Pamiętaj, że głupie komponenty, takie jak Highlights.vue i Content.vue , istnieją po to, by zachować parzystość między wizualną strukturą dashboardu a kodem, który piszemy.

UVIndex.vue

Zmiany wprowadzone do poprzedniego kodu są następujące:

  • W <template> i <style> div id został zmieniony na uvIndex , który jest bardziej czytelny.
  • W export default {} funkcja data() zwraca teraz obiekt ciągu uvIndex , którego wartość jest pobierana z obiektu Highlights otrzymanego przez komponent za pomocą props . Ten uvIndex jest teraz tymczasowo używany do wyświetlania wartości jako tekstu w <template> . Później wstawimy tę wartość do struktury danych odpowiedniej do renderowania wykresu.

Widoczność.vue

 <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility.toString() } }, methods: { }, computed: { }, } </script> <style> </style>

Jedyną zmianą dokonaną w tym pliku (w stosunku do poprzedniego kodu) jest to, że definicja obiektu visibility zwracanego przez funkcję data() zawiera teraz toString() na swoim końcu, ponieważ wartość otrzymana od rodzica będzie zmiennoprzecinkowa numer punktu, który należy zamienić na ciąg.

WindStatus.vue

 <template> <div> <p>Wind Speed — {{ windSpeed }}</p> <p>Wind Direction — {{ derivedWindDirection }}, or {{ windDirection }} degree clockwise with respect to true N as 0 degree.</p> </div> </template> <script> export default { props: ["highlights"], data () { return { windSpeed: this.highlights.windStatus.windSpeed, derivedWindDirection: this.highlights.windStatus.derivedWindDirection, windDirection: this.highlights.windStatus.windDirection } }, methods: { }, computed: { }, } </script> <style> </style>

Zmiany wprowadzone do poprzedniego kodu są następujące:

  • W całym pliku windstatus została zmieniona na windStatus , aby zwiększyć czytelność, a także zapewnić synchronizację z obiektem Highlights udostępnianym przez App.vue z rzeczywistymi danymi.
  • Podobne zmiany w nazewnictwie zostały wprowadzone dla prędkości i kierunku — nowe to windSpeed ​​i windDirection .
  • Do gry wszedł nowy obiekt pochodnyWindDirection (dostarczany również przez derivedWindDirection w App.vue Highlights).

Na razie otrzymane dane są renderowane jako tekst; później zostanie podłączony do struktury danych niezbędnej do wizualizacji.

Testowanie z fikcyjnymi danymi

Wielokrotne korzystanie z fikcyjnych danych może być dla Ciebie nieco frustrujące, ale jest kilka dobrych powodów:

  • Wprowadziliśmy wiele zmian w kodzie każdego komponentu i dobrym pomysłem jest sprawdzenie, czy te zmiany nie łamią kodu. Innymi słowy, teraz, gdy przechodzimy do bardziej złożonych części projektu, powinniśmy sprawdzić, czy przepływ danych jest nienaruszony.
  • Prawdziwe dane z internetowego interfejsu API pogody będą wymagały dużo masowania, a żonglowanie między kodem do pozyskiwania i przetwarzania danych a kodem do płynnego przepływu danych przez komponenty może być przytłaczające. Chodzi o to, aby utrzymać pod kontrolą kwant złożoności, abyśmy mogli lepiej zrozumieć błędy, z którymi możemy się spotkać.

W tej sekcji zasadniczo zakodujemy na sztywno niektóre dane json w App.vue , które oczywiście zostaną zastąpione danymi na żywo w najbliższej przyszłości. Istnieje wiele podobieństw między fikcyjną strukturą json a strukturą json, której użyjemy do rzeczywistych danych. Zapewnia to również przybliżone wyobrażenie o tym, czego można się spodziewać po prawdziwych danych, gdy tylko je napotkamy.

Przyznajemy jednak, że jest to dalekie od idealnego podejścia, jakie można by przyjąć budując taki projekt od podstaw. W prawdziwym świecie często zaczynasz od prawdziwego źródła danych, bawisz się nim trochę, aby zrozumieć, co można i należy zrobić, aby je oswoić, a następnie zastanawiasz się nad odpowiednią strukturą danych json, aby przechwycić odpowiednie informacje. Celowo uchroniliśmy Cię przed całą tą brudną robotą, ponieważ oddala Cię to od celu — nauki korzystania z Vue.js i FusionCharts do budowy pulpitu nawigacyjnego.

Przejdźmy teraz do nowego kodu App.vue:

 <template> <div> <dashboard-content :highlights="highlights" :tempVar="tempVar"></dashboard-content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'dashboard-content': Content }, data () { return { tempVar: { tempToday: [ {hour: '11.00 AM', temp: '35'}, {hour: '12.00 PM', temp: '36'}, {hour: '1.00 PM', temp: '37'}, {hour: '2.00 PM', temp: '38'}, {hour: '3.00 PM', temp: '36'}, {hour: '4.00 PM', temp: '35'}, ], }, highlights: { uvIndex: 4, visibility: 10, windStatus: { windSpeed: '30 km/h', windDirection: '30', derivedWindDirection: 'NNE', }, }, } }, methods: { }, computed: { }, } </script> <style> </style>

Zmiany wprowadzone w kodzie w stosunku do jego poprzedniej wersji są następujące:

  • Nazwa komponentu podrzędnego została zmieniona na zawartość pulpitu nawigacyjnego, a zatem niestandardowy element HTML w <template> został poprawiony. Zauważ, że teraz mamy dwa atrybuty — highlights i tempVar — zamiast jednego atrybutu, którego używaliśmy wcześniej z elementem niestandardowym. W związku z tym zmieniły się również dane związane z tymi atrybutami. Co ciekawe, możemy użyć dyrektywy v-bind: lub jej skrótu : (tak jak to zrobiliśmy tutaj) z wieloma atrybutami niestandardowego elementu HTML!
  • Funkcja data() zwraca teraz obiekt filename (który istniał wcześniej) wraz z dwoma nowymi obiektami (zamiast starych weather_data ): tempVar i highlights . Struktura json jest odpowiednia dla kodu, który napisaliśmy w komponentach potomnych, dzięki czemu mogą wyodrębnić potrzebne fragmenty danych ze zrzutów. Struktury są dość oczywiste i można się spodziewać, że będą dość podobne, gdy mamy do czynienia z danymi na żywo. Jednak istotną zmianą, którą napotkasz, jest brak twardego kodowania (oczywiście, prawda) — pozostawimy wartości puste jako stan domyślny i napiszemy kod, aby dynamicznie je aktualizować na podstawie wartości, które otrzymamy od API pogody.

Napisałeś dużo kodu w tej sekcji, nie widząc rzeczywistych wyników. Zanim przejdziesz dalej, spójrz na przeglądarkę (w razie potrzeby zrestartuj serwer za pomocą npm run dev ) i rozkoszuj się swoim osiągnięciem. Strona internetowa, którą powinieneś zobaczyć w tym momencie, wygląda jak na poniższym obrazku:

Wynik kodu wyświetlanego w przeglądarce
(duży podgląd)

Kod do pozyskiwania i przetwarzania danych

Ta sekcja będzie mięsem projektu, z całym kodem, który zostanie napisany w App.vue dla następujących rzeczy:

  • Dane lokalizacji od użytkownika — wystarczy pole wprowadzania i przycisk wezwania do działania;
  • Funkcje użytkowe do różnych zadań; funkcje te będą wywoływane później w różnych częściach kodu komponentu;
  • Uzyskiwanie szczegółowych danych geolokalizacyjnych z Google Maps API dla JavaScript;
  • Uzyskiwanie szczegółowych danych pogodowych z API Dark Sky;
  • Formatowanie i przetwarzanie danych geolokalizacyjnych i pogodowych, które zostaną przekazane do komponentów podrzędnych.

Poniższe podrozdziały ilustrują, w jaki sposób możemy zrealizować zadania określone w powyższych punktach. Z pewnymi wyjątkami większość z nich będzie postępować zgodnie z kolejnością.

Dane wejściowe od użytkownika

Jak widać, akcja rozpoczyna się w momencie, gdy użytkownik poda nazwę miejsca, dla którego mają być wyświetlane dane pogodowe. Aby tak się stało, musimy zaimplementować:

  • Pole do wprowadzania lokalizacji;
  • Przycisk przesyłania, który informuje naszą aplikację, że użytkownik wprowadził lokalizację i nadszedł czas, aby zrobić resztę. Zaimplementujemy również zachowanie, gdy przetwarzanie rozpocznie się po naciśnięciu klawisza Enter .

Kod, który pokazujemy poniżej, będzie ograniczony do części szablonu HTML App.vue . Wspomnimy tylko nazwę metody powiązanej ze zdarzeniami kliknięcia i zdefiniujemy je później w obiekcie metody <skryptu> w App.vue.

 <div> <input type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div>

Umieszczenie powyższego fragmentu we właściwym miejscu jest banalne — zostawiamy to Tobie. Jednak interesujące części fragmentu kodu to:

  • @keyup.enter="organizeAllDetails"
  • @click="organizeAllDetails"

Jak wiecie z poprzednich sekcji, @ jest skrótem Vue dla dyrektywy v-on :, która jest powiązana z pewnym zdarzeniem. Nowością jest „ organizeAllDetails ” — to nic innego jak metoda, która zostanie uruchomiona po wystąpieniu zdarzenia (naciśnięcie klawisza Enter lub kliknięcie przycisku). Jeszcze nie zdefiniowaliśmy metody, a zagadka będzie gotowa do końca tej sekcji.

Wyświetlanie informacji tekstowych kontrolowane przez App.vue

Gdy dane wejściowe użytkownika wyzwalają akcję i wiele danych jest pobieranych z interfejsów API, napotykamy nieuniknione pytanie — „Co zrobić z tymi wszystkimi danymi?”. Oczywiście wymagane jest masowanie danych, ale to nie odpowiada w pełni na nasze pytanie! Musimy zdecydować, jaki jest końcowy sposób wykorzystania danych, a dokładniej, jakie są podmioty, które otrzymują różne porcje pozyskanych i przetworzonych danych?

Komponenty podrzędne App.vue , w oparciu o ich hierarchię i przeznaczenie, są głównymi rywalami o większość danych. Jednak będziemy mieć również pewne dane, które nie należą do żadnego z tych komponentów podrzędnych, ale są dość pouczające i sprawiają, że pulpit nawigacyjny jest kompletny. Możemy je dobrze wykorzystać, wyświetlając je jako informacje tekstowe bezpośrednio kontrolowane przez App.vue , podczas gdy reszta danych jest przekazywana dziecku, aby ostatecznie wyświetlić je jako ładne wykresy.

Mając ten kontekst na uwadze, skupmy się na kodzie do ustawienia etapu korzystania z danych tekstowych. W tym momencie jest to prosty szablon HTML, na którym dane w końcu przyjdą i zasiądą.

 <div> <div class="wrapper-left"> <div> {{ currentWeather.temp }} <span>°C</span> </div> <div>{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div> <img src="./assets/location.svg" width="10.83" height="15.83" > {{ currentWeather.full_location }} <div class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div>

W powyższym fragmencie powinieneś zrozumieć, co następuje:

  • Rzeczy wewnątrz {{ }} — są sposobem Vue na wstawianie dynamicznych danych do szablonu HTML, zanim zostaną one wyrenderowane w przeglądarce. Spotkałeś ich już wcześniej i nie ma nic nowego ani zaskakującego. Pamiętaj tylko, że te obiekty danych pochodzą z metody data() w obiekcie export default() App.vue . Mają wartości domyślne, które ustawimy zgodnie z naszymi wymaganiami, a następnie napiszemy określone metody, aby zapełnić obiekty rzeczywistymi danymi API.

Nie martw się, że nie zobaczysz zmian w przeglądarce — dane nie są jeszcze zdefiniowane i Vue nie renderuje rzeczy, których nie zna. Jednak po ustawieniu danych (a na razie możesz nawet sprawdzić, zakodowując dane), dane tekstowe będą kontrolowane przez App.vue .

Metoda data()

Metoda data() jest specjalną konstrukcją w plikach .vue — zawiera i zwraca obiekty danych, które są tak ważne dla aplikacji. Przypomnij sobie ogólną strukturę części <script> w dowolnym pliku .vue — zawiera ona z grubsza następujące elementy:

 <script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. // the data objects will have certain default values chosen by us. // The methods that we define below will manipulate the data. // Since the data is bounded to various attributes and directives, they // will update as and when the values of the data objects change. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. }, computed: { // computed properties here }, // other objects, as necessary } </script>

Do tej pory napotkałeś nazwy niektórych obiektów danych, ale jest ich znacznie więcej. Większość z nich dotyczy komponentów podrzędnych, z których każdy obsługuje inny aspekt zrzutu informacji o pogodzie. Poniżej podana jest cała metoda data() , której będziemy potrzebować w tym projekcie — będziesz miał dobre pojęcie o tym, jakich danych oczekujemy od interfejsów API i jak je rozpowszechniamy, w oparciu o nomenklaturę obiektów.

 data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; },

Jak widać, w większości przypadków domyślna wartość jest pusta, ponieważ w tym momencie to wystarczy. Zostaną napisane metody manipulowania danymi i wypełniania ich odpowiednimi wartościami, zanim zostaną one wyrenderowane lub przekazane do komponentów podrzędnych.

Metody w App.vue

W przypadku plików .vue metody są zazwyczaj zapisywane jako wartości kluczy zagnieżdżonych w obiekcie Method methods { } . Ich podstawową rolą jest manipulowanie obiektami danych komponentu. Będziemy pisać metody w App.vue pamiętając o tej samej filozofii. Jednak na podstawie ich przeznaczenia możemy podzielić metody App.vue na następujące:

  • Metody użytkowe
  • Metody zorientowane na działanie/zdarzenia
  • Metody pozyskiwania danych
  • Metody przetwarzania danych
  • Metody klejenia wysokiego poziomu

Ważne, żebyś to zrozumiał — przedstawiamy Ci metody na talerzu, ponieważ już zorientowaliśmy się, jak działają API, jakie dane podają i jak powinniśmy je wykorzystać w naszym projekcie. Nie chodzi o to, że wyciągnęliśmy metody z powietrza i napisaliśmy jakiś tajemny kod, aby poradzić sobie z danymi. W celu nauki dobrym ćwiczeniem jest uważne przeczytanie i zrozumienie kodu metod i danych. Jednak w obliczu nowego projektu, który musisz zbudować od zera, całą brudną robotę musisz wykonać sam, a to oznacza dużo eksperymentowania z interfejsami API — ich programistycznym dostępem i strukturą danych, zanim skleisz je bezproblemowo z danymi struktura, której wymaga Twój projekt. Nie będziesz trzymać się za rękę i będą frustrujące chwile, ale to wszystko jest częścią dojrzewania jako programista.

W kolejnych podrozdziałach wyjaśnimy każdy z typów metod, a także pokażemy implementację metod należących do tej kategorii. Nazwy metod są dość oczywiste, jeśli chodzi o ich cel, podobnie jak ich implementacja, co naszym zdaniem będzie wystarczająco łatwe do naśladowania. Jednak wcześniej przypomnij sobie ogólny schemat pisania metod w plikach .vue :

 <script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. method_1: function(arg_1) { }, method_2: function(arg_1, arg_2) { }, method_3: function(arg_1) { }, ……. }, computed: { // computed properties here }, // other objects, as necessary } </script>

Metody użytkowe

Metody użytkowe, jak sama nazwa wskazuje, są metodami napisanymi głównie w celu modularyzacji powtarzalnego kodu używanego do zadań brzegowych. W razie potrzeby są wywoływane innymi metodami. Poniżej podano metody narzędziowe dla App.vue :

 convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
 // To format the “possibility” (of weather) string obtained from the weather API formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
 // To convert Unix timestamps according to our convenience unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; },
 // To convert temperature from fahrenheit to celcius fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; },
 // To convert the air pressure reading from millibar to kilopascal milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); },
 // To convert distance readings from miles to kilometers mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); },
 // To format the wind direction based on the angle deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; },

Chociaż tego nie zaimplementowaliśmy, możesz pobrać metody narzędziowe z pliku .vue i umieścić je w osobnym pliku JavaScript. Wszystko, co musisz zrobić, to zaimportować plik .js na początku części skryptu w pliku .vue i powinieneś być gotowy. Takie podejście działa naprawdę dobrze i utrzymuje kod w czystości, zwłaszcza w dużych aplikacjach, w których można użyć wielu metod, które są lepiej pogrupowane razem na podstawie ich przeznaczenia. Możesz zastosować to podejście do wszystkich grup metod wymienionych w tym artykule i zobaczyć sam efekt. Sugerujemy jednak wykonanie tego ćwiczenia po przejściu kursu przedstawionego tutaj, aby mieć pełny obraz zrozumienia wszystkich części pracujących w pełnej synchronizacji, a także mieć działające oprogramowanie, do którego można się odwołać, gdy coś przerwy podczas eksperymentowania.

Metody zorientowane na działanie/zdarzenia

Te metody są zazwyczaj wykonywane, gdy musimy wykonać akcję odpowiadającą zdarzeniu. W zależności od przypadku zdarzenie może zostać wywołane przez interakcję użytkownika lub programowo. W pliku App.vue metody te znajdują się poniżej metod narzędziowych.

 makeInputEmpty: function() { this.$refs.input.value = ''; },
 makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; },
 detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); },
 locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); },

Jedną z interesujących rzeczy w niektórych z powyższych fragmentów kodu jest użycie $ref . Mówiąc prościej, jest to sposób Vue na powiązanie zawierającej go instrukcji kodu z konstrukcją HTML, której ma dotyczyć (więcej informacji można znaleźć w oficjalnym przewodniku). Na przykład metody makeInputEmpty() i detectEnterKeyPress() wpływają na pole wejściowe, ponieważ w kodzie HTML pola wejściowego wymieniliśmy wartość atrybutu ref jako input .

Metody akwizycji danych

W naszym projekcie używamy następujących dwóch API:

  • Interfejs API Geokodera Map Google
    Ten interfejs API służy do pobierania współrzędnych lokalizacji wyszukiwanej przez użytkownika. Potrzebujesz klucza API dla siebie, który możesz uzyskać, postępując zgodnie z dokumentacją pod podanym linkiem. Na razie możesz użyć klucza API używanego przez FusionCharts, ale prosimy o nie nadużywanie go i uzyskanie własnego klucza. Odwołujemy się do API JavaScript z pliku index.html tego projektu i użyjemy dostarczonych przez niego konstruktorów dla naszego kodu w pliku App.vue .
  • Interfejs API Pogody Ciemnego Nieba
    Ten interfejs API służy do pobierania danych pogodowych odpowiadających współrzędnym. Jednak nie będziemy go używać bezpośrednio; owiniemy go w adres URL, który przekierowuje przez jeden z serwerów FusionCharts. Powodem jest to, że jeśli wyślesz żądanie GET do interfejsu API z aplikacji całkowicie klienckiej, takiej jak nasza, spowoduje to frustrujący błąd CORS (więcej informacji tutaj i tutaj).

Ważna uwaga : ponieważ korzystaliśmy z interfejsów API Map Google i Dark Sky, oba te interfejsy API mają własne klucze API, które udostępniliśmy w tym artykule. Pomoże Ci to skupić się na rozwoju po stronie klienta, a nie na problemie związanym z implementacją zaplecza. Zalecamy jednak tworzenie własnych kluczy , ponieważ nasze klucze API będą miały limity i jeśli te limity przekroczą, nie będziesz w stanie samodzielnie wypróbować aplikacji.

W przypadku Map Google przejdź do tego artykułu, aby uzyskać klucz API. W przypadku interfejsu Dark Sky API odwiedź stronę https://darksky.net/dev, aby utworzyć klucz API i odpowiednie punkty końcowe.

Mając na uwadze kontekst, przyjrzyjmy się implementacji metod pozyskiwania danych dla naszego projektu.

 getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); },
 /* The coordinates that Google Maps Geocoder API returns are way too accurate for our requirements. We need to bring it into shape before passing the coordinates on to the weather API. Although this is a data processing method in its own right, we can't help mentioning it right now, because the data acquisition method for the weather API has dependency on the output of this method. */ setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } },
 /* This method dynamically creates the the correct weather API query URL, based on the formatted latitude and longitude. The complete URL is then fed to the method querying for weather data. Notice that the base URL used in this method (without the coordinates) points towards a FusionCharts server — we must redirect our GET request to the weather API through a server to avoid the CORS error. */ fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; },
 fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } },

Through these methods, we have introduced the concept of async-await in our code. If you have been a JavaScript developer for some time now, you must be familiar with the callback hell, which is a direct consequence of the asynchronous way JavaScript is written. ES6 allows us to bypass the cumbersome nested callbacks, and our code becomes much cleaner if we write JavaScript in a synchronous way, using the async-await technique. However, there is a downside. It takes away the speed that asynchronous code gives us, especially for the portions of the code that deals with data being exchanged over the internet. Since this is not a mission-critical application with low latency requirements, and our primary aim is to learn stuff, the clean code is much more preferable over the slightly fast code.

Data Processing Methods

Now that we have the methods that will bring the data to us, we need to prepare the ground for properly receiving and processing the data. Safety nets must be cast, and there should be no spills — data is the new gold (OK, that might be an exaggeration in our context)! Enough with the fuss, let's get to the point.

Technically, the methods we implement in this section are aimed at getting the data out of the acquisition methods and the data objects in App.vue , and sometimes setting the data objects to certain values that suits the purpose.

getTimezone: function() { return this.rawWeatherData.timezone; },
 getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; },
 getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; },
 getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; },
 getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); },
 getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; },
 getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; },
 getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; },
 getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } },
 getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; },
 getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); },
 getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); },

Metody klejenia wysokiego poziomu

Po usunięciu narzędzi użyteczności, pozyskiwania i przetwarzania, pozostaje nam teraz zadanie zaaranżowania całości. Robimy to, tworząc metody klejenia wysokiego poziomu, które zasadniczo wywołują metody opisane powyżej w określonej kolejności, dzięki czemu cała operacja jest wykonywana bezproblemowo.

 // Top level for info section // Data in this.currentWeather organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); },
 // Top level for highlights organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); },
 // Top level organization and rendering organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); },

zamontowane

Vue udostępnia haki cyklu życia instancji — właściwości, które są zasadniczo metodami i są wyzwalane, gdy cykl życia instancji osiągnie ten etap. Na przykład utworzone, zamontowane, przed aktualizacją itp. są bardzo przydatnymi hakami cyklu życia, które pozwalają programiście kontrolować instancję na znacznie bardziej szczegółowym poziomie, niż byłoby to możliwe w innym przypadku.

W kodzie komponentu Vue te hooki cyklu życia są zaimplementowane tak, jak w przypadku każdego innego prop . Na przykład:

 <template> </template> <script> // import statements export default { data() { return { // data objects here } }, methods: { // methods here }, mounted: function(){ // function body here }, } </script> <style> </style>

Uzbrojony w to nowe zrozumienie, spójrz na poniższy kod dla mounted podpory App.vue :

 mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); }

Kompletny kod dla App.vue

W tej sekcji omówiliśmy wiele tematów, a kilka ostatnich sekcji dało ci rzeczy w kawałkach. Jednak ważne jest, aby mieć kompletny, skompilowany kod dla App.vue (podlega dalszym modyfikacjom w kolejnych sekcjach). Oto jest:

 <template> <div> <div class="container-fluid"> <div class="row"> <div class="col-md-3 col-sm-4 col-xs-12 sidebar"> <div> <input type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div> <div> <div class="wrapper-left"> <div> {{ currentWeather.temp }} <span>°C</span> </div> <div>{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div> <img src="./assets/location.svg" width="10.83" height="15.83" > {{ currentWeather.full_location }} <div class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div> </div> <dashboard-content class="col-md-9 col-sm-8 col-xs-12 content" :highlights="highlights" :tempVar="tempVar" ></dashboard-content> </div> </div> </div> </template> <script> import Content from './components/Content.vue'; export default { name: 'app', props: [], components: { 'dashboard-content': Content }, data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; }, methods: { // Some utility functions convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; }, fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; }, milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); }, mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); }, deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; }, // Some basic action oriented functions makeInputEmpty: function() { this.$refs.input.value = ''; }, makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; }, detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); }, locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); }, getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); }, // Some basic asynchronous functions setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } }, fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; }, fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } }, // Get and set functions; often combined, because they are short // For basic info — left panel/sidebar getTimezone: function() { return this.rawWeatherData.timezone; }, getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; }, getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; }, getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; }, getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); }, getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; }, getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; }, getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; }, getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } }, // For Today Highlights getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; }, getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); }, getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); }, // top level for info section organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); }, organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); }, // topmost level orchestration organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); }, }, mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); } }; </script>

I wreszcie, po tylu cierpliwości i ciężkiej pracy, możesz zobaczyć przepływ danych z jego czystą mocą! Odwiedź aplikację w przeglądarce, odśwież stronę, wyszukaj lokalizację w polu wyszukiwania aplikacji i naciśnij Enter !

Aplikacja pokazana w przeglądarce
(duży podgląd)

Teraz, gdy skończyliśmy z dźwiganiem ciężarów, zrób sobie przerwę. Kolejne sekcje koncentrują się na wykorzystaniu danych do tworzenia pięknych i pouczających wykresów, a następnie zapewniają naszej brzydko wyglądającej aplikacji zasłużoną sesję pielęgnacyjną za pomocą CSS.

5. Wizualizacja danych za pomocą FusionCharts

Podstawowe zagadnienia dotyczące wykresów

Dla użytkownika końcowego istotą pulpitu nawigacyjnego jest zasadniczo to: zbiór przefiltrowanych i starannie dobranych informacji na określony temat, przekazywanych za pomocą instrumentów wizualnych/graficznych w celu szybkiego przyswojenia. Nie dbają o subtelności inżynierii potoku danych ani estetykę kodu — chcą tylko widoku wysokiego poziomu w 3 sekundy. Dlatego nasza prymitywna aplikacja wyświetlająca dane tekstowe nic dla nich nie znaczy, a najwyższy czas wdrożyć mechanizmy owijania danych wykresami.

Zanim jednak zagłębimy się w implementację wykresów, rozważmy kilka istotnych pytań i możliwych odpowiedzi z naszej perspektywy:

  • Jakie rodzaje wykresów są odpowiednie dla rodzaju danych, z którymi mamy do czynienia?
    Cóż, odpowiedź ma dwa aspekty — kontekst i cel. Przez kontekst rozumiemy typ danych, który ogólnie pasuje do schematu większych rzeczy, ograniczonych zakresem i odbiorcami projektu. I przez cel zasadniczo mamy na myśli „na co chcemy podkreślić?”. Na przykład, możemy przedstawić dzisiejszą temperaturę o różnych porach dnia za pomocą wykresu kolumnowego (pionowe kolumny o równej szerokości, z wysokością proporcjonalną do wartości, jaką reprezentuje kolumna). Rzadko jednak interesują nas poszczególne wartości, ale raczej ogólna zmienność i trend w danych. Aby dostosować się do tego celu, w naszym najlepszym interesie jest użycie wykresu liniowego i wkrótce to zrobimy.
  • O czym należy pamiętać przed wyborem biblioteki wykresów?
    Ponieważ realizujemy projekt głównie przy użyciu technologii opartych na JavaScript, nie ma wątpliwości, że każda biblioteka do tworzenia wykresów, którą wybierzemy dla naszego projektu, powinna pochodzić ze świata JavaScript. Mając na uwadze to podstawowe założenie, przed wyzerowaniem jakiejkolwiek konkretnej biblioteki powinniśmy rozważyć następujące kwestie:
    • Wsparcie dla wybranych przez nas frameworków , którym w tym przypadku jest Vue.js. Projekt można rozwijać w innych popularnych frameworkach JavaScript, takich jak React czy Angular — sprawdź obsługę biblioteki wykresów dla swojego ulubionego frameworka. Należy również wziąć pod uwagę obsługę innych popularnych języków programowania, takich jak Python, Java, C++, .Net (AS i VB), zwłaszcza gdy projekt obejmuje poważne rzeczy związane z backendem.
    • Dostępność typów i funkcji wykresów , ponieważ prawie niemożliwe jest wcześniejsze ustalenie, jaki będzie ostateczny kształt i przeznaczenie danych w projekcie (zwłaszcza jeśli wymagania są regulowane przez Twoich klientów w profesjonalnym otoczeniu). W takim przypadku powinieneś obsłużyć całą sieć i wybrać bibliotekę wykresów, która ma najszerszą kolekcję wykresów. Co ważniejsze, aby odróżnić swój projekt od innych, biblioteka powinna mieć wystarczającą ilość funkcji w postaci konfigurowalnych atrybutów wykresu, aby można było dostroić i dostosować większość aspektów wykresów i odpowiedni poziom szczegółowości. Ponadto domyślne konfiguracje wykresów powinny być rozsądne, a dokumentacja biblioteki musi być na najwyższym poziomie, z powodów, które są oczywiste dla profesjonalnych programistów.
    • Należy również wziąć pod uwagę krzywą uczenia się, społeczność wsparcia i równowagę , zwłaszcza jeśli dopiero zaczynasz wizualizację danych. Na jednym końcu spektrum masz zastrzeżone narzędzia, takie jak Tableau i Qlickview, które kosztują bombę, mają płynną krzywą uczenia się, ale także mają wiele ograniczeń w zakresie możliwości dostosowania, integracji i wdrażania. Z drugiej strony jest d3.js — rozległy, darmowy (open source) i konfigurowalny do samego rdzenia, ale musisz zapłacić bardzo stromą krzywą uczenia się, aby móc zrobić coś produktywnego z biblioteką.

To, czego potrzebujesz, to optymalny punkt — odpowiednia równowaga między wydajnością, zasięgiem, możliwościami dostosowywania, krzywą uczenia się i oczywiście kosztami. Zachęcamy do zapoznania się z FusionCharts — najbardziej wszechstronną i gotową do użycia w przedsiębiorstwach biblioteką wykresów JavaScript dla sieci i urządzeń mobilnych, której będziemy używać w tym projekcie do tworzenia wykresów.

Wprowadzenie do FusionCharts

FusionCharts jest używane na całym świecie jako biblioteka wykresów JavaScript przez miliony programistów z setek krajów na całym świecie. Technicznie jest tak załadowany i konfigurowalny, jak to tylko możliwe, z obsługą integracji z prawie każdym popularnym stosem technologicznym używanym w projektach internetowych. Komercyjne korzystanie z FusionCharts wymaga licencji i musisz zapłacić za licencję w zależności od przypadku użycia (jeśli jesteś ciekawy, skontaktuj się z działem sprzedaży). Jednak używamy FusionCharts w tych projektach tylko po to, aby wypróbować kilka rzeczy, a zatem wersję nielicencjonowaną (pochodzi z małym znakiem wodnym na wykresach i kilkoma innymi ograniczeniami). Korzystanie z wersji nielicencjonowanej jest w porządku, gdy testujesz wykresy i używasz ich w swoich niekomercyjnych lub osobistych projektach. Jeśli planujesz komercyjne wdrożenie aplikacji, upewnij się, że masz licencję od FusionCharts.

Ponieważ jest to projekt wykorzystujący Vue.js, będziemy potrzebować dwóch modułów, które należy zainstalować, jeśli nie zrobiono tego wcześniej:

  • Moduł fusioncharts , ponieważ zawiera wszystko, czego potrzebujesz do tworzenia wykresów
  • Moduł vue-fusioncharts , który jest zasadniczo opakowaniem dla fusioncharts, dzięki czemu można go używać w projekcie Vue.js

Jeśli nie zainstalowałeś ich wcześniej (zgodnie z instrukcjami w trzeciej sekcji), zainstaluj je, wykonując następujące polecenie z katalogu głównego projektu:

 npm install fusioncharts vue-fusioncharts --save

Następnie upewnij się, że plik src/main.js projektu ma następujący kod (wspomniany również w sekcji 3):

 import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); Vue.use(VueFusionCharts, FusionCharts); new Vue({ el: '#app', render: h => h(App) })

Być może najbardziej krytyczna linia w powyższym fragmencie jest następująca:

 Vue.use(VueFusionCharts, FusionCharts)

Instruuje Vue, aby używał modułu vue-fusioncharts do zrozumienia wielu rzeczy w projekcie, które najwyraźniej nie są przez nas wyraźnie zdefiniowane, ale są zdefiniowane w samym module. Ponadto tego typu stwierdzenie implikuje globalną deklarację, przez co rozumiemy, że gdziekolwiek Vue napotka coś dziwnego w kodzie naszego projektu (rzeczy, których nie zdefiniowaliśmy wprost na temat korzystania z FusionCharts), przynajmniej raz spojrzy na wykresy vue-fusioncharts i moduły węzłów fusioncharts do ich definicji, zanim wyrzucą błędy. Gdybyśmy używali FusionCharts w izolowanej części naszego projektu (nie używali go w prawie wszystkich plikach składowych), być może lokalna deklaracja miałaby większy sens.

Dzięki temu wszystko jest gotowe do użycia FusionCharts w projekcie. Będziemy korzystać z wielu różnych wykresów, wybór zależy od aspektu danych pogodowych, które chcemy wizualizować. Zobaczymy także wzajemne oddziaływanie wiązania danych, niestandardowych komponentów i obserwatorów w akcji.

Ogólny schemat korzystania z Fusioncharts w plikach .vue

W tej sekcji wyjaśnimy ogólną ideę używania FusionCharts do tworzenia różnych wykresów w plikach .vue . Ale najpierw zobaczmy pseudokod, który schematycznie ilustruje podstawową ideę.

 <template> <div> <fusioncharts :attribute_1="data_object_1" :attribute_2="data_object_2" … … ... > </fusioncharts> </div> </template> <script> export default { props: ["data_prop_received_by_the_component"], components: {}, data() { return { data_object_1: "value_1", data_object_2: "value_2", … … }; }, methods: {}, computed: {}, watch: { data_prop_received_by_the_component: { handler: function() { // some code/logic, mainly data manipulation based }, deep: true } } }; </script> <style> // component specific special CSS code here </style>

Rozumiemy różne części powyższego pseudokodu:

  • W <template> , na najwyższym poziomie <div> (jest to prawie obowiązkowe dla kodu HTML szablonu każdego komponentu), mamy niestandardowy komponent <fusioncharts> . Mamy definicję komponentu zawartą w module vue-fusioncharts Node, który zainstalowaliśmy dla tego projektu. Wewnętrznie vue-fusioncharts opiera się na module fusioncharts , który również został zainstalowany. Zaimportowaliśmy niezbędne moduły i rozwiązaliśmy ich zależności, poinstruowaliśmy Vue, aby używał wrappera globalnie (w całym projekcie) w pliku src/main.js , dzięki czemu nie brakuje definicji dla niestandardowego komponentu <fusioncharts> , którego użyliśmy tutaj. Ponadto komponent niestandardowy ma niestandardowe atrybuty, a każdy z nich jest powiązany z obiektem danych (i z kolei ich wartościami) przez dyrektywę v-bind , dla której skrótem jest symbol dwukropka ( : ). O atrybutach i związanych z nimi obiektach danych dowiemy się bardziej szczegółowo, omawiając niektóre z wykresów używanych w tym projekcie.
  • W <script> najpierw deklarujesz właściwości, które komponent ma otrzymać, a następnie definiujesz obiekty danych, które są powiązane z atrybutami <fusioncharts> . Wartości przypisane do obiektów danych są wartościami, które wciągają atrybuty <fusioncharts> , a wykresy są tworzone na podstawie tych wciągniętych wartości. Poza tym najciekawszą częścią kodu jest obiekt watch { } . Jest to bardzo szczególny obiekt w schemacie rzeczy Vue — zasadniczo instruuje Vue, aby czuwał nad wszelkimi zmianami zachodzącymi w pewnych danych, a następnie podejmował działania w oparciu o to, jak zdefiniowano funkcję handler dla tych danych. Na przykład, chcemy, aby Vue pilnował otrzymanej data_prop_received_by_the_component prop pseudokodzie. prop staje się kluczem w obiekcie watch { } , a wartością klucza jest inny obiekt — metoda obsługi, która opisuje, co należy zrobić po każdej prop właściwości. Dzięki tak eleganckim mechanizmom obsługi zmian aplikacja zachowuje swoją reaktywność. deep: true reprezentuje flagę logiczną, którą można powiązać z obserwatorami, dzięki czemu obserwowany obiekt jest obserwowany dość głęboko, tj. śledzone są nawet zmiany dokonane w zagnieżdżonych poziomach obiektu.
    ( Aby uzyskać więcej informacji na temat obserwatorów, zapoznaj się z oficjalną dokumentacją ).

Teraz, gdy jesteś już wyposażony w zrozumienie ogólnego schematu rzeczy, przyjrzyjmy się konkretnym implementacjom wykresów w plikach składowych .vue . Kod będzie dość oczywisty i powinieneś spróbować zrozumieć, jak konkrety pasują do ogólnego schematu rzeczy opisanych powyżej.

Implementacja wykresów w plikach .vue

Chociaż sama specyfika implementacji różni się w zależności od wykresu, poniższe wyjaśnienie dotyczy wszystkich z nich:

  • <template>
    Jak wyjaśniono wcześniej, niestandardowy komponent <fusioncharts> ma kilka atrybutów, z których każdy jest powiązany z odpowiednim obiektem danych zdefiniowanym w funkcji data() za pomocą dyrektywy v-bind :. Nazwy atrybutów są dość oczywiste, co oznaczają, a ustalenie odpowiednich obiektów danych jest również trywialne.
  • <script>
    W funkcji data() obiekty danych i ich wartości są tym, co sprawia, że ​​wykresy działają, ze względu na powiązanie wykonane przez dyrektywy v-bind ( : ) używane w atrybutach <fusioncharts> . Zanim zagłębimy się w poszczególne obiekty danych, warto wspomnieć o kilku ogólnych cechach:
    • Obiekty danych, których wartości wynoszą 0 lub 1 , mają charakter logiczny, gdzie 0 reprezentuje coś niedostępnego/wyłączone, a 1 reprezentuje stan dostępności/włączony. Należy jednak zachować ostrożność, ponieważ obiekty danych innych niż logiczne mogą mieć również wartości 0 lub 1 , poza innymi możliwymi wartościami — zależy to od kontekstu. Na przykład containerbackgroundopacity z domyślną wartością 0 jest wartością logiczną, podczas gdy lowerLimit z domyślną wartością 0 oznacza po prostu, że liczba zero jest jego wartością dosłowną.
    • Niektóre obiekty danych zajmują się właściwościami CSS, takimi jak margines, dopełnienie, rozmiar czcionki itp. — wartość ma domniemaną jednostkę „px” lub piksel. Podobnie inne obiekty danych mogą mieć niejawne jednostki skojarzone z ich wartościami. Aby uzyskać szczegółowe informacje, zapoznaj się z odpowiednią stroną atrybutów wykresu w FusionCharts Dev Center.
  • W funkcji data() prawdopodobnie najbardziej interesującym i nieoczywistym obiektem jest dataSource. W tym obiekcie zagnieżdżone są trzy główne obiekty:
    • wykres : ten obiekt zawiera wiele atrybutów wykresu związanych z konfiguracją i wyglądem wykresu. Jest to prawie obowiązkowa konstrukcja, którą znajdziesz we wszystkich wykresach, które stworzysz dla tego projektu.
    • colorrange : Ten obiekt jest w pewnym stopniu specyficzny dla rozważanego wykresu i występuje głównie na wykresach, które dotyczą wielu kolorów/odcieni w celu rozgraniczenia różnych podzakresów skali używanej na wykresie.
    • wartość: ten obiekt ponownie występuje na wykresach, które mają konkretną wartość, którą należy wyróżnić w zakresie skali.
  • Obiekt watch { } jest prawdopodobnie najważniejszą rzeczą, która sprawia, że ​​ten wykres i inne wykresy używane w tym projekcie ożywają. Reaktywność wykresów, czyli wykresów aktualizujących się na podstawie nowych wartości wynikających z nowego zapytania użytkownika, jest kontrolowana przez obserwatorów zdefiniowanych w tym obiekcie. Na przykład zdefiniowaliśmy obserwatora dla highlights rekwizytów otrzymywanych przez komponent, a następnie zdefiniowaliśmy funkcję obsługi, która poinstruuje Vue o niezbędnych działaniach, które powinien wykonać, gdy coś zmieni się w obserwowanym obiekcie w całym projekcie. Oznacza to, że za każdym razem, gdy App.vue zwraca nową wartość dla dowolnego obiektu w highlights , informacja spływa aż do tego komponentu, a nowa wartość jest aktualizowana w obiektach danych tego komponentu. Wykres związany z wartościami również jest aktualizowany w wyniku tego mechanizmu.

Powyższe wyjaśnienia są dość szerokimi pociągnięciami, aby pomóc nam rozwinąć intuicyjne zrozumienie szerszego obrazu. Gdy intuicyjnie zrozumiesz pojęcia, zawsze możesz zapoznać się z dokumentacją Vue.js i FusionCharts, gdy coś nie jest dla Ciebie jasne z samego kodu. Ćwiczenie pozostawiamy tobie i od następnego podrozdziału nie będziemy wyjaśniać spraw, które omówiliśmy w tym podrozdziale.

src/components/TempVarChart.vue

Schemat przedstawiający temperaturę godzinową;
(duży podgląd)
 <template> <div class="custom-card header-card card"> <div class="card-body pt-0"> <fusioncharts type="spline" width="100%" height="100%" dataformat="json" dataEmptyMessage="i-https://i.postimg.cc/R0QCk9vV/Rolling-0-9s-99px.gif" dataEmptyMessageImageScale=39 :datasource="tempChartData" > </fusioncharts> </div> </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { tempChartData: { chart: { caption: "Hourly Temperature", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", baseFont: "Roboto", chartTopMargin: "30", showHoverEffect: "1", theme: "fusion", showaxislines: "1", numberSuffix: "°C", anchorBgColor: "#6297d9", paletteColors: "#6297d9", drawCrossLine: "1", plotToolText: "$label<br><hr><b>$dataValue</b>", showAxisLines: "0", showYAxisValues: "0", anchorRadius: "4", divLineAlpha: "0", labelFontSize: "13", labelAlpha: "65", labelFontBold: "0", rotateLabels: "1", slantLabels: "1", canvasPadding: "20" }, data: [], }, }; }, methods: { setChartData: function() { var data = []; for (var i = 0; i < this.tempVar.tempToday.length; i++) { var dataObject = { label: this.tempVar.tempToday[i].hour, value: this.tempVar.tempToday[i].temp }; data.push(dataObject); } this.tempChartData.data = data; }, }, mounted: function() { this.setChartData(); }, watch: { tempVar: { handler: function() { this.setChartData(); }, deep: true }, }, }; </script> <style> </style>

src/components/UVIndex.vue

Ten komponent zawiera niezwykle przydatny wykres — miernik kątowy.

Wskaźnik promieniowania ultrafioletowego
(duży podgląd)

Kod komponentu podano poniżej. Aby uzyskać szczegółowe informacje na temat atrybutów wykresu wskaźnika kątowego, zobacz stronę Centrum deweloperów FusionCharts dla wskaźnika kątowego.

 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" ></fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return { type: "angulargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", datasource: { chart: { caption: "UV Index", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", lowerLimit: "0", upperLimit: "15", lowerLimitDisplay: "1", upperLimitDisplay: "1", showValue: "0", theme: "fusion", baseFont: "Roboto", bgAlpha: "0", canvasbgAlpha: "0", gaugeInnerRadius: "75", gaugeOuterRadius: "110", pivotRadius: "0", pivotFillAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", tickValueDistance: "3", autoAlignTickValues: "1", majorTMAlpha: "20", chartTopMargin: "30", chartBottomMargin: "40" }, colorrange: { color: [ { minvalue: "0", maxvalue: this.highlights.uvIndex.toString(), code: "#7DA9E0" }, { minvalue: this.highlights.uvIndex.toString(), maxvalue: "15", code: "#D8EDFF" } ] }, annotations: { groups: [ { items: [ { id: "val-label", type: "text", text: this.highlights.uvIndex.toString(), fontSize: "20", font: "Source Sans Pro", fontBold: "1", fillcolor: "#212529", x: "$gaugeCenterX", y: "$gaugeCenterY" } ] } ] }, dials: { dial: [ { value: this.highlights.uvIndex.toString(), baseWidth: "0", radius: "0", borderThickness: "0", baseRadius: "0" } ] } } }; }, methods: {}, computed: {}, watch: { highlights: { handler: function() { this.datasource.colorrange.color[0].maxvalue = this.highlights.uvIndex.toString(); this.datasource.colorrange.color[1].minvalue = this.highlights.uvIndex.toString(); this.datasource.annotations.groups[0].items[0].text = this.highlights.uvIndex.toString(); }, deep: true } } }; </script>

src/components/Visibility.vue

W tym komponencie używamy poziomego miernika liniowego do reprezentowania widoczności, jak pokazano na poniższym obrazku:

Zrzut ekranu poziomego miernika liniowego przedstawiającego widoczność z powietrza (16 km)
(duży podgląd)

Kod komponentu podano poniżej. Aby uzyskać dogłębne zrozumienie różnych atrybutów tego typu wykresu, zapoznaj się ze stroną FusionCharts Dev Center dla poziomego miernika liniowego.

 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-left border-right border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" > </fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, methods: {}, computed: {}, data() { return { type: "hlineargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", creditLabel: false, datasource: { chart: { caption: "Air Visibility", captionFontBold: "0", captionFontColor: "#000000", baseFont: "Roboto", numberSuffix: " km", lowerLimit: "0", upperLimit: "40", showPointerShadow: "1", animation: "1", transposeAnimation: "1", theme: "fusion", bgAlpha: "0", canvasBgAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", pointerBorderAlpha: "0", chartBottomMargin: "40", captionPadding: "30", chartTopMargin: "30" }, colorRange: { color: [ { minValue: "0", maxValue: "4", label: "Fog", code: "#6297d9" }, { minValue: "4", maxValue: "10", label: "Haze", code: "#7DA9E0" }, { minValue: "10", maxValue: "40", label: "Clear", code: "#D8EDFF" } ] }, pointers: { pointer: [ { value: this.highlights.visibility.toString() } ] } } }; }, watch: { highlights: { handler: function() { this.datasource.pointers.pointer[0].value = this.highlights.visibility.toString(); }, deep: true } } }; </script>

src/components/WindStatus.vue

Ten składnik wyświetla prędkość i kierunek wiatru (prędkość wiatru, jeśli znasz się na fizyce) i bardzo trudno jest przedstawić wektor za pomocą wykresu. W takich przypadkach sugerujemy przedstawienie ich za pomocą ładnych obrazów i wartości tekstowych. Ponieważ reprezentacja, o której myśleliśmy, jest całkowicie zależna od CSS, zaimplementujemy ją w następnej sekcji, która dotyczy CSS. Jednak spójrz na to, co zamierzamy stworzyć:

Status wiatru: kierunek wiatru (po lewej) i prędkość wiatru (po prawej)
(duży podgląd)
 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <div class="card-heading pt-5">Wind Status</div> <div class="row pt-4 mt-4"> <div class="col-sm-6 col-md-6 mt-2 text-center align-middle"> <p class="card-sub-heading mt-3">Wind Direction</p> <p class="mt-4"><img src="../assets/winddirection.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.derivedWindDirection }}</p> </div> <div class="col-sm-6 col-md-6 mt-2"> <p class="card-sub-heading mt-3">Wind Speed</p> <p class="mt-4"><img src="../assets/windspeed.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.windSpeed }} km/h</p> </div> </div> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return {}; }, methods: {}, computed: {} }; </script>

Podsumowanie za pomocą Highlights.vue

Przypomnijmy, że zaimplementowaliśmy już kod z CSS dla wszystkich komponentów — z wyjątkiem Content.vue i Highlights.vue . Ponieważ Content.vue jest głupim komponentem, który tylko przekazuje dane, minimalna stylizacja, której potrzebuje, została już omówiona. Ponadto napisaliśmy już odpowiedni kod do stylizacji paska bocznego i kart zawierających wykresy. Dlatego pozostaje nam tylko dodać kilka stylistycznych bitów do Highlights.vue , co wiąże się przede wszystkim z użyciem klas CSS:

 <template> <div class="custom-content-card content-card card"> <div class="card-body pb-0"> <div class="content-header h4 text-center pt-2 pb-3">Highlights</div> <div class="row"> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </div> </div> </template> <script> import UVIndex from "./UVIndex.vue"; import Visibility from "./Visibility.vue"; import WindStatus from "./WindStatus.vue"; export default { props: ["highlights"], components: { "uv-index": UVIndex, "visibility": Visibility, "wind-status": WindStatus, }, }; </script>

Wdrożenie i kod źródłowy

Mając uporządkowane wykresy i styl, gotowe! Poświęć chwilę, aby docenić piękno swojego dzieła.

Wynik
(duży podgląd)

Teraz nadszedł czas na wdrożenie aplikacji i udostępnienie jej innym użytkownikom. Jeśli nie masz pojęcia o wdrożeniu i oczekujesz od nas pomocy, zapoznaj się z naszym podejściem do pomysłów wdrożeniowych. Połączony artykuł zawiera również sugestie, jak usunąć znak wodny FusionCharts w lewym dolnym rogu każdego wykresu.

Jeśli gdzieś zepsujesz i potrzebujesz punktu odniesienia, kod źródłowy jest dostępny na Github.