# 13. 通訊錄 手作 API 2

# 載入 cdn

  • Vue
  • Axios

# 建立 Vue 實體

使用立即函式封裝,指派 el 並建立 data

// 我的寫法
(function(){
    let vm = new Vue({
        el: '#app',
        data: {
            contacts: [],
            input: {
                name: '',
                email: ''
            }
        }
    })
})()

// Alex 的寫法
;(function(Vue){
    new Vue({
        el: '#app',
        data: {
            contacts: [],
            input: {
                name: '',
                email: ''
            }
        }
    })
})(Vue)

# 取得資料 (GET)

# 在 mounted 階段取得資料

以我的寫法為例:

  • mounted 階段,讓網頁 DOM 掛載完成後開始執行動作。
  • 顯示 loading 畫面
    • 使用變數 loadingv-ifv-else 控制畫面。
    • AJAX 尚未載入完成時, loading = true 顯示 loading 畫面。
  • 運作 JSON Server
    • 執行 npm run json 來運作 JSON Server,得到資料庫路徑。
    • 如果沒有執行 npm run json ,瀏覽器會一直在 loading 畫面。
  • 取得 JSON 資料
    • 使用 axios.get("資料庫位置") 取得 JSON 資料。
    • 在成功取得資料後,關掉 loading 畫面 (loading = false)。

HTML

<div id="app">
  <!-- 尚未載入完成時顯示的畫面 -->
  <section v-if="loading">
    Loading...
  </section>
  
  <!-- 載入完成後顯示的畫面 -->
  <section v-else>
    // 主要程式碼
  </section>
</div>

JS

// 我的寫法
(function(){
    let vm = new Vue({
        el: '#app',
        data: {
            contacts: [],
            input: {
                name: '',
                email: ''
            },
            loading: false  // 控制 loading 畫面切換
        },
        // DOM 掛載完之後取得資料並關閉 loading 畫面
        mounted(){
            this.loading = true
            axios
                .get('http://localhost:8888/contact')
                .then((res) => {
                    this.contacts = res.data
                    this.loading = false
                })
                .catch((err) => {
                    console.log(err)
                })
        }
    })
})()

# 將取得的資料放到網頁上

把透過 AJAX 取得的資料取代假資料顯示到網頁上。

  • 雙向綁定輸入框與資料
    • inputv-model 綁定輸入欄位。
    • 使用修飾詞 .trim 去除頭尾空白。
  • 陳列聯絡人資訊
    • contactsv-for 列出資料庫中的聯絡人資訊。
<section v-else>
    <div class="container">
      <div class="title">
        <div class="input">
          <input type="text" placeholder="會員名稱" v-model.trim="input.name" />
          <input type="text" placeholder="電子信箱" v-model.trim="input.email" />
          <button>送出</button>
          <button>取消</button>
        </div>
        <div class="menu">
          <div class="menuItem" v-for="(item,index) in contacts">
            <span class="number">{{ index + 1 }}</span>
            <span class="type">{{ item.name }}</span>
            <a :href="'mailto:'+item.email" class="title">{{ item.email }}</a>
            <span class="edit">修改</span>
            <span class="delete">刪除</span>
          </div>
        </div>
      </div>
    </div>
</section>

# 新增資料 (POST)

# 綁定點擊事件

在輸入資料的區塊,將「送出」和「取消」按鈕各自綁定點擊事件。

<div class="input">
  <input
    type="text"
    placeholder="會員名稱"
    v-model.trim="input.name"
  />
  <input
    type="text"
    placeholder="電子信箱"
    v-model.trim="input.email"
  />
  <button @click="submitHandler">送出</button>
  <button @click="cancelHandler">取消</button>
</div>

# 撰寫事件邏輯進行處理

然後到 Vue 裡面用 methods 來進行處理。

  • 取消:把 input 內容清空。
  • 送出:將 input 的內容加到資料庫。
    • 如果 nameemail 任意欄位空白,則跳出不執行。
    • 使用 loading 頁面。
    • 使用 axios.post(資料庫位置, 要新增的資料) 將資料新增到資料庫。
      • 把這筆資料加到 contacts 的陣列中。
      • 執行 cancelHandler 來清空 input
      • 若有使用 loading 頁面,記得要切換回來。
methods: {
    // 送出
    submitHandler(){
        let {name, email} = this.input
        if(!name || !email) return
        this.loading = true
        axios
            .post('http://localhost:8888/contact', this.input)
            .then((res) => {
                this.contacts.push(res.data)
                this.cancelHandler()
                this.loading = false
            })
            .catch((err) => {
                console.log(err)
            })
    },
    // 取消
    cancelHandler(){
        // 寫法一
        this.input.name = '',
        this.input.email = ''
        // 寫法二
        this.input = {
            name: '',
            email: ''
        }
    }
}

# 刪除資料 (DELETE)

# 綁定事件

在「刪除」按鈕綁上用來刪除的點擊事件,並且傳入那一項在陣列中的索引值,以判斷要刪除的是哪一筆資料。

<span class="delete" @click="deleteHandler(index)">刪除</span>

# 撰寫事件邏輯刪除資料

然後到 Vue 裡面用 methods 來進行處理。

  • 使用 loading 頁面。
  • 使用 confirm() 讓使用者再次確認,以免誤刪。
  • 使用 axios.delete(該筆資料位置) 將該筆資料從資料庫中刪除。
    • 畫面上也要用 splice() 將該筆資料從 contacts 陣列中刪除。
    • 執行 cancelHandler 來清空 input。以防按了「修改」後又按了「刪除」,結果被刪的資料還留在 input 裡面。
    • 若有使用 loading 頁面,記得要切換。
methods: {
    deleteHandler(index){
        this.loading = true
        if(confirm(`是否刪除 ${this.contacts[index].name}`)){
            axios
                .delete('http://localhost:8888/contact/' + this.contacts[index].id])
                .then((res) => {
                    this.contacts.splice(index, 1)
                    this.cancelHandler()
                    this.loading = false
                })
                .catch((err) => {
                    console.log(err)
                })
        } else {
            this.loading = false
        }
    }
}

# 修改資料 (PUT)

修改資料時,流程如下:

  • 點擊某筆資料的「修改」按鈕。
  • 將該筆資料的內容放到對應的 input 欄位。
  • 按下「送出」按鈕修改資料庫和畫面。

# 點擊某筆資料的「修改」按鈕

在「修改」按鈕綁上用來修改的點擊事件,並且傳入那一項在陣列中的索引值,以判斷要修改的是哪一筆資料。

<span class="edit" @click="editHandler(index)">修改</span>

# 將該筆資料的內容放到對應的 input 欄位。

然後到 Vue 裡面用 methods 來進行處理。

  • 判斷被點擊「修改」按鈕的是 contacts 陣列中的哪一筆資料,然後將資料放到對應的 input 位置。
  • 由於新增跟修改同樣要使用「送出」按鈕,為了辨別是要新增還是修改資料,建立變數 editIndex 並預設為 null
    • 當資料被點擊「修改」按鈕後,editIndex 的值為該資料的 id
    • 若修改過程中反悔想取消,按下「取消」按鈕時,也要讓 editIndex 回歸到 null
    • 若修改過程中刪除了資料,一樣要清空input ,並且 editIndex 要回到 null
data: {
    editIndex: null
},
methods: {
    editHandler(index){
        let {name, email} = this.contacts[index]
        this.input = {name, email}
        this.editIndex = index
    },
    cancelHandler(){
        this.editIndex = null
        this.input.name = '',
        this.input.email = ''
    }
}

# 按下「送出」按鈕修改資料庫和畫面

由於修改和新增共用同一個按鈕,因此當「送出」按鈕被點擊時,原本的 submitHandler 函式要先進行判斷,再執行不同的工作。

  • editIndexnull 時,表示按下「送出」是要新增資料。
  • editIndex 為陣列中的索引值 (index) 時,則修改該筆資料。
    • 使用 axios.put(該筆資料位置, 要修改的資料) 修改資料庫中指定資料的內容。
      • 畫面上該筆資料原本的內容,會被修改後的內容取代。
      • 執行 cancelHandler 來清空 input
      • 若有使用 loading 頁面,記得要切換。
methods: {
    submitHandler(){
        let {name, email} = this.input
        if(!name || !email) return
        this.loading = true
        // 使用 editIndex 來區別要新增或修改
        if(this.editIndex == null) {
            axios
                .post('http://localhost:8888/contact', this.input)
                .then((res) => {
                    this.contacts.push(res.data)
                    this.cancelHandler()
                    this.loading = false
                })
                .catch((err) => {
                    console.log(err)
                })
        } else {
            axios
                .put('http://localhost:8888/contact/' + this.contacts[this.editIndex].id, this.input)
                .then((res) => {
                    this.contacts[this.editIndex] = res.data
                    this.cancelHandler()
                    this.loading = false
                })
                .catch((err) => {
                    console.log(err)
                })
        }
    }
}

# 小結

  • GET 資料
    • mounted 階段
    • 使用 GET 方法,取得資料後存到陣列,並切換 loading 。
  mounted() {
    console.log('Vue in mounted...');
    this.loading = true;
    axios
      .get('http://localhost:8888/contact')
      .then((res) => {
        this.contacts = res.data;
        this.loading = false;
      })
      .catch((err) => {
        console.log(err);
      });
  },
  • POST 資料
    • 取消:清空輸入框
    • 新增:
      • 使用 POST 推送資料到伺服器。
      • 將資料用 .push() 加到陣列、清空輸入框、切換 loading 狀態。
  • DELETE 資料
    • 先判斷要刪除哪一筆,再使用 DELETE 刪除指定資料。
    • 將資料用 .splice() 刪除、清空輸入框、切換 loading 狀態。
  • PUT 資料
    • 先判斷要修改哪一筆,將該筆資料內容放到對應的輸入框。
    • 由於「新增」與「修改」共用同一個「送出」按鈕,因此需先判斷行為。
    • 建立變數 editIndex 幫助判斷行為。
      • 如果要修改,則值為該筆資料的 index,執行 PUT。
      • 若為非修改、取消修改、修改中刪除資料,則值為 null,執行 POST。
  data: {
    contacts: [],
    input: {
      name: '',
      email: '',
    },
    loading: false,
    editIndex: null,
  },
  methods: {
    submitHandler() {
      let { name, email } = this.input;
      if (!name || !email) return;
      this.loading = true;
      if (this.editIndex == null) {
        axios
          .post('http://localhost:8888/contact', { name, email })
          .then((res) => {
            this.contacts.push(res.data);
            this.cancelHandler();
            this.loading = false;
          })
          .catch((err) => {
            console.log(err);
          });
      } else {
        axios
          .put(
            'http://localhost:8888/contact/' +
              this.contacts[this.editIndex].id,
            this.input
          )
          .then((res) => {
            this.contacts[this.editIndex] = res.data;
            this.cancelHandler();
            this.loading = false;
          })
          .catch((err) => {
            console.log(err);
          });
      }
    },
    cancelHandler() {
      this.editIndex = null;
      this.input.name = '';
      this.input.email = '';
    },
    editHandler(index) {
      let { name, email } = this.contacts[index];
      this.input = { name, email };
      this.editIndex = index;
    },
    deleteHandler(index) {
      this.loading = true;
      if (confirm(`是否刪除 ${this.contacts[index].name} ?`)) {
        axios
          .delete(
            'http://localhost:8888/contact/' + this.contacts[index].id
          )
          .then((res) => {
            this.contacts.splice(index, 1);
            this.cancelHandler();
            this.loading = false;
          })
          .catch((err) => {
            console.log(err);
          });
      } else {
        this.loading = false;
      }
    },
  }
});