Vue 初心者筆記 #37 後台的上架產品列表

完成登入 & 登出功能後,我們要來製作後台的產品列表頁面。本文將介紹如何透過 Table + Modal 來把資料送回 API 更新與儲存。

ProductList

產品列表篇

  • 使用 Bootstrap Tables 製作產品列表
  • 使用 Bootstrap Modal 完成新增、修改、刪除產品等功能

關於 Modal 的內容,這裡是直接使用六角學院提供的模板

1. 製作產品列表 (Tables)

限制表格寬度

使用 Boostrap Tables 製作產品列表時,可以只調整 “要限制寬度” 的 th,剩下的 th 會自動調整。
以這邊來說,產品名稱最需要空間,所以其他部分都限制寬度,把最多的剩餘空間通通給產品名稱。

1
2
3
4
5
6
7
8
<thead>
<th width="100">分類</th>
<th>產品名稱</th>
<th width="120">原價</th>
<th width="120">售價</th>
<th width="100">是否啟用</th>
<th width="120">編輯</th>
</thead>

使用 v-for 製作 tr

使用 v-for 時一律都建議加上唯一的 key 值。

1
2
3
4
<tbody>
<tr v-for="(item) in products" :key="item.id">
</tr>
</tbody>

getProducts 事件與 init 初始化

透過 getProducts() 事件,從資料庫取得產品資料,再把資料呈現於畫面上。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 取得產品資料
getProducts() {
// 加上 admin 才是管理者使用的
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/products`;
const vm = this;
console.log(process.env.APIPATH, process.env.CUSTOMPATH);
this.$http.get(api).then(response => {
console.log(response.data);
// 存回 vm.products
vm.products = response.data.products;
console.log(vm.products);
});
},

接著再加上 created 的 Hook,讓網頁自動觸發 getProducts 事件,達到初始化 (init) 的效果。

1
2
3
created() {
this.getProducts();
}

2. 新增、編輯產品 (Modal)

在開始製作前,要先在 data 中新增 tempProduct 綁定所有的欄位後,用 POSTtempProduct 裡的資料新增到資料庫,這樣才能與資料庫的資料同步更新。

productModal

tempProduct 的資料與 Modal 裡的各個輸入欄位做 v-model 綁定。
大致上會有以下幾種欄位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 圖片網址 (input)
v-model="tempProduct.imageUrl"
// 圖片 (img)
:src="tempProduct.imageUrl"
// 標題 (input)
v-model="tempProduct.title"
// 分類 (input)
v-model="tempProduct.category"
// 單位 (input)
v-model="tempProduct.unit"
// 原價 (input)
v-model="tempProduct.origin_price"
// 售價 (input)
v-model="tempProduct.price"
// 產品描述 (textarea)
v-model="tempProduct.description"
// 說明內容 (textarea)
v-model="tempProduct.content"
// 是否啟用 (checkbox)
v-model="tempProduct.is_enabled"
:true-value="1" // 產品如果為啟用:is_enabled == 1
:false-value="0"

Button trigger productModal

原本畫面上的 Button 是透過 data-toggle="modal" data-target="#productModal" 來打開 Modal,
但是這裡要改為使用我們自訂的 Method openModal 來打開 Modal。

元件頁面要記得 import jQuery:import $ from "jquery"

1
2
3
4
<!-- 建立新商品 Button -->
<button class="btn btn-primary" @click="openModal(true)">建立新產品</button>
<!-- 編輯 Button -->
<button class="btn btn-sm btn-outline-primary" @click="openModal(false, item)">編輯</button>

openModal 事件

  • 按下按鈕後,等 AJAX 完成才開啟 Modal
  • 透過 .modal(‘show’) 開啟 Modal:$("#productModal").modal("show")
  • 新舊判斷:決定開啟的 Modal 是新增還是編輯功能

透過 isNew 判斷 openModal 事件是要建立新商品,還是編輯舊的商品。
若為新增,就會將 tempProduct 清空,以便新增資料到資料庫。
若為編輯,則將該 item 的值寫給 tempProduct,待編輯後新增至資料庫。

1
2
3
4
5
6
7
8
9
10
11
12
13
openModal(isNew, item) {
// 新舊判斷
if (isNew) {
// 如果是新增
this.tempProduct = {}; // tempProduct = 空物件
this.isNew = true; // 代表是"新的"
} else {
// this.tempProduct = item; // 物件傳參考特性
this.tempProduct = Object.assign({}, item); // (ES6) 將 item 的值寫到一個空物件 (而且可以避免傳參考的特性之問題)
this.isNew = false;
}
$("#productModal").modal("show"); // 延後到這裡才打開 Modal
},

為避免物件傳參考的特性,這裡使用了 ES6 的 Object.assign() 語法來複製物件

updateProduct 事件

最後,當我們按下 productModal 裡的”確認”按鈕時,就會觸發 updateProduct 事件。

1
<button type="button" class="btn btn-primary" @click="updateProduct">確認</button>

如同 openModal 事件,這邊也會做新舊判斷!
關閉 Modal 的方式也改用 .modal(‘hide’) 方法,而非原先 Button 上的 data-dismiss 屬性。

  • 新增與編輯的 API 不同
  • 新增產品的 HTTP 行為是 post,編輯是用 put
  • 編輯產品時,tempProduct 要符合 API 規範的格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
updateProduct() {
// 商品建立
let api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/product`;
const vm = this;

let httpMethod = "post";
if (!vm.isNew) {
// 如果不是新的,是"修改",就改 api
api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/product/${vm.tempProduct.id}`; // :id => ${vm.tempProduct.id}
// HTTP 行為也要改為 put
httpMethod = "put";
}

console.log(process.env.APIPATH, process.env.CUSTOMPATH);
// 符合格式 data : {...}
this.$http[httpMethod](api, { data: vm.tempProduct }).then(response => {
console.log(response.data);
if (response.data.success) {
// 如果新增成功,就把 Modal 關閉
$("#productModal").modal("hide");
// 並且再重新取得一次遠端的資料 (更新畫面)
vm.getProducts();
} else {
// 如果新增失敗,做一樣的動作,但是再補上 console.log
$("#productModal").modal("hide");
vm.getProducts();
console.log("新增失敗");
}
});
},

3. 刪除產品 (Modal)

最後剩下刪除產品的部分了,這裡跟新增、編輯產品很類似,也是使用 Button 配上 Modal,再觸發刪除的事件來完成整個功能。

Button trigger delProductModal

點擊產品列表中的刪除按鈕,觸發 openDelModal 事件來打開 delProductModal。

1
<button class="btn btn-sm btn-outline-danger" @click="openDelModal(item)">刪除</button>

openDelModal 事件

要特別注意,刪除產品這邊只要用 this.tempProduct = item 即可。

1
2
3
4
openDelModal(item) {
this.tempProduct = item;
$("#delProductModal").modal("show");
},

之前在編輯產品那邊,之所以會寫 this.tempProduct = Object.assign({}, item)
是因為將 item 的值寫到空物件裡面,可以避免 this.tempProductitem 之間的傳參考特性,
所以才會使用 ES6 的 Object.assign({}, item)

delProductModal

打開刪除 Modal 後,會詢問是否要刪除,點擊確認刪除就會觸發 deleteProduct 事件。

1
<button type="button" class="btn btn-danger" @click="deleteProduct">確認刪除</button>

deleteProduct 事件

deleteProduct() 是透過 API 路徑裡的產品 ID,即 ${vm.tempProduct.id},來判斷要刪除的產品是哪一個。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
deleteProduct() {
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/product/${vm.tempProduct.id}`;
console.log(process.env.APIPATH, process.env.CUSTOMPATH);
this.$http.delete(api).then(response => {
console.log(response.data);
if (response.data.success) {
// 如果刪除成功,就把 Modal 關閉,並更新遠端資料與畫面
$("#delProductModal").modal("hide");
vm.getProducts();
} else {
// 如果刪除失敗,一樣關閉 Modal 與更新畫面,但是再補上 console.log
$("#delProductModal").modal("hide");
vm.getProducts();
console.log("刪除失敗");
}
});
}

完成上述的三個功能,大概就完成產品列表的部分囉!

後記:HTTP Method 整理

我們常見的 HTTP Method 就是 getpost,而這次製作編輯產品功能時是使用 put,製作刪除功能時則是使用到了 delete

在本文的最後,我們就來整理一下這次用到的這四種 HTTP Method 吧!

其實有很多種 Method,但其中有六種是與網頁資料有關的 HTTP Method,分別是:headgetpostdeleteputpatch

  • get:取得想要的資料
  • post:新增一項資料,如果已存在,會新增一個新的資料(結果總共會有兩筆資料)
  • put:新增一項資料,如果已存在,會直接覆蓋過去(結果仍然只有一筆資料)
  • delete:刪除資料

所以不同的 Method 會對同一件事情做不同的操作,我們再以本文中的各種針對產品的操作功能為例:

  • get:取得產品列表
  • post:新增產品
  • put:編輯產品
  • delete:刪除產品

以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫。