Byte Ebi's Logo

Byte Ebi 🍤

每天一小口,蝦米變鯨魚

[Express+Vue 搭建電商網站] 21 使用 Element UI 加入載入過動畫

使用 Express + Vue 搭建一個電商網站 - 套用 Element UI 加入載入動畫

Ray

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

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 組件庫套用在前端樣板中的流程,並且一步一步的進行了重構
到了這邊整個專案基本上已經可以正常運行了,使用者的體驗也得到的明顯的改善!


專案範例程式碼 GitHub 網址:ray247k/mini-E-commerce

最新文章

Category

Tag