- Vue基礎(chǔ)入門(mén)
Vue基礎(chǔ)入門(mén)
1. Vue簡(jiǎn)介
1.1 Vue.js 是什么
Vue是一套用于構(gòu)建用戶界面的<b>漸進(jìn)式框架</b>。與其它大型框架不同的是,Vue 被設(shè)計(jì)為可以自底向上逐層應(yīng)用。Vue 的核心庫(kù)只關(guān)注視圖層,不僅易于上手,還便于與第三方庫(kù)或既有項(xiàng)目整合。另一方面,當(dāng)與現(xiàn)代化的工具鏈以及各種支持類(lèi)庫(kù)結(jié)合使用時(shí),Vue 也完全能夠?yàn)閺?fù)雜的單頁(yè)應(yīng)用提供驅(qū)動(dòng)。
1.2 Vue.js 特點(diǎn)
- <b>簡(jiǎn)潔:</b> HTML 模板 + JSON 數(shù)據(jù),再創(chuàng)建一個(gè) Vue 實(shí)例,就這么簡(jiǎn)單。
- <b>數(shù)據(jù)驅(qū)動(dòng):</b> 自動(dòng)追蹤依賴的模板表達(dá)式和計(jì)算屬性。
- <b>組件化:</b> 用解耦、可復(fù)用的組件來(lái)構(gòu)造界面。
- <b>輕量:</b> ~20kb min+gzip,無(wú)依賴。
- <b>快速:</b> 精確有效的異步批量 DOM 更新。
- <b>模塊友好:</b> 通過(guò) NPM 或 Bower 安裝,無(wú)縫融入你的工作流。
2. 所需工具
- 包管理工具:npm,基于node.js
- 模塊打包工具:webpack
- 調(diào)試 開(kāi)發(fā)者工具: vscode
3. 框架體系
- MVVM框架:Vue.js
- 項(xiàng)目腳手架:vue-cli
- UI組件庫(kù):Element UI
- 路由配置:vue-router
- 前端請(qǐng)求接口:axios
- 狀態(tài)管理工具:Vuex
- 腳本語(yǔ)言:ES6語(yǔ)法
- css預(yù)處理器:scss樣式預(yù)處理器
4. 搭建項(xiàng)目
4.1 簡(jiǎn)單安裝過(guò)程:
4.1.1 安裝node.js
這個(gè)直接推薦一篇菜鳥(niǎo)安裝教程,包含windows和linux下安裝,傳送門(mén)如何安裝node.js
4.1.2 安裝vue及依賴包
安裝好node.js后,就已經(jīng)安裝了npm。但是直接使用npm下載組件會(huì)很慢,我們首先安裝淘寶鏡像。
一下命令都是通過(guò)打開(kāi)cmd控制臺(tái)里面運(yùn)行,除了特別提醒需要cd到某些目錄下運(yùn)行的意外,其他的都直接在cmd根目錄運(yùn)行即可。
npm install -g cnpm --registry=https://registry.npm.taobao.org
安裝完淘寶鏡像后就可以較快的安裝如下模塊了。
全局安裝webpack(安裝過(guò)程可能會(huì)出錯(cuò),如果出錯(cuò),可以關(guān)掉cmd,重新打開(kāi),并使用npm install webpack -g命令重新安裝,即不使用淘寶鏡像)
cnpm install webpack -g
全局安裝vue-cli腳手架
npm install vue-cli -g
接下來(lái)測(cè)試下是否成功安裝了vue,cmd控制臺(tái)輸入:
vue -V
可以查看到vue版本,如圖:

4.1.3 創(chuàng)建一個(gè)vue項(xiàng)目
首先選擇需要將創(chuàng)建的目錄放到哪個(gè)位置,使用cd命令切換到目錄下

該目錄下輸入命令創(chuàng)建項(xiàng)目
vue create vue-project
<b>4.1.3.1</b> 選擇項(xiàng)目的配置,此處選Manually select features手動(dòng)配置項(xiàng)目(上下鍵移動(dòng)項(xiàng),回車(chē)選擇)

<b>4.1.3.2</b> 選擇要使用的插件(上下鍵移動(dòng),空格選擇,回車(chē)進(jìn)入下一步),此處選babel,Router,Vuex,Css,Linter

<b>4.1.3.3</b> 選擇啟動(dòng)項(xiàng)目的Vue.js版本

<b>4.1.3.4</b> 詢問(wèn)是夠使用history模式,輸入Y回車(chē)

<b>4.1.3.5</b> 選擇less

<b>4.1.3.6</b> 選擇eslint相關(guān)配置,此處我選第三項(xiàng)默認(rèn)配置

<b>4.1.3.7</b> 選擇何時(shí)進(jìn)行l(wèi)int校驗(yàn),保存時(shí)校驗(yàn)還是fix時(shí)

<b>4.1.3.8</b> 詢問(wèn)單獨(dú)配置還是一起在packpage中配置,選單獨(dú)配置

<b>4.1.3.9</b> 最后是否存儲(chǔ)以上配置以便下次新建項(xiàng)目時(shí)使用

<b>4.1.3.10</b> 至此新建成功,新項(xiàng)目默認(rèn)沒(méi)有vue.config.js,需要在根目錄手動(dòng)創(chuàng)建,然后進(jìn)行自己需要的配置
<b>4.1.3.11</b> 啟動(dòng)項(xiàng)目


<b>4.1.3.12</b> 安裝element-ui組件庫(kù)
npm i element-ui -S
引入elment
你可以引入整個(gè) Element,或是根據(jù)需要僅引入部分組件。我們先介紹如何引入完整的 Element。
在 main.js 中寫(xiě)入以下內(nèi)容:
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 的引入。需要注意的是,樣式文件需要單獨(dú)引入。
5. Vue常用指令
5.1 條件判斷:v-if、v-else-if、v-else
判斷條件是否成立,按條件渲染頁(yè)面元素;相近的指令v-show
v-if和v-show的區(qū)別:
v-show:使用該指令的元素會(huì)始終渲染在dom中,通過(guò)改變?cè)撛氐膁isplay值來(lái)實(shí)現(xiàn)是否顯示在頁(yè)面上,不需要操作js,開(kāi)銷(xiāo)相比較小
v-if:真正的條件渲染,當(dāng)滿足條件時(shí)才將元素添加到dom中并渲染到頁(yè)面,反之銷(xiāo)毀元素。因此性能開(kāi)銷(xiāo)大。
<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指令可以很方便地遍歷數(shù)組或?qū)ο蟛⑵鋽?shù)據(jù)展示到頁(yè)面上
<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用于響應(yīng)地更新 HTML 特性,比如綁定某個(gè)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指令來(lái)實(shí)現(xiàn)表單元素和數(shù)據(jù)的雙向綁定。監(jiān)聽(tīng)用戶的輸入,然后更新數(shù)據(jù)
<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 (簡(jiǎn)寫(xiě)@)
使用v-on可以很簡(jiǎn)便地監(jiān)聽(tīng)dom事件,如focus、click、mousedown等,同時(shí)vue允許在v-on指令中添加js代碼并執(zhí)行
<template>
<input v-model="message"/>
<button @click="changeMessage">點(diǎn)擊</button>
</template>
<script>
export default {
name: 'Home',
data () {
return {
message: 'hello'
}
},
methods: {
changeMessage () {
this.message = 'hello1'
}
}
}
</script>
6.V-router路由系統(tǒng)
6.1 路由配置&&路由嵌套
v-router通過(guò)在router文件夾中的index.js配置路由表,可很方便的切換組件展示內(nèi)容,使用路由嵌套則可很方便地實(shí)現(xiàn)頁(yè)面菜單切換,層次分明易維護(hù)。
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 路由跳轉(zhuǎn)的幾種方式
<b>6.2.1</b> router-link
<router-link to="/a">A</router-link>
<b>6.2.2</b> this.$router.push() (在函數(shù)里面調(diào)用)
this.$router.push({
path: '/c',
query: {
id: 1,
name: 'xc'
}
})
<b>6.2.3</b> this.$router.replace() (用法同push)
<b>兩者區(qū)別:</b>
this.$router.push 跳轉(zhuǎn)到指定url路徑,并想history棧中添加一個(gè)記錄,點(diǎn)擊后退會(huì)返回到上一個(gè)頁(yè)面
this.$router.replace 跳轉(zhuǎn)到指定url路徑,但是history棧中不會(huì)有記錄,點(diǎn)擊返回會(huì)跳轉(zhuǎn)到上上個(gè)頁(yè)面 (就是直接替換了當(dāng)前頁(yè)面)
6.3 動(dòng)態(tài)路由的使用
動(dòng)態(tài)路由(可傳遞參數(shù))
this.$router.push({
path: '/c',
query: {
id: 1,
name: 'xc'
}
})
獲取路由參數(shù)
<p v-if="$route.query.id">獲取到的id:{{$route.query.id}}</p>
7. Vue生命周期
7.1 什么是生命周期?
簡(jiǎn)而言之:從生到死的過(guò)程,從Vue實(shí)例創(chuàng)建-運(yùn)行-銷(xiāo)毀的過(guò)程
Vue實(shí)例有一個(gè)完整的生命周期,也就是從開(kāi)始創(chuàng)建、初始化數(shù)據(jù)、編譯模板、掛載Dom、渲染→更新→渲染、銷(xiāo)毀等一系列過(guò)程
<b>生命周期方法?</b>
Vue從生到死的過(guò)程中伴隨著各種各樣的事件,這些事件會(huì)自動(dòng)觸發(fā)一些方法.這些方法我們統(tǒng)稱(chēng)為生命周期方法
生命周期鉤子 = 生命周期函數(shù) = 生命周期事件
創(chuàng)建期間生命周期方法
beforeCreate:
created:
beforeMount
mounted
運(yùn)行期間生命周期方法
beforeUpdate
updated
銷(xiāo)毀期間的生命周期方法
beforeDestroy
destroyed
7.2 代碼解釋
<script>
export default {
name: 'c',
data () {
return {
msg: 'IT程序員的日常'
}
},
components: {
},
methods: {
say () {
console.log('say')
}
},
beforeCreate () {
// 執(zhí)行beforeCreate的時(shí)候,表示Vue剛剛出生,還沒(méi)有任何內(nèi)容,data/methods都沒(méi)有初始化
console.log(this.msg, '----beforeCreate')
// this.say()
},
created () {
// 執(zhí)行created的時(shí)候,表示Vue實(shí)例已經(jīng)初始化好了部分內(nèi)容,data/methods,想在Vue實(shí)例中最早訪問(wèn)到data/methods,只有在這個(gè)方法才能訪問(wèn)
console.log(this.msg, '----created')
this.say()
},
beforeMount () {
// 執(zhí)行beforeMount,表示已經(jīng)根據(jù)數(shù)據(jù)編譯好了模板,但是還沒(méi)有渲染到界面上
console.log('----beforeMount')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
mounted () {
// 執(zhí)行mounted,表示已經(jīng)根據(jù)數(shù)據(jù)編譯好了模板,已經(jīng)將模板有渲染到界面上,此時(shí)可以對(duì)界面進(jìn)行其他操作了
console.log('----mounted')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
beforeUpdate () {
// 主要data中的數(shù)據(jù)發(fā)生了變化就會(huì)執(zhí)行,執(zhí)行beforeUpdate時(shí)候,data的數(shù)據(jù)已經(jīng)是最新的了,但是沒(méi)有更新界面上的數(shù)據(jù)
console.log(this.msg, '----beforeUpdate')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
updated () {
// 主要data中的數(shù)據(jù)發(fā)生了變化就會(huì)執(zhí)行,執(zhí)行updated時(shí)候,data的數(shù)據(jù)已經(jīng)是最新的了,界面上的數(shù)據(jù)也已經(jīng)更新
console.log(this.msg, '----updated')
console.log(document.querySelector('p').innerText)
console.log(document.querySelector('p').innerHTML)
},
beforeDestroy () {
// 執(zhí)行beforeDestroy的時(shí)候,表示Vue實(shí)例即將銷(xiāo)毀,但是還未銷(xiāo)毀,實(shí)例中的數(shù)據(jù)等都可以使用,最后能使用Vue實(shí)例的地址
},
destroyed () {
// 執(zhí)行destroyed的時(shí)候,表示vue實(shí)例完全銷(xiāo)毀,實(shí)例中的任何內(nèi)容都不能被使用了
}
}
</script>
8. Vue組件傳值
組件是 vue.js最強(qiáng)大的功能之一,而組件實(shí)例的作用域是相互獨(dú)立的,這就意味著不同組件之間的數(shù)據(jù)無(wú)法相互引用。一般來(lái)說(shuō),組件可以有以下幾種關(guān)系:

如上圖所示,A 和 B、B 和 C、B 和 D 都是父子關(guān)系,C 和 D 是兄弟關(guān)系,A 和 C 是隔代關(guān)系(可能隔多代)。
針對(duì)不同的使用場(chǎng)景,如何選擇行之有效的通信方式?本文總結(jié)了vue組件常用通信的幾種方式,如props、on、vuex,以通俗易懂的實(shí)例講述這其中的差別及使用場(chǎng)景。
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: { // 這個(gè)就是父組件中子標(biāo)簽自定義名字
type: Array,
required: true
}
},
data () {
return {
}
},
components: {
},
methods: {
}
}
</script>
總結(jié):父組件通過(guò)props向下傳遞數(shù)據(jù)給子組件。注:組件中的數(shù)據(jù)共有三種形式:data、props、computed
8.1.2 子組件向父組件傳值(通過(guò)事件形式)
// 子組件
<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>
總結(jié):子組件通過(guò)events給父組件發(fā)送消息,實(shí)際上就是子組件把自己的數(shù)據(jù)發(fā)送到父組件。
8.2
on
這種方法通過(guò)一個(gè)空的Vue實(shí)例作為中央事件總線(事件中心),用它來(lái)觸發(fā)事件和監(jiān)聽(tīng)事件,巧妙而輕量地實(shí)現(xiàn)了任何組件間的通信,包括父子、兄弟、跨級(jí)。當(dāng)我們的項(xiàng)目比較大時(shí),可以選擇更好的狀態(tài)管理解決方案vuex。
<b>8.2.1</b> 具體實(shí)現(xiàn)方式:
Vue 3.0寫(xiě)法
var Event=new Mitt();
Event.emit(事件名,數(shù)據(jù));
Event.on(事件名,data => {});
Vue 2.0寫(xiě)法
var Event=new Vue();
Event.$emit(事件名,數(shù)據(jù));
Event.$on(事件名,data => {});
<b>舉個(gè)例子</b>
假設(shè)兄弟組件有三個(gè),分別是A、B、C組件,C組件如何獲取A或者B組件的數(shù)據(jù)
<b>初始化</b>
首先你需要做的是創(chuàng)建事件總線并將其導(dǎo)出,以便其它模塊可以使用或者監(jiān)聽(tīng)它。新創(chuàng)建一個(gè) .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">將數(shù)據(jù)發(fā)送給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">將數(shù)組發(fā)送給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 () {
// 在模板編譯完成后執(zhí)行
Event.on('data-a', (name) => {
this.name = name
})
Event.on('data-b', (age) => {
this.age = age
})
}
}
</script>
on 監(jiān)聽(tīng)了自定義事件 data-a和data-b,因?yàn)橛袝r(shí)不確定何時(shí)會(huì)觸發(fā)事件,一般會(huì)在 mounted 或 created 鉤子中來(lái)監(jiān)聽(tīng)。
8.3 Vuex使用
8.3.1 Vuex五個(gè)核心概念
VueX 是一個(gè)專(zhuān)門(mén)為 Vue.js 應(yīng)用設(shè)計(jì)的狀態(tài)管理架構(gòu),統(tǒng)一管理和維護(hù)各個(gè)vue組件的可變化狀態(tài)(你可以理解成 vue 組件里的某些 data )。
Vue有五個(gè)核心概念,state, getters, mutations, actions, modules。
1、state:vuex的基本數(shù)據(jù),用來(lái)存儲(chǔ)變量(后四個(gè)屬性都是用來(lái)操作state里面儲(chǔ)存的變量的)。
2、getters:是對(duì)state里面的變量進(jìn)行過(guò)濾的。
3、mutations:store中更改state數(shù)據(jù)狀態(tài)的唯一方法。(mutations必須是同步函數(shù))
4、actions:像一個(gè)裝飾器,包含異步操作(請(qǐng)求API方法)、回調(diào)函數(shù)提交mutaions更改state數(shù)據(jù)狀態(tài),使之可以異步。
5、modules:項(xiàng)目特別復(fù)雜的時(shí)候,可以讓每一個(gè)模塊擁有自己的state、mutation、action、getters,使得結(jié)構(gòu)非常清晰,方便管理。
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綜合項(xiàng)目——仿網(wǎng)易云音樂(lè)播放器
9.1 項(xiàng)目需求
<b>主界面</b>

<b>mv界面</b>

從上到下,分別是搜索欄、主體部分、音頻進(jìn)度條,主體部分從左到右分別是音樂(lè)列表、磁盤(pán)動(dòng)畫(huà)、留言列表。
<b>主要功能有:</b>
1.歌曲搜索:搜索欄中輸入關(guān)鍵字,按下回車(chē)或者鼠標(biāo)點(diǎn)擊輸入框后面的放大鏡圖標(biāo),返回給左側(cè)音樂(lè)列表所有相關(guān)的歌曲名稱(chēng)。
2.歌曲播放:點(diǎn)擊音樂(lè)列表中每條音樂(lè)前面的紅色播放圖標(biāo)實(shí)現(xiàn)播放功能,下方進(jìn)度條顯示當(dāng)前播放進(jìn)度。
3.查看熱評(píng):歌曲播放的同時(shí),右側(cè)評(píng)論列表展示所有熱評(píng)。
4.歌曲播放時(shí)的動(dòng)畫(huà):歌曲播放的同時(shí),磁盤(pán)中央封面改成相對(duì)應(yīng)的專(zhuān)輯封面,上面的磁頭旋轉(zhuǎn)到磁盤(pán)上,歌曲暫停后,磁頭再旋轉(zhuǎn)回去。
5.mv播放:點(diǎn)擊音樂(lè)列表中右側(cè)的小電視圖標(biāo),展現(xiàn)出四周由遮罩層包含的mv界面。
6.mv暫停:點(diǎn)擊四周遮罩層,mv暫停,回到主界面。
9.2 相關(guān)接口
1.歌曲搜索接口
請(qǐng)求地址:https://autumnfish.cn/search
請(qǐng)求方法:get
請(qǐng)求參數(shù):keywords(查詢關(guān)鍵字)
響應(yīng)內(nèi)容:歌曲搜索結(jié)果
2.歌曲url獲取接口
請(qǐng)求地址:https://autumnfish.cn/song/url
請(qǐng)求方法:get
請(qǐng)求參數(shù):id(歌曲id)
響應(yīng)內(nèi)容:歌曲url地址
3.歌曲詳情獲取
請(qǐng)求地址:https://autumnfish.cn/song/detail
請(qǐng)求方法:get
請(qǐng)求參數(shù):ids(歌曲id)
響應(yīng)內(nèi)容:歌曲詳情(包括封面信息)
4.熱門(mén)評(píng)論獲取
請(qǐng)求地址:https://autumnfish.cn/comment/hot?type=0
請(qǐng)求方法:get
請(qǐng)求參數(shù):id(歌曲id,地址中的type固定為0)
響應(yīng)內(nèi)容:歌曲的熱門(mén)評(píng)論
5.mv地址獲取
請(qǐng)求地址:https://autumnfish.cn/mv/url
請(qǐng)求方法:get
請(qǐng)求參數(shù):id(mvid,為0表示沒(méi)有mv)
響應(yīng)內(nèi)容:mv的地址
9.3 代碼實(shí)現(xiàn)
<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">
<!-- 點(diǎn)擊放歌 -->
<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>
<!-- 評(píng)論容器 -->
<div class="comment_wrapper" ref="comment_wrapper">
<h5 class="title">熱門(mén)留言</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;
}
/* 旋轉(zhuǎn)的動(dòng)畫(huà) */
@keyframes Rotate {
from {
transform: rotateZ(0);
}
to {
transform: rotateZ(360deg);
}
}
/* 旋轉(zhuǎn)的類(lèi)名 */
.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;
}
/* 播放桿 轉(zhuǎn)回去 */
.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 {
// 搜索關(guān)鍵字
query: '',
// 歌曲列表
musicList: [],
// 歌曲url
musicUrl: '',
// 是否正在播放
isPlay: false,
// 歌曲熱門(mén)評(píng)論
hotComments: [],
// 歌曲封面地址
coverUrl: '',
// 顯示視頻播放
showVideo: false,
// mv地址
mvUrl: ''
}
},
components: {},
// 方法
methods: {
// 搜索歌曲
searchMusic () {
if (this.query == 0) {
return false
}
axios.get('/api/search?keywords=' + this.query).then(response => {
// 保存內(nèi)容
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
})
// 獲取歌曲熱門(mén)評(píng)論
axios.get('/api/comment/hot?type=0&id=' + musicId).then(response => {
// 保存熱門(mén)評(píng)論
this.hotComments = response.data.hotComments
})
// 獲取歌曲封面
axios.get('/api/song/detail?ids=' + musicId).then(response => {
// 設(shè)置封面
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
})
}
},
// 關(guān)閉mv界面
closeMv () {
this.showVideo = false
this.$refs.video.pause()
}
}
}
</script>