【个人练习】网易云音乐PC端仿站 Vue 2(三)

0
回复
114
查看
[复制链接]

微信扫一扫 分享朋友圈

2

主题

5

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 2023-2-14 19:55:54 | 显示全部楼层 |阅读模式
目录

【个人练习】网易云音乐PC端仿站 Vue 2(一)
【个人练习】网易云音乐PC端仿站 Vue 2(二)
【个人练习】网易云音乐PC端仿站 Vue 2(三)
【个人练习】网易云音乐PC端仿站 Vue 2(四)
【个人练习】网易云音乐PC端仿站 Vue 2(五)
【个人练习】网易云音乐PC端仿站 Vue 2(六)
【个人练习】网易云音乐PC端仿站 Vue 2(七)
7 根组件

根组件包含:头部导航栏、路由出口、返回顶部组件、音乐播放器组件、底部组件
注:在登录状态下,“我的音乐”页面不包含底部组件
<template>
  <div id="app">
    <!-- 头部导航 -->
    <Header />
    <!-- 路由出口 -->
    <router-view></router-view>
    <!-- 返回顶部组件 -->
    <GoTop />
    <!-- 音乐播放器组件 -->
    <Music />
    <!-- 底部组件 -->
    <Footer v-show="isShowFooter"/>
  </div>
</template>

<script>
import Header from '@/components/Header.vue'
import GoTop from '@/components/GoTop.vue'
import Music from '@/components/Music.vue'
import Footer from '@/components/Footer.vue'

export default {
  name: 'App',
  components: { Header, GoTop, Music, Footer },
  data() {
    return {
      isShowFooter: true
    }
  },

  computed: {
    path() {
      return this.$route.path
    }
  },

  watch: {
    path() {
      if (this.$route.path.indexOf('/my') !== -1 && localStorage.getItem('COOKIE')) this.isShowFooter = false
      else this.isShowFooter = true
    }
  }
}
</script>7-1 头部导航组件 Header




  • 通过this.$route.path获取当前路由地址,判断当前选中的导航栏项目,赋予选中样式。
<template>
<div class="header">
  <!-- 导航栏 -->
  <ul class="nav">
    <li class="nav-item" :class="[curIndex === 0 ? 'is-active' : '']" @click="curIndex = 0">
      <router-link to="/">发现音乐</router-link>
      <sub :class="[curIndex === 0 ? 'cur' : '']"></sub>
    </li>
      
    <li class="nav-item" :class="[curIndex === 1 ? 'is-active' : '']" @click="curIndex = 1">
      <router-link to="/my">我的音乐</router-link>
      <sub :class="[curIndex === 1 ? 'cur' : '']"></sub>
    </li>
      
    <li class="nav-item" :class="[curIndex === 2 ? 'is-active' : '']" @click="curIndex = 2">
      <router-link to="/friend">关注</router-link>
      <sub :class="[curIndex === 2 ? 'cur' : '']"></sub>
    </li>
      
    <li class="nav-item">
      <a href="https://music.163.com/store/product" target="_blank">商城</a>
    </li>
      
    <li class="nav-item">
      <a href="https://music.163.com/st/musician" target="_blank">音乐人</a>
    </li>
      
    <li class="nav-item">
      <a href="https://music.163.com/#/download">下载客户端</a>
      <sup class="hot"></sup>
    </li>
  </ul>
</div>
</template>

<script>
export default {
  data () {
    return {
      isFixed: false, // 头部导航栏是否固定
    }
  }
  computed: {
    path() {
      return this.$route.path // 获取当前路径
    },
  },
  
  watch: {
    path: {
      handler() {
        // 当前url
        this.getCurIndex() // path改变时,更新导航栏item序号
        // Header在'我的音乐'页面固定
        if (this.$route.path.indexOf('/my') !== -1) this.isFixed = true
        else this.isFixed = false
      },
      immediate: true,
    }
  },
   
  methods: {
    // 获取导航栏当前展示的item序号
    getCurIndex() {
      let path = this.$route.path
      if (path.indexOf('/home') != -1) this.curIndex = 0
      else if (path.indexOf('/my') != -1) this.curIndex = 1
      else if (path.indexOf('/friend') != -1) this.curIndex = 2
      else this.curIndex = 0
    }
  }
}
</script>2. 搜索框:



  • 搜索框中输入的关键字使用v-model属性双向绑定,使用变量searchText记录搜索关键字;
  • 当搜索框获得焦点,且关键字不为空时,展示搜索建议列表,使用布尔变量isOpenSearch控制搜索建议列表展示与否;在失去焦点时,使用定时器延后将变量isOpenSearch变为false,这样是为了防止点击搜索列表中的歌曲、歌手、专辑、歌单等页面无法跳转。
  • 调用接口获取搜索建议:
搜索建议
说明 : 调用此接口 , 传入搜索关键词可获得搜索建议 , 搜索结果同时包含单曲 , 歌手 , 歌单信息
必选参数 :keywords : 关键词
可选参数 :type : 如果传 'mobile' 则返回移动端数据
接口地址 :/search/suggest
// api/index.js
// 获取搜索建议
export function reqSearchSuggest(keywords) {
  return request('/search/suggest', 'get', { 'keywords': keywords })
}
<template>
<div class="header">
  <!-- 搜索栏 -->
  <div class="search">
    <i class="el-icon-search"></i>
    <input
      v-model="searchText"
      type="text"
      placeholder="音乐/视频/电台/用户"
      onfocus="this.placeholder=''"
      onblur="this.placeholder='音乐/视频/电台/用户'"
      @focus="isOpenSearch = true"
      @blur="searchBlur"
    />
  </div>

  <!-- 搜索建议列表 -->
  <div class="searchList" v-show="isOpenSearch && searchText">
    <p class="note">
      <a href="javascript:;">搜"{{ searchText }}" 相关用户</a>>
    </p>
    <div class="rap">
      <div class="songs" v-if="suggestList.songs">
        <h3 class="hd">
          <i class="icn"></i>单曲
        </h3>
        <ul class="bd">
          <li v-for="item in suggestList.songs" :key="item.id">
            <router-link :to="{ path: '/song', query: { id: `${item.id}` } }" class="ellipsis">{{item.name}}</router-link>
          </li>
        </ul>
      </div>
      <div class="artists" v-if="suggestList.artists">
        <h3 class="hd">
          <i class="icn"></i>歌手
        </h3>
        <ul class="bd odd">
          <li v-for="item in suggestList.artists" :key="item.id">
            <router-link :to="{ path: '/artist', query: { id: `${item.id}` } }" class="ellipsis">{{item.name}}</router-link>
          </li>
        </ul>
      </div>
      <div class="albums" v-if="suggestList.albums">
        <h3 class="hd">
          <i class="icn"></i>专辑
        </h3>
        <ul class="bd">
          <li v-for="item in suggestList.albums" :key="item.id">
            <router-link :to="{ path: '/album', query: { id: `${item.id}` } }" class="ellipsis">{{item.name}}</router-link>
          </li>
        </ul>
      </div>
      <div class="playlists" v-if="suggestList.playlists">
        <h3 class="hd">
          <i class="icn"></i>歌单
        </h3>
        <ul class="bd odd">
          <li v-for="item in suggestList.playlists" :key="item.id">
            <router-link :to="{ path: '/playlist', query: { id: `${item.id}` } }" class="ellipsis">{{item.name}}</router-link>
          </li>
        </ul>
      </div>
    </div>
  </div>
</div>
</template>

<script>
export default{
  data () {
    return {
      searchText: '', // 搜索内容
      isOpenSearch: true, // 是否展示搜索建议列表
      suggestList: {}, // 搜索建议
    }
  },
   
  watch: {
    searchText() {
      // 搜索栏搜索内容
      if (this.searchText !== '') {
        this.getSearchSuggest()
      }
    }
  },
   
  methods: {
    // 搜索栏失去焦点
    searchBlur() {
      setTimeout(() => {
        this.isOpenSearch = false
      }, 100)
    },
      
     // 获取搜索建议
    async getSearchSuggest() {
      let result = await this.$api.reqSearchSuggest(this.searchText)
      this.suggestList = result.result
    },
  }
}
</script>
3. 判断本地localStorage是否保存有cookie,如果有cookie,根据cookie获取用户信息,当前处于登录状态,头部导航栏中显示用户头像;否则为未登录状态,头部导航栏中显示登录按钮。
获取账号信息
说明 : 登录后调用此接口 ,可获取用户账号信息
接口地址 :/user/account
// 根据Cookie获取账号信息
export function reqUserAccount() {
  return request('/user/account', 'post')
}

<template>
<div class="header">
  <!-- 登录按钮 -->
  <div class="login" v-if="!isLogin" @click="openLoginBox">
    <a href="javascript:;">登录</a>
  </div>
  <!-- 用户头像 -->
  <div class="user" v-if="isLogin">
    <img :src="userImg" @mouseenter="isShowUserBox = true" />
  </div>
</div>
</template>

<script>
export default {
  data () {
    return {
      isLogin: false, // 是否登录
      userImg: '', // 用户头像
      isShowUserBox: false, // 是否展示用户属性框
    }
  },
   
  mounted() {
    // 获取用户信息
    try {
      if (this.$store.state.user.cookie) {
        this.$store
          .dispatch('user/getUserInfo', this.$store.state.user.cookie)
          .then(() => {
            this.userImg = this.$store.state.user.userInfo.avatarUrl
            this.isLogin = true
          })
      }
    } catch (err) {
      console.log(err)
      this.isLogin = false
    }
  }
}
</script>4. 在登录状态下,当鼠标移动到用户头像时,展示用户属性框。这里只实现了退出登录的功能,当点击退出登录时,清除本地保存的cookie以及用户ID,清除用户信息,展示登录按钮。


退出登录
说明 : 调用此接口 , 可退出登录
接口地址:/logout
// 退出登录
export function reqLogout() {
  return request('/logout', 'get')
}

<template>
<div class="header">
  <!-- 用户属性框 -->
  <div class="user-box" v-show="isShowUserBox" @mouseleave="isShowUserBox = false">
    <i class="arrow"></i>
    <ul>
      <li>
        <a href="javascript:;"><i class="icon-user"></i>我的主页</a>
      </li>
      <li>
        <a href="javascript:;"><i class="icon-message"></i>我的消息</a>
      </li>
      <li>
        <a href="javascript:;"><i class="icon-level"></i>我的等级</a>
      </li>
      <li>
        <a href="javascript:;"><i class="icon-vip"></i>VIP会员</a>
      </li>
    </ul>
    <ul>
      <li>
        <a href="javascript:;"><i class="icon-settings"></i>个人设置</a>
      </li>
      <li>
        <a href="javascript:;"><i class="icon-validation"></i>实名认证</a>
      </li>
    </ul>
    <ul>
      <li @click="logout">
        <a href="javascript:;"><i class="icon-logout"></i>退出登录</a>
      </li>
    </ul>
  </div>
</div>
</template>

<script>
export default {
  methods: {
    // 退出登录
    logout() {
      this.isLogin = false
      this.isShowUserBox = false
      this.$store.dispatch('user/logout').then(() => {
        location.reload()
      })
    },
  }
}
</script>
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表