├── babel.config.js
├── .eslintignore
├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ ├── hot_icon.png
│ └── hot_music_bg_2x.jpg
├── store
│ ├── state.js
│ ├── mutation-types.js
│ ├── getters.js
│ ├── index.js
│ ├── mutations.js
│ └── actions.js
├── permission.js
├── views
│ ├── Home.vue
│ ├── 404.vue
│ ├── MusicPlayr.vue
│ └── SongMenu.vue
├── main.js
├── App.vue
├── router.js
├── components
│ ├── Tab
│ │ ├── components
│ │ │ ├── searchHot
│ │ │ │ └── index.vue
│ │ │ ├── searchBar
│ │ │ │ └── index.vue
│ │ │ └── searchResult
│ │ │ │ └── index.vue
│ │ ├── TabSearch.vue
│ │ ├── TabIndex.vue
│ │ └── TabRank.vue
│ ├── HomeTop
│ │ ├── index.vue
│ │ └── logo.svg
│ └── SongsList
│ │ └── index.vue
├── utils
│ ├── request.js
│ └── index.js
└── api
│ └── api.js
├── .env.production
├── .env.staging
├── .gitignore
├── .env.development
├── vue.config.js
├── README.md
├── .github
└── workflows
│ └── deploy.yml
├── package.json
└── .eslintrc.js
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/app"]
3 | };
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # 不检测的文件夹
2 | build/*.js
3 | src/assets
4 | public
5 | dist
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Francis1024/NeteaseMusic/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/hot_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Francis1024/NeteaseMusic/HEAD/src/assets/hot_icon.png
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'production'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/api/'
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/hot_music_bg_2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Francis1024/NeteaseMusic/HEAD/src/assets/hot_music_bg_2x.jpg
--------------------------------------------------------------------------------
/.env.staging:
--------------------------------------------------------------------------------
1 | NODE_ENV = production
2 |
3 | # just a flag
4 | ENV = 'staging'
5 |
6 | # base api
7 | VUE_APP_BASE_API = '/api/'
8 |
9 |
--------------------------------------------------------------------------------
/src/store/state.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | // 推荐歌单
3 | remdList: [],
4 | // 最新音乐
5 | newSongList: [],
6 | // 当前播放音乐的地址
7 | currentMusicUrl: ''
8 | };
9 |
10 | export default state;
11 |
--------------------------------------------------------------------------------
/src/store/mutation-types.js:
--------------------------------------------------------------------------------
1 | // 推荐歌单
2 | export const SET_REMDLIST = 'SET_REMDLIST';
3 | // 最新音乐
4 | export const SET_NEWSONGLIST = 'SET_NEWSONGLIST';
5 | // 音乐地址
6 | export const SET_MUSICURL = 'SET_MUSICURL';
7 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router';
2 |
3 | router.beforeEach(async(to, from, next) => {
4 | // set page title
5 | next();
6 | });
7 |
8 | router.afterEach(() => {
9 | // finish progress bar
10 |
11 | });
12 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | // 截取6条歌单数据
3 | remdList: state => {
4 | return state.remdList.slice(0, 6);
5 | },
6 | // 获取最新歌曲列表
7 | newSongList: state => {
8 | return state.newSongList;
9 | }
10 | };
11 | export default getters;
12 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /deploy
4 | /dist
5 | package-lock.json
6 |
7 | # local env files
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import state from './state';
4 | import getters from './getters';
5 | import mutations from './mutations';
6 | import actions from './actions';
7 | Vue.use(Vuex);
8 |
9 | export default new Vuex.Store({
10 | state,
11 | getters,
12 | mutations,
13 | actions
14 | });
15 |
--------------------------------------------------------------------------------
/src/views/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
404 找不到页面
4 | 返回首页
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from '@/App.vue';
3 | import router from '@/router';
4 | import store from '@/store/index';
5 | import 'lib-flexible';
6 | import Vant from 'vant';
7 | import 'vant/lib/index.css';
8 |
9 | Vue.use(Vant);
10 | Vue.config.productionTip = false;
11 |
12 | // console.log('环境变量', process.env);
13 |
14 | new Vue({
15 | router,
16 | store,
17 | render: h => h(App)
18 | }).$mount('#app');
19 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
13 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types';
2 |
3 | const mutations = {
4 | // 推荐歌单
5 | [types.SET_REMDLIST](state, remdList) {
6 | state.remdList = remdList;
7 | },
8 | // 最新音乐
9 | [types.SET_NEWSONGLIST](state, newSongList) {
10 | state.newSongList = newSongList;
11 | },
12 | // 音乐地址
13 | [types.SET_MUSICURL](state, currentMusicUrl) {
14 | console.log(state);
15 | console.log(currentMusicUrl.url);
16 | state.currentMusicUrl = currentMusicUrl.url;
17 | }
18 | };
19 | export default mutations;
20 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'development'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/api/'
6 |
7 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
8 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled.
9 | # It only does one thing by converting all import() to require().
10 | # This configuration can significantly increase the speed of hot updates,
11 | # when you have a large number of pages.
12 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
13 |
14 | VUE_CLI_BABEL_TRANSPILE_MODULES = true
15 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | function resolve(dir) {
4 | return path.join(__dirname, dir)
5 | }
6 | module.exports = {
7 | devServer: {
8 | proxy: {
9 | '/api': {
10 | // 需要代理的接口,一般会加前缀来区分
11 | target: 'https://music.ztyuu.com', // 接口域名
12 | changeOrigin: true // 是否跨域
13 | }
14 | }
15 | },
16 | configureWebpack: {
17 | // provide the app's title in webpack's name field, so that
18 | // it can be accessed in index.html to inject the correct title.
19 | resolve: {
20 | alias: {
21 | '@': resolve('src')
22 | }
23 | }
24 | },
25 |
26 | // 将构建好的文件输出到哪里
27 | outputDir: 'dist'
28 | };
29 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 |
4 | Vue.use(Router);
5 | // 路由懒加载
6 | // const getComponent = name => () => import(`@/views/${name}.vue`);
7 | export default new Router({
8 | mode: 'history',
9 | routes: [
10 | // 主页面
11 | {
12 | path: '/',
13 | component: () => import('@/views/Home')
14 | },
15 | // 歌单详情页面
16 | {
17 | path: '/songMenuDetail',
18 | component: () => import('@/views/SongMenu')
19 | },
20 | // 歌曲播放页面
21 | {
22 | path: '/MusicPlayr',
23 | component: () => import('@/views/MusicPlayr')
24 | },
25 | {
26 | path: '*',
27 | component: () => import('@/views/404')
28 | }
29 | ]
30 | });
31 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 网易云音乐
7 |
8 |
9 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ### 项目描述
4 |
5 | [](https://996.icu)[](https://github.com/996icu/996.ICU/blob/master/LICENSE)
6 |
7 | 一个基于 Vue-Cli 3 + Vuex +Vant UI 的网易云音乐移动端
8 |
9 | ### 项目运行
10 |
11 | ```
12 | #克隆到本地
13 | git clone https://github.com/Francis1024/NeteaseMusic.git
14 |
15 | #进入项目文件夹
16 | cd Netease-Music
17 |
18 | #安装依赖
19 | npm install
20 |
21 | #运行项目
22 | npm run serve
23 |
24 | #项目部署
25 | npm run build
26 | ```
27 |
28 |
29 |
30 | ### Todo
31 |
32 | - [x] 推荐页面
33 | - [x] 热歌榜页面
34 | - [x] 搜索页面
35 | - [x] 音乐播放页面
36 | - [x] 歌单信息页面
37 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy NeteaseMusic
2 | on:
3 | push:
4 | branches:
5 | - master # 只在master上push触发部署
6 | paths-ignore: # 下列文件的变更不触发部署,可以自行添加
7 | - README.md
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest # 使用ubuntu系统镜像运行自动化脚本
13 | strategy:
14 | matrix:
15 | node-version: [12.x]
16 | steps: # 自动化步骤
17 | - uses: actions/checkout@master # 第一步,下载代码仓库
18 | - name: Build
19 | run: |
20 | npm install && npm run build
21 | - name: Deploy # 第二步,rsync推文件
22 | uses: AEnterprise/rsync-deploy@v1.0 # 使用别人包装好的步骤镜像
23 | env:
24 | DEPLOY_KEY: ${{ secrets.ACCESS_TOKEN }} # 引用配置,SSH私钥
25 | ARGS: -avz --delete --exclude='*.pyc' # rsync参数,排除.pyc文件
26 | SERVER_PORT: ${{ secrets.SERVER_PORT }} # SSH端口
27 | FOLDER: ./dist/ # 要推送的文件夹,路径相对于代码仓库的根目录
28 | SERVER_IP: ${{ secrets.REMOTE_HOST }} # 引用配置,服务器的host名(IP或者域名domain.com)
29 | USERNAME: ${{ secrets.REMOTE_USER }} # 引用配置,服务器登录名
30 | SERVER_DESTINATION: ${{ secrets.TARGET }} # 部署到目标文件夹
31 |
--------------------------------------------------------------------------------
/src/components/Tab/components/searchHot/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
28 |
29 |
32 |
55 |
--------------------------------------------------------------------------------
/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import { getRemdSongList, getNewSongList, getMusicUrl } from '@/api/api';
2 |
3 | const actions = {
4 | // 推荐歌单
5 | getRemdSongList({ commit }) {
6 | return new Promise((resolve, reject) => {
7 | getRemdSongList().then(res => {
8 | console.log('推荐歌单数据', res);
9 | if (res.code === 200) {
10 | commit('SET_REMDLIST', res.result);
11 | resolve();
12 | } else {
13 | reject();
14 | }
15 | });
16 | });
17 | },
18 | getNewSongList({ commit }) {
19 | return new Promise((resolve, reject) => {
20 | getNewSongList().then(res => {
21 | console.log('最新歌曲数据', res);
22 | if (res.code === 200) {
23 | commit('SET_NEWSONGLIST', res.result);
24 | resolve();
25 | } else {
26 | reject();
27 | }
28 | });
29 | });
30 | },
31 | getMusicUrl({ commit }, id) {
32 | return new Promise((resolve, reject) => {
33 | getMusicUrl(id).then(res => {
34 | console.log('当前歌曲数据', res);
35 | if (res.code === 200) {
36 | commit('SET_MUSICURL', res.data[0]);
37 | resolve();
38 | } else {
39 | reject();
40 | }
41 | });
42 | });
43 | }
44 | };
45 | export default actions;
46 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | // create an axios instance
4 | const service = axios.create({
5 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
6 | // withCredentials: true, // send cookies when cross-domain requests
7 | timeout: 5000 // request timeout
8 |
9 | });
10 |
11 | // request interceptor
12 | service.interceptors.request.use(
13 | config => {
14 | return config
15 | },
16 | error => {
17 | // do something with request error
18 | console.log(error) // for debug
19 | return Promise.reject(error)
20 | }
21 | )
22 |
23 | // response interceptor
24 | service.interceptors.response.use(
25 | /**
26 | * If you want to get http information such as headers or status
27 | * Please return response => response
28 | */
29 |
30 | /**
31 | * Determine the request status by custom code
32 | * Here is just an example
33 | * You can also judge the status by HTTP Status Code
34 | */
35 | response => {
36 | const res = response.data;
37 | if (res.code !== 200) {
38 | return Promise.reject(new Error(res.message || 'Error'));
39 | } else {
40 | return res;
41 | }
42 | },
43 | error => {
44 | console.log('err' + error) // for debug
45 | return Promise.reject(error)
46 | }
47 | )
48 |
49 | export default service
50 |
--------------------------------------------------------------------------------
/src/api/api.js:
--------------------------------------------------------------------------------
1 | // 引入axios
2 | import request from '@/utils/request.js';
3 | // 获取推荐歌单
4 | export function getRemdSongList() {
5 | return request({
6 | url: '/personalized'
7 | })
8 | }
9 | // 获取最新歌曲
10 | export function getNewSongList() {
11 | return request({
12 | url: '/personalized/newsong'
13 | })
14 | }
15 | // 获取歌曲地址
16 | export function getMusicUrl(id) {
17 | return request({
18 | url: '/song/url',
19 | params: { id: id }
20 | })
21 | }
22 | // 获取音乐详情
23 | export function getMusicDetail(id) {
24 | return request({
25 | url: '/song/detail',
26 | params: { ids: id }
27 | })
28 | }
29 |
30 | // 排行榜
31 | // https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=排行榜
32 | export function getMusicTopList(params) {
33 | return request({
34 | url: '/top/list',
35 | params
36 | })
37 | }
38 |
39 | // 热门搜索
40 | export function getSearchHot() {
41 | return request({
42 | url: '/search/hot'
43 | })
44 | }
45 | // 搜索多重匹配
46 | export function getSearchMultimatch(params) {
47 | return request({
48 | url: '/search/multimatch',
49 | params
50 | })
51 | }
52 | // 搜索接口
53 | // /search?keywords= 海阔天空
54 | export function getSearchMusic(params) {
55 | return request({
56 | url: '/search',
57 | params
58 | })
59 | }
60 | // 获取歌单详情
61 | export function getPlaylistDetail(params) {
62 | return request({
63 | url: '/playlist/detail',
64 | params
65 | })
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/HomeTop/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
33 |
34 |
55 |
--------------------------------------------------------------------------------
/src/components/Tab/TabSearch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
55 |
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "netease-music",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "dev": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "axios": "^0.21.2",
13 | "core-js": "^2.6.5",
14 | "lib-flexible": "^0.3.2",
15 | "postcss-pxtorem": "^4.0.1",
16 | "vant": "^2.1.8",
17 | "vue": "^2.6.10",
18 | "vue-router": "^3.0.3",
19 | "vuex": "^3.0.1"
20 | },
21 | "devDependencies": {
22 | "@vue/cli-plugin-babel": "^3.12.1",
23 | "@vue/cli-plugin-eslint": "^3.6.0",
24 | "@vue/cli-service": "^5.0.8",
25 | "@vue/eslint-config-prettier": "^4.0.1",
26 | "babel-eslint": "^10.0.1",
27 | "eslint": "^5.16.0",
28 | "eslint-plugin-vue": "^5.0.0",
29 | "sass": "^1.18.0",
30 | "sass-loader": "^7.1.0",
31 | "vue-template-compiler": "^2.5.21"
32 | },
33 | "eslintConfig": {
34 | "root": true,
35 | "env": {
36 | "node": true
37 | },
38 | "extends": [
39 | "plugin:vue/essential",
40 | "@vue/prettier"
41 | ],
42 | "rules": {
43 | "no-console": "off",
44 | "no-debugger": "off"
45 | },
46 | "parserOptions": {
47 | "parser": "babel-eslint"
48 | }
49 | },
50 | "postcss": {
51 | "plugins": {
52 | "autoprefixer": {
53 | "browsers": [
54 | "Android >= 4.0",
55 | "iOS >= 7"
56 | ]
57 | },
58 | "postcss-pxtorem": {
59 | "rootValue": 37.5,
60 | "propList": [
61 | "*"
62 | ]
63 | }
64 | }
65 | },
66 | "browserslist": [
67 | "> 1%",
68 | "last 2 versions",
69 | "not ie <= 8"
70 | ]
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/Tab/components/searchBar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
45 |
46 |
83 |
--------------------------------------------------------------------------------
/src/components/SongsList/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
9 |
10 |
{{ value.name }}
11 |
12 |
13 |
{{ value.song.artists[0].name }} - {{ value.name }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
暂无数据
22 |
23 |
24 |
25 |
36 |
37 |
94 |
--------------------------------------------------------------------------------
/src/components/Tab/components/searchResult/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
最佳匹配
4 |
5 | -
10 |
11 |
{{ value.name }}
12 |
13 |
14 |
{{ value.artists[0].name }} - {{ value.name }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
暂无数据
23 |
24 |
25 |
26 |
37 |
98 |
--------------------------------------------------------------------------------
/src/views/MusicPlayr.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
![]()
12 |
13 |
14 |
22 |
23 |
24 |
76 |
128 |
--------------------------------------------------------------------------------
/src/components/Tab/TabIndex.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 | -
12 |
13 |
![]()
14 |
15 |
16 | {{ (value.playCount / 10000).toFixed() }}
17 | 万
18 |
19 |
20 | {{ value.name }}
21 |
22 |
23 |
24 |
25 |
33 |
34 |
35 |
78 |
79 |
141 |
--------------------------------------------------------------------------------
/src/components/Tab/TabRank.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
更新时间:{{ time }}
6 |
7 |
30 |
31 |
32 |
61 |
149 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | import axios from 'axios';
6 | import store from '@/store';
7 |
8 | /**
9 | * Parse the time to string
10 | * @param {(Object|string|number)} time
11 | * @param {string} cFormat
12 | * @returns {string | null}
13 | */
14 | export function parseTime(time, cFormat) {
15 | if (arguments.length === 0) {
16 | return null
17 | }
18 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
19 | let date
20 | if (typeof time === 'object') {
21 | date = time
22 | } else {
23 | if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
24 | time = parseInt(time)
25 | }
26 | if ((typeof time === 'number') && (time.toString().length === 10)) {
27 | time = time * 1000
28 | }
29 | date = new Date(time)
30 | }
31 | const formatObj = {
32 | y: date.getFullYear(),
33 | m: date.getMonth() + 1,
34 | d: date.getDate(),
35 | h: date.getHours(),
36 | i: date.getMinutes(),
37 | s: date.getSeconds(),
38 | a: date.getDay()
39 | }
40 | const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
41 | const value = formatObj[key]
42 | // Note: getDay() returns 0 on Sunday
43 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
44 | return value.toString().padStart(2, '0')
45 | })
46 | return time_str
47 | }
48 |
49 | /**
50 | * @param {number} time
51 | * @param {string} option
52 | * @returns {string}
53 | */
54 | export function formatTime(time, option) {
55 | if (('' + time).length === 10) {
56 | time = parseInt(time) * 1000
57 | } else {
58 | time = +time
59 | }
60 | const d = new Date(time)
61 | const now = Date.now()
62 |
63 | const diff = (now - d) / 1000
64 |
65 | if (diff < 30) {
66 | return '刚刚'
67 | } else if (diff < 3600) {
68 | // less 1 hour
69 | return Math.ceil(diff / 60) + '分钟前'
70 | } else if (diff < 3600 * 24) {
71 | return Math.ceil(diff / 3600) + '小时前'
72 | } else if (diff < 3600 * 24 * 2) {
73 | return '1天前'
74 | }
75 | if (option) {
76 | return parseTime(time, option)
77 | } else {
78 | return (
79 | d.getMonth() +
80 | 1 +
81 | '月' +
82 | d.getDate() +
83 | '日' +
84 | d.getHours() +
85 | '时' +
86 | d.getMinutes() +
87 | '分'
88 | )
89 | }
90 | }
91 |
92 | /**
93 | * @param {string} pageTitle
94 | * @param {string} singerName
95 | * @returns {string}
96 | */
97 | export function getPageTitle(pageTitle, singerName) {
98 | const title = `${pageTitle} - ${singerName} `
99 | document.title = title
100 |
101 | // var s = title.split('');
102 | // setInterval(function() {
103 | // s.push(s[0]);
104 | // s.shift();
105 | // document.title = s.join('');
106 | // }, 500);
107 | }
108 | /**
109 | * @param {string} url
110 | * @returns {Object}
111 | */
112 | export function param2Obj(url) {
113 | const search = url.split('?')[1]
114 | if (!search) {
115 | return {}
116 | }
117 | return JSON.parse(
118 | '{"' +
119 | decodeURIComponent(search)
120 | .replace(/"/g, '\\"')
121 | .replace(/&/g, '","')
122 | .replace(/=/g, '":"')
123 | .replace(/\+/g, ' ') +
124 | '"}'
125 | )
126 | }
127 |
128 | /**
129 | * @param content
130 | */
131 | export function copy(content) {
132 | const aux = document.createElement('textarea');
133 | aux.value = content;
134 | document.body.appendChild(aux);
135 | aux.select();
136 | document.execCommand('copy');
137 | document.body.removeChild(aux);
138 | console.log('复制成功');
139 | }
140 |
141 | export function exportExcel(url, filename) {
142 | return new Promise((resolve, reject) => {
143 | axios(url, {
144 | headers: { 'token': store.getters.token },
145 | responseType: 'blob'
146 | }
147 | ).then(response => {
148 | const url = window.URL.createObjectURL(new Blob([response.data]));
149 | const link = document.createElement('a');
150 | link.href = url;
151 | link.setAttribute('download', filename);
152 | document.body.appendChild(link);
153 | link.click();
154 | resolve()
155 | }
156 | ).catch(err => {
157 | const response = err.response;
158 | const msg = response.statusText;
159 | console.error(msg);
160 | reject()
161 | })
162 | })
163 | }
164 |
165 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parserOptions: {
4 | parser: 'babel-eslint',
5 | sourceType: 'module'
6 | },
7 | env: {
8 | browser: true,
9 | node: true,
10 | es6: true,
11 | },
12 | extends: ['plugin:vue/recommended', 'eslint:recommended'],
13 |
14 | // add your custom rules here
15 | //it is base on https://github.com/vuejs/eslint-config-vue
16 | rules: {
17 | "vue/max-attributes-per-line": [2, {
18 | "singleline": 10,
19 | "multiline": {
20 | "max": 1,
21 | "allowFirstLine": false
22 | }
23 | }],
24 | "vue/singleline-html-element-content-newline": "off",
25 | "vue/multiline-html-element-content-newline":"off",
26 | "vue/name-property-casing": ["error", "PascalCase"],
27 | "vue/no-v-html": "off",
28 | 'accessor-pairs': 2,
29 | 'arrow-spacing': [2, {
30 | 'before': true,
31 | 'after': true
32 | }],
33 | 'block-spacing': [2, 'always'],
34 | 'brace-style': [2, '1tbs', {
35 | 'allowSingleLine': true
36 | }],
37 | 'camelcase': [0, {
38 | 'properties': 'always'
39 | }],
40 | 'comma-dangle': [2, 'never'],
41 | 'comma-spacing': [2, {
42 | 'before': false,
43 | 'after': true
44 | }],
45 | 'comma-style': [2, 'last'],
46 | 'constructor-super': 2,
47 | 'curly': [2, 'multi-line'],
48 | 'dot-location': [2, 'property'],
49 | 'eol-last': 2,
50 | 'eqeqeq': ["error", "always", {"null": "ignore"}],
51 | 'generator-star-spacing': [2, {
52 | 'before': true,
53 | 'after': true
54 | }],
55 | 'handle-callback-err': [2, '^(err|error)$'],
56 | 'indent': [2, 2, {
57 | 'SwitchCase': 1
58 | }],
59 | 'jsx-quotes': [2, 'prefer-single'],
60 | 'key-spacing': [2, {
61 | 'beforeColon': false,
62 | 'afterColon': true
63 | }],
64 | 'keyword-spacing': [2, {
65 | 'before': true,
66 | 'after': true
67 | }],
68 | 'new-cap': [2, {
69 | 'newIsCap': true,
70 | 'capIsNew': false
71 | }],
72 | 'new-parens': 2,
73 | 'no-array-constructor': 2,
74 | 'no-caller': 2,
75 | 'no-console': 'off',
76 | 'no-class-assign': 2,
77 | 'no-cond-assign': 2,
78 | 'no-const-assign': 2,
79 | 'no-control-regex': 0,
80 | 'no-delete-var': 2,
81 | 'no-dupe-args': 2,
82 | 'no-dupe-class-members': 2,
83 | 'no-dupe-keys': 2,
84 | 'no-duplicate-case': 2,
85 | 'no-empty-character-class': 2,
86 | 'no-empty-pattern': 2,
87 | 'no-eval': 2,
88 | 'no-ex-assign': 2,
89 | 'no-extend-native': 2,
90 | 'no-extra-bind': 2,
91 | 'no-extra-boolean-cast': 2,
92 | 'no-extra-parens': [2, 'functions'],
93 | 'no-fallthrough': 2,
94 | 'no-floating-decimal': 2,
95 | 'no-func-assign': 2,
96 | 'no-implied-eval': 2,
97 | 'no-inner-declarations': [2, 'functions'],
98 | 'no-invalid-regexp': 2,
99 | 'no-irregular-whitespace': 2,
100 | 'no-iterator': 2,
101 | 'no-label-var': 2,
102 | 'no-labels': [2, {
103 | 'allowLoop': false,
104 | 'allowSwitch': false
105 | }],
106 | 'no-lone-blocks': 2,
107 | 'no-mixed-spaces-and-tabs': 2,
108 | 'no-multi-spaces': 2,
109 | 'no-multi-str': 2,
110 | 'no-multiple-empty-lines': [2, {
111 | 'max': 1
112 | }],
113 | 'no-native-reassign': 2,
114 | 'no-negated-in-lhs': 2,
115 | 'no-new-object': 2,
116 | 'no-new-require': 2,
117 | 'no-new-symbol': 2,
118 | 'no-new-wrappers': 2,
119 | 'no-obj-calls': 2,
120 | 'no-octal': 2,
121 | 'no-octal-escape': 2,
122 | 'no-path-concat': 2,
123 | 'no-proto': 2,
124 | 'no-redeclare': 2,
125 | 'no-regex-spaces': 2,
126 | 'no-return-assign': [2, 'except-parens'],
127 | 'no-self-assign': 2,
128 | 'no-self-compare': 2,
129 | 'no-sequences': 2,
130 | 'no-shadow-restricted-names': 2,
131 | 'no-spaced-func': 2,
132 | 'no-sparse-arrays': 2,
133 | 'no-this-before-super': 2,
134 | 'no-throw-literal': 2,
135 | 'no-trailing-spaces': 2,
136 | 'no-undef': 2,
137 | 'no-undef-init': 2,
138 | 'no-unexpected-multiline': 2,
139 | 'no-unmodified-loop-condition': 2,
140 | 'no-unneeded-ternary': [2, {
141 | 'defaultAssignment': false
142 | }],
143 | 'no-unreachable': 2,
144 | 'no-unsafe-finally': 2,
145 | 'no-unused-vars': [2, {
146 | 'vars': 'all',
147 | 'args': 'none'
148 | }],
149 | 'no-useless-call': 2,
150 | 'no-useless-computed-key': 2,
151 | 'no-useless-constructor': 2,
152 | 'no-useless-escape': 0,
153 | 'no-whitespace-before-property': 2,
154 | 'no-with': 2,
155 | 'one-var': [2, {
156 | 'initialized': 'never'
157 | }],
158 | 'operator-linebreak': [2, 'after', {
159 | 'overrides': {
160 | '?': 'before',
161 | ':': 'before'
162 | }
163 | }],
164 | 'padded-blocks': [2, 'never'],
165 | 'quotes': [2, 'single', {
166 | 'avoidEscape': true,
167 | 'allowTemplateLiterals': true
168 | }],
169 | // 'semi': [2, 'never'],
170 | // 'semi-spacing': [2, {
171 | // 'before': false,
172 | // 'after': true
173 | // }],
174 | 'space-before-blocks': [2, 'always'],
175 | 'space-before-function-paren': [2, 'never'],
176 | 'space-in-parens': [2, 'never'],
177 | 'space-infix-ops': 2,
178 | 'space-unary-ops': [2, {
179 | 'words': true,
180 | 'nonwords': false
181 | }],
182 | 'spaced-comment': [2, 'always', {
183 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
184 | }],
185 | 'template-curly-spacing': [2, 'never'],
186 | 'use-isnan': 2,
187 | 'valid-typeof': 2,
188 | 'wrap-iife': [2, 'any'],
189 | 'yield-star-spacing': [2, 'both'],
190 | 'yoda': [2, 'never'],
191 | 'prefer-const': 2,
192 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
193 | 'object-curly-spacing': [2, 'always', {
194 | objectsInObjects: false
195 | }],
196 | 'array-bracket-spacing': [2, 'never']
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/views/SongMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
71 |
72 |
73 |
93 |
278 |
--------------------------------------------------------------------------------
/src/components/HomeTop/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------