Vue3.0 + typescript 高仿网易云音乐 WebApp

前言

Vue3.0 的正式发布,让我心动不已,于是尝试用 vue3 实现一个完整的项目,整个项目全部使用了 composition api, 相比较 options 方式,逻辑更加清晰,使用也更加灵活。

先贴上地址,喜欢的可以 star 一波
源码地址 : https://github.com/bianyingchun/vue3-music
这里还有 react hooks 版本

项目介绍

本项目是使用 vue-cli 4.5 搭建的项目, vue-router 以及 vuex 均为 4.0 版本,以支持 composition api 的使用。使用 typescript 编写完成。该项目的所有数据接口来自大佬 binaryify 的网易云音乐 NodeJS 版 API

本项目将会持续维护开发中,目前实现了以下模块:

滚动加载更多

这个项目中几乎所有的列表页都使用到了滚动加载更多的逻辑,于是我封装了一个公共 hooks,以供复用

import _ from 'lodash'
import { Ref, onMounted, onUnmounted } from 'vue'
export function useLoadMore(
  refreshElm: Ref<null htmlelement>,
  loadData: () =&gt; any
) {
  let element: HTMLElement
  const _loadMore = _.throttle(() =&gt; {
    const containerHeight = element.clientHeight
    const scrollTop = element.scrollTop
    const scrollHeight = element.scrollHeight
    if (containerHeight + scrollTop + 20 &gt;= scrollHeight) {
      loadData()
    }
  }, 200)

  onMounted(() =&gt; {
    element = refreshElm.value as HTMLElement
    element.addEventListener('scroll', _loadMore)
  })
  onUnmounted(() =&gt; {
    element.removeEventListener('scroll', _loadMore)
  })
  return {}
}

主题切换

通过 css 变量和 sass mixin 的结合,实现了明暗主题的切换,当然你可以定义不同的主题。

@mixin css-var(
  $text,
  $text-reversal,
  $text-nav,
  $header-bg,
  $text-secondary,
  $primary,
  $link-bg,
  $body-bg,
  $module-bg,
  $border
) {
  #{--body-bg}: $body-bg;
  #{--text}: $text;
  #{--text-reversal}: $text-reversal;
  #{--text-nav}: $text-nav;
  #{--text-secondary}: $text-secondary;
  #{--primary}: $primary;
  #{--header-bg}: $header-bg;
  #{--link-bg}: $link-bg;
  #{--module-bg}: $module-bg;
  #{--border}: $border;
  #{--bg-reversal-opacity-9}: rgba($text, 0.9);
}
.light {
  @include css-var(
    $text: #333,
    $text-reversal: #fff,
    $text-nav: #5d5d5d,
    $text-secondary: #888,
    $primary: #d23931,
    //active
      $link-bg: #f34d3f,
    $header-bg: #da3d34,
    $body-bg: #f0f0f0,
    $module-bg: #fff,
    $border: #ccc
  );
}
.dark {
  @include css-var(
    $text: #fff,
    $text-reversal: #333,
    $text-nav: #d7d7d7,
    $text-secondary: #ccc,
    $primary: #f1423d,
    $link-bg: #414141,
    $header-bg: #414141,
    $body-bg: #262626,
    $module-bg: #2c2c2c,
    $border: #4e4a4a
  );
}
dark-theme.png
light-theme.png

登录

通过登录以体验更丰富的功能,如发表评论,点赞,关注用户,歌手,收藏歌曲,创建歌单等。在未登录状态下触及到这些功能时会自动显示登录界面,登录成功后会返回到当前页面,刷新当前用户状态。
为了共享用户状态,我将用户状态保存到 store 中,并抽离出公共逻辑 useAuth.ts

export function useAuth(store: Store<globalstate>) {
  const account = computed(() =&gt; store.state.auth.account);
  const profile = computed(() =&gt; store.state.auth.profile);
  const toggleLoginBox = (val: boolean) =&gt;
    store.commit(`auth/${SET_LOGIN_VISIBLE}`, val);
  return {
    account,
    profile,
    toggleLoginBox,
  };
}
login.png

个人中心

在用户登录成功,个人中心会显示用户信息,以及个人创建和收藏的歌单, 用户可以创建、删除、编辑歌单、取消收藏的歌单。


create-playlist.png

播放器

播放器算是此项目中最核心的模块了。实现了一个播放器应该有的基本功能。

  • 列表播放
  • 插入歌曲
  • 切换歌曲
  • 从播放列表中删除歌曲
  • 清空播放列表
  • 切换播放模式
  • 歌词同步
  • 调整播放进度
  • 添加到歌单
    由于在多个页面中使用到了播放歌曲的功能,我抽离出了公共逻辑 usePlayMusic.ts
export function usePlayMusic(store: Store<globalstate>) {
  const playing = computed(() =&gt; store.state.player.playing);
  const currentSong = computed(() =&gt; store.getters["player/currentSong"]);
  function selectPlay(list: Track[], index: number) {
    store.dispatch("player/selectPlay", {
      list,
      index,
    });
  }
  function insertSong(song: Track) {
    store.dispatch("player/insertSong", song);
  }
  return {
    currentSong,
    insertSong,
    selectPlay,
    playing,
  };
}

在实现收藏歌曲到歌单时,考虑到这个功能多次被使用,且无需反复创建,故设计为公共单例组件,并封装成 hooks,方便调用。

import { ref, createApp, App, h } from "vue";
import Comp from "@/components/achive/fav-to-mix.vue";
import { useMylist } from "./usePlaylist";
import { useAuth } from "./useAuth";
import { Track, Playlist } from "@/types";
import store from "@/store";

let favToMixVm: App | null = null;

const show = ref(false);
function hide() {
  show.value = false;
}

export function favTrackToMix(track: Track) {
  const { account, toggleLoginBox } = useAuth(store);
  if (!account.value) return toggleLoginBox(true);
  show.value = true;
  const { likelist, createdList, addTrack } = useMylist(store);
  const list = likelist.value
    ? [likelist.value, ...createdList.value]
    : createdList.value;
  async function onSelect(mix: Playlist) {
    show.value = false;
    await addTrack(mix.id, track);
  }
  if (!favToMixVm) {
    favToMixVm = createApp({
      setup() {
        return () =&gt;
          h(Comp, {
            show: show.value,
            list,
            hide,
            onSelect,
          });
      },
    });
    const el = document.createElement("div");
    document.body.appendChild(el);
    favToMixVm.mount(el);
  }
}
player.png

lyric.png

play-list.png

fav-to-mix.png

搜索

搜索页面中,实现功能的功能有

  • 搜索歌手、歌单、歌曲、用户
  • 搜索提示(函数防抖)
  • 热门搜索、历史搜索


    search.png

    search-result.png

歌单详情

在歌单页面中,可以收藏和取消收藏其他用户的歌单,点击歌曲可以播放整个歌单歌曲列表,如果是自己的歌单可以将歌曲从歌单中删除。补充:排行榜的详情页以及每日推荐其实也是歌单,只是歌曲样式稍有不同。


playlist.png

评论

歌曲和歌单的评论是同一个组件,只是传入的参数不同。
实现的功能有

  • 评论列表
  • 发布评论
  • 回复评论
  • 查看回复列表
  • 点赞评论
  • 删除自己发布的评论
comment.png

reply-list.png

用户主页

user.png

关注/粉丝列表

follows.png

歌手主页

歌手主页和用户主页使用的是同一个布局组件,通过传入 slot 插槽, 呈现不同的主体。


artist.png

歌手分类

artist-list.png

歌单广场

playlist-square.png

排行榜

toplist.png

项目运行

  1. 克隆到本地
    git clone https://github.com/bianyingchun/react-music.git
  2. 安装依赖
    yarn install
  3. 运行
    yarn serve
  4. 打包
    yarn build
    </globalstate></globalstate></null>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容