Vue学习笔记(1)vue基础

本文是Vue学习笔记的第一部分,涵盖了Vue的基础知识,包括Vue概述、基本使用、模板语法、常用特性及组件化开发。介绍了Vue的声明式渲染、数据响应式、双向数据绑定、事件处理、指令、计算属性、组件注册和通信等核心概念,并通过实例深入解析了Vue的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

vue入门

Vue概述

Vue:渐进式JavaScript框架

声明式渲染->组件系统->客户端路由->集中式状态管理->项目构建

  • 易用
  • 灵活
  • 高效

Vue基本使用

原生js->jquery->框架

<div id="app">
    {{msg}}
</div>
<script>
    var vm=new Vue({
        el:'#app',
        data:{
            msg:'Hello Vue'
        }
    })
</script>

el:元素的挂载位置

data:模型数据

{{}}:差值表达式

原理分析:Vue语法–(Vue框架)->原生语法

Vue模板概述

前端渲染?

把数据填充到HTML标签中

  • 原生js拼接字符串(复杂,后期维护困难)
  • 使用前端模板引擎(没有事件机制)
  • 使用vue特有的模板语法

语法概述

  • 差值表达式

  • 指令(v-)

  • 事件绑定

  • 属性绑定

  • 样式绑定

  • 分支循环结构

指令

指令本质就是自定义属性

v-cloak

解决问题:闪动

原理:先隐藏,替换好值之后再显示最终的值

<style>
    [v-clock] {
        display: none;
    }
</style>
<div v-cloak>{{msg}}</div>
v-text

相比差值表达式更加简洁,没有”闪动“问题

<div v-text="msg"></div>
 msg: 'Hello Vue',
v-html
<div v-html="msg1"></div>
msg1:'<h1>Hello Vue</h1>'

比较危险,可能会导致xxs(跨站脚本危机),本网站内部数据可以使用

v-pre
<div v-pre>{{msg}}</div>

显示原始信息

数据响应式

数据变化导致页面内容的变化

  • v-once只编译一次,再次更改数据不会变化,提高性能
<div v-once>{{msg}}</div>

双向数据绑定

<div>{{msg}}</div>
<label>
    <input type="text" v-model="msg"/>
</label>

结果:数据同步

页面内容影响数据,数据在影响页面内容

MVVM设计思想

M(model)

V(view)

VM(View-Model)

Vue模板语法

事件绑定

v-on指令 or @

绑定函数名称

<div id="app">
<div v-cloak>{{msg}}</div>
<button v-on:click="msg++">点击</button>
<button @click="msg++">点击</button>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 0
        }
    })
</script>

绑定函数调用

 <button @click="handle(1,2,$event)">点击3</button>
<button @click="handle1">点击4</button>
var vm = new Vue({
    el: '#app',
    data: {
        msg: 0,
    },
    methods:{
        handle:function (p,p1,event){
                console.log(event.target.innerHTML);
                this.msg++;
            },
            handle1:function (event){
                console.log(event.target.innerHTML);
            }
    }
})
  • 如果时间直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数

  • 如果事件绑定函数调用,那么时间对象必须作为最后一个参数显示传递,并且事件对象的名称必须是$event

事件修饰符

  • 阻止冒泡
<div v-text="msg"></div>
<div v-on:click="handle1" >
<button @click="handle">按钮</button>
 </div>
methods:{
  handle:function (event) {
      event.stopPropagation();
  },
    handle1:function (){
      this.msg++;
    }
}

或者直接使用修饰符

<button @click.stop="handle">按钮</button>
  • 阻止默认行为
<button @click.prevent="handle">按钮</button>

或者直接使用修饰符

<a href="http://www.baidu.com" v-on:click.prevent>百度</a>

按键修饰符

submit:function (){
  console.log(this.username+"    "+this.password);
},
deleteAll:function (){
  this.username='';
}
  • enter回车键(示例:提交)
  <input type="text" v-model="password" v-on:keyup.enter="submit">
  • delete删除键(示例:删除全部)
<input type="text" v-model="username" v-on:keyup.delete="deleteAll">

Vue自定义按键修饰符

<input type="text" v-on:keyup.65="custom">

或者

<input type="text" v-on:keyup.a="custom">
  Vue.config.keyCodes.a=65;

简答计算器

<div id="app">
    <h1>简单计算器</h1>
    数值A:<input type="text" v-model=numberA>
    <br/>
    数值B:<input type="text" v-model=numberB>
    <br/>
    <button @click="calculate">计算</button>
    <br/>
    计算结果:{{result}}
</div>
<script>
    var v=new Vue({
        el:'#app',
        data:{
            numberA:'',
            numberB:'',
            result:''
        },
        methods:{
            calculate:function (){
                this.result=parseInt(this.numberA)+parseInt(this.numberB);
            }
        }
    })
</script>

属性绑定

<a v-bind:href="url">百度</a>
<script>
    var v=new Vue({
        el:'#app',
        data:{
            url:"http://www.baidu.com"
        }
    })
</script>

样式绑定

class样式处理
.active{
    border: 1px solid red;
    width: 100px;
    height: 100px;
}
  • 对象语法
<div v-bind:class="{active:isActive}"></div>
        data:{
            isActive:true
        },methods:{
            handle:function (){
                this.isActive=!this.isActive;
            }
        }
  • 数组语法
<button v-on:click="handle">按钮</button>
<div v-bind:class="[activeClass,errorClass]"></div>
data: {
    activeClass: "active",
    errorClass: "error"
}, methods: {
    handle:function () {
        this.activeClass="";
    }
}
  • 数组和对象结合使用
<div v-bind:class="[activeClass,errorClass,{test:isTest}]"></div>
  • class绑定的值可以简化操作
<div v-bind:class="combineClass"></div>
data: {
    activeClass: "active",
    errorClass: "error",
    combineClass:["active","error"]
}

或者

<div v-bind:class="objClass"></div>
objClass:{
    active:true,
    error:true
}
  • 默认的class会保留
style样式处理
  • 对象
<div v-bind:style="{border:borderStyle,width:widthStyle,height:heightStyle}"></div>
data: {
    borderStyle:"1px solid blue",
    widthStyle:"100px",
    heightStyle:"100px",
}
  • 数组
<div v-bind:style='[objStyles,overrideStyle]'></div>
            objStyles:{
              border:'1px solid green',
              width:'200px',
              height: '100px'
            },
            overrideStyle:{
                backgroundColor:"red"
            }

分支循环结构

<div v-if="score>=90">优秀</div>
<div v-else-if="score<90&&score>=80">良好</div>
<div v-else-if="score<80&&score>=60">及格</div>
<div v-else>不及格</div>
<div v-show="flag">测试flag</div>
  • v-if和v-show的区别

v-if控制元素是否渲染到页面

v-show控制元素是否显示

循环结构

<ul>
    <li v-for="(item,index) in color">{{item}}{{index}}</li>
</ul>
data: {
    color:["red","blue","green","black"]
}
  • 使用**:key**:帮助Vue区分不同的元素,从而提高性能

  • v-if和v-for配合使用,v-for遍历对象

<ul>
    <li  v-if="index==1" v-for="(value,key,index) in person">{{value}}{{key}}{{index}}</li>
</ul>
person:{
    username:"张三",
    password:"123",
    favorite:["song","run","dance"]
}

基础案例-选项卡

 <style>
*{
    padding: 0;
    margin: 0;
    list-style: none;
}
#app{
    margin: 0 auto;
}
#app ul{
    overflow: hidden;
}
#app li{
    display: inline-block;
    float: left;
    height: 20px;
    width: 100px;
    border: 1px solid black;
    text-align: center;
}
#app .pic img{
    height: 200px;
}
#app .pic div {
    height: 200px;
    display: none;
}
#app .pic .active{
    display: block;
}
#app ul .choose{
    background-color: orange;
}
    </style>
<div id="app">
<div>
    <ul>
       <li v-for="(item,index) in picture" v-on:click="change(index)" v-bind:class='currentIndex===index?"choose":""'>{{item.name}}</li>
    </ul>
    <div class="pic">
        <div v-for="(item,index) in picture" v-bind:class='currentIndex===index?"active":""'>
            <img v-bind:src=item.path class="active">
        </div>
    </div>
</div>
</div>
<script>
    var v = new Vue({
        el: '#app',
        data: {
            currentIndex:0,
            picture:[
                {
                    id:0,
                    name:"图片01",
                    path:"图片01.jpg"
                },
                {  id:1,
                    name:"图片02",
                    path:"图片02.jpg"},
                {  id:2,
                    name:"图片03",
                    path:"图片03.png"}
            ]
        }, methods: {
            change:function (index) {
                this.currentIndex=index;
            }
        }
    });
</script>

Vue常用特性

表单操作

*{
    padding: 0;
    margin: 0;
}
#app div span{
    display: inline-block;
    height: 30px;
    width: 100px;
}
<div id="app">
    <form action="#">
        <div>
            <span>姓名:</span>
            <input type="text" v-model="username">
        </div>
        <div>
            <span>性别:</span>
            <input type="radio" value="1" v-model="sex" >
            <label for=""></label>
            <input type="radio" value="0" v-model="sex">
            <label for=""></label>
        </div>
        <div>
            <span>爱好:</span>
            <input type="checkbox" v-model="hobbies" value="0">
            <label for="">篮球</label>
            <input type="checkbox" v-model="hobbies" value="1">
            <label for="">唱歌</label>
            <input type="checkbox" v-model="hobbies" value="2">
            <label for="">写代码</label>
        </div>
        <div>
            <span>职业:</span>
            <select name="" id="" v-model="job">
                <option value="0">请选择职业</option>
                <option value="1">教师</option>
                <option value="2">软件工程师</option>
                <option value="3">律师</option>
            </select>
        </div>
        <div>
            <span>个人简历:</span>
            <textarea name="" v-model="description"></textarea>
        </div>
        <input type="submit" v-on:click.prevent="submit">
    </form>
</div>
var v = new Vue({
    el: '#app',
    data: {
        username:'',
        sex:0,
        hobbies:[],
        job:0,
        description:''
    }, methods: {
        submit:function (){
            console.log(this.username);
            console.log(this.sex);
            console.log(this.hobbies);
            console.log(this.job);
            console.log(this.description);
        }
    }
});

表单域修饰符

  • 表单域转换为number类型
<textarea name="" v-model.number="description"></textarea>
  • 将表单域前后空格去掉
<textarea name="" v-model.trim="description"></textarea>
  • 将input事件切换为change事件
<textarea name="" v-model.lazy="description"></textarea>

自定义指令

全局指令
  • 网页刷新,input获取焦点
<input type="text" v-model="username" v-focus>
Vue.directive("focus",{
    inserted:function (el){
        el.focus();
    }
})
  • 改变元素背景色
<input type="text" v-color="color">
Vue.directive("color",{
    bind:function (el,binding) {
        el.style.backgroundColor=binding.value;
    }
})
color:"red"
局部指令

局部指令只能在组件中生效

 data: {},
 methods:{},
 directives:{
    color:{
        bind:function (el,binding) {
            el.style.backgroundColor=binding.value;
        }
    }
}

计算属性

使模板更加简洁

<div>{{reverseString}}</div>
 data: {},
 methods:{},
 computed:{
    reverseString:function (){
        return this.msg.split("").reverse().join("")
    }
}
  • 计算属性和方法的区别
    • 计算属性是基于他们的依赖进行缓存的(同一个数据则只会计算一次,结果保存在缓存中)
    • 方法不存在缓存

侦听器

数据的监听

<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
{{fullName}}
watch:{
    firstName:function (val) {
        this.fullName=val+''+this.lastName;
    },
    lastName:function (val) {
        this.fullName=this.firstName+''+val;
    }
}

登陆验证案例

  • 通过v-model实现数据绑定
  • 需要提供提示信息
  • 需要侦听器监听输入信息的变化
  • 需要修改触发的事件
<input type="text" v-model.lazy="username">
<span v-text="tip"></span>
var v = new Vue({
    el: '#app',
    data: {
        username: '',
        tip: ''
    }, methods: {
        checkName:function (username) {
            var that=this;
            setTimeout(function (){
                if(username==="admin"){
                    that.tip="用户名不可用,请替换";
                }else{
                    that.tip="用户名可以使用";
                }
            },2000);
        }
    },
    watch: {
        username: function (val) {
            this.checkName(val);
            this.tip = "正在验证...";
        }
    }
});

过滤器

格式化数据

<div>{{username|upper}}</div>
Vue.filter("upper", function (val) {
    return val.charAt(0).toUpperCase()+val.slice(1);
})

和自定义指令一样,有局部过滤器

 var v = new Vue({
        el: '#app',
        data: {
            username: ''
        }, 
        methods: {},
        filters: {
            upper: function (val) {
                return val.charAt(0).toUpperCase() + val.slice(1);
            }
        }
    });
日期格式化
<div>{{data|format("yyyy-MM-dd hh:mm")}}</div>
data: new Date()
Vue.filter("format",function (date,fmt){
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
    }
    let o = {
        'M+': date.getMonth() + 1,
        'd+': date.getDate(),
        'h+': date.getHours(),
        'm+': date.getMinutes(),
        's+': date.getSeconds()
    };
    for (let k in o) {
        if (new RegExp(`(${k})`).test(fmt)) {
            let str = o[k] + '';
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
        }
    }
    return fmt;
    function padLeftZero(str) {
        return ('00' + str).substr(str.length);
    }
})

声明周期

  • 挂载
  • 更新
  • 销毁

综合案例-图书管理

    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #booksManagement {
            margin: 0 auto;
        }

        table {
            margin: 0 auto;
            border-collapse: collapse;
            width: 600px;
        }

        table td {
            border: 1px solid orange;
            padding: 10px;
            text-align: center;
        }

        table thead th {
            padding: 10px;
            text-align: center;
            background-color: orange;
        }

        a {
            text-decoration: none;
        }

        .table {
            margin: 0 auto;
            width: 600px;
        }

        .grid {
            text-align: center;
            margin: 25px;
        }

        .handle {
            text-align: center;
            background-color: orange;
        }
        .total{
            padding: 10px;
            text-align: center;
            background-color: orange;
        }
    </style>
</head>
<body>
<div id="booksManagement">
    <div class="table">
        <div class="grid">
            <h1>
                图书管理
            </h1>
        </div>
        <div class="handle">
            <label>
                编号:
                <input type="text" v-model="id" v-bind:disabled="flag" v-focus>
            </label>
            <label>
                名称:
                <input type="text" v-model="name">
            </label>
            <button v-on:click="handle()" v-bind:disabled="submit">提交</button>
        </div>
        <div class="total">
            <span >图书总数:</span>
            {{total}}
        </div>
    </div>
    <div>
        <table>
            <thead>
            <tr>
                <th>编号</th>
                <th>名称</th>
                <th>时间</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="item in books">
                <td>{{ item.id }}</td>
                <td>{{ item.bookName}}</td>
                <td>{{ item.addTime}}</td>
                <td><a href="#" v-on:click.prevent="editBook(item.id)">修改</a>|<a href="#" v-on:click.prevent="deleteBook(item.id)">删除</a></td>
            </tr>
            </tbody>
        </table>
    </div>
</div>

<script type="text/javascript">
    Vue.filter("format", function (date, fmt) {
        if (/(y+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
        }
        let o = {
            'M+': date.getMonth() + 1,
            'd+': date.getDate(),
            'h+': date.getHours(),
            'm+': date.getMinutes(),
            's+': date.getSeconds()
        };
        for (let k in o) {
            if (new RegExp(`(${k})`).test(fmt)) {
                let str = o[k] + '';
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
            }
        }
        return fmt;

        function padLeftZero(str) {
            return ('00' + str).substr(str.length);
        }
    });
    Vue.directive("focus",{
        inserted:function (el){
            el.focus();
        }
    })
    var bm = new Vue({
        el: "#booksManagement",
        data: {
            flag: false,
            id: "",
            name: "",
            submit:false,
            books: [{
                id: 1,
                bookName: "三国演义",
                addTime: new Date()
            }, {
                id: 2,
                bookName: "水浒传",
                addTime: new Date()
            }, {
                id: 3,
                bookName: "红楼梦",
                addTime: new Date()
            }, {
                id: 4,
                bookName: "西游记",
                addTime: new Date()
            }]
        },
        methods: {
            handle: function () {
                if (this.flag) {
                    // 编辑操作
                    this.books.some((item) => {
                        if (item.id == this.id) {
                            item.bookName = this.name;
                            return true;
                        }
                    })
                    this.flag=false;
                } else {
                    // 添加操作
                    var addBook = {};
                    addBook.id = this.id;
                    addBook.bookName = this.name;
                    addBook.date = new Date();
                    this.books.push(addBook);
                    this.id = '';
                    this.name = '';
                }
                this.id='';
                this.name='';
            },
            editBook: function (id) {
                var book = this.books.filter(function (item) {
                    return item.id == id;
                });
                this.name = book[0].bookName;
                this.id = book[0].id;
                this.flag = true;
            },
            deleteBook:function (id) {
                // 删除图书
                var index=this.books.findIndex(function (item){
                    return item.id==id;
                })
                this.books.splice(index,1);
            }
        },computed:{
            total:function () {
                return this.books.length;
            }
        },watch:{
            name:function (val) {
                var flag=this.books.some(function (item) {
                    return item.bookName==val;
                });
                if(flag){
                this.submit=true;
                }else{
                this.submit=false;
            }}
        }
    });
</script>
</body>

Vue组件化开发

组件化开发思想

  • 标准
  • 分治
  • 重用
  • 组合
组件化规范:Web Components
  • 我们希望尽可能多的重用代码
  • 自定义组件的方式不太容易
  • 多次使用组件可能导致冲突

全局组件注册

案例
<div id="app">
<button-counter></button-counter>
</div>
// 注册组件
Vue.component('button-counter', {
    data: function () {
        return {
            count: 0
        }
    }, template: '<button @click="handle">点击了{{count}}次</button>',
    methods:{
        handle:function () {
            this.count+=2;
        }
    }
})
var vm = new Vue({
    el: "#app",
    data: {}
})
注意事项
  • data必须是一个函数
  • 组件模板内容必须是单个根元素
  • 组件模板内容可以是模板字符串
template: `<button @click="handle">点击了{{count}}次</button>`
  • 组件命名方式

短横线 or 驼峰

button-counter
Buttonounter

使用驼峰式命名法,只能在字符串模板中使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件

局部组件注册

<div id="app">
    <hello-world></hello-world>
    <hello-tom></hello-tom>
</div>
var HelloWorld = {
    data: function () {
        return {
            msg: 'Hello World'
        }
    },
    template: `
      <div>{{ msg }}</div>`
};
var HelloTom = {
    data: function () {
        return {
            msg: 'Hello Tom'
        }
    },
    template: `
      <div>{{ msg }}</div>`
};
var vm = new Vue({
    el: "#app",
    data: {},
    components: {
        'hello-world': HelloWorld,
        'hello-tom': HelloTom
    }
});

局部组件只能在注册他的父组件中使用

组件间数据交互

父组件向子组件传值
<hello-tom title="来自父组件的值" content="来自父组件的值2"></hello-tom>
var HelloTom = {
    props:['title','content'],
    data: function () {
        return {
            msg: 'Hello Tom'
        }
    },
    template: `
      <div>{{ title+msg+content }}</div>`
};
var vm = new Vue({
    el: "#app",
    data: {},
    components: {
        'hello-tom': HelloTom
    }
});
  • 在props中使用驼峰形式,模板中需要使用短横线的形式
  • 字符串形式的模板中没有这个限制

props属性值类型

String,Number,Boolean,数组,对象

子组件向父组件传值

子组件向父组件传值-基本用法

props传递数据原则:单项数据流

解决:

子组件通过自定义事件向父组件传递信息 $emit

父组件监听子组件的事件

<div :style="{fontSize:fontSize+'px'}">水果</div>
<menu-list :fruits="fruits" @enlarge-text="handle"></menu-list>
 Vue.component('menu-list', {
        props: ["fruits"],
        template: `
          <div>
          <ul>
            <li :key="index" v-for="(item,index) in fruits">{{ item }}</li>
          </ul>
          <button @click="fruits.push('lemon')">点击</button>
          <button @click="$emit('enlarge-text')">扩大字体</button>
          </div>`
    })
handle: function () {
    this.fontSize += 5;
}
子组件通过自定义事件向父组件传递信息
<menu-list :fruits="fruits" @enlarge-text="handle($event)"></menu-list>
<button @click="$emit('enlarge-text',5)">扩大字体</button>
<button @click="$emit('enlarge-text',10)">扩大字体</button>

单独的事件中心管理组件间的通信

通过事件中心(例:hub)管理事件的通信

<div id="app">
    <test-jerry></test-jerry>
    <test-tom></test-tom>
    <button @click="handle">销毁事件</button>
</div>

<script type="text/javascript">
    // 事件中心
    var hub = new Vue();
    // 注册组件
    Vue.component('test-tom', {
        data: function () {
            return {
                num: 0
            }
        },
        template: `
          <div>
          <div>jerry:{{ num }}</div>
          <div>
            <button @click="handle">点击</button>
          </div>
          </div>`, methods: {
            handle: function () {
                hub.$emit("tom-event", 1);
            }
        }, mounted: function () {
            hub.$on("jerry-event", (val) => {
                this.num += val;
            });
        }


    });
    Vue.component('test-jerry', {
        data: function () {
            return {
                num: 0
            }
        },
        template: `
          <div>
          <div>tom:{{ num }}</div>
          <div>
            <button @click="handle">点击</button>
          </div>
          </div>`, methods: {
            handle: function () {
                hub.$emit("jerry-event", 2);
            }
        }, mounted: function () {
            hub.$on("tom-event", (val) => {
                this.num += val;
            });
        }
    })
    var vm = new Vue({
        el: "#app",
        data: {}, methods: {
            handle: function () {
                hub.$off("tom-event");
                hub.$off("jerry-event")
            }
        }
    });
</script>

组件插槽

<div id="app">
    <alert-box>bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
</div>
Vue.component('alert-box', {
    template: `
      <div>
      <strong>ERROR:</strong>
      <slot></slot>
      <div>`
});
具名插槽用法
<alert-box>
    <h1 slot="header">header</h1>
    <h1 slot="footer">footer</h1>
    <h1>none</h1>
</alert-box>
Vue.component('alert-box', {
    template: `
      <div>
    <header>
    <slot name="header"></slot>
    </header>
      <main>
      <slot></slot>
      </main>
       <footer>
       <slot name="footer"></slot>
       </footer>
      <div>`
});

or

<alert-box>
    <template slot="header">
        <h1>header</h1>
    </template>
    <template slot="footer">
        <h1>footer</h1>
    </template>
    <template slot="default">
        <h1>none</h1>
    </template>
</alert-box>
作用域插槽
<div id="app">
    <fruit-list v-bind:list="fruits">
        <template slot-scope="slotProps">
            <strong v-if="slotProps.info.id===2" style="color: orange">{{ slotProps.info.name }}</strong>
            <span v-else>{{ slotProps.info.name }}</span>
        </template>
    </fruit-list>
</div>
Vue.component("fruit-list", {
    props: ['list'],
    template: `
      <div>
      <li :key="index" v-for="(item,index) in list">
        <slot v-bind:info="item">{{ item.name }}</slot>
      </li>

      </div>
    `
})

综合案例-购物车

布局,样式

* {
    padding: 0;
    margin: 0;
}

.container {
}

.container .cart {
    width: 300px;
    margin: auto;
}

.container .title {
    background-color: lightblue;
    height: 40px;
    line-height: 40px;
    text-align: center;
    /*color: #fff;*/
}

.container .total {
    background-color: #FFCE46;
    height: 50px;
    line-height: 50px;
    text-align: right;
}

.container .total button {
    margin: 0 10px;
    background-color: #DC4C40;
    height: 35px;
    width: 80px;
    border: 0;
}

.container .total span {
    color: red;
    font-weight: bold;
}

.container .item {
    height: 55px;
    line-height: 55px;
    position: relative;
    border-top: 1px solid #ADD8E6;
}

.container .item img {
    width: 45px;
    height: 45px;
    margin: 5px;
}

.container .item .name {
    position: absolute;
    width: 90px;
    top: 0;
    left: 55px;
    font-size: 16px;
}

.container .item .change {
    width: 100px;
    position: absolute;
    top: 0;
    right: 50px;
}

.container .item .change a {
    font-size: 20px;
    width: 30px;
    text-decoration: none;
    background-color: lightgray;
    vertical-align: middle;
}

.container .item .change .num {
    width: 40px;
    height: 25px;
}

.container .item .del {
    position: absolute;
    top: 0;
    right: 0px;
    width: 40px;
    text-align: center;
    font-size: 40px;
    cursor: pointer;
    color: red;
}

.container .item .del:hover {
    background-color: orange;
}
<div id="app">
    <div class="container">
        <my-cart></my-cart>
    </div>
</div>

标题组件

var CartTitle = {
    props: ["username"],
    template: `
      <div class="title">{{ username }}的商品</div>
    `
}

列表组件

var CartList = {
    props: ["list"],
    template: `
      <div>
      <div class="item" :key="index" v-for="(item,index) in list">
        <img v-bind: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" v-bind: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: {
        // 删除商品
        del: function (id) {
            this.$emit('cart-del', id);
        },
        // 数量变更
        changeNum: function (id, event) {
            this.$emit('change-num', {
                id: id,
                type: "change",
                num: event.target.value
            });
        },
        sub: function (id) {
            this.$emit('change-num', {
                id: id,
                type: "sub"
            });
        },
        add: function (id) {
            this.$emit('change-num', {
                id: id,
                type: "add"
            });
        }
    }
}

结算组件

var CartTotal = {
    props: ["list"],
    template: `
      <div class="total">
      <span>总价:{{ total }}</span>
      <button>结算</button>
      </div>
    `, computed: {
        total: function () {
            // 商品总价
            var total = 0;
            this.list.forEach(item => {
                total += item.price * item.num;
            });
            return total;
        }
    }
}

父组件

Vue.component('my-cart', {
    data: function () {
        return {
            username: '张三',
            list: [{
                id: 1,
                name: 'TCL彩电',
                price: 1000,
                num: 1,
                img: 'http://img3m8.ddimg.cn/56/9/28986068-1_l_3.jpg'
            }, {
                id: 2,
                name: '机顶盒',
                price: 1000,
                num: 1,
                img: 'http://img3m4.ddimg.cn/72/28/28997964-1_l_1.jpg'
            }, {
                id: 3,
                name: '海尔冰箱',
                price: 1000,
                num: 1,
                img: 'http://img3m7.ddimg.cn/48/5/28998237-1_l_9.jpg'
            }, {
                id: 4,
                name: '小米手机',
                price: 1000,
                num: 1,
                img: 'http://img3m1.ddimg.cn/94/31/26193811-1_l_22.jpg'
            }, {
                id: 5,
                name: 'PPTV电视',
                price: 1000,
                num: 2,
                img: 'http://img3m2.ddimg.cn/88/10/29134402-1_l_3.jpg'
            }]
        };
    },
    template: `
      <div class='cart'>
      <cart-title v-bind:username="username"></cart-title>
      <cart-list v-bind:list="list" v-on:cart-del="delCart($event)" @change-num="changeNum($event)"></cart-list>
      <cart-total v-bind:list="list"></cart-total>
      </div>
    `,
    components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
    }, methods: {
        delCart: function (id) {
            var index = this.list.findIndex(item => {
                return item.id === id;
            });
            this.list.splice(id, 1);
        },
        changeNum: function (val) {
            if(val.type==='change'){
                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;
                    }
                })
            }

        }
    }
});
    var vm = new Vue({
        el: '#app',
        data: {}
    });
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

h13245

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值