Подпишитесь на мою рассылку сейчас по адресу http://jauyeung.net/subscribe/.

Подпишитесь на меня в Twitter по адресу https://twitter.com/AuMayeung

Перетаскивание - это функция многих интерактивных веб-приложений. Он предоставляет пользователям интуитивно понятный способ манипулировать своими данными. Добавление функции перетаскивания легко добавить в приложения Vue.js.

Приложение, которое мы создаем

Мы создадим приложение todo с двумя столбцами - столбцом To Do и столбцом Done. Вы можете перетаскивать между ними два, чтобы изменить статус с сделать на сделано и наоборот. Для создания приложения мы используем библиотеку Vue Material с пакетом Vue Draggable, чтобы приложение выглядело хорошо и с легкостью предоставляло возможность перетаскивания. У него также будет меню навигации и верхняя панель.

Начиная

Чтобы начать создание приложения, мы начнем с установки Vue CLI. Устанавливаем, запустив npm i -g @vue/cli. После этого мы можем создавать проект. Для этого запускаем vue create todo-app. Вместо того, чтобы выбирать значение по умолчанию, мы настраиваем каркас приложения, выбирая альтернативный вариант и выбирая Vue Router, Babel и препроцессор CSS. Строительные леса должны быть завершены после выполнения инструкций, а затем мы готовы добавить несколько библиотек.

Нам нужно добавить Axios для выполнения HTTP-запросов и библиотеку Vue Material с пакетами Vue Draggable, о которых мы упоминали ранее, чтобы сделать наше приложение красивее и предоставить возможности перетаскивания, которые мы желаем соответственно. Кроме того, нам нужен пакет Vee Validate, чтобы мы могли выполнять проверку формы в нашем приложении. Чтобы установить эти пакеты, мы запускаем npm i axios vuedraggable vue-material [email protected].

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

Теперь мы можем написать код. Для начала мы добавляем миксин, чтобы мы могли делать наши запросы. Для этого мы создаем папку mixins и добавляем файл с именем todoMixin.js, а затем помещаем в наш файл следующее:

const axios = require('axios');
const apiUrl = 'http://localhost:3000';
export const todoMixin = {
    methods: {
        getTodos() {
            return axios.get(`${apiUrl}/todos`);
        },
        addTodo(data) {
            return axios.post(`${apiUrl}/todos`, data);
        },
        editTodo(data) {
            return axios.put(`${apiUrl}/todos/${data.id}`, data);
        },
        deleteTodo(id) {
            return axios.delete(`${apiUrl}/todos/${id}`);
        }
    }
}

Эти функции будут использоваться на нашей домашней странице для выполнения HTTP-запросов на выполнение CRUD в нашем списке задач. Сейчас мы создадим домашнюю страницу. В Home.vue мы заменяем то, что у нас есть, на следующее:

<template>
  <div class="home">
    <div class="center">
      <h1>To Do List</h1>
      <md-button class="md-raised" @click="showDialog = true">Add Todo</md-button>
    </div>
<div class="content">
      <md-dialog :md-active.sync="showDialog">
        <md-dialog-title>Add Todo</md-dialog-title>
        <form @submit="addNewTodo" novalidate>
          <md-field :class="{ 'md-invalid': errors.has('description') }">
            <label for="description">Description</label>
            <md-input
              type="text"
              name="description"
              v-model="taskData.description"
              v-validate="'required'"
            ></md-input>
            <span class="md-error" v-if="errors.has('description')">{{errors.first('description')}}</span>
          </md-field>
<md-dialog-actions>
            <md-button class="md-primary" @click="showDialog = false">Close</md-button>
            <md-button class="md-primary" @click="showDialog = false">Save</md-button>
          </md-dialog-actions>
        </form>
      </md-dialog>
      <div class="lists">
        <div class="left">
          <h2>To Do</h2>
          <draggable v-model="todo" group="tasks" @change="updateTodo">
            <div v-for="t in todo" :key="t.id" class="item">
              {{t.description}}
              <a @click="deleteTask(t.id)">
                <md-icon>close</md-icon>
              </a>
            </div>
          </draggable>
        </div>
        <div class="right">
          <h2>Done</h2>
          <draggable v-model="done" group="tasks" @change="updateTodo">
            <div v-for="d in done" :key="d.id" class="item">
              {{d.description}}
              <a @click="deleteTask(d.id)">
                <md-icon>close</md-icon>
              </a>
            </div>
          </draggable>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
// @ is an alias to /src
import draggable from "vuedraggable";
import { todoMixin } from "@/mixins/todoMixin";
export default {
  name: "home",
  components: {
    draggable
  },
  computed: {
    isFormDirty() {
      return Object.keys(this.fields).some(key => this.fields[key].dirty);
    }
  },
  mixins: [todoMixin],
  data() {
    return {
      todo: [],
      done: [],
      showDialog: false,
      taskData: {}
    };
  },
  beforeMount() {
    this.getNewTodos();
  },
  methods: {
    async addNewTodo(evt) {
      evt.preventDefault();
      if (!this.isFormDirty || this.errors.items.length > 0) {
        return;
      }
      await this.addTodo(this.taskData);
      this.showDialog = false;
      this.getNewTodos();
    },
    async getNewTodos() {
      const response = await this.getTodos();
      this.todo = response.data.filter(t => !t.done);
      this.done = response.data.filter(t => t.done);
    },
    async updateTodo(evt) {
      let todo = evt.removed && evt.removed.element;
      if (todo) {
        todo.done = !todo.done;
        await this.editTodo(todo);
      }
    },
    async deleteTask(id) {
      const todo = await this.deleteTodo(id);
      this.getNewTodos();
    }
  }
};
</script>
<style lang="scss" scoped>
.center {
  text-align: center;
}
.md-dialog {
  width: 70vw;
}
form {
  width: 92%;
}
.md-dialog-title.md-title {
  color: black !important;
}
.lists {
  padding-left: 5vw;
  display: flex;
  align-items: flex-start;
  .left,
  .right {
    width: 45vw;
    padding: 20px;
    min-height: 200px;
    .item {
      padding: 10px;
      border: 1px solid black;
      background-color: white;
      display: flex;
      justify-content: space-between;
      a {
        cursor: pointer;
      }
    }
  }
}
</style>

Мы добавили диалоговое окно с формой для ввода описания нашей задачи. Поле обязательно для заполнения, но пользователь может ввести что угодно. Функция addTodo принимает введенные данные и отправляет их, если они действительны. Функция this.fields предоставляется пакетом Vee Validate и имеет все поля в объекте, поэтому мы можем проверить, были ли поля изменены или нет. Все, что указано в свойстве computed, является компьютером всякий раз, когда что-либо, возвращаемое функцией, изменяется.

Затем мы добавили 2 списка, которые мы перетаскиваем между собой, чтобы мы могли изменить статус задач на выполненные или невыполненные. Списки - это draggable компоненты в шаблоне. Мы определили модели в draggable компонентах в объекте, возвращаемом функцией data. Важно, чтобы у нас была одна и та же group опора, чтобы мы могли перетаскивать между ними 2 перетаскиваемых компонента. Всякий раз, когда происходит перетаскивание, возникает событие change и вызывается функция updateTodo. Функция переключит флаг done задачи и сделает запрос на сохранение задачи.

У каждой задачи также есть кнопка для ее удаления. При нажатии кнопки закрытия вызывается функция deleteTodo. id задачи передается в функцию, поэтому мы можем сделать запрос на удаление задачи.

Затем в App.vue мы добавляем меню и левую панель навигации со следующим кодом:

<template>
  <div id="app">
    <md-toolbar>
      <md-button class="md-icon-button" @click="showNavigation = true">
        <md-icon>menu</md-icon>
      </md-button>
      <h3 class="md-title">Todo App</h3>
    </md-toolbar>
    <md-drawer :md-active.sync="showNavigation" md-swipeable>
      <md-toolbar class="md-transparent" md-elevation="0">
        <span class="md-title">Todo App</span>
      </md-toolbar>
<md-list>
        <md-list-item>
          <router-link to="/">
            <span class="md-list-item-text">Home</span>
          </router-link>
        </md-list-item>
      </md-list>
    </md-drawer>
    <router-view />
  </div>
</template>
<script>
export default {
  name: "app",
  data: () => {
    return {
      showNavigation: false
    };
  }
};
</script>
<style>
.center {
  text-align: center;
}
form {
  width: 95vw;
  margin: 0 auto;
}
.md-toolbar.md-theme-default {
  background: #009688 !important;
  height: 60px;
}
.md-title,
.md-toolbar.md-theme-default .md-icon {
  color: #fff !important;
}
</style>

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

В main.js мы помещаем:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueMaterial from 'vue-material';
import VeeValidate from 'vee-validate';
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'
Vue.config.productionTip = false;
Vue.use(VueMaterial);
Vue.use(VeeValidate);
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

чтобы включить дополнительные библиотеки Vue.js, которые мы используем в этом приложении.

И в router.js мы добавляем:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue';
Vue.use(Router)
export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    }
  ]
})

Это добавляет домашнюю страницу в наш список маршрутов, так что она будет отображаться пользователю при вводе URL-адреса или нажатии ссылки на страницу.

Наш JSON API будет добавлен без написания кода с использованием пакета JSON Server Node.js, расположенного по адресу https://github.com/typicode/json-server. Данные будут сохранены в файл JSON, поэтому нам не нужно создавать собственные серверные надстройки для сохранения некоторых простых данных. Устанавливаем сервер, запустив npm i -g json-server. Затем, когда это будет сделано, перейдите в каталог нашего проекта и запустите json-server --watch db.json. В db.json мы помещаем:

{
  "todos": []
}

так что мы можем использовать эти конечные точки для сохранения данных в db.json, которые имеют те же URL-адреса, что и в todoMixin.

После того, как все работы проделаны, у нас есть следующее: