HTML5 拖放API與Vue.js實戰

2021-03-02 前端先鋒
// 每日前端夜話 第466篇
// 正文共:3800 字
// 預計閱讀時間:12 分鐘

拖放 API 將可拖動元素添加到 HTML,使我們可以構建包含可以拖動的具有豐富 UI 元素的 Web 應用。

在本文中我們將用 Vue.js 構建一個簡單的看板應用。看板是一種項目管理工具,使用戶可以從頭到尾直觀地管理項目。Trello、Pivotal Tracker 和 Jira 等工具都屬於看板應用。

設置看板

運行以下命令創建我們的看板項目:

vue create kanban-board

在創建項目時,該選擇只包含 Babel 和 ESlint 的默認預設。

完成後,刪除默認組件 HelloWorld ,將 App 組件修改為空,僅包含裸組件模板:

<template> <div></div> </template>
<script>
export default {
  name: 'App',
  components: {},
};
</script>
<style></style>

接下來用 Bootstrap 進行樣式設置,只需 Bootstrap CSS CDN 就夠了。將其添加到 public/index.html 的 head 重。

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
    integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

在看板中構建 UI 組件

看板的樣子應該是這樣的:

通常看板要有列和卡片。卡片是要執行的單個項目或任務,列用來顯示特定卡片的狀態。

所以需要創建三個 Vue 組件:一個用於列,一個用於卡片,最後一個用於創建新卡片。

創建 card 組件

先來創建 card 組件。在 /component 目錄中創建一個新文件 Card.vue。

把下面的代碼添加到組件中:

<template>
  <div class="card">
    <div class="card-body">A Sample Card</div>
  </div>
</template>
<script>
export default {};
</script>
<style scoped>
div.card {
  margin-bottom: 15px;
  box-shadow: 0 0 5px #cccccc;
  transition: all ease 300ms;
  background: #fdfdfd;
}
div.card:hover {
  box-shadow: 0 0 10px #aaaaaa;
  background: #ffffff;
}
</style>

這樣就創建並設置了卡片組件的樣式。不過還沒有向組件添加可拖動功能,因為這只是組件的框架。

創建 AddCard 組件

顧名思義,這個組件將負責創建新卡片並將其添加到列中。

在 /components 目錄中創建一個 AddCard.vue 文件,並添加以下代碼:

<template>
  <div class="">
    <button
      class="btn btn-sm btn-info w-100"
      v-if="!inAddMode"
      @click="inAddMode = true"
    >
      Add Card
    </button>
    <form action="#" class="card p-3" ref="form" v-else>
      <div class="form-group">
        <input
          type="text"
          name="title"
          id="title"
          class="form-control"
          placeholder="Something interesting..."
          v-model="cardData"
        />
      </div>
      <div class="d-flex justify-content-center">
        <button type="submit" class="btn w-50 btn-primary mr-3">Save</button>
        <button type="reset" class="btn w-50 btn-danger">
          Cancel
        </button>
      </div>
    </form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      inAddMode: false,
      cardData: '',
    };
  },
  methods: {},
};
</script>
<style></style>

具體功能將在後面進行構建。

創建 Column 組件

這是最後一個組件,它用來顯示卡列表,還會包含 AddCard 組件,以便可以將新卡片直接創建到列中。

在 components 目錄中創建一個 Column.vue 文件,並添加以下代碼:

<template>
  <div class="col-md-3 card column" ref="column">
    <header class="card-header">
      <h3 class="col">Column Name</h3>
    </header>
    <div class="card-list"></div>
  </div>
</template>
<script>
export default {};
</script>
<style scoped>
div.column {
  padding: 0;
  padding-bottom: 15px;
  margin: 0 15px;
  box-shadow: 0 0 10px #cccccc;
}
div.card-list {
  padding: 0 15px;
}
header {
  margin-bottom: 10px;
}
header h3 {
  text-align: center;
}
</style>

現在項目的框架搭好了,接下來先概述一下拖放功能在瀏覽器中是怎樣工作的。

HTML5 拖放 API 是什麼?

當用戶將滑鼠移到可拖動元素上時,拖動操作開始,然後將元素移動到啟用拖放的元素上。

再默認情況下,唯一可拖動的 HTML 元素是圖像和連結。為了使其他元素可拖動,需要通過將 draggable 屬性添加到元素;也可以在 JavaScript 中選擇元素並將 draggable 屬性設置為 true 來顯式創建功能。

在元素上將 draggable 屬性設置為 true 之後,你會注意到 draggable 屬性已添加到該元素。

<!-- Making an element draggable in HTML -->
<div draggable="true">This is a draggable div in HTML</div>

<script>
// Making an element draggable in javascript
const div = document.querySelector('div');
div.draggable = true;
</script>

拖動元素的目的是將數據從頁面的一個部分傳輸到另一部分。

對於圖像,要傳輸的數據是圖像 URL 或它的 base 64 表示形式。如果是連結,傳輸的數據是 URL。可以將連結移動到瀏覽器的 URL 欄中,這樣使瀏覽器跳轉到該 URL。

所以,如果沒有數據傳輸的能力,那麼拖動元素就毫無用處了。可以通過 DataTransfer API 把通過拖動操作傳輸的數據保存在拖動數據存儲區中,這個 API 提供了在拖放操作期間存儲和訪問數據的方式。

DataTransfer 提供了添加要通過拖放傳輸的項目的位置。可以在開始拖動操作時(調用 dragstart 事件時)將數據添加到拖動數據存儲中,並且只能在完成拖放操作後(調用 drop 事件時)才能接收數據。

從拖動到釋放元素的這段時間中,元素被拖放後,將會在被拖動的元素上觸發兩個事件:dragstart 和 dragend。

現在還不能把可拖動元素拖放到任何地方。與需要顯式的使元素可拖動一樣,它也需要啟用放置。

要啟用元素拖放功能需要偵聽 dragover 事件並阻止默認的瀏覽器操作。

<!-- Make a section drop-enabled -->
<section class="section"></section>
<script>
const section = document.querySelector('.section');
section.addEventListener('dragover', (e) => {
  e.preventDefault();
});
</script>

將元素拖動到啟用拖放的元素上時,將會在啟用拖放的元素上觸發以下事件:

Dragenter:當一個元素被拖動到啟用拖放的元素上時觸發一次Dragover:只要元素仍然位於啟用了 drop 的元素上,就會連續觸發Drop:在把拖動的元素拖放到啟用了拖放的元素上之後觸發。

需要注意的是,僅在觸發放置事件時才能訪問存儲在 DataTransfer 對象中的數據,而不能在 dragenter 或 dragover 上訪問。

❞組合所有的組件

在向組件添加拖放功能之前,先討論一下 app state。

這裡的 app state 將存儲在 App 組件中,然後可以作為 props 向下傳遞到 Column 組件。另一方面,列組件在渲染時會將所需的 props 傳遞給卡片組件。

修改 App.vue 使其能夠反映狀態和組件組成:

// App.vue
<template>
  <div class="container-fluid">
    <h2 class="m-5">
      Vue Kanban Board
    </h2>
    <div class="row justify-content-center">
      <Column
        v-for="(column, index) in columns"
        :column="column"
        :key="index"
      />
    </div>
  </div>
</template>
<script>
import Column from './components/Column';
export default {
  name: 'App',
  components: {
    Column,
  },
  data() {
    return {
      columns: [
        {
          name: 'TO-DO',
          cards: [
            {
              value: 'Prepare breakfast',
            },
            {
              value: 'Go to the market',
            },
            {
              value: 'Do the laundry',
            },
          ],
        },
        {
          name: 'In Progress',
          cards: [],
        },
        {
          name: 'Done',
          cards: [],
        },
      ],
    };
  },
};
</script>
<style>
h2 {
  text-align: center;
}
</style>

在這裡,我們導入了列組件,並在狀態為 columns 的狀態下循環訪問數據時,將每一列的數據傳遞給 column 組件。在這種情況下,只有 「To-Do」,「In Progress」 和 「Done」 三列,每列都有一個卡片數組。

接下來,更新 Column 組件來接收 props 並顯示它:

// Column.vue
<template>
  <div class="col-md-3 card column" ref="column">
    <header class="card-header">
      <h3 class="col">{{ column.name }}</h3>
      <AddCard />
    </header>
    <div class="card-list">
      <Card v-for="(card, index) in column.cards" :key="index" :card="card" />
    </div>
  </div>
</template>
<script>
import Card from './Card';
import AddCard from './AddCard';
export default {
  name: 'Column',
  components: {
    Card,
    AddCard,
  },
  props: {
    column: {
      type: Object,
      required: true,
    },
  },
};
</script>

...

Column 組件從 App 組件接收 props,並用 props 渲染 Card 組件列表。在這裡還會使用 AddCard 組件,因為應該可以將新卡直接添加到列中。

最後更新 Card 組件顯示從 Column 接收的數據。

// Card.vue
<template>
  <div class="card" ref="card">
    <div class="card-body">{{ card.value }}</div>
  </div>
</template>
<script>
export default {
  name: 'Card',
  props: {
    card: {
      type: Object,
      required: true,
    },
  },
};
</script>

Card 組件僅從 Column 接收它需要的所有數據並顯示出來。我們還在此處添加了對 card 元素的引用,這樣在用 JavaScript 訪問 card 元素時非常有用。

完成上述操作後,你的應用應該是下面這樣了:

添加拖放功能

添加拖放功能的第一步是識別可拖動組件和放置目標。

用戶應該能夠按照卡片中的活動進度將卡片從一列拖到另一列。所以可拖動組件應該是 Card 組件,而放置目標是 Column 組件。

使卡片可拖動

需要執行以下操作才能使卡組件可拖動:

用 DataTransfer 對象設置要傳輸的數據

應該先把 draggable 設置為 true,根據 Vue 生命周期 hook,安全的位置應該是已安裝的 hook。把以下內容添加到 Card 組件的已安裝 hook 中:

// Card.vue
<script>
export default {
  name: 'Card',
  props: {...},

  mounted() {
    this.setDraggable();
  },

  methods: {
    setDraggable() {
      // Get Card element.
      const card = this.$refs.card;
      card.draggable = true;
      // Setup event listeners.
      card.addEventListener('dragstart', this.handleDragStart);
      card.addEventListener('dragend', this.handleDragEnd);
    },
  },
</script>

在上面,我們創建了一個 setDraggable 方法來使卡片組件可拖動。

在 setDraggable 中,從上一節中添加的引用中得到卡片,並將 draggable 屬性設置為 true 。

同時還需要設置事件監聽器:

// Card.vue
<script>
export const CardDataType = 'text/x-kanban-card';

export default {
...
  methods: {
    setDraggable() {...},
    handleDragStart(event) {
      const dataTransfer = event.dataTransfer;
      // Set the data to the value of the card which is gotten from props.
      dataTransfer.setData(CardDataType, this.card.value);
      dataTransfer.effectAllowed = 'move';
      // Add visual cues to show that the card is no longer in it's position.
      event.target.style.opacity = 0.2;
    },
    handleDragEnd(event) {
      // Return the opacity to normal when the card is dropped.
      event.target.style.opacity = 1;
    }
  }
}
</script>

在前面提到,只有在 dragstart 事件被調用時,數據才可以被添加到拖動數據存儲中。所以需要在 handleDragStart 方法中添加數據。

設置數據時要用到的重要信息是格式,可以是字符串。在我們的例子中,它被設置為 text/x-kanban-card。存儲這個數據格式並導出它,因為在刪除卡後獲取數據時,Column 組件將會用到它。

最後,將 card 的透明度降低到 0.2 ,以便向用戶提供一些反饋,表明該卡實際上已被拉出其原始位置。拖動完成後,再把透明度恢復為 1。

現在可以拖動卡片了。接下來添加放置目標。

把 dragover 設置為 drop-enabled

將卡片拖到列組件上時,會立即觸發 dragover 事件,將卡放入列中後會觸發 drop 事件。

要使卡片掉落到列中,需要偵聽這些事件。

// Column.vue
<template>...</template>
<script>
import Card { CardDataType } from './Card';
import AddCard from './AddCard';
export default {
  name: 'Column',
  components: {...},
  props: {...},
  mounted() {
    this.enableDrop();
  },
  methods: {
    enableDrop() {
      const column = this.$refs.column;
      column.addEventListener('dragenter', this.handleDragEnter);
      column.addEventListener('dragover', this.handleDragOver);
      column.addEventListener('drop', this.handleDrop);
    },
    /**
     * @param {DragEvent} event
     */
    handleDragEnter(event) {
      if (event.dataTransfer.types.includes[CardDataType]) {
        // Only handle cards.
        event.preventDefault();
      }
    },
    handleDragOver(event) {
      // Create a move effect.
      event.dataTransfer.dropEffect = 'move';
      event.preventDefault();
    },
    /**
     * @param {DragEvent} event
     */
    handleDrop(event) {
      const data = event.dataTransfer.getData(CardDataType);
      // Emit a card moved event.
      this.$emit('cardMoved', data);
    },
  },
};
</script>

在這裡將設置在掛載 Column 組件之後啟用 drop 所需的所有事件偵聽器。

在這三個事件中,第一個被觸發的是 dragenter *,*當可拖動元素被拖到列中時會立即被觸發。對於我們的程序,只希望將卡片放入一列中,所以在 dragenter 事件中,只阻止數據類型的默認值,數據類型包括在 card 組件中所定義的 card 數據類型。

在 dragover 事件中,把放置效果設置為 move。

在 drop 事件中獲得從 dataTransfer 對象傳輸的數據。

接下來,需要更新狀態並將卡片移動到當前列。因為我們的程序狀態位於 App 組件中,所以在 drop 偵聽器中發出 cardMoved 事件,傳遞已傳輸的數據,並在 App 組件中偵聽 cardMoved 事件。

更新 App.vue 來監聽 cardMoved 事件:

// App.vue

<template>
  <div class="container-fluid">
    ...
    <div class="row justify-content-center">
      <Column
        v-for="(column, index) in columns"
        :column="column"
        :key="index"
        @cardMoved="moveCardToColumn($event, column)"
      />
    </div>
  </div>
</template>

<script>
import Column from './components/Column';
export default {
  name: 'App',
  components: {...},
  data() {
    return {...}
  },
  methods: {
    moveCardToColumn(data, newColumn) {
      const formerColumn = this.columns.find(column => {
        // Get all the card values in a column.
        const cardValues = column.cards.map((card) => card.value);
        return cardValues.includes(data);
      })
      // Remove card from former column.
      formerColumn.cards = formerColumn.cards.filter(
        (card) => card.value !== data
      );
      // Add card to the new column.
      newColumn.cards.push({ value: data });
    },
  },
}
</script>

在這裡通過 @cardMoved 偵聽 cardMoved 事件,並調用 moveCardToColumn 方法。cardMoved 事件發出一個值(卡片數據),可以通過 $event 訪問這個值,另外還傳遞了放置卡的當前列(這是調度事件的位置)。

moveCardToColumn 函數做了三件事:找到卡偏先前所在的列,從該列中取出卡片,最後把卡片加到新列中。

完成看板

現在我們已經實現了拖放功能,最後只剩下添加卡片的功能了。

在 AddCard.vue 中添加以下代碼:

<template>
  <div class="">
    <button
      class="btn btn-sm btn-info w-100"
      v-if="!inAddMode"
      @click="inAddMode = true"
    >
      Add Card
    </button>
    <form
      action="#"
      class="card p-3"
      @submit.prevent="handleSubmit"
      @reset="handleReset"
      ref="form"
      v-else
    >
      ...
    </form>
  </div>
</template>
<script>
export default {
  data() {
    return {...};
  },
  methods: {
    handleSubmit() {
      if (this.cardData.trim()) {
        this.cardData = '';
        this.inAddMode = false;
        this.$emit('newcard', this.cardData.trim());
      }
    },
    handleReset() {
      this.cardData = '';
      this.inAddMode = false;
    },
  },
};
</script>

上面的代碼是在提交「add card」表單或重置時運行的函數。

重置後清除 cardData,並將 inAddMode 設置為 false。

在提交表單後還要清除 cardData ,以便在添加新項目時不會顯示以前的數據,並且還要將 inAddMode 設置為 false 並發出 newcard 事件。

Column組件中使用了AddCard組件,所以需要在 Column 組件中監聽 newcard 事件。在 Column 組件中添加偵聽 newcard 事件的代碼:

<template>
  <div class="col-md-3 card column" ref="column">
    <header class="card-header">
      <h3 class="col">{{ column.name }}</h3>
      <AddCard @newcard="$emit('newcard', $event)"></AddCard>
    </header>
    ...
</template>
...

在這裡重新發出 newcard 事件,這樣可以使它到達 App 組件,實際的動作將在該組件上發生。

自定義 Vue 事件不會冒泡,因此 App 組件無法偵聽 AddCard 組件中發出的 newcard 事件,因為它不是直接子組件。

更新 App  組件處理 newcard 事件的代碼:

// App.vue

<template>
  <div class="container-fluid">
    ...
    <div class="row justify-content-center">
      <Column
        v-for="(column, index) in columns"
        :column="column"
        :key="index"
        @cardMoved="moveCardToColumn($event, column)"
        @newcard="handleNewCard($event, column)"
      />
    </div>
  </div>
</template>

<script>
import Column from './components/Column';
export default {
  name: 'App',
  components: {...},
  data() {
    return {...}
  },
  methods: {
    moveCardToColumn(data, newColumn) {...},
    handleNewCard(data, column) {
      // Add new card to column.
      column.cards.unshift({ value: data });
    },
  },
};
</script>

在這裡偵聽從 Column 組件調用的 newcard 事件,在獲取數據後,創建一個新卡片並將其添加到創建該卡的列中。

總結

在本文中,我們介紹了什麼是 HTML 5 拖放 API ,如何使用,以及如何在 Vue.js 中實現。

拖放功能也可以在其他前端框架和原生 JavaScript 中使用。




強力推薦前端面試刷題神器
精彩文章回顧,點擊直達


相關焦點

  • 實戰:在Node.js和Vue.js中構建文件壓縮應用程式
    在 client 目錄中,創建兩個文件:index.html 和 main.js。我們將從為應用程式定義用戶界面開始。我們將搭建一個HTML 5樣板文件,然後在模板頭中添加vuei .js腳本、Bootstrap CSS CDN和Axios腳本。
  • 【Vue.js入門到實戰教程】11-Vue Loader(下)| 編寫一個單文件 Vue 組件
    然後在 src/main.js 中引入 Bootstrap 的腳本和樣式文件:import Vue from 'vue'import App from './App.vue'import 'bootstrap'import 'bootstrap/dist/css/bootstrap.min.css'...接下來,就可以正式編寫單文件組件了。
  • Ultimate Vue.js和Laravel CRUD教程
    resources/assets/js/components/App.vue<template>...resources/assets/js/components/App.vue...methods() {read() { window.axios.get('/api/cruds').then(({ data }) => { // console.log(data) }); }, ...}
  • 【Vue.js 入門到實戰教程】01-Vue.js 數據綁定的基本實現和代碼分析
    數據綁定的基本實現我們新建一個 vue_learning 項目,在該項目下新建一個 basic 目錄用於存放 Vue 基本語法的演示代碼。在 basic 目錄下新建一個名為 hello 的 HTML 5 文件:
  • Vue.js以業務為中心的常見面試題
    在vue.js中的MVVM模式:vue.js是通過數據驅動的,vue.js實例化對象將dom和數據進行綁定,一旦綁定,dom和數據將保持同步,每當數據發生變化,dom也會隨著變化;ViewModel是Vue.js的核心,它是Vue.js的一個實例。
  • 【項目推薦】Vue.js
    作者是尤雨溪,寫下這篇文章時 vue.js版本為 1.0.7 。我推薦使用 sublime text 作為編輯器,關於這個編輯器可以看我這篇文章。在 package control中安裝Vuejs SnippetsVue Syntax Highlight推薦使用 npm 管理,新建兩個文件 app.html,app.js,為了美觀使用 bootstrap,我們的頁面模板看起來是這樣:<!
  • 「Vue.js開發連載一」Vue.js簡介
    Vue.js的目標是通過儘可能簡單的API實現響應的數據綁定和組合的視圖組件。Vue.js自身不是一個全能框架——它只聚焦於視圖層。因此它非常容易學習,非常容易與其它庫或已有項目整合。另一方面,在與相關工具和支持庫一起使用時,Vue.js也能完美地驅動複雜的單頁應用。
  • 使用Vue.js、Node和Okta構建安全的用戶管理
    本教程使用以下技術,但不需要深入了解相關知識:帶有vue-cli, vue-router和 Okta Vue SDK的 Vue.js 使用 Express, Okta JWT驗證程序, Sequelize和 Epilogue結點
  • 初步認識vue.js框架的使用
    vue.js框架是幹什麼的Vue.js 是一個JavaScriptMVVM庫,是一套構建用戶界面的漸進式框架。它是以數據驅動和組件化的思想構建的,採用自底向上增量開發的設計。相比於Angular.js,Vue.js提供了更加簡潔、更易於理解的API,使得我們能夠快速地上手並使用Vue.js。如何使用vue.js1.下載 vue.min.js 並用 <script> 標籤引入。
  • 一張圖教你快速玩轉vue-cli3
    我們可以通過如下三種方式解決此類問題:將依賴添加到 vue.config.js 中的 transpileDependencies 選項// vue.config.jsmodule.exports = {// 默認情況下 babel-loader 會忽略所有 node_modules 中的文件。
  • Vue入門10 vue+elementUI
    十二、實戰快速上手我們採用實戰教學模式並結合ElementUI組件庫,將所需知識點應用到實際中,以最快速度帶領大家掌握Vue的使用;
  • Vue.js布局
    動態Vue.js布局組件前言vue.js是漸進增強的視圖庫,可以作為.html頁面部分使用,也可以結合vue-router、vuex、axios用來構建單頁面或多頁面應用。
  • ref vue 獲取文本專題及常見問題 - CSDN
    ref的官網介紹https://cn.vuejs.org/v2/api/#ref需求在普通的js操作中,一般都是直接操作dom元素,但是對於Vue.js框架來說,一般是不允許直接操作dom元素的。那麼其實Vue.js框架提供了ref獲取dom元素,以及組件引用。
  • Vue-Router源碼學習之index.js(vue-router類)
    今天,帶來Vue-Router源碼解析系列的第二篇文章:index.js。正文vue-router類裡面都做了什麼?index.js是vue-router這個類的主構造函數,所以內容上算是比較關鍵的:從圖片中我們可以看出來,這是一個ES6聲明類的方法,vue-router源碼中類的聲明都是使用類ES的語法,constructor (options: RouterOptions
  • 7個有用的Vue開發技巧
    今天我們介紹的是 vue.js 2.6 新增加的 Observable API ,通過使用這個 api 我們可以應對一些簡單的跨組件數據狀態共享的情況。如下這個例子,我們將在組件外創建一個 store,然後在 App.vue組件裡面使用 store.js 提供的 store和 mutation方法,同理其它組件也可以這樣使用,從而實現多個組件共享數據狀態。
  • 【分享】Vue.js新手入門指南
    這個時候如果你學過Vue.js,那麼這些抱怨將不復存在。5.Vue.js為什麼能讓基於網頁的前端應用程式開發起來這麼方便?因為Vue.js有聲明式,響應式的數據綁定,與組件化的開發,並且還使用了Virtual DOM這個看名字就覺得高大上的技術。可是這些名詞都是啥?
  • 20 道必看的 Vue 面試題|原力計劃
    請說出 vue-cli 工程中每個文件夾和文件的用處。build 文件夾:存放 webpack 的相關配置以及腳本文件,在實際開發過程中只會偶爾用到 webpack.base.conf.js,配置 less、babel 等。
  • 實戰教學使用 Vue3 重構 Vue2 項目(萬字好文推薦)
    集成Vue周邊庫我們將Vue CLI初始化的項目文件替換到用vite初始化的項目中去,然後修改packge.json中的相關依賴,然後重新安裝依賴即可>      function(error) {        if (error) {          // 請求已發出,但不在2xx範圍內          errorHandle(error.status, error.data.msg);          return Promise.reject(error);        } else {
  • Vue入門-實戰教程
    Vue2+VueRouter2+Webpack+Axios 構建項目實戰(一)基礎知識概述:http://www.javazhiyin.com/?p=287Vue2+VueRouter2+Webpack+Axios 構建項目實戰(三)認識項目所有文件:http://www.javazhiyin.com/?
  • [前端]分別原生JS和Vue實現計數器功能
    題目用vue實現計數器功能,其中vue實現的代碼由黑馬程式設計師vue教程給出,這裡對其CSS代碼進行了注釋