本文由圖雀社群成員 Holy 使用 Tuture 實戰教程寫作工具 寫作而成,歡迎加入圖雀社群,一起創作精彩的免費技術實戰教程,予力程式設計行業發展。
在之前的六篇教程中我們已經基本實現了迷你全棧電商應用,相信大家對於一個全棧應用的開發已經有了一個全面的認知。但是一個追求完美的工程師是不會吝嗇他的藝術創造,僅僅實現應用的功能還不能滿足使用者的高需求,應用的介面效果也是提高使用者體驗的關鍵因素。因此本篇教程將基於element-ui元件庫重構專案的前端程式碼,改善迷你電商應用的介面效果,提高使用者的體驗感。雖然我們可以輕鬆地引入現成的元件庫,但是與之對應的資料處理也值得我們注意,那我會在引入元件庫的同時帶大家一起踩一踩element-ui給我們挖的坑,畢竟踩坑才能成長嘛。
歡迎閱讀《從零到部署:用 Vue 和 Express 實現迷你全棧電商應用》系列:
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(一)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(二)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(三)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(四)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(五)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(六)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(七)》(也就是這篇)
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(終篇)》
如果你希望直接從這一步開始,請執行以下命令:
git clone -b section-seven https://github.com/tuture-dev/vue-online-shop-frontend.git
cd vue-online-shop-frontend
複製程式碼
本文所涉及的原始碼都放在了 Github 上,如果您覺得我們寫得還不錯,希望您能給❤️這篇文章點贊+Github倉庫加星❤️哦~
程式碼重構
這一部分我們主要利用element-ui
元件庫重構之前的專案程式碼,實現極具美感的迷你電商應用。
這裡我們簡單介紹一下element-ui
元件庫(如果您瞭解,您可以跳過這部分):
Element UI 是一套採用 Vue 2.0 作為基礎框架實現的元件庫,一套為開發者、設計師和產品經理準備的基於 Vue 2.0的元件庫,提供了配套設計資源,幫助網站快速成型。
Element UI文件提供了很多例項程式碼,一般情況下我們直接拷下示例程式碼稍微看看改改資料之類的就OK了。但是在某些場景下,我們可能又需要使用到一些特殊的功能和屬性,而這些功能屬性一般在官方提供的元件中都已經內建了,所以我們可以直接先從文件中尋找檢視是否有屬性或者方法等能夠滿足我們的需求,從而避免重複造輪子。
安裝element-ui依賴
- npm 安裝推薦使用 npm 的方式安裝,它能更好地和 webpack 打包工具配合使用。
npm i element-ui -S
複製程式碼
- 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
檔案中匯入並註冊依賴。
你可以引入整個 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()
註冊元件庫。
至此,一個基於 Vue 和 Element 的開發環境已經搭建完畢,現在就可以愉快地使用元件庫進行程式碼重構了。
重構導航欄
我們首先來到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-for
將 product
物件遍歷到每個 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
,從本地獲取指定 id
的 product
物件。
我們已經迫不及待把專案跑起來了,看看我們的首頁導航以及商品資訊列表發生了怎樣不可思議的改變:
重構商品資訊功能
這部分內容主要是有關商品資訊功能的重構,包括商品資訊列表的展示、修改指定商品資訊以及新增新商品,我們都使用了 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
是一個製造商物件,包含製造商 id
和 name
屬性。
現在我們再進入 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倉庫加星❤️哦