Простое руководство с примерами

Vue.js - популярный интерфейсный фреймворк для создания одностраничных приложений. Он обеспечивает структуру и абстракцию. Приложения структурированы путем разделения частей приложений на компоненты. Связывание данных также является важным преимуществом использования Vue.js. У каждого компонента своя логика, шаблон для отображения вещей и стиль. Он также обеспечивает дополнительную маршрутизацию и хранилище потоков. Bootstrap - популярный фреймворк, который предоставляет набор стилизованных виджетов, которые хорошо выглядят, сокращая время на разработку. Vue.js делает создание интерфейсных приложений очень простым и приятным.

Bootstrap создан Twitter для стилизации собственных элементов пользовательского интерфейса. Он предоставляет виджеты в HTML, CSS и простом JavaScript. Разработчики создали надстройку Bootstrap для Vue.js, которая обеспечивает все те же преимущества Bootstrap в сочетании с преимуществами Vue.js, такими как привязка данных. Версия Bootstrap для Vue.js, называемая Vue-Bootstrap, поставляется в виде набора компонентов.

Чтобы использовать BootstrapVue, мы импортируем виджеты в наше приложение Vue и ссылаемся на компоненты BootstrapVue в наших шаблонах. Полный список компонентов доступен, а также есть несколько директив для модификации существующих элементов.

В этой истории мы собираемся создать приложение, использующее Meal API.

Создание приложения

Создать Vue.js с помощью Bootstrap очень просто. Для начала нам понадобится Vue CLI. Устанавливаем, запустив npm install -g @vue/cli. Нам нужно инициализировать проект. Запустите vue create meal-app. После выполнения команды вы можете перейти в папку meal-app и начать писать приложение.

Теперь мы можем приступить к созданию приложения. Запускаем vue serve, чтобы запустить сервер разработки. Он будет обновляться каждый раз, когда мы меняем код в папке нашего проекта.

Нам нужно установить несколько зависимостей, таких как BootstrapVue и HTTP-клиент, чтобы мы могли отправлять и получать данные с приложением. Для этого запускаем:

npm i bootstrap bootstrap-vue superagent vee-validate vue-router

Нам нужны эти библиотеки, потому что мы используем Bootstrap для создания нашего приложения. bootstrap bootstrap-vue восполняет эту потребность. superagent - это выбранный нами HTTP-клиент. vee-validate - это библиотека для проверки форм Vue.js. vue-router используется для маршрутизации URL-адресов, которые пользователь вводит в наши компоненты, чтобы наши компоненты были видны.

Теперь у нас есть все необходимое для написания кода. Начнем с App.vue, где добавим:

<template>
  <div id="app">
    <nav-bar></nav-bar>
    <div id='router-view'>
      <router-view/>
    </div>
  </div>
</template>
<script>
export default {
  name: "app"
};
</script>
<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
#router-view {
  padding: 20px 0px;
  margin: 0 auto;
}
</style>

Это основная составляющая нашей страницы. Он отображает материал, к которому маршрутизатор направляет в <router-view/>, а также нашу панель навигации, которую мы создадим.

Далее в main.js мы помещаем:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
router.beforeEach((to, from, next) => {
  document.title = to.meta.title;
  next()
})
let APIURL = 'http://mealapi.jauyeung.net/index.php/';
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})
export { APIURL };

Мы помещаем заголовок нашей страницы в этот блок:

router.beforeEach((to, from, next) => {
  document.title = to.meta.title;
  next()
})

Нам нужно вызвать next(), чтобы загрузился следующий маршрут.

Маршрутизатор Vue отслеживает изменения в URL-адресе и соответственно устанавливает заголовок.

Блок ниже - это запись для нашего приложения.

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

Мы помещаем наше приложение в элемент с ID app. Теперь добавляем некоторые компоненты в папку components. Если его там нет, создайте папку.

Затем в папке создаем следующие файлы:

Filter.vue
FilterRow.vue
Latest.vue
MealRow.vue
NavBar.vue
Random.vue
Search.vue

В роутере мы создаем index.js и вводим:

import Vue from 'vue'
import Router from 'vue-router'
import Latest from '@/components/Latest'
import Random from '@/components/Random'
import Search from '@/components/Search'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import VeeValidate from 'vee-validate';
import MealRow from '../components/MealRow'
import NavBar from '../components/NavBar'
import Filter from '../components/Filter'
import FilterRow from '../components/FilterRow'
Vue.component('meal-row', MealRow);
Vue.component('nav-bar', NavBar);
Vue.component('filter-row', FilterRow);
Vue.use(Router);
Vue.use(BootstrapVue);
Vue.use(VeeValidate);
export default new Router({
  routes: [
    {
      path: '/',
      name: 'latest',
      component: Latest,
      meta: { title: 'Home' }
    },
    {
      path: '/random',
      name: 'random',
      component: Random,
      meta: { title: 'Random' }
    },
    {
      path: '/search',
      name: 'search',
      component: Search,
      meta: { title: 'Search' }
    },
    {
      path: '/search/:keyword',
      name: 'search-keyword',
      component: Search,
      meta: { title: 'Search' }
    },
    {
      path: '/search/:type/:keyword',
      name: 'filter-keyword',
      component: Filter,
      meta: { title: 'Filter' }
    }
  ]
})

Это определяет все наши маршруты и надстройки Vue, которые мы используем с Vue.use. В следующем блоке мы регистрируем компоненты, которые мы вкладываем в наши шаблоны:

Vue.component('meal-row', MealRow);
Vue.component('nav-bar', NavBar);
Vue.component('filter-row', FilterRow);

У нас будет страница поиска, страница для отображения случайных рецептов и страница для отображения последнего рецепта из Meal API.

В Filter.vue мы вводим:

<template>
  <div class="container">
    <div class='row'>
      <div class="col-12">
        <h1>
          <span v-if="$route.params.type == 'category'">Category - </span>
          <span v-if="$route.params.type == 'area'">Area - </span>
          {{$route.params.keyword}}
        </h1>
        <div v-for="meal in meals" v-bind:key='meal.idMeal'>
          <filter-row :meal='meal'></filter-row>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
import MealRow from "./MealRow";
export default {
  name: "search",
  data() {
    return {
      keyword: "",
      meals: []
    };
  },
  methods: {
    searchByParam(type, keyword) {
      request.get(`${APIURL}filter/${type}/${keyword}`).end((err, res) => {
        this.meals = res.body.meals;
        if (!res.body.meals){
          return;
        }
        this.meals = this.meals.map(m => {
          m.ingredients = [];
          m.measures = [];
          for (let key in m) {
            if (key.includes("strIngredient")) {
              let index = +key.replace("strIngredient", "");
              m.ingredients[index] = m[key];
            }
if (key.includes("strMeasure")) {
              let index = +key.replace("strMeasure", "");
              m.measures[index] = m[key];
            }
          }
          m.ingredients = m.ingredients.filter(i => {
            return i;
          });
m.measures = m.measures.filter(m => {
            return m;
          });
          return m;
        });
      });
    }
  },
  beforeMount() {
    if (this.$route.params.keyword) {
      this.searchByParam(this.$route.params.type, this.$route.params.keyword);
    }
  },
  beforeRouteUpdate(to, from, next) {
    this.searchByParam(to.params.type, to.params.keyword);
    next();
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Отсюда мы получаем результаты поиска из API. Мы инициируем запрос к Meals API и возвращаем результаты. Результаты возвращаются в шаблоны с помощью функции привязки данных Vue и отображаются путем перебора результатов с помощью директивы v-for. Директивы - это части кода Vue.js, которые изменяют существующий элемент.

Например, когда вы переходите к /#/search/chicken, ключевое слово chicken принимается this.$route.params.keyword переменной и запускается функция searchByParam для выполнения поиска.

FilterRow.vue - дочерний компонент Filter.vue. К нему добавляем следующее:

<template>
  <div class="row">
    <div class="col">
      <h1>{{meal.strMeal}}</h1>
      <img :src="meal.strMealThumb" @click="showModal" class="meal-thumb">
      <b-modal ref="mealModal" hide-footer :title="meal.strMeal">
        <div class="row">
          <div class="col">
            <h1>{{meal.strMeal}}</h1>
            <p>
              <b>Area:</b> {{meal.strArea}}</p>
            <p>
              <b>Category:</b> {{meal.strCategory}}</p>
            <img :src="meal.strMealThumb" class="meal-photo">
            <table class="table">
              <thead>
                <tr>
                  <th>Ingredient</th>
                  <th>Measure</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="(ingredient, index) in meal.ingredients" v-bind:key="index">
                  <td>{{meal.ingredients[index]}}</td>
                  <td>{{meal.measures[index]}}</td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
        <b-btn class="mt-3" variant="outline-danger" block @click="hideModal">Close</b-btn>
      </b-modal>
    </div>
  </div>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
export default {
  name: "meal-row",
  props: ["meal"],
  data() {
    return {};
  },
  methods: {
    showModal() {
      this.$refs.mealModal.show();
      this.getMeal(this.meal.idMeal);
    },
    hideModal() {
      this.$refs.mealModal.hide();
    },
    getMeal(id) {
      request.get(`${APIURL}lookup/${id}`).end((err, res) => {
        this.meal = res.body.meals[0];
        this.meal.ingredients = [];
        this.meal.measures = [];
        for (let key in this.meal) {
          if (key.includes("strIngredient")) {
            let index = +key.replace("strIngredient", "");
            this.meal.ingredients[index] = this.meal[key];
          }
if (key.includes("strMeasure")) {
            let index = +key.replace("strMeasure", "");
            this.meal.measures[index] = this.meal[key];
          }
        }
        this.meal.ingredients = this.meal.ingredients.filter(i => {
          return i;
        });
this.meal.measures = this.meal.measures.filter(m => {
          return m;
        });
      });
    }
  },
  computed: {
    computedClass: function() {
      if (this.meal != null) {
        return this.meal;
      }
return {};
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.meal-photo {
  width: 100%;
}
.meal-thumb {
  cursor: pointer;
  width: 100%;
}
</style>

Свойство meal - это то место, где мы передаем объект для отображения FilterRow компонента. b-modal - это модальное окно BootstrapVue, которое мы используем для отображения всплывающего окна с данными рецепта. Мы можем скрыть модальное окно, вызвав this.$refs.mealModal.hide(). request.get из библиотеки superagent. Он возвращает обещание, где мы можем связать then функцию после нее, чтобы запустить дополнительный код после выполнения обещания.

В Latest.vue мы помещаем:

<template>
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>Today's Meal: {{meal.strMeal}}</h1>
        <p>Area: {{meal.strArea}}</p>
        <p>Category: {{meal.strCategory}}</p>
        <img :src="meal.strMealThumb" class="meal-photo">
        <table class="table">
          <thead>
            <tr>
              <th>Ingredient</th>
              <th>Measure</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(ingredient, index) in meal.ingredients" v-bind:key="index">
              <td>{{meal.ingredients[index]}}</td>
              <td>{{meal.measures[index]}}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
export default {
  name: "latest",
  data() {
    return {
      meal: {}
    };
  },
  methods: {},
  beforeMount: function() {
    request.get(`${APIURL}latest`).end((err, res) => {
      this.meal = res.body.meals[0];
      this.meal.ingredients = [];
      this.meal.measures = [];
      for (let key in this.meal) {
        if (key.includes("strIngredient")) {
          let index = +key.replace("strIngredient", "");
          this.meal.ingredients[index] = this.meal[key];
        }
      if (key.includes("strMeasure")) {
          let index = +key.replace("strMeasure", "");
          this.meal.measures[index] = this.meal[key];
        }
      }
      this.meal.ingredients = this.meal.ingredients.filter(i => {
        return i;
      });
this.meal.measures = this.meal.measures.filter(m => {
        return m;
      });
    });
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.meal-photo {
  width: 100%;
}
</style>

Эта страница получает последнюю еду и отображает ее пользователю на домашней странице нашего приложения, поскольку у нас есть следующие строки inindex.js.

{
  path: '/',
  name: 'latest',
  component: Latest,
  meta: { title: 'Home' }
},

Поле title - это заголовок страницы. Он будет в поле to.meta.title объекта to:

router.beforeEach((to, from, next) => {
  document.title = to.meta.title;
  next()
})

В MealRow.vue мы помещаем:

<template>
  <div class="row">
    <div class="col">
      <h1>{{meal.strMeal}}</h1>
      <p>
        <b>Area:</b> {{meal.strArea}}</p>
      <p>
        <b>Category:</b> {{meal.strCategory}}</p>
      <table class="table">
        <thead>
          <tr>
            <th>Ingredient</th>
            <th>Measure</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(ingredient, index) in meal.ingredients" v-bind:key="index">
            <td>{{meal.ingredients[index]}}</td>
            <td>{{meal.measures[index]}}</td>
          </tr>
        </tbody>
      </table>
      <img :src="meal.strMealThumb">
    </div>
  </div>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
export default {
  name: "meal-row",
  props: ["meal"],
  data() {
    return {};
  },
  methods: {},
  computed: {
    computedClass: function() {
      if (this.meal != null) {
        return this.meal;
      }
return {};
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
img {
    max-width: 100%;
}
</style>

Он отображает ингредиенты для рецептов.

NavBar.vue содержит панель навигации нашего приложения. Ставим:

<template>
  <b-navbar toggleable="md" type="dark" variant="info">
    <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
    <b-navbar-brand>
      <img :src="require('../assets/logo-small.png')" id='logo'> Meal App
    </b-navbar-brand>
    <b-collapse is-nav id="nav_collapse">
      <b-navbar-nav>
        <b-nav-item :to="{ name: 'latest'}" :active="$route.name == 'latest'">Home</b-nav-item>
        <b-nav-item :to="{ name: 'random'}" :active="$route.name.includes('random')">Random</b-nav-item>
        <b-nav-item :to="{ name: 'search'}" :active="$route.name.includes('search')">Search</b-nav-item>
        <b-nav-item-dropdown text="Categories" right>
          <b-dropdown-item v-for="(cat, index) in categories" :active="$route.path.includes(`search/category/${cat.strCategory}`)" :key='index' :to="`/search/category/${cat.strCategory}`">
            {{cat.strCategory}}
          </b-dropdown-item>
        </b-nav-item-dropdown>
        <b-nav-item-dropdown text="Areas" right>
          <b-dropdown-item v-for="(area, index) in areas" :active="$route.path.includes(`search/area/${area.strArea}`)" :key='index' :to="`/search/area/${area.strArea}`">
            {{area.strArea}}
          </b-dropdown-item>
        </b-nav-item-dropdown>
     </b-navbar-nav>
     <!-- Right aligned nav items -->
     <b-navbar-nav class="ml-auto">
        <b-nav-form @submit="search">
          <b-form-input size="sm" class="mr-sm-2" type="text" placeholder="Search" name='keyword' v-model="keyword" v-validate="'required'" />
          <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
        </b-nav-form>
      </b-navbar-nav>
    </b-collapse>
  </b-navbar>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
export default {
  name: "nav-bar",
  data() {
    return {
      keyword: "",
      categories: [],
      areas: [],
      ingredients: []
    };
  },
  methods: {
    search(evt) {
      evt.preventDefault();
      if (this.errors.any()){
        return;
      }
      this.$router.push({
        name: 'search-keyword',
        params: {
          keyword: this.keyword
        }
      });      
    }
  },
  beforeMount() {
    request.get(`${APIURL}categories`).end((err, res) => {
      this.categories = res.body.meals;
    });
request.get(`${APIURL}area`).end((err, res) => {
      this.areas = res.body.meals;
    });
request.get(`${APIURL}ingredients`).end((err, res) => {
      this.ingredients = res.body.meals;
    });
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#logo {
  width: 100px;
  margin-top: -3px;
}
</style>

Мы отображаем раскрывающийся список «Категории» после получения категорий из API MealDB и делаем то же самое с меню «Области».

Обратите внимание, что у нас есть:

<b-navbar toggleable="md" type="dark" variant="info">
  <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
  <b-navbar-brand>
      <img :src="require('../assets/logo-small.png')" id='logo'> Meal App
  </b-navbar-brand>
  <b-collapse is-nav id="nav_collapse">
     <b-navbar-nav>
        <b-nav-item :to="{ name: 'latest'}" :active="$route.name == 'latest'">Home</b-nav-item>
        <b-nav-item :to="{ name: 'random'}" :active="$route.name.includes('random')">Random</b-nav-item>
        <b-nav-item :to="{ name: 'search'}" :active="$route.name.includes('search')">Search</b-nav-item>
        <b-nav-item-dropdown text="Categories" right>
          <b-dropdown-item v-for="(cat, index) in categories" :active="$route.path.includes(`search/category/${cat.strCategory}`)" :key='index' :to="`/search/category/${cat.strCategory}`">
            {{cat.strCategory}}
          </b-dropdown-item>
        </b-nav-item-dropdown>
        <b-nav-item-dropdown text="Areas" right>
          <b-dropdown-item v-for="(area, index) in areas" :active="$route.path.includes(`search/area/${area.strArea}`)" :key='index' :to="`/search/area/${area.strArea}`">
            {{area.strArea}}
          </b-dropdown-item>
        </b-nav-item-dropdown>
    </b-navbar-nav>
<!-- Right aligned nav items -->
     <b-navbar-nav class="ml-auto">
        <b-nav-form @submit="search">
          <b-form-input size="sm" class="mr-sm-2" type="text" placeholder="Search" name='keyword' v-model="keyword" v-validate="'required'" />
          <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
        </b-nav-form>
      </b-navbar-nav>
  </b-collapse>
</b-navbar>

Это определяет нашу левую и правую навигацию. BootstrapVue упрощает определение панели навигации. В левой части есть меню, а в правой части - поле поиска, которое будет перенаправлять на компонент Filter.vue, когда мы отправим ключевое слово для поиска.

Обратите внимание, что мы используем form-input для входных данных. Таким образом, мы получаем выгоду от двусторонней привязки данных, которую предоставляет Vue.js. v-model - это место, где мы выполняем привязку данных. v-validate предоставляется пакет vee-validate Vue. Это означает, что поле формы является обязательным. Он также добавляет объект this.errors к нашему компоненту с блоком ниже:

if (this.errors.any()){
  return;
}

Мы также добавляем evt.preventDefault();, чтобы предотвратить стандартное поведение браузера при отправке, которое заключается в отправке запроса на некоторый сервер и обновлении страницы. Вместо этого это позволяет остальной части функции обработчика отправки продолжать работу и позволяет нам сделать запрос к MealDB API, чтобы получить нужные данные.

Это предотвратит отправку формы с недопустимыми данными.

Следующий блок выполняет перенаправление с ключевым словом в качестве параметра запроса.

this.$router.push({
  name: 'search-keyword',
  params: {
    keyword: this.keyword
  }
});

В Random.vue мы вводим:

<template>
  <div class="container">
    <h1>Random Recipes</h1>
    <div class='row'>
      <div class="col-12">
        <div v-for="meal in meals" :key='meal.idMeal'>
          <meal-row :meal='meal'></meal-row>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
export default {
  name: "random-recipe",
  data() {
    return {
      meals: []
    };
  },
  methods: {
    search() {
      request.get(`${APIURL}random`).end((err, res) => {
        this.meals = res.body.meals;
        this.meals = this.meals.map(m => {
          m.ingredients = [];
          m.measures = [];
          for (let key in m) {
            if (key.includes("strIngredient")) {
              let index = +key.replace("strIngredient", "");
              m.ingredients[index] = m[key];
            }
            if (key.includes("strMeasure")) {
              let index = +key.replace("strMeasure", "");
              m.measures[index] = m[key];
            }
          }
          m.ingredients = m.ingredients.filter(i => {
            return i;
          });
          m.measures = m.measures.filter(m => {
            return m;
          });
          return m;
        });
      });
    }
  },
  beforeMount() {
    this.search();
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Это генерирует случайный рецепт из MealDB API, вызывая конечную точку random и отображая все, что от нее возвращается.

В Search.vue мы вводим:

<template>
  <div class="container">
    <div class='row'>
      <div class="col-12">
        <h1>Search</h1>
      </div>
    </div>
    <div class='row'>
      <div class="col-12">
        <b-form @submit="search" @reset="keyword = ''">
          <b-form-group label="Keyword">
            <b-form-input type="text" v-model="keyword" required name="keyword" v-validate="'required'" placeholder="Search keyword">
            </b-form-input>
            <span v-show="errors.has('keyword')">{{ errors.first('keyword') }}</span>
          </b-form-group>
          <b-button type="submit" variant="primary">Search</b-button>
        </b-form>
      </div>
    </div>
    <div class='row'>
      <div class="col-12" v-if="meals && meals.length > 0">
        <h1>Results</h1>
        <div v-for="meal in meals" v-bind:key='meal.idMeal'>
          <meal-row :meal='meal'></meal-row>
        </div>
      </div>
      <div class="col-12" v-else>
        <h2>No Results Found</h2>
      </div>
    </div>
  </div>
</template>
<script>
const request = require("superagent");
import { APIURL } from "../main";
import MealRow from "./MealRow";
export default {
  name: "search",
  data() {
    return {
      keyword: "",
      meals: []
    };
  },
  methods: {
    search(evt) {
      evt.preventDefault();
      if (this.errors.any()) {
        return;
      }
      request.get(`${APIURL}search/${this.keyword}`).end((err, res) => {
        this.meals = res.body.meals;
        if (!res.body.meals){
          return;
        }
        this.meals = this.meals.map(m => {
          m.ingredients = [];
          m.measures = [];
          for (let key in m) {
            if (key.includes("strIngredient")) {
              let index = +key.replace("strIngredient", "");
              m.ingredients[index] = m[key];
            }
          if (key.includes("strMeasure")) {
              let index = +key.replace("strMeasure", "");
              m.measures[index] = m[key];
            }
          }
          m.ingredients = m.ingredients.filter(i => {
            return i;
          });
          m.measures = m.measures.filter(m => {
            return m;
          });
          return m;
        });
      });
    },
    searchParam(keyword) {
      this.keyword = keyword;
      request
        .get(`${APIURL}search/${keyword}`)
        .end((err, res) => {
          this.meals = res.body.meals;
          this.meals = this.meals.map(m => {
            m.ingredients = [];
            m.measures = [];
            for (let key in m) {
              if (key.includes("strIngredient")) {
                let index = +key.replace("strIngredient", "");
                m.ingredients[index] = m[key];
              }
              if (key.includes("strMeasure")) {
                let index = +key.replace("strMeasure", "");
                m.measures[index] = m[key];
              }
            }
            m.ingredients = m.ingredients.filter(i => {
              return i;
            });
            m.measures = m.measures.filter(m => {
              return m;
            });
            return m;
          });
        });
    }
  },
  beforeMount() {
    this.$route.params.keyword && this.searchParam(this.$route.params.keyword);
  },
  beforeRouteUpdate(to, from, next) {
    this.searchParam(to.params.keyword);
    next();
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Это страница поиска, где мы можем искать по ключевым словам. Он работает, отправляя ключевое слово, введенное в форму, и отображая данные.

В папке assets мы можем добавить логотип или удалить изображение с панели инструментов.

Результат

После добавления всего этого кода у нас есть:

Подпишитесь на мою рассылку сейчас по адресу http://jauyeung.net/subscribe/. Следуйте за мной в Твиттере по адресу https://twitter.com/AuMayeung