0
点赞
收藏
分享

微信扫一扫

【Vue】Vue全家桶(三)Vue脚手架

非凡兔 2022-04-14 阅读 64

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)4mounted()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>
举报

相关推荐

0 条评论