1. 程式人生 > >[譯] 用 Flask 和 Vue.js 開發一個單頁面應用

[譯] 用 Flask 和 Vue.js 開發一個單頁面應用

這篇文章會一步一步的教會你如何用 VUE 和 Flask 建立一個基礎的 CRUD 應用。我們將從使用 Vue CLI 建立一個新的 Vue 應用開始,接著我們會使用 Python 和 Flask 提供的後端介面 RESTful API 執行基礎的 CRUD 操作。

最終效果:

final app

主要依賴:

  • Vue v2.5.2
  • Vue CLI v2.9.3
  • Node v10.3.0
  • npm v6.1.0
  • Flask v1.0.2
  • Python v3.6.5

目錄

目的

在本教程結束的時候,你能夠...

  1. 解釋什麼是 Flask
  2. 解釋什麼是 Vue 並且它和其他 UI 庫以及 Angular、React 等前端框架相比又如何
  3. 使用 Vue CLI 搭建一個 Vue 專案
  4. 在瀏覽器中建立並渲染 Vue 元件
  5. 使用 Vue 元件建立一個單頁面應用(SPA)
  6. 將一個 Vue 應用與後端的 Flask 連線
  7. 使用 Flask 開發一個 RESTful API
  8. 在 Vue 元件中使用 Bootstrap 樣式
  9. 使用 Vue Router 去建立路由和渲染元件

什麼是 Flask?

Flask 是一個用 Python 編寫的簡單,但是及其強大的輕量級 Web 框架,非常適合用來構建 RESTful API。就像 Sinatra(Ruby)和 Express(Node)一樣,它也十分簡便,所以你可以從小處開始,根據需求構建一個十分複雜的應用。

第一次使用 Flask?看看這下面兩個教程吧:

  1. Flaskr TDD
  2. Flask for Node Developers

什麼是 Vue?

Vue 是一個用於構建使用者介面的開源 JavaScript 框架。它綜合了一些 React 和 Angular 的優點。也就是說,與 React 和 Angular 相比,它更加友好,所以初學者額能夠很快的學習並掌握。它也同樣強大,因此它能夠提供所有你需要用來建立一個前端應用所需要的功能。

有關 Vue 的更多資訊,以及使用它與 Angular 和 React 的利弊,請檢視以下文章:

  1. Vue: Comparison with Other Frameworks
  2. Angular vs. React vs. Vue: A 2017 comparison

第一次使用 Vue?不妨花點時間閱讀官方指南中的 介紹

安裝 Flask

首先建立一個新專案資料夾:

$ mkdir flask-vue-crud
$ cd flask-vue-crud
複製程式碼

在 “flask-vue-crud” 資料夾中,建立一個新資料夾並取名為 “server”。然後,在 “server” 資料夾中建立並執行一個虛擬環境:

$ python3.6 -m venv env
$ source env/bin/activate
複製程式碼

以上命令因環境而異。

安裝 Flask 和 Flask-CORS 擴充套件:

(env)$ pip install Flask==1.0.2 Flask-Cors==3.0.4
複製程式碼

在新建立的資料夾中新增一個 app.py 檔案

from flask import Flask, jsonify
from flask_cors import CORS


# configuration
DEBUG = True

# instantiate the app
app = Flask(__name__)
app.config.from_object(__name__)

# enable CORS
CORS(app)


# sanity check route
@app.route('/ping', methods=['GET'])
def ping_pong():
    return jsonify('pong!')


if __name__ == '__main__':
    app.run()
複製程式碼

為什麼我們需要 Flask-CORS?為了進行跨域請求 — e.g.,來自不同協議,IP 地址,域名或埠的請求 — 你需要允許 跨域資源共享(CORS)。而這正是 Flask-CORS 能為我們提供的。

值得注意的是上述安裝允許跨域請求在全部路由無論任何域,協議或者埠都可用。在生產環境中,你應該允許跨域請求成功在前端應用託管的域上。參考 Flask-CORS 文件 獲得更多資訊。

執行應用:

(env)$ python app.py
複製程式碼

開始測試,將你的瀏覽器指向到 http://localhost:5000/ping。你將會看到:

"pong!"
複製程式碼

返回終端,按下 Ctrl+C 來終止服務端然後退回到專案根目錄。接下來,讓我們把注意力轉到前端進行 Vue 的安裝。

安裝 Vue

我們將會使用強力的 Vue CLI 來生成一個自定義專案模板。

全域性安裝:

$ npm install -g [email protected]
複製程式碼

第一次使用 npm?瀏覽一下 什麼是 npm? 官方指南吧

然後,在 “flask-vue-crud” 中,執行以下命令初始化一個叫做 client 的新 Vue 專案幷包含 webpack 配置:

$ vue init webpack client
複製程式碼

webpack 是一個模組打包構建工具,用於構建,壓縮以及打包 JavaScript 檔案和其他客戶端資源。

它會請求你對這個專案進行一些配置。按下回車鍵去選擇前三個為預設設定,然後使用以下的設定去完成後續的配置:

  1. Vue build: Runtime + Compiler
  2. Install vue-router?: Yes
  3. Use ESLint to lint your code?: Yes
  4. Pick an ESLint preset: Airbnb
  5. Set up unit tests: No
  6. Setup e2e tests with Nightwatch: No
  7. Should we run npm install for you after the project has been created: Yes, use NPM

你會看到一些配置請求比如:

? Project name client
? Project description A Vue.js project
? Author Michael Herman [email protected]
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Airbnb
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm
複製程式碼

快速瀏覽一下生成的專案架構。看起來好像特別多,但是我們會用到那些在 “src” 中的檔案和 index.html 檔案。

index.html 檔案是我們 Vue 應用的起點。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>client</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
複製程式碼

注意那個 idapp<div> 元素。那是一個佔位符,Vue 將會用來連線生成的 HTML 和 CSS 構建 UI。

注意那些在 “src” 資料夾中的資料夾:

├── App.vue
├── assets
│   └── logo.png
├── components
│   └── HelloWorld.vue
├── main.js
└── router
    └── index.js
複製程式碼

分解:

名字 作用
main.js app 接入點,將會和根元件一起載入並初始化 Vue
App.vue 根元件 —— 起點,所有其他元件都將從此處開始渲染
“assets” 儲存影象和字型等靜態資源
“components” 儲存 UI 元件
“router” 定義 URL 地址並對映到元件

檢視 client/src/components/HelloWorld.vue 檔案。這是一個 單檔案元件,它分為三個不同的部分:

  1. template:特定元件的 HTML
  2. script:通過 JavaScript 實現元件邏輯
  3. style:CSS 樣式

執行開發服務端:

$ cd client
$ npm run dev
複製程式碼

在你的瀏覽器中導航到 http://localhost:8080。你將會看到:

default vue app

新增一個新元件在 “client/src/components” 資料夾中,並取名為 Ping.vue

<template>
  <div>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  name: 'Ping',
  data() {
    return {
      msg: 'Hello!',
    };
  },
};
</script>
複製程式碼

更新 client/src/router/index.js 使 ‘/’ 對映到 Ping 元件:

import Vue from 'vue';
import Router from 'vue-router';
import Ping from '@/components/Ping';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Ping',
      component: Ping,
    },
  ],
});
複製程式碼

最後,在 client/src/App.vue 中,從 template 裡刪除掉圖片:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>
複製程式碼

你現在應該能在瀏覽器中看見一個 Hello!

為了更好地使客戶端 Vue 應用和後端 Flask 應用連線,我們可以使用 axios 庫來發送 AJAX 請求。

那麼我們開始安裝它:

$ npm install [email protected] --save
複製程式碼

然後在 Ping.vue 中更新元件的 script 部分,就像這樣:

<script>
import axios from 'axios';

export default {
  name: 'Ping',
  data() {
    return {
      msg: '',
    };
  },
  methods: {
    getMessage() {
      const path = 'http://localhost:5000/ping';
      axios.get(path)
        .then((res) => {
          this.msg = res.data;
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        });
    },
  },
  created() {
    this.getMessage();
  },
};
</script>
複製程式碼

在新的終端視窗啟動 Flask 應用。在瀏覽器中開啟 http://localhost:8080 你會看到 pong!。基本上,當我們從後端得到回覆的時候,我們會將 msg 設定為響應物件的 data 的值。

安裝 Bootstrap

接下來,讓我們引入一個熱門 CSS 框架 Bootstrap 到應用中以方便我們快速新增一些樣式。

安裝:

$ npm install [email protected] --save
複製程式碼

忽略 jquerypopper.js 的警告。不要把它們新增到你的專案中。稍後會告訴你為什麼。

插入 Bootstrap 樣式到 client/src/main.js 中:

import 'bootstrap/dist/css/bootstrap.css';
import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});
複製程式碼

更新 client/src/App.vue 中的 style

<style>
#app {
  margin-top: 60px
}
</style>
複製程式碼

通過使用 ButtonContainer 確保 Bootstrap 在 Ping 元件中正確連線:

<template>
  <div class="container">
    <button type="button" class="btn btn-primary">{{ msg }}</button>
  </div>
</template>
複製程式碼

執行開發服務端:

$ npm run dev
複製程式碼

你應該會看到:

vue with bootstrap

然後,新增一個叫做 Books 的新元件到新檔案 Books.vue 中:

<template>
  <div class="container">
    <p>books</p>
  </div>
</template>
複製程式碼

更新路由:

import Vue from 'vue';
import Router from 'vue-router';
import Ping from '@/components/Ping';
import Books from '@/components/Books';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Books',
      component: Books,
    },
    {
      path: '/ping',
      name: 'Ping',
      component: Ping,
    },
  ],
  mode: 'hash',
});
複製程式碼

測試:

  1. http://localhost:8080
  2. http://localhost:8080/#/ping

想要擺脫掉 URL 中的雜湊值嗎?更改 modehistory 以使用瀏覽器的 history API 來導航:

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Books',
      component: Books,
    },
    {
      path: '/ping',
      name: 'Ping',
      component: Ping,
    },
  ],
  mode: 'history',
});
複製程式碼

檢視文件以獲得更多路由 資訊

最後,讓我們新增一個高效的 Bootstrap 風格表格到 Books 元件中:

<template>
  <div class="container">
    <div class="row">
      <div class="col-sm-10">
        <h1>Books</h1>
        <hr><br><br>
        <button type="button" class="btn btn-success btn-sm">Add Book</button>
        <br><br>
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">Title</th>
              <th scope="col">Author</th>
              <th scope="col">Read?</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>foo</td>
              <td>bar</td>
              <td>foobar</td>
              <td>
                <button type="button" class="btn btn-warning btn-sm">Update</button>
                <button type="button" class="btn btn-danger btn-sm">Delete</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>
複製程式碼

你現在應該會看到:

books component

現在我們可以開始構建我們的 CRUD 應用的功能。

我們的目的是什麼?

我們的目標是設計一個後端 RESTful API,由 Python 和 Flask 驅動,對應一個單一資源 — books。這個 API 應當遵守 RESTful 設計原則,使用基本的 HTTP 動詞:GET、POST、PUT 和 DELETE。

我們還會使用 Vue 搭建一個前端應用來使用這個後端 API:

final app

本教程只設計簡單步驟。處理錯誤是讀者(就是你!)的額外練習。通過你的理解解決前後端出現的問題吧。

獲取路由

服務端

新增一個書單到 server/app.py 中:

BOOKS = [
    {
        'title': 'On the Road',
        'author': 'Jack Kerouac',
        'read': True
    },
    {
        'title': 'Harry Potter and the Philosopher\'s Stone',
        'author': 'J. K. Rowling',
        'read': False
    },
    {
        'title': 'Green Eggs and Ham',
        'author': 'Dr. Seuss',
        'read': True
    }
]
複製程式碼

新增路由介面:

@app.route('/books', methods=['GET'])
def all_books():
    return jsonify({
        'status': 'success',
        'books': BOOKS
    })
複製程式碼

執行 Flask 應用,如果它並沒有執行,嘗試在 http://localhost:5000/books 手動測試路由。

想更有挑戰性?寫一個自動化測試吧。檢視 這個 資源可以瞭解更多關於測試 Flask 應用的資訊。

客戶端

更新元件:

<template>
  <div class="container">
    <div class="row">
      <div class="col-sm-10">
        <h1>Books</h1>
        <hr><br><br>
        <button type="button" class="btn btn-success btn-sm">Add Book</button>
        <br><br>
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">Title</th>
              <th scope="col">Author</th>
              <th scope="col">Read?</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(book, index) in books" :key="index">
              <td>{{ book.title }}</td>
              <td>{{ book.author }}</td>
              <td>
                <span v-if="book.read">Yes</span>
                <span v-else>No</span>
              </td>
              <td>
                <button type="button" class="btn btn-warning btn-sm">Update</button>
                <button type="button" class="btn btn-danger btn-sm">Delete</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      books: [],
    };
  },
  methods: {
    getBooks() {
      const path = 'http://localhost:5000/books';
      axios.get(path)
        .then((res) => {
          this.books = res.data.books;
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        });
    },
  },
  created() {
    this.getBooks();
  },
};
</script>
複製程式碼

當元件初始化完成後,通過 created 生命週期鉤子呼叫 getBooks() 方法,它從我們剛剛設定的後端介面獲取書籍。

查閱 例項生命週期鉤子 瞭解更多有關元件生命週期和可用方法的資訊。

在模板中,我們通過 v-for 指令遍歷書籍列表,每次遍歷建立一個新表格行。索引值用作 key。最後,使用 v-ifYesNo,來表現使用者已讀或未讀這本書。

books component

Bootstrap Vue

在下一節中,我們將會使用一個模態去新增新書。為此,我們在本節會加入 Bootstrap Vue 庫到專案中,它提供了一組基於 Bootstrap 的 HTML 和 CSS 設計的 Vue 元件。

為什麼選擇 Bootstrap Vue?Bootstrap 的 模態 元件使用 jQuery,但你應該避免把它和 Vue 在同一專案中一起使用,因為 Vue 使用 虛擬 DOM 來更新 DOM。換句話來說,如果你用 jQuery 來操作 DOM,Vue 不會有任何反應。至少,如果你一定要使用 jQuery,不要在同一個 DOM 元素上同時使用 jQuery 和 Vue。

安裝:

$ npm install [email protected] --save
複製程式碼

client/src/main.js 中啟用 Bootstrap Vue 庫:

import 'bootstrap/dist/css/bootstrap.css';
import BootstrapVue from 'bootstrap-vue';
import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

Vue.use(BootstrapVue);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});
複製程式碼

POST 路由

服務端

更新現有路由以處理新增新書的 POST 請求:

@app.route('/books', methods=['GET', 'POST'])
def all_books():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        BOOKS.append({
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'read': post_data.get('read')
        })
        response_object['message'] = 'Book added!'
    else:
        response_object['books'] = BOOKS
    return jsonify(response_object)
複製程式碼

更新 imports:

from flask import Flask, jsonify, request
複製程式碼

執行 Flask 服務端後,你可以在新的終端裡測試 POST 路由:

$ curl -X POST http://localhost:5000/books -d \
  '{"title": "1Q84", "author": "Haruki Murakami", "read": "true"}' \
  -H 'Content-Type: application/json'
複製程式碼

你應該會看到:

{
  "message": "Book added!",
  "status": "success"
}
複製程式碼

你應該會在 http://localhost:5000/books 的末尾看到新書。

如果書名已經存在了呢?如果一個書名對應了幾個作者呢?通過處理這些小問題可以加深你的理解,另外,如何處理 書名作者,以及 閱覽狀態 都缺失的無效負載情況。

客戶端

在客戶端上,讓我們新增那個模態以新增一本新書,從 HTML 開始:

<b-modal ref="addBookModal"
         id="book-modal"
         title="Add a new book"
         hide-footer>
  <b-form @submit="onSubmit" @reset="onReset" class="w-100">
  <b-form-group id="form-title-group"
                label="Title:"
                label-for="form-title-input">
      <b-form-input id="form-title-input"
                    type="text"
                    v-model="addBookForm.title"
                    required
                    placeholder="Enter title">
      </b-form-input>
    </b-form-group>
    <b-form-group id="form-author-group"
                  label="Author:"
                  label-for="form-author-input">
        <b-form-input id="form-author-input"
                      type="text"
                      v-model="addBookForm.author"
                      required
                      placeholder="Enter author">
        </b-form-input>
      </b-form-group>
    <b-form-group id="form-read-group">
      <b-form-checkbox-group v-model="addBookForm.read" id="form-checks">
        <b-form-checkbox value="true">Read?</b-form-checkbox>
      </b-form-checkbox-group>
    </b-form-group>
    <b-button type="submit" variant="primary">Submit</b-button>
    <b-button type="reset" variant="danger">Reset</b-button>
  </b-form>
</b-modal>
複製程式碼

div 標籤中新增這段程式碼。然後簡單閱覽一下。v-model 是一個用於 表單輸入繫結 的指令。你馬上就會看到。

hide-footer 具體幹了什麼?在 Bootstrap Vue 的 文件 中瞭解更多

更新 script 部分:

<script>
import axios from 'axios';

export default {
  data() {
    return {
      books: [],
      addBookForm: {
        title: '',
        author: '',
        read: [],
      },
    };
  },
  methods: {
    getBooks() {
      const path = 'http://localhost:5000/books';
      axios.get(path)
        .then((res) => {
          this.books = res.data.books;
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        });
    },
    addBook(payload) {
      const path = 'http://localhost:5000/books';
      axios.post(path, payload)
        .then(() => {
          this.getBooks();
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.log(error);
          this.getBooks();
        });
    },
    initForm() {
      this.addBookForm.title = '';
      this.addBookForm.author = '';
      this.addBookForm.read = [];
    },
    onSubmit(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide();
      let read = false;
      if (this.addBookForm.read[0]) read = true;
      const payload = {
        title: this.addBookForm.title,
        author: this.addBookForm.author,
        read, // property shorthand
      };
      this.addBook(payload);
      this.initForm();
    },
    onReset(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide();
      this.initForm();
    },
  },
  created() {
    this.getBooks();
  },
};
</script>
複製程式碼

實現了什麼?

  1. addBookForm 的值被 表單輸入繫結 到,沒錯,v-model。當資料更新時,另一個也會跟著更新。這被稱之為雙向繫結。花點時間從 這裡 瞭解一下吧。想想這個帶來的結果。你認為這會使狀態管理更簡單還是更復雜?React 和 Angular 又會如何做到這點?在我看來,雙向資料繫結(可變性)使得 Vue 和 React 相比更加友好,但是從長遠看擴充套件性不足。

  2. onSubmit 會在使用者提交表單成功時被觸發。在提交時,我們會阻止瀏覽器的正常行為(evt.preventDefault()),關閉模態框(this.$refs.addBookModal.hide()),觸發 addBook 方法,然後清空表單(initForm())。

  3. addBook 傳送一個 POST 請求到 /books 去新增一本新書。

  4. 根據自己的需要檢視其他更改,並根據需要參考 Vue 的 文件

你能想到客戶端或者服務端還有什麼潛在的問題嗎?思考這些問題去試著加強使用者體驗吧。

最後,更新 template 中的 “Add Book” 按鈕,這樣一來我們點選按鈕就會顯示出模態框:

<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>
複製程式碼

那麼元件應該是這樣子的:

<template>
  <div class="container">
    <div class="row">
      <div class="col-sm-10">
        <h1>Books</h1>
        <hr><br><br>
        <button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>
        <br><br>
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">Title</th>
              <th scope="col">Author</th>
              <th scope="col">Read?</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(book, index) in books" :key="index">
              <td></td>
              <td></td>
              <td>
                <span v-if="book.read">Yes</span>
                <span v-else>No</span>
              </td>
              <td>
                <button type="button" class="btn btn-warning btn-sm">Update</button>
                <button type="button" class="btn btn-danger btn-sm">Delete</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    <b-modal ref="addBookModal"
             id="book-modal"
             title="Add a new book"
             hide-footer>
      <b-form @submit="onSubmit" @reset="onReset" class="w-100">
      <b-form-group id="form-title-group"
                    label="Title:"
                    label-for="form-title-input">
          <b-form-input id="form-title-input"
                        type="text"
                        v-model="addBookForm.title"
                        required
                        placeholder="Enter title">
          </b-form-input>
        </b-form-group>
        <b-form-group id="form-author-group"
                      label="Author:"
                      label-for="form-author-input">
            <b-form-input id="form-author-input"
                          type="text"
                          v-model="addBookForm.author"
                          required
                          placeholder="Enter author">
            </b-form-input>
          </b-form-group>
        <b-form-group id="form-read-group">
          <b-form-checkbox-group v-model="addBookForm.read" id="form-checks">
            <b-form-checkbox value="true">Read?</b-form-checkbox>
          </b-form-checkbox-group>
        </b-form-group>
        <b-button type="submit" variant="primary">Submit</b-button>
        <b-button type="reset" variant="danger">Reset</b-button>
      </b-form>
    </b-modal>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      books: [],
      addBookForm: {
        title: '',
        author: '',
        read: [],
      },
    };
  },
  methods: {
    getBooks() {
      const path = 'http://localhost:5000/books';
      axios.get(path)
        .then((res) => {
          this.books = res.data.books;
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        });
    },
    addBook(payload) {
      const path = 'http://localhost:5000/books';
      axios.post(path, payload)
        .then(() => {
          this.getBooks();
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.log(error);
          this.getBooks();
        });
    },
    initForm() {
      this.addBookForm.title = '';
      this.addBookForm.author = '';
      this.addBookForm.read = [];
    },
    onSubmit(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide();
      let read = false;
      if (this.addBookForm.read[0]) read = true;
      const payload = {
        title: this.addBookForm.title,
        author: this.addBookForm.author,
        read, // property shorthand
      };
      this.addBook(payload);
      this.initForm();
    },
    onReset(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide();
      this.initForm();
    },
  },
  created() {
    this.getBooks();
  },
};
</script>
複製程式碼

趕緊測試一下!試著新增一本書:

add new book

alert 元件

接下來,讓我們新增一個 Alert 元件,當新增一本新書後,它會顯示一個資訊給當前使用者。我們將為此建立一個新元件,因為你以後可能會在很多元件中經常用到這個功能。

新增一個新檔案 Alert.vue 到 “client/src/components” 中:

<template>
  <p>It works!</p>
</template>
複製程式碼

然後,在 Books 元件的 script 中引入它並註冊這個元件:

<script>
import axios from 'axios';
import Alert from './Alert';

...

export default {
  data() {
    return {
      books: [],
      addBookForm: {
        title: '',
        author: '',
        read: [],
      },
    };
  },
  components: {
    alert: Alert,
  },

  ...

};
</script>
複製程式碼

現在,我們可以在 template 中引用這個新元件:

<template>
  <b-container>
    <b-row>
      <b-col col sm="10">
        <h1>Books</h1>
        <hr><br><br>
        <alert></alert>
        <button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>

        ...

      </b-col>
    </b-row>
  </b-container>
</template>
複製程式碼

重新整理瀏覽器,你會看到:

bootstrap alert

從 Vue 官方文件的 元件化應用構建 中獲得更多有關元件化應用構建的資訊。

接下來,讓我們加入 b-alert 元件到 template 中:

<template>
  <div>
    <b-alert variant="success" show>{{ message }}</b-alert>
    <br>
  </div>
</template>

<script>
export default {
  props: ['message'],
};
</script>
複製程式碼

記住 script 中的 props 選項。我們可以從父元件(Books)傳遞資訊,就像這樣:

<alert message="hi"></alert>
複製程式碼

試試這個:

bootstrap alert

文件 中獲取更多 props 相關資訊。

為了方便我們動態傳遞自定義訊息,我們需要在 Books.vue 中使用 bind 繫結資料。

<alert :message="message"></alert>
複製程式碼

message 新增到 Books.vue 中的 data 中:

data() {
  return {
    books: [],
    addBookForm: {
      title: '',
      author: '',
      read: [],
    },
    message: '',
  };
},
複製程式碼

接下來,在 addBook 中,更新 message 內容。

addBook(payload) {
  const path = 'http://localhost:5000/books';
  axios.post(path, payload)
    .then(() => {
      this.getBooks();
      this.message = 'Book added!';
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.log(error);
      this.getBooks();
    });
},
複製程式碼

最後,新增一個 v-if,以保證只有 showMessage 值為 true 的時候警告才會顯示。

<alert :message=message v-if="showMessage"></alert>
複製程式碼

新增 showMessagedata 中:

data() {
  return {
    books: [],
    addBookForm: {
      title: '',
      author: '',
      read: [],
    },
    message: '',
    showMessage: false,
  };
},
複製程式碼

再次更新 addBook,設定 showMessage 的值為 true

addBook(payload) {
  const path = 'http://localhost:5000/books';
  axios.post(path, payload)
    .then(() => {
      this.getBooks();
      this.message = 'Book added!';
      this.showMessage = true;
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.log(error);
      this.getBooks();
    });
},
複製程式碼

趕快測試一下吧!

add new book

挑戰:

  1. 想想什麼情況下 showMessage 應該被設定為 false。更新你的程式碼。
  2. 試著用 Alert 元件去顯示錯誤資訊。
  3. 修改 Alert 為 可取消 的樣式。

PUT 路由

服務端

對於更新,我們需要使用唯一識別符號,因為我們不能依靠標題作為唯一。我們可以使用 Python 基本庫 提供的 uuid 作為唯一。

server/app.py 中更新 BOOKS

BOOKS = [
    {
        'id': uuid.uuid4().hex,
        'title': 'On the Road',
        'author': 'Jack Kerouac',
        'read': True
    },
    {
        'id': uuid.uuid4().hex,
        'title': 'Harry Potter and the Philosopher\'s Stone',
        'author': 'J. K. Rowling',
        'read': False
    },
    {
        'id': uuid.uuid4().hex,
        'title': 'Green Eggs and Ham',
        'author': 'Dr. Seuss',
        'read': True
    }
]
複製程式碼

不要忘了引入:

import uuid
複製程式碼

我們需要重構 all_books 來保證每一本新增的書都有它的唯一 ID:

@app.route('/books', methods=['GET', 'POST'])
def all_books():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        BOOKS.append({
            'id': uuid.uuid4().hex,
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'read': post_data.get('read')
        })
        response_object['message'] = 'Book added!'
    else:
        response_object['books'] = BOOKS
    return jsonify(response_object)
複製程式碼

新增一個新的路由:

@app.route(
            
           

相關推薦

[] Flask Vue.js 開發一個頁面應用

原文地址:Developing a Single Page App with Flask and Vue.js 原文作者:Michael Herman 譯文出自:掘金翻譯計劃 本文永久連結:github.com/xitu/gold-m… 譯者:Mcskiller

基於laravel5.4 vue vue-element搭建的頁面後臺CMS

data pos dev https art .sql blog -s sql 介紹 該項目後臺是基於vue和laravel搭建的單頁面CMS系統,包含了文章管理,權限管理,用戶管理等基本模塊。 前臺使用了傳統web技術,laravel渲染搭建了個博客系統 githu

一個例子入門Vue2.X+vue-router+Vuex+Webpack頁面應用程式

本篇博文講解如何使用Vue2.X+vue-router+VueX+Webpack實現一個模組化的單頁面應用程式,新手向。 1.功能實現 使用Vue2.X的理由是它屬於輕量級的JS庫,對於流量敏感的移動端來說更友好;容易上手,具有完備的中文文件,學習曲線較

【前端框架】Backbone.js在大型頁面應用中的應用實踐

Backbone.js是什麼? Backbone.js是一個JavaScript MVC框架,提供了良好的程式碼組織能力,可以方便地將應用程式解耦成可以複用的部分,為建立大型的單頁面應用提供框架支援,目前的版本是0.9.10(注:現在已到1.2.1版本)。通過將應用程式分解成MVC模式中不同職責的模組,

通過Blazor使用C#開發SPA頁面應用程式(3)

  今天我們來看看Blazor開發的一些基本知識。 一、Blazor元件結構  Blazor中元件的基本結構可以分為3個部分,如下所示: //Counter.razor //Directives section 指令部分 @page "/counter" //Ra

通過Blazor使用C#開發SPA頁面應用程式(4) - Ant Design Button

  前面學習了Blazor的特點、環境搭建及基礎知識,現在我們嘗試的做個實際的元件。   Ant Design是螞蟻金服是基於Ant Design設計體系的 UI 元件庫,主要用於研發企業級中後臺產品。目前官方是基於React和Angular實現的,今年也推出了Vue的實現。其元件涵蓋面

vue.jsvue-router重構一個網站

拿了個以前的專案做練手,用了vue-cli,vue-router,vuex。主要還是想學習一下webpack和單檔案元件的思想。不過程式碼寫得其實挺匆忙,感覺還有很多地方還能改進,元件化思想還是不能很好的掌握。 github地址:https://github.

vue.js開發環境搭建以及創建一個vue實例

init 技術分享 自動 安裝失敗 das 命令行 環境搭建 項目 向上 Vue.js 是一套構建用戶界面的漸進式框架。Vue 只關註視圖層, 采用自底向上增量開發的設計。Vue 的目標是通過盡可能簡單的 API 實現響應的數據綁定和組合的視圖組件。 在使用 vue.js

Vue.js搭建一個小說閱讀網站

1.簡介 這是一個使用vue.js + mint-ui + .net core api的小說網站。 最近在學習vue.js,而拋開實踐的學習都是在裝逼,所以結合實際,準備做一個小說網站,這樣麻麻再也不用擔心我在看小說時被不良資訊侵擾了哈哈。 首先說明一下,小說資料來源於網路搜尋,並不直接儲存於自己的伺服

僅此記錄巧vue.js一個頁面

  <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="ie=edge

uni-app 是一個使用 Vue.js 開發跨平臺應用的前端框架,開發者編寫一套程式碼,可編譯到iOS、Android、微信小程式等多個平臺。

uni-app 是一個使用 Vue.js 開發跨平臺應用的前端框架,開發者編寫一套程式碼,可編譯到iOS、Android、微信小程式等多個平臺。 uni-app在跨端數量、擴充套件能力、效能體

vue.js一個級聯聯動效果

<!DOCTYPE html><html><head><meta charset="utf-8" /><title>vue.js級聯</title><script src="js/vue.js"&g

美團開源 Vue.js 開發小程式的前端框架 mpvue

https://mp.weixin.qq.com/s/trz-nTuEv1jibj38BVeyvg開源最前線(ID:OpenSourceTop) 猿妹 編譯素材來源:http://mpvue.com美團開源的mpvue引起了不少前端er們的注意。我們來介紹一款由美團點評研發,

vue.js開發環境搭建

回車 try htm 成功 效果 webpack log 表示 測試 1、安裝node.js,忽略 2、基於node.js,利用淘寶npm鏡像安裝相關依賴在cmd裏直接輸入:npm install -g cnpm –-registry=https://regi

Vue.js 開發實踐:實現精巧的無限加載與分頁功能

cti head 設定 命令 webpack transform style time default https://segmentfault.com/a/1190000005351971#articleHeader9 本篇文章是一篇Vue.js的教程,目標在於用一

vue.js開發環境初步搭建、腳手架工具安裝(node.js安裝)

環境搭建 ima pil utf node.js jni 可用 turn t430 當然,首先是node.js的安裝,百度node,js出現 (為後面的鋪墊) 選擇一個版本進行安裝,安裝完成後,在cmd命令行中輸入node --version(註意有兩個 --)查看

使用VS2017開發APP中使用VUE.js開發遇到打包出來的android文件 在低版本的android(4.3)中無法正常使用

vue.js 文件 默認 項目 let ons dir file 開發app 使用VS2017開發VUE的APP應用遇到的問題集合 1, 打包出來的apk文件在Android 6.0版本以上手機可以正常打開,在Android 4.3版本手機上無法打開 原因:一開

30行代碼開發一個上傳、下載文件的接口

run 接口 isf urn load() p s route 保存文件 for 分享一段代碼,開發了3個接口: 1、上傳文件 2、查看所有文件 3、下載文件 使用python開發,需要安裝fla

在window下搭建Vue.Js開發環境(轉)

圖片 需要 int alt first .html ron 接下來 post nodejs官網http://nodejs.cn/下載安裝包,無特殊要求可本地傻瓜式安裝,這裏選擇2017-5-2發布的 v6.10.3 cmd命令行: node -v //顯示

CORS 解決vue.js django跨域調

監聽 網頁設計 訪問 eth 調用 http ati rsh 要求 Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是 JSONP 模式的現代版