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

assets為全局css,圖片,以及一些工具類的js,components為vue的組件,router為路由配置,app.vue為主頁面的組件,config.js為目錄配置項,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ù),還會處理vue組件的加載,最后實(shí)例化vue。
5. App.vue
.vue文件是什么? https://cn.vuejs.org/v2/guide/single-file-components.html
App.vue可以認(rèn)為是應(yīng)用最外層的一個容器。
<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,否則會報warn -->
<el-submenu :index="'' + (index + 1)">
<template slot="title">{{ item.name }}</template>
<template v-for="(subItem, i) in item.value">
<!-- 此處index格式為父級的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, // 菜單項是否唯一開啟
menuRouter: true // 是否開啟路由模式
}
},
mounted: function () {
...
},
watch: {
'$route' (to) {
this.menuIndex = to.name
}
}
}
</script>
這邊偷了一個懶,沒有把左側(cè)的menu單獨(dú)做成一個vue而是混入App.vue中。
6. 路由
在正式寫代碼之前,首先要確定要項目的結(jié)構(gòu),模塊如何劃分,哪個模塊對應(yīng)哪個路由。
因為整個項目現(xiàn)在就劃分出兩個大板塊,通訊錄與記賬本,所以路由第一級就只有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)系人頁面下,還能讓active狀態(tài)停留在左側(cè)我的聯(lián)系人上,可以看到App.vue中的代碼this.menuIndex = to.name就是進(jìn)行的該操作,
雖然這樣vue會報一個warn告訴我別重名[捂臉],暫時能想到的就是這樣的操作方式了,有考慮過依靠判斷path來確定是否顯示高亮狀態(tài),但是當(dāng)目錄層級較深且較復(fù)雜的情況下,這樣就不是很靠譜了。
component這里為什么是這種形式,而不是直接用一個組件名呢,因為當(dāng)路由開始多起來的時候,一下把所有的組件都加載進(jìn)來會非常非常慢且會加載到許多當(dāng)時并沒有用到的組件,通過import這種形式,可以讓webpack將路由變換時用到的組件分開打包,網(wǎng)頁會根據(jù)使用情況再進(jìn)行
由于router是vue的組件,所以使用時記得要Vue.use一下。
7. 聯(lián)系人列表頁 --- 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>高級檢索</h3>
<el-form ref="contactSearch" :model="searchParams" :inline=true>
<el-form-item label="姓名">
<el-input v-model="searchParams.name" placeholder="請輸入需要檢索的姓名"></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)于該模塊的主頁,新增與編輯頁面通過右上角的新建按鈕或者列表中的編輯按鈕進(jìn)入,查看頁面通過引入View.vue作為一個彈窗放在列表頁中展示,不單獨(dú)設(shè)置路由。
列表展示所使用的是elementUI的table組件
刪除對象時一定要使用$delete,否則不會觸發(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 () {
// 需手動關(guān)閉彈窗,找到父組件中調(diào)用的地方進(jìn)行事件的觸發(fā)
this.$parent.$refs.contactView.$emit('update:viewShow', false)
}
}
}
</script>
這里有個比較值得注意的點(diǎn),就是關(guān)閉查看彈窗,彈窗的開啟關(guān)閉狀態(tài)通過list也就是父級中的viewShow來控制,viewShow通過view也就是子級中的props流入到子級中,但是vue中的數(shù)據(jù)流向是默認(rèn)是單向的,想要子級中修改父級屬性必須使用emit,詳見上面代碼。
這里原先使用elementUI的dialog組件的自己的關(guān)閉,會報錯,只能自己修改了。
ps: 為什么這里不用vuex處理父子組件的通信?因為如果是一個大型的后臺管理系統(tǒng),像這樣的情況會經(jīng)常發(fā)生,如果都放在vuex中管理,那vuex的體積會非常龐大,反而不利于維護(hù)。
8. 聯(lián)系人編輯(新增)頁 --- 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="請選擇性別">
<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ī)號格式不正確'))
} else {
callback()
}
}
return {
type: '', // 控制是否是新建
...
rules: {
name: [{validator: nameValid, trigger: 'blur'}],
mobile: [
// {required: true, message: '手機(jī)號不能為空', 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: {
// 檢查頁面是新建還是編輯
checkPageStatus: function (id) { ... },
cancelForm: function () {
this.$router.push('/contact')
},
onSubmit: function (formName) { ... }
}
}
</script>
可以看到mounted與beforeRouteUpdate中的代碼有些重合,那是因為vue在路由僅僅只是參數(shù)變換的時候,是不會重新重新加載組件的,所以需要在beforeRouteUpdate中處理初始的數(shù)據(jù)。
nameValid與mobileValid為表單驗證的函數(shù),el-form配置rules屬性名稱,然后data中相應(yīng)的添加rules即可開啟表單驗證,但是有一點(diǎn)一定要注意el-form-item上一定要設(shè)置對應(yīng)的prop屬性,rules才會生效。
9. 總結(jié)
非常簡單的一個項目,但是有幾個點(diǎn)一定要關(guān)注好:
模塊的劃分,模塊劃分要合理,盡量能保證模塊的復(fù)用性
狀態(tài)的管理,一定要明確什么東西要放vuex中,什么東西不用放,以免使項目的維護(hù)反而變得更復(fù)雜
如果是大型項目,路由中一定要讓.vue文件在需要時再引入,否則會加重初次加載的負(fù)擔(dān)
為了減少篇幅,刪減了很多不重要的代碼,需要查看源碼請移步,項目地址: https://github.com/junjunhuahua/vue-basic-demo
github上的項目已改為后臺提供接口,不再使用localStorage操作數(shù)據(jù),后臺項目使用MongoDB+node實(shí)現(xiàn),具體項目:https://github.com/junjunhuahua/mongodb-demo
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
創(chuàng)建項目及包管理yarn create vite源碼學(xué)習(xí)
這篇文章主要為大家介紹了創(chuàng)建項目及包管理yarn create vite源碼學(xué)習(xí)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
vue translate peoject實(shí)現(xiàn)在線翻譯功能【新手必看】
這篇文章主要介紹了vue translate peoject實(shí)現(xiàn)在線翻譯功能,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06
使用Vue3和ApexCharts實(shí)現(xiàn)交互式3D折線圖
ApexCharts 是一個功能強(qiáng)大的 JavaScript 庫,用于創(chuàng)建交互式、可定制的圖表,在 Vue.js 中,它可以通過 vue3-apexcharts 插件輕松集成,本文給大家介紹了使用Vue3和ApexCharts實(shí)現(xiàn)交互式3D折線圖,需要的朋友可以參考下2024-06-06
教你使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏
這篇文章主要為大家介紹了使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
vue-cli-service build 環(huán)境設(shè)置方式
這篇文章主要介紹了vue-cli-service build 環(huán)境設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。2023-01-01
解決前后端分離 vue+springboot 跨域 session+cookie失效問題
這篇文章主要介紹了前后端分離 vue+springboot 跨域 session+cookie失效問題的解決方法,解決過程也很簡單 ,需要的朋友可以參考下2019-05-05

