├── .browserslistrc ├── public └── favicon.ico ├── src ├── shims-vue.d.ts ├── assets │ ├── images │ │ ├── logo.png │ │ ├── singer-bg.png │ │ ├── login-avatar.jpg │ │ ├── music-load.jpg │ │ ├── music-error.svg │ │ └── music-ico.svg │ └── less │ │ └── reset.less ├── doc │ └── images │ │ ├── comment.gif │ │ ├── player.gif │ │ ├── search.gif │ │ ├── singer.gif │ │ └── createSong.gif ├── views │ ├── player │ │ ├── image │ │ │ ├── random.png │ │ │ ├── listloop.png │ │ │ ├── singleloop.png │ │ │ └── musiclist.svg │ │ ├── readme.ts │ │ └── childComp │ │ │ └── mini-player.vue │ ├── my │ │ ├── childRouter │ │ │ ├── my_radio.vue │ │ │ ├── my_star.vue │ │ │ ├── watch_new_music.vue │ │ │ └── play_history.vue │ │ ├── index.vue │ │ └── childComp │ │ │ ├── my_music.vue │ │ │ └── head.vue │ ├── searchResult │ │ └── childComp │ │ │ ├── radio.vue │ │ │ ├── album.vue │ │ │ ├── topbar.vue │ │ │ ├── video.vue │ │ │ ├── singer.vue │ │ │ ├── song-sheet.vue │ │ │ ├── user.vue │ │ │ └── single.vue │ ├── video │ │ └── index.vue │ ├── find │ │ ├── image │ │ │ ├── paihang.svg │ │ │ ├── music.svg │ │ │ ├── mv.svg │ │ │ ├── diantai.svg │ │ │ └── jiemu.svg │ │ ├── childComp │ │ │ ├── recommend.vue │ │ │ ├── find-swiper.vue │ │ │ ├── new-album.vue │ │ │ └── recmend-songlist.vue │ │ └── index.vue │ ├── singer │ │ └── childComp │ │ │ └── topbar.vue │ ├── singerDetail │ │ └── childComp │ │ │ ├── mv.vue │ │ │ ├── album.vue │ │ │ ├── home.vue │ │ │ └── head.vue │ ├── search │ │ ├── index.vue │ │ └── childComp │ │ │ ├── search-history.vue │ │ │ └── hot-search-list.vue │ ├── songManage │ │ └── updateSong │ │ │ ├── image │ │ │ ├── emotion.svg │ │ │ ├── style.svg │ │ │ ├── theme.svg │ │ │ ├── language.svg │ │ │ └── scene.svg │ │ │ ├── edit-song-desc.vue │ │ │ └── edit-song-name.vue │ ├── test.vue │ ├── login │ │ ├── index.vue │ │ ├── phone.vue │ │ └── register.vue │ ├── comment │ │ └── childComp │ │ │ └── head.vue │ └── musicList │ │ ├── index.vue │ │ └── childComp │ │ ├── songlist.vue │ │ └── head.vue ├── components │ ├── common │ │ ├── loading │ │ │ ├── loading.gif │ │ │ └── loading.vue │ │ ├── toast │ │ │ ├── index.js │ │ │ ├── toast.vue │ │ │ ├── index.d.ts │ │ │ └── main.js │ │ ├── swiper │ │ │ └── SwiperItem.vue │ │ ├── navbar │ │ │ └── navbar.vue │ │ ├── message │ │ │ └── message.vue │ │ ├── kl-dialog │ │ │ └── kl-dialog.vue │ │ ├── gridview │ │ │ └── grid-view.vue │ │ ├── bottomPopup │ │ │ └── bottom-popup.vue │ │ ├── kl-confirm │ │ │ └── kl-confirm.vue │ │ ├── scroll │ │ │ └── scroll.vue │ │ └── scrollNavBar │ │ │ └── scroll-nav-bar.vue │ └── content │ │ ├── mv-list │ │ ├── mv-list.vue │ │ └── mv-list-items.vue │ │ ├── album-list │ │ ├── album-list.vue │ │ └── album-list-items.vue │ │ ├── single-list │ │ ├── single-list.vue │ │ └── single-list-items.vue │ │ ├── progress-circle │ │ └── progress-circle.vue │ │ ├── tab-bar │ │ └── tab-bar.vue │ │ ├── head-menu │ │ └── head-menu.vue │ │ ├── progress-bar │ │ └── progress-bar.vue │ │ ├── songlist-operation │ │ └── index.vue │ │ └── create-song-dialog │ │ └── index.vue ├── shims-tsx.d.ts ├── utils │ ├── debounce.ts │ ├── dom.ts │ ├── html.ts │ ├── formatDate.ts │ ├── cookie.ts │ ├── longpress.ts │ └── lyric-parser.js ├── store │ ├── interface.ts │ ├── getters.ts │ ├── index.ts │ └── actions.ts ├── service │ ├── player.ts │ ├── user.ts │ ├── service.ts │ ├── search.ts │ ├── find.ts │ ├── musiclist.ts │ ├── singer.ts │ ├── login.ts │ ├── rankinglist.ts │ └── songsheet.ts ├── router │ ├── musiclist.ts │ ├── login.ts │ ├── my.ts │ ├── songManage.ts │ └── index.ts ├── conf │ ├── playlist.ts │ └── songsInfo.ts ├── main.ts ├── vue-content-loader.d.ts └── App.vue ├── babel.config.js ├── .gitignore ├── postcss.config.js ├── tsconfig.json ├── package.json ├── vue.config.js └── README.en.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/doc/images/comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/doc/images/comment.gif -------------------------------------------------------------------------------- /src/doc/images/player.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/doc/images/player.gif -------------------------------------------------------------------------------- /src/doc/images/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/doc/images/search.gif -------------------------------------------------------------------------------- /src/doc/images/singer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/doc/images/singer.gif -------------------------------------------------------------------------------- /src/doc/images/createSong.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/doc/images/createSong.gif -------------------------------------------------------------------------------- /src/assets/images/singer-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/assets/images/singer-bg.png -------------------------------------------------------------------------------- /src/assets/images/login-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/assets/images/login-avatar.jpg -------------------------------------------------------------------------------- /src/assets/images/music-load.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/assets/images/music-load.jpg -------------------------------------------------------------------------------- /src/views/player/image/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/views/player/image/random.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ], 5 | plugins: ["lodash"] 6 | } 7 | -------------------------------------------------------------------------------- /src/views/player/image/listloop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/views/player/image/listloop.png -------------------------------------------------------------------------------- /src/views/player/image/singleloop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/views/player/image/singleloop.png -------------------------------------------------------------------------------- /src/components/common/loading/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lang1427/vue-typescript-music/HEAD/src/components/common/loading/loading.gif -------------------------------------------------------------------------------- /src/components/common/toast/index.js: -------------------------------------------------------------------------------- 1 | import Toast from './main' 2 | 3 | export default{ 4 | install: function(Vue,opts = {}){ 5 | 6 | Vue.prototype.$toast = Toast 7 | 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/assets/images/music-error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/views/my/childRouter/my_radio.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/views/my/childRouter/my_star.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/common/swiper/SwiperItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param funcName 需要进行防抖的函数名 4 | * @param delay 防抖所需时间 5 | */ 6 | export function debounce(funcName: any, delay: number = 50) { 7 | let timer: any = null 8 | 9 | return function(this: any) { 10 | if (timer) clearTimeout(timer) 11 | timer = window.setTimeout(() => { 12 | funcName.apply(this) 13 | }, delay) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/views/my/childRouter/watch_new_music.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/views/searchResult/childComp/radio.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /src/utils/dom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取 / 设置 自定义属性 3 | * @param el 元素 4 | * @param name 属性名 5 | * @param val 属性值 6 | */ 7 | export function getData(el: HTMLElement, name: string, val?: string) { 8 | const prefix = 'data-' 9 | name = prefix + name 10 | if (val) { 11 | // 如果有val就设置自定义属性 12 | return el.setAttribute(name, val) 13 | } else { 14 | // 获取自定义属性 15 | return el.getAttribute(name) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/store/interface.ts: -------------------------------------------------------------------------------- 1 | export interface IState { 2 | loadingShow: boolean 3 | searchKeyWrold: string | null 4 | searchHistory: string | never[] | null 5 | loginAccount: string | null 6 | account: object 7 | playList: object[] | null 8 | currentPlayIndex: number 9 | playMode: EPlayMode 10 | playHistory: object[] | null 11 | } 12 | // 定义播放的类型 13 | export enum EPlayMode { 14 | listLoop, // 0列表循环 15 | singleLoop, // 1单曲循环 16 | random // 2随机播放 17 | } -------------------------------------------------------------------------------- /src/views/video/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/service/player.ts: -------------------------------------------------------------------------------- 1 | import { service } from './service' 2 | 3 | // 音乐是否可用 4 | export function isCanMusic(id: number) { 5 | return service({ 6 | url: '/check/music', 7 | params: { 8 | id 9 | } 10 | }) 11 | } 12 | 13 | // 获取音乐url 14 | export function musicUrl(id: number) { 15 | return service({ 16 | url: '/song/url', 17 | params: { 18 | id 19 | } 20 | }) 21 | } 22 | 23 | // 获取音乐歌词 24 | export function musicLyric(id: number) { 25 | return service({ 26 | url: 'lyric', 27 | params: { 28 | id 29 | } 30 | }) 31 | } -------------------------------------------------------------------------------- /src/router/musiclist.ts: -------------------------------------------------------------------------------- 1 | 2 | const album = () => import(/*webpackChunkName:'album'*/'views/musicList/index.vue') 3 | const songsheet = () => import(/*webpackChunkName:'songsheet'*/'views/musicList/index.vue') 4 | const topList = () => import(/*webpackChunkName:'topList'*/'views/musicList/index.vue') // 排行榜 5 | 6 | export default [ 7 | { 8 | path: '/album/:id', 9 | name: 'album', 10 | component: album 11 | }, 12 | { 13 | path: '/songsheet/:id', 14 | name: 'songsheet', 15 | component: songsheet 16 | }, { 17 | path: '/toplist/:id', 18 | name: 'toplist', 19 | component: topList 20 | } 21 | ] -------------------------------------------------------------------------------- /src/views/find/image/paihang.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/common/loading/loading.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 33 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | "postcss-px-to-viewport": { 5 | viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度,Iphone6的一般是375 (xx/375*100vw) 6 | viewportHeight: 667, // 视窗的高度,Iphone6的一般是667 7 | unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除) 8 | viewportUnit: "vw", // 指定需要转换成的视窗单位,建议使用vw 9 | selectorBlackList: ['.ignore', '.hairlines'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名 10 | minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值 11 | mediaQuery: false, // 允许在媒体查询中转换`px` 12 | exclude: [/mini-player/] // 底部迷你版播放器不转化 忽略文件 不转化成视窗单位 (必须是正则) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/views/player/image/musiclist.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/content/mv-list/mv-list.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | 30 | -------------------------------------------------------------------------------- /src/service/user.ts: -------------------------------------------------------------------------------- 1 | import { service } from './service' 2 | 3 | // 登陆状态 4 | export function loginStatus() { 5 | return service({ 6 | url: '/login/status' 7 | }) 8 | } 9 | export class UserBaseInfo { 10 | userId!: number 11 | nickname!: string 12 | avatarUrl!: string 13 | constructor(profile: IProfile) { 14 | this.userId = profile.userId 15 | this.nickname = profile.nickname 16 | this.avatarUrl = profile.avatarUrl 17 | } 18 | } 19 | export interface IProfile { 20 | userId: number, 21 | nickname: string, 22 | avatarUrl: string 23 | } 24 | 25 | // 获取用户详情 26 | export function userDetail(id: number) { 27 | return service({ 28 | url: '/user/detail', 29 | params: { 30 | uid: id 31 | } 32 | }) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/components/content/album-list/album-list.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /src/service/service.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import $store from '@/store/index' 4 | 5 | const baseURL = 6 | process.env.NODE_ENV === 'production' ? '' : 'http://localhost:3000' 7 | 8 | export function service(options: object): any { 9 | return new Promise((resolve, reject) => { 10 | const instance = axios.create({ 11 | baseURL, 12 | withCredentials: true 13 | }) 14 | instance.interceptors.request.use(req => { 15 | $store.state.loadingShow = true 16 | return req 17 | }) 18 | instance.interceptors.response.use(res => { 19 | $store.state.loadingShow = false 20 | return res.data 21 | }) 22 | 23 | instance(options) 24 | .then(res => { 25 | resolve(res) 26 | }) 27 | .catch(err => { 28 | reject(err) 29 | }) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/html.ts: -------------------------------------------------------------------------------- 1 | // html 编码(转义) 2 | export function htmlEncode(str: string) { 3 | var temp = ""; 4 | if (str.length == 0) return ""; 5 | temp = str.replace(/&/g, "&"); 6 | temp = temp.replace(//g, ">"); 8 | temp = temp.replace(/\s/g, " "); 9 | temp = temp.replace(/\'/g, "'"); 10 | temp = temp.replace(/\"/g, """); 11 | return temp; 12 | } 13 | 14 | // html编码(反转义) 15 | export function htmlDecode(str: string) { 16 | var temp = ""; 17 | if (str.length == 0) return ""; 18 | temp = str.replace(/&/g, "&"); 19 | temp = temp.replace(/</g, "<"); 20 | temp = temp.replace(/>/g, ">"); 21 | temp = temp.replace(/ /g, " "); 22 | temp = temp.replace(/'/g, "\'"); 23 | temp = temp.replace(/"/g, "\""); 24 | return temp; 25 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } -------------------------------------------------------------------------------- /src/utils/formatDate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * date: new Date(date*1000) 3 | * fmt: yyyy-MM-dd hh:mm:ss */ 4 | export function formatDate(date: Date, fmt: string) { 5 | if (/(y+)/.test(fmt)) { 6 | fmt = fmt.replace( 7 | RegExp.$1, 8 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 9 | ) 10 | } 11 | let o: any = { 12 | 'M+': date.getMonth() + 1, 13 | 'd+': date.getDate(), 14 | 'h+': date.getHours(), 15 | 'm+': date.getMinutes(), 16 | 's+': date.getSeconds() 17 | } 18 | for (let k in o) { 19 | if (new RegExp(`(${k})`).test(fmt)) { 20 | let str = o[k] + '' 21 | fmt = fmt.replace( 22 | RegExp.$1, 23 | RegExp.$1.length === 1 ? str : padLeftZero(str) 24 | ) 25 | } 26 | } 27 | return fmt 28 | } 29 | 30 | function padLeftZero(str: string) { 31 | return ('00' + str).substr(str.length) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/common/navbar/navbar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | 22 | 42 | -------------------------------------------------------------------------------- /src/views/singer/childComp/topbar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /src/conf/playlist.ts: -------------------------------------------------------------------------------- 1 | interface IPlaylist { 2 | id: number 3 | name: string 4 | imgURL: string 5 | } 6 | export interface ISongs { 7 | songsId: number 8 | songsName: string 9 | imgUrl: string 10 | } 11 | // 播放列表容器的类 12 | export class PlayList implements IPlaylist { 13 | id: number 14 | name: string 15 | imgURL: string 16 | constructor(songs: ISongs) { 17 | this.id = songs.songsId 18 | this.name = songs.songsName 19 | this.imgURL = songs.imgUrl 20 | } 21 | } 22 | 23 | // 当有播放列表时,需要设置一个marginBottom,没有播放列表时去除marginBottom 24 | export function playerSetMarginBottom() { 25 | (document.querySelector('#app')).children[1] ? (document.querySelector('#app')).children[1].style.marginBottom = '50px' : false 26 | } 27 | export function playerRemoveMarginBottom() { 28 | (document.querySelector('#app')).children[1] ? (document.querySelector('#app')).children[1].style.marginBottom = '0px' : false 29 | } -------------------------------------------------------------------------------- /src/utils/cookie.ts: -------------------------------------------------------------------------------- 1 | /* 封装cookie */ 2 | 3 | // 获取cookie 4 | export function getCookie(name:string) { 5 | var arr:any; 6 | var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)') 7 | 8 | if (document.cookie.match(reg)) { 9 | arr = document.cookie.match(reg); 10 | return (arr[2]) 11 | } else { 12 | return null 13 | } 14 | } 15 | 16 | // 设置cookie,增加到vue实例方便全局调用 17 | export function setCookie(cName:string, value:string, expiredays:number) { 18 | var exdate:any = new Date() 19 | exdate.setDate(exdate.getDate() + expiredays) 20 | document.cookie = cName + '=' + escape(value) + ((expiredays == null) ? '' : ';expires=' + exdate.toGMTString()) 21 | } 22 | 23 | // 删除cookie 24 | export function delCookie(name:string) { 25 | var exp:any = new Date() 26 | exp.setTime(exp.getTime() - 1) 27 | var cval = getCookie(name) 28 | if (cval != null) { 29 | document.cookie = name + '=' + cval + ';expires=' + exp.toGMTString() 30 | } 31 | } -------------------------------------------------------------------------------- /src/components/content/single-list/single-list.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | -------------------------------------------------------------------------------- /src/service/search.ts: -------------------------------------------------------------------------------- 1 | import { service } from './service' 2 | 3 | /**获取热搜列表数据 */ 4 | export function hotSearch() { 5 | return service({ 6 | url: '/search/hot/detail' 7 | }) 8 | } 9 | 10 | /** 输入框搜索建议 */ 11 | export function searchSuggest(keywords: string, type: string = 'mobile') { 12 | return service({ 13 | url: '/search/suggest', 14 | params: { 15 | keywords, 16 | type 17 | } 18 | }) 19 | } 20 | 21 | /**搜索 (翻页的坑,没有正确理解后台APi接口) 22 | * keywords:搜索关键字 23 | * type:类型:(1.单曲 10.专辑 100.歌手 1000.歌单 1002.用户 1004.MV 1006.歌词 1009.电台 1014.视频 1018.综合) 24 | * limit:返回数量(默认30) 25 | * offset:页数(默认0) 26 | * */ 27 | export function search( 28 | keywords: string, 29 | type: number = 1, 30 | limit: number = 30, 31 | offset: number = 0 // offset: 翻页 offset*limit 32 | ) { 33 | return service({ 34 | url: '/search', 35 | params: { 36 | keywords, 37 | limit, 38 | offset: offset * limit, 39 | type 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/views/singerDetail/childComp/mv.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/common/message/message.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | import 'font-awesome/css/font-awesome.css' 7 | 8 | Vue.config.productionTip = false 9 | 10 | import FastClick from 'fastclick' 11 | ;(FastClick).attach(document.body) 12 | 13 | import LazyLoad from 'vue-lazyload' 14 | Vue.use(LazyLoad, { 15 | loading: require('./assets/images/music-load.jpg'), 16 | error: require('./assets/images/music-error.svg') 17 | }) 18 | 19 | import Toast from './components/common/toast/index.js' 20 | Vue.use(Toast) 21 | 22 | Vue.prototype.$bus = new Vue() 23 | 24 | Vue.filter('finalPlayCount', (playCount: number): number | string => { 25 | if (playCount < 100000) { 26 | return playCount 27 | } else if (playCount >= 100000 && playCount < 100000000) { 28 | return (playCount / 10000).toFixed(0) + '万' 29 | } 30 | return (playCount / 100000000).toFixed(0) + '亿' 31 | }) 32 | 33 | new Vue({ 34 | router, 35 | store, 36 | render: h => h(App) 37 | }).$mount('#app') 38 | -------------------------------------------------------------------------------- /src/views/singerDetail/childComp/album.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 40 | 41 | 51 | -------------------------------------------------------------------------------- /src/views/search/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | 40 | -------------------------------------------------------------------------------- /src/service/find.ts: -------------------------------------------------------------------------------- 1 | import { service } from './service' 2 | 3 | /**获取轮播图数据 4 | * 0:pc端 5 | * 1.Android端 6 | * 2.iphone 7 | * 3.ipad 8 | * */ 9 | export function getBanner(type: number = 0) { 10 | return service({ 11 | url: '/banner', 12 | params: { 13 | type: type 14 | } 15 | }) 16 | } 17 | export class bannerData { 18 | private readonly bannerId: string 19 | private readonly pic: string 20 | private readonly titleColor: string 21 | private readonly typeTitle: string 22 | private readonly url: string 23 | constructor(bannerlist: any) { 24 | this.bannerId = bannerlist.bannerId 25 | this.pic = bannerlist.pic 26 | this.titleColor = bannerlist.titleColor 27 | this.typeTitle = bannerlist.typeTitle 28 | this.url = bannerlist.url 29 | } 30 | } 31 | 32 | /**获取推荐歌单数据 */ 33 | export function getSonglist(limit: number = 30) { 34 | return service({ 35 | url: '/personalized', 36 | params: { 37 | limit 38 | } 39 | }) 40 | } 41 | 42 | /**获取新碟上线数据 */ 43 | export function getNewAlbum() { 44 | return service({ 45 | url: '/album/newest' 46 | }) 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/service/musiclist.ts: -------------------------------------------------------------------------------- 1 | import { service } from './service' 2 | 3 | /** 获取专辑内容 (新碟返回的数据为专辑) 需要当前专辑的id*/ 4 | export function albumContent(id: number) { 5 | return service({ 6 | url: '/album', 7 | params: { 8 | id 9 | } 10 | }) 11 | } 12 | interface IAlbum { 13 | picUrl: string 14 | name: string 15 | artist: { 16 | name: string 17 | id: number 18 | } 19 | publishTime: number 20 | description: string 21 | info: { 22 | commentCount: number 23 | shareCount: number 24 | } 25 | } 26 | export class AlbumBaseInfo { 27 | imgUrl: string 28 | title: string 29 | singerName: string 30 | singerId: number 31 | publishTime: number 32 | description: string 33 | commentCount: number 34 | shareCount: number 35 | constructor(album: IAlbum) { 36 | this.imgUrl = album.picUrl 37 | this.title = album.name 38 | this.singerName = album.artist.name 39 | this.singerId = album.artist.id 40 | this.publishTime = album.publishTime 41 | this.description = album.description 42 | this.commentCount = album.info.commentCount // 评论数量 43 | this.shareCount = album.info.shareCount // 分享数量 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/vue-content-loader.d.ts: -------------------------------------------------------------------------------- 1 | import { VueConstructor } from 'vue'; 2 | 3 | export const ContentLoader: ContentLoaderConstructor 4 | export const FacebookLoader: FacebookLoaderConstructor 5 | export const BulletListLoader: BulletListLoaderConstructor 6 | export const InstagramLoader: InstagramLoaderConstructor 7 | export const CodeLoader: CodeLoaderConstructor 8 | export const ListLoader: ListLoaderConstructor 9 | 10 | export interface ContentLoaderProps { 11 | width: number, 12 | height: number, 13 | speed: number, 14 | preserveAspectRatio: string, 15 | primaryColor: string, 16 | secondaryColor: string, 17 | uniqueKey: string, 18 | animate: boolean 19 | } 20 | 21 | export interface ContentLoaderConstructor extends VueConstructor { 22 | props: ContentLoaderProps 23 | } 24 | 25 | export interface FacebookLoaderConstructor extends ContentLoaderConstructor{} 26 | export interface CodeLoaderConstructor extends ContentLoaderConstructor{} 27 | export interface BulletListLoaderConstructor extends ContentLoaderConstructor{} 28 | export interface InstagramLoaderConstructor extends ContentLoaderConstructor{} 29 | export interface ListLoaderConstructor extends ContentLoaderConstructor{} -------------------------------------------------------------------------------- /src/router/login.ts: -------------------------------------------------------------------------------- 1 | const login = () => import(/*webpackChunkName:'login'*/'@/views/login/index.vue') 2 | 3 | const phone = () => import(/*webpackChunkName:'phone'*/'@/views/login/phone.vue') 4 | const loginPhone = ()=>import(/*webpackChunkName:'loginPhone'*/'@/views/login/login-phone.vue') 5 | 6 | const email = () => import(/*webpackChunkName:'email'*/'@/views/login/email.vue') 7 | 8 | const register = ()=>import(/*webpackChunkName:'register'*/'@/views/login/register.vue') 9 | 10 | 11 | export default [ 12 | { 13 | path: '/login', 14 | name: 'login', 15 | component: login, 16 | children: [ 17 | { 18 | path: 'phone', 19 | name: 'phone', 20 | component: phone 21 | }, 22 | { 23 | path:'login-phone', 24 | name:'loginPhone', 25 | component:loginPhone 26 | }, 27 | { 28 | path: 'email', 29 | name: 'email', 30 | component: email 31 | } 32 | ] 33 | }, 34 | { 35 | path:'/register', 36 | name:'register', 37 | component:register 38 | } 39 | 40 | ] -------------------------------------------------------------------------------- /src/store/getters.ts: -------------------------------------------------------------------------------- 1 | import { IState } from './interface' 2 | export default { 3 | encodeLoginAccount(state: IState) { 4 | return (state.loginAccount).replace((state.loginAccount).substr(3, 4), '****') 5 | }, 6 | playListLength(state: IState) { 7 | return (state.playList).length 8 | }, 9 | // state.currentPlayIndex = -1 bug? state获取localstorage时需要使用三目运算符而是逻辑或 10 | // 这里的防错处理的比较多 播放列表发生改变时,如果播放索引大于播放列表时的需要做防止找不到id,name,img的处理 11 | playMusicID(state: IState) { 12 | return state.currentPlayIndex != -1 ? (state.currentPlayIndex < (state.playList).length ? (state).playList[state.currentPlayIndex].id : -1) : -1 13 | }, 14 | playMusicName(state: IState) { 15 | return state.currentPlayIndex != -1 ? (state.currentPlayIndex < (state.playList).length ? (state).playList[state.currentPlayIndex].name : '') : '' 16 | }, 17 | playMusicImg(state: IState) { 18 | return state.currentPlayIndex != -1 ? (state.currentPlayIndex < (state.playList).length ? (state).playList[state.currentPlayIndex].imgURL : '') : '' 19 | }, 20 | playHistoryLength(state:IState){ 21 | return (state.playHistory as object[]).length 22 | } 23 | } -------------------------------------------------------------------------------- /src/router/my.ts: -------------------------------------------------------------------------------- 1 | const my = () => import(/*webpackChunkName:'my'*/'@/views/my/index.vue') 2 | const playHistory = ()=>import(/*webpackChunkName:'playHistory'*/'@/views/my/childRouter/play_history.vue') 3 | const radio = () => import(/*webpackChunkName:'radio'*/'@/views/my/childRouter/my_radio.vue') 4 | const star = () => import(/*webpackChunkName:'star'*/'@/views/my/childRouter/my_star.vue') 5 | const watchNewMusic = () => import(/*webpackChunkName:'watchNewMusic'*/'@/views/my/childRouter/watch_new_music.vue') 6 | 7 | export default [ 8 | { 9 | path: '/my', 10 | name: 'my', 11 | component: my, 12 | children: [ 13 | { 14 | path:'playhistory', 15 | name:'playHistory', 16 | component:playHistory 17 | }, 18 | { 19 | path: 'radio', 20 | name: 'radio', 21 | component: radio 22 | }, { 23 | path: 'star', 24 | name: 'star', 25 | component: star 26 | }, { 27 | path: 'watchnewmusic', 28 | name: 'watchnewmusic', 29 | component: watchNewMusic 30 | } 31 | ] 32 | } 33 | ] 34 | 35 | -------------------------------------------------------------------------------- /src/views/singerDetail/childComp/home.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | 49 | -------------------------------------------------------------------------------- /src/views/songManage/updateSong/image/emotion.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/longpress.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.directive('longpress', { 4 | bind(el, binding) { 5 | 6 | let timer: any = null 7 | 8 | el.addEventListener('touchstart', function () { 9 | if (timer === null) { 10 | timer = window.setTimeout(() => { 11 | binding.value.methods(binding.value.params) 12 | }, 300) 13 | } 14 | }) 15 | 16 | el.addEventListener('touchmove', function () { 17 | window.clearTimeout(timer) 18 | timer = null 19 | }) 20 | 21 | el.addEventListener('touchend', function () { 22 | window.clearTimeout(timer) 23 | timer = null 24 | }) 25 | 26 | // var event = new CustomEvent("longpress", {"detail":{"message":'长按事件'}}) 27 | // el.dispatchEvent(event) 28 | } 29 | }) 30 | 31 | /** 32 | * 33 | 34 | 使用方式: 35 | params可以是 undefined, string, number, boolean, array, object 类型的值 36 | 37 | html: 38 | 39 |
自定义事件
40 | 41 | 42 | script: 43 | 44 | didi(id:string){ 45 | console.log(id) 46 | console.log('didi') 47 | } 48 | 49 | 50 | * 51 | */ -------------------------------------------------------------------------------- /src/views/find/image/music.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/conf/songsInfo.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface ISonginfo { 3 | id: number 4 | name: string 5 | al: { 6 | id: number 7 | picUrl: string 8 | } 9 | album: { 10 | id: number 11 | artist: { 12 | img1v1Url: string 13 | } 14 | } 15 | ar: [{ name: string }] 16 | artists: [{ name: string }] 17 | } 18 | 19 | export class SongsInfoClass { 20 | songsId: number 21 | songsName: string 22 | albumId: number 23 | imgUrl: string 24 | singerName: string 25 | constructor(songsInfo: ISonginfo) { 26 | this.songsId = songsInfo.id 27 | this.songsName = songsInfo.name 28 | this.albumId = songsInfo.al && songsInfo.al.id || songsInfo.album && songsInfo.album.id 29 | this.imgUrl = songsInfo.al && songsInfo.al.picUrl || songsInfo.album && songsInfo.album.artist.img1v1Url 30 | let singers: any[] = [] 31 | if (songsInfo.ar) { 32 | for (let item of songsInfo.ar) { 33 | singers.push(item.name) 34 | } 35 | } else { 36 | for (let item of songsInfo.artists) { 37 | singers.push(item.name) 38 | } 39 | } 40 | this.singerName = singers.length === 1 ? singers[0] : singers.join(' | ') 41 | } 42 | } -------------------------------------------------------------------------------- /src/views/find/image/mv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/music-ico.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | const state: IState = { 7 | loadingShow: false, 8 | searchKeyWrold: window.sessionStorage.getItem('searchKeyWrold') 9 | ? window.sessionStorage.getItem('searchKeyWrold') 10 | : '', 11 | searchHistory: JSON.parse( 12 | window.localStorage.getItem('musicHistorySearch') || '[]' 13 | ), 14 | loginAccount: window.sessionStorage.getItem('loginAccount') ? window.sessionStorage.getItem('loginAccount') : '', 15 | account: JSON.parse((window.localStorage).getItem('account')) || {}, 16 | playList: JSON.parse(window.localStorage.getItem('playlist') || '[]'), // 播放列表中的容器 17 | currentPlayIndex: (window.localStorage).getItem('playIndex') ? parseInt((window.localStorage).getItem('playIndex')) : -1, 18 | // currentPlayIndex: parseInt((window.localStorage).getItem('playIndex')) || -1, // 当前播放容器的索引值 19 | playMode: parseInt((window.localStorage).getItem('mode')) || EPlayMode.listLoop, 20 | playHistory: JSON.parse(window.localStorage.getItem('playHistory') || '[]') 21 | } 22 | import { IState, EPlayMode } from './interface' 23 | import { mutations } from './mutatioins' 24 | import { actions } from './actions' 25 | import getters from './getters' 26 | export default new Vuex.Store({ 27 | state, 28 | mutations, 29 | actions, 30 | getters 31 | }) 32 | -------------------------------------------------------------------------------- /src/service/singer.ts: -------------------------------------------------------------------------------- 1 | import { service } from '@/service/service' 2 | 3 | /** 获取100个热门歌手 */ 4 | export function getSinger() { 5 | return service({ 6 | url: '/top/artists?limit=100' 7 | }) 8 | } 9 | export interface ISinger { 10 | id: number 11 | name: string 12 | picUrl: string 13 | pin: string 14 | } 15 | export class SingerData { 16 | id!: number 17 | name!: string 18 | imgUrl!: string 19 | 20 | constructor(artists: ISinger) { 21 | this.id = artists.id 22 | this.name = artists.name 23 | this.imgUrl = artists.picUrl 24 | } 25 | } 26 | 27 | /** 获取歌手单曲(可获得部分信息和热门歌曲) 用于歌手详情页 */ 28 | export function getSingerDetail(id: number) { 29 | return service({ 30 | url: '/artists', 31 | params: { 32 | id 33 | } 34 | }) 35 | } 36 | export interface ISingerHeadInfo { 37 | musicSize?: number 38 | albumSize?: number 39 | mvSize?: number 40 | img1v1Url?: string 41 | } 42 | 43 | /** 获取歌手专辑 */ 44 | export function getSingerAlbum( 45 | id: number, 46 | offset: number = 0, 47 | limit: number = 50 48 | ) { 49 | return service({ 50 | url: '/artist/album', 51 | params: { 52 | id, 53 | offset: limit * offset, 54 | limit 55 | } 56 | }) 57 | } 58 | 59 | /** 获取歌手MV */ 60 | export function getSingerMv(id: number) { 61 | return service({ 62 | url: '/artist/mv', 63 | params: { 64 | id 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /src/views/find/image/diantai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-typescript-music", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "@types/better-scroll": "^1.12.1", 11 | "@types/fastclick": "^1.0.28", 12 | "@types/lodash": "^4.14.149", 13 | "axios": "^0.19.0", 14 | "better-scroll": "^1.13.2", 15 | "core-js": "^3.4.3", 16 | "fastclick": "^1.0.6", 17 | "font-awesome": "^4.7.0", 18 | "lodash": "^4.17.15", 19 | "moment": "^2.26.0", 20 | "pinyin": "^2.9.0", 21 | "vue": "^2.6.10", 22 | "vue-class-component": "^7.0.2", 23 | "vue-content-loader": "^0.2.3", 24 | "vue-lazyload": "^1.3.3", 25 | "vue-property-decorator": "^8.3.0", 26 | "vue-router": "^3.1.3", 27 | "vuex": "^3.1.2" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "^4.1.0", 31 | "@vue/cli-plugin-typescript": "^4.1.0", 32 | "@vue/cli-service": "^4.1.0", 33 | "babel-plugin-lodash": "^3.3.4", 34 | "less": "^3.0.4", 35 | "less-loader": "^5.0.0", 36 | "lodash-webpack-plugin": "^0.11.5", 37 | "moment-locales-webpack-plugin": "^1.2.0", 38 | "postcss-px-to-viewport": "^1.1.1", 39 | "style-resources-loader": "^1.3.2", 40 | "typescript": "~3.5.3", 41 | "vue-template-compiler": "^2.6.10", 42 | "webpack-bundle-analyzer": "^3.6.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/content/single-list/single-list-items.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 31 | 32 | -------------------------------------------------------------------------------- /src/views/my/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 50 | -------------------------------------------------------------------------------- /src/components/content/mv-list/mv-list-items.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /src/views/find/childComp/recommend.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | 38 | 56 | -------------------------------------------------------------------------------- /src/views/singerDetail/childComp/head.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | 33 | 63 | -------------------------------------------------------------------------------- /src/service/login.ts: -------------------------------------------------------------------------------- 1 | import {service} from './service' 2 | 3 | // 发送验证码 4 | export function sendVerifyCode(phone:string){ 5 | return service({ 6 | url:'/captcha/sent', 7 | params:{ 8 | phone 9 | } 10 | }) 11 | } 12 | // 验证验证码 13 | export function testVerifyCode(phone:string,captcha:string){ 14 | return service({ 15 | url:'/captcha/verify', 16 | params:{ 17 | phone, 18 | captcha 19 | } 20 | }) 21 | } 22 | // 检测手机号码是否已注册 23 | export function testIsRegister(phone:string){ 24 | return service({ 25 | url:'/cellphone/existence/check', 26 | params:{ 27 | phone 28 | } 29 | }) 30 | } 31 | 32 | // 注册/修改密码 33 | export function registerAccount(captcha:string,phone:string,password:string,nikename:string){ 34 | return service({ 35 | url:'/register/cellphone', 36 | params:{ 37 | captcha, 38 | phone, 39 | password, 40 | nikename 41 | } 42 | }) 43 | } 44 | 45 | // 手机登录 46 | export function phoneLogin(phone:string,password:string){ 47 | return service({ 48 | url:'/login/cellphone', 49 | params:{ 50 | phone, 51 | password 52 | } 53 | }) 54 | } 55 | // 邮箱登录 56 | export function emailLogin(email:string,password:string){ 57 | return service({ 58 | url:'/login', 59 | params:{ 60 | email, 61 | password 62 | } 63 | }) 64 | } -------------------------------------------------------------------------------- /src/router/songManage.ts: -------------------------------------------------------------------------------- 1 | const deleteSong = () => import(/*webpackChunkName:'deleteSong'*/'@/views/songManage/delete-song.vue') 2 | const updateSong = () => import(/*webpackChunkName:'updateSong'*/'@/views/songManage/updateSong/index.vue') 3 | const editSongName = () => import(/*webpackChunkName:'editSongName'*/'@/views/songManage/updateSong/edit-song-name.vue') 4 | const editSongTags = () => import(/*webpackChunkName:'editSongTags'*/'@/views/songManage/updateSong/edit-song-tags.vue') 5 | const editSongDesc = () => import(/*webpackChunkName:'editSongDesc'*/'@/views/songManage/updateSong/edit-song-desc.vue') 6 | const addSong = ()=>import(/*webpackChunkName:'addSong'*/'@/views/songManage/add-song.vue') 7 | 8 | export default [ 9 | { 10 | path: '/songmanage/delete', 11 | name: 'deleteSong', 12 | component: deleteSong 13 | }, 14 | { 15 | path: '/songmanage/update', 16 | name: 'updateSong', 17 | component: updateSong, 18 | children: [ 19 | { 20 | path: 'editname', 21 | name: 'editSongName', 22 | component: editSongName 23 | }, { 24 | path: 'edittags', 25 | name: 'editSongTags', 26 | component: editSongTags 27 | }, { 28 | path: 'editdesc', 29 | name: 'editSongDesc', 30 | component: editSongDesc 31 | } 32 | ] 33 | },{ 34 | path:'/songmanage/add', 35 | name:'addSong', 36 | component:addSong 37 | } 38 | ] -------------------------------------------------------------------------------- /src/views/songManage/updateSong/image/style.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 3 | const LodashWebpackPlugin = require('lodash-webpack-plugin') 4 | const MomentLocalesPlugin = require('moment-locales-webpack-plugin') 5 | module.exports = { 6 | 7 | publicPath: process.env.NODE_ENV === 'production' ? './' : '/', 8 | 9 | productionSourceMap: false, 10 | 11 | chainWebpack: confirm => { 12 | const types = ['vue-modules', 'vue', 'normal-module', 'normal'] 13 | types.forEach(type => { 14 | addStyleResource(confirm.module.rule('less').oneOf(type)) 15 | }) 16 | }, 17 | css: { 18 | loaderOptions: { 19 | less: { 20 | javascriptEnabled: true 21 | } 22 | } 23 | }, 24 | 25 | configureWebpack: { 26 | plugins: [ 27 | // new BundleAnalyzerPlugin() 28 | new LodashWebpackPlugin() // 通过 webpack-bundle-analyzer和babel-plugin-lodash 对 lodash 进行按需引入 29 | ,new MomentLocalesPlugin({ 30 | localesToKeep: ['es-us', 'zh-cn'] 31 | }) // 移除 moment 不必要的语言环境 32 | ], 33 | resolve: { 34 | alias: { 35 | 'components': '@/components', 36 | 'utils': '@/utils', 37 | 'views': '@/views' 38 | } 39 | } 40 | } 41 | } 42 | 43 | function addStyleResource(rule) { 44 | rule.use('style-resource').loader('style-resources-loader').options({ 45 | patterns: [ 46 | path.resolve(__dirname, './src/assets/less/reset.less') 47 | ] 48 | }) 49 | } -------------------------------------------------------------------------------- /src/views/find/childComp/find-swiper.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | 38 | 54 | -------------------------------------------------------------------------------- /src/components/content/progress-circle/progress-circle.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 44 | -------------------------------------------------------------------------------- /src/views/searchResult/childComp/album.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 52 | 53 | 66 | -------------------------------------------------------------------------------- /src/views/songManage/updateSong/image/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/common/kl-dialog/kl-dialog.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 42 | -------------------------------------------------------------------------------- /src/views/searchResult/childComp/topbar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 43 | 44 | -------------------------------------------------------------------------------- /src/views/test.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | -------------------------------------------------------------------------------- /src/views/player/readme.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 2020/04/01 音乐播放器的实现思路: 3 | * 4 | * 播放操作: 5 | * 1.当点击音乐播放列表时,将当前的音乐列表存放在一个容器中,并记录当前播放音乐的索引值 6 | * 2.监听当前播放音乐的索引值,一旦发生变化,就去请求 得到音乐的url地址 7 | * 3.当前音乐播放完了 跳入下一首 8 | * 4.播放完成之后判断播放模式(单曲循环、列表循环、随机播放) 9 | * 如果是单曲循环 则添加 loop属性 =====> (思路错误) 10 | * 当播放完成之后 在添加loop属性 已经没有任何意义了 还会导出其他模式下也是循环播放的状态 11 | * 解决的办法则是 设置当前的时长为0 让它回到初始状态 继续播放 12 | * 13 | * 而播放完成之后 则是进行下一首的播放 所以需要一个 next 方法 去调用 14 | * next 只有两种情况 列表循环和随机播放 当去点击下一首时 此时只能是列表循环模式 所以无论如何 只要不是 随机模式 即一定为 列表循环 15 | * prev 方法同上思想 只是currentPlayIndex减1 16 | * 如果是列表循环 则让当前的currentPlayIndex加1,需做超出判断 17 | * 如果是随机播放 则生成一个符合规则的随机数设置在当前的currentPlayIndex 规则:0 ~ playlist.length-1 整数 18 | * 19 | * 暂停/播放操作: 20 | * 默认为暂停状态,当然 如果音乐容器中没有内容是不允许显示播放器来的 21 | * 当音乐有url时,即设置为播放状态,此时容器中已有内容 22 | * 当点击暂停或播放图标时,将当前的播放状态取反向外发射通知, 23 | * 当父级组件收到通知后 立即 将当前播放状态 设置为子级传过来的参数 24 | * 并判断,如果是true 则此时需要从暂停状态 -> 播放状态 否则则相反 25 | * bug? 刷新后,再次点击播放 无效 26 | * 刷新后,默认是暂停状态,当点击播放时,因为没有url所以将会 报错: DOMException: The play() request was interrupted by a call to pause() 27 | * 解决方案: 28 | * 在mounted的时候手动调用一次获取音乐的url的请求,并设置为暂停状态 29 | * 因为mounted中才可以操作DOM