如何利用vue+vue-router+elementUI實(shí)現(xiàn)簡(jiǎn)易通訊錄
一個(gè)具有基本增刪改查功能的通訊錄,數(shù)據(jù)保存在本地的localStorage中。
demo地址: https://junjunhuahua.github.io
1. 所用技術(shù)
js框架: vue2 https://cn.vuejs.org/
ui框架: elementUI http://element.eleme.io/#/zh-CN
腳手架: vue-cli
單頁(yè): vue-router https://router.vuejs.org/zh-cn/
模塊打包: webpack
2. 腳手架搭建
# 全局安裝 vue-cli $ npm install -g vue-cli # 創(chuàng)建一個(gè)基于 webpack 模板的新項(xiàng)目 $ vue init webpack contact $ cd contact # 安裝依賴 $ npm install $ npm run dev
這是vue官方基于webpack的腳手架,run dev后瀏覽器會(huì)自動(dòng)打開localhost:8080,也可以使用run build命令,執(zhí)行build命令后會(huì)自動(dòng)將src目錄中的內(nèi)容進(jìn)行編譯打包壓縮,然后在dist目錄中可以看到這些文件
3. 目錄結(jié)構(gòu)
項(xiàng)目根目錄:
build為構(gòu)建項(xiàng)目所用的node代碼,config為構(gòu)建時(shí)的一些配置項(xiàng),dist為打包后(npm run build 用于發(fā)布)的代碼,node_modules為node模塊,src為開發(fā)時(shí)所用的代碼。
src目錄:

assets為全局css,圖片,以及一些工具類的js,components為vue的組件,router為路由配置,app.vue為主頁(yè)面的組件,config.js為目錄配置項(xiàng),main.js為入口js
4. main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import utils from './assets/utils.js'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/normalize.css'
Vue.use(ElementUI)
Vue.use(utils)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
ElementUI,
template: '<App/>',
components: { App }
})
main.js的主要工作是引入一些框架,全局css,以及工具函數(shù),還會(huì)處理vue組件的加載,最后實(shí)例化vue。
5. App.vue
.vue文件是什么? https://cn.vuejs.org/v2/guide/single-file-components.html
App.vue可以認(rèn)為是應(yīng)用最外層的一個(gè)容器。
<template>
<div id="app">
<div class="app-left">
<el-row class="tac">
<el-col>
<el-menu :default-active="menuIndex" class="el-menu-vertical-demo"
background-color="#545c64" text-color="#fff" :unique-opened="menuUniqueOpen" :router="menuRouter"
active-text-color="#ffd04b">
<h3>我的應(yīng)用</h3>
<template v-for="(item, index) in menuData">
<!-- 此處的index需顯示轉(zhuǎn)換為string,否則會(huì)報(bào)warn -->
<el-submenu :index="'' + (index + 1)">
<template slot="title">{{ item.name }}</template>
<template v-for="(subItem, i) in item.value">
<!-- 此處index格式為父級(jí)的index加上下劃線加上當(dāng)前的index,index都需加1 -->
<router-link tag="span" :to="subItem.path">
<el-menu-item :index="subItem.name">{{ subItem.title }}</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</el-menu>
</el-col>
</el-row>
</div>
<div class="app-right">
<router-view></router-view>
</div>
</div>
</template>
<script>
import menuData from './config'
export default {
name: 'app',
data () {
return {
menuData,
menuIndex: '', // 菜單當(dāng)前所在位置
menuUniqueOpen: true, // 菜單項(xiàng)是否唯一開啟
menuRouter: true // 是否開啟路由模式
}
},
mounted: function () {
...
},
watch: {
'$route' (to) {
this.menuIndex = to.name
}
}
}
</script>
這邊偷了一個(gè)懶,沒有把左側(cè)的menu單獨(dú)做成一個(gè)vue而是混入App.vue中。
6. 路由
在正式寫代碼之前,首先要確定要項(xiàng)目的結(jié)構(gòu),模塊如何劃分,哪個(gè)模塊對(duì)應(yīng)哪個(gè)路由。
因?yàn)檎麄€(gè)項(xiàng)目現(xiàn)在就劃分出兩個(gè)大板塊,通訊錄與記賬本,所以路由第一級(jí)就只有contact和account兩種。
Vue.use(VueRouter)
let myRouter = new VueRouter({
routes: [
{
path: '*',
component: () => import('../components/NotFoundComponent.vue')
},
{
path: '/',
redirect: '/contact'
},
{
path: '/contact',
name: 'Contact',
component: () => import('../components/contact/List.vue')
},
{
path: '/contact/edit',
name: 'Contact',
component: () => import('../components/contact/Edit.vue')
},
{
path: '/account',
name: 'Account',
component: () => import('../components/account/list.vue')
}
]
})
可以看到上面/contact和/contact/edit的name是相同的,這是為了讓在新增或者編輯聯(lián)系人頁(yè)面下,還能讓active狀態(tài)停留在左側(cè)我的聯(lián)系人上,可以看到App.vue中的代碼this.menuIndex = to.name就是進(jìn)行的該操作,
雖然這樣vue會(huì)報(bào)一個(gè)warn告訴我別重名[捂臉],暫時(shí)能想到的就是這樣的操作方式了,有考慮過依靠判斷path來確定是否顯示高亮狀態(tài),但是當(dāng)目錄層級(jí)較深且較復(fù)雜的情況下,這樣就不是很靠譜了。
component這里為什么是這種形式,而不是直接用一個(gè)組件名呢,因?yàn)楫?dāng)路由開始多起來的時(shí)候,一下把所有的組件都加載進(jìn)來會(huì)非常非常慢且會(huì)加載到許多當(dāng)時(shí)并沒有用到的組件,通過import這種形式,可以讓webpack將路由變換時(shí)用到的組件分開打包,網(wǎng)頁(yè)會(huì)根據(jù)使用情況再進(jìn)行
由于router是vue的組件,所以使用時(shí)記得要Vue.use一下。
7. 聯(lián)系人列表頁(yè) --- contact/list.vue
<template>
<div class="contact-list">
<div class="contact-list-header">
<el-button @click="goToNew" type="primary">新增聯(lián)系人</el-button>
</div>
<div class="contact-list-content">
<template>
<div class="contact-list-wrap">
<h3>高級(jí)檢索</h3>
<el-form ref="contactSearch" :model="searchParams" :inline=true>
<el-form-item label="姓名">
<el-input v-model="searchParams.name" placeholder="請(qǐng)輸入需要檢索的姓名"></el-input>
</el-form-item>
</el-form>
<el-button type="primary" size="mini" round @click="contactSearch('contactSearch')">搜索</el-button>
</div>
<div class="contact-list-wrap">
<h3>聯(lián)系人列表</h3>
<el-table
:data="listNewData"
style="width: 100%"
@row-click="viewContact"
:default-sort="{prop: 'name', order: 'descending'}"
>
<el-table-column
label="姓名"
prop="name"
sortable
width="180">
</el-table-column>
...
<el-table-column
label="功能">
<template scope="scope">
<el-button size="mini" type="primary" @click.stop="editContact(scope)">編輯</el-button>
<el-button size="mini" @click.stop="deleteContact(scope)">刪除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</div>
<contact-view ref="contactView" :viewData="curData" :viewShow.sync="viewShow"></contact-view>
</div>
</template>
<script>
import contactView from './View.vue'
export default {
data () { ... },
components: {
contactView
},
computed: {
listNewData: function () { ... },
mounted: function () {
this.listData = this.utils.getLocalStorage('vueContact')
},
methods: {
goToNew: function () {
this.$router.push('/contact/edit')
},
sexFormatter: function (row) { ... },
deleteContact: function (res) {
let data = res.row
this.$confirm('此操作將永久刪除該聯(lián)系人, 是否繼續(xù)?', '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning',
callback: (action) => {
if (action === 'confirm') {
this.$delete(this.listData, data.id)
this.utils.setLocalStorage('vueContact', this.listData)
}
}
})
},
editContact: function (res) {
let data = res.row
this.$router.push({
path: '/contact/edit', query: {id: data.id}
})
},
viewContact: function (row) {
this.viewShow = true
this.curData = this.listData[row.id]
},
contactSearch: function () {
let data = this.utils.getLocalStorage('vueContact')
let newData = {}
for (let item in data) {
if (data[item].name.indexOf(this.searchParams.name) > -1) {
newData[item] = data[item]
}
}
this.listData = newData
}
}
}
</script>
list.vue相當(dāng)于該模塊的主頁(yè),新增與編輯頁(yè)面通過右上角的新建按鈕或者列表中的編輯按鈕進(jìn)入,查看頁(yè)面通過引入View.vue作為一個(gè)彈窗放在列表頁(yè)中展示,不單獨(dú)設(shè)置路由。
列表展示所使用的是elementUI的table組件
刪除對(duì)象時(shí)一定要使用$delete,否則不會(huì)觸發(fā)視圖更新
view.vue代碼:
<template>
<div class="contact-view">
<el-dialog :before-close="closePop" ref="myDialog" :visible="viewShow">
<el-form :model="viewData" label-width="60px">
<el-form-item label="姓名" prop="name">
<el-input :readonly="true" v-model="viewData.name"></el-input>
</el-form-item>
...
<el-form-item label="備注">
<el-input :readonly="true" type="textarea" v-model="viewData.desc"></el-input>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
export default {
props: ['viewShow', 'viewData'],
methods: {
closePop: function () {
// 需手動(dòng)關(guān)閉彈窗,找到父組件中調(diào)用的地方進(jìn)行事件的觸發(fā)
this.$parent.$refs.contactView.$emit('update:viewShow', false)
}
}
}
</script>
這里有個(gè)比較值得注意的點(diǎn),就是關(guān)閉查看彈窗,彈窗的開啟關(guān)閉狀態(tài)通過list也就是父級(jí)中的viewShow來控制,viewShow通過view也就是子級(jí)中的props流入到子級(jí)中,但是vue中的數(shù)據(jù)流向是默認(rèn)是單向的,想要子級(jí)中修改父級(jí)屬性必須使用emit,詳見上面代碼。
這里原先使用elementUI的dialog組件的自己的關(guān)閉,會(huì)報(bào)錯(cuò),只能自己修改了。
ps: 為什么這里不用vuex處理父子組件的通信?因?yàn)槿绻且粋€(gè)大型的后臺(tái)管理系統(tǒng),像這樣的情況會(huì)經(jīng)常發(fā)生,如果都放在vuex中管理,那vuex的體積會(huì)非常龐大,反而不利于維護(hù)。
8. 聯(lián)系人編輯(新增)頁(yè) --- edit.vue
<template>
<div class="contact-edit">
<el-form ref="contactForm" :model="form" :rules="rules" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="性別">
<el-select v-model="form.sex" placeholder="請(qǐng)選擇性別">
<el-option label="男" value="male"></el-option>
<el-option label="女" value="female"></el-option>
</el-select>
</el-form-item>
...
<el-form-item label="備注">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('contactForm')">{{ btnName }}</el-button>
<el-button @click="cancelForm">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data () {
var nameValid = (rule, value, callback) => {
if (!value) {
callback(new Error('姓名不能為空'))
} else {
callback()
}
}
var mobileValid = (rule, value, callback) => {
let phonePattern = /(^\s*$)|(^[1][3,4,5,7,8][0-9]{9}$)/
if (value && !phonePattern.test(value)) {
callback(new Error('手機(jī)號(hào)格式不正確'))
} else {
callback()
}
}
return {
type: '', // 控制是否是新建
...
rules: {
name: [{validator: nameValid, trigger: 'blur'}],
mobile: [
// {required: true, message: '手機(jī)號(hào)不能為空', trigger: 'blur'},
{validator: mobileValid, trigger: 'blur'}
]
}
}
},
// 組件加載后的鉤子
mounted: function () {
this.checkPageStatus(this.$route.query.id)
},
// 路由在組件中的鉤子
beforeRouteUpdate: function (to, from, next) {
this.checkPageStatus(to.query.id)
next()
},
methods: {
// 檢查頁(yè)面是新建還是編輯
checkPageStatus: function (id) { ... },
cancelForm: function () {
this.$router.push('/contact')
},
onSubmit: function (formName) { ... }
}
}
</script>
可以看到mounted與beforeRouteUpdate中的代碼有些重合,那是因?yàn)関ue在路由僅僅只是參數(shù)變換的時(shí)候,是不會(huì)重新重新加載組件的,所以需要在beforeRouteUpdate中處理初始的數(shù)據(jù)。
nameValid與mobileValid為表單驗(yàn)證的函數(shù),el-form配置rules屬性名稱,然后data中相應(yīng)的添加rules即可開啟表單驗(yàn)證,但是有一點(diǎn)一定要注意el-form-item上一定要設(shè)置對(duì)應(yīng)的prop屬性,rules才會(huì)生效。
9. 總結(jié)
非常簡(jiǎn)單的一個(gè)項(xiàng)目,但是有幾個(gè)點(diǎn)一定要關(guān)注好:
模塊的劃分,模塊劃分要合理,盡量能保證模塊的復(fù)用性
狀態(tài)的管理,一定要明確什么東西要放vuex中,什么東西不用放,以免使項(xiàng)目的維護(hù)反而變得更復(fù)雜
如果是大型項(xiàng)目,路由中一定要讓.vue文件在需要時(shí)再引入,否則會(huì)加重初次加載的負(fù)擔(dān)
為了減少篇幅,刪減了很多不重要的代碼,需要查看源碼請(qǐng)移步,項(xiàng)目地址: https://github.com/junjunhuahua/vue-basic-demo
github上的項(xiàng)目已改為后臺(tái)提供接口,不再使用localStorage操作數(shù)據(jù),后臺(tái)項(xiàng)目使用MongoDB+node實(shí)現(xiàn),具體項(xiàng)目:https://github.com/junjunhuahua/mongodb-demo
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue使用iframe嵌入網(wǎng)頁(yè)的示例代碼
本篇文章主要介紹了vue使用iframe嵌入網(wǎng)頁(yè)的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
vuejs手把手教你寫一個(gè)完整的購(gòu)物車實(shí)例代碼
這篇文章主要介紹了vuejs-手把手教你寫一個(gè)完整的購(gòu)物車實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
在Vue-cli里應(yīng)用Vuex的state和mutations方法
今天小編就為大家分享一篇在Vue-cli里應(yīng)用Vuex的state和mutations方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vuejs 動(dòng)態(tài)添加input框的實(shí)例講解
今天小編就為大家分享一篇vuejs 動(dòng)態(tài)添加input框的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Ant?Design?of?Vue?select框獲取key和name的問題
這篇文章主要介紹了Ant?Design?of?Vue?select框獲取key和name的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
Vue?2?如何添加?register-service-worker?實(shí)現(xiàn)緩存請(qǐng)求的問題
這篇文章主要介紹了Vue?2?如何添加?register-service-worker?以實(shí)現(xiàn)緩存請(qǐng)求的目的,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11

