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版本,如圖:


image.png

4.1.3 創(chuàng)建一個(gè)vue項(xiàng)目

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


image.png

該目錄下輸入命令創(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ē)選擇)

image.png

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

image.png

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

image.png

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

image.png

<b>4.1.3.5</b> 選擇less

image.png

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

image.png

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

image.png

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

image.png

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

image.png

<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)目


image.png
image.png

<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)系:

image.png

如上圖所示,A 和 B、B 和 C、B 和 D 都是父子關(guān)系,C 和 D 是兄弟關(guān)系,A 和 C 是隔代關(guān)系(可能隔多代)。

針對(duì)不同的使用場(chǎng)景,如何選擇行之有效的通信方式?本文總結(jié)了vue組件常用通信的幾種方式,如props、emit/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 emit/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>

image.png

<b>mv界面</b>

image.png

從上到下,分別是搜索欄、主體部分、音頻進(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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容