仿写网易云音乐Demo项目(Vue3+Vite+Vuex+Vue-Router4.0)

前言

学习了一段时间vue3的基础知识学习,百学不如一练,想着还是做出一个实际的demo项目(适配为移动端),来实践巩固自己所学的知识点。

项目简介

1.前端技术栈:

  • vue3.0全家桶(组合式API)
  • vuexvuex-persistedstate(持久化数据存储)
  • 开发与构建工具Vite(极速的开发服务器启动)
  • 坚守前端MVVM的设计理念,遵循组件化、模块化的编程思想

2.后端:

  • GitHub上开源的网易云音乐NodeJS版api接口NeteaseCloudMusicApi

成果

仿写网易云音乐Demo项目(Vue3+Vite+Vuex+Vue-Router4.0)_第1张图片

项目结构

├─ src
    ├─api                   // 网路请求代码
    ├─assets                // 字体配置及全局样式
    ├─style                 // 公共样式
    ├─components            // 可复用的 UI 组件
    ├─utils                 // 工具类函数和相关配置
    ├─views                 // 页面
    ├─router                // 路由配置文件
    └─store                 // redux 相关文件
      App.jsx               // 根组件
      main.jsx              // 入口文件

项目内容

  • Vue-Router4.0。
    router/index.js代码如下:
import {
    createRouter,
    createWebHistory,
    RouteRecordRaw
} from 'vue-router'


const Login = () => import('../views/Login/Login.vue')
const Phone = () => import('../views/Login/Phone.vue')
const Vcode = () => import('../views/Login/VerCode.vue')
const Home = () => import('../views/Home.vue')
const View = () => import('../views/View.vue')
const Comment = () => import('../views/Comment.vue')

//类型校验,规范化typescript,增加路由对象类型限制,好处:允许在基础路由里增加开发自定义属性
const routes: RouteRecordRaw[] = [
    {
        path: '/',
        redirect: '/login',
    },
    {
        path: '/login',
        name: 'Login',
        meta: {
            type: 'Login'
        },
        component: Login,
    },
    {
        path: '/login/phone',
        name: 'Phone',
        meta: {
            type: 'Login'
        },
        component: Phone,
    },
    {
        path: '/login/phone/vcode',
        name: 'Vcode',
        component: Vcode,
    }
    .......
  • 路由跳转实现
    使用router-Link点击导航、编程式导航,代码片段如下:
      
       ......
      
  setup(props) {
    const router = useRouter();
    let Nickname = ref("");
    let avatarUrl = ref("");

    function jumpComment() {
      router.push({
        path: "comment",
        query: { id: props.listID },
      });
    }
    .....
   }
  • 组件通信
    组件中通信,兄弟间组件可以使用:
    1.事件总线EventBusvue3中,取消了全局事件总线,如果想要使用全局事件总线,那么就需要使用一个插件mitt
安装mitt: npm i mitt -s

在utils下创建bus.js文件:
//注册event bus事件
import mitt from 'mitt'
const emitter = mitt()

export default emitter

使用代码片段:
  //emitter.emit使用
  setup(props) {
    const store = useStore();
    /*     let musicObj = ref(""); */
    let isListen = ref(Boolean);
    let indexNumber = ref("");

    function handleIcon(index) {
      isListen = !isListen;
      indexNumber.value = index;
      //通过点击传递指定列表数据
      emitter.emit("event", store.state.musicObj[index]);
    }
     ......
   }
   
   //emitter.on的使用
  setup() {
     .......
    //获取事件总线传递过来的数据
    emitter.on("event", (e) => {
      .......
    });

    //事件总线的卸载,否则会存粗之前的调用
    onBeforeUnmount(() => {
      emitter.off("event");
    });
   .......
  },

2.消息发布与订阅PubSub

安装第三方库: npm i pubsub-js

消息订阅:
pubsub.subscribe('event',(funname, data) => { 
// funname为消息名称 data为传递的参数 
console.log(funname, data) })

消息发布:
pubsub.publish('event', 'Tom') //第一个参数为消息名称,第二个参数为发布的数据

具体代码点这里

当写到底部的播放音乐的组件的时候,不同的歌单列表下进行点击会影响数据的改变,发现如果单一的传递数据会过于混杂,这时候就需要用到vuex来管理,并能够实现多组件共享存储数据(集中式存储管理应用)
效果如图:
仿写网易云音乐Demo项目(Vue3+Vite+Vuex+Vue-Router4.0)_第2张图片仿写网易云音乐Demo项目(Vue3+Vite+Vuex+Vue-Router4.0)_第3张图片仿写网易云音乐Demo项目(Vue3+Vite+Vuex+Vue-Router4.0)_第4张图片

安装:npm i vuex@next --save

store/inex.js代码如下:
import { createStore } from 'vuex'
import { getPlayList } from '../api/index'
......


const store = createStore({
    state() {
        return {
            //整个音乐列表的数据存储
            musicObj: {},
            ......
        }
    },
    mutations:
    {
        //保存发现好歌单信息
        saveMusic(state, obj) {
            state.musicObj = obj
        },
        //通过acion异步获取底部歌单播放详情
        getMusic(state, obj) {
            state.bottomMusic = obj
        },
       ......
    },
   ......
})

export default store


代码片段:
//获取state
import { useStore } from "vuex";
import emitter from "../utils/bus";
import { getPlayList } from "../api/index";

export default {
  setup() {
    const store = useStore();
    let musicPic = ref("");
    let musicName = ref("");
    let musicAhtuor = ref("");
    let isActive = ref(store.state.isAcitve);
    ......
   }

//mutations提交数据
......
import { useStore } from "vuex";
import ListBotton from "../components/listBotton.vue";

/* 消息的发布与订阅 */
import pubsub from "pubsub-js";
export default {
  components: { listTop, listMiddle, ListBotton },
  setup() {
    const route = useRoute();
    const store = useStore();
    let state = reactive({ playData: {} });
    let listID = ref();
    let isShow=ref(false)

    onBeforeMount(async () => {
      isShow.value=true
      let res = await getPlayList(route.query.id);
      state.playData = reactive(res.data.playlist);
      store.commit("saveMusic", state.playData.tracks);
      ......
    });
   }
   
 //actions异步的使用
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    store.dispatch("getMusicList");
  },
};

具体代码点这里

主要页面编写

为了方便参考网易云的布局结构,参考的是ipad的网易云HD,所以页面有一些icon会看起来有点拥挤。

Phone页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oH8ki9Nf-1662524328940)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6fc865f35884612bb6483bb93f5f6be~tplv-k3u1fbpfcp-watermark.image?)]
Phone页面是进行手机号的验证,并向手机号码发送实时验证码短信。
通过点击文件框获取焦点的时候灵活激活数字键盘,watch来监听手机号码的长度来判断是否可以点击“下一步”的按钮,并发起请求发送验证码。

  • Login/Phone.vue代码片段:
   watch(number, (newNumber) => {
      if (newNumber.length !== 0) {
        isActive.value = false;
      }
    });
   ......
    async function handleLogin(value) {
      if (!isPhoneNumber(number.value) && value == false) {
        return ElMessage.error("手机号不合格!");
      } else if (value == false) {
        let phoneInfo = number.value;
        let res = await postNumber(phoneInfo);
        sessionStorage.setItem("phone", phoneInfo);
        console.log(res);

        router.push({
          path: "/login/phone/vcode",
          name: "Vcode",
        });
      }
    }

View页面和Comment页面

View页面是歌单列表详情页、Comment页面是评论列表详情页,都主要以处理数据并进行渲染为主,通过后端得到的数据进行分析,并用v-for进行循环排列歌单列表。

View页面效果:
仿写网易云音乐Demo项目(Vue3+Vite+Vuex+Vue-Router4.0)_第5张图片

通过点击对应的歌曲,拿到相应的index存储起来,icon是通过判断当前点击的index和歌曲的index是否一致,一致则显示icon。

  • viewList/listMiddle.vue代码片段:
     ......
    

{{ index + 1 }}

...... function handleIcon(index) { isListen = !isListen; indexNumber.value = index; //通过点击传递指定列表数据 emitter.emit("event", store.state.musicObj[index]); } ......

Comment页面效果:

通过后端数据返回来的评论时间的时间戳,并获取当前的时间戳,如果年份相同则在“精彩评论”中出现年份,否则不出现。
通过点击点赞事件来将点赞次数增加并且复制一份并播放动画,更改后端返回的某个数据或者增加属性,最后将刷新的数据交出去并进行页面渲染。

  • views/Comment.vue
 /* 计算时间戳 */
 ......
    function hotTime(params) {
      let date = new Date(params);
      let newDate = new Date();
      let newY = newDate.getFullYear();

      let Y = date.getFullYear();
      let M =
        date.getMonth() + 1 < 10
          ? "0" + (date.getMonth() + 1)
          : date.getMonth() + 1;
      let D = date.getDay() > 10 ? "0" + date.getDate() : date.getDate();
      return newY === Y ? M + "月" + D + "日" : Y + "年" + M + "月" + D + "日";
    }

    const NewComment = "new";
    const HotComment = "hot";
    /* 判断是否第一次点击 */
    function handleLike(index, value) {
      let item =
        value === NewComment ? newComment.news[index] : hotComment.hot[index];

      item.liked = !item.liked;
      item.liked === true ? item.likedCount++ : item.likedCount--;
      value === NewComment
        ? (newComment = reactive({ news: newComment.news }))
        : (hotComment = reactive({ hot: hotComment.hot }));
    }
  ......

具体代码点这里

优化

  • 路由懒加载:把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。
  • Suspense:加载异步组件时,进行 Loading 的处理
  
    
    
  
  • 采用了“骨架屏”的方式去提升用户体验

项目遇到的坑

刷新页面后vuex的数据丢失了
在使用vuex进行存储列表歌曲数据时,在每一次页面刷新后所有的数据都丢失了,才知道vuex不能够持久化存储数据,一开始在尝试浏览器storage来实施本地存储,通过后来的学习,发现可以安装vuex-persistedstate插件来进行持久化本地数据存储。

安装:npm install vuex-persistedstate

//引入
import { createStore } from 'vuex'
import persistedState from 'vuex-persistedstate'
//
const store = createStore({
     ......
    plugins: [persistedState(/* { storage: window.sessionStorage } */)]
})

export default store

使用事件总线EventBus和消息发布与订阅PubSub的时机
EventBus: 要先$on来接收频道信号,后$emit 发送频道信号(就是要先知道谁接收,才能发送)
PubSub: 先subscribe,后publish(就是要先知道谁订阅,才能发布)
两者都必须在得到数据的页面卸载之前把EventBusPubSub事件给注销,因此这两者都具有缓存性(也就说在下一次的接收会收到本次和上一次发送的数据)

总结

这个Demo是自己手把手撸出来的,算是比较粗糙,比如说代码规划还有代码风格可能不太好;功能也不是很全,还有比如Home页面中可以多个类似的组件可以做利用插槽进行代码优化,如果后面有时间的话可能会一点点去完善,毕竟学习不会止步。

源码

项目源码地址:GitHub,欢迎star

你可能感兴趣的:(web前端,vue.js,vue.js,前端,javascript)