1 Vue 脚手架
1.1 Vue 脚手架安装
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。
安装最新版本 vue-cli
npm install -g @vue/cli
 
安装vue-cli 3.x及以上指定版本
npm install  '@vue-cli@3.x.x' -g
 
检查安装是否成功
vue -V  
 
创建项目
vue create xxx
 
看项目需求,可以选择vue2和vue3
 
运行项目
npm run serve
 
1.2 项目示例
模板项目结构
 
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── School.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
├── vue.config.js:vue可选的配置文件
 
代码展示
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8" />
    <!--让IE浏览器以最高的渲染级别渲染页面  -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!-- 开启移动端的理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <!-- 配置页签图标 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <!-- 配置网页标题 -->
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <!-- 当浏览器不支持js时 noscript中的元素就会被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    
    <!-- 容器 -->
    <div id="app"></div>
     <!-- 此处不用App.vue vue脚手架默认App.vue在此编译 -->
  </body>
</html>
 
 
/*
 该文件是整个项目的入口文件 
*/
//引入Vue
import Vue from 'vue';
//引入父组件App
import App from './App.vue';
//关闭vue的生产提示
Vue.config.productionTip = false;
//创建Vue实例对象
new Vue({
  //挂载dom元素:该实例为#app标签服务
  el: '#app',
  //创建App模板,将App组件放到容器中
  render: h => h(App),
});
 
 
<template>
  <!-- 组件模板必须只包含一个根元素,这个根元素为遍历起始点 -->
  <div>
    <img src="./assets/logo.png" alt="log" />
    <!-- 使用子组件 -->
    <School></School>
    <Student></Student>
  </div>
</template>
<script>
//引入子组件
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
   name: "App",//可以不写
  //注册子组件
  components: { Student, School },
};
</script>
<style>
</style>
 
 
<template>
  <!-- 组件模板 --> 
  <div class="demo">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="showName">点我提示学校名</button>
  </div>
</template>
<script>
// 组件交互(数据、方法相关代码)
export default {
  //此处省略了Vue.extend()
  //组件名
  name: "School",
  data() {
    return {
      name: "尚硅谷",
      address: "北京",
    };
  },
  methods: {
    showName() {
      alert(this.name);
    },
  },
};
</script>
<style>
/* 组件样式 */
.demo {
  background-color: pink;
}
</style>
 
 
<template>
  <div>
    <h2>学生姓名:{{ name }}</h2>
    <h2>学校年龄:{{ age }}</h2>
  </div>
</template>
<script>
export default {
  //name: "Student",
  data() {
    return {
      name: "张三",
      age: 18,
    };
  },
};
</script>
<style>
</style>
 

项目报错
 
 报错原因:eslint语法检查的时候把命名不规范的代码当成了错误
 解决方案:
- 更改组件名,使其符合Vue推荐的双驼峰或-衔接命名规范,如: StudentName 或者 student-name
 - 修改配置项,关闭eslint语法检查
1.在项目的根目录找到(没有就创建)vue.config.js文件
2.在文件中添加如下内容,随后保存文件重新编译即可 
module.exports = {
  lintOnSave: false, //关闭eslint检查
};
 
关于不同版本的Vue
 vue.js与vue.runtime.xxx.js(main.js中引入的运行版)的区别:
 vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
 vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。render 函数和 template 一样都是创建 html 模板的
vue.config.js配置文件
 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
2 Vue组件小知识
2.1 ref属性
ref被用来给元素或子组件注册引用信息(id的替代者),应用在html标签上获取的是真实DOM元素或应用在组件标签上是组件实例对象(vc)
 使用方式:
//打标识
<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>
//获取
this.$refs.xxx
 
<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
    <School ref="sch" />
  </div>
</template>
<script>
//引入School组件
import School from "./components/School";
export default {
  name: "App",
  components: { School },
  data() {
    return {
      msg: "欢迎学习Vue!",
    };
  },
  methods: {
    showDOM() {
      console.log(this.$refs.title); //真实DOM元素
      console.log(this.$refs.btn); //真实DOM元素
      console.log(this.$refs.sch); //School组件的实例对象(vc)
    },
  },
};
</script>
 
2.2 props配置项
props配置项:让组件接收外部传过来的数据(prop属性)
父组件:传递数据
//name="xxx"为传递的数据
<Student name="xxx"/>  
 
子组件:接收数据
//第一种方式(只接收)最常用
props:['name']
//第二种方式(限制类型)
props:{name:String}
//第三种方式(限制类型、限制必要性、指定默认值)
props:{
	name:{
	type:String, //类型
	required:true, //必要性
	default:'张三' //默认值
	}
}
 
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
<template>
  <div>
    <!-- 传递数据 -->
    <Student name='李四' sex='女'  :age='18'></Student>
  </div>
</template>
<script>
//引入子组件
import Student from "./components/Student.vue";
export default {
   name: "App",
   components: { Student },
};
</script>
<style>
</style>
 
 
<template>
  <div>
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <h2>学生年龄:{{ myAge + 1 }}</h2>
    <button @click="updateAge">尝试修改收到的年龄</button>
  </div>
</template>
<script>
export default {
  name: "Student",
  data() {
    return {
    //若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
      myAge: this.age,
    };
  },
  methods: {
    updateAge() {
      this.myAge++;
    },
  },
  
  //接收数据
  
  //1.简单声明接收
 props:['name','age','sex']
 
  //2.接收的同时对数据进行类型限制
  /* props:{
			name:String,
			age:Number,
			sex:String
		} */
		
  //3.接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
  /*props: {
    name: {
      type: String, //name的类型是字符串
      required: true, //name是必要的
    },
    age: {
      type: Number,
      default: 23, //默认值
    },
    sex: {
      type: String,
      required: true,
    },
  },*/
};
</script>
 
 
2.3 mixin(混入)
mixin(混入):可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步:定义混合
{
    data(){....},
    methods:{....}
    ....
}
 
 
//定义混合
export const mixin = {
  methods: {
    showName() {
      alert(this.name);
    },
  },
};
 
第二步:使用混入
全局混入:Vue.mixin(xxx)
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {mixin} from './mixin'
//全局混入
Vue.mixin(mixin)
//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})
 
局部混入:mixins:[‘xxx’]
<template>
  <div class="demo">
    <h2 @click="showName">学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>
<script>
import { mixin } from "../mixin";
export default {
  data() {
    return {
      name: "尚硅谷",
      address: "北京",
    };
  },
  mixins: [mixin],
};
</script>
<style>
</style>
 
 
<template>
  <div>
    <h2 @click="showName">学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
  </div>
</template>
<script>
 import {mixin} from '../mixin'
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      sex: "男",
    };
  },
  //局部混入
 mixins:[mixin]
};
</script>
 
2.4 Vue插件
Vue插件:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件
对象.install = function (Vue, options) {
    // 1. 添加全局过滤器
    Vue.filter(....)
    // 2. 添加全局指令
    Vue.directive(....)
    // 3. 配置全局混入(合)
    Vue.mixin(....)
    // 4. 添加实例方法
    Vue.prototype.myMethod = function () {...}
    Vue.prototype.myProperty = xxxx
}
 
使用插件
Vue.use()
 
实例
export default {
  install(Vue) {
    //全局过滤器
    Vue.filter("mySlice", function (value) {
      return value.slice(0, 4);
    });
    //给Vue原型上添加一个方法(vm和vc就都能用了)
    Vue.prototype.hello = () => {
      alert("你好啊");
    };
  },
};
 
 
//引入Vue
import Vue from "vue";
//引入App
import App from "./App.vue";
//引入插件
import plugins from "./plugins";
//使用插件
Vue.use(plugins);
//创建vm
new Vue({
  el: "#app",
  render: (h) => h(App),
});
 
 
<template>
  <div>
    <h2>学校名称:{{ name | mySlice }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="test">点我测试一个hello方法</button>
  </div>
</template>
<script>
export default {
  name: "School",
  data() {
    return {
      name: "尚硅谷atguigu",
      address: "北京",
    };
  },
  methods: {
    test() {
      this.hello();
    },
  },
};
</script>
 

2.5 scoped样式
scoped样式:让样式在局部生效,防止冲突。
 写法:
<style scoped>
</style>
 
2.6 nextTick
语法:this.$nextTick(回调函数)
 作用:在下一次 DOM 更新结束后执行其指定的回调。
 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
3. 组件间数据交互
父向子传值:子组件设置props + 父组件设置v-bind属性绑定
子向父传值: 子组件的$emit + 父组件设置v-on事件绑定
兄弟组件任意组件通信:通过事件车的方式传递数据, 新建一个空的全局Vue对象 EventBus,利用$emit发送 , $on接收
3.1 父组件向子组件传值props

1.子组件内部通过props接收父组件传递过来的值
props传递数据原则:单向数据流,只能父传子
v-bind是不支持使用驼峰标识的,例如cUser要改成c-User。
Vue.component(‘menu-item',{
    //props接受父组件传递的数据
	props: ['title'],
	template: '<div>{{ title }}</div>'
})
 
2.父组件通过属性绑定v-bind将值传递给子组件
//传统方式
<menu-item title="来自父组件的数据"></menu-item>
//动态绑定
<menu-item :title="title"></menu-item>
 
 //07-父组件向子组件传值-props命名规则.html
<!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <title></title>
 </head>
 <body>
   <div id="app">
     <div>{{pmsg}}</div>
	//传统方式
     <menu-item title='来自父组件的值'></menu-item>
	//动态绑定
     <menu-item :title='ptitle' content='hello'></menu-item>
   </div>
   <script type='text/javascript' src='js/vue.js'></script>
   <script type='text/javascript'>
     //子组件(相对根组件而言)
     Vue.component('menu-item', {
       //props接受父组件传递的数据
       props: ['title', 'content'],
       data: function () {
         return {
           msg: '子组件本身的数据'
         }
       },
       template: `
     <div>{{msg + '-----' +title + '-----' +content}}</div>
     `
     })
     //Vue实例为根组件
     const app = new Vue({
       el: "#app",
       data: {
         pmsg: "父组件中内容",
         ptitle: '动态绑定的值'
       }
     })
   </script>
 </body>
 </html>
 

3.props属性名规则
-  
在props中使用驼峰形式,在html中需要使用短横线的形式
 -  
字符串形式的模板中没有这个限制
Vue.component(‘menu-item', { // 在JavaScript中是驼峰式的 props: ['menuTitle'], template: '<div>{{ menuTitle }}</div>' }) //在html中是短横线方式的 <menu-item menu-title=“nihao"></menu-item> 
4.props属性值类型
-  
字符串String
 -  
数值Number
 -  
布尔值Boolean
 -  
数组Array
 -  
对象Object
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="app"> <div>{{pmsg}}</div> <menu-item :pstr='pstr' :pnum='12' pboo='true' :parr='parr' :pobj='pobj'></menu-item> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> Vue.component('menu-item', { props: ['pstr','pnum','pboo','parr','pobj'], template: ` <div> <div>{{pstr}}</div> <div>{{12 + pnum}}</div> <div>{{typeof pboo}}</div> <ul> <li :key='index' v-for='(item,index) in parr'>{{item}}</li> </ul> <span>{{pobj.name}}</span> <span>{{pobj.age}}</span> </div> </div> ` }); var vm = new Vue({ el: '#app', data: { pmsg: '父组件中内容', pstr: 'hello', parr: ['apple','orange','banana'], pobj: { name: 'lisi', age: 12 } } }); </script> </body> </html> 

3.2 子组件向父组件传值$emit
1.子组件通过自定义事件$emit向父组件传递信息
子组件向父组件传值,使用自定义事件$emit。$emit(自定义事件名称)
<button @click='$emit("enlarge-text")'>扩大父组件字体</button>
 
2.在父组件中通过v-on监听子组件的事件
<menu-item @enlarge-text='handle'></menu-item>
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <!-- 整个app为父组件 -->
  <div id="app">
    <div :style='{fontSize:fontsize+"px"}'>{{msg}}</div>
    <!-- 在父组件中监听子组件事件 -->
    <menu-item @enlarge-text='handle'></menu-item>
  </div>
  <script type='text/javascript' src='js/vue.js'></script>
  <script type='text/javascript'>
    //子组件点击按钮控制父组件字体大小
    //创建子组件
    Vue.component('menu-item', {
      template: `
<button @click='$emit("enlarge-text")'>扩大父组件字体</button>
`
    })
    const app = new Vue({
      el: "#app",
      data: {
        msg: '父组件中的内容',
        fontsize: 10
      },
      methods: {
        handle: function () {
          this.fontsize += 5;
        }
      }
    })
  </script>
</body>
</html>
 
 
3.子组件通过自定义事件向父组件传递信息,携带额外的值
5为另外额外的值
<button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
 
4.父组件监听子组件的事件
通过$event接受额外的值
<menu-item  @enlarge-text='handle($event)'></menu-item>
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <div id="app">
    <div :style='{fontSize:fontsize+"px"}'>{{msg}}</div>
    <menu-item @enlarge-text='handle($event)'></menu-item>
  </div>
  <script type='text/javascript' src='js/vue.js'></script>
  <script type='text/javascript'>
    Vue.component('menu-item', {
      template: `
<button @click='$emit("enlarge-text",5)'>扩大父组件字体</button>
`
    })
    const app = new Vue({
      el: "#app",
      data: {
        msg: '父组件中的内容',
        fontsize: 10
      },
      methods: {
        handle: function (val) {
          this.fontsize += val;
        }
      }
    })
  </script>
</body>
</html>
 
3.3 非父子组件间传值

 1、任意组件之间传递数据需要借助于事件车,通过事件车的方式传递数据
 2、创建一个Vue的实例,让各个组件共用同一个事件机制。
 3、传递数据方,通过一个事件触发eventBus.
    
     
      
       
        e
       
       
        m
       
       
        i
       
       
        t
       
       
        (
       
       
        方
       
       
        法
       
       
        名
       
       
        ,
       
       
        传
       
       
        递
       
       
        的
       
       
        数
       
       
        据
       
       
        )
       
       
        。
       
       
        4
       
       
        、
       
       
        接
       
       
        收
       
       
        数
       
       
        据
       
       
        方
       
       
        ,
       
       
        通
       
       
        过
       
       
        m
       
       
        o
       
       
        u
       
       
        n
       
       
        t
       
       
        e
       
       
        d
       
       
        (
       
       
        )
       
       
       
        触
       
       
        发
       
       
        e
       
       
        v
       
       
        e
       
       
        n
       
       
        t
       
       
        B
       
       
        u
       
       
        s
       
       
        .
       
      
      
       emit(方法名,传递的数据)。 4、接收数据方,通过mounted(){}触发eventBus.
      
     
    emit(方法名,传递的数据)。4、接收数据方,通过mounted()触发eventBus.on (方法名,function(接收数据的参数){用该组件的数据接收传递过来的数据}),此时函数中的this已经发生了改变,可以使用箭头函数。
首先创建一个单独的js文件bus.js,内容如下
import Vue from 'vue'
export default new Vue
 
父组件如下:
<template>
     <components-a></components-a>
     <components-b></components-b>
</template>
 
子组件a如下:
<template>
      <div>
           <button @click="abtn">A按钮</button>
      </div>
</template>
<script>
import  from '../../js/bus.js'
export default {
      name: 'components-a',
      data () {
        return {
                ‘msg':"我是组件A"
        }
      },
      methods:{
           abtn:function(){
                  let  _this = this;
                   //$emit这个方法会触发一个事件
                   bus .$emit("myFun", _this.msg)  
           }
      }
}
</script>
 
子组件b如下:
<template>
     <div>
         <div>{{btext}}</div>
     </div>
</template>
<script>
import bus from '../../js/bus.js'
export default {
   name: 'components-b',
   data () {
        return {
           'btext':"我是B组件内容"
        }
   },
   created:function(){
       this.bbtn();
   },
   methods:{
       bbtn:function(){
            bus .$on("myFun",(message)=>{   
            //这里最好用箭头函数,不然this指向有问题
                 this.btext = message      
            })
       }
    }
}
</script>
 
这样的话点击组件A里面的按钮,组件B即可获取A组件传来的值。
4. 组件插槽
匿名插槽 唯一存在 <slot></slot>
 具名插槽 可存在多个 <slot name="up"></slot>
 作用域插槽:
 插槽的使用:子组件定义插槽的位置,父组件定义插槽的内容, 控制内容的显示与隐藏
4.1 组件插槽的作用
编译作用域:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
 插槽的目的是让我们原来的设备具备更多的扩展性。比如电脑的USB我们可以插入U盘。
 组件插槽是Vue的内置组件,为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么。
 组件插槽的作用:父组件向子组件传递内容
 
4.2 组件插槽基本用法
在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽。
该插槽插入什么内容取决于父组件如何使用。
如何封装合适呢?
抽取共性,保留不同。最好的封装方式就是将共性抽取到组件中,将不同预留为插槽。
1.插槽位置
Vue.component('alert-box', {
	template: `
		<div class="demo-alert-box">
			<strong>Error!</strong>
			<slot></slot>
		</div>
	`
})
 
2.插槽内容
<alert-box>Something had happened.</alert-box>
 
//12-插槽基本用法.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    Vue.component('alert-box', {
      template: `
        <div>
          <strong>ERROR:</strong>
          <slot>默认内容</slot>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>
 

4.3 具名插槽用法
1. 插槽定义
<div class="container">
	<header>
		<slot name="header"></slot>
	</header>
	<main>
		<slot></slot>
	</main>
	<footer>
		<slot name="footer"></slot>
	</footer>
</div>
 
2.插槽内容
//根据名称进行匹配,没有匹配到的放在默认插槽中
<base-layout>
	<h1 slot="header">标题内容</h1>
	<p>主要内容1</p>
	<p>主要内容2</p>
	<p slot="footer">底部内容</p>
</base-layout>
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <base-layout>
      <template slot='header'>
        <p>标题信息1</p>
        <p>标题信息2</p>
      </template>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
      </template>
    </base-layout>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    Vue.component('base-layout', {
      template: `
        <div>
          <header>
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
      }
    });
  </script>
</body>
</html>
 

4.4 作用域插槽
应用场景:父组件对子组件的内容进行加工处理
1.插槽定义
<ul>
	<li v-for= "item in list" v-bind:key= "item.id" >
		<slot v-bind:item="item">
			{{item.name}}
		</slot>
	</li>
</ul>
 
2.插槽内容
<fruit-list v-bind:list= "list">
	<template slot-scope="slotProps">
		<strong v-if="slotProps.item.current">
			{{ slotProps.item.text }}
		</strong>
	</template>
</fruit-list>
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<style type="text/css">
  .current {
    color: orange;
  }
</style>
<body>
  <div id="app">
    <fruit-list :list='list'>
      <template slot-scope='slotProps'>
        <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
        <span v-else>{{slotProps.info.name}}</span>
      </template>
    </fruit-list>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    Vue.component('fruit-list', {
      props: ['list'],
      template: `
        <div>
          <li :key='item.id' v-for='item in list'>
            <slot :info='item'>{{item.name}}</slot>
          </li>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        list: [{
          id: 1,
          name: 'apple'
        },{
          id: 2,
          name: 'orange'
        },{
          id: 3,
          name: 'banana'
        }]
      }
    });
  </script>
</body>
</html>
 

5. 基于组件的案例:购物车

案例需求分析:按照组件化方式实现业务需求
根据业务功能进行组件化划分
①标题组件(展示文本)
②列表组件(列表展示、商品数量变更、商品删除)
③结算组件(计算商品总额)
功能实现步骤:
- 实现整体布局和样式效果
 - 划分独立的功能组件
 - 组合所有的子组件形成整体结构
 - 逐个实现各个组件功 
  
- 标题组件
 - 列表组件
 - 结算组件
 
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- 引入css样式 -->
  <link rel="stylesheet" type="text/css" href="购物车案例.css" />
</head>
<body>
  <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    //标题组件
    var CartTitle = {
      //传递用户名数据
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    //列表组件
    var CartList = {
      props: ['list'],
      //列表展示
        //列表名
      //商品数量变更
        //商品数量减
        //呈现数据 失去焦点呈现数据
        //商品数量加
      //商品删除
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :src="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">             
              <a href="" @click.prevent='sub(item.id)'>-</a>             
              <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>             
              <a href="" @click.prevent='add(item.id)'>+</a>
            </div>            
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        //商品数量输入域修改
        changeNum: function (id, event) {
          this.$emit('change-num', {
            id: id,
            type: 'change',
            num: event.target.value //当前输入域最新值
          });
        },
        //商品数量减
        add: function (id) {
          this.$emit('change-num', {
            id: id,
            type: 'add'
          });
        },
        //商品数量加
        sub: function (id) {
          this.$emit('change-num', {
            id: id,
            type: 'sub'
          });
        },
        //商品删除
        del: function (id) {
          // 把id传递给父组件
          this.$emit('cart-del', id);
        }
      }
    }
    //结算组件
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function () {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart', {
      data: function () {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          }, {
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          }, {
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          }, {
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          }, {
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 1,
            img: 'img/e.jpg'
          }]
        }
      },
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
          <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        changeNum: function (val) {
          // 商品数量变更
          //分为三种情况:商品数量输入域变更、商品数量加、商品数量减
          if (val.type == 'change') {
            // 根据子组件传递过来的数据,跟新list中对应的数据
            this.list.some(item => {
              if (item.id == val.id) {
                item.num = val.num;
                // 终止遍历
                return true;
              }
            });
          } else if (val.type == 'sub') {
            // 减一操作
            this.list.some(item => {
              if (item.id == val.id) {
                item.num -= 1;
                // 终止遍历
                return true;
              }
            });
          } else if (val.type == 'add') {
            // 加一操作
            this.list.some(item => {
              if (item.id == val.id) {
                item.num += 1;
                // 终止遍历
                return true;
              }
            });
          }
        },
        //列表删除功能
        delCart: function (id) {
          // 根据id删除list中对应的数据
          // 1、找到id所对应数据的索引
          var index = this.list.findIndex(item => {
            return item.id == id;
          });
          // 2、根据索引删除对应数据
          this.list.splice(index, 1);
        }
      }
    });
    var vm = new Vue({
      el: '#app',
    });
  </script>
</body>
</html>
 










