[Express+Vue 搭建電商網站] 21 加入載入動畫

21 加入載入動畫

加入載入動畫

等待的時候很無聊,所以我們加點動畫。
基本的動畫是用來告訴使用者動作執行結果,這樣才知道自己剛剛做的事情有沒有完成

ManufactureForm 組件

在這個組件中,會在使用者新建或是修改製造商資訊後,當後端完成處理前出現 Loading 的動態效果

<template>
 <div class="manufacturerInfo">
 <el-form
 class="form"
 ref="form"
 label-width="180px"
 v-loading="loading"
 element-loading-text="Loading..."
 element-loading-spinner="el-icon-loading"
 element-loading-background="rgba(0, 0, 0, 0.8)"
 >
 <el-form-item label="Name">
 <el-input v-model="manufacturerData.name"></el-input>
 </el-form-item>
 <el-form-item>
 <el-button
 v-if="isEditing"
 type="primary"
 native-type="submit"
 @click="onSubmit"
 >Update Manufacturer</el-button>
 <el-button v-else @click="onSubmit">Add Manufacturer</el-button>
 </el-form-item>
 </el-form>
 </div>
</template>
<script>
export default {
 props: ["model", "isEditing"],
 data() {
 return {
 manufacturerData: { name: "" }
 };
 },
 created() {
 this.manufacturerData = this.model;
 },
 watch: {
 model(val) {
 this.manufacturerData = val;
 }
 },
 computed: {
 loading() {
 return this.$store.state.showLoader;
 }
 },
 methods: {
 onSubmit() {
 this.$emit("save-manufacturer", this.manufacturerData);
 }
 }
};
</script>

可以看到我們使用了 element-ui 组件庫提供的指令 v-loading 來判斷 loading 是否為真來決定是否要顯示載入動畫,而 loading 這個 computed 屬性使用 store.state.showLoader 的資料。
同時也把當初在 ProductForm 中解決過「無法修改標單內容」的問題用同樣方法解決了。

ProductForm 組件

當然啦,製造商表單的效果在 ProductForm 中應該也要有。接著就打開 ProductForm 組件進行編輯

<template>
 <div class="productInfo">
 <el-form
 class="form"
 ref="form"
 label-width="180px"
 v-loading="loading"
 element-loading-text="Loading..."
 element-loading-spinner="el-icon-loading"
 element-loading-background="rgba(0, 0, 0, 0.8)"
 >
 <el-form-item label="Name">
 <el-input v-model="modelData.name"></el-input>
 </el-form-item>
 <el-form-item label="Price">
 <el-input v-model="modelData.price"></el-input>
 </el-form-item>
 <el-form-item label="Manufacturer ">
 <el-select v-model="modelData.manufacturer.name" clearable placeholder="請選擇製造商">
 <el-option
 v-for="manufacturer in manufacturers"
 :key="manufacturer._id"
 :label="manufacturer.name"
 :value="manufacturer.name"
 ></el-option>
 </el-select>
 </el-form-item>
 <el-form-item label="Image ">
 <el-input v-model="modelData.image"></el-input>
 </el-form-item>
 <el-form-item label="Description ">
 <el-input type="textarea" v-model="modelData.description"></el-input>
 </el-form-item>
 <el-form-item>
 <el-button
 v-if="isEditing"
 type="primary"
 native-type="submit"
 @click="onSubmit"
 >Update Product</el-button>
 <el-button v-else @click="onSubmit">Add Product</el-button>
 </el-form-item>
 </el-form>
 </div>
</template>
<script>
export default {
 data() {
 return {
 modelData: { manufacturer: { name: "" } }
 };
 },
 props: ["model", "manufacturers", "isEditing"],
 created() {
 const product = this.model;
 this.modelData = { ...product, manufacturer: { ...product.manufacturer } };
 },
 watch: {
 model(val) {
 this.modelData = val;
 }
 },
 computed: {
 loading() {
 return this.$store.state.showLoader;
 }
 },
 methods: {
 onSubmit() {
 // 表單中只有 modelData.manufacturer.name,但後端需要整個製造商物件,所以要找出對應的製造商物件寫入到 modelData 中
 const manufacturer = this.manufacturers.find(
 item => item.name === this.modelData.manufacturer.name
 );
 this.modelData.manufacturer = manufacturer;
 this.$emit("save-product", this.modelData);
 }
 }
};
</script>
<style>
.productInfo {
 padding-top: 10px;
}
.form {
 margin: 0 auto;
 width: 500px;
}
.el-input__inner {
 height: 60px;
}
</style>

在這個組件中我們也加入了 loading() 這個 computed 屬性

實現提示功能

在組件中加入功能後,我們要實際把提示功能完成
首先打開 src/store/actions.js 加入提示訊息的功能

import axios from 'axios';
import { Message } from 'element-ui';
import {
 ADD_PRODUCT,
 ADD_PRODUCT_SUCCESS,
 PRODUCT_BY_ID,
 PRODUCT_BY_ID_SUCCESS,
 UPDATE_PRODUCT,
 UPDATE_PRODUCT_SUCCESS,
 REMOVE_PRODUCT,
 REMOVE_PRODUCT_SUCCESS,
 ALL_PRODUCTS,
 ALL_PRODUCTS_SUCCESS,
 ALL_MANUFACTURERS,
 ALL_MANUFACTURERS_SUCCESS,
 MANUFACTURER_BY_ID,
 MANUFACTURER_BY_ID_SUCCESS,
 ADD_MANUFACTURER,
 ADD_MANUFACTURER_SUCCESS,
 UPDATE_MANUFACTURER,
 UPDATE_MANUFACTURER_SUCCESS,
 REMOVE_MANUFACTURER,
 REMOVE_MANUFACTURER_SUCCESS,
} from './mutation-types';
const API_BASE = 'http://localhost:3000/api/v1';
export const productActions = {
 allProducts({ commit }) {
 commit(ALL_PRODUCTS)
 axios.get(`${API_BASE}/products`).then(response => {
 commit(ALL_PRODUCTS_SUCCESS, {
 products: response.data,
 });
 })
 },
 productById({ commit }, payload) {
 commit(PRODUCT_BY_ID);
 const { productId } = payload;
 axios.get(`${API_BASE}/products/${productId}`).then(response => {
 commit(PRODUCT_BY_ID_SUCCESS, {
 product: response.data,
 });
 })
 },
 removeProduct({ commit }, payload) {
 commit(REMOVE_PRODUCT);
 const { productId } = payload;
 axios.delete(`${API_BASE}/products/${productId}`)
 .then(() => {
 // 回傳 productId,用來刪除對應商品
 commit(REMOVE_PRODUCT_SUCCESS, {
 productId,
 });
 Message({
 message: '產品刪除完成',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('產品刪除失敗');
 })
 },
 updateProduct({ commit }, payload) {
 commit(UPDATE_PRODUCT);
 const { product } = payload;
 axios.put(`${API_BASE}/products/${product._id}`, product)
 .then(response => {
 commit(UPDATE_PRODUCT_SUCCESS, {
 product: response.data,
 });
 Message({
 message: '商品更新成功',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('商品更新失敗');
 })
 },
 addProduct({ commit }, payload) {
 commit(ADD_PRODUCT);
 const { product } = payload;
 axios.post(`${API_BASE}/products`, product)
 .then(response => {
 commit(ADD_PRODUCT_SUCCESS, {
 product: response.data,
 })
 Message({
 message: '已新建商品',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('商品建立失敗');
 })
 }
};
export const manufacturerActions = {
 allManufacturers({ commit }) {
 commit(ALL_MANUFACTURERS);
 axios.get(`${API_BASE}/manufacturers`).then(response => {
 commit(ALL_MANUFACTURERS_SUCCESS, {
 manufacturers: response.data,
 });
 })
 },
 manufacturerById({ commit }, payload) {
 commit(MANUFACTURER_BY_ID);
 const { manufacturerId } = payload;
 axios.get(`${API_BASE}/manufacturers/${manufacturerId}`).then(response => {
 commit(MANUFACTURER_BY_ID_SUCCESS, {
 manufacturer: response.data,
 });
 })
 },
 removeManufacturer({ commit }, payload) {
 commit(REMOVE_MANUFACTURER);
 const { manufacturerId } = payload;
 axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`)
 .then(() => {
 // 回傳 manufacturerId,用來刪除對應的製造商
 commit(REMOVE_MANUFACTURER_SUCCESS, {
 manufacturerId,
 });
 Message({
 message: '製造商刪除完成',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('製造商刪除完成失敗');
 })
 },
 updateManufacturer({ commit }, payload) {
 commit(UPDATE_MANUFACTURER);
 const { manufacturer } = payload;
 axios.put(`${API_BASE}/manufacturers/${manufacturer._id}`, manufacturer)
 .then(response => {
 commit(UPDATE_MANUFACTURER_SUCCESS, {
 manufacturer: response.data,
 });
 Message({
 message: '製造商更新完成',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('製造商更新失敗');
 })
 },
 addManufacturer({ commit }, payload) {
 commit(ADD_MANUFACTURER);
 const { manufacturer } = payload;
 axios.post(`${API_BASE}/manufacturers`, manufacturer)
 .then(response => {
 commit(ADD_MANUFACTURER_SUCCESS, {
 manufacturer: response.data,
 });
 Message({
 message: '製造商建立完成',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('製造商建立失敗');
 })
 }
}

首先導入了 element-ui 组件庫提供的 Message 提示訊息組件。
接著在各個操作中加入提示訊息的物件,成功或失敗都會回傳對應的訊息。
接著開啟 src/store/mutations.js 做部分內容修改,為的是修改購物車的提示訊息

export const cartMutations = {
 [ADD_TO_CART](state, payload) {
 const { product } = payload;
 state.cart.push(product);
 Message({
 message: '成功加入購物車',
 type: 'success'
 })
 },
 [REMOVE_FROM_CART](state, payload) {
 const { productId } = payload
 state.cart = state.cart.filter(product => product._id !== productId)
 Message({
 message: '已從購物車移除商品',
 type: 'success'
 })
 },
}

同樣導入了 element-ui 组件庫提供的 Message 提示訊息組件,當使用者加入或移除購物車商品時就會收到提示了!
結果看起來像這樣,十分酷炫有型
message-ui
重構到這邊,測試起來似乎又有點什麼問題。那就是在表單按下更新後,看到了更新成功的訊息,但畫面上的資料似乎沒有同步成最新的
當資料出現問題,應該依據 Vue 的單向資料流原則來修正。使用者更新資料後,應該從後端同步更新資料到狀態池中進行渲染,因此我們要修改的就是 src/store/actions.js 的內容。
可以大膽的猜測,是因為後端請求結束後 action 提交到 mutations.js 中的不是修改後的最新資料,所以才沒有改變狀態池中的物件
所以要來修改 src/store/actions.js 檔案中更新數據的部分
兩個方法在不同的常數中,但為了節省版面就只展示其中關鍵的方法

updateProduct({ commit }, payload) {
 commit(UPDATE_PRODUCT);
 const { product } = payload;
 axios.put(`${API_BASE}/products/${product._id}`, product)
 .then(() => {
 commit(UPDATE_PRODUCT_SUCCESS, {
 product: product,
 });
 Message({
 message: '商品更新成功',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('商品更新失敗');
 })
},
updateManufacturer({ commit }, payload) {
 commit(UPDATE_MANUFACTURER);
 const { manufacturer } = payload;
 axios.put(`${API_BASE}/manufacturers/${manufacturer._id}`, manufacturer)
 .then(() => {
 commit(UPDATE_MANUFACTURER_SUCCESS, {
 manufacturer: manufacturer,
 });
 Message({
 message: '製造商更新完成',
 type: 'success'
 })
 })
 .catch(() => {
 Message.error('製造商更新失敗');
 })
},

可以看到我們不理會 axios 返回的結果,直接使用原本作為 payload 去執行 API 的資料回傳到 mutations 來修改狀態
所以接著就要修改 mutations.js,將新的資料同步到狀態池中,也是針對更新的部分做局部修改,將最新的資料同步到狀態池中。

[UPDATE_PRODUCT_SUCCESS](state, payload) {
 state.showLoader = false;
 const { product: newProduct } = payload;
 state.products = state.products.map(product => {
 if (product._id === newProduct._id) {
 return newProduct;
 }
 return product;
 });
 state.product = newProduct;
},
[UPDATE_MANUFACTURER_SUCCESS](state, payload) {
 state.showLoader = false;
 const { manufacturer: newManufacturer } = payload;
 state.manufacturers = state.manufacturers.map(manufacturer => {
 if (manufacturer._id === newManufacturer._id) {
 return newManufacturer;
 }
 return manufacturer;
 });
 state.manufacturer = newManufacturer;
},

於是我們就修好了表單的修改功能並直接顯示最新資料!
這就是在實際開發中使用 element-ui 組件庫套用在前端樣板中的流程,並且一步一步的進行了重構。
到了這邊整個專案基本上已經可以正常運行了,使用者的體驗也得到的明顯的改善。

留言