- Vue基础入门
Vue基础入门
1. Vue简介
1.1 Vue.js 是什么
Vue是一套用于构建用户界面的<b>渐进式框架</b>。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
1.2 Vue.js 特点
- <b>简洁:</b> HTML 模板 + JSON 数据,再创建一个 Vue 实例,就这么简单。
- <b>数据驱动:</b> 自动追踪依赖的模板表达式和计算属性。
- <b>组件化:</b> 用解耦、可复用的组件来构造界面。
- <b>轻量:</b> ~20kb min+gzip,无依赖。
- <b>快速:</b> 精确有效的异步批量 DOM 更新。
- <b>模块友好:</b> 通过 NPM 或 Bower 安装,无缝融入你的工作流。
2. 所需工具
- 包管理工具:npm,基于node.js
- 模块打包工具:webpack
- 调试 开发者工具: vscode
3. 框架体系
- MVVM框架:Vue.js
- 项目脚手架:vue-cli
- UI组件库:Element UI
- 路由配置:vue-router
- 前端请求接口:axios
- 状态管理工具:Vuex
- 脚本语言:ES6语法
- css预处理器:scss样式预处理器
4. 搭建项目
4.1 简单安装过程:
4.1.1 安装node.js
这个直接推荐一篇菜鸟安装教程,包含windows和linux下安装,传送门如何安装node.js
4.1.2 安装vue及依赖包
安装好node.js后,就已经安装了npm。但是直接使用npm下载组件会很慢,我们首先安装淘宝镜像。
一下命令都是通过打开cmd控制台里面运行,除了特别提醒需要cd到某些目录下运行的意外,其他的都直接在cmd根目录运行即可。
npm install -g cnpm --registry=https://registry.npm.taobao.org
安装完淘宝镜像后就可以较快的安装如下模块了。
全局安装webpack(安装过程可能会出错,如果出错,可以关掉cmd,重新打开,并使用npm install webpack -g命令重新安装,即不使用淘宝镜像)
cnpm install webpack -g
全局安装vue-cli脚手架
npm install vue-cli -g
接下来测试下是否成功安装了vue,cmd控制台输入:
vue -V
可以查看到vue版本,如图:
image.png
4.1.3 创建一个vue项目
首先选择需要将创建的目录放到哪个位置,使用cd命令切换到目录下
image.png
该目录下输入命令创建项目
vue create vue-project
<b>4.1.3.1</b> 选择项目的配置,此处选Manually select features手动配置项目(上下键移动项,回车选择)
image.png<b>4.1.3.2</b> 选择要使用的插件(上下键移动,空格选择,回车进入下一步),此处选babel,Router,Vuex,Css,Linter
image.png<b>4.1.3.3</b> 选择启动项目的Vue.js版本
image.png<b>4.1.3.4</b> 询问是够使用history模式,输入Y回车
image.png<b>4.1.3.5</b> 选择less
image.png<b>4.1.3.6</b> 选择eslint相关配置,此处我选第三项默认配置
image.png<b>4.1.3.7</b> 选择何时进行lint校验,保存时校验还是fix时
image.png<b>4.1.3.8</b> 询问单独配置还是一起在packpage中配置,选单独配置
image.png<b>4.1.3.9</b> 最后是否存储以上配置以便下次新建项目时使用
image.png<b>4.1.3.10</b> 至此新建成功,新项目默认没有vue.config.js,需要在根目录手动创建,然后进行自己需要的配置
<b>4.1.3.11</b> 启动项目
image.png image.png
<b>4.1.3.12</b> 安装element-ui组件库
npm i element-ui -S
引入elment
你可以引入整个 Element,或是根据需要仅引入部分组件。我们先介绍如何引入完整的 Element。
在 main.js 中写入以下内容:
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
以上代码便完成了 Element 的引入。需要注意的是,样式文件需要单独引入。
5. Vue常用指令
5.1 条件判断:v-if、v-else-if、v-else
判断条件是否成立,按条件渲染页面元素;相近的指令v-show
v-if和v-show的区别:
v-show:使用该指令的元素会始终渲染在dom中,通过改变该元素的display值来实现是否显示在页面上,不需要操作js,开销相比较小
v-if:真正的条件渲染,当满足条件时才将元素添加到dom中并渲染到页面,反之销毁元素。因此性能开销大。
<template>
<div v-if="type == 'A'">
A
</div>
<div v-else-if="type == 'B'">
B
</div>
<div v-else>
C
</div>
</template>
<script>
export default {
name: 'Home',
data () {
return {
type: 'B'
}
}
}
</script>
5.2 列表遍历、渲染:v-for
使用v-for指令可以很方便地遍历数组或对象并将其数据展示到页面上
<template>
<ul>
<li v-for="(item, index) in list" :key="index">
{{item.name}}
</li>
</ul>
</template>
<script>
export default {
name: 'Home',
data () {
return {
list: [
{
id: 1,
name: '列表一'
},
{
id: 2,
name: '列表二'
}
]
}
}
}
</script>
5.3 属性绑定: v-bind
使用v-bind用于响应地更新 HTML 特性,比如绑定某个class元素或元素的style样式。
<template>
<ul>
<li v-for="(item, index) in list" :key="index">
<a :class="item.id == 1 ? 'red': ''">
{{item.name}}
</a>
</li>
</ul>
</template>
<script>
export default {
name: 'Home',
data () {
return {
list: [
{
id: 1,
name: '列表一'
},
{
id: 2,
name: '列表二'
}
]
}
}
}
</script>
5.4 表单输入绑定:v-model
使用v-model指令来实现表单元素和数据的双向绑定。监听用户的输入,然后更新数据
<template>
<input v-model="message"/>
<p>{{message}}</p>
</template>
<script>
export default {
name: 'Home',
data () {
return {
message: 'hello'
}
},
methods: {
changeMessage () {
this.message = 'hello1'
}
}
}
</script>
5.5 事件处理:v-on (简写@)
使用v-on可以很简便地监听dom事件,如focus、click、mousedown等,同时vue允许在v-on指令中添加js代码并执行
<template>
<input v-model="message"/>
<button @click="changeMessage">点击</button>
</template>
<script>
export default {
name: 'Home',
data () {
return {
message: 'hello'
}
},
methods: {
changeMessage () {
this.message = 'hello1'
}
}
}
</script>
6.V-router路由系统
6.1 路由配置&&路由嵌套
v-router通过在router文件夹中的index.js配置路由表,可很方便的切换组件展示内容,使用路由嵌套则可很方便地实现页面菜单切换,层次分明易维护。
index.js
import { createRouter, createWebHistory } from 'vue-router'
const Home = () => import('../views/Home.vue')
const pageA = () => import('../views/a.vue')
const pageB = () => import('../views/b.vue')
const pageC = () => import('../views/c.vue')
const routes = [
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/a',
name: 'a',
component: pageA
},
{
path: '/b',
name: 'b',
component: pageB
},
{
path: '/c',
name: 'c',
component: pageC
}
]
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
Home.vue
<div class="router">
<router-link to="/a">A</router-link>
<router-link :to="{path:'/b',query:{id:1}}">B</router-link>
<router-link to="/c">C</router-link>
<router-view></router-view>
</div>
a.vue
<template>
<div class="a">
This is page A
</div>
</template>
b.vue
<template>
<div class="b">
This is page B
</div>
</template>
c.vue
<template>
<div class="c">
This is page C
</div>
</template>
6.2 路由跳转的几种方式
<b>6.2.1</b> router-link
<router-link to="/a">A</router-link>
<b>6.2.2</b> this.$router.push() (在函数里面调用)
this.$router.push({
path: '/c',
query: {
id: 1,
name: 'xc'
}
})
<b>6.2.3</b> this.$router.replace() (用法同push)
<b>两者区别:</b>
this.$router.push 跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面
this.$router.replace 跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)
6.3 动态路由的使用
动态路由(可传递参数)
this.$router.push({
path: '/c',
query: {
id: 1,
name: 'xc'
}
})
获取路由参数
<p v-if="$route.query.id">获取到的id:{{$route.query.id}}</p>
7. Vue生命周期
7.1 什么是生命周期?
简而言之:从生到死的过程,从Vue实例创建-运行-销毁的过程
Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、销毁等一系列过程
<b>生命周期方法?</b>
Vue从生到死的过程中伴随着各种各样的事件,这些事件会自动触发一些方法.这些方法我们统称为生命周期方法
生命周期钩子 = 生命周期函数 = 生命周期事件
创建期间生命周期方法
beforeCreate:
created:
beforeMount
mounted
运行期间生命周期方法
beforeUpdate
updated
销毁期间的生命周期方法
beforeDestroy
destroyed
7.2 代码解释
<script>
export default {
name: 'c',
data () {
return {
msg: 'IT程序员的日常'
}
},
components: {
},
methods: {
say () {
console.log('say')
}
},
beforeCreate () {
// 执行beforeCreate的时候,表示Vue刚刚出生,还没有任何内容,data/methods都没有初始化
console.log(this.msg, '----beforeCreate')
// this.say()
},
created () {
// 执行created的时候,表示Vue实例已经初始化好了部分内容,data/methods,想在Vue实例中最早访问到data/methods,只有在这个方法才能访问
console.log(this.msg, '----created')
this.say()
},
beforeMount () {
// 执行beforeMount,表示已经根据数据编译好了模板,但是还没有渲染到界面上
console.log('----beforeMount')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
mounted () {
// 执行mounted,表示已经根据数据编译好了模板,已经将模板有渲染到界面上,此时可以对界面进行其他操作了
console.log('----mounted')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
beforeUpdate () {
// 主要data中的数据发生了变化就会执行,执行beforeUpdate时候,data的数据已经是最新的了,但是没有更新界面上的数据
console.log(this.msg, '----beforeUpdate')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
updated () {
// 主要data中的数据发生了变化就会执行,执行updated时候,data的数据已经是最新的了,界面上的数据也已经更新
console.log(this.msg, '----updated')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
beforeDestroy () {
// 执行beforeDestroy的时候,表示Vue实例即将销毁,但是还未销毁,实例中的数据等都可以使用,最后能使用Vue实例的地址
},
destroyed () {
// 执行destroyed的时候,表示vue实例完全销毁,实例中的任何内容都不能被使用了
}
}
</script>
8. Vue组件传值
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
image.png如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。
针对不同的使用场景,如何选择行之有效的通信方式?本文总结了vue组件常用通信的几种方式,如props、on、vuex,以通俗易懂的实例讲述这其中的差别及使用场景。
8.1 props/$emit
8.1.1 父组件向子组件传值
// Home.vue父组件
<template>
<div>
<User :userList="userList"></User>
</div>
</template>
<script>
import User from '@/components/user.vue'
export default {
name: 'Home',
data () {
return {
userList: ['user1', 'user2']
}
},
components: {
User
},
methods: {
}
}
</script>
<template>
<div class="user">
<ul>
<li v-for="(item, index) in userList" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'user',
props: {
userList: { // 这个就是父组件中子标签自定义名字
type: Array,
required: true
}
},
data () {
return {
}
},
components: {
},
methods: {
}
}
</script>
总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed
8.1.2 子组件向父组件传值(通过事件形式)
// 子组件
<template>
<div class="color">
<button @click="updateColor">改变颜色</button>
</div>
</template>
<script>
export default {
name: 'color',
data () {
return {
}
},
components: {
},
methods: {
updateColor () {
this.$emit('changeColor', '子向父组件传值--绿色') // 自定义事件 传递值“子向父组件传值”
}
}
}
</script>
// 父组件
<template>
<div>
<color @changeColor="changeColor"></color>
{{color}}
</div>
</template>
<script>
import color from '@/components/color.vue'
export default {
name: 'Home',
data () {
return {
color: '红色'
}
},
components: {
color
},
methods: {
changeColor (e) {
this.color = e
}
}
}
</script>
总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。
8.2 on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
<b>8.2.1</b> 具体实现方式:
Vue 3.0写法
var Event=new Mitt();
Event.emit(事件名,数据);
Event.on(事件名,data => {});
Vue 2.0写法
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
<b>举个例子</b>
假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据
<b>初始化</b>
首先你需要做的是创建事件总线并将其导出,以便其它模块可以使用或者监听它。新创建一个 .js 文件,比如 eventBus.js :
import Mitt from 'mitt'
export default new Mitt()
<template>
<div>
<my-a></my-a>
<my-b></my-b>
<my-c></my-c>
</div>
</template>
<script>
import A from '@/components/a.vue'
import B from '@/components/b.vue'
import C from '@/components/c.vue'
export default {
name: 'Home',
data () {
return {
}
},
components: {
'my-a': A,
'my-b': B,
'my-c': C
},
methods: {
}
}
</script>
// A组件
<template>
<div>
<h4>A组件:{{name}}</h4>
<button @click="send">将数据发送给C组件</button>
</div>
</template>
<script>
import Event from '@/assets/js/eventBus.js'
export default {
name: 'a1',
data () {
return {
name: 'tom'
}
},
components: {
},
methods: {
send () {
Event.emit('data-a', this.name)
}
}
}
</script>
// B组件
<template>
<div>
<h4>B组件:{{age}}</h4>
<button @click="send">将数组发送给C组件</button>
</div>
</template>
<script>
import Event from '@/assets/js/eventBus.js'
export default {
name: 'b1',
data () {
return {
age: 20
}
},
components: {
},
methods: {
send () {
Event.emit('data-b', this.age)
}
}
}
</script>
// C组件
<template>
<div>
<h4>C组件:{{ name }},{{ age }}</h4>
</div>
</template>
<script>
import Event from '@/assets/js/eventBus.js'
export default {
name: 'c1',
data () {
return {
name: '',
age: ''
}
},
components: {},
methods: {},
mounted () {
// 在模板编译完成后执行
Event.on('data-a', (name) => {
this.name = name
})
Event.on('data-b', (age) => {
this.age = age
})
}
}
</script>
on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
8.3 Vuex使用
8.3.1 Vuex五个核心概念
VueX 是一个专门为 Vue.js 应用设计的状态管理架构,统一管理和维护各个vue组件的可变化状态(你可以理解成 vue 组件里的某些 data )。
Vue有五个核心概念,state, getters, mutations, actions, modules。
1、state:vuex的基本数据,用来存储变量(后四个属性都是用来操作state里面储存的变量的)。
2、getters:是对state里面的变量进行过滤的。
3、mutations:store中更改state数据状态的唯一方法。(mutations必须是同步函数)
4、actions:像一个装饰器,包含异步操作(请求API方法)、回调函数提交mutaions更改state数据状态,使之可以异步。
5、modules:项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
8.3.2 主要代码store.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
mutations: {
update_count (state, data) {
state.count = data
}
},
actions: {
},
modules: {
}
})
// home.vue
<template>
<div>
<p>{{count}}</p>
<button @click="add">增加</button>
<button @click="reduce">减少</button>
</div>
</template>
<script>
import { mapMutations, mapState } from 'vuex'
export default {
name: 'Home',
data () {
return {
}
},
computed: {
...mapState([
'count'
])
},
components: {
},
methods: {
...mapMutations(['update_count']),
add () {
let num = this.count
num++
this.update_count(num) // 映射 this.update_count() 为 this.$store.commit('update_count')
},
reduce () {
let num = this.count
num--
this.update_count(num)
}
}
}
</script>
9. vue综合项目——仿网易云音乐播放器
9.1 项目需求
<b>主界面</b>
image.png<b>mv界面</b>
image.png从上到下,分别是搜索栏、主体部分、音频进度条,主体部分从左到右分别是音乐列表、磁盘动画、留言列表。
<b>主要功能有:</b>
1.歌曲搜索:搜索栏中输入关键字,按下回车或者鼠标点击输入框后面的放大镜图标,返回给左侧音乐列表所有相关的歌曲名称。
2.歌曲播放:点击音乐列表中每条音乐前面的红色播放图标实现播放功能,下方进度条显示当前播放进度。
3.查看热评:歌曲播放的同时,右侧评论列表展示所有热评。
4.歌曲播放时的动画:歌曲播放的同时,磁盘中央封面改成相对应的专辑封面,上面的磁头旋转到磁盘上,歌曲暂停后,磁头再旋转回去。
5.mv播放:点击音乐列表中右侧的小电视图标,展现出四周由遮罩层包含的mv界面。
6.mv暂停:点击四周遮罩层,mv暂停,回到主界面。
9.2 相关接口
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的地址
9.3 代码实现
<b>html:</b>
<template>
<div class="wrap">
<div class="play_wrap" id="player">
<div class="search_bar">
<img src="../assets/img/player_title.png" alt="" />
<!-- 搜索歌曲 -->
<input
type="text"
autocomplete="off"
v-model="query"
@keyup.enter="searchMusic()"
/>
</div>
<div class="center_con">
<!-- 搜索歌曲列表 -->
<div class="song_wrapper" ref="song_wrapper">
<ul class="song_list">
<li v-for="(item, index) in musicList" :key="index">
<!-- 点击放歌 -->
<a href="javascript:;" @click="playMusic(item.id)"></a>
<b>{{ item.name }}</b>
<span>
<i @click="playMv(item.mvid)" v-if="item.mvid != 0"></i>
</span>
</li>
</ul>
<img src="../assets/img/line.png" class="switch_btn" alt="" />
</div>
<!-- 歌曲信息容器 -->
<div class="player_con" :class="{ playing: isPlay }">
<img src="../assets/img/player_bar.png" class="play_bar" />
<!-- 黑胶碟片 -->
<img src="../assets/img/disc.png" class="disc autoRotate" />
<img
:src="coverUrl == '' ? require('../assets/img/cover.png') : coverUrl"
class="cover autoRotate"
/>
</div>
<!-- 评论容器 -->
<div class="comment_wrapper" ref="comment_wrapper">
<h5 class="title">热门留言</h5>
<div class="comment_list">
<dl v-for="(item, index) in hotComments" :key="index">
<dt>
<img :src="item.user.avatarUrl" alt="" />
</dt>
<dd class="name">{{ item.user.nickname }}</dd>
<dd class="detail">
{{ item.content }}
</dd>
</dl>
</div>
<img src="../assets/img/line.png" class="right_line" />
</div>
</div>
<!--播放-->
<div class="audio_con">
<audio
ref="audio"
@play="play"
@pause="pause"
:src="musicUrl"
controls
autoplay
loop
class="myaudio"
></audio>
</div>
<!-- 播放mv -->
<div class="video_con" v-show="showVideo">
<video ref="video" :src="mvUrl" controls="controls"></video>
<div class="mask" @click="closeMv"></div>
</div>
</div>
</div>
</template>
<b>css:</b>
<style scoped>
body,
ul,
dl,
dd {
margin: 0px;
padding: 0px;
}
.wrap {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: url("../assets/img/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;
}
.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("../assets/img/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("../assets/img/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("../assets/img/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("../assets/img/shutoff.png") no-repeat;
left: 50%;
margin-left: 400px;
margin-top: -273px;
top: 50%;
z-index: 995;
}
</style>
<b>js:</b>
<script>
import axios from 'axios'
export default {
name: 'music',
data () {
return {
// 搜索关键字
query: '',
// 歌曲列表
musicList: [],
// 歌曲url
musicUrl: '',
// 是否正在播放
isPlay: false,
// 歌曲热门评论
hotComments: [],
// 歌曲封面地址
coverUrl: '',
// 显示视频播放
showVideo: false,
// mv地址
mvUrl: ''
}
},
components: {},
// 方法
methods: {
// 搜索歌曲
searchMusic () {
if (this.query == 0) {
return false
}
axios.get('/api/search?keywords=' + this.query).then(response => {
// 保存内容
this.musicList = response.data.result.songs
})
// 清空搜索
this.query = ''
},
// 播放歌曲
playMusic (musicId) {
// 获取歌曲url
axios.get('/api/song/url?id=' + musicId).then(response => {
// 保存歌曲url地址
this.musicUrl = response.data.data[0].url
})
// 获取歌曲热门评论
axios.get('/api/comment/hot?type=0&id=' + musicId).then(response => {
// 保存热门评论
this.hotComments = response.data.hotComments
})
// 获取歌曲封面
axios.get('/api/song/detail?ids=' + musicId).then(response => {
// 设置封面
this.coverUrl = response.data.songs[0].al.picUrl
})
},
// audio的play事件
play () {
this.isPlay = true
// 清空mv信息
this.mvUrl = ''
},
// audio的pause事件
pause () {
this.isPlay = false
},
// 播放mv
playMv (vid) {
if (vid) {
this.showVideo = true
// 获取mv信息
axios.get('/api/mv/url?id=' + vid).then(response => {
// 暂停歌曲播放
this.$refs.audio.pause()
// 获取mv地址
this.mvUrl = response.data.data.url
})
}
},
// 关闭mv界面
closeMv () {
this.showVideo = false
this.$refs.video.pause()
}
}
}
</script>
网友评论