入门学习
简介
什么是 Vue
Vue.js 是一款流行的 JavaScript 前端框架,目的是简化 Web 开发。Vue 所关注的核心是 MVC 模式中的视图层,同时,它也能方便地获取数据更新,实现视图与模型的交互。
MVVM思想
-
M:即 Model,模型,包括数据和一些基本操作,
-
V:即 View,视图,页面渲染结果,
-
VM:即 View-Model,模型与视图间的双向操作(无需开发人员干涉), 在 MVVM 之前,开发人员从后端获取需要的数据模型,然后要通过 DOM 操作 Model 渲染 到 View 中。而后当用户操作视图,我们还需要通过 DOM 获取 View 中的数据,然后同步到 Model 中。
而 MVVM 中的 VM 要做的事情就是把 DOM 操作完全封装起来,开发人员不用再关心 Model 和 View 之间是如何互相影响的: 只要我们 Model 发生了改变,View 上自然就会表现出来。 当用户修改了 View,Model 中的数据也会跟着改变。 把开发人员从繁琐的 DOM 操作中解放出来,把关注点放在如何操作 Model 上。
Vue 的生命周期
-
created方法:在页面渲染前执行的方法。此方法一般用于获取 json数据
-
mounted方法:在页面渲染后执行的方法。
<div id="app"> {{msg}} </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { msg:'hello' }, created() { //在页面渲染之前执行 debugger //加断点 console.log('created.....') }, mounted() {//在页面渲染之后执行 debugger console.log('mounted.....') } }) </script>
语法
vue指令语法:v-指令名称。
声明式渲染:Vue.js 的核心是允许采用简洁的模板语法来声明式地将数据渲染进 DOM的系统。将 vue.min.js文件引入文件夹,然后在页面中引入 vue,
-
引入 vue文件 或 npm init -y 然后 npm install vue(或npm install vue@2.6.10)
-
编写 vue代码
//绑定 vue作用的范围 el:'#app',
{{}} 插值表达式
-
只能用在标签体里面;所以如果想让标签头里面动态绑定数据,就得用 v-bind 等,
-
{{表达式}}该表达式支持 JS 语法,可以调用 js 内置函数(必须有返回值);
-
表达式必须有返回结果。例如 1 + 1,没有结果的表达式不允许使用,如:let a = 1 + 1;
-
可以直接获取 Vue 实例中定义的数据或函数; 可以使用 v-text 和 v-html 指令来替代 {{}}。
会产生插值闪烁现象,就是如果网速很慢,就会先显示出 {{message}},然后再显示对应的值。v-html 和 v-text 则不会有这种现象。
<body> <script src="vue.min.js"></script> <!--id标识 vue作用的范围--> <div id="app"> <!-- {{}}插值表达式,绑定 vue中的 data 的 message 数据--> {{message}} </div> <script> //创建一个 vue对象 new Vue({ //绑定 vue作用的范围 el:'#app', //定义页面中显示的模型数据 data: { message: 'hello vue' } }) </script> </body>
v-html
将数据输出到元素内部,如果输出的数据有 HTML 代码,会被渲染,比如 message 是以下这样子,就会解析然后输出 h1标题的 Hello。
<h1>Hello</h1>
v-text
将数据输出到元素内部,如果输出的数据有 HTML 代码,会作为普通文本输出。比如 message 是以上这样子,就会直接输出 <h1>Hello</h1>。
v-bind 单向绑定指令
单项绑定,即数据变化的时候,页面的元素会发生变化,但是页面元素变化了,数据是不会变的。想用双向绑定得用 v-model。
v-bind 简写为 : 。用在标签属性上,通过指令获取 data 中定义的变量值。
而且在将 v-bind
用于 class
和 style
时,Vue.js 做了专门的增强。
<div id="app"> <div v-bind:href="msg">单向绑定1</div> <div :href="msg">单向绑定2,简写方式</div> </div> <script> new Vue({ el: '#app', data: { href:'http://www.baidu.com' } }) </script>
绑定 class
class 有一个 active属性,这个属性的意思就是控制这个 class 的生效与否,这个属性的值动态绑定了 isActive,isActive 为 true 时,此 class 则有,否则则无;
然后还有一个 text-danger属性,因为有 - 这个特殊符号,所以需要用 '' 引起来,然后这个属性的值动态绑定了 hasError。
<div v-bind:class="{active:isActive, 'text-danger':hasError}"> </div> <script> let vm = new Vue({ el: "#app", data: { isActive: true, hasError: false } }) </script>
绑定 style
v-bind:style
的对象语法十分直观,看着非常像 CSS,但其实是一个 JavaScript 对象。style属性名可以用驼峰式(camelCase)或短横线分隔(kebab-case,这种方式记得用单引号括起来)来命名。例如:'font-size' -> fontSize。
结果:<div style="color: red; font-size: 30px;"></div> <div id="app" v-bind:style="{color:activeColor, fontSize:fontSize+'px'}"> </div> <script> let vm = new Vue({ el: "#app", data: { activeColor: 'red', fontSize: 30 } }) </script>
v-model 双向绑定指令
-
当数据发生变化的时候,视图也会跟着发生变化
-
数据模型发生了改变,会直接显示在页面上
-
当视图发生变化的时候,数据也会跟着同步变化
-
用户在页面上的修改,会自动同步到数据模型中去
<div id="app"> <input type="checkbox" v-model="language" value="Java" />Java<br /> <input type="checkbox" v-model="language" value="PHP" />PHP<br /> <input type="checkbox" v-model="language" value="Python" />Python<br /> <h1> 你选择了:{{language.join(',')}} </h1> </div> <script src="../node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> let vm = new Vue({ el: "#app", data: { language: [] } }) </script>
v-on 事件指令
v-on 指令用于给页面元素绑定事件。
语法是:v-on:事件名称="js 片段或函数名",简写为 @事件名称="调用的方法"。
<div id="app"> <!--事件中直接写 js 片段--> <button v-on:click="num++">点赞</button> <!--事件指定一个回调函数,必须是 Vue 实例中定义的函数--> <button v-on:click="decrement">取消</button> <h1>有{{num}}个赞</h1> </div> <script src="../node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> let vm = new Vue({ el: "#app", data: { num: 100 }, methods: {decrement() { this.num--; //要使用 data 中的属性,必须 this.属性名 } } }) </script>
事件修饰符
当父子两个 div 都绑定了事件时,你点击子 div,不仅会触发子 div 的事件,也会出发父 div 的事件,这时候就需要进行区分了。
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。 为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。修饰符是由点开头的指令后缀来表示的。
-
.stop
:阻止事件冒泡到父元素, -
.prevent
:阻止默认事件发生, -
.capture
:使用事件捕获模式, -
.self
:只有元素自身触发事件才执行。(冒泡或捕获的都不执行), -
.once
:只执行一次,
效果:右键“点赞”,不会触发默认的浏览器右击事件;右键“取消”,会触发默认的浏览器右击事件) <div id="app"> <!--右击事件,并阻止默认事件发生--> <button v-on:contextmenu.prevent="num++">点赞</button><br /> <!--右击事件,不阻止默认事件发生,阻止冒泡--> <button v-on:stop="decrement($event)">取消</button><br /> <h1>有{{num}}个赞</h1> </div> <script src="../node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> let app = new Vue({ el: "#app", data: { num: 100 }, methods: { decrement(ev) { // ev.preventDefault(); this.num--; } } }) </script>
按键修饰符
在监听键盘事件时,我们经常需要检查常见的键值。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` --> <input v-on:keyup.13="submit"> 记住所有的 `keyCode` 比较困难,所以 Vue 为最常用的按键提供了别名: <!-- 同上 --> <input v-on:keyup.enter="submit"> <!-- 缩写语法 --> <input @keyup.enter="submit"> 全部的按键别名: `.enter` `.tab` `.delete` (捕获“删除”和“退格”键) `.esc` `.space` `.up` `.down` `.left` `.right`
组合按钮
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。.ctrl
、.alt
、.shift
。
<!-- Alt + C --> <input @keyup.alt.67="clear"> <!-- Ctrl + Click --> <div @click.ctrl="doSomething">Do something</div>
v-if 条件指令
v-if="boolean变量",v-else-if,v-else。
v-if 如果是 false,就会直接不显示,v-show 则是使用样式进行隐藏的。
<div id="app"> <input type="checkbox" v-model="ok"/>同意许可协议 <br/> <div v-if="ok">选中了</div> <div v-else>没有选中</div> <h1 v-show="ok">show</div> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { ok:false } }) </script>
v-for 循环指令
v-for="对象 in 集合"、v-for="(对象,index) in 集合"。index 即数组索引值,即数组角标,从 0 开始,这个 index 取什么名字都可以。user 即当前遍历的数组元素别名,userList 即要进行遍历的数组。
:key 是方便遍历的时候区分不同数据的,提高 vue 的渲染效率。这个是个优化方向。
<div id="app"> <div v-for="user in userList" :key="user.id"> {{user.name}} -- {{user.age}} </div> <div v-for="(user,index) in userList"> {{index}} -- {{user.name}} -- {{user.age}} </div> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { userList:[ {"name":"lucy","age":20}, {"name":"mary","age":30} ] } }) </script>
遍历对象
v-for 不仅可以遍历数组,也可以遍历对象。语法为:
-
v-for="value in object",1 个参数时,得到的是对象的属性值,
-
v-for="(value,key) in object",2 个参数时,第一个是属性值,第二个是属性名,
-
v-for="(value,key,index) in object",3 个参数时,第三个是索引,从 0 开始,
<li v-for="(value, key, index) in user" :key="index"> {{index + 1}}. {{key}} - {{value}} </li> data: { user: { name: '张三', gender: '男', age: 18 } }
小知识
随机数
random=Math.random()
页面跳转
this.$router.push({path:'/hospitalSet/list'})
dom渲染完毕后再执行回调函数
this.$nextTick(() -> {});
8.2、页面获取路径中的参数
如果是直接传参,没有 key=value,就是第一种;是的话就是第二种
const id = this.$route.params.id let token = this.$route.query.token
8.3、两个地址共用一个页面
//router/index.js { path: 'add', name: '添加医院设置', component: () => import('@/views/hospitalset/add'), meta: { title: '添加医院设置', icon: 'tree' } }, //隐藏路由,路径会显示 edit,但实际并没有增加页面 { path: 'edit/:id', name: 'EduTeacherEdit', component: () =>import('@/views/hospitalset/add'), meta: { title: '编辑医院设置', noCache: true }, hidden: true } //views的自定义 vue页面 //如果路径有 id,做数据显示 if(this.$route.params && this.$route.params.id){ const id = this.$route.params.id this.getById(id) }else{ //清空表单数据,防止数据遗留 this.hospitalSet = {} }
8.3.1、组件重用问题
-
问题 可能会出现组件重用问题,即 vue-router导航切换 时,如果两个路由都渲染同个组件,
组件的生命周期方法(created或者mounted)不会再被调用, 组件会被重用,显示上一个路由渲染出来的组件
-
解决方法 可以简单的在 router-view上加上一个唯一的key,来保证路由切换时都会重新触发生命周期方法,确保组件被重新初始化,
src/views/layout/components/AppMain.vue,分别在 template的 transition下和 script下的 computed下加入语句
<router-view :key="key"></router-view> key() {return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()}
8.4、从父节点递归查子节点
不需要后端递归查出数据然后给前端,后端只需要有一个 通过父节点能查询到子节点数据信息的接口即可,前端通过 vue组件实现递归查找。
8.4.1、后端
@ApiOperation(value = "根据父节点 id查询子数据列表") @GetMapping("findChildById/{id}") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "父节点id", paramType = "path", dataType = "Long", required = true) }) public Result findChildById(@PathVariable("id") Long id) { List<Dict> dictList = dictService.findChildById(id); return Result.success(dictList); }
8.4.2、前端
<el-table :data="list" style="width: 100%" row-key="id" border lazy :load="getChildrens" :tree-props="{children: 'children', hasChildren: 'hasChildren'}"> <el-table-column label="名称" width="230" align="left"> <template slot-scope="scope"> <span>{{ scope.row.name }}</span> </template> </el-table-column> </el-table> methods: { //递归查询子节点数据 getChildrens(tree, treeNode, resolve) { dict.findChildById(tree.id).then(response => { resolve(response.data) }) }, //数据字典列表 getDictList(id){ dict.findChildById(id).then(response=>{ this.list = response.data }) }, },
8.5、读写 excel
使用 EasyExcel来完成。
-
在后端引入依赖,
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.1</version> </dependency>
-
然后创建实体类,在实体类的属性上添加 @ExcelProperty(value="表头名称",index=0)
8.5.1、写出 excel
在方法中,设置 filePath,然后使用 EasyExcel.write( filePath, 实体类.class).sheet("表信息").doWrite(list);
8.5.1.1、后端
@Override public void exportData(HttpServletResponse response) { try { //设置下载信息 response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); //这里URLEncoder.encode可以防止中文乱码 当然和 easyexcel没有关系 String fileName = URLEncoder.encode("数据字典", "UTF-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); List<Dict> dictList = baseMapper.selectList(null); List<DictEeVo> dictVoList = new ArrayList<>(dictList.size()); dictList.forEach((dict) -> { DictEeVo dictVo = new DictEeVo(); BeanUtils.copyProperties(dict, dictVo); dictVoList.add(dictVo); }); EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("数据字典").doWrite(dictVoList); } catch (IOException e) { e.printStackTrace(); } } @ApiOperation(value = "导出数据字典 excel表格") @GetMapping("exportData") public Result exportData(HttpServletResponse response) { dictService.exportData(response); return Result.success(); }
8.5.1.2、前端
<div class="el-toolbar"> <div class="el-toolbar-body" style="justify-content: flex-start;"> <!-- <a href="http://localhost:8202/admin/cmn/dict/exportData" target="_blank"> --> <el-button type="primary" size="mini" @click="exportData"><i class="fa fa-plus"/> 导出</el-button> <!-- </a> --> </div> </div> exportData(){window.location.href = 'http://localhost:8202/admin/cmn/dict/exportData'},
8.5.2、读入 excel
需要一个监听器类 extends AnalysisEventListener<T>,监听 EasyExcel的读取操作,然后实现其 invokeHeadMap(读取表头信息)、invoke(一行行读取,从第二行开始读)、doAfterAllAnalysed(读取之后执行)方法,这样就可控制读取操作的细节了。
在方法中,设置 filePath,使用 EasyExcel.read(filePath, 实体类.class, new 监听器类).sheet().doRead();
8.5.2.1、后端
public class DictListener extends AnalysisEventListener<DictEeVo> { private DictMapper dictMapper; public DictListener(DictMapper dictMapper) { this.dictMapper = dictMapper; } //从第二行开始,一行一行读取 @Override public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) { Dict dict = new Dict(); BeanUtils.copyProperties(dictEeVo, dict); dictMapper.insert(dict); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } } @Override public void importData(MultipartFile multipartFile) { try { EasyExcel.read(multipartFile.getInputStream(),DictEeVo.class,new DictListener(baseMapper)).sheet().doRead(); } catch (IOException e) { e.printStackTrace(); } } @ApiOperation(value = "导入 excel表格") @PostMapping("importData") public Result importData(MultipartFile multipartFile) { dictService.importData(multipartFile); return Result.success(); }
8.5.2.2、前端
<el-button type="primary" plain size="mini" @click="importData"><i class="fa fa-plus"/>导入</el-button> <el-dialog title="导入" :visible.sync="dialogImportVisible" width="480px"> <el-form label-position="right" label-width="170px"> <el-form-item label="文件"> <el-upload :multiple="false" :on-success="onUploadSuccess" :action="'http://localhost:8202/admin/cmn/dict/importData'" class="upload-demo"> <el-button size="mini" type="primary">点击上传</el-button> <div slot="tip" class="el-upload__tip">只能上传 excel文件,且不超过500kb</div> </el-upload> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogImportVisible = false">取消</el-button> </div> </el-dialog> //上传成功时会调用 onUploadSuccess(){ //关闭弹窗 this.dialogImportVisible = false //刷新页面 this.getDictList(1) }, //导出数据字典 excel表格 importData(){this.dialogImportVisible = true},
其他工具
computed 计算属性
某些结果是基于之前数据实时计算出来的,我们可以利用计算属性来完成。只要是算式中使用到的值,发生改变时就会重新进行计算。
<div id="app"> <ul> <li>西游记:价格{{xyjPrice}},数量: <input type="number" v-model="xyjNum"></li> <li>水浒传:价格{{shzPrice}},数量: <input type="number" v-model="shzNum"></li> <li>总价:{{totalPrice}}</li> </ul> </div> <script src="../node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> let app = new Vue({ el: "#app", data: { xyjPrice: 56.73, shzPrice: 47.98, xyjNum: 1, shzNum: 1 }, // 计算属性 computed: { totalPrice(){ return this.xyjPrice*this.xyjNum + this.shzPrice*this.shzNum; } }, }) </script>
watch 侦听器
watch 可以让我们监控一个值的变化。从而做出相应的反应。
<div id="app"> <ul> <li>西游记:价格{{xyjPrice}},数量: <input type="number" v-model="xyjNum"></li> <li>水浒传:价格{{shzPrice}},数量: <input type="number" v-model="shzNum"></li> <li>总价:{{totalPrice}}</li> {{msg}} </ul> </div> <script src="../node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> let app = new Vue({ el: "#app", data: { xyjPrice: 56.73, shzPrice: 47.98, xyjNum: 1, shzNum: 1, msg:"" }, computed: { totalPrice(){ return this.xyjPrice*this.xyjNum + this.shzPrice*th is.shzNum; } }, watch: { // 监控 xyjNum 这个属性值, xyjNum(newVal, oldVal){ if(newVal >= 3){ this.msg = "西游记没有更多库存了,最多只能买两本"; this.xyjNum = 2; }else{ this.msg = ""; } } } }) </script>
filters 过滤器
过滤器不改变真正的data
,而只是改变渲染的结果,并返回过滤后的版本。在很多不同的
情况下,过滤器都是有用的,比如尽可能保持 API响应的干净,并在前端处理数据的格式。示例:展示用户列表性别显示男女。
<!-- 使用代码块也可以实现,但是有代码侵入 --> <td>{{user.gender===1? "男":"女"}}</td> <!-- | 是管道符号:表示使用后面的过滤器处理前面的数据。vue3 中已经过时了 --> <td>{{user.gender | genderFilter}}</td> <!-- 局部过滤器 --> let app = new Vue({ el: "#app", data: { userList: [ { id: 1, name: 'jacky', gender: 1 }, { id: 2, name: 'peter', gender: 0 } ] }, // filters 定义局部过滤器,只可以在当前 vue 实例中使用 filters: { genderFilter(gender) { return gender === 1 ? '男~' : '女~' } } });
组件化
在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。但是如果每个页面都独自开发,这无疑增加了我们开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。在 vue 里,所有的 vue 实例都是组件。
组件化开发时,@符号就代表了当前目录,比如:src。
全局组件
我们通过 Vue 的 component 方法来定义一个全局组件。
组件其实也是一个 Vue 实例,因此它在定义时也会接收:data、methods、生命周期函
数等,不同的是组件不会与页面的元素绑定,否则就无法复用了,因此没有 el 属性。
但是组件渲染需要 html 模板,所以增加了 template 属性,值就是 HTML 模板。全局组件定义完毕,任何 vue 实例都可以直接在 HTML 中通过组件名称来使用组件了。
data 必须是一个函数,不再是一个对象。
<div id="app"> <!--使用定义好的全局组件--> <counter></counter> </div> <script src="../node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> // 定义全局组件,两个参数:第一个,组件名称。第二个,组件参数 Vue.component("counter", { template: '<button v-on:click="count++">你点了我 {{ count }} 次,我记住了.</button>', data() { return { count: 0 } } }) let app = new Vue({ el: "#app" }) </script>
局部组件
一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着 Vue 的加载而加载。因此,对于一些并不频繁使用的组件,我们会采用局部注册。我们先在外部定义一个对象,结构与创建组件时传递的第二个参数一致:
-
components 就是当前 vue 对象子组件集合。
-
其 key 就是子组件名称,其值就是组件对象名
-
效果与刚才的全局注册是类似的,不同的是,这个 counter 组件只能在当前的 Vue 实例 中使用。
// 定义局部组件 const buttonCounter = { template: '<button v-on:click="count++">你点了我 {{ count }} 次,我记住了.</button>', data() { return { count: 0 } } }; # 在指定 Vue 中可以使用这个组件。<buttonCounter></buttonCounter> let app = new Vue({ el: "#app", components: { buttonCounter: buttonCounter // 将定义的对象注册为组件, // 可以直接简写成 buttonCounter。 } })
生命周期和钩子函数
每个 Vue 实例在被创建时都要经过一系列的初始化过程 :创建实例,装载模板,渲染模
板等等。Vue 为生命周期中的每个状态都设置了钩子函数(监听函数)。每当 Vue 实例处于
不同的生命周期时,对应的函数就会被触发调用。
生命周期
钩子函数
-
beforeCreated:我们在用Vue时都要进行实例化,因此,该函数就是在Vue实例化时调用,也可以将他理解为初始化函数比较方便一点,在 Vue1.0 时,这个函数的名字就是 init。
-
created:在创建实例之后进行调用。
-
beforeMount:页面加载完成,没有渲染。如:此时页面还是{{name}}
-
mounted:我们可以将他理解为原生js中的window.οnlοad=function({.,.}),或许大家也在用jquery,所以也可以理解为jquery中的$(document).ready(function(){….}),他的功能就是:在dom文档渲染完毕之后将要执行的函数,该函数在Vue1.0版本中名字为
-
compiled。此时页面中的{{name}}已被渲染成张三
-
beforeDestroy:该函数将在销毁实例前进行调用。
-
destroyed:改函数将在销毁实例时进行调用。
-
beforeUpdate:组件更新之前。
-
updated:组件更新之后。
项目结构
build
这个文件夹是跟打包工具 webpack 相关的代码,
config
防止配置信息的文件夹,比如端口配置等。
node_modules
当前 vue项目安装的所有依赖。
src
我们编写代码的文件夹。
main.js
是主程序。
App.vue
前端项目的页面框架。
static
防止静态资源的文件夹。
其他
其他都是一些配置文件。
package.json
vue项目的核心配置文件,包含项目信息,项目依赖,项目启动相关脚本,有 npm下载的依赖包的配置信息。
简单使用
使用脚手架进行模块化开发
初始化项目
// vue 脚手架使用 webpack 模板初始化一个项目名字叫 vue-demo 的项目 vue init webpack vue-demo // 第一步确认项目名,不改就直接回车;第二部是项目描述信息,写完后回车; // 项目构建信息一般选上面推荐的 recommended;vue-router页面跳转,y; // ESLint规范代码,n;之后都是 n;除了选择 npm 作为包管理工具。
启动项目
// 项目的 package.json 中有 scripts,代表我们能运行的命令 // 启动项目 npm start = npm run dev // 将项目打包 npm run build
element-ui
简介
element-ui 是饿了么前端出品的基于 Vue.js的 后台组件库,方便程序员进行页面快速布局和构建,官网:Element - The world's most popular Vue UI framework。
下载或引入
npm下载
npm install element-ui = npm i element-ui
简单使用
// vue 在 main.js 中引入 element-ui 就可以全局使用了。 import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI);
树形组件
实例1
懒加载+局部刷新。
<template> <el-tree :props="props" :load="loadNode" lazy show-checkbox> <span class="custom-tree-node" slot-scope="{ node, data }"> <span>{{ node.label }}</span> <span> <!-- TODO 以后记得改逻辑 --> <el-button node-key="id" v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">新增</el-button> <el-button node-key="id" v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">删除</el-button> </span> </span> </el-tree> </template> <script> // 这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等) // 例如:import 《组件名称》 from '《组件路径》'; export default { // import 引入的组件需要注入到对象中才能使用 components: {}, props: {}, data() { return { props: { label: "name", isLeaf: false, }, }; }, // 方法集合 methods: { // 加载子树的方法 // node 其实是需要展开树节点,但是第一次的 node 是个无用的数据,可以认为这个 level 等于 0 的 node 是 element 给我们创建的 loadNode(node, resolve) { if (node.level == 0) { this.getCategory(0, resolve); } else { this.getCategory(node.data.id, resolve); } }, // 根据父级主键查询分类列表 getCategory(id, resolve) { this.$http({ url: this.$http.adornUrl("/product/category/list/" + id), method: "get", }).then(({data}) => { console.log("成功获取类目!", data.data); return resolve(data.data); }); }, // 新增分类 append(data) { }, // 删除分类 remove(node, data) { var idList = [data.id] this.$http({ url: this.$http.adornUrl('/product/category/list'), method: 'delete', data: this.$http.adornData(idList, false) }).then(({data}) => { console.log("成功删除类目!") // 局部刷新 node.parent.loaded = false; node.parent.expand() }) }, }, // 计算属性 类似于 data 概念 computed: {}, // 监控 data 中的数据变化 watch: {}, // 生命周期 - 创建完成(可以访问当前 this 实例) created() {}, // 生命周期 - 挂载完成(可以访问 DOM 元素) mounted() {}, // 生命周期-创建之前 beforeCreate() {}, // 生命周期 - 挂载之前 beforeMount() {}, // 生命周期 - 更新之前 beforeUpdate() {}, // 生命周期 - 更新之后 updated() {}, // 生命周期 - 销毁之前 beforeDestroy() {}, // 生命周期 - 销毁完成 destroyed() {}, // 如果页面有 keep-alive 缓存功能,这个函数会触发 activated() {}, }; </script> <style scoped> </style>
vue模板
1、使用
-
解压文件到工作区
-
vscode中右键该文件打开终端,使用 npm install命令,根据 package.json下载所需依赖。下载好后会多一个 node_modules文件。记得提前修改镜像
npm config set registry https://registry.npm.taobao.org 或者自己指定(换了淘宝镜像后还是下载不了的情况) npm i -g node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
-
启动项目 npm run dev
1.1、配合 element-ui使用
-
添加组件元素,把 element-ui里拿出来的代码放进去,注意元素的主体位置
-
创建该组件需要的属性和方法,并为组件替换,
-
如果方法是需要调用接口的方法,那么还需要去 api中定义
2、框架理解
2.1、package.json
npm项目的核心配置文件,包含项目信息,项目依赖,项目启动相关脚本。
dev脚本:webpack-dev-server --inline --progress --config build/webpack.dev.conf.js
-
webpack-dev-server:一个小型的基于Node.js的http服务器,可以运行前端项目
-
--inline:一种启动模式
-
--progress:显示启动进度
-
--config build/webpack.dev.conf.js:指定webpack配置文件所在位置
2.2、src
2.2.1、main.js
import各种依赖,最后 new Vue
2.2.2、api(重要)
定义接口地址
2.2.3、assets
相关的静态资源
2.2.4、components
相关的第三方组件
2.2.5、views(重要)
各种页面
2.2.6、router(重要)
路由
3、基于模板开发
utils/request.js已经为我们做了 axios等的基础的封装,我们只需要在 api中 import request from '@/utils/request' 引入,然后再做路由,别的页面引用 api即可。
修改 src/utils/request.js的 response拦截器,编码为 200视作通过。
在 package.json文件中可以修改插件的版本,但是修改版本需要去 node_modules中删除对应的文件夹,然后 npm install。
还可以下载其他插件,比如:npm install js-cookie
3.1、登录修改
-
修改 src/store/modules/user.js的 Login、GetInfo、LogOut这三个方法的数据校验代码
-
修改 src/utils/request.js的 config.handers[]内的值,变成 token
3.2、添加路由
路由信息在 src/router/index.js中。复制一份已有的进行修改即可。
-
path就是路径,子模块有父模块的路径
-
component: () => import('@/views/table/index') 就是在设置跳转的页面路径,
3.2.1、例子1
在 views下创建 hospitalset文件夹,再在 hospitalset文件夹下创建 list.vue、add.vue,然后在 src/router/index.js添加路由
{ path: '/hospitalSet', component: Layout, redirect: '/hospitalSet/list', name: '医院设置管理', meta: { title: '医院设置管理', icon: 'example' }, children: [ { path: 'list', name: '医院设置列表', component: () => import('@/views/hospitalset/list'), meta: { title: '医院设置列表', icon: 'table' } }, { path: 'add', name: '添加医院设置', component: () => import('@/views/hospitalset/add'), meta: { title: '添加医院设置', icon: 'tree' } } ] },
3.3、定义接口路径
在 api文件夹下创建 js文件,并定义接口路径,然后使用的时候 import即可
3.3.1、例子1
修改 config/dev.env.js内的 BASE_API,BASE_API: '"http://localhost:8201"',
在 src/api下创建 hospitalset.js,url部分使用 反引号`,就可以在路径中引用值了,当然也可以做字符串拼接,但是不好。
import request from '@/utils/request' export default{ //条件查询医院设置信息并分页 getHospitalSet(current,limit,hospitalSetQueryVo){ return request({ url: `/admin/hospital/hospitalSet/findByConditionWithPage/${current}/${limit}`, method: 'post', //使用 json data: hospitalSetQueryVo }) }, //逻辑删除医院设置 deleteHospitalSet(id){ return request({ url: `/admin/hospital/hospitalSet/removeById/${id}`, method: 'delete' }) }, //批量逻辑删除医院设置 batchDeleteHospitalSet(idList){ return request({ url: `/admin/hospital/hospitalSet/batchRemoveById`, method: 'delete', data: idList }) }, //锁定/解锁 lockOrUnlock(id,status){ return request({ url: `/admin/hospital/hospitalSet/lockOrUnlockById/${id}/${status}`, method: 'put' }) }, }
3.4、vue页面编写
data中定义变量,created中使用 methods的方法进行页面初始化,methods中定义方法
3.4.1、例子
<template> <div class="app-container"> <!-- 条件查询框 --> <el-form :inline="true" class="demo-form-inline"> <el-form-item> <el-input v-model="hospitalSetQueryVo.hosname" placeholder="医院名称" /> </el-form-item> <el-form-item> <el-input v-model="hospitalSetQueryVo.hoscode" placeholder="医院编号" /> </el-form-item> <el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button> <!-- <el-button type="default" @click="resetData()">清空</el-button> --> </el-form> <!-- 批量删除按钮 --> <div> <el-button type="danger" size="mini" @click="removeRows()">批量删除</el-button> </div> <!-- 医院设置信息表格 --> <el-table :data="list" style="width: 100%" height="338" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="45"/> <el-table-column type="index" label="序号" width="50" /> <el-table-column prop="hosname" label="医院名称" width="200" /> <el-table-column prop="hoscode" label="医院编码" width="200" /> <el-table-column prop="apiUrl" label="api基础路径" width="200" /> <el-table-column prop="contactsName" label="联系人" width="100" /> <el-table-column prop="contactsPhone" label="手机号" width="120" /> <el-table-column label="状态" width="70"> <template slot-scope="scope"> {{ scope.row.status === 1 ? "可用" : "不可用" }} </template> </el-table-column> <el-table-column fixed="right" label="操作" width="200"> <template slot-scope="scope"> <!-- <el-button @click="handleClick(scope.row)" size="mini" icon="el-icon-search" /> --> <el-button type="primary" size="mini" icon="el-icon-edit" /> <el-button v-if="scope.row.status==1" type="warning" size="mini" @click="lockOrUnlock(scope.row.id,0)" >锁定</el-button> <el-button v-if="scope.row.status==0" type="success" size="mini" @click="lockOrUnlock(scope.row.id,1)" >解锁</el-button> <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)" /> </template> </el-table-column> </el-table> <!-- 分页框 --> <el-pagination :current-page="current" :page-size="limit" :total="total" style="padding: 30px 0; text-align: center" layout="total, prev, pager, next, jumper" @current-change="getList" /> <!-- 调整每页条数的分页框 --> <!-- <el-pagination @size-change="handleSizeChange" @current-change="getList" :current-page.sync="currentPage2" :page-sizes="[5, 20, 50, 100]" :page-size="limit" layout="sizes, prev, pager, next" :total="total"> </el-pagination> --> </div> </template> <script> import hospitalset from "@/api/hospitalset"; export default { //定义变量和初始值 data() { return { //当前页 current: 1, //每页数量 limit: 5, //条件封装对象 hospitalSetQueryVo: {}, //每页的数据集合 list: [], total: 0, multipleSelection: [] }; }, //在页面渲染前执行 //一般调用 methods定义的方法,得到数据 created() { this.getList(); }, //定义方法,调用接口 methods: { //锁定和解锁 lockOrUnlock(id,status){ hospitalset.lockOrUnlock(id,status) .then(response=>{ this.getList() }) }, //批量删除医院设置 removeRows() { this.$confirm("此操作将删除医院是设置信息,是否继续?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { //点确定就执行 then() var idList = [] for(var i=0;i<this.multipleSelection.length;i++){ idList.push(this.multipleSelection[i].id) } hospitalset.batchDeleteHospitalSet(idList).then((response) => { //提示 this.$message({ type: "success", message: "删除成功!", }); //刷新页面 this.getList(1); }); }); }, //获取复选框所有的选中项赋值给括号中的变量,使其默认为数组变量 handleSelectionChange(selection){ this.multipleSelection = selection }, //删除医院设置 removeDataById(id) { this.$confirm("此操作将删除医院是设置信息,是否继续?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { //点确定就执行 then() hospitalset.deleteHospitalSet(id).then((response) => { //提示 this.$message({ type: "success", message: "删除成功!", }); //刷新页面 this.getList(1); }); }); }, //得到医院设置信息分页列表 getList(page = 1) { this.current = page; hospitalset .getHospitalSet(this.current, this.limit, this.hospitalSetQueryVo) .then((response) => { //console.log(response) this.list = response.data.records; this.total = response.data.total; }) .catch((error) => { console.log(error); }); }, }, }; </script>
在页面引入 js文件,然后使用 axios进行接口调用,把接口返回数据在页面显示
4、
前端开发知识
表单参数校验
<template> <el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible"> <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="110px"> <el-form-item label="检索首字母" prop="firstLetter"> <!--v-model.number="",这样可以让表单只接收数字--> <el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input> </el-form-item> </el-form> </el-dialog> </template> <script> export default { data () { dataRule: { name: [ { required: true, message: '品牌名不能为空', trigger: 'blur' } ], logo: [ { required: true, message: '品牌logo地址不能为空', trigger: 'blur' } ], status: [ { required: true, message: '状态[0-停用;1-启用]不能为空', trigger: 'blur' } ], firstLetter: [ { validator: (rule, value, callback) => { // Number.isInteger(value); 这个方法可以校验是不是一个整数 if (value === '') { callback(); } else if (!/^[a-zA-Z]$/.test(value)) { callback(new Error('首字母必须是英文字母')); } else { callback(); } }, trigger: 'blur' } ], } } }
父子组件传递数据
子组件给父组件传递数据,使用事件机制,子组件发送事件,事件携带数据,引用了子组件的父组件监听这个事件即可触发。
Element-UI 的树形控件中有事件机制,比如 node-click。
子组件
<template> <el-tree ref="categoryTree" :props="props" :load="loadNode" lazy @node-click="nodeClick"></el-tree> </template> <script> export default { // 方法集合 methods: { // 点击后调用此方法 nodeClick(data, node, component) { // 向父组件发送事件,事件名叫:tree-node-click this.$emit("tree-node-click", data, node, component) }, } } </script>
父组件
<template> <el-row :gutter="20"> <el-col :span="5"> <category @tree-node-click="treeNodeClicked"></category> </el-col> </el-row> </template> <script> // 这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等) // 例如:import 《组件名称》 from '《组件路径》'; import Category from '../common/category'; import AddOrUpdate from './attrgroup-add-or-update'; export default { // import 引入的组件需要注入到对象中才能使用 components: { Category, AddOrUpdate }, // 方法集合 methods: { // 树节点被点击 treeNodeClicked(data, node, component) { }, } }