Vue-Cli3 官网
https://cli.vuejs.org/zh/
vue-cli3 开发单文件组件
# js Vue.component({ }) new Vue({ })
### 缺点
- 全局定义组件的名字时 不能重复
- 字符串模板: es6提供模板字符串
- 不支持 CSS
- 没有构建步骤 Pug 、 Babel .vue
在Vue 中把 .vue 的文件成为,单文件组件,webpack等构建工具
单文件组件,可以获得:
- 完整语法高亮
- common JS模块
- 组件作用域的CSS
Vue CLI3 脚手架
基本配置
- 安装Nodejs
- https://nodejs.org/en/download
- 保证Node.js8.9 或者更高版本
- 终端中输入 `node -v ` , 保证已安装成功
安装淘宝镜像
https://npm.taobao.org/ npm install -g cnpm --registry=https://registry.npm.taobao.org // 以后的npm 可以用cnpm代替
安装Vue Cli3 脚手架
cnpm install -g @vue/cli
检查其他版本是否正确
vue --version
快速原型开发
使用 ` vue server ` 和 `vue build` 命令对单个 ` *.vue` 文件进行快速原型开发
不过这需要先额外安装一个全局的扩展:
cnpm install -g @vue/cli-service-global // -g 全局
vue serve // 的缺点就是它需要安装全局依赖
这使得它在不同机器上的一致性不能得到保证
因此这只适合于快速原型开发
需要的仅仅是一个 ` App.vue` 文件:
### vue ###
<template> <h3>{{msg}}</h3> </template> <script> export default { data(){ return{ msg:"我是你爸爸" }; }, } </script> <style scoped> h3{ color: red; } </style>
# scoped 表示注入的意思, 只对当前的h3 标签起作用
简单3步启动并访问
- npm init
- vue serve
- 访问 http://localhost:8080/
vue-cli3生成项目
vue-cli3官网 : https://cli.vuejs.org/zh/
在控制台创建 vue-cli3 项目
vue create <projectname> 1. vue create <projectname> 2. cd <projectname> 3. npm run serve
目录介绍
- node_modules : 包存储路径
- dependencies : 项目依赖
- devDependencies : 开发依赖
- public : 静态加载文件目录
- src : 一般开发目录
- assets : 当前项目的依赖
- components : 注入组件们
- main.js : 入口文件
- App.vue : app组件
购物车项目搭建
App.vue
<template> <div id="app"> <h1>{{alltitle}}</h1> <ul> <li v-for="itme in cartList" :key="itme.id"> <h2>{{itme.title}}</h2> <p>¥ {{itme.price}}</p> </li> </ul> <mycart :cart="cartList" :alltitle="alltitle"></mycart> </div> </template> <script> import mycart from './components/Cart' export default { name: 'app', data(){ return{ cartList:[ {id:1, title:'秘籍1', price: 999, action:true, count:1}, {id:2, title:'秘籍2', price: 1999, action:true, count:2}, {id:3, title:'秘籍3', price: 2999, action:true, count:3}, ], alltitle: "购物车", } }, components: { mycart } } </script>
./components/cart.vue
<template> <div> <h2>{{alltitle}}</h2> <table border="1"> <tr> <th>#</th> <th>课程</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="c in cart" :key="c.id" > <td><input type="checkbox" v-model="c.action"></td> <td>{{c.title}}</td> <td>¥{{c.price}}</td> <td> <button> - </button> {{c.count}} <button> + </button> </td> <td>¥{{c.price * c.count}}</td> </tr> </table> </div> </template> <script> export default { name:'cart', props:['alltitle','cart'] } </script>
购物车项目操作
app.vue
<template> <div id="app"> <h1>{{alltitle}}</h1> <ul> <li v-for="itme in cartList" :key="itme.id"> <h2>{{itme.title}}</h2> <p>¥ {{itme.price}}</p> </li> </ul> <mycart :cart="cartList" :alltitle="alltitle"></mycart> </div> </template> <script> // 导入文件 import mycart from './components/Cart' export default { name: 'app', data(){ return{ cartList:[ {id:1, title:'秘籍1', price: 999, active:true, count:1}, {id:2, title:'秘籍2', price: 1999, active:true, count:2}, {id:3, title:'秘籍3', price: 2999, active:true, count:3}, ], alltitle: "购物车", } }, components: { mycart } } </script>
cart.vue
<template> <div> <h2>{{alltitle}}</h2> <table border="1"> <tr> <th>#</th> <th>课程</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in cart" :key="c.id" > <td><input type="checkbox" v-model="c.active"></td> <td>{{c.title}}</td> <td>¥{{c.price}}</td> <td> <button @click="sub(index)"> - </button> {{c.count}} <button @click="add(index)"> + </button> </td> <td>¥{{c.price * c.count}}</td> </tr> <tr> <td></td> <!-- colspan 合并 --> <td colspan="2">{{activeCount}} / {{count}}</td> <td colspan="2">¥ {{total}}</td> </tr> </table> </div> </template> <script> export default { name:'cart', props:['alltitle','cart'], methods:{ remove(i){ if (window.confirm('是否确定删除')){ // 这个数组中 删除 那个索引的 1条数据 this.cart.splice(i,1); } }, sub(i){ // 取到当前的数量 let count = this.cart[i].count; // 如果大于1 ,'?' 相当于then,则 则 -1, ':' = 否则 删除那一条数据 count > 1 ? this.cart[i].count -=1 : this.remove(i); }, add(i){ // 取到当前的一条数据 +1 this.cart[i].count ++ }, }, computed:{ count(){ // cart的总数 return this.cart.length }, activeCount(){ // 过滤参数 把active 是false 过滤掉 // 返回 v.active 为 true的对象 return this.cart.filter(v=>v.active).length }, total(){ // let num = 0; // this.cart.forEach(c=>{ // if(c.active){ // num += c.price * c.count; // } // }); // return num // reduce 回调函数, 第一个参数是起始参数就是后面定义的0,第二个参数是数组遍历的对象 return this.cart.reduce((num,c)=>{ if (c.active){ num += c.price * c.count; } return num },0) } }, } </script>
Mock模拟数据
发起请求
安装
cnpm i axios -S # -S 保存
去入口文件 导入 axios对象 ,然后挂载到Vue 的原型上
import axios from 'axios' Vue.prototype.$http = axios;
可以在每个实例里 this.$http 拿到axios 对象
created(){ this.$http.get('/api/cartList') }
做数据持久化
入口文件 main.js
// 使用中央数据总线 创建一个bus,传递数据使用 Vue.prototype.$bus = new Vue;
App.vue
<template> <div id="app"> <h1>{{alltitle}}</h1> <ul> <li v-for="(itme, index) in cartList" :key="itme.id"> <h2>{{itme.title}}</h2> <p>¥ {{itme.price}}</p> <button @click="addCart(index)">添加商品</button> </li> </ul> <mycart :cart="cartList" :alltitle="alltitle"></mycart> </div> </template> <script> import mycart from './components/Cart' export default { name: 'app', data(){ return{ cartList:[], // cartList:[ // {id:1, title:'秘籍1', price: 999, active:true, count:1}, // {id:2, title:'秘籍2', price: 1999, active:true, count:2}, // {id:3, title:'秘籍3', price: 2999, active:true, count:3}, // ], alltitle: "购物车", } }, methods:{ addCart(i){ const good = this.cartList[i]; // 使用中央数据总线 传递数据到 cart this.$bus.$emit('addCart',good) } }, // created(){ // this.$http.get('/api/cartList') // .then(res=>{ // this.cartList = res.data.result; // }).catch(err=>{ // console.log(err); // }) // }, async created(){ try{ const res = await this.$http.get('/api/cartList') this.cartList = res.data.result } catch (err) { console.log(err) } }, components: { mycart }, } </script>
cart.vue
<template> <div> <h2>{{alltitle}}</h2> <table border="1"> <tr> <th>#</th> <th>课程</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in cart" :key="c.id" > <td><input type="checkbox" v-model="c.active"></td> <td>{{c.title}}</td> <td>¥{{c.price}}</td> <td> <button @click="sub(index)"> - </button> {{c.count}} <button @click="add(index)"> + </button> </td> <td>¥{{c.price * c.count}}</td> </tr> <tr> <td></td> <!-- colspan 合并 --> <td colspan="2">{{activeCount}} / {{count}}</td> <td colspan="2">¥ {{total}}</td> </tr> </table> </div> </template> <script> export default { name:'cart', props:['alltitle',], data(){ return{ // 初始一个空数组,存放添加或者删除的数据 // 刷新网页时 获取里面的数据,如果没有数据就是一个空数组 cart:JSON.parse(localStorage.getItem('cart')) || [], } }, watch:{ // 深度监听deep:true cart 数据 cart:{ handler(n){ this.setLocalData(n); }, deep:true } }, created() { // 从中央数据总线里把数据取出来, this.$bus.$on('addCart',good=>{ // find查找那个 商品对应的ID const ret = this.cart.find(v=>v.id===good.id); // 如果购物车 没有数据 商品那一条数据, 则会添加进 那个列表里 if (!ret){ this.cart.push(good); }else{ // 如果有 则会自加1 ret.count ++ } }) }, methods:{ setLocalData(n){ // 可以计算总数量 localStorage.setItem('cart',JSON.stringify(n)); }, remove(i){ if (window.confirm('是否确定删除')){ // 这个数组中 删除 那个索引的 1条数据 this.cart.splice(i,1); } }, sub(i){ // 取到当前的数量 let count = this.cart[i].count; // 如果大于1 ,'?' 相当于then,则 则 -1, ':' = 否则 删除那一条数据 count > 1 ? this.cart[i].count -=1 : this.remove(i); }, add(i){ // 取到当前的一条数据 +1 this.cart[i].count ++ }, }, computed:{ count(){ // cart的总数 return this.cart.length }, activeCount(){ // 过滤参数 把active 是false 过滤掉 // 返回 v.active 为 true的对象 return this.cart.filter(v=>v.active).length }, total(){ // let num = 0; // this.cart.forEach(c=>{ // if(c.active){ // num += c.price * c.count; // } // }); // return num
// reduce 回调函数, 第一个参数是起始参数就是后面定义的0,第二个参数是数组遍历的对象 return this.cart.reduce((num,c)=>{ if (c.active){ num += c.price * c.count; } return num },0) } }, } </script>
第三方组件(element-ui)
1. 通用组件
- 基础组件,大部分UI都是这种组件,比如表单,布局,弹窗等
2. 业务组件
- 与需求挂钩,会被复用,比如抽奖,摇一摇等
3. 页面组件
- 每个页面都是一个组件,不会复用
使用组件
安装 element-ui 插件库
npm install element-ui -S # -S 保存
修改main.js
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css' import App from './App.vue'; Vue.use(ElementUI); new Vue({ el: '#app', render: h => h(App) });
按需引入
借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
首先,安装 babel-plugin-component:
npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
{ "presets": [["es2015", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
vue-cli 中可以使用 vue add element 安装
vue add element
选择按需导入
import on demand
安装之前注意提前提交当前工作内容, 脚手架会覆盖若干文件
发现项目发生了变化,打开App.vue , ctrl+z 撤回
饿了么UI 官网
https://element.eleme.cn/#/zh-CN/component/quickstart
main.js
import Vue from 'vue' import App from './App.vue' import axios from 'axios' // 加入UI import './plugins/element.js' // 提示警告信息 Vue.config.productionTip = false Vue.prototype.$http = axios; // 使用中央数据总线 创建一个bus,传递数据使用 Vue.prototype.$bus = new Vue; new Vue({ render: h => h(App), }).$mount('#app')
element.js
import Vue from 'vue' import { Button, Table ,TableColumn,InputNumber } from 'element-ui' Vue.use(Button) Vue.use(Table) Vue.use(TableColumn) Vue.use(InputNumber)
cart.vue
<template> <div> <h2>{{alltitle}}</h2> <template> <el-table ref="multipleTable" :data="cart" border tooltip-effect="dark" style=" 100%" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55"></el-table-column> <el-table-column prop="title" label="课程" width="120"></el-table-column> <el-table-column prop="price" label="单价" width="120"></el-table-column> <el-table-column label="数量" width="200"> <template slot-scope="scope"> <el-input-number v-model="scope.row.count" :min="0" :max="100" ></el-input-number> </template> </el-table-column> <el-table-column label="总价" width="120"> <template slot-scope="scope"> ¥ {{scope.row.price * scope.row.count}} </template> </el-table-column> </el-table> <div style="margin-top: 20px"> <el-button @click="toggleSelection()">取消选择</el-button> </div> </template> <table border="1"> <tr> <th>#</th> <th>课程</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in cart" :key="c.id" > <td><input type="checkbox" v-model="c.active"></td> <td>{{c.title}}</td> <td>¥{{c.price}}</td> <td> <button @click="sub(index)"> - </button> {{c.count}} <button @click="add(index)"> + </button> </td> <td>¥{{c.price * c.count}}</td> </tr> <tr> <td></td> <!-- colspan 合并 --> <td colspan="2">{{activeCount}} / {{count}}</td> <td colspan="2">¥ {{total}}</td> </tr> </table> </div> </template> <script> export default { name:'cart', props:['alltitle',], data(){ return{ // 初始一个空数组,存放添加或者删除的数据 // 刷新网页时 获取里面的数据,如果没有数据就是一个空数组 cart:JSON.parse(localStorage.getItem('cart')) || [], multipleSelection: [] } }, watch:{ // 深度监听deep:true cart 数据 cart:{ handler(n){ this.setLocalData(n); }, deep:true } }, created() { // 从中央数据总线里把数据取出来, this.$bus.$on('addCart',good=>{ // find查找那个 商品对应的ID const ret = this.cart.find(v=>v.id===good.id); // 如果购物车 没有数据 商品那一条数据, 则会添加进 那个列表里 if (!ret){ this.cart.push(good); }else{ // 如果有 则会自加1 ret.count ++ } }) }, methods:{ // element UI toggleSelection(rows) { if (rows) { rows.forEach(row => { this.$refs.multipleTable.toggleRowSelection(row); }); } else { this.$refs.multipleTable.clearSelection(); } }, handleSelectionChange(val) { this.multipleSelection = val; }, setLocalData(n){ // 可以计算总数量 localStorage.setItem('cart',JSON.stringify(n)); }, remove(i){ if (window.confirm('是否确定删除')){ // 这个数组中 删除 那个索引的 1条数据 this.cart.splice(i,1); } }, sub(i){ // 取到当前的数量 let count = this.cart[i].count; // 如果大于1 ,'?' 相当于then,则 则 -1, ':' = 否则 删除那一条数据 count > 1 ? this.cart[i].count -=1 : this.remove(i); }, add(i){ // 取到当前的一条数据 +1 this.cart[i].count ++ }, }, computed:{ count(){ // cart的总数 return this.cart.length }, activeCount(){ // 过滤参数 把active 是false 过滤掉 // 返回 v.active 为 true的对象 return this.multipleSelection.length; // return this.multipleSelection.filter( v => v.active ).length; }, total(){ // let num = 0; // this.cart.forEach(c=>{ // if(c.active){ // num += c.price * c.count; // } // }); // return num // reduce 回调函数, 第一个参数是起始参数就是后面定义的0,第二个参数是数组遍历的对象 return this.cart.reduce((num,c)=>{ if (c.active){ num += c.price * c.count; } return num },0) } }, } </script>
Element的表单组件分析
自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator。
element.js import Vue from 'vue' import { Button, Table ,TableColumn,InputNumber,Form,FormItem,Input } from 'element-ui' Vue.use(Button) Vue.use(Table) Vue.use(TableColumn) Vue.use(InputNumber) Vue.use(Form) Vue.use(FormItem) Vue.use(Input)
App.vue
<template> <div id="app"> <FormItem></FormItem> </div> </template> <script> // @ 相当于src 目录下 import FormItem from '@/components/FormItem.vue'; components: { FormItem }, } </script>
FormItem.vue
<template> <div> <h3>element 表单</h3> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="用户名" prop="name"> <el-input type="text" v-model="ruleForm.name" ></el-input> </el-form-item> <el-form-item label="密码" prop="pwd"> <el-input type="password" v-model="ruleForm.pwd" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button> </el-form-item> </el-form> </div> </template> <script> // 自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator // Form 定义数据 负责定义校验规则 // FormItem 负责显示错误信息 // Input 负责双向数据绑定 // provide inject 内部共享数据 export default { data() { return { ruleForm: { name: 'huige', pwd: '123', }, rules: { name: [ { required: true, message: '请输入名字' }, { mix:6,max:10, message: '请输入6~10位用户名' }, ], pwd: [ { required: true, message: '请输入密码' } ] } }; }, methods: { //自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator。 submitForm(name) { this.$refs[name].validate((valid) => { if (valid) { alert('验证成功,可以提交!'); } else { console.log('验证失败,不能提交!!'); return false; } }); }, resetForm(name) { this.$refs[name].resetFields(); } } } </script>
表单组件设计-Input实现双向数据绑定
Input.vue
<template> <div> <!-- 实现 v-model 是 v-bind:value 绑定值 ,v-on:input 绑定事件 --> <input :type="type" :value="inputVal" @input="handleInput" > </div> </template> <script> export default { props:{ type:{ type: String, default: 'text', }, value:{ type:String, default:"", } }, data(){ return { inputVal: this.value } }, methods:{ handleInput(e){ // 赋值, 实现双向数据绑定 this.inputVal = e.target.value; this.$emit('input',this.inputVal) // 通知父组件值的更新 } } } </script>
FormElemtent.vue
<template> <div> {{ruleForm}} <m-input v-model="ruleForm.name" ></m-input> <m-input type='password' v-model="ruleForm.pwd" ></m-input> </div> </template> <script> import MInput from './Input' export default { components : { MInput }, } </script>
表单组件-设计FormItem组件
0. label 和 prop 实行
1. 获取当前输入框的规则
2. 如果输入框和rule不匹配 ,显示错误信息
3. Input 组件中涌入输入内容时,通知FormItem做校验(校验方法)
4. 使用async-validator 做校验
Form.vue
<template> <div> <slot></slot> </div> </template> <script> // 1. 申明 props 获取数据模型(model) 和校验规则对象 export default { // provide: {} provide(){ return{ form: this } }, props:{ // 对象数据 model:{ type:Object, required:true, }, rules:{ type: Object } }, methods:{ validate(callback){ // 获取所有的验证结果 统一处理,只要有一个失败就失败 // tasks保存着验证之后的多个promise对象 const tasks = this.fileds.map(item=>item.validate()) let ret = true; Promise.all(tasks).then(results=>{ results.forEach(valid=>{ if(!valid){ ret = false } }) }) callback(ret) } }, created() { // 缓存需要校验的表单项 this.fileds = []; this.$on('formItemAdd', item=>{ this.fileds.push(item); }) }, } </script>
FormItem.vue
<template> <div> <label v-if="label">{{label}} </label> <!-- 槽作用 --> <slot> </slot> <!-- 显示错误信息 --> <p v-if='validateState==="error"' class="error" >{{errorMessage}}</p> </div> </template> <script> import schema from 'async-validator' export default { // 0. label 和 prop 实行 // 1. 获取当前输入框的规则 // 2. 如果输入框和rule不匹配 ,显示错误信息 // 3. Input 组件中涌入输入内容时,通知FormItem做校验(校验方法) // 4. 使用async-validator 做校验 // 需要安装 npm i async-validator -S data(){ return{ validateState:'', // 显示校验值 errorMessage:'', // 显示错误信息 } }, // 通过provide可以获取到全局的组件对象 inject:['form'], // 这个form 就是 Form.vue 里的 this mounted() { if (this.prop){ // 必须判断,form组件的子组件可能不是formitem this.$parent.$emit('formItemAdd',this) } }, methods:{ validate(value){ return new Promise(resolve => { // console.log(value); // 输入框的值 // 这个key 在es6里面 可以是一个动态的数组 const descriptor = {[this.prop] : this.form.rules[this.prop]}; //此对象要存储校验规则 // 获取检验对象 -> Form组件对象 -> rules[this.prop] // console.log(this.form.rules[this.prop]) const validate = new schema(descriptor); // 这个key 在es6里面 可以是一个动态的数组 validate.validate({[this.prop]: value},errors=>{ if (errors){ // 显示错误 this.validateState= "error"; this.errorMessage = errors[0].message; resolve(false) }else { // 错误置空 this.validateState= ""; this.errorMessage =""; resolve(true) } }) }) } }, created(){ // 创建一个监听事件,监听 validate 函数 this.$on('validate',this.validate); }, props:{ label: { type:String, default: "", }, prop:{ // name 或者 pwd的规则 type: String, default: "", } } } </script> <style scoped> .error{ color: red; } </style>
FormElement.vue
<template> <div> <h3>element 表单</h3> {{ruleForm}} <m-form :model="ruleForm" :rules="rules"> <m-form-item label="用户名" prop="name"> <m-input v-model="ruleForm.name" ></m-input> </m-form-item> <m-form-item label="密码" prop="pwd"> <m-input type='password' v-model="ruleForm.pwd" ></m-input> </m-form-item> <m-form-item> <el-button type="primary" @click="submitForm2('ruleForm')">提交</el-button> </m-form-item> </m-form> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="用户名" prop="name"> <el-input type="text" v-model="ruleForm.name" ></el-input> </el-form-item> <el-form-item label="密码" prop="pwd"> <el-input type="password" v-model="ruleForm.pwd" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button> </el-form-item> </el-form> </div> </template> <script> // 自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator // Form 定义数据 负责定义校验规则 // FormItem 负责显示错误信息 // Input 负责双向数据绑定 // provide inject 内部共享数据 import MInput from './Input' import MFormItem from './FormItem' import MForm from './Form' export default { components : { MInput, MFormItem, MForm }, data() { return { ruleForm: { name: 'huige', pwd: '123', }, rules: { name: [ { required: true, message: '请输入名字' }, { min:6,max:10, message: '请输入6~10位用户名' }, ], pwd: [ { required: true, message: '请输入密码' } ] } }; }, methods: { //自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator。 submitForm(name) { this.$refs[name].validate((valid) => { if (valid) { alert('验证成功,可以提交!'); } else { alert('验证失败,不能提交!!'); return false; } }); }, // 自己写的校验事件 submitForm2(name) { this.$refs[name].validate((valid) => { if (valid) { alert('验证成功,可以提交!'); } else { alert('验证失败,不能提交!!'); return false; } }); }, resetForm(name) { this.$refs[name].resetFields(); } } } </script>