如何利用vue+vue-router+elementUI实现简易通讯录


Posted in Javascript onMay 13, 2019

一个具有基本增删改查功能的通讯录,数据保存在本地的localStorage中。

 demo地址: https://junjunhuahua.github.io

1. 所用技术

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

# 创建一个基于 webpack 模板的新项目

$ vue init webpack contact

$ cd contact

# 安装依赖

$ npm install

$ npm run dev

这是vue官方基于webpack的脚手架,run dev后浏览器会自动打开localhost:8080,也可以使用run build命令,执行build命令后会自动将src目录中的内容进行编译打包压缩,然后在dist目录中可以看到这些文件

3. 目录结构

项目根目录:

如何利用vue+vue-router+elementUI实现简易通讯录 

build为构建项目所用的node代码,config为构建时的一些配置项,dist为打包后(npm run build 用于发布)的代码,node_modules为node模块,src为开发时所用的代码。

src目录:

如何利用vue+vue-router+elementUI实现简易通讯录

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,以及工具函数,还会处理vue组件的加载,最后实例化vue。

5. App.vue

.vue文件是什么?  https://cn.vuejs.org/v2/guide/single-file-components.html

App.vue可以认为是应用最外层的一个容器。

<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>我的应用</h3>
   <template v-for="(item, index) in menuData">
    <!-- 此处的index需显示转换为string,否则会报warn -->
    <el-submenu :index="'' + (index + 1)">
    <template slot="title">{{ item.name }}</template>
    <template v-for="(subItem, i) in item.value">
     <!-- 此处index格式为父级的index加上下划线加上当前的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: '', // 菜单当前所在位置
  menuUniqueOpen: true, // 菜单项是否唯一开启
  menuRouter: true // 是否开启路由模式
  }
 },
 mounted: function () {
  ...
 },
 watch: {
  '$route' (to) {
  this.menuIndex = to.name
  }
 }
 }
</script>

这边偷了一个懒,没有把左侧的menu单独做成一个vue而是混入App.vue中。

6. 路由

在正式写代码之前,首先要确定要项目的结构,模块如何划分,哪个模块对应哪个路由。

因为整个项目现在就划分出两个大板块,通讯录与记账本,所以路由第一级就只有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是相同的,这是为了让在新增或者编辑联系人页面下,还能让active状态停留在左侧我的联系人上,可以看到App.vue中的代码this.menuIndex = to.name就是进行的该操作,

虽然这样vue会报一个warn告诉我别重名[捂脸],暂时能想到的就是这样的操作方式了,有考虑过依靠判断path来确定是否显示高亮状态,但是当目录层级较深且较复杂的情况下,这样就不是很靠谱了。

component这里为什么是这种形式,而不是直接用一个组件名呢,因为当路由开始多起来的时候,一下把所有的组件都加载进来会非常非常慢且会加载到许多当时并没有用到的组件,通过import这种形式,可以让webpack将路由变换时用到的组件分开打包,网页会根据使用情况再进行

由于router是vue的组件,所以使用时记得要Vue.use一下。

7. 联系人列表页 --- contact/list.vue

<template>
 <div class="contact-list">
 <div class="contact-list-header">
  <el-button @click="goToNew" type="primary">新增联系人</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>联系人列表</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('此操作将永久删除该联系人, 是否继续?', '提示', {
   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相当于该模块的主页,新增与编辑页面通过右上角的新建按钮或者列表中的编辑按钮进入,查看页面通过引入View.vue作为一个弹窗放在列表页中展示,不单独设置路由。

列表展示所使用的是elementUI的table组件

删除对象时一定要使用$delete,否则不会触发视图更新

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 () {
  // 需手动关闭弹窗,找到父组件中调用的地方进行事件的触发
  this.$parent.$refs.contactView.$emit('update:viewShow', false)
  }
 }

 }
</script>

这里有个比较值得注意的点,就是关闭查看弹窗,弹窗的开启关闭状态通过list也就是父级中的viewShow来控制,viewShow通过view也就是子级中的props流入到子级中,但是vue中的数据流向是默认是单向的,想要子级中修改父级属性必须使用emit,详见上面代码。

这里原先使用elementUI的dialog组件的自己的关闭,会报错,只能自己修改了。

ps: 为什么这里不用vuex处理父子组件的通信?因为如果是一个大型的后台管理系统,像这样的情况会经常发生,如果都放在vuex中管理,那vuex的体积会非常庞大,反而不利于维护。

8. 联系人编辑(新增)页 --- 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('手机号格式不正确'))
  } else {
   callback()
  }
  }
  return {
  type: '', // 控制是否是新建
  ...
  rules: {
   name: [{validator: nameValid, trigger: 'blur'}],
   mobile: [
//   {required: true, message: '手机号不能为空', 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在路由仅仅只是参数变换的时候,是不会重新重新加载组件的,所以需要在beforeRouteUpdate中处理初始的数据。

nameValid与mobileValid为表单验证的函数,el-form配置rules属性名称,然后data中相应的添加rules即可开启表单验证,但是有一点一定要注意el-form-item上一定要设置对应的prop属性,rules才会生效。

9. 总结

非常简单的一个项目,但是有几个点一定要关注好:

模块的划分,模块划分要合理,尽量能保证模块的复用性

状态的管理,一定要明确什么东西要放vuex中,什么东西不用放,以免使项目的维护反而变得更复杂

如果是大型项目,路由中一定要让.vue文件在需要时再引入,否则会加重初次加载的负担

为了减少篇幅,删减了很多不重要的代码,需要查看源码请移步,项目地址: https://github.com/junjunhuahua/vue-basic-demo

github上的项目已改为后台提供接口,不再使用localStorage操作数据,后台项目使用MongoDB+node实现,具体项目:https://github.com/junjunhuahua/mongodb-demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Js四则运算函数代码
Jul 21 Javascript
jQuery点缩略图弹出层显示大图片
Feb 13 Javascript
JQuery中DOM事件冒泡实例分析
Jun 13 Javascript
jQuery自动完成插件completer附源码下载
Jan 04 Javascript
javascript匀速动画和缓冲动画详解
Oct 20 Javascript
canvas实现粒子时钟效果
Feb 06 Javascript
js和jquery中获取非行间样式
May 05 jQuery
JavaScript脚本语言是什么_动力节点Java学院整理
Jun 26 Javascript
基于jquery ajax的多文件上传进度条过程解析
Sep 11 jQuery
js DOM的事件常见操作实例详解
Dec 16 Javascript
基于vue-cli3+typescript的tsx开发模板搭建过程分享
Feb 28 Javascript
Javascript异步流程控制之串行执行详解
Sep 27 Javascript
vue百度地图 + 定位的详解
May 13 #Javascript
教你使用vue-cli快速构建的小说阅读器
May 13 #Javascript
详解js根据百度地图提供经纬度计算两点距离
May 13 #Javascript
解决前后端分离 vue+springboot 跨域 session+cookie失效问题
May 13 #Javascript
Vue.js@2.6.10更新内置错误处机制Fundebug同步支持相应错误监控
May 13 #Javascript
详解async/await 异步应用的常用场景
May 13 #Javascript
轻松解决JavaScript定时器越走越快的问题
May 13 #Javascript
You might like
一些被忽视的PHP函数(简单整理)
2010/04/30 PHP
php动态函数调用方法
2015/05/21 PHP
php实现批量修改文件名称的方法
2016/07/23 PHP
php实现自定义中奖项数和概率的抽奖函数示例
2017/05/26 PHP
PHP基于GD库实现的生成图片缩略图函数示例
2017/07/05 PHP
Docker 安装 PHP并与Nginx的部署实例讲解
2021/02/27 PHP
PHP获取php,mysql,apche的版本信息及更多服务器信息
2021/03/09 PHP
jQuery1.6 正式版发布并提供下载
2011/05/05 Javascript
jquery div 居中技巧应用介绍
2012/11/24 Javascript
Javascript实现滑块滑动改变值的实现代码
2013/04/12 Javascript
浅析ajax请求json数据并用js解析(示例分析)
2013/07/13 Javascript
chrome下jq width()方法取值为0的解决方法
2014/05/26 Javascript
Node.js中使用Buffer编码、解码二进制数据详解
2014/08/16 Javascript
javascript实现列表切换效果
2016/05/02 Javascript
js实现打地鼠小游戏
2017/02/13 Javascript
详解angularjs popup-table 弹出框表格指令
2017/09/20 Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
2017/10/23 Javascript
JavaScript怎样在删除前添加确认弹出框?
2019/05/27 Javascript
JavaScript ES6 Class类实现原理详解
2020/05/08 Javascript
[09:31]2016国际邀请赛中国区预选赛Yao赛后采访 答题送礼
2016/06/27 DOTA
Python实现的简单发送邮件脚本分享
2014/11/07 Python
详解Django中Request对象的相关用法
2015/07/17 Python
python操作字典类型的常用方法(推荐)
2016/05/16 Python
Python迭代器定义与简单用法分析
2018/04/30 Python
用Python分析3天破10亿的《我不是药神》到底神在哪?
2018/07/12 Python
使用Flask-Cache缓存实现给Flask提速的方法详解
2019/06/11 Python
OpenCV+Python--RGB转HSI的实现
2019/11/27 Python
Expedia意大利旅游网站:酒店、机票和租车预订
2017/10/30 全球购物
考勤制度通知
2015/04/25 职场文书
责任书范本大全
2015/05/11 职场文书
班级班风口号大全
2015/12/25 职场文书
爱岗敬业事迹材料
2019/06/20 职场文书
2019年圣诞节祝福语集锦
2019/12/25 职场文书
Python可变集合和不可变集合的构造方法大全
2021/12/06 Python
详解nginx安装过程并代理下载服务器文件
2022/02/12 Servers
spring boot实现文件上传
2022/08/14 Java/Android