從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(七)

從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(七)

本文由圖雀社區成員 Holy 使用 Tuture 實戰教程寫做工具 寫做而成,歡迎加入圖雀社區,一塊兒創做精彩的免費技術實戰教程,予力編程行業發展。

在以前的六篇教程中咱們已經基本實現了迷你全棧電商應用,相信你們對於一個全棧應用的開發已經有了一個全面的認知。可是一個追求完美的工程師是不會吝嗇他的藝術創造,僅僅實現應用的功能還不能知足用戶的高需求,應用的界面效果也是提升用戶體驗的關鍵因素。所以本篇教程將基於element-ui組件庫重構項目的前端代碼,改善迷你電商應用的界面效果,提升用戶的體驗感。雖然咱們能夠輕鬆地引入現成的組件庫,可是與之對應的數據處理也值得咱們注意,那我會在引入組件庫的同時帶你們一塊兒踩一踩element-ui給咱們挖的坑,畢竟踩坑才能成長嘛。css

歡迎閱讀《從零到部署:用 Vue 和 Express 實現迷你全棧電商應用》系列:html

若是你但願直接從這一步開始,請運行如下命令:前端

git clone -b section-seven https://github.com/tuture-dev/vue-online-shop-frontend.git
cd vue-online-shop-frontend
本文所涉及的源代碼都放在了 Github 上,若是您以爲咱們寫得還不錯,但願您能給❤️ 這篇文章點贊+Github倉庫加星❤️哦~

代碼重構

這一部分咱們主要利用element-ui組件庫重構以前的項目代碼,實現極具美感的迷你電商應用。vue

這裏咱們簡單介紹一下element-ui組件庫(若是您瞭解,您能夠跳過這部分):webpack

Element UI 是一套採用 Vue 2.0 做爲基礎框架實現的組件庫,一套爲開發者、設計師和產品經理準備的基於 Vue 2.0的組件庫,提供了配套設計資源,幫助網站快速成型。

Element UI文檔提供了不少實例代碼,通常狀況下咱們直接拷下示例代碼稍微看看改改數據之類的就OK了。可是在某些場景下,咱們可能又須要使用到一些特殊的功能和屬性,而這些功能屬性通常在官方提供的組件中都已經內置了,因此咱們能夠直接先從文檔中尋找查看是否有屬性或者方法等可以知足咱們的需求,從而避免重複造輪子。ios

安裝element-ui依賴

  1. npm 安裝推薦使用 npm 的方式安裝,它能更好地和 webpack 打包工具配合使用。
npm i element-ui -S
  1. CDN引入目前能夠經過 unpkg.com/element-ui 獲取到最新版本的資源,在頁面上引入 js 和 css 文件便可開始使用。
<!-- 引入樣式 --><link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"><!-- 引入組件庫 --><script src="https://unpkg.com/element-ui/lib/index.js"></script>
咱們建議使用 CDN 引入 Element 的用戶在連接地址上鎖定版本,以避免未來 Element升級時受到非兼容性更新的影響。鎖定版本的方法請查看 unpkg.com

導入依賴

依賴安裝完成以後,咱們須要在項目的main.js文件中導入並註冊依賴。git

你能夠引入整個 Element,或是根據須要僅引入部分組件,這裏咱們引入了完整的 Element。
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import { ValidationProvider } from 'vee-validate';

import App from './App';
import router from './router';
import store from './store';
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false;
Vue.component('ValidationProvider', ValidationProvider);
Vue.use(ElementUI);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>',
});

main.js文件中咱們首先導入了element-ui組件庫,須要注意的是咱們要單獨引入樣式文件;除此以外還要使用Vue.use()註冊組件庫。github

至此,一個基於 Vue 和 Element 的開發環境已經搭建完畢,如今就能夠愉快地使用組件庫進行代碼重構了。web

重構導航欄

咱們首先來到App組件,這裏以前是採用普通的nav標籤展現首頁導航,顯得甚是簡陋,如今咱們可使用element-ui組件庫提供的el-menu導航菜單組件重構導航欄,絕對酷炫。數據庫

<template>
  <div id="app">
    <el-menu
      class="menu"
      :default-active="activeIndex2"
      mode="horizontal"
      @select="handleSelect"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b">
      <el-menu-item index="1"><router-link to="/" tag="div">Home</router-link></el-menu-item>
      <el-submenu index="2">
        <template slot="title">Admin</template>
        <el-menu-item index="2-1"><router-link to="/admin" tag="div">查看商品</router-link></el-menu-item>
        <el-menu-item index="2-2"><router-link to="/admin/new" tag="div">添加商品</router-link></el-menu-item>
        <el-menu-item index="2-3"><router-link to="/admin/manufacturers" tag="div">查看生產商</router-link></el-menu-item>
        <el-menu-item index="2-4"><router-link to="/admin/manufacturers/new" tag="div">添加生產商</router-link></el-menu-item>
      </el-submenu>  
      <el-menu-item index="3"><router-link to="/cart" tag="div">Cart</router-link></el-menu-item>
    </el-menu>
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      activeIndex: '1',
      activeIndex2: '1'
    };
  },
  methods: {
    handleSelect(key, keyPath) {
        console.log(key, keyPath);
    }
  }

};
</script>

// ...

這裏導航欄組件的使用相信你們都能看懂,這裏咱們只講一下比較特殊的地方。咱們不須要在乎 data 屬性以及 handleSelect 方法,咱們暫時用不到。這裏一個特殊的地方就是 el-menu-item 標籤中的 tag 屬性,咱們將其值設置爲 "div" 表示將該標籤渲染爲 "div" 盒子,若是不設置該屬性則該標籤默認渲染爲 "a" 標籤,致使標籤包裹的內容帶有下劃線,所以這裏 tag 屬性的設置是爲了去除下劃線。

重構商品列表

從新修改 ProductList 組件,因爲該組件中的子組件 ProductItem 進行了重構,所以這裏也須要作必定的修改,看到後面 ProductItem 組件的重構您就會明白咱們這裏修改的用意。

<template>
  <div>
    <div class="products">
      <div class="container">
        This is ProductList
      </div>
      <!-- <template v-for="product in products"> -->
        <product-item :products="products"></product-item>
      <!-- </template> -->
    </div>
  </div>
</template>

<script>
import ProductItem from './ProductItem.vue';
export default {
  name: 'product-list',
  created() {
    if (this.products.length === 0) {
      this.$store.dispatch('allProducts')
    }
  },
  computed: {
    // a computed getter
    products() {
      return this.$store.getters.allProducts;
    }
  },
  components: {
    'product-item': ProductItem
  }
}
</script>

這裏以前是將從本地獲取的 products 數組利用 v-forproduct 對象遍歷到每一個 ProductItem 組件中分別進行展現,可是咱們這裏取消了 v-for 遍歷 products 數組,選擇直接將 products 數組傳入 ProductItem 組件中。請容許我先在這裏賣個關子,繼續往下看。

從新進入 ProductItem 組件進行修改,這裏咱們使用了 element-ui 組件庫提供的 el-table 表格組件取代了原始標籤來展現商品信息列表。

<template>
  <div class="products">
    <el-table
    class="table"
    :data="products"
    max-height="250">
      <el-table-column
        prop="name"
        label="產品名稱"
        width="180">
      </el-table-column>
      <el-table-column
        prop="description"
        label="介紹"
        width="180">
      </el-table-column>
      <el-table-column
        prop="price"
        label="價格"
        width="180">
      </el-table-column>
      <el-table-column
        prop="manufacturer.name"
        label="生產廠商"
        width="180">
      </el-table-column>
      <!-- <el-table-column
        label="圖片"
        width="200">
        <img :src="image" alt="" class="product__image">
      </el-table-column> -->
      <el-table-column
        label="操做"
        width="180">
        <template slot-scope="scope">
          <product-button :id="scope.row._id"></product-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
  // ...
</template>

// ...

<script>
import ProductButton from './ProductButton';
export default {
  name: 'product-item',
  props: ['products'],
  components: {
    'product-button': ProductButton,
  }
}
</script>

其實這裏的修改相信你們都能看懂,咱們就簡單的作一下介紹。您可能還記得咱們在上面賣的一個關子,爲何咱們直接向該組件中傳入了 products 數組而不是遍歷的 product 對象?相信你們看了該組件的重構也能豁然開朗,那就是由於咱們使用的 el-table 表格組件須要傳入一個數組做爲 data 屬性,並將每一個元素對象做爲 prop 傳入表格,按照對應的列名展現出來。

除此以外,相信你們也發現了最後一個 el-table-column 標籤中並無定義 prop 屬性,這是由於最後一列單元格中放置的是按鈕而不是商品信息,該按鈕是用於對指定行對象進行指定操做,這裏咱們使用 scope.row 獲取指定行對象並將其id傳遞給了 ProductButton 按鈕組件。

經過 slot-scope 能夠獲取到 row, column, $index 和 store(table 內部的狀態管理)的數據

再次進入 ProductButton 組件進行修改,這裏咱們使用了 element-ui 組件庫提供的 el-button 按鈕組件替代以前普通的 button 標籤並修改了對應的數據處理。

<template>
  <div>
    <el-button
          v-if="isAdding"
          @click="addToCart"
          type="text"
          size="small">
          加入購物車
    </el-button>
    <el-button
          v-else
          @click="removeFromCart(id)"
          type="text"
          size="small">
          從購物車移除
    </el-button>
  </div>
</template>

<script>
export default {
  props: ['id'],
  computed: {
    product() {
      let product = this.$store.getters.allProducts.find(product => product._id === this.id)
      return product;
    },
    isAdding() {
      let isAdding = true;
      this.cart.map(product => {
        if (product._id === this.product._id) {
          isAdding = false;
        }
      });

      return isAdding;
    },
    cart() {
      return this.$store.state.cart;
    }
  },
  methods: {
    addToCart() {
      this.$store.commit('ADD_TO_CART', {
        product: this.product,
      })
    },
    removeFromCart(productId) {
      this.$store.commit('REMOVE_FROM_CART', {
        productId,
      })
    }
  }
}
</script>

這裏咱們首先簡單地使用了 el-button 按鈕組件,而後將從父組件獲取的 product 對象修改成了 id,由於咱們在 ProductItem 組件中傳入的是指定對象的 id,所以咱們在按鈕組件中定義了計算屬性 product,從本地獲取指定 idproduct 對象。

咱們已經火燒眉毛把項目跑起來了,看看咱們的首頁導航以及商品信息列表發生了怎樣難以想象的改變:

在這裏插入圖片描述

重構商品信息功能

這部份內容主要是有關商品信息功能的重構,包括商品信息列表的展現、修改指定商品信息以及添加新商品,咱們都使用了 element-ui 組件庫提供的組件進行重構,提升用戶操做商品信息時的交互體驗。

首先咱們進入 Products 組件,一樣使用了 element-ui 組件庫提供的 el-table 組件替換了以前普通表格來展現商品信息列表。

<template>
  <div class="products">
    <el-table
    class="table"
    :data="products">
      <el-table-column
        prop="name"
        label="名稱"
        width="180">
      </el-table-column>
      <el-table-column
        prop="price"
        label="價格"
        width="180">
      </el-table-column>
      <el-table-column
        prop="manufacturer.name"
        label="製造商"
        width="180">
      </el-table-column>
      <el-table-column
        label="操做"
        width="200">
        <template slot-scope="scope">
          <el-button class="modify" type="text" size="small"><router-link :to="'/admin/edit/' + scope.row._id">修改</router-link></el-button>
          <el-button class="remove" @click="removeProduct(scope.row._id), deleteRow(scope.$index, products)" type="text" size="small">刪除</el-button>
        </template>
      </el-table-column>
    </el-table>
    // ...
  </div>
</template>

// ...

細心的你們確定已經發現了這裏的表格有點似曾相識,沒錯,這裏的表格與 ProductItem 組件中的表格很是類似,都是用來展現本地商品信息,可是二者的區別是對商品對象的操做,ProductItem 組件中的按鈕組件是用於將商品添加或移出購物車,而該組件中的按鈕組件是用於修改或刪除商品對象。

這是咱們重構以後的商品信息列表:
在這裏插入圖片描述

而後咱們先對修改功能進行重構,再次進入 Edit 組件,咱們在這裏作了數據處理修改,目的是嘗試解決商品信息表單沒法編輯問題。

<template>
  <div>
    <div class="title">
      <h1>This is Admin/Edit</h1>
    </div>
    <product-form
      @save-product="updateProduct"
      :model="model"
      :manufacturers="manufacturers"
      :isEditing="true"
      ></product-form>
  </div>
</template>

<script>
import ProductForm from '@/components/products/ProductForm.vue';
export default {
  data: {
    model() {
      const product = this.$store.getters.productById(this.$route.params['id']);
      // 這裏返回 product 的拷貝,是爲了在修改 product 的拷貝以後,在保存以前不修改本地 Vuex stire 的 product 屬性
      return { ...product, manufacturer: { ...product.manufacturer } };
    }
  },
  created() {
    const { name } = this.model;
    if (!name) {
      this.$store.dispatch('productById', {
        productId: this.$route.params['id']
      });
    }

    if (this.manufacturers.length === 0) {
      this.$store.dispatch('allManufacturers');
    }
  },
  computed: {
    manufacturers() {
      return this.$store.getters.allManufacturers;
    }
  },
  methods: {
    updateProduct(product) {
      this.$store.dispatch('updateProduct', {
        product,
      })
    }
  },
  components: {
    'product-form': ProductForm
  }
}
</script>

這裏咱們把定義的計算屬性 model 修改成 data 屬性,由於咱們發現若是商品對象 model 做爲計算屬性傳給子組件 ProductForm 進行信息展現時,沒法進行表單編輯,你們能夠運行起來嘗試一下是否能夠進行編輯。咱們初始猜測是 el-form 表單組件中的表單數據對象 model 不能來自計算屬性,不然沒法進行編輯,所以咱們首度嘗試將該組件中的計算屬性 model 放到 data 屬性中。

再次進入 ProductForm 組件進行重構,這裏咱們使用了 element-ui 組件庫提供的 el-form 表單組件替換以前的普通表單展現商品信息。

<template>
  <div class="productInfo">
    <el-form class="form" ref="form" :model="model" label-width="180px">
      <el-form-item label="Name">
        <el-input v-model="model.name"></el-input>
      </el-form-item>
      <el-form-item label="Price">
        <el-input v-model="model.price"></el-input>
      </el-form-item>
      <el-form-item label="Manufacturer ">
        <el-select v-model="model.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="model.image"></el-input>
      </el-form-item>
      <el-form-item label="Description ">
        <el-input type="textarea" v-model="model.description"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button v-if="isEditing" type="primary" @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 {
  props: ['model', 'manufacturers', 'isEditing'],
  created() {
    console.log(this.model)
  },
  methods: {
    onSubmit() {
      this.$emit('save-product', this.model)
    }
  }
}
</script>
<style>
.productInfo {
  padding-top: 10px;
}
.form {
  margin: 0 auto;
  width: 500px;
}
.el-input__inner {
  height: 60px;
}
</style>

相信你們也能輕鬆的看懂 el-form 表單組件的使用,這裏的 model 屬性表示表單數據對象,咱們可使用 v-model 將表單數據對象中的信息雙向綁定到相應的表單內組件上。特別提醒一下商品對象 model 中的 manufacturer 是一個製造商對象,包含製造商 idname 屬性。

如今咱們再進入 New 組件進行重構,當咱們發現 Edit 組件中的問題以後,咱們一樣嘗試將該組件中的計算屬性 model 定義到 data 屬性中。

<template>
  <product-form
    @save-product="addProduct"
    :model="model"
    :manufacturers="manufacturers"
  >
  </product-form>
</template>

<script>
import ProductForm from '@/components/products/ProductForm.vue';
export default {
  data() {
    return {
      model: {manufacturer:{name: ''}}
    }
  },
  created() {
    if (this.manufacturers.length === 0) {
      this.$store.dispatch('allManufacturers');
    }
  },
  computed: {
    manufacturers() {
      return this.$store.getters.allManufacturers;
    }
  },
  methods: {
    addProduct(model) {
      this.$store.dispatch('addProduct', {
        product: model,
      })
    },
  },
  components: {
  'product-form': ProductForm
  }
}
</script>

由於該組件是新建商品組件,所以咱們定義的是一個空對象 model,可是咱們須要給其一個默認初始形式 model: {manufacturer: {name: ' '}},防止在子組件表單中沒法訪問 name 屬性致使報錯。

如今咱們添加或者修改商品信息的表單界面變成了這樣:
在這裏插入圖片描述

重構製造商信息功能

製造商信息功能包括製造商信息展現,添加製造商以及修改製造商信息,同重構商品信息功能同樣,咱們也使用了 element-ui 組件庫提供的組件進行重構,提升用戶操做製造商信息時的交互體驗。

首先咱們進入 Manufacturers 組件進行重構,同 Products 組件相似,咱們使用了 element-ui 組件庫提供的 el-table 表格組件替換了以前普通的表格展現製造商信息列表。

<template>
  <div class="manufacturers">
    <el-table
    class="table"
    :data="manufacturers">
      <el-table-column
        prop="name"
        label="製造商"
        width="180">
      </el-table-column>
      <el-table-column
        label="操做"
        width="200">
        <template slot-scope="scope">
          <el-button class="modify" type="text" size="small"><router-link :to="'/admin/manufacturers/edit/' + scope.row._id">修改</router-link></el-button>
          <el-button class="remove" @click="removeManufacturer(scope.row._id), deleteRow(scope.$index, products)" type="text" size="small">刪除</el-button>
        </template>
      </el-table-column>
    </el-table>
    // ...
  </div>
</template>

// ...
<script>
export default {
  created() {
    this.$store.dispatch('allManufacturers');
  },
  computed: {
    manufacturers() {
      return this.$store.getters.allManufacturers
    }
  },
  methods: {
    removeManufacturer(manufacturerId) {
      // 使用 JavaScript BOM 的 confirm 方法來詢問用戶是否刪除此製造商
      const res = confirm('是否刪除此製造商?');

      // 若是用戶贊成,那麼就刪除此製造商
      if (res) {
        this.$store.dispatch('removeManufacturer', {
          manufacturerId,
        })
      }
    }
  }
}
</script>

這是咱們重構後的製造商信息列表:
在這裏插入圖片描述

再次進入 NewManufacturers 組件進行重構,一樣的將定義的計算屬性 model 放到 data 屬性中。

<template>
  <manufacturer-form
    @save-manufacturer="addManufacturer"
    :model="model"
  >
  </manufacturer-form>
</template>

<script>
import ManufacturerForm from '@/components/ManufacturerForm.vue';
export default {
  data() {
    return {
      model: {}
    }
  },
  methods: {
    addManufacturer(model) {
      this.$store.dispatch('addManufacturer', {
        manufacturer: model,
      })
    },
  },
  components: {
  'manufacturer-form': ManufacturerForm
  }
}
</script>

而後進入子組件 ManufacturerForm 中進行重構,同 ProductForm 組件相似,使用 element-ui 組件庫提供的 el-form 表單組件替換了以前普通的表單展現製造商信息。

<template>
  <div class="manufacturerInfo">
    <el-form class="form" ref="form" :model="model" label-width="180px">
      <el-form-item label="Name">
        <el-input v-model="model.name"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button v-if="isEditing" type="primary" @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'],
  methods: {
    onSubmit() {
      this.$emit('save-manufacturer', this.model)
    }
  }
}
</script>
// ...

這是咱們重構後用戶添加或者修改製造商信息時的表單界面:
在這裏插入圖片描述

最後咱們進入 Cart 組件進行重構,咱們會發現該組件與 ProductList 組件極其類似,由於二者都複用了子組件 ProductItem,該組件是爲了展現購物車商品信息列表。

<template>
  <div>
    <div class="title">
      <h1>{{msg}}</h1>
    </div>
    <product-item :products="cart"></product-item>
  </div>
</template>

<script>
import ProductItem from '@/components/products/ProductItem.vue';
  export default {
    name: 'home',
    data () {
      return {
        msg: 'Welcome to the Cart Page'
      }
    },
    computed: {
      cart() {
        return this.$store.state.cart;
      }
    },
    components: {
      'product-item': ProductItem
    }
  }
</script>

這是重構後的購物車界面:
在這裏插入圖片描述

小結

這一節咱們主要就是使用 element-ui 組件庫進行項目代碼的重構,實現了首頁導航欄、商品信息功能、製造商信息功能以及購物車的頁面升級,提升了用戶的交互體驗。可是這也形成了部分功能邏輯的癱瘓,咱們在下一節會帶你們一塊兒去解決問題。

修復element-ui表單雙向綁定問題

上一節咱們使用了 element-ui 組件庫完成項目代碼重構,但是當咱們把項目跑起來以後發現表單信息仍然沒法編輯,說明咱們以前的嘗試失敗。不過咱們並無灰心,而是選擇繼續嘗試,這一節咱們又嘗試新方法來修復
element-ui 表單雙向綁定問題。

你們遇到的問題應該是這樣子:
在這裏插入圖片描述

重構 Edit 組件

咱們首先進入 Edit 組件進行修復,這裏咱們主要恢復了原先的數據定義。

<template>
  <div>
    <div class="title">
      <h1>This is Admin/Edit</h1>
    </div>
    <product-form
      @save-product="updateProduct"
      :model="model"
      :manufacturers="manufacturers"
      :isEditing="true"
    ></product-form>
  </div>
</template>

<script>
import ProductForm from "@/components/products/ProductForm.vue";
export default {
  created() {
    const { name = "" } = this.modelData || {};

    if (!name) {
      this.$store.dispatch("productById", {
        productId: this.$route.params["id"]
      });
    }

    if (this.manufacturers.length === 0) {
      this.$store.dispatch("allManufacturers");
    }
  },
  computed: {
    manufacturers() {
      return this.$store.getters.allManufacturers;
    },
    model() {
      const product = this.$store.getters.productById(this.$route.params["id"]);
      const res = { ...product, manufacturer: { ...product.manufacturer } };

      return res;
    }
  },
  methods: {
    updateProduct(product) {
      this.$store.dispatch("updateProduct", {
        product
      });
    }
  },
  components: {
    "product-form": ProductForm
  }
};
</script>

咱們又將替換到 data 屬性中的 model 對象恢復到了計算屬性中,用於緩存 model 對象信息,提升性能。咱們打算在下面的 ProductForm 組件中進行修復表單沒法編輯的問題。

重構 ProductForm 組件

再次進入 ProductForm 組件中,咱們嘗試另外一種方法來修復表單沒法編輯的問題。

<template>
  <div class="productInfo">
    <el-form class="form" ref="form" label-width="180px">
      <el-form-item label="Name">
        <el-input v-model="model.name"></el-input>
      </el-form-item>
      <el-form-item label="Price">
        <el-input v-model="model.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="model.image"></el-input>
      </el-form-item>
      <el-form-item label="Description ">
        <el-input type="textarea" v-model="model.description"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button v-if="isEditing" type="primary" @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, oldVal) {
      this.modelData = val;
    }
  },
  methods: {
    onSubmit() {
      this.$emit("save-product", this.modelData);
    }
  }
};
</script>
// ...

這裏咱們沒有直接使用從父組件獲取的 model 對象做爲表單數據對象,而是在該組件中自定義一個 modelData 對象,並使用默認初始形式。而後在組件剛被建立時,先將從父組件獲取的 model 對象賦值給一個臨時變量 product,而後將 product 淺拷貝到 modelData 對象中,這樣就避免了表單數據對象使用計算屬性。可是這僅僅完成了一半的工做,由於咱們須要實現雙向綁定的效果,所以咱們須要監測表單組件的變化,經過使用 watch 方法監測用戶的輸入,而後將新數據儲存到 modelData 對象中,這樣就成功實現了雙向綁定,並且表單也能隨意進行編輯。

可是這裏咱們僅僅在下拉菜單中使用了 modelData 對象進行嘗試,所以後面咱們會在整個表單內組件使用該對象。

小結

這一節咱們主要帶你們修復了 element-ui 表單雙向綁定問題,經過自定義 modelData 對象以及 watch 方法監測表單數據的改變實現了表單數據的雙向綁定,而且解決了表單沒法編輯的問題。可是僅僅在下拉菜單中進行嘗試,後面咱們會重構整個商品信息表單組件。

完善表單雙向綁定問題

重構 ProductForm 組件

再次進入 ProductForm 組件,咱們須要完善上一節遺留的問題,也就是僅僅對商品信息表單中的下拉菜單進行了嘗試,而且嘗試成功,所以這一節咱們須要將 modelData 對象導入全部表單內組件中,解決其餘表單內組件沒法編輯的問題。

<template>
  <div class="productInfo">
    <el-form class="form" ref="form" label-width="180px">
      <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" @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, oldVal) {
      this.modelData = val;
    }
  },
  methods: {
    onSubmit() {
      this.$emit("save-product", this.modelData);
    }
  }
};
</script>
// ...

小結

這一節咱們帶你們補充了上一節遺留的問題,也就是複製下拉菜單中的嘗試到其餘表單內組件中,保證整個表單組件都可以順利地實現編輯功能。

解決操做商品信息表單報錯問題

重構 ProductForm 組件

相信你們在對商品信息表單進行添加或者修改操做時,控制檯會出現 id 屬性未定義的錯誤,咱們首先應該進入報錯的組件中進行調試,你們應該都看到了報錯信息出如今 ProductForm 組件中,所以咱們須要進入 ProductForm 組件進行調試。

<template>
  <div class="productInfo">
    <el-form class="form" ref="form" label-width="180px">
      <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" @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, oldVal) {
      this.modelData = val;
    }
  },
  methods: {
    onSubmit() {
      const manufacturer = this.manufacturers.find(item => item.name === this.modelData.manufacturer.name);
      this.modelData.manufacturer = manufacturer;
      this.$emit("save-product", this.modelData);
    }
  }
};
</script>
// ...

首先你們應該清楚商品對象中還包含了相應的製造商對象,而且製造商對象中包含了 id 屬性和 name 屬性。可是咱們應該能夠發現商品信息表單中的下拉菜單雙向綁定的是商品對象中的製造商對象的 name 屬性,所以在 watch 方法中存儲到 modelData 對象中的製造商對象也只有 name 屬性,可是後端數據庫要求製造商對象必須也要有 id 屬性,這就是咱們點擊更新商品信息出現報錯的緣由。

這裏咱們使用了本地製造商數組的 find 方法,檢索到了對應 name 的製造商對象並將其覆蓋掉 modelData 對象中的製造商對象,這樣咱們的 modelData 對象中的製造商對象就是一個符合後端數據庫要求的對象了。

小結

這一節咱們帶你們分析並嘗試解決了操做商品信息表單出現 id 屬性未定義的問題。

添加動態效果及消息提示

咱們注意到了當用戶進行添加或修改商品或者製造商信息時,不免會遇到更新延遲的問題,這個時候若是頁面毫無反饋會顯得些許尷尬,所以咱們認爲只要用戶進行添加或者修改操做,在後端數據同步完成以前咱們爲頁面添加一個動態加載的效果,給用戶一個反饋表示數據正在處理中,請耐心等待;而且在後端同步完成以後爲頁面添加一個消息提示,給用戶一個反饋表示數據處理成功,這樣就避免了尷尬的場面,提升了用戶的交互體驗。

實現loading動態加載效果

再次進入 ManufactureForm 組件,實現用戶在添加或者修改製造商信息時且當後端數據同步完成以前,頁面出現 loading動態加載效果。

<template>
  <div class="manufacturerInfo">
    <el-form 
    class="form" 
    ref="form" 
    label-width="180px"
    v-loading="loading"
    element-loading-text="拼命加載中"
    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, oldVal) {
      this.manufacturerData = val;
    }
  },
  computed: {
    loading() {
      return this.$store.state.showLoader
    }
  },
  methods: {
    onSubmit() {
      this.$emit('save-manufacturer', this.manufacturerData);
    }
  }
}
</script>
// ...

首先咱們在該組件中使用了 element-ui 組件庫提供的自定義指令 v-loading,經過判斷 loading 爲true仍是false來決定是否實現動態加載效果。這裏咱們經過獲取本地狀態中的 showLoader 屬性做爲 loading 屬性值,由於在用戶剛進行添加或修改操做時,向後端發起數據請求,此時本地狀態中的 showLoader 屬性值爲true,當成功獲取到了數據響應以後,也就是後端數據同步完成,此時 showLoader 屬性值爲false,這樣就實現了在指定時間顯示動態加載效果;除此以外,咱們還按照 ProductForm 組件補充修改了數據處理,解決製造商表單組件編輯問題。

一樣進入 ProductForm 組件進行修改,實現用戶在添加或修改商品信息時,且當後端數據同步完成以前,頁面出現 loading 動態加載效果。

<template>
  <div class="productInfo">
    <el-form 
    class="form" 
    ref="form" 
    label-width="180px"
    v-loading="loading"
    element-loading-text="拼命加載中"
    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, oldVal) {
      this.modelData = val;
    }
  },
  computed: {
    loading() {
      return this.$store.state.showLoader
    }
  },
  methods: {
    onSubmit() {
      // 因爲表單中只綁定了modelData.manufacturer.name,
      // 缺乏manufacturer._id,可是後端須要manufacturer整個對象,
      // 因此須要將manufacturers中對應的manufacturer找出並覆蓋到modelData中
      const manufacturer = this.manufacturers.find(item => item.name === this.modelData.manufacturer.name);
      this.modelData.manufacturer = manufacturer;

      this.$emit("save-product", this.modelData);
    }
  }
};
</script>
// ...

實現消息提示功能

首先進入 actions.js 文件進行修改,因爲發送網絡請求數據的操做在該文件中執行,所以咱們能夠將消息提示功能添加到這裏。

import axios from 'axios';

import {
  // ...
} from './mutation-types';
import { Message } from 'element-ui';
 // ...
const API_BASE = 'http://localhost:3000/api/v1';
 // ...
export const productActions = {
  // ...
  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 = {
  // ...
  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 消息提示組件,並在網絡請求成功以後添加成功消息提醒,在請求失敗以後添加失敗消息提醒。

而後進入 mutations.js 文件進行修改,這裏的修改是爲本地購物車數據處理添加消息提示。

import {
  // ...
} from './mutation-types';
import { Message } from 'element-ui';
 // ...
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 消息提示組件,當用戶進行添加或者移除購物車操做時,執行操做成功消息提醒。

咱們在進行添加、刪除、修改以及加入或移除購物車操做時都會獲得這樣的反饋:
在這裏插入圖片描述

小結

這一節咱們主要作的幾點工做:

  • 爲表單組件添加 element-ui 組件庫提供的 v-loading 指令,實現動態加載效果;
  • 添加了 element-ui 組件庫提供的 Message 消息提示組件,實現用戶操做表單信息後獲得的反饋消息提示。

解決表單信息修改後沒法顯示最新

重構到這裏相信有些朋友已經火燒眉毛地將項目跑起來了,可是老是事與願違,可是你們絲絕不用方,只要您跟着咱們一步一步腳踏實地地去分析問題,那麼什麼問題都會迎刃而解了。如今的問題就是當用戶對商品或者製造商進行信息修改時,點擊更新以後表單卻又顯示了舊信息。

你們遇到的情況應該是這樣:
在這裏插入圖片描述
在這裏插入圖片描述

數據出現問題咱們應該根據 vue 的單向數據流原則進行調試,當用戶對錶單信息進行更新時,應該首先向後端發起網絡請求,而後將最新數據同步到本地狀態中進行展現,所以咱們來到 actions.js 文件中進行調試。

提交最新數據

再次進入 actions.js 文件進行調試,咱們能夠大膽的猜想網絡請求成功以後提交到 mutations.js 文件中的數據對象不是用戶修改的最新數據。

import axios from 'axios';
 // ...
import {
  // ...
} from './mutation-types';
import { Message } from 'element-ui';
 // ...
const API_BASE = 'http://localhost:3000/api/v1';
 // ...
export const productActions = {
  // ...
  updateProduct({ commit }, payload) {
    commit(UPDATE_PRODUCT);
 // ...
    const { product } = payload;
    axios.put(`${API_BASE}/products/${product._id}`, product)
    .then(response => {
      commit(UPDATE_PRODUCT_SUCCESS, {
        product: product,
      });
      Message({
        message: '恭喜你,商品更新成功!',
        type: 'success'
      })
    })
    .catch(() => {
      Message.error('很差意思,商品更新失敗!');
    })
  },
  // ...
};
 // ...
export const manufacturerActions = {
  // ...
  updateManufacturer({ commit }, payload) {
    commit(UPDATE_MANUFACTURER);
 // ...
    const { manufacturer } = payload;
    axios.put(`${API_BASE}/manufacturers/${manufacturer._id}`, manufacturer)
    .then(response => {
      commit(UPDATE_MANUFACTURER_SUCCESS, {
        manufacturer: manufacturer,
      });
      Message({
        message: '恭喜你,製造商更新成功!',
        type: 'success'
      })
    })
    .catch(() => {
      Message.error('很差意思,製造商更新失敗!');
    })
  },
  // ...
}

咱們在這裏將網絡請求成功時提交的載荷修改成了最新數據對象,而後提交到對應類型的 mutation 中進行本地數據的更新。

將最新數據同步到本地

緊接着咱們須要進入 mutations.js 文件,將其獲取到的最新數據同步到本地狀態中。

import {
  // ...
} from './mutation-types';
import { Message } from 'element-ui';
 // ...
export const productMutations = {
  // ...
  [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組件庫對電商應用前端代碼進行重構所遇到的一些問題,而且咱們一步一步地帶你們去分析及嘗試解決問題。但願這篇教程讓你們對element-ui組件庫的使用須要注意的問題有一個大體的瞭解,重要的是分析和嘗試解決問題的能力。好了,到這裏咱們的項目基本上能夠愉快地跑起來了,用戶的交互體驗感明顯獲得了改善。

若是你們在項目運行中遇到了其餘問題,但願你們不要吝嗇本身的質疑,多多和咱們溝通哦!

想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。

本文所涉及的源代碼都放在了 Github 上,若是您以爲咱們寫得還不錯,但願您能給❤️這篇文章點贊+Github倉庫加星❤️哦

相關文章
相關標籤/搜索