[Express+Vue 搭建電商網站] 21 使用 Element UI 加入載入過動畫
使用 Express + Vue 搭建一個電商網站 - 套用 Element UI 加入載入動畫
等待的時候很無聊,所以我們加點動畫
基本的動畫是用來告訴使用者動作執行結果,做到這樣使用者才知道自己剛剛做的事情有沒有完成
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
提示訊息組件
當使用者加入或移除購物車商品時就會收到提示了!
結果看起來像這樣,十分酷炫有型
重構到這邊,測試起來似乎又有點什麼問題
那就是在表單按下更新後,看到了更新成功的訊息,但畫面上的資料似乎沒有同步成最新的
當資料出現問題,應該依據 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 組件庫套用在前端樣板中的流程,並且一步一步的進行了重構
到了這邊整個專案基本上已經可以正常運行了,使用者的體驗也得到的明顯的改善!
專案範例程式碼 GitHub 網址:ray247k/mini-E-commerce