Простое фото приложение на Vue.js, Axios и Flickr API — Часть 3
Ранее мы реорганизовали наше приложение, чтобы оно было более гибким и DRY. В этой статье мы добавим 3 новых компонента на домашнюю страницу, которая в настоящее время пуста:
- Компонент HeroSection
- Компонент RecentPhotos для отображения последних загруженных изображений с Flickr
- Компонент PlacePhotos, позволяющий пользователям просматривать фотографии из списка мест.
Шаг 1: Компонент HeroSection
Начнем с компонента HeroSection.vue. Создадим файл с таким именем в каталоге компонентов. В нем будет следующее:
- Шаблон будет состоять из одного элемента section, к которому будет применен backgroundImage. Внутри него будут title и subtitle. Эти элементы будут Strings, которые компонент получает в качестве props , чтобы мы могли теоретически снова использовать этот компонент на другой странице с другими images / titles / subtitles.
- BackgroundImage будет установлен через атрибут :style. Обычные фоновые изображения CSS создаются с этим синтаксисом:
background-image: url('relative-or-external-path.jpg');
- Но поскольку мы делаем это в шаблоне, мы должны использовать camel case стиль для имен стилей CSS (backgroundImage, а не background-image), и мы используем метод heroImagePath() для построения необходимого пути. heroImagePath() возвращает объединенный относительный путь к файлу, который мы будем хранить в каталоге assets нашего проекта. Поскольку мы используем Webpack для компиляции файлов, мы должны использовать метод require(), чтобы помочь Webpack найти требуемое изображение.
- Так же мы добавили адаптивные стили CSS в конце файла.
<template> <section class="hero" :style="{backgroundImage: heroImagePath()}"> <h1 class="hero-title">{{ title }}</h1> <p class="hero-subtitle">{{ subtitle }}</p> </section> </template> <script> export default { name: 'heroSection', props: { backgroundImage: String, title: String, subtitle: String, }, methods: { heroImagePath() { return 'url(' + require(`../assets/${this.backgroundImage}`) + ')'; } } } </script> <style lang="scss"> .hero { background-size: cover; background-repeat: no-repeat; background-position: center center; color: white; text-align: center; display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 600px; @media only screen and (min-width: 600px) and (max-width: 900px) { min-height: 400px; } @media only screen and (max-width: 599px) { min-height: 250px; } } .hero-title { font-size: 3rem; margin: 0; } .hero-subtitle { font-size: 1.3rem; } </style>
Теперь посетите Unsplash или Pixabay, и загрузите от туда бесплатную фотографию на ваш выбор в качестве фонового изображения. Сохраните его в assets как homepage-hero.jpg.
Затем перейдите в Home.vue и обновите его следующим образом:
<template> <div> <hero-section backgroundImage="homepage-hero.jpg" title="Welcome to Instaflickr" subtitle="A fake photo app"/> <div class="wrapper"> </div> </div> </template> <script> import HeroSection from '@/components/HeroSection'; export default { name: 'home', components: { HeroSection, } }; </script>
В нем мы импортируем и регистрируем компонент HeroSection. Затем мы добавляем его в шаблон, передавая необходимые реквизиты.
Шаг 2: Компонент RecentPhotos
Теперь давайте создадим раздел на главной странице, чтобы показать три самые последние фотографии, которые были загружены на Flickr. Мы будем использовать метод flickr.photos.getRecent, который потребует наш api_key и так де принимает много дополнительных параметров, аналогичных методу flickr.photos.search, таких как extras, per_page и page. Он отлично подойдет к нашему вспомогательному методу flickr.js, который мы сделали в части 2. А так же мы воспользуемся нашим компонентом ImageCard для отображения получаемых изображений.
Создайте файл с именем RecentPhotos.vue внутри папки компонентов. В нем мы сделаем следующее:
- Шаблон будет состоять из title и ul из компонентов ImageCard (импортированных и зарегистрированных). Цикл v-for будет перебирать вычисляемое свойство mostRecentPhotos.
- mostRecentPhotos будет возвращать первые три изображения в массиве с именем RecentPhotos, который будет хранится в data().
- RecentPhotos инициализируется пустым массивом, который заполняется изображениями во время хука create().
- В create() мы вызываем метод fetchRecentPhotos(), который использует наш вспомогательный метод flickr() для вызова API.
- flickr() получает два параметра: метод, который мы хотим вызвать (photos.getRecent), и дополнительные параметры, которые ему нужны (extras, page и per_page). Если запрос метода успешно разрешен, мы обновляем latestPhotos в методе then(). Если есть ошибка, мы отобразим ее в консоли в методе catch().
Это будет наш стандартный подход/процесс для всех остальных вызовов API в этом проекте.
<template> <section class="content-section margin-y--6"> <h2 class="centered">Browse the Latest Uploads</h2> <ul class="image-card-grid"> <image-card v-for="image in mostRecentPhotos" :key="image.id" :image="image" /> </ul> </section> </template> <script> import flickr from '../flickr' import ImageCard from './ImageCard.vue' export default { name: 'recentPhotos', components: { ImageCard }, created() { this.fetchRecentPhotos() }, data() { return { recentPhotos: [], } }, computed: { mostRecentPhotos() { return this.recentPhotos.slice(0, 3) }, }, methods: { fetchRecentPhotos() { return flickr("photos.getRecent", { extras: "url_n, owner_name, description, date_taken, views", page: 1, per_page: 3, }) .then((response) => { this.recentPhotos = response.data.photos.photo }) .catch((error) => { console.log("Error occured: ", error) }) }, } } </script> <style> .centered { text-align: center; } .margin-y--6 { margin-top: 6rem; margin-bottom: 6rem; } </style>
Затем добавьте новый компонент в компонент Home.vue, импортировав и зарегистрировав его и, затем добавим тег в шаблон.
<template> <div> <hero-section backgroundImage="homepage-hero.jpg" title="Welcome to Instaflickr" subtitle="A fake photo app"/> <div class="wrapper"> <recent-photos /> </div> </div> </template> <script> import HeroSection from '@/components/HeroSection'; import RecentPhotos from '@/components/RecentPhotos'; export default { name: 'home', components: { HeroSection, RecentPhotos } }; </script>
Шаг 3: Компонент PlacePhotos
Другой популярный способ поиска фотографий — по названию города или места. Давайте создадим интерактивный компонент, который позволяет пользователям просматривать фотографии из ограниченного списка «популярных» мест. Мы произвольно укажем места какие мы хотим видеть.
Создайте файл с именем PlacePhotos.vue внутри папки компонентов. Наш шаблон будет состоять из заголовка и список кнопок с названием мест. Каждая кнопка будет обновляет список из 3 фотографий с выбранного места; вторая строка — это loading который будет появляться во время выборки изображений. Внизу будет ссылка, по которой пользователь может щелкнуть, чтобы просмотреть другие фотографии этого места на странице SearchResults.
Разберем это еще раз и начнем с первой части, списка мест ul:
- Цикл li проходит через PopularPlaces, массив в data(), который состоит из объектов place. Каждый объект имеет свойства name и place_id. Place_id происходит из метода flickr.places.find. Не стесняйтесь заменять эти имена и идентификаторами по вашему выбору!
- Когда пользователь нажимает кнопку «place», вызывается метод updateSelectedPlaceIndex(index). updateSelectedPlaceIndex принимает один аргумент — индекс объекта place в PopularPlaces и обновляет свойство данных selectedPlaceIndex, чтобы сохранить новое значение. Как мы увидим через минуту, это является частью того, что заставляет фотографии обновляться.
Теперь давайте посмотрим на вторую часть, image-card-grid u:
- Оно состоит из списка ImageCards, изображений которые получены из popularPlacePhotos , вычисляемого свойства, которое возвращает первые три изображения из placePhotos.
- placePhotos аналогичен последним фотографиям из предыдущего компонента. Он инициализируется как пустой массив, а затем заполняется данными во время хука жизненного цикла create(), на этот раз с помощью метода fetchPlacePhotos().
- fetchPlacePhotos() делает то же самое, что и fetchRecentPhotos(), только с другим методом API, photos.search.
- Основное отличие в том что мы ищем по place_id, а не по тегу на этот раз. Мы знаем, какой place_id нужно передать, путем индексации в PopularPlaces, используя текущее значение selectedPlaceIndex, а затем передавая place_id этого места. Но вместо того, чтобы писать:
place_id: this.popularPlaces[this.selectedPlaceIndex].place_id
мы перемещаем первую часть в ее собственное вычисляемое свойство с именем selectedPlace , что позволяет нам написать так:
place_id: this.selectedPlace.place_id
Что гораздо более читабельно.
Наконец, давайте посмотрим на третью часть шаблона:
- Он состоит из выровненного по центру тега <p>, который оборачивает элемент router-link (он же тег <a>).
- Далее указывается имя маршрута name в searchResults и передает имя места в качестве значения для нашего поискового параметра name, тега tag.
<template> <section class="content-section margin-y--6"> <h2 class="centered">Daydream by Place</h2> <ul class="place-list"> <li v-for="(place, index) in popularPlaces" :key="place.place_id" class="place-list__item"> <button @click="updateSelectedPlaceIndex(index)" class="place-list__item__button" :class="{'selected': index === selectedPlaceIndex}"> {{place.name}} </button> </li> </ul> <ul v-if="!loading" class="image-card-grid"> <image-card v-for="image in popularPlacePhotos" :key="image.id" :image="image" /> </ul> <ul v-else class="image-card-grid"> <image-card v-for="n in 3" :key="n" :loading="true" /> </ul> <p class="centered"> <router-link :to="{name: 'searchResults', params: { tag: selectedPlace.name }}" class="btn btn--dark-grey more-photos"> More Photos of {{selectedPlace.name}} </router-link> </p> </section> </template> <script> import flickr from '../flickr' import ImageCard from './ImageCard.vue' export default { name: 'daydreamByPlace', components: { ImageCard }, created() { this.fetchPlacePhotos() }, watch: { selectedPlace() { this.fetchPlacePhotos() } }, data() { return { loading: true, placePhotos: [], selectedPlaceIndex: 0, popularPlaces: [ { name: 'Agra', place_id: 'S4OOvxtTULO18fQG' }, { name: 'Bali', place_id: 'lm4_wrhTUb4oe5pO' }, { name: 'Rio de Janeiro', place_id: 'mAqmHW5VV78OT5o' }, { name: 'Paris', place_id: 'EsIQUYZXU79_kEA' }, { name: 'Tokyo', place_id: 'FRthiQZQU7uKHvmP' }, { name: 'Tolanaro', place_id: 'qdHKy7VQUbxPQVBX' } ] } }, computed: { selectedPlace() { return this.popularPlaces[this.selectedPlaceIndex] }, popularPlacePhotos() { return this.placePhotos.slice(0, 3) }, }, methods: { fetchPlacePhotos() { this.loading = true flickr("photos.search", { place_id: this.selectedPlace.place_id, extras: "url_n, owner_name, description, date_taken, views", page: 1, per_page: 3, }).then((response) => { this.placePhotos = response.data.photos.photo this.loading = false }) .catch((error) => { console.log("Error occured: ", error) }) }, updateSelectedPlaceIndex(index) { this.selectedPlaceIndex = index; }, } } </script> <style lang="scss"> .btn--dark-grey { background: #2c3e50; color: white; text-decoration: none; } .place-list { list-style: none; padding: 0; margin: 0; width: 100%; display: flex; align-items: center; justify-content: center; flex-wrap: wrap; } .place-list__item { margin: 0 .5rem; } .place-list__item__button { background: transparent; font-size: 1rem; border: 0; outline: 0; cursor: pointer; border-radius: 3px; padding: .5rem .75rem; &.selected { background: #F0F0F0; } transition: all .3s ease; &:hover { color: #42b983; } } </style>
Единственными оставшимися шагами теперь являются импорт и регистрация PlacePhotos.vue в Home.vue и добавление его в шаблон:
<template> <div> <hero-section backgroundImage="homepage-hero.jpg" title="Welcome to Instaflickr" subtitle="A fake photo app"/> <div class="wrapper"> <recent-photos /> <place-photos /> </div> </div> </template> <script> import HeroSection from '@/components/HeroSection'; import RecentPhotos from '@/components/RecentPhotos'; import PlacePhotos from '@/components/PlacePhotos'; export default { name: 'home', components: { HeroSection, RecentPhotos, PlacePhotos } }; </script>
Заключение
И вот наконец мы все сделали! Три новых компонента и еще два места, где мы используем Flickr API. Спасибо за чтение и до встречи!
Оригинал: Simple Photo App with Vue.js, Axios, and Flickr API — Part 3