Vue基础入门


先来look一下:

在这里插入图片描述
OK,look完~
开始割韭菜~

Vue基础入门

这个笔记是学习Vue.js的时候做的,视频链接:黑马程序员vue前端基础教程-4个小时带你快速入门vue

这个教程只是基础入门,真的基础。。。如果想实际开发的话,还远远不够,我是学后端的,所以前端我也不太会,但是可以交流一下,在学习过程中可以参考我做的这个笔记,也可以参考别人的,反正希望大家都能学好。这个是我写的第一篇博客,有什么不足的地方,欢迎指正和点赞,后续会写更多的博客

别人的github上有这个Vue.js入门案例的素材,写完笔记才发现,巨亏。。。点击下面的链接即可。

有需要的也可以看看这个,别人写的比较好的笔记

文章最后有福利,但是还是希望大家能耐心看完这篇文章,然后高抬贵手,点个赞,谢谢~

1、Vue基础入门

1.1 前置知识

在学习 Vue 之前,建议先学HTML,CSS,JavaScript,AjAx,对前端基础有一定的了解和掌握再来学Vue,因为Vue是建立在这些基础上的一个JavaScript框架。


1.2 前端的发展(了解)

在聊 Vue 之前先来了解一下前端的发展历程,我学东西喜欢把前因给弄清楚,才不至于学得一脸懵逼。

  • 静态页面

    最初的网页以HTML为主,是纯静态的网页。网页是只读的,信息流只能从服务端到客户端单向流通。开发人员
    也只关心页面的样式和内容。

  • 动态页面

    1995年,网景工程师Brendan Eich 花了10天时间设计了JavaScript语言。
    随着JavaScript的诞生,我们可以操作页面的DOM元素及样式,页面有了一些动态的效果,但是依然是以静态为主。

  • 异步刷新

    2005年开始,ajax逐渐被前端开发人员所重视,因为不用刷新整个页面就可以更新页面的数据和渲染效果,实际上就是一个局部刷新的效果,当每次页面上的数据发生变化,只需要重新渲染数据发生变化的区域即可,不用刷新整个页面,大大缩短了响应时间,提高了用户体验。
    此时的开发人员不仅仅要编写HTML样式,还要懂ajax与后端交互,然后通过JS操作DOM元素来实现页面动态效果。比较流行的框架如 jQuery 就是典型代表。

到这里,基本的前端编写是没有问题了,也可以从后台获取数据模型,
通过JS操作页面的DOM元素,将从后台获取到的数据模型,渲染到页面中了,
同时结合AjAx实现了页面的局部刷新。
但也存在一些问题,如下:

开发人员从后端获取需要的数据模型,然后要通过DOM操作Model,将数据渲染到View中。

而后当用户操作视图,我们还需要通过DOM获取View中的数据,然后同步到Model中。

开发人员仍然要面对很繁琐的DOM操作,这就是存在的问题。

  • 2008年,google的Chrome发布,随后就以极快的速度占领市场,超过IE成为浏览器市场的主导者。
  • 2009年,Ryan Dahl在谷歌的Chrome V8引擎基础上,打造了基于事件循环的异步IO框架:Node.js。
  • 2010年,NPM作为node.js的包管理系统首次发布,开发人员可以遵循Common.js规范来编写Node.js模块,然后发布到NPM上供其他开发人员使用。目前已经是世界最大的包模块管理系统。
  • node.js的伟大之处不在于让JS迈向了后端开发,而是构建了一个庞大的生态系统。
  • 随后,在node的基础上,涌现出了一大批的前端框架,Vue 就是其中一个。

刚刚在上面我们发现了一些存在的问题,为了解决上面的这些问题,提出了 MVVM 模式。

MVVM模式

  • M:即Model,模型,包括数据和一些基本操作
  • V:即View,视图,页面渲染结果
  • VM:即View-Model,模型与视图间的双向操作(无需开发人员干涉)

MVVM模式采用了数据双向绑定机制。也就是说,View的变动,会自动反映在ViewModel上,反之亦然。这样,开发者就不用手动侦听事件并触发相应的View的更新了,因为这些都由MVVM中的VM搞定了。

MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响的:
1、只要Model发生了改变,View上自然就会表现出来。
2、当用户修改了View,Model中的数据也会跟着改变。

备注:Vue就是MVVM模式的一种实现框架。

关于MVVM模式可以参考下面的链接:

维基百科(需要翻墙)

浅析前端开发中的 MVC/MVP/MVVM 模式

秒懂MVVM模式在Android中的应用


1.3 Vue是什么

Vue 是一套用于构建用户界面渐进式JavaScript框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
前端框架三巨头:Vue.js、React.js、AngularJS,vue.js以其轻量易用著称,vue.js和React.js发展速度最快。
特点:

  • 渐进式的JavaScript框架

  • 简化DOM操作

  • 响应式数据驱动

    渐进式:

  • 可以选择性的使用该框架的一个或一些组件,这些组件的使用也不需要将框架全部组件都应用;

  • 而且用了这些组件也不要求你的系统全部都使用该框架。


1.4 第一个Vue程序

Vue.js官网

步骤:

新建一个HTML文件,编写简单的html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-HelloWorld</title>
</head>
<body>
<div id="app">
</div>
</body>
</html>

打开官网,导入开发版本的Vue.js(要在使用Vue之前引入)。

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

创建Vue实例对象,设置 eldata 属性。

<script>
    var app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue!'
        }
    })
</script>

使用简洁的(差值表达式)模板语法 {{}} 把数据渲染到页面上。

<div id="app">
    {{message}}
</div>

完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-HelloWorld</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>

<div id="app">
    {{ message }}
</div>

<script>
    var app = new Vue({
        /*el:通过该属性和div进行绑定,div的id属性值和该属性值相同,
        也可以是其他选择器,但推荐使用id选择器*/
        el: '#app',
        /*data:放置要渲染的数据*/
        data: {
            /*message:要和{{ }}中的字符串一样*/
            message: 'Hello Vue!'
        }
    })
</script>
</body>
</html>
1.4.1 Vue程序分析

{{ }}:差值表达式,和下面学到的v-text指令差不多。

理解成Vue的一种语法即可,用于把数据渲染到页面中。

el:Vue程序挂载点

  • el的作用是什么?

    el是用来设置Vue实例挂载(管理)的元素。
    
  • Vue实例的作用范围是什么?

    Vue会管理el属性对应的元素和它内部的后代元素。
    
  • 是否可以使用其他选择器?

    可以使用其他选择器,但是建议使用id选择器,
    因为id选择器一般是唯一的。
    
  • 是否可以设置其他的DOM元素?

    Vue可以设置在双标签中,但不支持HTML和 body 标签,
    也不支持设置在单标签中,因为单标签之间不能写内容。
    

data:Vue的数据对象,用于存放数据的。

  • 可以存字符串
  • 可以存数组
  • 可以存对象

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>data数据对象</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <p>{{msg}}</p>
    <p>{{people.name}}</p>
    <p>{{people.age}}</p>
    <p>{{hobby[0]}}</p>
    <p>{{hobby[2]}}</p>
    <p>{{hobby[5]}}</p>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            //字符串
            msg: '隔壁老王来了',
            //对象
            people: {
                name: '老王',
                age: 18
            },
            //数组
            hobby: [
                '打篮球', '打游戏',
                '看美女', '旅游',
                '听音乐', '看电影']
        },
        methods: {
            add: function () {
                console.log("add...");
            }
        }
    });
</script>
</body>
</html>

data总结

  • data用于定义Vue中用到的数据
  • data中可以写复杂类型的数据
  • 渲染复杂类型数据时,遵循JS的语法即可。如:对象的 . 语法,数组的索引语法。

拓展:methods

  • methods中用于定义方法(函数)。

2、Vue指令

1、内容绑定,事件绑定(v-text,v-html,v-on(基础))

v-text

作用:

  • 设置标签文本的文本值(textContent),无论设置什么内容,只会解析为文本
  • v-text会替换标签内的文本值,{{}}不会替换,相当于拼接字符串
  • v-text和{{}} 都支持表达式

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-text指令</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>

<div id="app">
    <div>
        <!--会被替换-->
        <h2 v-text="msg">广州</h2>
        <!--相当于拼接-->
        <h2>{{msg}}广州</h2>
    </div>
    <div>
        <!--两种方式都可以进行字符串拼接-->
        <h2 v-text="info+'北京'">广州</h2> <!--“广州”会被替换-->
        <h2>{{info + "广州"}}</h2>
    </div>
</div>

<script>
    var app = new Vue({
        el: '#app',
        data: {
            msg: '深圳',
            info: '666'
        }
    })
</script>
</body>
</html>

v-html

作用:

  • 设置标签的innerHTML。如果设置的是文本,则原样显示,如果设置的是html,则会被解析成对应的网页效果。
  • 解析文本用v-text,解析html用v-html

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-html指令</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>

<div id="app">
    <div>
        <!--普通文本和v-text显示效果一样-->
        <!--‘哈哈哈’会被全部替换-->
        <p v-html="content">哈哈哈</p>
        <p v-text="content">哈哈哈</p>
    </div>
    <div>
        <!--v-html解析为网页效果-->
        <p v-html="msg"></p>
        <!--v-text解析为纯文本-->
        <p v-text="msg"></p>
    </div>
</div>

<script>
    var app = new Vue({
        el: '#app',
        data: {
            content: "嘿嘿嘿~~",
            msg: "<a href='https://www.baidu.com'>百度</a>"
        }
    })
</script>
</body>
</html>

v-on(基础)

作用:

  • 为元素绑定事件。如:鼠标点击,鼠标移出,按下键盘事件…
  • 事件名不用写on
  • 指令可以简写为@
  • 事件绑定的方法(也就是事件触发时执行的函数)定义在methods
  • 方法内部可以通过this来调用data中的数据,方法中的this指代的是当前Vue实例

代码如下:

<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>v-on基础</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <input type="button" value="v-on指令" v-on:click="doWatchTV"/>
    <input type="button" value="v-on指令-@简写" @click="doWatchTV"/>
    <input type="button" value="v-on指令-@双击" @dblclick="doWatchTV"/>
    <h2 @click="changeFood">{{food}}</h2>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            food: "西红柿炒蛋"
        },
        methods: {
            doWatchTV: function () {
                alert("看电视");
            },
            changeFood: function () {
                /*方法中的this指向的是Vue实例对象*/
                this.food += "超好吃的!"
            }
        }
    })
</script>
</body>
</html>

2、显示,条件绑定,属性绑定(v-show,v-if,v-bind)

v-show

作用:

  • 根据给定表达式,判断表达式值的真假,来控制元素的显示和隐藏(原理是修改元素的display属性值)。
  • 指令后面的内容最终都会解析为布尔值,原理是修改元素的display属性值,实现显示隐藏。
  • 在要控制的元素标签里使用v-show指令,值为布尔值,true显示,false隐藏。
  • 数据改变之后,对应元素的值也会同步更新。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-show指令</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <span v-show="isShow">{{ msg }}</span>
    <div v-show="age>=18">老王 {{age}} 岁了,可以看到了,哇哈哈哈~~~</div>
    <button @click="changeShow">改变文字显示状态</button>
    <button @click="changeAge">年龄累加</button>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            msg: '老王666',
            age: 17,
            content: "",
            isShow: false
        },
        methods: {
            changeShow: function () {
                this.isShow = !this.isShow;
            },
            changeAge: function () {
                this.age++;
            }
        }
    })
</script>
</body>
</html>

v-if

作用:

  • 根据表达式的真假,切换元素的显示隐藏(原理是操作的是DOM元素)。
  • 操作DOM的性能消耗较大,v-if是直接从DOM中移除元素,因此频繁切换的元素使用v-show

用法:

  • v-show一样的用法,只是本质不一样,把v-show换成v-if即可。

v-bind

作用:

  • 设置元素的属性。如a标签的href属性,img的src属性,class类样式属性…

用法:

  • v-bind:属性名=表达式
  • :属性名=表达式

代码如下:

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>v-bind指令</title>
</head>
<style>
    .demo {
        width: 100px;
        height: 100px;
        margin-top: 12px;
        background-color: aliceblue;
    }

    #active {
        border-radius: 50%;
        background-color: red;
    }
    #active2 {
        border-radius: 50%;
        background-color: green;
    }
</style>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <!--标签属性的绑定-->
    <!--方式一-->
    <div>
        <a v-bind:href="aHref">百度</a>
    </div>
    <!--方式二 简写-->
    <div>
        <a :href="aHref">百度一下</a>
    </div>
    <!--元素样式的绑定-->
    <!--方式一:三元表达式-->
    <div :id="isActive?'active':''" class="demo" @click="changeActive">
    </div>
    <!--方式二:对象方式 active2这个选择器是否生效,取决于isActive的值。
    不知道为什么没生效...
    -->
    <div :id="{active2:isActive}" class="demo" @click="changeActive">
    </div>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            isActive: false,
            aHref: "https://www.baidu.com"
        },
        methods: {
            changeActive: function () {
                console.log("changeActive...");
                this.isActive = !this.isActive;
            }
        }
    })
</script>
</body>
</html>

3、列表循环,表单元素绑定(v-for,v-on(补充),v-model)

v-for

作用:

  • 用于生成列表和遍历数据。
  • 可以结合其他指令一起使用。

用法:

  • 情况1:v-for=“当前循环到的元素 in 待循环的数据” :key=“唯一值”
  • 情况2:v-for="(当前循环到的元素,当前的索引) in 待循环的数据" :key=“索引”
  • vue中,循环的时候,key属性加不加不会报错,只是为了提高页面的渲染性能,建议加上,不影响显示效果。
  • 如果没有id,可以考虑使用索引替代。切记key的值不能重复,只要遵循不重复的原则即可,值是什么无所谓。

代码如下:

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>v-for指令</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>

<div id="app">
    <!--普通数组-->
    <p>第一种写法</p>
    <ul>
        <li v-for="item in arr">
            {{item}}
        </li>
    </ul>
    <br/>
    <p>第二种写法</p>
    <ul>
        <li v-for="(item,index) in arr" :key="index">
            {{index+1}}--{{item}}
        </li>
    </ul>

    <div>
        <!--对象数组,v-bind:属性名=属性值 是进行属性绑定的,可简写为 :属性名=属性值-->
        <h3 v-for="(item,index) in people" :key="index" v-bind:title="item.name">
            {{index}}-->{{item.name}}
        </h3>
    </div>

    <!--测试数组数据变化对v-for的影响-->
    <div>
        <button @click="add">添加数据</button>
        <button @click="remove">移除数据</button>
    </div>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            /*普通数组*/
            arr: ["Java", "HTML", "JavaScript", "Vue.js"],
            /*对象数组*/
            people: [
                {name: "老王"},
                {name: "小红"},
                {name: "小欧"}
            ]
        },
        methods: {
            add: function () {
                this.people.push({name: "小卢"});
            },
            remove: function () {
                /*shift();默认移除最左边的元素*/
                this.people.shift();
            }
        }
    })
</script>
</body>
</html>

v-on(补充)

补充:

  • 事件传参
    • 定义方法时需要定义形参来接收传入的实参。
  • 事件修饰符:v-on补充-修饰符
    • 事件的后面加上 .修饰符 可以对事件的触发进行更好的控制。
    • .enter限制的按键为回车键,事件修饰符还有很多,参照官网即可。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-on补充</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <!--事件方法传参-->
    <button @click="doSomething('老王',hobby[0].name)">点击弹窗</button>
    <!--事件修饰符:键盘按下enter键触发doSomething事件-->
    <input type="text" @keydown.enter="doSomething('小卢',hobby[2].name)"/>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            hobby: [{name: '睡觉'}, {name: '打游戏'},
                {name: '看美女'}, {name: '旅游'},
                {name: '听音乐'}, {name: '看电影'}]
        },
        methods: {
            doSomething: function (p1, p2) {
                alert("我叫" + p1 + ",喜欢" + p2);
            }
        }
    })
</script>
</body>
</html>

v-model

作用:

  • 获取和设置表单元素的值(双向数据绑定)。
  • 只能获取和设置表单元素的值,实现data表单元素数据的双向绑定,不能绑定非表单的元素。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-model指令</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <input type="text" v-model="msg">
    <br>
    <br>
    <button @click="setMsg">修改message</button>
    <br>
    <h3>{{msg}}</h3>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            msg: 'v-model指令'
        },
        methods: {
            getMsg: function () {
                alert(this.msg);
            },
            setMsg: function () {
                this.msg = "老王666";
            }
        }
    })
</script>
</body>
</html>

3、Vue网络应用

3.1 什么是Axios

Axios 是一个基于 Promise 的 HTTP(网络请求) 库,简单讲就是可以发送get、post请求等,功能较单一。

Axios同时也是一个JS库,通过Promise实现XHR封装,其中Promise是控制手段,XHR是实际发送Http请求的客户端。

就像$.ajax是通过callback+XHR实现一样,你也可以造个轮子叫XXX的,都是AJAX技术的一种具体实现。

简单来说: AJAX技术是实现网页的局部数据刷新,你可以通过XHR、Fetch、WebSocket等API实现

axios的GitHub地址

3.2 Axios基本使用

用法:

  • axios必须先导入才可以使用。
  • 使用get或post方法即可发送对应的请求。
  • then方法中的回调函数会在请求成功或失败时触发。
    • 第一个回调函数在成功时触发。
    • 第二个回调函数在失败时触发。
  • 通过回调函数的形参可以获取响应内容或错误信息。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>axios基本使用</title>
</head>
<body>
<input type="button" value="axios:get请求" class="get">
<input type="button" value="axios:post请求" class="post">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    /*
        接口1:随机笑话
        请求地址:https://autumnfish.cn/api/joke/list
        请求方法:get
        请求参数:num(笑话条数,数字)
        响应内容:随机笑话
    */
    document.querySelector(".get").onclick = function () {
        // axios.get("https://autumnfish.cn/api/joke/list666?num=6")
        axios.get("https://autumnfish.cn/api/joke/list?num=6")
            .then(
                /*成功*/
                function (success) {
                    console.log(success);
                },
                /*失败*/
                function (error) {
                    console.log(error);
                }
            )
    };
    /*
         接口2:用户注册
         请求地址:https://autumnfish.cn/api/user/reg
         请求方法:post
         请求参数:username(用户名,字符串)
         响应内容:注册成功或失败
     */
    document.querySelector(".post").onclick = function () {
        // axios.post("https://autumnfish.cn/api/user/reg333", {username: "axios基本使用"})
        axios.post("https://autumnfish.cn/api/user/reg", {username: "axios基本使用"})
            .then(
                /*成功*/
                function (success) {
                    console.log(success);
                },
                /*失败*/
                function (error) {
                    console.log(error);
                }
            )
    }
</script>
</body>
</html>

3.3 Vue整合Axios

随机获取一条笑话,并通过Vue渲染到页面上。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue整合axios</title>
</head>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <button @click="getJoke">获取一条笑话</button>
    <p>{{msg}}</p>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    /*
        接口:随机获取一条笑话
        请求地址:https://autumnfish.cn/api/joke
        请求方法:get
        请求参数:无
        响应内容:随机笑话
    */
    var app = new Vue({
        el: '#app',
        data: {
            msg: '一条笑话'
        },
        methods: {
            getJoke: function () {
                var that = this;
                /*把this这个引用传递给that,因为在axios成功和失败的回调函数无法访问this,
                this实际上指向的就是当前Vue的实例,拿到this,然后才能把响应数据渲染到页面上*/
                axios.get("https://autumnfish.cn/api/joke")
                    .then(
                        /*请求成功的回调*/
                        function (success) {
                            that.msg = success.data;
                        },
                        /*请求失败的回调*/
                        function (error) {
                            that.msg = "请求发生错误,请重新请求!";
                        }
                    )
            }
        }
    })
</script>
</body>
</html>

总结

  • axios回调函数中的this已经改变,无法访问到data中的数据。
  • 把this保存到其他变量中(引用传递),回调函数中直接使用保存的this即可。
  • this可以通过 . 调用其他方法和data中的数据。
  • 网络应用和本地应用最大的区别就是改变了数据来源。

4、案例

4.1 计数器

需求:

  • 点击加号数字加一,但不能超过10;
  • 点击减号数字减一,但不能小于0;
  • 否则给出提示。

实现思路:

  • 使用Vue指令为按钮绑定点击事件
  • data中定义要显示的数字,并赋初值。使用 {{}} 在页面上显示数字
  • 当事件触发时,改变页面上的数字,也就是data中的数字值。在methods中定义两个方法,进行处理。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计数器</title>
</head>
<style>
    .bon {
        margin-left: 8px;
    }
</style>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <button class="bon" @click="add">+</button>
    <span>{{num}}</span>
    <button class="bon" @click="sub">-</button>
</div>

<script>
    var app = new Vue({
        el: "#app",
        data: {
            num: 1
        },
        methods: {
            add: function () {
                console.log("add...");
                console.log(this.num);
                if (this.num < 10) {
                    this.num++;
                } else {
                    alert("不能超过10");
                }
            },
            sub: function () {
                console.log(this.num);
                console.log("sub...");
                if (this.num > 0) {
                    this.num--;
                } else {
                    alert("不能小于0");
                }
            }
        }
    })
</script>
</body>
</html>

4.2 图片切换

需求:

  • 点击上一张显示上一张图片,点击下一张显示下一张图片。
  • 当前如果是第一张图片,则不显示上一张的按钮,反之则显示。
  • 当前如果是最后一张图片,则不显示下一张的按钮,反之则显示。

实现思路:

  • 使用Vue指令v-on:click@click为按钮绑定点击事件。
  • data中定义要显示的图片数组(images),以及图片数组的index,并赋初值。
  • methods中定义当点击上一张和下一张的时执行方法。
  • 使用v-bind:bind 绑定imgsrc属性,图片的路径从图片数组(images)中动态获取。
  • 使用v-showv-if配合索引来判断和控制按钮是否显示和隐藏。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图片切换</title>
</head>
<style>
    .img_div {
        margin: 18px auto;
        width: 560px;
        height: 350px;
    }
    img {
        width: 560px;
        height: 350px;
    }
</style>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
    <div class="img_div">
        <!-- 拼接图片路径 -->
        <img :src="'images/'+images[index]" alt="">
    </div>

    <div>
        <button v-show="index>0" @click="forward">上一张</button>
        <br/>
        <button v-show="index<images.length-1" @click="next">下一张</button>
    </div>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            index: 0,
            /*图片名称数组*/
            images: [
                "bg01.jpg", "bg02.jpg",
                "bg03.jpg", "bg04.jpg",
                "bg05.jpg", "bg06.jpg",
                "bg07.jpg", "bg08.jpg",
                "bg09.jpg", "bg10.jpg"
            ]
        },
        methods: {
            /*上一张*/
            forward: function () {
                this.index--;
            },
            /*下一张*/
            next: function () {
                this.index++;
            }
        }
    })
</script>
</body>
</html>

4.3 记事本

需求:

  • 实现数据的新增。
  • 单条删除和清空数据。
  • 列表展示,数据条数统计。
  • 当列表无数据时,底部的数据条数和批量删除隐藏。

实现思路:

  • 新增:用表单进行输入,配合v-on,结合事件修饰符,v-model等实现。
  • 单条删除:v-on为元素绑定单击事件,单击则删除对应数据,传递对应索引删除数组元素即可。
  • 清空数据:清空列表数(数组)据即可。
  • 列表展示:v-for实现。
  • 数据统计:取数组的长度即可。
  • 元素的隐藏和显示:v-showv-if

代码如下:

index.css
html,
body {
    margin: 0;
    padding: 0;
}

body {
    background: #fff;
}

button {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font-size: 100%;
    vertical-align: baseline;
    font-family: inherit;
    font-weight: inherit;
    color: inherit;
    -webkit-appearance: none;
    appearance: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
    line-height: 1.4em;
    background: #f5f5f5;
    color: #4d4d4d;
    min-width: 230px;
    max-width: 550px;
    margin: 0 auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-weight: 300;
}

:focus {
    outline: 0;
}

.hidden {
    display: none;
}

#todoapp {
    background: #fff;
    margin: 180px 0 40px 0;
    position: relative;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

#todoapp input::-webkit-input-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}

#todoapp input::-moz-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}

#todoapp input::input-placeholder {
    font-style: italic;
    font-weight: 300;
    color: gray;
}

#todoapp h1 {
    position: absolute;
    top: -160px;
    width: 100%;
    font-size: 60px;
    font-weight: 100;
    text-align: center;
    color: rgba(175, 47, 47, .8);
    -webkit-text-rendering: optimizeLegibility;
    -moz-text-rendering: optimizeLegibility;
    text-rendering: optimizeLegibility;
}

.new-todo,
.edit {
    position: relative;
    margin: 0;
    width: 100%;
    font-size: 24px;
    font-family: inherit;
    font-weight: inherit;
    line-height: 1.4em;
    border: 0;
    color: inherit;
    padding: 6px;
    border: 1px solid #999;
    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.new-todo {
    padding: 16px;
    border: none;
    background: rgba(0, 0, 0, 0.003);
    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}

.main {
    position: relative;
    z-index: 2;
    border-top: 1px solid #e6e6e6;
}

.toggle-all {
    width: 1px;
    height: 1px;
    border: none; /* Mobile Safari */
    opacity: 0;
    position: absolute;
    right: 100%;
    bottom: 100%;
}

.toggle-all + label {
    width: 60px;
    height: 34px;
    font-size: 0;
    position: absolute;
    top: -52px;
    left: -13px;
    -webkit-transform: rotate(90deg);
    transform: rotate(90deg);
}

.toggle-all + label:before {
    content: "❯";
    font-size: 22px;
    color: #e6e6e6;
    padding: 10px 27px 10px 27px;
}

.toggle-all:checked + label:before {
    color: #737373;
}

.todo-list {
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 420px;
    overflow: auto;
}

.todo-list li {
    position: relative;
    font-size: 24px;
    border-bottom: 1px solid #ededed;
    height: 60px;
    box-sizing: border-box;
}

.todo-list li:last-child {
    border-bottom: none;
}

.todo-list .view .index {
    position: absolute;
    color: gray;
    left: 10px;
    top: 20px;
    font-size: 16px;
}

.todo-list li .toggle {
    text-align: center;
    width: 40px;
    /* auto, since non-WebKit browsers doesn't support input styling */
    height: auto;
    position: absolute;
    top: 0;
    bottom: 0;
    margin: auto 0;
    border: none; /* Mobile Safari */
    -webkit-appearance: none;
    appearance: none;
}

.todo-list li .toggle {
    opacity: 0;
}

.todo-list li .toggle + label {
    /*
          Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
          IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
      */
    background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center left;
}

.todo-list li .toggle:checked + label {
    background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E");
}

.todo-list li label {
    word-break: break-all;
    padding: 15px 15px 15px 60px;
    display: block;
    line-height: 1.2;
    transition: color 0.4s;
}

.todo-list li.completed label {
    color: #d9d9d9;
    text-decoration: line-through;
}

.todo-list li .destroy {
    display: none;
    position: absolute;
    top: 0;
    right: 10px;
    bottom: 0;
    width: 40px;
    height: 40px;
    margin: auto 0;
    font-size: 30px;
    color: #cc9a9a;
    margin-bottom: 11px;
    transition: color 0.2s ease-out;
}

.todo-list li .destroy:hover {
    color: #af5b5e;
}

.todo-list li .destroy:after {
    content: "×";
}

.todo-list li:hover .destroy {
    display: block;
}

.todo-list li .edit {
    display: none;
}

.todo-list li.editing:last-child {
    margin-bottom: -1px;
}

.footer {
    color: #777;
    padding: 10px 15px;
    height: 20px;
    text-align: center;
    border-top: 1px solid #e6e6e6;
}

.footer:before {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 50px;
    overflow: hidden;
    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
    0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
    0 17px 2px -6px rgba(0, 0, 0, 0.2);
}

.todo-count {
    float: left;
    text-align: left;
}

.todo-count strong {
    font-weight: 300;
}

.filters {
    margin: 0;
    padding: 0;
    list-style: none;
    position: absolute;
    right: 0;
    left: 0;
}

.filters li {
    display: inline;
}

.filters li a {
    color: inherit;
    margin: 3px;
    padding: 3px 7px;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: 3px;
}

.filters li a:hover {
    border-color: rgba(175, 47, 47, 0.1);
}

.filters li a.selected {
    border-color: rgba(175, 47, 47, 0.2);
}

.clear-completed,
html .clear-completed:active {
    float: right;
    position: relative;
    line-height: 20px;
    text-decoration: none;
    cursor: pointer;
}

.clear-completed:hover {
    text-decoration: underline;
}

.info {
    margin: 50px auto 0;
    color: #bfbfbf;
    font-size: 15px;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
    text-align: center;
}

.info p {
    line-height: 1;
}

.info a {
    color: inherit;
    text-decoration: none;
    font-weight: 400;
}

.info a:hover {
    text-decoration: underline;
}

/*
	Hack to remove background from Mobile Safari.
	Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio: 0) {
    .toggle-all,
    .todo-list li .toggle {
        background: none;
    }

    .todo-list li .toggle {
        height: 40px;
    }
}

@media (max-width: 430px) {
    .footer {
        height: 50px;
    }

    .filters {
        bottom: 10px;
    }
}
记事本.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>记事本</title>
    <link rel="stylesheet" type="text/css" href="./css/index.css"/>
</head>
<body>
<!-- 主体区域 -->
<section id="todoapp">
    <!-- 输入框 -->
    <header class="header">
        <h1>记事本</h1>
        <!--v-model:表单数据和data中的inputValue进行双向绑定
        @keyup.enter(v-on:keyup.enter):键盘上的enter键抬起时,触发(执行)add方法-->
        <input autofocus="autofocus" autocomplete="off"
               placeholder="请输入任务" class="new-todo"
               v-model="inputValue" @keyup.enter="add"/>
    </header>
    <!-- 列表区域 -->
    <section class="main">
        <ul class="todo-list">
            <li class="todo" v-for="(item,index) in list" :key="index">
                <div class="view">
                    <span class="index">{{index+1}}</span>
                    <label>{{item}}</label>
                    <!--从index位置开始移除,移除1个元素-->
                    <button class="destroy" @click="remove(index,1)"></button>
                </div>
            </li>
        </ul>
    </section>
    <!-- 统计和清空 -->
    <footer class="footer">
        <span class="todo-count" v-show="list.length!=0">
            共有
            <strong>{{list.length}}</strong></span>
        <button class="clear-completed" @click="clear" v-if="list.length!=0">
            清空
        </button>
    </footer>
</section>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    new Vue({
        el: "#todoapp",
        data: {
            /*数据列表*/
            list: ["吃饭", "睡觉", "打豆豆"],
            /*文本框输入的值*/
            inputValue: ""
        },
        methods: {
            /*新增*/
            add: function () {
                if (this.inputValue == "") {
                    alert("输入不能为空!");
                    return;
                }
                this.list.push(this.inputValue);
                this.inputValue = "";
            },
            /*移除单个元素*/
            remove: function (index, count) {
                /*从index位置开始移除,移除count个元素*/
                this.list.splice(index, count);
            },
            /*清空数组*/
            clear: function () {
                this.list = [];
            }
        }
    })
</script>
</body>
</html>

4.4 天气查询

需求:

  • 根据对应的城市,查询对应的天气,并展示到页面上。

实现思路:

  • v-model实现表单数据和data的绑定,从而获取输入的城市名。
  • v-on结合事件修饰符实现回车查询。
  • 利用axios请求天气接口,获取数据。
  • v-for实现数据的渲染。

代码如下:

main.js
/*
  请求地址:http://wthrcdn.etouch.cn/weather_mini
  请求方法:get
  请求参数:city(城市名)
  响应内容:天气信息

  1. 点击回车
  2. 查询数据
  3. 渲染数据
*/
new Vue({
    el: "#app",
    data: {
        city: "",
        weatherList: []
    },
    methods: {
        getWeather: function () {
            if (typeof this.city == "undefined" || this.city == null || this.city == "") {
                alert("城市不能为空!");
                return;
            }
            let that = this;
            axios.get("http://wthrcdn.etouch.cn/weather_mini?city=" + this.city)
                .then(
                    /*请求成功的函数回调*/
                    function (success) {
                        console.log("请求成功!");
                        console.log(success.data.data);
                        that.weatherList = success.data.data.forecast;
                    }
                    /*,function (error) {
                        /!*请求失败的函数回调*!/
                        alert("请求发生错误,请重新请求!");
                    }*/
                ).catch(function (error) {
                /*请求失败的函数回调*/
                alert("请求发生错误,请重新请求!");
            })
        },
        queryWeather: function (city) {
            this.city = city;
            this.getWeather();
        }
    }
});
index.css
body {
    font-family: 'Microsoft YaHei';
}

.wrap {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    /* background: radial-gradient(#f3fbfe, #e4f5fd, #8fd5f4); */
    /* background:#8fd5f4; */
    /* background: linear-gradient(#6bc6ee, #fff); */
    background: #fff;

}

.search_form {
    width: 640px;
    margin: 100px auto 0;
}

.logo img {
    display: block;
    margin: 0 auto;
}

.form_group {
    width: 640px;
    height: 40px;
    margin-top: 45px;
}

.input_txt {
    width: 538px;
    height: 38px;
    padding: 0px;
    float: left;
    border: 1px solid #41a1cb;
    outline: none;
    text-indent: 10px;
}

.input_sub {
    width: 100px;
    height: 40px;
    border: 0px;
    float: left;
    background-color: #41a1cb;
    color: #fff;
    font-size: 16px;
    outline: none;
    cursor: pointer;
    position: relative;
}

.input_sub.loading::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background: url('../img/loading.gif');
}

.hotkey {
    margin: 8px 0 0 2px;
}

.hotkey a {
    font-size: 14px;
    color: #666;
    padding-right: 15px;
}

.weather_list {
    height: 200px;
    text-align: center;
    margin-top: 50px;
    font-size: 0px;
}

.weather_list li {
    display: inline-block;
    width: 140px;
    height: 200px;
    padding: 0 10px;
    overflow: hidden;
    position: relative;
    background: url('../img/line.png') right center no-repeat;
    background-size: 1px 130px;
}

.weather_list li:last-child {
    background: none;
}

/* .weather_list .col02{
    background-color: rgba(65, 165, 158, 0.8);
}
.weather_list .col03{
    background-color: rgba(94, 194, 237, 0.8);
}
.weather_list .col04{
    background-color: rgba(69, 137, 176, 0.8);
}
.weather_list .col05{
    background-color: rgba(118, 113, 223, 0.8);
} */

.info_date {
    width: 100%;
    height: 40px;
    line-height: 40px;
    color: #999;
    font-size: 14px;
    left: 0px;
    bottom: 0px;
    margin-top: 15px;
}

.info_date b {
    float: left;
    margin-left: 15px;
}

.info_type span {
    color: #fda252;
    font-size: 30px;
    line-height: 80px;
}

.info_temp {
    font-size: 14px;
    color: #fda252;
}

.info_temp b {
    font-size: 13px;
}

.tem .iconfont {
    font-size: 50px;
}
天气查询.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>天气查询</title>
    <link rel="stylesheet" href="css/reset.css"/>
    <link rel="stylesheet" href="css/index.css"/>
</head>

<body>
<div class="wrap" id="app">
    <div class="search_form">
        <div class="logo">
            <img src="img/logo.png" alt="logo"/>
        </div>
        <div class="form_group">
            <!--按下回车键查询天气-->
            <input type="text" class="input_txt" placeholder="请输入查询的天气"
                   v-model="city" @keydown.enter="getWeather"/>
            <!--点击搜索按钮查询天气-->
            <button class="input_sub" @click="getWeather">
                搜 索
            </button>
        </div>
        <!--点击查询城市天气-->
        <div class="hotkey">
            <span>热门城市:</span>
            <a href="javascript:;" @click="queryWeather('北京')">北京</a>
            <a href="javascript:;" @click="queryWeather('上海')">上海</a>
            <a href="javascript:;" @click="queryWeather('广州')">广州</a>
            <a href="javascript:;" @click="queryWeather('深圳')">深圳</a>
        </div>
    </div>
    <ul class="weather_list">
        <li v-for="item in weatherList">
            <div class="info_type">
                <span class="iconfont">{{item.type}}</span></div>
            <div class="info_temp">
                <b>{{item.low}}</b>
                ~
                <b>{{item.high}}</b>
            </div>
            <div class="info_date">
                <span>{{item.date}}</span>
            </div>
        </li>
    </ul>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 官网提供的 axios 在线地址 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!--自定义的js-->
<script src="./js/main.js"></script>
</body>
</html>

4.5 音乐播放器

需求:

  • 歌曲的搜索,播放/暂停。
  • 歌曲封面随播放歌曲的不同而不同。
  • 歌曲的评论。
  • 歌曲封面的碟片随歌曲播放而转动(动画)。
  • MV的播放/暂停,以及显示。点击MV空白处关闭MV。
  • 自动播放下一首歌,若当前歌曲是最后一首,则从头开始播放(列表循环)。

实现思路:

  • 歌曲搜索
    • 按下回车进行查询:v-on,.enter
    • 查询数据:axios调用音乐接口,v-model将输入内容和data进行绑定。
    • 渲染数据:v-for循环遍历音乐数据即可。
  • 歌曲播放
    • 点击播放:v-on(@),绑定点击事件。
    • 歌曲地址获取:axios请求歌曲URL获取接口即可。
    • 歌曲地址设置:v-bind:属性名=属性值(:属性名=属性值),设置src属性的值即可。
  • 歌曲封面
    • 获取封面图片:axios请求对应接口获取即可。
    • 设置封面图片:v-bind:属性名=属性值(:属性名=属性值),设置图片的src属性的值即可。
  • 播放动画
    • 监听音乐的播放和暂停:首先,audio标签的play事件会在音频播放的时候触发,
    • audio标签的pause事件会在音频暂停的时候触发。
    • v-on为音乐的播放(play)和暂停(pause)绑定监听事件,
    • 同时设置一个标志,播放和暂停时,改变标志的状态即可。
    • 配合v-bind,播放音乐时为盒子(div)加上类样式修饰即可播放动画,
    • 即:类样式是否生效,取决于我们所设置的标志,值为true则类样式生效。
  • 歌曲MV相关功能
    • 歌曲MV图标的显示和隐藏:根据歌曲是否有MV,结合v-ifv-show实现即可。
    • MV的获取:axios请求MV获取接口即可。
    • 遮罩层显示和隐藏:v-showv-if,配合v-on实现。
    • MV地址切换:v-bind设置对应标签的src属性值即可。
  • 自动播放下一首(列表循环)
    • 播放当前歌曲时,把当前歌曲的索引记录起来。
    • audio标签添加播放结束的监听,若当前歌曲播放结束,则拿到刚刚所记录的歌曲索引,
    • 判断歌曲索引是否是最后一个,若不是,则索引值加一,调用播放歌曲的方法,进行播放下一首,
    • 如果当前歌曲是最后一首,则把索引置为 0,调用播放歌曲的方法,进行歌曲的播放。
    • 该功能使用原生JavaScript实现。

代码如下:

index.css
body,
ul,
dl,
dd {
    margin: 0px;
    padding: 0px;
}

.wrap {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background: url("../images/bg.jpg") no-repeat;
    background-size: 100% 100%;
}

.play_wrap {
    width: 800px;
    height: 544px;
    position: fixed;
    left: 50%;
    top: 50%;
    margin-left: -400px;
    margin-top: -272px;
    /* background-color: #f9f9f9; */
}

.search_bar {
    height: 60px;
    background-color: #1eacda;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: relative;
    z-index: 11;
}

.search_bar img {
    margin-left: 23px;
}

.search_bar input {
    margin-right: 23px;
    width: 296px;
    height: 34px;
    border-radius: 17px;
    border: 0px;
    background: url("../images/zoom.png") 265px center no-repeat rgba(255, 255, 255, 0.45);
    text-indent: 15px;
    outline: none;
}

.center_con {
    height: 435px;
    background-color: rgba(255, 255, 255, 0.5);
    display: flex;
    position: relative;
}

.song_wrapper {
    width: 200px;
    height: 435px;
    box-sizing: border-box;
    padding: 10px;
    list-style: none;
    position: absolute;
    left: 0px;
    top: 0px;
    z-index: 1;
}

.song_stretch {
    width: 600px;
}

.song_list {
    width: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    height: 100%;
}

.song_list::-webkit-scrollbar {
    display: none;
}

.song_list li {
    font-size: 12px;
    color: #333;
    height: 40px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    width: 580px;
    padding-left: 10px;
}

.song_list li:nth-child(odd) {
    background-color: rgba(240, 240, 240, 0.3);
}

.song_list li a {
    display: block;
    width: 17px;
    height: 17px;
    background-image: url("../images/play.png");
    background-size: 100%;
    margin-right: 5px;
    box-sizing: border-box;
}

.song_list li b {
    font-weight: normal;
    width: 122px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.song_stretch .song_list li b {
    width: 200px;
}

.song_stretch .song_list li em {
    width: 150px;
}

.song_list li span {
    width: 23px;
    height: 17px;
    margin-right: 50px;
}

.song_list li span i {
    display: block;
    width: 100%;
    height: 100%;
    cursor: pointer;
    background: url("../images/table.png") left -48px no-repeat;
}

.song_list li em,
.song_list li i {
    font-style: normal;
    width: 100px;
}

.player_con {
    width: 400px;
    height: 435px;
    position: absolute;
    left: 200px;
    top: 0px;
}

.player_con2 {
    width: 400px;
    height: 435px;
    position: absolute;
    left: 200px;
    top: 0px;
}

.player_con2 video {
    position: absolute;
    left: 20px;
    top: 30px;
    width: 355px;
    height: 265px;
}

.disc {
    position: absolute;
    left: 73px;
    top: 60px;
    z-index: 9;
}

.cover {
    position: absolute;
    left: 125px;
    top: 112px;
    width: 150px;
    height: 150px;
    border-radius: 75px;
    z-index: 8;
}

.comment_wrapper {
    width: 180px;
    height: 435px;
    list-style: none;
    position: absolute;
    left: 600px;
    top: 0px;
    padding: 25px 10px;
}

.comment_wrapper .title {
    position: absolute;
    top: 0;
    margin-top: 10px;
}

.comment_wrapper .comment_list {
    overflow: auto;
    height: 410px;
}

.comment_wrapper .comment_list::-webkit-scrollbar {
    display: none;
}

.comment_wrapper dl {
    padding-top: 10px;
    padding-left: 55px;
    position: relative;
    margin-bottom: 20px;
}

.comment_wrapper dt {
    position: absolute;
    left: 4px;
    top: 10px;
}

.comment_wrapper dt img {
    width: 40px;
    height: 40px;
    border-radius: 20px;
}

.comment_wrapper dd {
    font-size: 12px;
}

.comment_wrapper .name {
    font-weight: bold;
    color: #333;
    padding-top: 5px;
}

.comment_wrapper .detail {
    color: #666;
    margin-top: 5px;
    line-height: 18px;
}

.audio_con {
    height: 50px;
    background-color: #f1f3f4;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
}

.myaudio {
    width: 800px;
    height: 40px;
    margin-top: 5px;
    outline: none;
    background-color: #f1f3f4;
}

/* 旋转的动画 */
@keyframes Rotate {
    from {
        transform: rotateZ(0);
    }
    to {
        transform: rotateZ(360deg);
    }
}

/* 旋转的类名 */
.autoRotate {
    animation-name: Rotate;
    animation-iteration-count: infinite;
    animation-play-state: paused;
    animation-timing-function: linear;
    animation-duration: 5s;
}

/* 是否正在播放 */
.player_con.playing .disc,
.player_con.playing .cover {
    animation-play-state: running;
}

.play_bar {
    position: absolute;
    left: 200px;
    top: -10px;
    z-index: 10;
    transform: rotate(-25deg);
    transform-origin: 12px 12px;
    transition: 1s;
}

/* 播放杆 转回去 */
.player_con.playing .play_bar {
    transform: rotate(0);
}

/* 搜索历史列表 */
.search_history {
    position: absolute;
    width: 296px;
    overflow: hidden;
    background-color: rgba(255, 255, 255, 0.3);
    list-style: none;
    right: 23px;
    top: 50px;
    box-sizing: border-box;
    padding: 10px 20px;
    border-radius: 17px;
}

.search_history li {
    line-height: 24px;
    font-size: 12px;
    cursor: pointer;
}

.switch_btn {
    position: absolute;
    right: 0;
    top: 0;
    cursor: pointer;
}

.right_line {
    position: absolute;
    left: 0;
    top: 0;
}

.video_con video {
    position: fixed;
    width: 800px;
    height: 546px;
    left: 50%;
    top: 50%;
    margin-top: -273px;
    transform: translateX(-50%);
    z-index: 990;
}

.video_con .mask {
    position: fixed;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    z-index: 980;
    background-color: rgba(0, 0, 0, 0.8);
}

.video_con .shutoff {
    position: fixed;
    width: 40px;
    height: 40px;
    background: url("../images/shutoff.png") no-repeat;
    left: 50%;
    margin-left: 400px;
    margin-top: -273px;
    top: 50%;
    z-index: 995;
}
main.js
/*
  1:歌曲搜索接口
    请求地址:https://autumnfish.cn/search
    请求方法:get
    请求参数:keywords(查询关键字)
    响应内容:歌曲搜索结果
  2:歌曲url获取接口
    请求地址:https://autumnfish.cn/song/url
    请求方法:get
    请求参数:id(歌曲id)
    响应内容:歌曲url地址
  3.歌曲详情获取
    请求地址:https://autumnfish.cn/song/detail
    请求方法:get
    请求参数:ids(歌曲id)
    响应内容:歌曲详情(包括封面信息)
  4.热门评论获取
    请求地址:https://autumnfish.cn/comment/hot?type=0
    请求方法:get
    请求参数:id(歌曲id,地址中的type固定为0)
    响应内容:歌曲的热门评论
  5.mv地址获取
    请求地址:https://autumnfish.cn/mv/url
    请求方法:get
    请求参数:id(mvid,为0表示没有mv)
    响应内容:mv的地址
*/

/**
 * 判断对象是否为空
 * @param obj 要判空的对象
 * @returns {boolean} 为空返回true,不为空返回false
 */
function isEmpty(obj) {
    if (typeof obj == "undefined" || obj == null || obj == "") {
        return true;
    } else {
        return false;
    }
}

var app = new Vue({
    el: "#player",
    data: {
        /*要查询的关键字*/
        queryKey: "",
        /*歌曲数据列表*/
        musicList: [],
        /*歌曲url*/
        musicUrl: "",
        /*当前播放歌曲的索引*/
        currentIndex: 0,
        /*歌曲封面图片url*/
        musicPicUrl: "",
        /*歌曲热门评论*/
        hotCommentList: [],
        /*是否播放动画,false为不播放*/
        isPlaying: false,
        /*歌曲MV路径*/
        songMvUrl: "",
        /*MV遮罩层的显示状态,false为不显示*/
        isShow: false,
    },
    methods: {
        /*搜索歌曲*/
        searchMusic: function () {
            if (isEmpty(this.queryKey)) {
                alert("搜索关键字不能为空!");
                return;
            }
            let that = this;
            axios.get("https://autumnfish.cn/search?keywords=" + this.queryKey)
                .then(
                    /*请求成功的回调*/
                    function (success) {
                        console.log(success.data.result.songs);
                        that.musicList = success.data.result.songs;
                    }
                    /*请求失败的回调*/
                ).catch(function (error) {
                alert("请求发生错误,请重新请求!");
                return;
            })
        },
        /*播放音乐*/
        playMusic: function (musicId, currentIndex) {
            /*记录当前播放歌曲索引*/
            this.currentIndex = currentIndex;
            let that = this;
            /*歌曲url获取*/
            axios.get("https://autumnfish.cn/song/url?id=" + musicId)
                .then(
                    /*请求成功的回调*/
                    function (success) {
                        // console.log(success.data.data[0].url);
                        that.musicUrl = success.data.data[0].url;
                    }
                    /*请求失败的回调*/
                ).catch(function (error) {
                alert("请求发生错误,请重新请求!");
                return;
            });

            /*歌曲详情获取(包含歌曲图片等信息)*/
            axios.get("https://autumnfish.cn/song/detail?ids=" + musicId)
                .then(
                    /*请求成功的回调*/
                    function (success) {
                        that.musicPicUrl = success.data.songs[0].al.picUrl;
                    }
                    /*请求失败的回调*/
                ).catch(function (error) {
                alert("请求发生错误,请重新请求!");
                return;
            });
            /*获取歌曲热门评论*/
            axios.get("https://autumnfish.cn/comment/hot?type=0&id=" + musicId)
                .then(
                    /*请求成功的回调*/
                    function (success) {
                        // console.log(success);
                        // console.log(success.data.hotComments);
                        that.hotCommentList = success.data.hotComments;
                    }
                    /*请求失败的回调*/
                ).catch(function (error) {
                alert("请求发生错误,请重新请求!");
                return;
            });
        },

        /*动画播放*/
        animationPlay: function () {
            // console.log("播放...");
            this.isPlaying = true;
        },
        /*动画暂停*/
        animationPause: function () {
            // console.log("暂停...");
            this.isPlaying = false;
        },
        /*获取歌曲的MV*/
        playSongMV: function (mvId) {
            // alert(mvId);
            let that = this;
            axios.get("https://autumnfish.cn/mv/url?id=" + mvId)
                .then(
                    /*请求成功的回调*/
                    function (success) {
                        // console.log(success);
                        that.songMvUrl = success.data.data.url;
                        /*显示MV遮罩层*/
                        that.isShow = true;
                        // console.log("调用成功,url:" + that.songMvUrl)
                    },
                    /*请求失败的回调*/
                    function (error) {
                        alert("请求发生错误,请重新请求!");
                        return;
                    }
                )
        },
        /*隐藏MV遮罩层,并停止MV*/
        hideMv: function () {
            if (confirm("你确定要关闭当前播放的MV视频吗?")) {
                this.isShow = false;
                this.songMvUrl = "";
            } else {
                return;
            }
        }
    },
    /*Vue初始化加载完成,执行*/
    mounted: function () {
        this.queryKey = "胡歌";
        this.searchMusic();
    }
});

/*自动播放下一首歌曲,原生js实现*/
let audio = document.getElementById("audioPlay");
audio.loop = false;
/*为‘audio’标签添加播放结束事件*/
audio.addEventListener('ended', function () {
    // alert("歌曲播放结束!");
    // console.log(app.musicList);
    let list = app.musicList;
    let index = app.currentIndex;
    if (index < list.length - 1) {
        /*如果不是最后一首*/
        let nextMusic = list[index + 1];
        app.playMusic(nextMusic.id, index + 1);
    } else {
        /*如果是最后一首*/
        app.playMusic(list[0].id, 0);
    }
}, false);
音乐播放器.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>音乐播放器</title>
    <!-- 自定义css -->
    <link rel="stylesheet" href="./css/index.css">
</head>

<body>
<div class="wrap">
    <!-- 播放器主体区域 -->
    <div class="play_wrap" id="player">
        <div class="search_bar">
            <img src="images/player_title.png" alt=""/>
            <!-- 搜索歌曲 -->
            <!--v-model:对表单输入框进行绑定
            @keydown.enter(v-bind:keydown.enter):按下回车键搜索-->
            <input type="text" autocomplete="off" placeholder="输入歌名或歌手"
                   v-model="queryKey" @keydown.enter="searchMusic"/>
        </div>
        <div class="center_con">
            <!-- 搜索歌曲列表 -->
            <div class='song_wrapper'>
                <ul class="song_list">
                    <li v-for="(song,index) in musicList" :key="index">
                        <a href="javascript:;" @click="playMusic(song.id,index)"></a>
                        <b>{{song.name}}</b>
                        <!--当歌曲的mvid!=0的时候表示有MV,则显示MV图标,
                        因为图标不需要频繁的显示和隐藏,所以用‘v-if’指令-->
                        <span><i v-if="song.mvid!=0" @click="playSongMV(song.mvid)"></i></span>
                    </li>
                </ul>
                <img src="images/line.png" class="switch_btn" alt="">
            </div>
            <!-- 歌曲信息容器 -->
            <!--v-bind绑定属性class,‘playing’这个类样式是否生效,动画是否播放取决于‘isPlaying’的值-->
            <div class="player_con" :class="{playing:isPlaying}">
                <img src="images/player_bar.png" class="play_bar"/>
                <!-- 黑胶碟片 -->
                <img src="images/disc.png" class="disc autoRotate"/>
                <!--专辑图片-->
                <img class="cover autoRotate" :src="musicPicUrl"/>
            </div>
            <!-- 评论容器 -->
            <div class="comment_wrapper">
                <h5 class='title'>热门留言</h5>
                <div class='comment_list'>
                    <dl v-for="(comment,index) in hotCommentList" :key="index">
                        <dt><img :src="comment.user.avatarUrl" alt=""></dt>
                        <dd class="name">{{comment.user.nickname}}</dd>
                        <dd class="detail">{{comment.content}}</dd>
                    </dl>
                </div>
                <img src="images/line.png" class="right_line">
            </div>
        </div>
        <div class="audio_con">
            <!--播放歌曲实际上就是改变‘audio’的src属性值-->
            <!--当点击播放(@play="animationPlay")和暂停(@pause="animationPause")时,触发对应的方法改变动画播放的标志-->
            <audio ref='audio' controls autoplay class="myaudio" id="audioPlay"
                   :src="musicUrl" @play="animationPlay" @pause="animationPause">
            </audio>
        </div>
        <!--MV遮罩层的div,默认不显示-->
        <!--设置MV遮罩层并设置MV地址-->
        <!--v-if是通过添加和移除dom元素来实现显示隐藏,v-show是通过改变元素的display属性值实现显示隐藏。
        style="display: none;"和v-if不能同时使用-->
        <div class="video_con" style="display: none;" v-show="isShow">
            <video controls="controls" autoplay :src="songMvUrl"></video>
            <!--点击MV外面隐藏(关闭)遮罩层和歌曲MV-->
            <div class="mask" @click="hideMv"></div>
        </div>
    </div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 官网提供的 axios 在线地址 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- 自定义js -->
<script src="./js/main.js"></script>
</body>
</html>

总结

  • 对于本地无法获取的数据,基本上都会有对应的接口。

5、其他资料(福利)

网上一个大佬写的Vue.js总结(图片)

学习Vue.js的一个网站

如果想深入学习Vue.js,可以看看官方文档:Vue.js官方文档

也可以看看这个视频教程:2021最新Vue迅速上手教程丨vue3.0入门到精通

上面这些是个人觉得比较好的教程资料,可以参考一下,没有打广告的意思,只是想给大家一个参考。不喜勿喷,谢谢。

最后希望大家看完都能有所收获,本人学后端的,写前端的东西难免有说错的地方,文章如有说错的地方,欢迎批评指正。

后面还会继续写博客的。如果这篇文章对你有所帮助,请高抬贵手,点个赞。谢谢。

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

sunnyboy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值