mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
✨ feat: 添加eslint 和 桌面歌词(未完成)
This commit is contained in:
13
.eslintignore
Normal file
13
.eslintignore
Normal file
@@ -0,0 +1,13 @@
|
||||
snapshot*
|
||||
dist
|
||||
lib
|
||||
es
|
||||
esm
|
||||
node_modules
|
||||
static
|
||||
cypress
|
||||
script/test/cypress
|
||||
_site
|
||||
temp*
|
||||
static/
|
||||
!.prettierrc.js
|
||||
133
.eslintrc
Normal file
133
.eslintrc
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"eslint-config-airbnb-base",
|
||||
"@vue/typescript/recommended",
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:vue-scoped-css/base",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jest": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"defineProps": "readonly",
|
||||
"defineEmits": "readonly"
|
||||
},
|
||||
"plugins": [
|
||||
"vue",
|
||||
"@typescript-eslint",
|
||||
"simple-import-sort"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"import/extensions": [
|
||||
".js",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-continue": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-shadow": "off",
|
||||
"guard-for-in": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/first": "off", // https://github.com/vuejs/vue-eslint-parser/issues/58
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"vue/first-attribute-linebreak": 0,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"class-methods-use-this": "off", // 因为AxiosCancel必须实例化而能静态化所以加的规则,如果有办法解决可以取消
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.vue"
|
||||
],
|
||||
"rules": {
|
||||
"vue/component-name-in-template-casing": [
|
||||
2,
|
||||
"kebab-case"
|
||||
],
|
||||
"vue/require-default-prop": 0,
|
||||
"vue/multi-word-component-names": 0,
|
||||
"vue/no-reserved-props": 0,
|
||||
"vue/no-v-html": 0,
|
||||
"vue-scoped-css/enforce-style-type": [
|
||||
"error",
|
||||
{
|
||||
"allows": [
|
||||
"scoped"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
], // https://github.com/typescript-eslint eslint-recommended
|
||||
"rules": {
|
||||
"constructor-super": "off", // ts(2335) & ts(2377)
|
||||
"getter-return": "off", // ts(2378)
|
||||
"no-const-assign": "off", // ts(2588)
|
||||
"no-dupe-args": "off", // ts(2300)
|
||||
"no-dupe-class-members": "off", // ts(2393) & ts(2300)
|
||||
"no-dupe-keys": "off", // ts(1117)
|
||||
"no-func-assign": "off", // ts(2539)
|
||||
"no-import-assign": "off", // ts(2539) & ts(2540)
|
||||
"no-new-symbol": "off", // ts(2588)
|
||||
"no-obj-calls": "off", // ts(2349)
|
||||
"no-redeclare": "off", // ts(2451)
|
||||
"no-setter-return": "off", // ts(2408)
|
||||
"no-this-before-super": "off", // ts(2376)
|
||||
"no-undef": "off", // ts(2304)
|
||||
"no-unreachable": "off", // ts(7027)
|
||||
"no-unsafe-negation": "off", // ts(2365) & ts(2360) & ts(2358)
|
||||
"no-var": "error", // ts transpiles let/const to var, so no need for vars any more
|
||||
"prefer-const": "error", // ts provides better types with const
|
||||
"prefer-rest-params": "error", // ts provides better types with rest args over arguments
|
||||
"prefer-spread": "error", // ts transpiles spread to apply, so no need for manual apply
|
||||
"valid-typeof": "off" // ts(2367)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
39
.prettierrc.js
Normal file
39
.prettierrc.js
Normal file
@@ -0,0 +1,39 @@
|
||||
module.exports = {
|
||||
// 一行最多 120 字符..
|
||||
printWidth: 120,
|
||||
// 使用 2 个空格缩进
|
||||
tabWidth: 2,
|
||||
// 不使用缩进符,而使用空格
|
||||
useTabs: false,
|
||||
// 行尾需要有分号
|
||||
semi: true,
|
||||
// 使用单引号
|
||||
singleQuote: true,
|
||||
// 对象的 key 仅在必要时用引号
|
||||
quoteProps: 'as-needed',
|
||||
// jsx 不使用单引号,而使用双引号
|
||||
jsxSingleQuote: false,
|
||||
// 末尾需要有逗号
|
||||
trailingComma: 'all',
|
||||
// 大括号内的首尾需要空格
|
||||
bracketSpacing: true,
|
||||
// jsx 标签的反尖括号需要换行
|
||||
jsxBracketSameLine: false,
|
||||
// 箭头函数,只有一个参数的时候,也需要括号
|
||||
arrowParens: 'always',
|
||||
// 每个文件格式化的范围是文件的全部内容
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
// 不需要写文件开头的 @prettier
|
||||
requirePragma: false,
|
||||
// 不需要自动在文件开头插入 @prettier
|
||||
insertPragma: false,
|
||||
// 使用默认的折行标准
|
||||
proseWrap: 'preserve',
|
||||
// 根据显示样式决定 html 要不要折行
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
// vue 文件中的 script 和 style 内不用缩进
|
||||
vueIndentScriptAndStyle: false,
|
||||
// 换行符使用 lf
|
||||
endOfLine: 'lf',
|
||||
};
|
||||
122
app.js
122
app.js
@@ -1,9 +1,10 @@
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, globalShortcut, nativeImage } = require('electron')
|
||||
const path = require('path')
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, globalShortcut, nativeImage } = require('electron');
|
||||
const path = require('path');
|
||||
const Store = require('electron-store');
|
||||
const setJson = require('./electron/set.json')
|
||||
const setJson = require('./electron/set.json');
|
||||
const { loadLyricWindow } = require('./electron/lyric');
|
||||
|
||||
let mainWin = null
|
||||
let mainWin = null;
|
||||
function createWindow() {
|
||||
mainWin = new BrowserWindow({
|
||||
width: 1200,
|
||||
@@ -11,129 +12,132 @@ function createWindow() {
|
||||
frame: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
// contextIsolation: false,
|
||||
preload: path.join(__dirname, '/electron/preload.js'),
|
||||
},
|
||||
})
|
||||
const win = mainWin
|
||||
win.setMinimumSize(1200, 780)
|
||||
});
|
||||
const win = mainWin;
|
||||
win.setMinimumSize(1200, 780);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
win.webContents.openDevTools({ mode: 'detach' })
|
||||
win.loadURL('http://localhost:4678/')
|
||||
win.webContents.openDevTools({ mode: 'detach' });
|
||||
win.loadURL('http://localhost:4678/');
|
||||
} else {
|
||||
win.loadURL(`file://${__dirname}/dist/index.html`)
|
||||
win.loadURL(`file://${__dirname}/dist/index.html`);
|
||||
}
|
||||
const image = nativeImage.createFromPath(path.join(__dirname, 'public/icon.png'))
|
||||
const tray = new Tray(image)
|
||||
const image = nativeImage.createFromPath(path.join(__dirname, 'public/icon.png'));
|
||||
const tray = new Tray(image);
|
||||
|
||||
// 创建一个上下文菜单
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '显示',
|
||||
click: () => {
|
||||
win.show()
|
||||
win.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '退出',
|
||||
click: () => {
|
||||
win.destroy()
|
||||
win.destroy();
|
||||
},
|
||||
},
|
||||
])
|
||||
]);
|
||||
|
||||
// 设置系统托盘图标的上下文菜单
|
||||
tray.setContextMenu(contextMenu)
|
||||
tray.setContextMenu(contextMenu);
|
||||
|
||||
// 当系统托盘图标被点击时,切换窗口的显示/隐藏
|
||||
tray.on('click', () => {
|
||||
if (win.isVisible()) {
|
||||
win.hide()
|
||||
win.hide();
|
||||
} else {
|
||||
win.show()
|
||||
win.show();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const set = store.get('set')
|
||||
const set = store.get('set');
|
||||
// store.set('set', setJson)
|
||||
|
||||
if (!set) {
|
||||
store.set('set', setJson)
|
||||
store.set('set', setJson);
|
||||
}
|
||||
|
||||
loadLyricWindow(ipcMain);
|
||||
}
|
||||
|
||||
// 限制只能启动一个应用
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
app.quit();
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
app.on('ready',()=>{
|
||||
app.on('ready', () => {
|
||||
globalShortcut.register('CommandOrControl+Alt+Shift+M', () => {
|
||||
if (mainWin.isVisible()) {
|
||||
mainWin.hide()
|
||||
mainWin.hide();
|
||||
} else {
|
||||
mainWin.show()
|
||||
mainWin.show();
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
app.quit();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
globalShortcut.unregisterAll()
|
||||
})
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
ipcMain.on('minimize-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.minimize()
|
||||
})
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
win.minimize();
|
||||
});
|
||||
|
||||
ipcMain.on('maximize-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win.isMaximized()) {
|
||||
win.unmaximize()
|
||||
win.unmaximize();
|
||||
} else {
|
||||
win.maximize()
|
||||
win.maximize();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ipcMain.on('close-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.destroy()
|
||||
})
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
win.destroy();
|
||||
});
|
||||
|
||||
ipcMain.on('drag-start', (event, data) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
ipcMain.on('drag-start', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
win.webContents.beginFrameSubscription((frameBuffer) => {
|
||||
event.reply('frame-buffer', frameBuffer)
|
||||
})
|
||||
})
|
||||
event.reply('frame-buffer', frameBuffer);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('mini-tray', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.hide()
|
||||
})
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
win.hide();
|
||||
});
|
||||
|
||||
// 重启
|
||||
ipcMain.on('restart', () => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
})
|
||||
app.relaunch();
|
||||
app.exit(0);
|
||||
});
|
||||
|
||||
const store = new Store();
|
||||
|
||||
// 定义ipcRenderer监听事件
|
||||
ipcMain.on('setStore', (_, key, value) => {
|
||||
store.set(key, value)
|
||||
})
|
||||
store.set(key, value);
|
||||
});
|
||||
|
||||
ipcMain.on('getStore', (_, key) => {
|
||||
let value = store.get(key)
|
||||
_.returnValue = value || ""
|
||||
})
|
||||
const value = store.get(key);
|
||||
_.returnValue = value || '';
|
||||
});
|
||||
|
||||
37
electron/lyric.js
Normal file
37
electron/lyric.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const { BrowserWindow } = require('electron');
|
||||
|
||||
let lyricWindow = null;
|
||||
|
||||
const loadLyricWindow = (ipcMain) => {
|
||||
lyricWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 300,
|
||||
frame: false,
|
||||
show: false,
|
||||
transparent: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: `${__dirname}/preload.js`,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
ipcMain.on('open-lyric', () => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
lyricWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
lyricWindow.loadURL('http://localhost:4678/#/lyric');
|
||||
} else {
|
||||
lyricWindow.loadURL(`file://${__dirname}/dist/index.html/#/lyric`);
|
||||
}
|
||||
|
||||
lyricWindow.show();
|
||||
});
|
||||
|
||||
ipcMain.on('send-lyric', (e, data) => {
|
||||
lyricWindow.webContents.send('receive-lyric', data);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
loadLyricWindow,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const { contextBridge, ipcRenderer, app } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
minimize: () => ipcRenderer.send('minimize-window'),
|
||||
@@ -7,19 +7,22 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
dragStart: (data) => ipcRenderer.send('drag-start', data),
|
||||
miniTray: () => ipcRenderer.send('mini-tray'),
|
||||
restart: () => ipcRenderer.send('restart'),
|
||||
})
|
||||
openLyric: () => ipcRenderer.send('open-lyric'),
|
||||
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
||||
});
|
||||
|
||||
const electronHandler = {
|
||||
ipcRenderer: {
|
||||
setStoreValue: (key, value) => {
|
||||
ipcRenderer.send("setStore", key, value)
|
||||
ipcRenderer.send('setStore', key, value);
|
||||
},
|
||||
|
||||
getStoreValue(key) {
|
||||
const resp = ipcRenderer.sendSync("getStore", key)
|
||||
return resp
|
||||
const resp = ipcRenderer.sendSync('getStore', key);
|
||||
return resp;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
app,
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', electronHandler)
|
||||
contextBridge.exposeInMainWorld('electron', electronHandler);
|
||||
|
||||
19
package.json
19
package.json
@@ -11,18 +11,21 @@
|
||||
"es": "vite && electron .",
|
||||
"start": "set NODE_ENV=development&&electron .",
|
||||
"e:b": "electron-builder --config ./electron.config.json",
|
||||
"eb": "vite build && e:b"
|
||||
"eb": "vite build && e:b",
|
||||
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss7-compat": "^2.2.4",
|
||||
"@vue/runtime-core": "^3.3.4",
|
||||
"@vueuse/core": "^10.7.1",
|
||||
"@vueuse/electron": "^10.9.0",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"axios": "^0.21.1",
|
||||
"electron-store": "^8.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"naive-ui": "^2.34.4",
|
||||
"postcss": "^7.0.36",
|
||||
"remixicon": "^4.2.0",
|
||||
"sass": "^1.35.2",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4",
|
||||
"vue": "^3.3.4",
|
||||
@@ -30,12 +33,22 @@
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sicons/antd": "^0.10.0",
|
||||
"@vicons/antd": "^0.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"electron": "^28.0.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"eslint-plugin-vue": "^9.21.1",
|
||||
"eslint-plugin-vue-scoped-css": "^2.7.2",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^4.3.2",
|
||||
"unplugin-auto-import": "^0.17.2",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
|
||||
73
src/App.vue
73
src/App.vue
@@ -1,37 +1,36 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<audio id="MusicAudio" ref="audioRef" :src="playMusicUrl" :autoplay="play"></audio>
|
||||
<n-config-provider :theme="darkTheme">
|
||||
<n-dialog-provider>
|
||||
<router-view></router-view>
|
||||
</n-dialog-provider>
|
||||
</n-config-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { darkTheme } from 'naive-ui'
|
||||
import store from '@/store'
|
||||
|
||||
const audio = ref<HTMLAudioElement | null>(null)
|
||||
|
||||
const playMusicUrl = computed(() => store.state.playMusicUrl as string)
|
||||
// 是否播放
|
||||
const play = computed(() => store.state.play as boolean)
|
||||
const windowData = window as any
|
||||
onMounted(()=>{
|
||||
if(windowData.electron){
|
||||
const setData = windowData.electron.ipcRenderer.getStoreValue('set');
|
||||
store.commit('setSetData', setData)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped >
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.app {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="app">
|
||||
<audio id="MusicAudio" ref="audioRef" :src="playMusicUrl" :autoplay="play"></audio>
|
||||
<n-config-provider :theme="darkTheme">
|
||||
<n-dialog-provider>
|
||||
<router-view></router-view>
|
||||
</n-dialog-provider>
|
||||
</n-config-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { darkTheme } from 'naive-ui';
|
||||
|
||||
import store from '@/store';
|
||||
|
||||
const playMusicUrl = computed(() => store.state.playMusicUrl as string);
|
||||
// 是否播放
|
||||
const play = computed(() => store.state.play as boolean);
|
||||
const windowData = window as any;
|
||||
onMounted(() => {
|
||||
if (windowData.electron) {
|
||||
const setData = windowData.electron.ipcRenderer.getStoreValue('set');
|
||||
store.commit('setSetData', setData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.app {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import request from "@/utils/request";
|
||||
import { IHotSinger } from "@/type/singer";
|
||||
import { ISearchKeyword, IHotSearch } from "@/type/search";
|
||||
import { IPlayListSort } from "@/type/playlist";
|
||||
import { IRecommendMusic } from "@/type/music";
|
||||
import { IAlbumNew } from "@/type/album";
|
||||
import { IAlbumNew } from '@/type/album';
|
||||
import { IRecommendMusic } from '@/type/music';
|
||||
import { IPlayListSort } from '@/type/playlist';
|
||||
import { IHotSearch, ISearchKeyword } from '@/type/search';
|
||||
import { IHotSinger } from '@/type/singer';
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface IHotSingerParams {
|
||||
offset: number;
|
||||
@@ -16,30 +16,30 @@ interface IRecommendMusicParams {
|
||||
|
||||
// 获取热门歌手
|
||||
export const getHotSinger = (params: IHotSingerParams) => {
|
||||
return request.get<IHotSinger>("/top/artists", { params });
|
||||
return request.get<IHotSinger>('/top/artists', { params });
|
||||
};
|
||||
|
||||
// 获取搜索推荐词
|
||||
export const getSearchKeyword = () => {
|
||||
return request.get<ISearchKeyword>("/search/default");
|
||||
return request.get<ISearchKeyword>('/search/default');
|
||||
};
|
||||
|
||||
// 获取热门搜索
|
||||
export const getHotSearch = () => {
|
||||
return request.get<IHotSearch>("/search/hot/detail");
|
||||
return request.get<IHotSearch>('/search/hot/detail');
|
||||
};
|
||||
|
||||
// 获取歌单分类
|
||||
export const getPlaylistCategory = () => {
|
||||
return request.get<IPlayListSort>("/playlist/catlist");
|
||||
return request.get<IPlayListSort>('/playlist/catlist');
|
||||
};
|
||||
|
||||
// 获取推荐音乐
|
||||
export const getRecommendMusic = (params: IRecommendMusicParams) => {
|
||||
return request.get<IRecommendMusic>("/personalized/newsong", { params });
|
||||
return request.get<IRecommendMusic>('/personalized/newsong', { params });
|
||||
};
|
||||
|
||||
// 获取最新专辑推荐
|
||||
export const getNewAlbum = () => {
|
||||
return request.get<IAlbumNew>("/album/newest");
|
||||
return request.get<IAlbumNew>('/album/newest');
|
||||
};
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
import { IList } from "@/type/list";
|
||||
import type { IListDetail } from "@/type/listDetail";
|
||||
|
||||
interface IListByTagParams {
|
||||
tag: string;
|
||||
before: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
interface IListByCatParams {
|
||||
cat: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
// 根据tag 获取歌单列表
|
||||
export function getListByTag(params: IListByTagParams) {
|
||||
return request.get<IList>("/top/playlist/highquality", { params: params });
|
||||
}
|
||||
|
||||
// 根据cat 获取歌单列表
|
||||
export function getListByCat(params: IListByCatParams) {
|
||||
return request.get("/top/playlist", {
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取推荐歌单
|
||||
export function getRecommendList(limit: number = 30) {
|
||||
return request.get("/personalized", { params: { limit } });
|
||||
}
|
||||
|
||||
// 获取歌单详情
|
||||
export function getListDetail(id: number | string) {
|
||||
return request.get<IListDetail>("/playlist/detail", { params: { id } });
|
||||
}
|
||||
|
||||
// 获取专辑内容
|
||||
export function getAlbum(id: number | string) {
|
||||
return request.get("/album", { params: { id } });
|
||||
}
|
||||
import { IList } from '@/type/list';
|
||||
import type { IListDetail } from '@/type/listDetail';
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface IListByTagParams {
|
||||
tag: string;
|
||||
before: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
interface IListByCatParams {
|
||||
cat: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
// 根据tag 获取歌单列表
|
||||
export function getListByTag(params: IListByTagParams) {
|
||||
return request.get<IList>('/top/playlist/highquality', { params });
|
||||
}
|
||||
|
||||
// 根据cat 获取歌单列表
|
||||
export function getListByCat(params: IListByCatParams) {
|
||||
return request.get('/top/playlist', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取推荐歌单
|
||||
export function getRecommendList(limit: number = 30) {
|
||||
return request.get('/personalized', { params: { limit } });
|
||||
}
|
||||
|
||||
// 获取歌单详情
|
||||
export function getListDetail(id: number | string) {
|
||||
return request.get<IListDetail>('/playlist/detail', { params: { id } });
|
||||
}
|
||||
|
||||
// 获取专辑内容
|
||||
export function getAlbum(id: number | string) {
|
||||
return request.get('/album', { params: { id } });
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import request from "@/utils/request";
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 创建二维码key
|
||||
// /login/qr/key
|
||||
export function getQrKey() {
|
||||
return request.get("/login/qr/key");
|
||||
return request.get('/login/qr/key');
|
||||
}
|
||||
|
||||
// 创建二维码
|
||||
// /login/qr/create
|
||||
export function createQr(key: any) {
|
||||
return request.get("/login/qr/create", { params: { key: key, qrimg: true } });
|
||||
return request.get('/login/qr/create', { params: { key, qrimg: true } });
|
||||
}
|
||||
|
||||
// 获取二维码状态
|
||||
// /login/qr/check
|
||||
export function checkQr(key: any) {
|
||||
return request.get("/login/qr/check", { params: { key: key } });
|
||||
return request.get('/login/qr/check', { params: { key } });
|
||||
}
|
||||
|
||||
// 获取登录状态
|
||||
// /login/status
|
||||
export function getLoginStatus() {
|
||||
return request.get("/login/status");
|
||||
return request.get('/login/status');
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
// /user/account
|
||||
export function getUserDetail() {
|
||||
return request.get("/user/account");
|
||||
return request.get('/user/account');
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
// /logout
|
||||
export function logout() {
|
||||
return request.get("/logout");
|
||||
return request.get('/logout');
|
||||
}
|
||||
|
||||
// 手机号登录
|
||||
// /login/cellphone
|
||||
export function loginByCellphone(phone: any, password: any) {
|
||||
return request.post("/login/cellphone", {
|
||||
phone: phone,
|
||||
password: password,
|
||||
return request.post('/login/cellphone', {
|
||||
phone,
|
||||
password,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { IPlayMusicUrl } from "@/type/music"
|
||||
import { ILyric } from "@/type/lyric"
|
||||
import request from "@/utils/request"
|
||||
import requestMusic from "@/utils/request_music"
|
||||
// 根据音乐Id获取音乐播放URl
|
||||
export const getMusicUrl = (id: number) => {
|
||||
return request.get<IPlayMusicUrl>("/song/url", { params: { id: id } })
|
||||
}
|
||||
|
||||
// 获取歌曲详情
|
||||
export const getMusicDetail = (ids: Array<number>) => {
|
||||
return request.get("/song/detail", { params: { ids: ids.join(",")}})
|
||||
}
|
||||
|
||||
// 根据音乐Id获取音乐歌词
|
||||
export const getMusicLrc = (id: number) => {
|
||||
return request.get<ILyric>("/lyric", { params: { id: id } })
|
||||
}
|
||||
|
||||
export const getParsingMusicUrl = (id: number) => {
|
||||
return requestMusic.get<any>("/music", { params: { id: id } })
|
||||
}
|
||||
import { ILyric } from '@/type/lyric';
|
||||
import { IPlayMusicUrl } from '@/type/music';
|
||||
import request from '@/utils/request';
|
||||
import requestMusic from '@/utils/request_music';
|
||||
// 根据音乐Id获取音乐播放URl
|
||||
export const getMusicUrl = (id: number) => {
|
||||
return request.get<IPlayMusicUrl>('/song/url', { params: { id } });
|
||||
};
|
||||
|
||||
// 获取歌曲详情
|
||||
export const getMusicDetail = (ids: Array<number>) => {
|
||||
return request.get('/song/detail', { params: { ids: ids.join(',') } });
|
||||
};
|
||||
|
||||
// 根据音乐Id获取音乐歌词
|
||||
export const getMusicLrc = (id: number) => {
|
||||
return request.get<ILyric>('/lyric', { params: { id } });
|
||||
};
|
||||
|
||||
export const getParsingMusicUrl = (id: number) => {
|
||||
return requestMusic.get<any>('/music', { params: { id } });
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IData } from '@/type'
|
||||
import { IMvItem, IMvUrlData } from '@/type/mv'
|
||||
import request from '@/utils/request'
|
||||
import { IData } from '@/type';
|
||||
import { IMvItem, IMvUrlData } from '@/type/mv';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 获取 mv 排行
|
||||
export const getTopMv = (limit: number) => {
|
||||
@@ -8,8 +8,8 @@ export const getTopMv = (limit: number) => {
|
||||
params: {
|
||||
limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取 mv 数据
|
||||
export const getMvDetail = (mvid: string) => {
|
||||
@@ -17,8 +17,8 @@ export const getMvDetail = (mvid: string) => {
|
||||
params: {
|
||||
mvid,
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取 mv 地址
|
||||
export const getMvUrl = (id: Number) => {
|
||||
@@ -26,5 +26,5 @@ export const getMvUrl = (id: Number) => {
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import request from "@/utils/request"
|
||||
import { ISearchDetail } from "@/type/search"
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface IParams {
|
||||
keywords: string
|
||||
type: number
|
||||
keywords: string;
|
||||
type: number;
|
||||
}
|
||||
// 搜索内容
|
||||
export const getSearch = (params: IParams) => {
|
||||
return request.get<any>('/cloudsearch', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import request from "@/utils/request";
|
||||
import request from '@/utils/request';
|
||||
|
||||
// /user/detail
|
||||
export function getUserDetail(uid: number) {
|
||||
return request.get("/user/detail", { params: { uid } });
|
||||
return request.get('/user/detail', { params: { uid } });
|
||||
}
|
||||
|
||||
// /user/playlist
|
||||
export function getUserPlaylist(uid: number) {
|
||||
return request.get("/user/playlist", { params: { uid } });
|
||||
return request.get('/user/playlist', { params: { uid } });
|
||||
}
|
||||
|
||||
// 播放历史
|
||||
// /user/record?uid=32953014&type=1
|
||||
export function getUserRecord(uid: number, type: number = 0) {
|
||||
return request.get("/user/record", { params: { uid, type } });
|
||||
return request.get('/user/record', { params: { uid, type } });
|
||||
}
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
<div class="music-title">{{ name }}</div>
|
||||
<!-- 歌单歌曲列表 -->
|
||||
<div class="music-list">
|
||||
<n-scrollbar >
|
||||
<div v-for="(item, index) in songList" :key="item.id" :class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 100)">
|
||||
<SongItem :item="formatDetail(item)" @play="handlePlay" />
|
||||
<n-scrollbar>
|
||||
<div
|
||||
v-for="(item, index) in songList"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
>
|
||||
<song-item :item="formatDetail(item)" @play="handlePlay" />
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,40 +22,42 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStore } from 'vuex'
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
import SongItem from "@/components/common/SongItem.vue";
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
import PlayBottom from './common/PlayBottom.vue';
|
||||
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
name: string;
|
||||
songList: any[]
|
||||
}>()
|
||||
const emit = defineEmits(['update:show'])
|
||||
songList: any[];
|
||||
}>();
|
||||
const emit = defineEmits(['update:show']);
|
||||
|
||||
const formatDetail = computed(() => (detail: any) => {
|
||||
let song = {
|
||||
const song = {
|
||||
artists: detail.ar,
|
||||
name: detail.al.name,
|
||||
id: detail.al.id,
|
||||
}
|
||||
};
|
||||
|
||||
detail.song = song
|
||||
detail.picUrl = detail.al.picUrl
|
||||
return detail
|
||||
})
|
||||
detail.song = song;
|
||||
detail.picUrl = detail.al.picUrl;
|
||||
return detail;
|
||||
});
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
const tracks = props.songList || []
|
||||
store.commit('setPlayList', tracks)
|
||||
}
|
||||
const handlePlay = () => {
|
||||
const tracks = props.songList || [];
|
||||
store.commit('setPlayList', tracks);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
emit('update:show', false)
|
||||
}
|
||||
emit('update:show', false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -72,4 +78,4 @@ const close = () => {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
<template>
|
||||
<!-- 歌单分类列表 -->
|
||||
<div class="play-list-type">
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">歌单分类</div>
|
||||
<div>
|
||||
<template v-for="(item, index) in playlistCategory?.sub" :key="item.name">
|
||||
<span
|
||||
class="play-list-type-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(index <= 19 ? index : index - 19)"
|
||||
v-show="isShowAllPlaylistCategory || index <= 19"
|
||||
@click="handleClickPlaylistType(item.name)"
|
||||
>{{ item.name }}</span>
|
||||
</template>
|
||||
<div
|
||||
class="play-list-type-showall"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="
|
||||
setAnimationDelay(
|
||||
!isShowAllPlaylistCategory
|
||||
? 25
|
||||
: playlistCategory?.sub.length || 100 + 30
|
||||
)
|
||||
"
|
||||
@click="isShowAllPlaylistCategory = !isShowAllPlaylistCategory"
|
||||
>{{ !isShowAllPlaylistCategory ? "显示全部" : "隐藏一些" }}</div>
|
||||
</div>
|
||||
<!-- 歌单分类列表 -->
|
||||
<div class="play-list-type">
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">歌单分类</div>
|
||||
<div>
|
||||
<template v-for="(item, index) in playlistCategory?.sub" :key="item.name">
|
||||
<span
|
||||
v-show="isShowAllPlaylistCategory || index <= 19"
|
||||
class="play-list-type-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(index <= 19 ? index : index - 19)"
|
||||
@click="handleClickPlaylistType(item.name)"
|
||||
>{{ item.name }}</span
|
||||
>
|
||||
</template>
|
||||
<div
|
||||
class="play-list-type-showall"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(!isShowAllPlaylistCategory ? 25 : playlistCategory?.sub.length || 100 + 30)"
|
||||
@click="isShowAllPlaylistCategory = !isShowAllPlaylistCategory"
|
||||
>
|
||||
{{ !isShowAllPlaylistCategory ? '显示全部' : '隐藏一些' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getPlaylistCategory } from "@/api/home";
|
||||
import type { IPlayListSort } from "@/type/playlist";
|
||||
import { setAnimationDelay, setAnimationClass } from "@/utils";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { getPlaylistCategory } from '@/api/home';
|
||||
import type { IPlayListSort } from '@/type/playlist';
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
// 歌单分类
|
||||
const playlistCategory = ref<IPlayListSort>();
|
||||
// 是否显示全部歌单分类
|
||||
@@ -41,39 +39,39 @@ const isShowAllPlaylistCategory = ref<boolean>(false);
|
||||
|
||||
// 加载歌单分类
|
||||
const loadPlaylistCategory = async () => {
|
||||
const { data } = await getPlaylistCategory();
|
||||
playlistCategory.value = data;
|
||||
const { data } = await getPlaylistCategory();
|
||||
playlistCategory.value = data;
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const handleClickPlaylistType = (type: any) => {
|
||||
router.push({
|
||||
path: "/list",
|
||||
query: {
|
||||
type: type,
|
||||
}
|
||||
});
|
||||
router.push({
|
||||
path: '/list',
|
||||
query: {
|
||||
type,
|
||||
},
|
||||
});
|
||||
};
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadPlaylistCategory();
|
||||
loadPlaylistCategory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
.play-list-type {
|
||||
width: 250px;
|
||||
@apply mx-6;
|
||||
&-item,
|
||||
&-showall {
|
||||
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 transition;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
&-showall {
|
||||
@apply block text-center;
|
||||
}
|
||||
width: 250px;
|
||||
@apply mx-6;
|
||||
&-item,
|
||||
&-showall {
|
||||
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 transition;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
&-showall {
|
||||
@apply block text-center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
<template>
|
||||
<div class="recommend-album">
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">最新专辑</div>
|
||||
<div class="recommend-album-list">
|
||||
<template v-for="(item,index) in albumData?.albums" :key="item.id">
|
||||
<div
|
||||
v-if="index < 6"
|
||||
class="recommend-album-list-item"
|
||||
:class="setAnimationClass('animate__backInUp')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<n-image
|
||||
class="recommend-album-list-item-img"
|
||||
:src="getImgUrl( item.blurPicUrl, '200y200')"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div class="recommend-album-list-item-content">{{ item.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="recommend-album">
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">最新专辑</div>
|
||||
<div class="recommend-album-list">
|
||||
<template v-for="(item, index) in albumData?.albums" :key="item.id">
|
||||
<div
|
||||
v-if="index < 6"
|
||||
class="recommend-album-list-item"
|
||||
:class="setAnimationClass('animate__backInUp')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<n-image
|
||||
class="recommend-album-list-item-img"
|
||||
:src="getImgUrl(item.blurPicUrl, '200y200')"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div class="recommend-album-list-item-content">{{ item.name }}</div>
|
||||
</div>
|
||||
<MusicList v-model:show="showMusic" :name="albumName" :song-list="songList" />
|
||||
</template>
|
||||
</div>
|
||||
<MusicList v-model:show="showMusic" :name="albumName" :song-list="songList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getNewAlbum } from "@/api/home"
|
||||
import { ref, onMounted } from "vue";
|
||||
import type { IAlbumNew } from "@/type/album"
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl } from "@/utils";
|
||||
import { getAlbum } from "@/api/list";
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { getNewAlbum } from '@/api/home';
|
||||
import { getAlbum } from '@/api/list';
|
||||
import type { IAlbumNew } from '@/type/album';
|
||||
import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
const albumData = ref<IAlbumNew>()
|
||||
const albumData = ref<IAlbumNew>();
|
||||
const loadAlbumList = async () => {
|
||||
const { data } = await getNewAlbum();
|
||||
albumData.value = data
|
||||
}
|
||||
const { data } = await getNewAlbum();
|
||||
albumData.value = data;
|
||||
};
|
||||
|
||||
const showMusic = ref(false)
|
||||
const songList = ref([])
|
||||
const albumName = ref('')
|
||||
const showMusic = ref(false);
|
||||
const songList = ref([]);
|
||||
const albumName = ref('');
|
||||
|
||||
const handleClick = async (item:any) => {
|
||||
albumName.value = item.name
|
||||
showMusic.value = true
|
||||
const res = await getAlbum(item.id)
|
||||
songList.value = res.data.songs.map((song:any)=>{
|
||||
song.al.picUrl = song.al.picUrl || item.picUrl
|
||||
return song
|
||||
})
|
||||
}
|
||||
const handleClick = async (item: any) => {
|
||||
albumName.value = item.name;
|
||||
showMusic.value = true;
|
||||
const res = await getAlbum(item.id);
|
||||
songList.value = res.data.songs.map((song: any) => {
|
||||
song.al.picUrl = song.al.picUrl || item.picUrl;
|
||||
return song;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadAlbumList()
|
||||
})
|
||||
loadAlbumList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recommend-album {
|
||||
@apply flex-1 mx-5;
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
@apply flex-1 mx-5;
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
|
||||
.recommend-album-list {
|
||||
@apply grid grid-cols-2 grid-rows-3 gap-2;
|
||||
&-item {
|
||||
@apply rounded-xl overflow-hidden relative;
|
||||
&-img {
|
||||
@apply rounded-xl transition w-full h-full;
|
||||
}
|
||||
&:hover img {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
&-content {
|
||||
@apply w-full h-full opacity-0 transition absolute z-10 top-0 left-0 p-4 text-xl bg-opacity-60 bg-black;
|
||||
}
|
||||
&-content:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.recommend-album-list {
|
||||
@apply grid grid-cols-2 grid-rows-3 gap-2;
|
||||
&-item {
|
||||
@apply rounded-xl overflow-hidden relative;
|
||||
&-img {
|
||||
@apply rounded-xl transition w-full h-full;
|
||||
}
|
||||
&:hover img {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
&-content {
|
||||
@apply w-full h-full opacity-0 transition absolute z-10 top-0 left-0 p-4 text-xl bg-opacity-60 bg-black;
|
||||
}
|
||||
&-content:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,85 +1,79 @@
|
||||
<template>
|
||||
<!-- 推荐歌手 -->
|
||||
<div class="recommend-singer">
|
||||
<div class="recommend-singer-list">
|
||||
<div
|
||||
class="recommend-singer-item relative"
|
||||
:class="setAnimationClass('animate__backInRight')"
|
||||
v-for="(item, index) in hotSingerData?.artists"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
:key="item.id"
|
||||
>
|
||||
<div
|
||||
:style="setBackgroundImg(getImgUrl(item.picUrl,'300y300'))"
|
||||
class="recommend-singer-item-bg"
|
||||
></div>
|
||||
<div
|
||||
class="recommend-singer-item-count p-2 text-base text-gray-200 z-10"
|
||||
>{{ item.musicSize }}首</div>
|
||||
<div class="recommend-singer-item-info z-10">
|
||||
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">
|
||||
<i class="iconfont icon-playfill text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 推荐歌手 -->
|
||||
<div class="recommend-singer">
|
||||
<div class="recommend-singer-list">
|
||||
<div
|
||||
v-for="(item, index) in hotSingerData?.artists"
|
||||
:key="item.id"
|
||||
class="recommend-singer-item relative"
|
||||
:class="setAnimationClass('animate__backInRight')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
>
|
||||
<div :style="setBackgroundImg(getImgUrl(item.picUrl, '300y300'))" class="recommend-singer-item-bg"></div>
|
||||
<div class="recommend-singer-item-count p-2 text-base text-gray-200 z-10">{{ item.musicSize }}首</div>
|
||||
<div class="recommend-singer-item-info z-10">
|
||||
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">
|
||||
<i class="iconfont icon-playfill text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { setBackgroundImg, setAnimationDelay, setAnimationClass,getImgUrl } from "@/utils";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getHotSinger } from "@/api/home";
|
||||
import type { IHotSinger } from "@/type/singer";
|
||||
import router from "@/router";
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { getHotSinger } from '@/api/home';
|
||||
import router from '@/router';
|
||||
import type { IHotSinger } from '@/type/singer';
|
||||
import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils';
|
||||
|
||||
// 歌手信息
|
||||
const hotSingerData = ref<IHotSinger>();
|
||||
|
||||
//加载推荐歌手
|
||||
// 加载推荐歌手
|
||||
const loadSingerList = async () => {
|
||||
const { data } = await getHotSinger({ offset: 0, limit: 5 });
|
||||
hotSingerData.value = data;
|
||||
const { data } = await getHotSinger({ offset: 0, limit: 5 });
|
||||
hotSingerData.value = data;
|
||||
};
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadSingerList();
|
||||
loadSingerList();
|
||||
});
|
||||
|
||||
|
||||
const toSearchSinger = (keyword: string) => {
|
||||
router.push({
|
||||
path: "/search",
|
||||
query: {
|
||||
keyword: keyword,
|
||||
},
|
||||
});
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
keyword,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recommend-singer {
|
||||
&-list {
|
||||
@apply flex;
|
||||
height: 280px;
|
||||
&-list {
|
||||
@apply flex;
|
||||
height: 280px;
|
||||
}
|
||||
&-item {
|
||||
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
|
||||
&-bg {
|
||||
@apply bg-gray-900 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
&-item {
|
||||
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
|
||||
&-bg {
|
||||
@apply bg-gray-900 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
&-info {
|
||||
@apply flex items-center p-2;
|
||||
&-play {
|
||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer;
|
||||
}
|
||||
}
|
||||
&-info {
|
||||
@apply flex items-center p-2;
|
||||
&-play {
|
||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
<template>
|
||||
<div class="recommend-music">
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">
|
||||
本周最热音乐
|
||||
</div>
|
||||
<div
|
||||
class="recommend-music-list"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
v-show="recommendMusic?.result"
|
||||
>
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">本周最热音乐</div>
|
||||
<div v-show="recommendMusic?.result" class="recommend-music-list" :class="setAnimationClass('animate__bounceInUp')">
|
||||
<!-- 推荐音乐列表 -->
|
||||
<template v-for="(item, index) in recommendMusic?.result" :key="item.id">
|
||||
<div
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
>
|
||||
<div :class="setAnimationClass('animate__bounceInUp')" :style="setAnimationDelay(index, 100)">
|
||||
<song-item :item="item" @play="handlePlay" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,30 +13,32 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getRecommendMusic } from '@/api/home'
|
||||
import type { IRecommendMusic } from '@/type/music'
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils'
|
||||
import SongItem from './common/SongItem.vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { getRecommendMusic } from '@/api/home';
|
||||
import type { IRecommendMusic } from '@/type/music';
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
import SongItem from './common/SongItem.vue';
|
||||
|
||||
const store = useStore();
|
||||
// 推荐歌曲
|
||||
const recommendMusic = ref<IRecommendMusic>()
|
||||
const recommendMusic = ref<IRecommendMusic>();
|
||||
|
||||
// 加载推荐歌曲
|
||||
const loadRecommendMusic = async () => {
|
||||
const { data } = await getRecommendMusic({ limit: 10 })
|
||||
recommendMusic.value = data
|
||||
}
|
||||
const { data } = await getRecommendMusic({ limit: 10 });
|
||||
recommendMusic.value = data;
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadRecommendMusic()
|
||||
})
|
||||
loadRecommendMusic();
|
||||
});
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
store.commit('setPlayList', recommendMusic.value?.result)
|
||||
}
|
||||
const handlePlay = () => {
|
||||
store.commit('setPlayList', recommendMusic.value?.result);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
<script lang="ts" setup>
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
|
||||
const props = defineProps({
|
||||
showPop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
})
|
||||
|
||||
const musicFullClass = computed(() => {
|
||||
if (props.showPop) {
|
||||
return setAnimationClass('animate__fadeInUp')
|
||||
} else {
|
||||
return setAnimationClass('animate__fadeOutDown')
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pop-page" v-show="props.showPop" :class="musicFullClass">
|
||||
<i class="iconfont icon-icon_error close" v-if="props.showClose"></i>
|
||||
<img src="http://code.myalger.top/2000*2000.jpg,f054f0,0f2255" />
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pop-page {
|
||||
height: 800px;
|
||||
@apply absolute top-4 left-0 w-full;
|
||||
background-color: #000000f0;
|
||||
.close {
|
||||
@apply absolute top-4 right-4 cursor-pointer text-white text-3xl;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script lang="ts" setup>
|
||||
import { setAnimationClass } from '@/utils';
|
||||
|
||||
const props = defineProps({
|
||||
showPop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const musicFullClass = computed(() => {
|
||||
if (props.showPop) {
|
||||
return setAnimationClass('animate__fadeInUp');
|
||||
}
|
||||
return setAnimationClass('animate__fadeOutDown');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-show="props.showPop" class="pop-page" :class="musicFullClass">
|
||||
<i v-if="props.showClose" class="iconfont icon-icon_error close"></i>
|
||||
<img src="http://code.myalger.top/2000*2000.jpg,f054f0,0f2255" />
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pop-page {
|
||||
height: 800px;
|
||||
@apply absolute top-4 left-0 w-full;
|
||||
background-color: #000000f0;
|
||||
.close {
|
||||
@apply absolute top-4 right-4 cursor-pointer text-white text-3xl;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<div class="bottom" v-if="isPlay"></div>
|
||||
<div v-if="isPlay" class="bottom"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStore } from 'vuex';
|
||||
const store = useStore()
|
||||
const isPlay = computed(() => store.state.isPlay as boolean)
|
||||
|
||||
const store = useStore();
|
||||
const isPlay = computed(() => store.state.isPlay as boolean);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bottom{
|
||||
.bottom {
|
||||
@apply h-28;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<div class="search-item" @click="handleClick">
|
||||
<div class="search-item-img">
|
||||
<n-image
|
||||
:src="getImgUrl(item.picUrl, 'album')"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<n-image :src="getImgUrl(item.picUrl, 'album')" lazy preview-disabled />
|
||||
</div>
|
||||
<div class="search-item-info">
|
||||
<div class="search-item-name">{{ item.name }}</div>
|
||||
<div class="search-item-artist">{{ item.desc}}</div>
|
||||
<div class="search-item-artist">{{ item.desc }}</div>
|
||||
</div>
|
||||
|
||||
<MusicList v-model:show="showMusic" :name="item.name" :song-list="songList" />
|
||||
@@ -17,54 +13,49 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getImgUrl } from '@/utils'
|
||||
import type {Album} from '@/type/album'
|
||||
import { getAlbum } from '@/api/list';
|
||||
import { getImgUrl } from '@/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
picUrl: string
|
||||
name: string
|
||||
desc: string
|
||||
type: string
|
||||
[key: string]: any
|
||||
}
|
||||
}>()
|
||||
picUrl: string;
|
||||
name: string;
|
||||
desc: string;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}>();
|
||||
|
||||
const songList = ref([])
|
||||
const songList = ref([]);
|
||||
|
||||
const showMusic = ref(false)
|
||||
const showMusic = ref(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
showMusic.value = true
|
||||
if(props.item.type === '专辑'){
|
||||
const res = await getAlbum(props.item.id)
|
||||
songList.value = res.data.songs.map((song:any)=>{
|
||||
song.al.picUrl = song.al.picUrl || props.item.picUrl
|
||||
return song
|
||||
})
|
||||
showMusic.value = true;
|
||||
if (props.item.type === '专辑') {
|
||||
const res = await getAlbum(props.item.id);
|
||||
songList.value = res.data.songs.map((song: any) => {
|
||||
song.al.picUrl = song.al.picUrl || props.item.picUrl;
|
||||
return song;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.search-item{
|
||||
.search-item {
|
||||
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition;
|
||||
margin: 0 10px;
|
||||
.search-item-img{
|
||||
.search-item-img {
|
||||
@apply w-12 h-12 mr-4 rounded-2xl overflow-hidden;
|
||||
}
|
||||
.search-item-info{
|
||||
&-name{
|
||||
.search-item-info {
|
||||
&-name {
|
||||
@apply text-white text-sm text-center;
|
||||
}
|
||||
&-artist{
|
||||
&-artist {
|
||||
@apply text-gray-400 text-xs text-center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
<template>
|
||||
<div class="song-item" :class="{'song-mini': mini}">
|
||||
<n-image
|
||||
v-if="item.picUrl "
|
||||
:src="getImgUrl( item.picUrl, '40y40')"
|
||||
class="song-item-img"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div class="song-item" :class="{ 'song-mini': mini }">
|
||||
<n-image v-if="item.picUrl" :src="getImgUrl(item.picUrl, '40y40')" class="song-item-img" lazy preview-disabled />
|
||||
<div class="song-item-content">
|
||||
<div class="song-item-content-title">
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">{{
|
||||
item.name
|
||||
}}</n-ellipsis>
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">{{ item.name }}</n-ellipsis>
|
||||
</div>
|
||||
<div class="song-item-content-name">
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">
|
||||
<span
|
||||
v-for="(artists, artistsindex) in item.song.artists"
|
||||
:key="artistsindex"
|
||||
>{{ artists.name
|
||||
}}{{
|
||||
artistsindex < item.song.artists.length - 1 ? ' / ' : ''
|
||||
}}</span
|
||||
<span v-for="(artists, artistsindex) in item.song.artists" :key="artistsindex"
|
||||
>{{ artists.name }}{{ artistsindex < item.song.artists.length - 1 ? ' / ' : '' }}</span
|
||||
>
|
||||
</n-ellipsis>
|
||||
</div>
|
||||
@@ -43,40 +30,43 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from 'vuex'
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { getImgUrl } from '@/utils'
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
item: SongResult
|
||||
mini?: boolean
|
||||
}>(), {
|
||||
mini: false
|
||||
})
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { getImgUrl } from '@/utils';
|
||||
|
||||
const store = useStore()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
item: SongResult;
|
||||
mini?: boolean;
|
||||
}>(),
|
||||
{
|
||||
mini: false,
|
||||
},
|
||||
);
|
||||
|
||||
const play = computed(() => store.state.play as boolean)
|
||||
const store = useStore();
|
||||
|
||||
const playMusic = computed(() => store.state.playMusic)
|
||||
const play = computed(() => store.state.play as boolean);
|
||||
|
||||
const playMusic = computed(() => store.state.playMusic);
|
||||
|
||||
// 判断是否为正在播放的音乐
|
||||
const isPlaying = computed(() => {
|
||||
return playMusic.value.id == props.item.id
|
||||
})
|
||||
return playMusic.value.id === props.item.id;
|
||||
});
|
||||
|
||||
const emits = defineEmits(['play'])
|
||||
const emits = defineEmits(['play']);
|
||||
|
||||
// 播放音乐 设置音乐详情 打开音乐底栏
|
||||
const playMusicEvent = (item: any) => {
|
||||
store.commit('setPlay', item)
|
||||
store.commit('setIsPlay', true)
|
||||
emits('play', item)
|
||||
}
|
||||
store.commit('setPlay', item);
|
||||
store.commit('setIsPlay', true);
|
||||
emits('play', item);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
// 配置文字不可选中
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
@@ -119,31 +109,31 @@ const playMusicEvent = (item: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
.song-mini{
|
||||
.song-mini {
|
||||
@apply p-2 rounded-2xl;
|
||||
.song-item{
|
||||
.song-item {
|
||||
@apply p-0;
|
||||
&-img{
|
||||
&-img {
|
||||
@apply w-10 h-10 mr-2;
|
||||
}
|
||||
&-content{
|
||||
&-content {
|
||||
@apply flex-1;
|
||||
&-title{
|
||||
&-title {
|
||||
@apply text-sm;
|
||||
}
|
||||
&-name{
|
||||
&-name {
|
||||
@apply text-xs;
|
||||
}
|
||||
}
|
||||
&-operating{
|
||||
&-operating {
|
||||
@apply pl-2;
|
||||
.iconfont{
|
||||
.iconfont {
|
||||
@apply text-base;
|
||||
}
|
||||
&-like{
|
||||
&-like {
|
||||
@apply mr-1;
|
||||
}
|
||||
&-play{
|
||||
&-play {
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const USER_SET_OPTIONS = [
|
||||
label: '设置',
|
||||
key: 'set',
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
export const SEARCH_TYPES = [
|
||||
{
|
||||
@@ -62,4 +62,4 @@ export const SEARCH_TYPES = [
|
||||
label: '综合',
|
||||
key: 1018,
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
10
src/electron.d.ts
vendored
10
src/electron.d.ts
vendored
@@ -1,10 +1,10 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
minimize: () => void
|
||||
maximize: () => void
|
||||
close: () => void
|
||||
dragStart: () => void
|
||||
}
|
||||
minimize: () => void;
|
||||
maximize: () => void;
|
||||
close: () => void;
|
||||
dragStart: () => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
// musicHistoryHooks
|
||||
import { RemovableRef, useLocalStorage } from '@vueuse/core'
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
|
||||
import type { SongResult } from '@/type/music';
|
||||
|
||||
export const useMusicHistory = () => {
|
||||
const musicHistory = useLocalStorage<SongResult[]>('musicHistory', [])
|
||||
const musicHistory = useLocalStorage<SongResult[]>('musicHistory', []);
|
||||
|
||||
const addMusic = (music: SongResult) => {
|
||||
const index = musicHistory.value.findIndex((item) => item.id === music.id)
|
||||
const index = musicHistory.value.findIndex((item) => item.id === music.id);
|
||||
if (index !== -1) {
|
||||
musicHistory.value[index].count =
|
||||
(musicHistory.value[index].count || 0) + 1
|
||||
musicHistory.value.unshift(musicHistory.value.splice(index, 1)[0])
|
||||
musicHistory.value[index].count = (musicHistory.value[index].count || 0) + 1;
|
||||
musicHistory.value.unshift(musicHistory.value.splice(index, 1)[0]);
|
||||
} else {
|
||||
musicHistory.value.unshift({ ...music, count: 1 })
|
||||
musicHistory.value.unshift({ ...music, count: 1 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const delMusic = (music: any) => {
|
||||
const index = musicHistory.value.findIndex((item) => item.id === music.id)
|
||||
const index = musicHistory.value.findIndex((item) => item.id === music.id);
|
||||
if (index !== -1) {
|
||||
musicHistory.value.splice(index, 1)
|
||||
musicHistory.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
const musicList = ref(musicHistory.value)
|
||||
};
|
||||
const musicList = ref(musicHistory.value);
|
||||
watch(
|
||||
() => musicHistory.value,
|
||||
() => {
|
||||
musicList.value = musicHistory.value
|
||||
}
|
||||
)
|
||||
musicList.value = musicHistory.value;
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
musicHistory,
|
||||
musicList,
|
||||
addMusic,
|
||||
delMusic,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,104 +1,169 @@
|
||||
import { getMusicLrc } from '@/api/music'
|
||||
import { ILyric } from '@/type/lyric'
|
||||
import { getIsMc } from '@/utils'
|
||||
import { getMusicLrc } from '@/api/music';
|
||||
import { ILyric } from '@/type/lyric';
|
||||
|
||||
interface ILrcData {
|
||||
text: string
|
||||
trText: string
|
||||
text: string;
|
||||
trText: string;
|
||||
}
|
||||
|
||||
const lrcData = ref<ILyric>()
|
||||
const newLrcIndex = ref<number>(0)
|
||||
const lrcArray = ref<Array<ILrcData>>([])
|
||||
const lrcTimeArray = ref<Array<Number>>([])
|
||||
export const lrcData = ref<ILyric>();
|
||||
export const newLrcIndex = ref<number>(0);
|
||||
export const lrcArray = ref<Array<ILrcData>>([]);
|
||||
export const lrcTimeArray = ref<Array<Number>>([]);
|
||||
|
||||
const parseTime = (timeString: string) => {
|
||||
const [minutes, seconds] = timeString.split(':')
|
||||
return parseInt(minutes) * 60 + parseFloat(seconds)
|
||||
}
|
||||
export const parseTime = (timeString: string) => {
|
||||
const [minutes, seconds] = timeString.split(':');
|
||||
return Number(minutes) * 60 + Number(seconds);
|
||||
};
|
||||
|
||||
const TIME_REGEX = /(\d{2}:\d{2}(\.\d*)?)/g
|
||||
const LRC_REGEX = /(\[(\d{2}):(\d{2})(\.(\d*))?\])/g
|
||||
const TIME_REGEX = /(\d{2}:\d{2}(\.\d*)?)/g;
|
||||
const LRC_REGEX = /(\[(\d{2}):(\d{2})(\.(\d*))?\])/g;
|
||||
|
||||
function parseLyricLine(lyricLine: string) {
|
||||
// [00:00.00] 作词 : 长友美知惠/
|
||||
const timeText = lyricLine.match(TIME_REGEX)?.[0] || ''
|
||||
const time = parseTime(timeText)
|
||||
const text = lyricLine.replace(LRC_REGEX, '').trim()
|
||||
return { time, text }
|
||||
const timeText = lyricLine.match(TIME_REGEX)?.[0] || '';
|
||||
const time = parseTime(timeText);
|
||||
const text = lyricLine.replace(LRC_REGEX, '').trim();
|
||||
return { time, text };
|
||||
}
|
||||
|
||||
interface ILyricText {
|
||||
text: string
|
||||
trText: string
|
||||
text: string;
|
||||
trText: string;
|
||||
}
|
||||
|
||||
function parseLyrics(lyricsString: string) {
|
||||
const lines = lyricsString.split('\n')
|
||||
const lyrics: Array<ILyricText> = []
|
||||
const times: number[] = []
|
||||
const lines = lyricsString.split('\n');
|
||||
const lyrics: Array<ILyricText> = [];
|
||||
const times: number[] = [];
|
||||
lines.forEach((line) => {
|
||||
const { time, text } = parseLyricLine(line)
|
||||
times.push(time)
|
||||
lyrics.push({ text, trText: '' })
|
||||
})
|
||||
return { lyrics, times }
|
||||
const { time, text } = parseLyricLine(line);
|
||||
times.push(time);
|
||||
lyrics.push({ text, trText: '' });
|
||||
});
|
||||
return { lyrics, times };
|
||||
}
|
||||
|
||||
const loadLrc = async (playMusicId: number): Promise<void> => {
|
||||
export const loadLrc = async (playMusicId: number): Promise<void> => {
|
||||
try {
|
||||
const { data } = await getMusicLrc(playMusicId)
|
||||
const { lyrics, times } = parseLyrics(data.lrc.lyric)
|
||||
lrcTimeArray.value = times
|
||||
lrcArray.value = lyrics
|
||||
const { data } = await getMusicLrc(playMusicId);
|
||||
const { lyrics, times } = parseLyrics(data.lrc.lyric);
|
||||
let tlyric: {
|
||||
[key: string]: string;
|
||||
} = {};
|
||||
if (data.tlyric.lyric) {
|
||||
const { lyrics: tLyrics, times: tTimes } = parseLyrics(data.tlyric.lyric);
|
||||
tlyric = tLyrics.reduce((acc: any, cur, index) => {
|
||||
acc[tTimes[index]] = cur.text;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (Object.keys(tlyric).length) {
|
||||
lyrics.forEach((item, index) => {
|
||||
item.trText = item.text ? tlyric[times[index].toString()] : '';
|
||||
});
|
||||
}
|
||||
lrcTimeArray.value = times;
|
||||
lrcArray.value = lyrics;
|
||||
} catch (err) {
|
||||
console.error('err', err)
|
||||
console.error('err', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 歌词矫正时间Correction time
|
||||
const correctionTime = ref(0.4)
|
||||
const correctionTime = ref(0.4);
|
||||
|
||||
// 增加矫正时间
|
||||
const addCorrectionTime = (time: number) => {
|
||||
correctionTime.value += time
|
||||
}
|
||||
export const addCorrectionTime = (time: number) => {
|
||||
correctionTime.value += time;
|
||||
};
|
||||
|
||||
// 减少矫正时间
|
||||
const reduceCorrectionTime = (time: number) => {
|
||||
correctionTime.value -= time
|
||||
}
|
||||
export const reduceCorrectionTime = (time: number) => {
|
||||
correctionTime.value -= time;
|
||||
};
|
||||
|
||||
const isCurrentLrc = (index: any, time: number) => {
|
||||
const currentTime = Number(lrcTimeArray.value[index])
|
||||
const nextTime = Number(lrcTimeArray.value[index + 1])
|
||||
const nowTime = time + correctionTime.value
|
||||
const isTrue = nowTime > currentTime && nowTime < nextTime
|
||||
export const isCurrentLrc = (index: any, time: number) => {
|
||||
const currentTime = Number(lrcTimeArray.value[index]);
|
||||
const nextTime = Number(lrcTimeArray.value[index + 1]);
|
||||
const nowTime = time + correctionTime.value;
|
||||
const isTrue = nowTime > currentTime && nowTime < nextTime;
|
||||
if (isTrue) {
|
||||
newLrcIndex.value = index
|
||||
newLrcIndex.value = index;
|
||||
}
|
||||
return isTrue
|
||||
}
|
||||
return isTrue;
|
||||
};
|
||||
|
||||
const nowTime = ref(0)
|
||||
const allTime = ref(0)
|
||||
export const nowTime = ref(0);
|
||||
export const allTime = ref(0);
|
||||
export const nowIndex = ref(0);
|
||||
|
||||
export const getLrcIndex = (time: number) => {
|
||||
for (let i = 0; i < lrcTimeArray.value.length; i++) {
|
||||
if (isCurrentLrc(i, time)) {
|
||||
nowIndex.value = i || nowIndex.value;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return nowIndex.value;
|
||||
};
|
||||
|
||||
// 设置当前播放时间
|
||||
const setAudioTime = (index: any, audio: HTMLAudioElement) => {
|
||||
audio.currentTime = lrcTimeArray.value[index] as number
|
||||
audio.play()
|
||||
}
|
||||
export const setAudioTime = (index: any, audio: HTMLAudioElement) => {
|
||||
audio.currentTime = lrcTimeArray.value[index] as number;
|
||||
audio.play();
|
||||
};
|
||||
|
||||
export {
|
||||
lrcData,
|
||||
lrcArray,
|
||||
lrcTimeArray,
|
||||
newLrcIndex,
|
||||
loadLrc,
|
||||
isCurrentLrc,
|
||||
addCorrectionTime,
|
||||
reduceCorrectionTime,
|
||||
setAudioTime,
|
||||
nowTime,
|
||||
allTime,
|
||||
}
|
||||
// 计算这个歌词的播放时间
|
||||
const getLrcTime = (index: any) => {
|
||||
return Number(lrcTimeArray.value[index]);
|
||||
};
|
||||
|
||||
// 获取当前播放的歌词
|
||||
export const getCurrentLrc = () => {
|
||||
const index = getLrcIndex(nowTime.value);
|
||||
const currentLrc = lrcArray.value[index];
|
||||
const nextLrc = lrcArray.value[index + 1];
|
||||
return { currentLrc, nextLrc };
|
||||
};
|
||||
|
||||
// 获取一句歌词播放时间是 几秒到几秒
|
||||
export const getLrcTimeRange = (index: any) => {
|
||||
const currentTime = Number(lrcTimeArray.value[index]);
|
||||
const nextTime = Number(lrcTimeArray.value[index + 1]);
|
||||
return { currentTime, nextTime };
|
||||
};
|
||||
|
||||
export const sendLyricToWin = (isPlay: boolean) => {
|
||||
try {
|
||||
// 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间
|
||||
let lyricWinData = null;
|
||||
if (lrcArray.value.length > 0) {
|
||||
const nowIndex = getLrcIndex(nowTime.value);
|
||||
const { currentLrc, nextLrc } = getCurrentLrc();
|
||||
const { currentTime, nextTime } = getLrcTimeRange(nowIndex);
|
||||
lyricWinData = {
|
||||
currentLrc,
|
||||
nextLrc,
|
||||
currentTime,
|
||||
nextTime,
|
||||
nowIndex,
|
||||
lrcTimeArray: lrcTimeArray.value,
|
||||
lrcArray: lrcArray.value,
|
||||
nowTime: nowTime.value,
|
||||
allTime: allTime.value,
|
||||
startCurrentTime: getLrcTime(nowIndex),
|
||||
isPlay,
|
||||
};
|
||||
|
||||
const windowData = window as any;
|
||||
windowData.electronAPI.sendLyric(JSON.stringify(lyricWinData));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const openLyric = () => {
|
||||
const windowData = window as any;
|
||||
windowData.electronAPI.openLyric();
|
||||
};
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
.n-image img {
|
||||
background-color: #111111;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +1,147 @@
|
||||
<template>
|
||||
<div class="layout-page">
|
||||
<div class="layout-main">
|
||||
<title-bar v-if="isElectron" />
|
||||
<div class="layout-main-page" :class="isElectron ? '' : 'pt-6'">
|
||||
<!-- 侧边菜单栏 -->
|
||||
<app-menu class="menu" :menus="menus" />
|
||||
<div class="main">
|
||||
<!-- 搜索栏 -->
|
||||
<search-bar />
|
||||
<!-- 主页面路由 -->
|
||||
<div class="main-content bg-black pb-" :native-scrollbar="false" :class="isPlay ? 'pb-20' : ''">
|
||||
<n-message-provider>
|
||||
<router-view class="main-page" v-slot="{ Component }" :class="route.meta.noScroll? 'pr-3' : ''">
|
||||
<template v-if="route.meta.noScroll">
|
||||
<keep-alive v-if="!route.meta.noKeepAlive">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-scrollbar>
|
||||
<keep-alive v-if="!route.meta.noKeepAlive">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component"/>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
</router-view>
|
||||
</n-message-provider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部音乐播放 -->
|
||||
<play-bar v-if="isPlay" />
|
||||
<div class="layout-page">
|
||||
<div class="layout-main">
|
||||
<title-bar v-if="isElectron" />
|
||||
<div class="layout-main-page" :class="isElectron ? '' : 'pt-6'">
|
||||
<!-- 侧边菜单栏 -->
|
||||
<app-menu class="menu" :menus="menus" />
|
||||
<div class="main">
|
||||
<!-- 搜索栏 -->
|
||||
<search-bar />
|
||||
<!-- 主页面路由 -->
|
||||
<div class="main-content bg-black pb-" :native-scrollbar="false" :class="isPlay ? 'pb-20' : ''">
|
||||
<n-message-provider>
|
||||
<router-view v-slot="{ Component }" class="main-page" :class="route.meta.noScroll ? 'pr-3' : ''">
|
||||
<template v-if="route.meta.noScroll">
|
||||
<keep-alive v-if="!route.meta.noKeepAlive">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else />
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-scrollbar>
|
||||
<keep-alive v-if="!route.meta.noKeepAlive">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else />
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
</router-view>
|
||||
</n-message-provider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部音乐播放 -->
|
||||
<play-bar v-if="isPlay" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const AppMenu = defineAsyncComponent(() => import('./components/AppMenu.vue'));
|
||||
const PlayBar = defineAsyncComponent(() => import('./components/PlayBar.vue'));
|
||||
const SearchBar = defineAsyncComponent(() => import('./components/SearchBar.vue'));
|
||||
const TitleBar = defineAsyncComponent(() => import('./components/TitleBar.vue'));
|
||||
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const isPlay = computed(() => store.state.isPlay as boolean)
|
||||
const menus = store.state.menus;
|
||||
const play = computed(() => store.state.play as boolean)
|
||||
const isPlay = computed(() => store.state.isPlay as boolean);
|
||||
const { menus } = store.state;
|
||||
const play = computed(() => store.state.play as boolean);
|
||||
|
||||
const route = useRoute()
|
||||
const route = useRoute();
|
||||
|
||||
const audio = {
|
||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement
|
||||
}
|
||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement,
|
||||
};
|
||||
|
||||
const windowData = window as any
|
||||
const windowData = window as any;
|
||||
const isElectron = computed(() => {
|
||||
return !!windowData.electronAPI
|
||||
})
|
||||
return !!windowData.electronAPI;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 监听音乐是否播放
|
||||
watch(
|
||||
() => play.value,
|
||||
value => {
|
||||
(value) => {
|
||||
if (value && audio.value) {
|
||||
audioPlay()
|
||||
audioPlay();
|
||||
} else {
|
||||
audioPause()
|
||||
audioPause();
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
document.onkeyup = (e) => {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
playMusicEvent()
|
||||
playMusicEvent();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
// 按下键盘按钮监听
|
||||
document.onkeydown = (e) => {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
return false
|
||||
return false;
|
||||
default:
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
const audioPlay = () => {
|
||||
if (audio.value) {
|
||||
audio.value.play()
|
||||
audio.value.play();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const audioPause = () => {
|
||||
if (audio.value) {
|
||||
audio.value.pause()
|
||||
audio.value.pause();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const playMusicEvent = async () => {
|
||||
if (play.value) {
|
||||
store.commit('setPlayMusic', false)
|
||||
store.commit('setPlayMusic', false);
|
||||
} else {
|
||||
store.commit('setPlayMusic', true)
|
||||
store.commit('setPlayMusic', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@apply flex justify-center items-center overflow-hidden;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@apply flex justify-center items-center overflow-hidden;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
@apply bg-black text-white shadow-xl flex flex-col relative;
|
||||
@apply bg-black text-white shadow-xl flex flex-col relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
&-page {
|
||||
@apply flex flex-1 overflow-hidden;
|
||||
}
|
||||
.menu {
|
||||
width: 90px;
|
||||
}
|
||||
.main {
|
||||
@apply flex-1 box-border flex flex-col;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
&-page{
|
||||
@apply flex flex-1 overflow-hidden;
|
||||
}
|
||||
.menu {
|
||||
width: 90px;
|
||||
}
|
||||
.main {
|
||||
@apply flex-1 box-border flex flex-col;
|
||||
height: 100%;
|
||||
&-content {
|
||||
@apply box-border flex-1 overflow-hidden;
|
||||
}
|
||||
}
|
||||
:deep(.n-scrollbar-content){
|
||||
@apply pr-3;
|
||||
&-content {
|
||||
@apply box-border flex-1 overflow-hidden;
|
||||
}
|
||||
}
|
||||
:deep(.n-scrollbar-content) {
|
||||
@apply pr-3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -8,13 +8,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-menu-list">
|
||||
<div class="app-menu-item" v-for="(item,index) in menus">
|
||||
<div v-for="(item, index) in menus" :key="item.path" class="app-menu-item">
|
||||
<router-link class="app-menu-item-link" :to="item.path">
|
||||
<i
|
||||
class="iconfont app-menu-item-icon"
|
||||
:style="iconStyle(index)"
|
||||
:class="item.meta.icon"
|
||||
></i>
|
||||
<i class="iconfont app-menu-item-icon" :style="iconStyle(index)" :class="item.meta.icon"></i>
|
||||
<span v-if="isText" class="app-menu-item-text ml-3">{{ item.meta.title }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
@@ -24,44 +20,47 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const props = defineProps({
|
||||
isText: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '26px'
|
||||
default: '26px',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#aaa'
|
||||
default: '#aaa',
|
||||
},
|
||||
selectColor: {
|
||||
type: String,
|
||||
default: '#10B981'
|
||||
default: '#10B981',
|
||||
},
|
||||
menus: {
|
||||
type: Array as any,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const path = ref(route.path);
|
||||
watch(() => route.path, async newParams => {
|
||||
path.value = newParams
|
||||
})
|
||||
watch(
|
||||
() => route.path,
|
||||
async (newParams) => {
|
||||
path.value = newParams;
|
||||
},
|
||||
);
|
||||
|
||||
const iconStyle = (index: any) => {
|
||||
let style = {
|
||||
const style = {
|
||||
fontSize: props.size,
|
||||
color: path.value === props.menus[index].path ? props.selectColor : props.color
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
color: path.value === props.menus[index].path ? props.selectColor : props.color,
|
||||
};
|
||||
return style;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -83,4 +82,4 @@ const iconStyle = (index: any) => {
|
||||
transform: scale(1.05);
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
<template>
|
||||
<n-drawer
|
||||
:show="musicFull"
|
||||
height="100vh"
|
||||
placement="bottom"
|
||||
:drawer-style="{ backgroundColor: 'transparent' }"
|
||||
>
|
||||
<n-drawer :show="musicFull" height="100vh" placement="bottom" :drawer-style="{ backgroundColor: 'transparent' }">
|
||||
<div id="drawer-target">
|
||||
<div class="drawer-back" :class="{'paused': !isPlaying}" :style="{backgroundImage:`url(${getImgUrl(playMusic?.picUrl, '300y300')})`}"></div>
|
||||
<div
|
||||
class="drawer-back"
|
||||
:class="{ paused: !isPlaying }"
|
||||
:style="{ backgroundImage: `url(${getImgUrl(playMusic?.picUrl, '300y300')})` }"
|
||||
></div>
|
||||
<div class="music-img">
|
||||
<n-image
|
||||
ref="PicImgRef"
|
||||
:src="getImgUrl(playMusic?.picUrl, '300y300')"
|
||||
class="img"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
||||
</div>
|
||||
<div class="music-content">
|
||||
<div class="music-content-name">{{ playMusic.song.name }}</div>
|
||||
<div class="music-content-singer">
|
||||
<span v-for="(item, index) in playMusic.song.artists" :key="index">
|
||||
{{ item.name
|
||||
}}{{ index < playMusic.song.artists.length - 1 ? ' / ' : '' }}
|
||||
{{ item.name }}{{ index < playMusic.song.artists.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
<n-layout
|
||||
ref="lrcSider"
|
||||
class="music-lrc"
|
||||
style="height: 55vh"
|
||||
ref="lrcSider"
|
||||
:native-scrollbar="false"
|
||||
@mouseover="mouseOverLayout"
|
||||
@mouseleave="mouseLeaveLayout"
|
||||
@@ -38,7 +30,8 @@
|
||||
:class="{ 'now-text': isCurrentLrc(index, nowTime) }"
|
||||
@click="setAudioTime(index, audio)"
|
||||
>
|
||||
{{ item.text }}
|
||||
<div>{{ item.text }}</div>
|
||||
<div class="music-lrc-text-tr">{{ item.trText }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-layout>
|
||||
@@ -53,20 +46,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { getImgUrl } from '@/utils'
|
||||
import { useStore } from 'vuex'
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import {
|
||||
addCorrectionTime,
|
||||
isCurrentLrc,
|
||||
lrcArray,
|
||||
newLrcIndex,
|
||||
isCurrentLrc,
|
||||
addCorrectionTime,
|
||||
nowTime,
|
||||
reduceCorrectionTime,
|
||||
setAudioTime,
|
||||
nowTime,
|
||||
} from '@/hooks/MusicHook'
|
||||
} from '@/hooks/MusicHook';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { getImgUrl } from '@/utils';
|
||||
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
const props = defineProps({
|
||||
musicFull: {
|
||||
@@ -77,39 +71,36 @@ const props = defineProps({
|
||||
type: HTMLAudioElement,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:musicFull'])
|
||||
});
|
||||
|
||||
// 播放的音乐信息
|
||||
const playMusic = computed(() => store.state.playMusic as SongResult)
|
||||
const isPlaying = computed(() => store.state.play as boolean)
|
||||
const playMusic = computed(() => store.state.playMusic as SongResult);
|
||||
const isPlaying = computed(() => store.state.play as boolean);
|
||||
// 获取歌词滚动dom
|
||||
const lrcSider = ref<any>(null)
|
||||
const isMouse = ref(false)
|
||||
const lrcSider = ref<any>(null);
|
||||
const isMouse = ref(false);
|
||||
// 歌词滚动方法
|
||||
const lrcScroll = () => {
|
||||
if (props.musicFull && !isMouse.value) {
|
||||
let top = newLrcIndex.value * 50 - 225
|
||||
lrcSider.value.scrollTo({ top: top, behavior: 'smooth' })
|
||||
const top = newLrcIndex.value * 60 - 225;
|
||||
lrcSider.value.scrollTo({ top, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
};
|
||||
const mouseOverLayout = () => {
|
||||
isMouse.value = true
|
||||
}
|
||||
isMouse.value = true;
|
||||
};
|
||||
const mouseLeaveLayout = () => {
|
||||
setTimeout(() => {
|
||||
isMouse.value = false
|
||||
}, 3000)
|
||||
}
|
||||
isMouse.value = false;
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
lrcScroll,
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@keyframes round {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@@ -118,7 +109,7 @@ defineExpose({
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.drawer-back{
|
||||
.drawer-back {
|
||||
@apply absolute bg-cover bg-center opacity-70;
|
||||
filter: blur(80px) brightness(80%);
|
||||
z-index: -1;
|
||||
@@ -161,28 +152,30 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
.music-content-time{
|
||||
.music-content-time {
|
||||
display: none;
|
||||
@apply flex justify-center items-center;
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
|
||||
.music-lrc {
|
||||
background-color: inherit;
|
||||
width: 500px;
|
||||
height: 550px;
|
||||
|
||||
.now-text {
|
||||
@apply text-red-500;
|
||||
}
|
||||
&-text {
|
||||
@apply text-white text-lg flex justify-center items-center cursor-pointer;
|
||||
height: 50px;
|
||||
@apply text-white text-lg flex flex-col justify-center items-center cursor-pointer font-bold;
|
||||
height: 60px;
|
||||
transition: all 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
@apply font-bold text-xl text-red-500;
|
||||
@apply font-bold text-red-500;
|
||||
}
|
||||
}
|
||||
|
||||
.now-text {
|
||||
@apply font-bold text-xl text-red-500;
|
||||
&-tr {
|
||||
@apply text-sm font-normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 展开全屏 -->
|
||||
<music-full ref="MusicFullRef" v-model:musicFull="musicFull" :audio="(audio.value as HTMLAudioElement)" />
|
||||
<music-full ref="MusicFullRef" v-model:musicFull="musicFull" :audio="audio.value as HTMLAudioElement" />
|
||||
<!-- 底部播放栏 -->
|
||||
<div class="music-play-bar" :class="setAnimationClass('animate__bounceInUp')">
|
||||
<n-image
|
||||
@@ -19,8 +19,7 @@
|
||||
<div class="music-content-name">
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">
|
||||
<span v-for="(item, index) in playMusic.song.artists" :key="index">
|
||||
{{ item.name
|
||||
}}{{ index < playMusic.song.artists.length - 1 ? ' / ' : '' }}
|
||||
{{ item.name }}{{ index < playMusic.song.artists.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
</n-ellipsis>
|
||||
</div>
|
||||
@@ -38,22 +37,14 @@
|
||||
</div>
|
||||
<div class="music-time">
|
||||
<div class="time">{{ getNowTime }}</div>
|
||||
<n-slider
|
||||
v-model:value="timeSlider"
|
||||
:step="0.05"
|
||||
:tooltip="false"
|
||||
></n-slider>
|
||||
<n-slider v-model:value="timeSlider" :step="0.05" :tooltip="false"></n-slider>
|
||||
<div class="time">{{ getAllTime }}</div>
|
||||
</div>
|
||||
<div class="audio-volume">
|
||||
<div>
|
||||
<i class="iconfont icon-notificationfill"></i>
|
||||
</div>
|
||||
<n-slider
|
||||
v-model:value="volumeSlider"
|
||||
:step="0.01"
|
||||
:tooltip="false"
|
||||
></n-slider>
|
||||
<n-slider v-model:value="volumeSlider" :step="0.01" :tooltip="false"></n-slider>
|
||||
</div>
|
||||
<div class="audio-button">
|
||||
<!-- <n-tooltip trigger="hover" :z-index="9999999">
|
||||
@@ -68,12 +59,12 @@
|
||||
</template>
|
||||
解析播放
|
||||
</n-tooltip> -->
|
||||
<!-- <n-tooltip trigger="hover" :z-index="9999999">
|
||||
<n-tooltip trigger="hover" :z-index="9999999">
|
||||
<template #trigger>
|
||||
<i class="iconfont icon-full" @click="setMusicFull"></i>
|
||||
<i class="iconfont ri-netease-cloud-music-line" @click="openLyric"></i>
|
||||
</template>
|
||||
歌词
|
||||
</n-tooltip> -->
|
||||
</n-tooltip>
|
||||
<n-popover trigger="click" :z-index="99999999" content-class="music-play" raw :show-arrow="false" :delay="200">
|
||||
<template #trigger>
|
||||
<n-tooltip trigger="manual" :z-index="9999999">
|
||||
@@ -87,154 +78,143 @@
|
||||
<div class="music-play-list-back"></div>
|
||||
<n-scrollbar>
|
||||
<div class="music-play-list-content">
|
||||
<song-item v-for="(item, index) in playList" :key="item.id" :item="item" mini></song-item>
|
||||
<song-item v-for="item in playList" :key="item.id" :item="item" mini></song-item>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</n-popover>
|
||||
</div>
|
||||
<!-- 播放音乐 -->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { secondToMinute, getImgUrl } from '@/utils'
|
||||
import { useStore } from 'vuex'
|
||||
import { setAnimationClass } from '@/utils'
|
||||
import {
|
||||
loadLrc,
|
||||
nowTime,
|
||||
allTime
|
||||
} from '@/hooks/MusicHook'
|
||||
import MusicFull from './MusicFull.vue'
|
||||
import SongItem from '@/components/common/SongItem.vue'
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const store = useStore()
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { allTime, loadLrc, nowTime, openLyric, sendLyricToWin } from '@/hooks/MusicHook';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
||||
|
||||
import MusicFull from './MusicFull.vue';
|
||||
|
||||
const store = useStore();
|
||||
|
||||
// 播放的音乐信息
|
||||
const playMusic = computed(() => store.state.playMusic as SongResult)
|
||||
const playMusic = computed(() => store.state.playMusic as SongResult);
|
||||
// 是否播放
|
||||
const play = computed(() => store.state.play as boolean)
|
||||
const play = computed(() => store.state.play as boolean);
|
||||
|
||||
const playList = computed(() => store.state.playList as SongResult[])
|
||||
const playList = computed(() => store.state.playList as SongResult[]);
|
||||
|
||||
const audio = {
|
||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement
|
||||
}
|
||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement,
|
||||
};
|
||||
|
||||
watch(
|
||||
() => store.state.playMusicUrl,
|
||||
() => {
|
||||
loadLrc(playMusic.value.id)
|
||||
loadLrc(playMusic.value.id);
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const audioPlay = () => {
|
||||
if (audio.value) {
|
||||
audio.value.play()
|
||||
audio.value.play();
|
||||
}
|
||||
}
|
||||
|
||||
const audioPause = () => {
|
||||
if (audio.value) {
|
||||
audio.value.pause()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 计算属性 获取当前播放时间的进度
|
||||
const timeSlider = computed({
|
||||
get: () => (nowTime.value / allTime.value) * 100,
|
||||
set: (value) => {
|
||||
if (!audio.value) return
|
||||
audio.value.currentTime = (value * allTime.value) / 100
|
||||
audioPlay()
|
||||
store.commit('setPlayMusic', true)
|
||||
if (!audio.value) return;
|
||||
audio.value.currentTime = (value * allTime.value) / 100;
|
||||
audioPlay();
|
||||
store.commit('setPlayMusic', true);
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
// 音量条
|
||||
const audioVolume = ref(1)
|
||||
const audioVolume = ref(1);
|
||||
const volumeSlider = computed({
|
||||
get: () => audioVolume.value * 100,
|
||||
set: (value) => {
|
||||
if(!audio.value) return
|
||||
audio.value.volume = value / 100
|
||||
if (!audio.value) return;
|
||||
audio.value.volume = value / 100;
|
||||
},
|
||||
})
|
||||
});
|
||||
// 获取当前播放时间
|
||||
const getNowTime = computed(() => {
|
||||
return secondToMinute(nowTime.value)
|
||||
})
|
||||
return secondToMinute(nowTime.value);
|
||||
});
|
||||
|
||||
// 获取总时间
|
||||
const getAllTime = computed(() => {
|
||||
return secondToMinute(allTime.value)
|
||||
})
|
||||
return secondToMinute(allTime.value);
|
||||
});
|
||||
|
||||
// 监听音乐播放 获取时间
|
||||
const onAudio = () => {
|
||||
if(audio.value){
|
||||
audio.value.removeEventListener('timeupdate', handleGetAudioTime)
|
||||
audio.value.removeEventListener('ended', handleEnded)
|
||||
audio.value.addEventListener('timeupdate', handleGetAudioTime)
|
||||
audio.value.addEventListener('ended', handleEnded)
|
||||
if (audio.value) {
|
||||
audio.value.removeEventListener('timeupdate', handleGetAudioTime);
|
||||
audio.value.removeEventListener('ended', handleEnded);
|
||||
audio.value.addEventListener('timeupdate', handleGetAudioTime);
|
||||
audio.value.addEventListener('ended', handleEnded);
|
||||
// 监听音乐播放暂停
|
||||
audio.value.addEventListener('pause', () => {
|
||||
store.commit('setPlayMusic', false)
|
||||
})
|
||||
store.commit('setPlayMusic', false);
|
||||
});
|
||||
audio.value.addEventListener('play', () => {
|
||||
store.commit('setPlayMusic', true)
|
||||
})
|
||||
store.commit('setPlayMusic', true);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onAudio()
|
||||
onAudio();
|
||||
|
||||
function handleEnded() {
|
||||
store.commit('nextPlay')
|
||||
store.commit('nextPlay');
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
store.commit('prevPlay')
|
||||
store.commit('prevPlay');
|
||||
}
|
||||
|
||||
const MusicFullRef = ref<any>(null)
|
||||
const MusicFullRef = ref<any>(null);
|
||||
|
||||
function handleGetAudioTime(this: any) {
|
||||
// 监听音频播放的实时时间事件
|
||||
const audio = this as HTMLAudioElement
|
||||
const audio = this as HTMLAudioElement;
|
||||
// 获取当前播放时间
|
||||
nowTime.value = Math.floor(audio.currentTime)
|
||||
nowTime.value = Math.floor(audio.currentTime);
|
||||
// 获取总时间
|
||||
allTime.value = audio.duration
|
||||
allTime.value = audio.duration;
|
||||
// 获取音量
|
||||
audioVolume.value = audio.volume
|
||||
MusicFullRef.value?.lrcScroll()
|
||||
audioVolume.value = audio.volume;
|
||||
sendLyricToWin(store.state.isPlay);
|
||||
MusicFullRef.value?.lrcScroll();
|
||||
}
|
||||
|
||||
// 播放暂停按钮事件
|
||||
const playMusicEvent = async () => {
|
||||
if (play.value) {
|
||||
store.commit('setPlayMusic', false)
|
||||
store.commit('setPlayMusic', false);
|
||||
} else {
|
||||
store.commit('setPlayMusic', true)
|
||||
store.commit('setPlayMusic', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const musicFull = ref(false)
|
||||
const musicFull = ref(false);
|
||||
|
||||
// 设置musicFull
|
||||
const setMusicFull = () => {
|
||||
musicFull.value = !musicFull.value
|
||||
}
|
||||
|
||||
musicFull.value = !musicFull.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -243,7 +223,9 @@ const setMusicFull = () => {
|
||||
@apply h-20 w-full absolute bottom-0 left-0 flex items-center rounded-t-2xl overflow-hidden box-border px-6 py-2;
|
||||
z-index: 9999;
|
||||
box-shadow: 0px 0px 10px 2px rgba(203, 203, 203, 0.034);
|
||||
background-color: rgba(0, 0, 0, 0.747); .music-content {
|
||||
background-color: rgba(0, 0, 0, 0.747);
|
||||
animation-duration: 0.5s !important;
|
||||
.music-content {
|
||||
width: 140px;
|
||||
@apply ml-4;
|
||||
|
||||
@@ -310,16 +292,15 @@ const setMusicFull = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.music-play{
|
||||
|
||||
&-list{
|
||||
.music-play {
|
||||
&-list {
|
||||
height: 50vh;
|
||||
@apply relative rounded-3xl overflow-hidden;
|
||||
&-back{
|
||||
&-back {
|
||||
backdrop-filter: blur(20px);
|
||||
@apply absolute top-0 left-0 w-full h-full bg-gray-800 bg-opacity-75;
|
||||
}
|
||||
&-content{
|
||||
&-content {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +1,161 @@
|
||||
<template>
|
||||
<div class="search-box flex">
|
||||
<div class="search-box-input flex-1">
|
||||
<n-input
|
||||
size="medium"
|
||||
round
|
||||
v-model:value="searchValue"
|
||||
:placeholder="hotSearchKeyword"
|
||||
class="border border-gray-600"
|
||||
@keydown.enter="search"
|
||||
>
|
||||
<template #prefix>
|
||||
<i class="iconfont icon-search"></i>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<div class="w-20 px-3 flex justify-between items-center">
|
||||
<div>{{ searchTypeOptions.find(item => item.key === searchType)?.label }}</div>
|
||||
<n-dropdown trigger="hover" @select="selectSearchType" :options="searchTypeOptions">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
<div class="user-box">
|
||||
<n-dropdown trigger="hover" @select="selectItem" :options="userSetOptions">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</n-dropdown>
|
||||
<n-avatar
|
||||
class="ml-2 cursor-pointer"
|
||||
circle
|
||||
size="medium"
|
||||
:src="getImgUrl(store.state.user.avatarUrl)"
|
||||
v-if="store.state.user"
|
||||
/>
|
||||
<div class="mx-2 rounded-full cursor-pointer text-sm" v-else @click="toLogin">登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getSearchKeyword } from '@/api/home';
|
||||
import { getUserDetail, logout } from '@/api/login';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import request from '@/utils/request_mt'
|
||||
import { getImgUrl } from '@/utils';
|
||||
import {USER_SET_OPTIONS, SEARCH_TYPES} from '@/const/bar-const'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore();
|
||||
const userSetOptions = ref(USER_SET_OPTIONS)
|
||||
|
||||
|
||||
// 推荐热搜词
|
||||
const hotSearchKeyword = ref("搜索点什么吧...")
|
||||
const hotSearchValue = ref("")
|
||||
const loadHotSearchKeyword = async () => {
|
||||
const { data } = await getSearchKeyword();
|
||||
hotSearchKeyword.value = data.data.showKeyword
|
||||
hotSearchValue.value = data.data.realkeyword
|
||||
}
|
||||
|
||||
const loadPage = async () => {
|
||||
const token = localStorage.getItem("token")
|
||||
if (!token) return
|
||||
const { data } = await getUserDetail()
|
||||
store.state.user = data.profile
|
||||
localStorage.setItem('user', JSON.stringify(data.profile))
|
||||
}
|
||||
|
||||
|
||||
watchEffect(() => {
|
||||
loadPage()
|
||||
if (store.state.user) {
|
||||
userSetOptions.value = USER_SET_OPTIONS
|
||||
} else {
|
||||
userSetOptions.value = USER_SET_OPTIONS.filter(item => item.key !== 'logout')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const toLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadHotSearchKeyword()
|
||||
loadPage()
|
||||
})
|
||||
|
||||
|
||||
// 搜索词
|
||||
const searchValue = ref("")
|
||||
const searchType = ref(1)
|
||||
const search = () => {
|
||||
let value = searchValue.value
|
||||
if (value == "") {
|
||||
searchValue.value = hotSearchValue.value
|
||||
} else {
|
||||
router.push({
|
||||
path: "/search",
|
||||
query: {
|
||||
keyword: value,
|
||||
type: searchType.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const selectSearchType = (key: any) => {
|
||||
searchType.value = key
|
||||
}
|
||||
|
||||
|
||||
const searchTypeOptions = ref(SEARCH_TYPES)
|
||||
|
||||
const selectItem = async (key: any) => {
|
||||
// switch 判断
|
||||
switch (key) {
|
||||
case 'card':
|
||||
await request.get('/?do=sign')
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
break;
|
||||
case 'card_music':
|
||||
await request.get('/?do=daka')
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
break;
|
||||
case 'listen':
|
||||
await request.get('/?do=listen&id=1885175990&time=300')
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
break;
|
||||
case 'logout':
|
||||
logout().then(() => {
|
||||
store.state.user = null
|
||||
localStorage.clear()
|
||||
})
|
||||
break;
|
||||
case 'login':
|
||||
router.push("/login")
|
||||
break;
|
||||
case 'set':
|
||||
router.push("/set")
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-box {
|
||||
@apply ml-4 flex text-lg justify-center items-center rounded-full pl-3 border border-gray-600;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
.search-box{
|
||||
@apply pb-4 pr-4;
|
||||
}
|
||||
.search-box-input {
|
||||
@apply relative;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="search-box flex">
|
||||
<div class="search-box-input flex-1">
|
||||
<n-input
|
||||
v-model:value="searchValue"
|
||||
size="medium"
|
||||
round
|
||||
:placeholder="hotSearchKeyword"
|
||||
class="border border-gray-600"
|
||||
@keydown.enter="search"
|
||||
>
|
||||
<template #prefix>
|
||||
<i class="iconfont icon-search"></i>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<div class="w-20 px-3 flex justify-between items-center">
|
||||
<div>{{ searchTypeOptions.find((item) => item.key === searchType)?.label }}</div>
|
||||
<n-dropdown trigger="hover" :options="searchTypeOptions" @select="selectSearchType">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
<div class="user-box">
|
||||
<n-dropdown trigger="hover" :options="userSetOptions" @select="selectItem">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</n-dropdown>
|
||||
<n-avatar
|
||||
v-if="store.state.user"
|
||||
class="ml-2 cursor-pointer"
|
||||
circle
|
||||
size="medium"
|
||||
:src="getImgUrl(store.state.user.avatarUrl)"
|
||||
/>
|
||||
<div v-else class="mx-2 rounded-full cursor-pointer text-sm" @click="toLogin">登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { getSearchKeyword } from '@/api/home';
|
||||
import { getUserDetail, logout } from '@/api/login';
|
||||
import { SEARCH_TYPES, USER_SET_OPTIONS } from '@/const/bar-const';
|
||||
import { getImgUrl } from '@/utils';
|
||||
import request from '@/utils/request_mt';
|
||||
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const userSetOptions = ref(USER_SET_OPTIONS);
|
||||
|
||||
// 推荐热搜词
|
||||
const hotSearchKeyword = ref('搜索点什么吧...');
|
||||
const hotSearchValue = ref('');
|
||||
const loadHotSearchKeyword = async () => {
|
||||
const { data } = await getSearchKeyword();
|
||||
hotSearchKeyword.value = data.data.showKeyword;
|
||||
hotSearchValue.value = data.data.realkeyword;
|
||||
};
|
||||
|
||||
const loadPage = async () => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
const { data } = await getUserDetail();
|
||||
store.state.user = data.profile;
|
||||
localStorage.setItem('user', JSON.stringify(data.profile));
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
loadPage();
|
||||
if (store.state.user) {
|
||||
userSetOptions.value = USER_SET_OPTIONS;
|
||||
} else {
|
||||
userSetOptions.value = USER_SET_OPTIONS.filter((item) => item.key !== 'logout');
|
||||
}
|
||||
});
|
||||
|
||||
const toLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadHotSearchKeyword();
|
||||
loadPage();
|
||||
});
|
||||
|
||||
// 搜索词
|
||||
const searchValue = ref('');
|
||||
const searchType = ref(1);
|
||||
const search = () => {
|
||||
const { value } = searchValue;
|
||||
if (value === '') {
|
||||
searchValue.value = hotSearchValue.value;
|
||||
} else {
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
keyword: value,
|
||||
type: searchType.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectSearchType = (key: any) => {
|
||||
searchType.value = key;
|
||||
};
|
||||
|
||||
const searchTypeOptions = ref(SEARCH_TYPES);
|
||||
|
||||
const selectItem = async (key: any) => {
|
||||
// switch 判断
|
||||
switch (key) {
|
||||
case 'card':
|
||||
await request.get('/?do=sign').then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
break;
|
||||
case 'card_music':
|
||||
await request.get('/?do=daka').then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
break;
|
||||
case 'listen':
|
||||
await request.get('/?do=listen&id=1885175990&time=300').then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
break;
|
||||
case 'logout':
|
||||
logout().then(() => {
|
||||
store.state.user = null;
|
||||
localStorage.clear();
|
||||
});
|
||||
break;
|
||||
case 'login':
|
||||
router.push('/login');
|
||||
break;
|
||||
case 'set':
|
||||
router.push('/set');
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-box {
|
||||
@apply ml-4 flex text-lg justify-center items-center rounded-full pl-3 border border-gray-600;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
.search-box {
|
||||
@apply pb-4 pr-4;
|
||||
}
|
||||
.search-box-input {
|
||||
@apply relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
<button @click="minimize">
|
||||
<i class="iconfont icon-minisize"></i>
|
||||
</button>
|
||||
<!-- <button @click="maximize">
|
||||
<i class="iconfont icon-maxsize"></i>
|
||||
</button> -->
|
||||
<button @click="close">
|
||||
<i class="iconfont icon-close"></i>
|
||||
</button>
|
||||
@@ -16,18 +13,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDialog } from 'naive-ui'
|
||||
import { useDialog } from 'naive-ui';
|
||||
|
||||
const dialog = useDialog()
|
||||
const windowData = window as any
|
||||
const dialog = useDialog();
|
||||
const windowData = window as any;
|
||||
|
||||
const minimize = () => {
|
||||
windowData.electronAPI.minimize()
|
||||
}
|
||||
|
||||
const maximize = () => {
|
||||
windowData.electronAPI.maximize()
|
||||
}
|
||||
windowData.electronAPI.minimize();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
dialog.warning({
|
||||
@@ -36,17 +29,17 @@ const close = () => {
|
||||
positiveText: '最小化',
|
||||
negativeText: '关闭',
|
||||
onPositiveClick: () => {
|
||||
windowData.electronAPI.miniTray()
|
||||
windowData.electronAPI.miniTray();
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
windowData.electronAPI.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
windowData.electronAPI.close();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const drag = (event: MouseEvent) => {
|
||||
windowData.electronAPI.dragStart(event)
|
||||
}
|
||||
windowData.electronAPI.dragStart(event);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AppMenu from "./AppMenu.vue";
|
||||
import PlayBar from "./PlayBar.vue";
|
||||
import SearchBar from "./SearchBar.vue";
|
||||
import AppMenu from './AppMenu.vue';
|
||||
import PlayBar from './PlayBar.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
|
||||
export { AppMenu, PlayBar, SearchBar };
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
lrcList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
lrcIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
lrcTime: {
|
||||
type: Number,
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
36
src/main.ts
36
src/main.ts
@@ -1,19 +1,17 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
import naive from "naive-ui";
|
||||
import "vfonts/Lato.css";
|
||||
import "vfonts/FiraCode.css";
|
||||
|
||||
// tailwind css
|
||||
import "./index.css";
|
||||
|
||||
import router from "@/router";
|
||||
|
||||
import store from "@/store";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
// app.use(naive);
|
||||
app.mount("#app");
|
||||
import 'vfonts/Lato.css';
|
||||
import 'vfonts/FiraCode.css';
|
||||
// tailwind css
|
||||
import './index.css';
|
||||
import 'remixicon/fonts/remixicon.css';
|
||||
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import router from '@/router';
|
||||
import store from '@/store';
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
app.mount('#app');
|
||||
|
||||
@@ -57,5 +57,5 @@ const layoutRouter = [
|
||||
},
|
||||
component: () => import('@/views/user/index.vue'),
|
||||
},
|
||||
]
|
||||
];
|
||||
export default layoutRouter;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import AppLayout from '@/layout/AppLayout.vue'
|
||||
import homeRouter from '@/router/home'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
|
||||
import AppLayout from '@/layout/AppLayout.vue';
|
||||
import homeRouter from '@/router/home';
|
||||
|
||||
const loginRouter = {
|
||||
path: '/login',
|
||||
@@ -11,7 +12,7 @@ const loginRouter = {
|
||||
icon: 'icon-Home',
|
||||
},
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
}
|
||||
};
|
||||
|
||||
const setRouter = {
|
||||
path: '/set',
|
||||
@@ -22,7 +23,7 @@ const setRouter = {
|
||||
icon: 'icon-Home',
|
||||
},
|
||||
component: () => import('@/views/set/index.vue'),
|
||||
}
|
||||
};
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -30,9 +31,13 @@ const routes = [
|
||||
component: AppLayout,
|
||||
children: [...homeRouter, loginRouter, setRouter],
|
||||
},
|
||||
]
|
||||
{
|
||||
path: '/lyric',
|
||||
component: () => import('@/views/lyric/index.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
export default createRouter({
|
||||
routes: routes,
|
||||
routes,
|
||||
history: createWebHashHistory(),
|
||||
})
|
||||
});
|
||||
|
||||
11
src/shims-vue.d.ts
vendored
11
src/shims-vue.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue';
|
||||
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { createStore } from 'vuex'
|
||||
import { SongResult } from '@/type/music'
|
||||
import { getMusicUrl, getParsingMusicUrl } from '@/api/music'
|
||||
import homeRouter from '@/router/home'
|
||||
import { getMusicProxyUrl } from '@/utils'
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook'
|
||||
import { createStore } from 'vuex';
|
||||
|
||||
import { getMusicUrl, getParsingMusicUrl } from '@/api/music';
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
||||
import homeRouter from '@/router/home';
|
||||
import { SongResult } from '@/type/music';
|
||||
import { getMusicProxyUrl } from '@/utils';
|
||||
|
||||
interface State {
|
||||
menus: any[]
|
||||
play: boolean
|
||||
isPlay: boolean
|
||||
playMusic: SongResult
|
||||
playMusicUrl: string
|
||||
user: any
|
||||
playList: SongResult[]
|
||||
playListIndex: number
|
||||
setData: any
|
||||
menus: any[];
|
||||
play: boolean;
|
||||
isPlay: boolean;
|
||||
playMusic: SongResult;
|
||||
playMusicUrl: string;
|
||||
user: any;
|
||||
playList: SongResult[];
|
||||
playListIndex: number;
|
||||
setData: any;
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
@@ -27,85 +28,79 @@ const state: State = {
|
||||
playList: [],
|
||||
playListIndex: 0,
|
||||
setData: null,
|
||||
}
|
||||
};
|
||||
|
||||
const windowData = window as any
|
||||
const windowData = window as any;
|
||||
|
||||
const musicHistory = useMusicHistory()
|
||||
const musicHistory = useMusicHistory();
|
||||
|
||||
const mutations = {
|
||||
setMenus(state: State, menus: any[]) {
|
||||
state.menus = menus
|
||||
state.menus = menus;
|
||||
},
|
||||
async setPlay(state: State, playMusic: SongResult) {
|
||||
state.playMusic = playMusic
|
||||
state.playMusicUrl = await getSongUrl(playMusic.id)
|
||||
state.play = true
|
||||
musicHistory.addMusic(playMusic)
|
||||
state.playMusic = playMusic;
|
||||
state.playMusicUrl = await getSongUrl(playMusic.id);
|
||||
state.play = true;
|
||||
musicHistory.addMusic(playMusic);
|
||||
},
|
||||
setIsPlay(state: State, isPlay: boolean) {
|
||||
state.isPlay = isPlay
|
||||
state.isPlay = isPlay;
|
||||
},
|
||||
setPlayMusic(state: State, play: boolean) {
|
||||
state.play = play
|
||||
state.play = play;
|
||||
},
|
||||
setPlayList(state: State, playList: SongResult[]) {
|
||||
state.playListIndex = playList.findIndex(
|
||||
(item) => item.id === state.playMusic.id
|
||||
)
|
||||
state.playList = playList
|
||||
state.playListIndex = playList.findIndex((item) => item.id === state.playMusic.id);
|
||||
state.playList = playList;
|
||||
},
|
||||
async nextPlay(state: State) {
|
||||
if (state.playList.length === 0) {
|
||||
state.play = true
|
||||
return
|
||||
state.play = true;
|
||||
return;
|
||||
}
|
||||
state.playListIndex = (state.playListIndex + 1) % state.playList.length
|
||||
await updatePlayMusic(state)
|
||||
state.playListIndex = (state.playListIndex + 1) % state.playList.length;
|
||||
await updatePlayMusic(state);
|
||||
},
|
||||
async prevPlay(state: State) {
|
||||
if (state.playList.length === 0) {
|
||||
state.play = true
|
||||
return
|
||||
state.play = true;
|
||||
return;
|
||||
}
|
||||
state.playListIndex =
|
||||
(state.playListIndex - 1 + state.playList.length) % state.playList.length
|
||||
await updatePlayMusic(state)
|
||||
state.playListIndex = (state.playListIndex - 1 + state.playList.length) % state.playList.length;
|
||||
await updatePlayMusic(state);
|
||||
},
|
||||
async setSetData(state: State, setData: any) {
|
||||
state.setData = setData
|
||||
windowData.electron.ipcRenderer.setStoreValue(
|
||||
'set',
|
||||
JSON.parse(JSON.stringify(setData))
|
||||
)
|
||||
state.setData = setData;
|
||||
windowData.electron.ipcRenderer.setStoreValue('set', JSON.parse(JSON.stringify(setData)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const getSongUrl = async (id: number) => {
|
||||
const { data } = await getMusicUrl(id)
|
||||
let url = ''
|
||||
const { data } = await getMusicUrl(id);
|
||||
let url = '';
|
||||
try {
|
||||
if (data.data[0].freeTrialInfo || !data.data[0].url) {
|
||||
const res = await getParsingMusicUrl(id)
|
||||
url = res.data.data.url
|
||||
const res = await getParsingMusicUrl(id);
|
||||
url = res.data.data.url;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error', error)
|
||||
console.error('error', error);
|
||||
}
|
||||
url = url ? url : data.data[0].url
|
||||
return getMusicProxyUrl(url)
|
||||
}
|
||||
url = url || data.data[0].url;
|
||||
return getMusicProxyUrl(url);
|
||||
};
|
||||
|
||||
const updatePlayMusic = async (state: State) => {
|
||||
state.playMusic = state.playList[state.playListIndex]
|
||||
state.playMusicUrl = await getSongUrl(state.playMusic.id)
|
||||
state.play = true
|
||||
musicHistory.addMusic(state.playMusic)
|
||||
}
|
||||
state.playMusic = state.playList[state.playListIndex];
|
||||
state.playMusicUrl = await getSongUrl(state.playMusic.id);
|
||||
state.play = true;
|
||||
musicHistory.addMusic(state.playMusic);
|
||||
};
|
||||
|
||||
const store = createStore({
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
})
|
||||
state,
|
||||
mutations,
|
||||
});
|
||||
|
||||
export default store
|
||||
export default store;
|
||||
|
||||
@@ -4,30 +4,30 @@ export interface IAlbumNew {
|
||||
}
|
||||
|
||||
export interface Album {
|
||||
name: string
|
||||
id: number
|
||||
type: string
|
||||
size: number
|
||||
picId: number
|
||||
blurPicUrl: string
|
||||
companyId: number
|
||||
pic: number
|
||||
picUrl: string
|
||||
publishTime: number
|
||||
description: string
|
||||
tags: string
|
||||
company: string
|
||||
briefDesc: string
|
||||
artist: Artist
|
||||
songs?: any
|
||||
alias: string[]
|
||||
status: number
|
||||
copyrightId: number
|
||||
commentThreadId: string
|
||||
artists: Artist2[]
|
||||
paid: boolean
|
||||
onSale: boolean
|
||||
picId_str: string
|
||||
name: string;
|
||||
id: number;
|
||||
type: string;
|
||||
size: number;
|
||||
picId: number;
|
||||
blurPicUrl: string;
|
||||
companyId: number;
|
||||
pic: number;
|
||||
picUrl: string;
|
||||
publishTime: number;
|
||||
description: string;
|
||||
tags: string;
|
||||
company: string;
|
||||
briefDesc: string;
|
||||
artist: Artist;
|
||||
songs?: any;
|
||||
alias: string[];
|
||||
status: number;
|
||||
copyrightId: number;
|
||||
commentThreadId: string;
|
||||
artists: Artist2[];
|
||||
paid: boolean;
|
||||
onSale: boolean;
|
||||
picId_str: string;
|
||||
}
|
||||
|
||||
interface Artist2 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IData<T> {
|
||||
code: number
|
||||
data: T
|
||||
code: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
@@ -7,42 +7,42 @@ export interface IList {
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
name: string
|
||||
id: number
|
||||
trackNumberUpdateTime: number
|
||||
status: number
|
||||
userId: number
|
||||
createTime: number
|
||||
updateTime: number
|
||||
subscribedCount: number
|
||||
trackCount: number
|
||||
cloudTrackCount: number
|
||||
coverImgUrl: string
|
||||
coverImgId: number
|
||||
description: string
|
||||
tags: string[]
|
||||
playCount: number
|
||||
trackUpdateTime: number
|
||||
specialType: number
|
||||
totalDuration: number
|
||||
creator: Creator
|
||||
tracks?: any
|
||||
subscribers: Subscriber[]
|
||||
subscribed: boolean
|
||||
commentThreadId: string
|
||||
newImported: boolean
|
||||
adType: number
|
||||
highQuality: boolean
|
||||
privacy: number
|
||||
ordered: boolean
|
||||
anonimous: boolean
|
||||
coverStatus: number
|
||||
recommendInfo?: any
|
||||
shareCount: number
|
||||
coverImgId_str?: string
|
||||
commentCount: number
|
||||
copywriter: string
|
||||
tag: string
|
||||
name: string;
|
||||
id: number;
|
||||
trackNumberUpdateTime: number;
|
||||
status: number;
|
||||
userId: number;
|
||||
createTime: number;
|
||||
updateTime: number;
|
||||
subscribedCount: number;
|
||||
trackCount: number;
|
||||
cloudTrackCount: number;
|
||||
coverImgUrl: string;
|
||||
coverImgId: number;
|
||||
description: string;
|
||||
tags: string[];
|
||||
playCount: number;
|
||||
trackUpdateTime: number;
|
||||
specialType: number;
|
||||
totalDuration: number;
|
||||
creator: Creator;
|
||||
tracks?: any;
|
||||
subscribers: Subscriber[];
|
||||
subscribed: boolean;
|
||||
commentThreadId: string;
|
||||
newImported: boolean;
|
||||
adType: number;
|
||||
highQuality: boolean;
|
||||
privacy: number;
|
||||
ordered: boolean;
|
||||
anonimous: boolean;
|
||||
coverStatus: number;
|
||||
recommendInfo?: any;
|
||||
shareCount: number;
|
||||
coverImgId_str?: string;
|
||||
commentCount: number;
|
||||
copywriter: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
interface Subscriber {
|
||||
@@ -120,8 +120,8 @@ interface AvatarDetail {
|
||||
}
|
||||
|
||||
interface Expert {
|
||||
"2": string;
|
||||
"1"?: string;
|
||||
'2': string;
|
||||
'1'?: string;
|
||||
}
|
||||
|
||||
// 推荐歌单
|
||||
|
||||
@@ -1,203 +1,203 @@
|
||||
export interface IListDetail {
|
||||
code: number;
|
||||
relatedVideos?: any;
|
||||
playlist: Playlist;
|
||||
urls?: any;
|
||||
privileges: Privilege[];
|
||||
sharedPrivilege?: any;
|
||||
resEntrance?: any;
|
||||
}
|
||||
|
||||
interface Privilege {
|
||||
id: number;
|
||||
fee: number;
|
||||
payed: number;
|
||||
realPayed: number;
|
||||
st: number;
|
||||
pl: number;
|
||||
dl: number;
|
||||
sp: number;
|
||||
cp: number;
|
||||
subp: number;
|
||||
cs: boolean;
|
||||
maxbr: number;
|
||||
fl: number;
|
||||
pc?: any;
|
||||
toast: boolean;
|
||||
flag: number;
|
||||
paidBigBang: boolean;
|
||||
preSell: boolean;
|
||||
playMaxbr: number;
|
||||
downloadMaxbr: number;
|
||||
rscl?: any;
|
||||
freeTrialPrivilege: FreeTrialPrivilege;
|
||||
chargeInfoList: ChargeInfoList[];
|
||||
}
|
||||
|
||||
interface ChargeInfoList {
|
||||
rate: number;
|
||||
chargeUrl?: any;
|
||||
chargeMessage?: any;
|
||||
chargeType: number;
|
||||
}
|
||||
|
||||
interface FreeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
id: number
|
||||
name: string
|
||||
coverImgId: number
|
||||
coverImgUrl: string
|
||||
coverImgId_str: string
|
||||
adType: number
|
||||
userId: number
|
||||
createTime: number
|
||||
status: number
|
||||
opRecommend: boolean
|
||||
highQuality: boolean
|
||||
newImported: boolean
|
||||
updateTime: number
|
||||
trackCount: number
|
||||
specialType: number
|
||||
privacy: number
|
||||
trackUpdateTime: number
|
||||
commentThreadId: string
|
||||
playCount: number
|
||||
trackNumberUpdateTime: number
|
||||
subscribedCount: number
|
||||
cloudTrackCount: number
|
||||
ordered: boolean
|
||||
description: string
|
||||
tags: string[]
|
||||
updateFrequency?: any
|
||||
backgroundCoverId: number
|
||||
backgroundCoverUrl?: any
|
||||
titleImage: number
|
||||
titleImageUrl?: any
|
||||
englishTitle?: any
|
||||
officialPlaylistType?: any
|
||||
subscribers: Subscriber[]
|
||||
subscribed: boolean
|
||||
creator: Subscriber
|
||||
tracks: Track[]
|
||||
videoIds?: any
|
||||
videos?: any
|
||||
trackIds: TrackId[]
|
||||
shareCount: number
|
||||
commentCount: number
|
||||
remixVideo?: any
|
||||
sharedUsers?: any
|
||||
historySharedUsers?: any
|
||||
}
|
||||
|
||||
interface TrackId {
|
||||
id: number;
|
||||
v: number;
|
||||
t: number;
|
||||
at: number;
|
||||
alg?: any;
|
||||
uid: number;
|
||||
rcmdReason: string;
|
||||
}
|
||||
|
||||
interface Track {
|
||||
name: string;
|
||||
id: number;
|
||||
pst: number;
|
||||
t: number;
|
||||
ar: Ar[];
|
||||
alia: string[];
|
||||
pop: number;
|
||||
st: number;
|
||||
rt?: string;
|
||||
fee: number;
|
||||
v: number;
|
||||
crbt?: any;
|
||||
cf: string;
|
||||
al: Al;
|
||||
dt: number;
|
||||
h: H;
|
||||
m: H;
|
||||
l?: H;
|
||||
a?: any;
|
||||
cd: string;
|
||||
no: number;
|
||||
rtUrl?: any;
|
||||
ftype: number;
|
||||
rtUrls: any[];
|
||||
djId: number;
|
||||
copyright: number;
|
||||
s_id: number;
|
||||
mark: number;
|
||||
originCoverType: number;
|
||||
originSongSimpleData?: any;
|
||||
single: number;
|
||||
noCopyrightRcmd?: any;
|
||||
mst: number;
|
||||
cp: number;
|
||||
mv: number;
|
||||
rtype: number;
|
||||
rurl?: any;
|
||||
publishTime: number;
|
||||
tns?: string[];
|
||||
}
|
||||
|
||||
interface H {
|
||||
br: number;
|
||||
fid: number;
|
||||
size: number;
|
||||
vd: number;
|
||||
}
|
||||
|
||||
interface Al {
|
||||
id: number;
|
||||
name: string;
|
||||
picUrl: string;
|
||||
tns: any[];
|
||||
pic_str?: string;
|
||||
pic: number;
|
||||
}
|
||||
|
||||
interface Ar {
|
||||
id: number;
|
||||
name: string;
|
||||
tns: any[];
|
||||
alias: any[];
|
||||
}
|
||||
|
||||
interface Subscriber {
|
||||
defaultAvatar: boolean;
|
||||
province: number;
|
||||
authStatus: number;
|
||||
followed: boolean;
|
||||
avatarUrl: string;
|
||||
accountStatus: number;
|
||||
gender: number;
|
||||
city: number;
|
||||
birthday: number;
|
||||
userId: number;
|
||||
userType: number;
|
||||
nickname: string;
|
||||
signature: string;
|
||||
description: string;
|
||||
detailDescription: string;
|
||||
avatarImgId: number;
|
||||
backgroundImgId: number;
|
||||
backgroundUrl: string;
|
||||
authority: number;
|
||||
mutual: boolean;
|
||||
expertTags?: any;
|
||||
experts?: any;
|
||||
djStatus: number;
|
||||
vipType: number;
|
||||
remarkName?: any;
|
||||
authenticationTypes: number;
|
||||
avatarDetail?: any;
|
||||
backgroundImgIdStr: string;
|
||||
anchor: boolean;
|
||||
avatarImgIdStr: string;
|
||||
avatarImgId_str: string;
|
||||
}
|
||||
export interface IListDetail {
|
||||
code: number;
|
||||
relatedVideos?: any;
|
||||
playlist: Playlist;
|
||||
urls?: any;
|
||||
privileges: Privilege[];
|
||||
sharedPrivilege?: any;
|
||||
resEntrance?: any;
|
||||
}
|
||||
|
||||
interface Privilege {
|
||||
id: number;
|
||||
fee: number;
|
||||
payed: number;
|
||||
realPayed: number;
|
||||
st: number;
|
||||
pl: number;
|
||||
dl: number;
|
||||
sp: number;
|
||||
cp: number;
|
||||
subp: number;
|
||||
cs: boolean;
|
||||
maxbr: number;
|
||||
fl: number;
|
||||
pc?: any;
|
||||
toast: boolean;
|
||||
flag: number;
|
||||
paidBigBang: boolean;
|
||||
preSell: boolean;
|
||||
playMaxbr: number;
|
||||
downloadMaxbr: number;
|
||||
rscl?: any;
|
||||
freeTrialPrivilege: FreeTrialPrivilege;
|
||||
chargeInfoList: ChargeInfoList[];
|
||||
}
|
||||
|
||||
interface ChargeInfoList {
|
||||
rate: number;
|
||||
chargeUrl?: any;
|
||||
chargeMessage?: any;
|
||||
chargeType: number;
|
||||
}
|
||||
|
||||
interface FreeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
id: number;
|
||||
name: string;
|
||||
coverImgId: number;
|
||||
coverImgUrl: string;
|
||||
coverImgId_str: string;
|
||||
adType: number;
|
||||
userId: number;
|
||||
createTime: number;
|
||||
status: number;
|
||||
opRecommend: boolean;
|
||||
highQuality: boolean;
|
||||
newImported: boolean;
|
||||
updateTime: number;
|
||||
trackCount: number;
|
||||
specialType: number;
|
||||
privacy: number;
|
||||
trackUpdateTime: number;
|
||||
commentThreadId: string;
|
||||
playCount: number;
|
||||
trackNumberUpdateTime: number;
|
||||
subscribedCount: number;
|
||||
cloudTrackCount: number;
|
||||
ordered: boolean;
|
||||
description: string;
|
||||
tags: string[];
|
||||
updateFrequency?: any;
|
||||
backgroundCoverId: number;
|
||||
backgroundCoverUrl?: any;
|
||||
titleImage: number;
|
||||
titleImageUrl?: any;
|
||||
englishTitle?: any;
|
||||
officialPlaylistType?: any;
|
||||
subscribers: Subscriber[];
|
||||
subscribed: boolean;
|
||||
creator: Subscriber;
|
||||
tracks: Track[];
|
||||
videoIds?: any;
|
||||
videos?: any;
|
||||
trackIds: TrackId[];
|
||||
shareCount: number;
|
||||
commentCount: number;
|
||||
remixVideo?: any;
|
||||
sharedUsers?: any;
|
||||
historySharedUsers?: any;
|
||||
}
|
||||
|
||||
interface TrackId {
|
||||
id: number;
|
||||
v: number;
|
||||
t: number;
|
||||
at: number;
|
||||
alg?: any;
|
||||
uid: number;
|
||||
rcmdReason: string;
|
||||
}
|
||||
|
||||
interface Track {
|
||||
name: string;
|
||||
id: number;
|
||||
pst: number;
|
||||
t: number;
|
||||
ar: Ar[];
|
||||
alia: string[];
|
||||
pop: number;
|
||||
st: number;
|
||||
rt?: string;
|
||||
fee: number;
|
||||
v: number;
|
||||
crbt?: any;
|
||||
cf: string;
|
||||
al: Al;
|
||||
dt: number;
|
||||
h: H;
|
||||
m: H;
|
||||
l?: H;
|
||||
a?: any;
|
||||
cd: string;
|
||||
no: number;
|
||||
rtUrl?: any;
|
||||
ftype: number;
|
||||
rtUrls: any[];
|
||||
djId: number;
|
||||
copyright: number;
|
||||
s_id: number;
|
||||
mark: number;
|
||||
originCoverType: number;
|
||||
originSongSimpleData?: any;
|
||||
single: number;
|
||||
noCopyrightRcmd?: any;
|
||||
mst: number;
|
||||
cp: number;
|
||||
mv: number;
|
||||
rtype: number;
|
||||
rurl?: any;
|
||||
publishTime: number;
|
||||
tns?: string[];
|
||||
}
|
||||
|
||||
interface H {
|
||||
br: number;
|
||||
fid: number;
|
||||
size: number;
|
||||
vd: number;
|
||||
}
|
||||
|
||||
interface Al {
|
||||
id: number;
|
||||
name: string;
|
||||
picUrl: string;
|
||||
tns: any[];
|
||||
pic_str?: string;
|
||||
pic: number;
|
||||
}
|
||||
|
||||
interface Ar {
|
||||
id: number;
|
||||
name: string;
|
||||
tns: any[];
|
||||
alias: any[];
|
||||
}
|
||||
|
||||
interface Subscriber {
|
||||
defaultAvatar: boolean;
|
||||
province: number;
|
||||
authStatus: number;
|
||||
followed: boolean;
|
||||
avatarUrl: string;
|
||||
accountStatus: number;
|
||||
gender: number;
|
||||
city: number;
|
||||
birthday: number;
|
||||
userId: number;
|
||||
userType: number;
|
||||
nickname: string;
|
||||
signature: string;
|
||||
description: string;
|
||||
detailDescription: string;
|
||||
avatarImgId: number;
|
||||
backgroundImgId: number;
|
||||
backgroundUrl: string;
|
||||
authority: number;
|
||||
mutual: boolean;
|
||||
expertTags?: any;
|
||||
experts?: any;
|
||||
djStatus: number;
|
||||
vipType: number;
|
||||
remarkName?: any;
|
||||
authenticationTypes: number;
|
||||
avatarDetail?: any;
|
||||
backgroundImgIdStr: string;
|
||||
anchor: boolean;
|
||||
avatarImgIdStr: string;
|
||||
avatarImgId_str: string;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
export interface ILyric {
|
||||
sgc: boolean;
|
||||
sfy: boolean;
|
||||
qfy: boolean;
|
||||
lrc: Lrc;
|
||||
klyric: Lrc;
|
||||
tlyric: Lrc;
|
||||
code: number;
|
||||
}
|
||||
|
||||
interface Lrc {
|
||||
version: number;
|
||||
lyric: string;
|
||||
}
|
||||
export interface ILyric {
|
||||
sgc: boolean;
|
||||
sfy: boolean;
|
||||
qfy: boolean;
|
||||
lrc: Lrc;
|
||||
klyric: Lrc;
|
||||
tlyric: Lrc;
|
||||
code: number;
|
||||
}
|
||||
|
||||
interface Lrc {
|
||||
version: number;
|
||||
lyric: string;
|
||||
}
|
||||
|
||||
@@ -1,197 +1,197 @@
|
||||
export interface IRecommendMusic {
|
||||
code: number;
|
||||
category: number;
|
||||
result: SongResult[];
|
||||
}
|
||||
|
||||
export interface SongResult {
|
||||
id: number
|
||||
type: number
|
||||
name: string
|
||||
copywriter?: any
|
||||
picUrl: string
|
||||
canDislike: boolean
|
||||
trackNumberUpdateTime?: any
|
||||
song: Song
|
||||
alg: string
|
||||
count?: number
|
||||
}
|
||||
|
||||
interface Song {
|
||||
name: string;
|
||||
id: number;
|
||||
position: number;
|
||||
alias: string[];
|
||||
status: number;
|
||||
fee: number;
|
||||
copyrightId: number;
|
||||
disc: string;
|
||||
no: number;
|
||||
artists: Artist[];
|
||||
album: Album;
|
||||
starred: boolean;
|
||||
popularity: number;
|
||||
score: number;
|
||||
starredNum: number;
|
||||
duration: number;
|
||||
playedNum: number;
|
||||
dayPlays: number;
|
||||
hearTime: number;
|
||||
ringtone: string;
|
||||
crbt?: any;
|
||||
audition?: any;
|
||||
copyFrom: string;
|
||||
commentThreadId: string;
|
||||
rtUrl?: any;
|
||||
ftype: number;
|
||||
rtUrls: any[];
|
||||
copyright: number;
|
||||
transName?: any;
|
||||
sign?: any;
|
||||
mark: number;
|
||||
originCoverType: number;
|
||||
originSongSimpleData?: any;
|
||||
single: number;
|
||||
noCopyrightRcmd?: any;
|
||||
rtype: number;
|
||||
rurl?: any;
|
||||
mvid: number;
|
||||
bMusic: BMusic;
|
||||
mp3Url?: any;
|
||||
hMusic: BMusic;
|
||||
mMusic: BMusic;
|
||||
lMusic: BMusic;
|
||||
exclusive: boolean;
|
||||
privilege: Privilege;
|
||||
}
|
||||
|
||||
interface Privilege {
|
||||
id: number;
|
||||
fee: number;
|
||||
payed: number;
|
||||
st: number;
|
||||
pl: number;
|
||||
dl: number;
|
||||
sp: number;
|
||||
cp: number;
|
||||
subp: number;
|
||||
cs: boolean;
|
||||
maxbr: number;
|
||||
fl: number;
|
||||
toast: boolean;
|
||||
flag: number;
|
||||
preSell: boolean;
|
||||
playMaxbr: number;
|
||||
downloadMaxbr: number;
|
||||
rscl?: any;
|
||||
freeTrialPrivilege: FreeTrialPrivilege;
|
||||
chargeInfoList: ChargeInfoList[];
|
||||
}
|
||||
|
||||
interface ChargeInfoList {
|
||||
rate: number;
|
||||
chargeUrl?: any;
|
||||
chargeMessage?: any;
|
||||
chargeType: number;
|
||||
}
|
||||
|
||||
interface FreeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
}
|
||||
|
||||
interface BMusic {
|
||||
name?: any;
|
||||
id: number;
|
||||
size: number;
|
||||
extension: string;
|
||||
sr: number;
|
||||
dfsId: number;
|
||||
bitrate: number;
|
||||
playTime: number;
|
||||
volumeDelta: number;
|
||||
}
|
||||
|
||||
interface Album {
|
||||
name: string;
|
||||
id: number;
|
||||
type: string;
|
||||
size: number;
|
||||
picId: number;
|
||||
blurPicUrl: string;
|
||||
companyId: number;
|
||||
pic: number;
|
||||
picUrl: string;
|
||||
publishTime: number;
|
||||
description: string;
|
||||
tags: string;
|
||||
company: string;
|
||||
briefDesc: string;
|
||||
artist: Artist;
|
||||
songs: any[];
|
||||
alias: string[];
|
||||
status: number;
|
||||
copyrightId: number;
|
||||
commentThreadId: string;
|
||||
artists: Artist[];
|
||||
subType: string;
|
||||
transName?: any;
|
||||
onSale: boolean;
|
||||
mark: number;
|
||||
picId_str: string;
|
||||
}
|
||||
|
||||
interface Artist {
|
||||
name: string;
|
||||
id: number;
|
||||
picId: number;
|
||||
img1v1Id: number;
|
||||
briefDesc: string;
|
||||
picUrl: string;
|
||||
img1v1Url: string;
|
||||
albumSize: number;
|
||||
alias: any[];
|
||||
trans: string;
|
||||
musicSize: number;
|
||||
topicPerson: number;
|
||||
}
|
||||
|
||||
export interface IPlayMusicUrl {
|
||||
data: Datum[];
|
||||
code: number;
|
||||
}
|
||||
|
||||
interface Datum {
|
||||
id: number;
|
||||
url: string;
|
||||
br: number;
|
||||
size: number;
|
||||
md5: string;
|
||||
code: number;
|
||||
expi: number;
|
||||
type: string;
|
||||
gain: number;
|
||||
fee: number;
|
||||
uf?: any;
|
||||
payed: number;
|
||||
flag: number;
|
||||
canExtend: boolean;
|
||||
freeTrialInfo?: any;
|
||||
level: string;
|
||||
encodeType: string;
|
||||
freeTrialPrivilege: FreeTrialPrivilege;
|
||||
freeTimeTrialPrivilege: FreeTimeTrialPrivilege;
|
||||
urlSource: number;
|
||||
}
|
||||
|
||||
interface FreeTimeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
type: number;
|
||||
remainTime: number;
|
||||
}
|
||||
|
||||
interface FreeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
}
|
||||
export interface IRecommendMusic {
|
||||
code: number;
|
||||
category: number;
|
||||
result: SongResult[];
|
||||
}
|
||||
|
||||
export interface SongResult {
|
||||
id: number;
|
||||
type: number;
|
||||
name: string;
|
||||
copywriter?: any;
|
||||
picUrl: string;
|
||||
canDislike: boolean;
|
||||
trackNumberUpdateTime?: any;
|
||||
song: Song;
|
||||
alg: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
interface Song {
|
||||
name: string;
|
||||
id: number;
|
||||
position: number;
|
||||
alias: string[];
|
||||
status: number;
|
||||
fee: number;
|
||||
copyrightId: number;
|
||||
disc: string;
|
||||
no: number;
|
||||
artists: Artist[];
|
||||
album: Album;
|
||||
starred: boolean;
|
||||
popularity: number;
|
||||
score: number;
|
||||
starredNum: number;
|
||||
duration: number;
|
||||
playedNum: number;
|
||||
dayPlays: number;
|
||||
hearTime: number;
|
||||
ringtone: string;
|
||||
crbt?: any;
|
||||
audition?: any;
|
||||
copyFrom: string;
|
||||
commentThreadId: string;
|
||||
rtUrl?: any;
|
||||
ftype: number;
|
||||
rtUrls: any[];
|
||||
copyright: number;
|
||||
transName?: any;
|
||||
sign?: any;
|
||||
mark: number;
|
||||
originCoverType: number;
|
||||
originSongSimpleData?: any;
|
||||
single: number;
|
||||
noCopyrightRcmd?: any;
|
||||
rtype: number;
|
||||
rurl?: any;
|
||||
mvid: number;
|
||||
bMusic: BMusic;
|
||||
mp3Url?: any;
|
||||
hMusic: BMusic;
|
||||
mMusic: BMusic;
|
||||
lMusic: BMusic;
|
||||
exclusive: boolean;
|
||||
privilege: Privilege;
|
||||
}
|
||||
|
||||
interface Privilege {
|
||||
id: number;
|
||||
fee: number;
|
||||
payed: number;
|
||||
st: number;
|
||||
pl: number;
|
||||
dl: number;
|
||||
sp: number;
|
||||
cp: number;
|
||||
subp: number;
|
||||
cs: boolean;
|
||||
maxbr: number;
|
||||
fl: number;
|
||||
toast: boolean;
|
||||
flag: number;
|
||||
preSell: boolean;
|
||||
playMaxbr: number;
|
||||
downloadMaxbr: number;
|
||||
rscl?: any;
|
||||
freeTrialPrivilege: FreeTrialPrivilege;
|
||||
chargeInfoList: ChargeInfoList[];
|
||||
}
|
||||
|
||||
interface ChargeInfoList {
|
||||
rate: number;
|
||||
chargeUrl?: any;
|
||||
chargeMessage?: any;
|
||||
chargeType: number;
|
||||
}
|
||||
|
||||
interface FreeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
}
|
||||
|
||||
interface BMusic {
|
||||
name?: any;
|
||||
id: number;
|
||||
size: number;
|
||||
extension: string;
|
||||
sr: number;
|
||||
dfsId: number;
|
||||
bitrate: number;
|
||||
playTime: number;
|
||||
volumeDelta: number;
|
||||
}
|
||||
|
||||
interface Album {
|
||||
name: string;
|
||||
id: number;
|
||||
type: string;
|
||||
size: number;
|
||||
picId: number;
|
||||
blurPicUrl: string;
|
||||
companyId: number;
|
||||
pic: number;
|
||||
picUrl: string;
|
||||
publishTime: number;
|
||||
description: string;
|
||||
tags: string;
|
||||
company: string;
|
||||
briefDesc: string;
|
||||
artist: Artist;
|
||||
songs: any[];
|
||||
alias: string[];
|
||||
status: number;
|
||||
copyrightId: number;
|
||||
commentThreadId: string;
|
||||
artists: Artist[];
|
||||
subType: string;
|
||||
transName?: any;
|
||||
onSale: boolean;
|
||||
mark: number;
|
||||
picId_str: string;
|
||||
}
|
||||
|
||||
interface Artist {
|
||||
name: string;
|
||||
id: number;
|
||||
picId: number;
|
||||
img1v1Id: number;
|
||||
briefDesc: string;
|
||||
picUrl: string;
|
||||
img1v1Url: string;
|
||||
albumSize: number;
|
||||
alias: any[];
|
||||
trans: string;
|
||||
musicSize: number;
|
||||
topicPerson: number;
|
||||
}
|
||||
|
||||
export interface IPlayMusicUrl {
|
||||
data: Datum[];
|
||||
code: number;
|
||||
}
|
||||
|
||||
interface Datum {
|
||||
id: number;
|
||||
url: string;
|
||||
br: number;
|
||||
size: number;
|
||||
md5: string;
|
||||
code: number;
|
||||
expi: number;
|
||||
type: string;
|
||||
gain: number;
|
||||
fee: number;
|
||||
uf?: any;
|
||||
payed: number;
|
||||
flag: number;
|
||||
canExtend: boolean;
|
||||
freeTrialInfo?: any;
|
||||
level: string;
|
||||
encodeType: string;
|
||||
freeTrialPrivilege: FreeTrialPrivilege;
|
||||
freeTimeTrialPrivilege: FreeTimeTrialPrivilege;
|
||||
urlSource: number;
|
||||
}
|
||||
|
||||
interface FreeTimeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
type: number;
|
||||
remainTime: number;
|
||||
}
|
||||
|
||||
interface FreeTrialPrivilege {
|
||||
resConsumable: boolean;
|
||||
userConsumable: boolean;
|
||||
}
|
||||
|
||||
160
src/type/mv.ts
160
src/type/mv.ts
@@ -1,84 +1,84 @@
|
||||
export interface IMvItem {
|
||||
id: number
|
||||
cover: string
|
||||
name: string
|
||||
playCount: number
|
||||
briefDesc?: any
|
||||
desc?: any
|
||||
artistName: string
|
||||
artistId: number
|
||||
duration: number
|
||||
mark: number
|
||||
mv: IMvData
|
||||
lastRank: number
|
||||
score: number
|
||||
subed: boolean
|
||||
artists: Artist[]
|
||||
transNames?: string[]
|
||||
alias?: string[]
|
||||
id: number;
|
||||
cover: string;
|
||||
name: string;
|
||||
playCount: number;
|
||||
briefDesc?: any;
|
||||
desc?: any;
|
||||
artistName: string;
|
||||
artistId: number;
|
||||
duration: number;
|
||||
mark: number;
|
||||
mv: IMvData;
|
||||
lastRank: number;
|
||||
score: number;
|
||||
subed: boolean;
|
||||
artists: Artist[];
|
||||
transNames?: string[];
|
||||
alias?: string[];
|
||||
}
|
||||
|
||||
export interface IMvData {
|
||||
authId: number
|
||||
status: number
|
||||
id: number
|
||||
title: string
|
||||
subTitle: string
|
||||
appTitle: string
|
||||
aliaName: string
|
||||
transName: string
|
||||
pic4v3: number
|
||||
pic16v9: number
|
||||
caption: number
|
||||
captionLanguage: string
|
||||
style?: any
|
||||
mottos: string
|
||||
oneword?: any
|
||||
appword: string
|
||||
stars?: any
|
||||
desc: string
|
||||
area: string
|
||||
type: string
|
||||
subType: string
|
||||
neteaseonly: number
|
||||
upban: number
|
||||
topWeeks: string
|
||||
publishTime: string
|
||||
online: number
|
||||
score: number
|
||||
plays: number
|
||||
monthplays: number
|
||||
weekplays: number
|
||||
dayplays: number
|
||||
fee: number
|
||||
artists: Artist[]
|
||||
videos: Video[]
|
||||
authId: number;
|
||||
status: number;
|
||||
id: number;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
appTitle: string;
|
||||
aliaName: string;
|
||||
transName: string;
|
||||
pic4v3: number;
|
||||
pic16v9: number;
|
||||
caption: number;
|
||||
captionLanguage: string;
|
||||
style?: any;
|
||||
mottos: string;
|
||||
oneword?: any;
|
||||
appword: string;
|
||||
stars?: any;
|
||||
desc: string;
|
||||
area: string;
|
||||
type: string;
|
||||
subType: string;
|
||||
neteaseonly: number;
|
||||
upban: number;
|
||||
topWeeks: string;
|
||||
publishTime: string;
|
||||
online: number;
|
||||
score: number;
|
||||
plays: number;
|
||||
monthplays: number;
|
||||
weekplays: number;
|
||||
dayplays: number;
|
||||
fee: number;
|
||||
artists: Artist[];
|
||||
videos: Video[];
|
||||
}
|
||||
|
||||
interface Video {
|
||||
tagSign: TagSign
|
||||
tag: string
|
||||
url: string
|
||||
duration: number
|
||||
size: number
|
||||
width: number
|
||||
height: number
|
||||
container: string
|
||||
md5: string
|
||||
check: boolean
|
||||
tagSign: TagSign;
|
||||
tag: string;
|
||||
url: string;
|
||||
duration: number;
|
||||
size: number;
|
||||
width: number;
|
||||
height: number;
|
||||
container: string;
|
||||
md5: string;
|
||||
check: boolean;
|
||||
}
|
||||
|
||||
interface TagSign {
|
||||
br: number
|
||||
type: string
|
||||
tagSign: string
|
||||
resolution: number
|
||||
mvtype: string
|
||||
br: number;
|
||||
type: string;
|
||||
tagSign: string;
|
||||
resolution: number;
|
||||
mvtype: string;
|
||||
}
|
||||
|
||||
interface Artist {
|
||||
id: number
|
||||
name: string
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// {
|
||||
@@ -97,16 +97,16 @@ interface Artist {
|
||||
// }
|
||||
|
||||
export interface IMvUrlData {
|
||||
id: number
|
||||
url: string
|
||||
r: number
|
||||
size: number
|
||||
md5: string
|
||||
code: number
|
||||
expi: number
|
||||
fee: number
|
||||
mvFee: number
|
||||
st: number
|
||||
promotionVo: null | any
|
||||
msg: string
|
||||
id: number;
|
||||
url: string;
|
||||
r: number;
|
||||
size: number;
|
||||
md5: string;
|
||||
code: number;
|
||||
expi: number;
|
||||
fee: number;
|
||||
mvFee: number;
|
||||
st: number;
|
||||
promotionVo: null | any;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ export interface IPlayListSort {
|
||||
}
|
||||
|
||||
interface SortCategories {
|
||||
"0": string;
|
||||
"1": string;
|
||||
"2": string;
|
||||
"3": string;
|
||||
"4": string;
|
||||
'0': string;
|
||||
'1': string;
|
||||
'2': string;
|
||||
'3': string;
|
||||
'4': string;
|
||||
}
|
||||
|
||||
interface SortAll {
|
||||
|
||||
@@ -540,7 +540,7 @@ interface Song2 {
|
||||
}
|
||||
|
||||
interface KsongInfos {
|
||||
"347230": _347230;
|
||||
'347230': _347230;
|
||||
}
|
||||
|
||||
interface _347230 {
|
||||
|
||||
@@ -1,64 +1,61 @@
|
||||
// 设置歌手背景图片
|
||||
export const setBackgroundImg = (url: String) => {
|
||||
return 'background-image:' + 'url(' + url + ')'
|
||||
}
|
||||
return `background-image:url(${url})`;
|
||||
};
|
||||
// 设置动画类型
|
||||
export const setAnimationClass = (type: String) => {
|
||||
return 'animate__animated ' + type
|
||||
}
|
||||
return `animate__animated ${type}`;
|
||||
};
|
||||
// 设置动画延时
|
||||
export const setAnimationDelay = (index: number = 6, time: number = 50) => {
|
||||
return 'animation-delay:' + index * time + 'ms'
|
||||
}
|
||||
return `animation-delay:${index * time}ms`;
|
||||
};
|
||||
|
||||
//将秒转换为分钟和秒
|
||||
// 将秒转换为分钟和秒
|
||||
export const secondToMinute = (s: number) => {
|
||||
if (!s) {
|
||||
return '00:00'
|
||||
return '00:00';
|
||||
}
|
||||
let minute: number = Math.floor(s / 60)
|
||||
let second: number = Math.floor(s % 60)
|
||||
let minuteStr: string =
|
||||
minute > 9 ? minute.toString() : '0' + minute.toString()
|
||||
let secondStr: string =
|
||||
second > 9 ? second.toString() : '0' + second.toString()
|
||||
return minuteStr + ':' + secondStr
|
||||
}
|
||||
const minute: number = Math.floor(s / 60);
|
||||
const second: number = Math.floor(s % 60);
|
||||
const minuteStr: string = minute > 9 ? minute.toString() : `0${minute.toString()}`;
|
||||
const secondStr: string = second > 9 ? second.toString() : `0${second.toString()}`;
|
||||
return `${minuteStr}:${secondStr}`;
|
||||
};
|
||||
|
||||
// 格式化数字 千,万, 百万, 千万,亿
|
||||
export const formatNumber = (num: any) => {
|
||||
num = num * 1
|
||||
num *= 1;
|
||||
if (num < 10000) {
|
||||
return num
|
||||
return num;
|
||||
}
|
||||
if (num < 100000000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
return `${(num / 10000).toFixed(1)}万`;
|
||||
}
|
||||
return (num / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
const windowData = window as any
|
||||
return `${(num / 100000000).toFixed(1)}亿`;
|
||||
};
|
||||
const windowData = window as any;
|
||||
export const getIsMc = () => {
|
||||
if (!windowData.electron) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
if (windowData.electron.ipcRenderer.getStoreValue('set').isProxy) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
}
|
||||
const ProxyUrl =
|
||||
import.meta.env.VITE_API_PROXY + '' || 'http://110.42.251.190:9856'
|
||||
return false;
|
||||
};
|
||||
const ProxyUrl = import.meta.env.VITE_API_PROXY || 'http://110.42.251.190:9856';
|
||||
|
||||
export const getMusicProxyUrl = (url: string) => {
|
||||
if (!getIsMc()) {
|
||||
return url
|
||||
return url;
|
||||
}
|
||||
const PUrl = url.split('').join('+')
|
||||
return `${ProxyUrl}/mc?url=${PUrl}`
|
||||
}
|
||||
const PUrl = url.split('').join('+');
|
||||
return `${ProxyUrl}/mc?url=${PUrl}`;
|
||||
};
|
||||
|
||||
export const getImgUrl = computed(() => (url: string, size: string = '') => {
|
||||
const bdUrl = 'https://image.baidu.com/search/down?url='
|
||||
const imgUrl = encodeURIComponent(`${url}?param=${size}`)
|
||||
return `${bdUrl}${imgUrl}`
|
||||
})
|
||||
const bdUrl = 'https://image.baidu.com/search/down?url=';
|
||||
const imgUrl = encodeURIComponent(`${url}?param=${size}`);
|
||||
return `${bdUrl}${imgUrl}`;
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import axios from "axios";
|
||||
let baseURL = import.meta.env.VITE_API + "";
|
||||
import axios from 'axios';
|
||||
|
||||
const baseURL = `${import.meta.env.VITE_API}`;
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: baseURL,
|
||||
baseURL,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
@@ -11,12 +12,12 @@ request.interceptors.request.use(
|
||||
(config) => {
|
||||
// 在请求发送之前做一些处理
|
||||
// 在get请求params中添加timestamp
|
||||
if (config.method === "get") {
|
||||
if (config.method === 'get') {
|
||||
config.params = {
|
||||
...config.params,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
let token = localStorage.getItem("token");
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.params.cookie = token;
|
||||
}
|
||||
@@ -27,7 +28,7 @@ request.interceptors.request.use(
|
||||
(error) => {
|
||||
// 当请求异常时做一些处理
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default request;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import axios from "axios";
|
||||
let baseURL = import.meta.env.VITE_API_MT + "";
|
||||
import axios from 'axios';
|
||||
|
||||
const baseURL = `${import.meta.env.VITE_API_MT}`;
|
||||
const request = axios.create({
|
||||
baseURL: baseURL,
|
||||
baseURL,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
@@ -13,7 +14,7 @@ request.interceptors.request.use(
|
||||
(error) => {
|
||||
// 当请求异常时做一些处理
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default request;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import axios from "axios"
|
||||
let baseURL = import.meta.env.VITE_API_MUSIC + ""
|
||||
import axios from 'axios';
|
||||
|
||||
const baseURL = `${import.meta.env.VITE_API_MUSIC}`;
|
||||
const request = axios.create({
|
||||
baseURL: baseURL,
|
||||
baseURL,
|
||||
timeout: 10000,
|
||||
})
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
return config
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
// 当请求异常时做一些处理
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
export default request
|
||||
export default request;
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="history-page">
|
||||
<div class="title">播放历史</div>
|
||||
<n-button @click="openLyric">打开歌词</n-button>
|
||||
<n-scrollbar :size="100">
|
||||
<div class="history-list-content" :class="setAnimationClass('animate__bounceInLeft')">
|
||||
<div class="history-item" v-for="(item, index) in musicList" :key="item.id"
|
||||
:class="setAnimationClass('animate__bounceIn')" :style="setAnimationDelay(index, 30)">
|
||||
<div
|
||||
v-for="(item, index) in musicList"
|
||||
:key="item.id"
|
||||
class="history-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(index, 30)"
|
||||
>
|
||||
<song-item class="history-item-content" :item="item" />
|
||||
<div class="history-item-count">
|
||||
{{ item.count }}
|
||||
@@ -19,33 +25,33 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook'
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
const {delMusic, musicList} = useMusicHistory();
|
||||
const { delMusic, musicList } = useMusicHistory();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.history-page{
|
||||
.history-page {
|
||||
@apply h-full w-full pt-2;
|
||||
.title{
|
||||
.title {
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
|
||||
.history-list-content{
|
||||
.history-list-content {
|
||||
@apply px-4 mt-2;
|
||||
.history-item{
|
||||
.history-item {
|
||||
@apply flex items-center justify-between;
|
||||
&-content{
|
||||
&-content {
|
||||
@apply flex-1;
|
||||
}
|
||||
&-count{
|
||||
&-count {
|
||||
@apply px-4 text-lg;
|
||||
}
|
||||
&-delete{
|
||||
&-delete {
|
||||
@apply cursor-pointer rounded-full border-2 border-gray-400 w-8 h-8 flex justify-center items-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<div class="main-page">
|
||||
<!-- 推荐歌手 -->
|
||||
<recommend-singer />
|
||||
<div class="main-content">
|
||||
<!-- 歌单分类列表 -->
|
||||
<playlist-type />
|
||||
<!-- 本周最热音乐 -->
|
||||
<recommend-songlist />
|
||||
<!-- 推荐最新专辑 -->
|
||||
<recommend-album />
|
||||
</div>
|
||||
<!-- 推荐歌手 -->
|
||||
<recommend-singer />
|
||||
<div class="main-content">
|
||||
<!-- 歌单分类列表 -->
|
||||
<playlist-type />
|
||||
<!-- 本周最热音乐 -->
|
||||
<recommend-songlist />
|
||||
<!-- 推荐最新专辑 -->
|
||||
<recommend-album />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const RecommendSinger = defineAsyncComponent(() => import("@/components/RecommendSinger.vue"));
|
||||
const PlaylistType = defineAsyncComponent(() => import("@/components/PlaylistType.vue"));
|
||||
const RecommendSonglist = defineAsyncComponent(() => import("@/components/RecommendSonglist.vue"));
|
||||
const RecommendAlbum = defineAsyncComponent(() => import("@/components/RecommendAlbum.vue"));
|
||||
const RecommendSinger = defineAsyncComponent(() => import('@/components/RecommendSinger.vue'));
|
||||
const PlaylistType = defineAsyncComponent(() => import('@/components/PlaylistType.vue'));
|
||||
const RecommendSonglist = defineAsyncComponent(() => import('@/components/RecommendSonglist.vue'));
|
||||
const RecommendAlbum = defineAsyncComponent(() => import('@/components/RecommendAlbum.vue'));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-page{
|
||||
.main-page {
|
||||
@apply h-full w-full;
|
||||
}
|
||||
.main-content {
|
||||
|
||||
@@ -1,75 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import { getRecommendList, getListDetail, getListByCat } from '@/api/list'
|
||||
import type { IRecommendItem } from "@/type/list";
|
||||
import type { IListDetail } from "@/type/listDetail";
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl, formatNumber } from "@/utils";
|
||||
import { useRoute } from 'vue-router';
|
||||
import MusicList from "@/components/MusicList.vue";
|
||||
|
||||
import { getListByCat, getListDetail, getRecommendList } from '@/api/list';
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
import MusicList from '@/components/MusicList.vue';
|
||||
import type { IRecommendItem } from '@/type/list';
|
||||
import type { IListDetail } from '@/type/listDetail';
|
||||
import { formatNumber, getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
const recommendList = ref()
|
||||
const showMusic = ref(false)
|
||||
const recommendList = ref();
|
||||
const showMusic = ref(false);
|
||||
|
||||
const recommendItem = ref<IRecommendItem>()
|
||||
const listDetail = ref<IListDetail>()
|
||||
const recommendItem = ref<IRecommendItem>();
|
||||
const listDetail = ref<IListDetail>();
|
||||
const selectRecommendItem = async (item: IRecommendItem) => {
|
||||
showMusic.value = true
|
||||
const { data } = await getListDetail(item.id)
|
||||
recommendItem.value = item
|
||||
listDetail.value = data
|
||||
}
|
||||
showMusic.value = true;
|
||||
const { data } = await getListDetail(item.id);
|
||||
recommendItem.value = item;
|
||||
listDetail.value = data;
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const listTitle = ref(route.query.type || "歌单列表");
|
||||
const listTitle = ref(route.query.type || '歌单列表');
|
||||
|
||||
const loadList = async (type: any) => {
|
||||
const params = {
|
||||
cat: type || '',
|
||||
limit: 30,
|
||||
offset: 0
|
||||
}
|
||||
offset: 0,
|
||||
};
|
||||
const { data } = await getListByCat(params);
|
||||
recommendList.value = data.playlists
|
||||
}
|
||||
recommendList.value = data.playlists;
|
||||
};
|
||||
|
||||
if (route.query.type) {
|
||||
loadList(route.query.type)
|
||||
loadList(route.query.type);
|
||||
} else {
|
||||
getRecommendList().then((res: { data: { result: any; }; }) => {
|
||||
recommendList.value = res.data.result
|
||||
})
|
||||
getRecommendList().then((res: { data: { result: any } }) => {
|
||||
recommendList.value = res.data.result;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
async newParams => {
|
||||
if(newParams.type){
|
||||
const params = {
|
||||
tag: newParams.type || '',
|
||||
limit: 30,
|
||||
before: 0
|
||||
}
|
||||
async (newParams) => {
|
||||
if (newParams.type) {
|
||||
loadList(newParams.type);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="list-page">
|
||||
<div
|
||||
class="recommend-title"
|
||||
:class="setAnimationClass('animate__bounceInLeft')"
|
||||
>{{ listTitle }}</div>
|
||||
<div class="recommend-title" :class="setAnimationClass('animate__bounceInLeft')">{{ listTitle }}</div>
|
||||
<!-- 歌单列表 -->
|
||||
<n-scrollbar class="recommend" @click="showMusic = false" :size="100">
|
||||
<div class="recommend-list" v-if="recommendList">
|
||||
<n-scrollbar class="recommend" :size="100" @click="showMusic = false">
|
||||
<div v-if="recommendList" class="recommend-list">
|
||||
<div
|
||||
v-for="(item, index) in recommendList"
|
||||
:key="item.id"
|
||||
class="recommend-item"
|
||||
v-for="(item,index) in recommendList"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(index, 30)"
|
||||
@click.stop="selectRecommendItem(item)"
|
||||
@@ -77,7 +68,7 @@ watch(
|
||||
<div class="recommend-item-img">
|
||||
<n-image
|
||||
class="recommend-item-img-img"
|
||||
:src="getImgUrl( (item.picUrl || item.coverImgUrl), '200y200')"
|
||||
:src="getImgUrl(item.picUrl || item.coverImgUrl, '200y200')"
|
||||
width="200"
|
||||
height="200"
|
||||
lazy
|
||||
@@ -91,9 +82,14 @@ watch(
|
||||
<div class="recommend-item-title">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
<MusicList v-if="listDetail?.playlist" v-model:show="showMusic" :name="listDetail?.playlist.name" :song-list="listDetail?.playlist.tracks" />
|
||||
<music-list
|
||||
v-if="listDetail?.playlist"
|
||||
v-model:show="showMusic"
|
||||
:name="listDetail?.playlist.name"
|
||||
:song-list="listDetail?.playlist.tracks"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -150,5 +146,4 @@ watch(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,170 +1,165 @@
|
||||
<script lang="ts" setup>
|
||||
import { getQrKey, createQr, checkQr, getLoginStatus } from '@/api/login'
|
||||
import { onMounted } from '@vue/runtime-core';
|
||||
import { getUserDetail, loginByCellphone } from '@/api/login';
|
||||
import { useStore } from 'vuex';
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
|
||||
const message = useMessage()
|
||||
const store = useStore();
|
||||
const router = useRouter()
|
||||
|
||||
const qrUrl = ref<string>()
|
||||
onMounted(() => {
|
||||
loadLogin()
|
||||
})
|
||||
|
||||
const loadLogin = async () => {
|
||||
const qrKey = await getQrKey()
|
||||
const key = qrKey.data.data.unikey
|
||||
const { data } = await createQr(key)
|
||||
qrUrl.value = data.data.qrimg
|
||||
timerIsQr(key)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const timerIsQr = (key: string) => {
|
||||
const timer = setInterval(async () => {
|
||||
const { data } = await checkQr(key)
|
||||
|
||||
if (data.code === 800) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
if (data.code === 803) {
|
||||
// 将token存入localStorage
|
||||
localStorage.setItem('token', data.cookie)
|
||||
const user = await getUserDetail()
|
||||
store.state.user = user.data.profile
|
||||
message.success('登录成功')
|
||||
|
||||
await getLoginStatus().then(res => {
|
||||
console.log(res);
|
||||
})
|
||||
clearInterval(timer)
|
||||
setTimeout(() => {
|
||||
router.push('/user')
|
||||
}, 1000);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
||||
// 是否扫码登陆
|
||||
const isQr = ref(true)
|
||||
const chooseQr = () => {
|
||||
isQr.value = !isQr.value
|
||||
}
|
||||
|
||||
// 手机号登录
|
||||
const phone = ref('')
|
||||
const password = ref('')
|
||||
const loginPhone = async () => {
|
||||
const { data } = await loginByCellphone(phone.value, password.value)
|
||||
if (data.code === 200) {
|
||||
message.success('登录成功')
|
||||
store.state.user = data.profile
|
||||
localStorage.setItem('token', data.cookie)
|
||||
setTimeout(() => {
|
||||
router.push('/user')
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="phone-login">
|
||||
<div class="bg"></div>
|
||||
<div class="content">
|
||||
<div class="phone" v-if="isQr" :class="setAnimationClass('animate__fadeInUp')">
|
||||
<div class="login-title">扫码登陆</div>
|
||||
<img class="qr-img" :src="qrUrl" />
|
||||
<div class="text">使用网易云APP扫码登录</div>
|
||||
</div>
|
||||
<div class="phone" v-else :class="setAnimationClass('animate__fadeInUp')">
|
||||
<div class="login-title">手机号登录</div>
|
||||
<div class="phone-page">
|
||||
<input v-model="phone" class="phone-input" type="text" placeholder="手机号" />
|
||||
<input v-model="password" class="phone-input" type="password" placeholder="密码" />
|
||||
</div>
|
||||
<n-button class="btn-login" @click="loginPhone()">登录</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="title" @click="chooseQr()">{{ isQr ? '手机号登录' : '扫码登录' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
@apply flex flex-col items-center justify-center p-20 pt-20;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
@apply text-2xl font-bold mb-6;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply mt-4 text-green-500 text-xs;
|
||||
}
|
||||
|
||||
.phone-login {
|
||||
width: 350px;
|
||||
height: 550px;
|
||||
@apply rounded-2xl rounded-b-none bg-cover bg-no-repeat relative overflow-hidden;
|
||||
background-image: url(http://tva4.sinaimg.cn/large/006opRgRgy1gw8nf6no7uj30rs15n0x7.jpg);
|
||||
background-color: #383838;
|
||||
box-shadow: inset 0px 0px 20px 5px #0000005e;
|
||||
|
||||
.bg {
|
||||
@apply absolute w-full h-full bg-black opacity-30;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
width: 200%;
|
||||
height: 250px;
|
||||
bottom: -180px;
|
||||
border-radius: 50%;
|
||||
left: 50%;
|
||||
padding: 10px;
|
||||
transform: translateX(-50%);
|
||||
color: #ffffff99;
|
||||
@apply absolute bg-black flex justify-center text-lg font-bold cursor-pointer;
|
||||
box-shadow: 10px 0px 20px #000000a9;
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply absolute w-full h-full p-4 flex flex-col items-center justify-center pb-20 text-center;
|
||||
.qr-img {
|
||||
@apply opacity-80 rounded-2xl cursor-pointer;
|
||||
}
|
||||
|
||||
.phone {
|
||||
animation-duration: 0.5s;
|
||||
&-page {
|
||||
background-color: #ffffffdd;
|
||||
width: 250px;
|
||||
@apply rounded-2xl overflow-hidden;
|
||||
}
|
||||
|
||||
&-input {
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
@apply w-full text-black px-4 outline-none;
|
||||
}
|
||||
}
|
||||
.btn-login {
|
||||
width: 250px;
|
||||
height: 40px;
|
||||
@apply mt-10 text-white rounded-xl bg-black opacity-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { checkQr, createQr, getLoginStatus, getQrKey, getUserDetail, loginByCellphone } from '@/api/login';
|
||||
import { setAnimationClass } from '@/utils';
|
||||
|
||||
const message = useMessage();
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
const qrUrl = ref<string>();
|
||||
onMounted(() => {
|
||||
loadLogin();
|
||||
});
|
||||
|
||||
const loadLogin = async () => {
|
||||
const qrKey = await getQrKey();
|
||||
const key = qrKey.data.data.unikey;
|
||||
const { data } = await createQr(key);
|
||||
qrUrl.value = data.data.qrimg;
|
||||
timerIsQr(key);
|
||||
};
|
||||
|
||||
const timerIsQr = (key: string) => {
|
||||
const timer = setInterval(async () => {
|
||||
const { data } = await checkQr(key);
|
||||
|
||||
if (data.code === 800) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
if (data.code === 803) {
|
||||
// 将token存入localStorage
|
||||
localStorage.setItem('token', data.cookie);
|
||||
const user = await getUserDetail();
|
||||
store.state.user = user.data.profile;
|
||||
message.success('登录成功');
|
||||
|
||||
await getLoginStatus().then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
clearInterval(timer);
|
||||
setTimeout(() => {
|
||||
router.push('/user');
|
||||
}, 1000);
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// 是否扫码登陆
|
||||
const isQr = ref(true);
|
||||
const chooseQr = () => {
|
||||
isQr.value = !isQr.value;
|
||||
};
|
||||
|
||||
// 手机号登录
|
||||
const phone = ref('');
|
||||
const password = ref('');
|
||||
const loginPhone = async () => {
|
||||
const { data } = await loginByCellphone(phone.value, password.value);
|
||||
if (data.code === 200) {
|
||||
message.success('登录成功');
|
||||
store.state.user = data.profile;
|
||||
localStorage.setItem('token', data.cookie);
|
||||
setTimeout(() => {
|
||||
router.push('/user');
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="phone-login">
|
||||
<div class="bg"></div>
|
||||
<div class="content">
|
||||
<div v-if="isQr" class="phone" :class="setAnimationClass('animate__fadeInUp')">
|
||||
<div class="login-title">扫码登陆</div>
|
||||
<img class="qr-img" :src="qrUrl" />
|
||||
<div class="text">使用网易云APP扫码登录</div>
|
||||
</div>
|
||||
<div v-else class="phone" :class="setAnimationClass('animate__fadeInUp')">
|
||||
<div class="login-title">手机号登录</div>
|
||||
<div class="phone-page">
|
||||
<input v-model="phone" class="phone-input" type="text" placeholder="手机号" />
|
||||
<input v-model="password" class="phone-input" type="password" placeholder="密码" />
|
||||
</div>
|
||||
<n-button class="btn-login" @click="loginPhone()">登录</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="title" @click="chooseQr()">{{ isQr ? '手机号登录' : '扫码登录' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
@apply flex flex-col items-center justify-center p-20 pt-20;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
@apply text-2xl font-bold mb-6;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply mt-4 text-green-500 text-xs;
|
||||
}
|
||||
|
||||
.phone-login {
|
||||
width: 350px;
|
||||
height: 550px;
|
||||
@apply rounded-2xl rounded-b-none bg-cover bg-no-repeat relative overflow-hidden;
|
||||
background-image: url(http://tva4.sinaimg.cn/large/006opRgRgy1gw8nf6no7uj30rs15n0x7.jpg);
|
||||
background-color: #383838;
|
||||
box-shadow: inset 0px 0px 20px 5px #0000005e;
|
||||
|
||||
.bg {
|
||||
@apply absolute w-full h-full bg-black opacity-30;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
width: 200%;
|
||||
height: 250px;
|
||||
bottom: -180px;
|
||||
border-radius: 50%;
|
||||
left: 50%;
|
||||
padding: 10px;
|
||||
transform: translateX(-50%);
|
||||
color: #ffffff99;
|
||||
@apply absolute bg-black flex justify-center text-lg font-bold cursor-pointer;
|
||||
box-shadow: 10px 0px 20px #000000a9;
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply absolute w-full h-full p-4 flex flex-col items-center justify-center pb-20 text-center;
|
||||
.qr-img {
|
||||
@apply opacity-80 rounded-2xl cursor-pointer;
|
||||
}
|
||||
|
||||
.phone {
|
||||
animation-duration: 0.5s;
|
||||
&-page {
|
||||
background-color: #ffffffdd;
|
||||
width: 250px;
|
||||
@apply rounded-2xl overflow-hidden;
|
||||
}
|
||||
|
||||
&-input {
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
@apply w-full text-black px-4 outline-none;
|
||||
}
|
||||
}
|
||||
.btn-login {
|
||||
width: 250px;
|
||||
height: 40px;
|
||||
@apply mt-10 text-white rounded-xl bg-black opacity-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
182
src/views/lyric/index.vue
Normal file
182
src/views/lyric/index.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="lyric-window" :class="theme">
|
||||
<div class="lyric-bar" :class="{ 'lyric-bar-hover': isDrag }">
|
||||
<div class="buttons">
|
||||
<!-- <div class="music-buttons">
|
||||
<div @click="handlePrev">
|
||||
<i class="iconfont icon-prev"></i>
|
||||
</div>
|
||||
<div class="music-buttons-play" @click="playMusicEvent">
|
||||
<i class="iconfont icon" :class="play ? 'icon-stop' : 'icon-play'"></i>
|
||||
</div>
|
||||
<div @click="handleEnded">
|
||||
<i class="iconfont icon-next"></i>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="check-theme" @click="checkTheme">
|
||||
<i v-if="theme === 'light'" class="icon ri-sun-line"></i>
|
||||
<i v-else class="icon ri-moon-line"></i>
|
||||
</div>
|
||||
<div class="button-move">
|
||||
<i class="icon ri-drag-move-2-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="lyricData.lrcArray[lyricData.nowIndex]" class="lyric-box">
|
||||
<h2 class="lyric lyric-current">{{ lyricData.lrcArray[lyricData.nowIndex].text }}</h2>
|
||||
<p class="lyric-current">{{ lyricData.currentLrc.trText }}</p>
|
||||
<template v-if="lyricData.lrcArray[lyricData.nowIndex + 1]">
|
||||
<h2 class="lyric lyric-next">
|
||||
{{ lyricData.lrcArray[lyricData.nowIndex + 1].text }}
|
||||
</h2>
|
||||
<p class="lyric-next">{{ lyricData.nextLrc.trText }}</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useIpcRenderer } from '@vueuse/electron';
|
||||
|
||||
const windowData = window as any;
|
||||
const ipcRenderer = useIpcRenderer();
|
||||
|
||||
const lyricData = ref({
|
||||
currentLrc: {
|
||||
text: '',
|
||||
trText: '',
|
||||
},
|
||||
nextLrc: {
|
||||
text: '',
|
||||
trText: '',
|
||||
},
|
||||
currentTime: 0,
|
||||
nextTime: 0,
|
||||
nowTime: 0,
|
||||
allTime: 0,
|
||||
startCurrentTime: 0,
|
||||
lrcArray: [] as any,
|
||||
lrcTimeArray: [] as any,
|
||||
nowIndex: 0,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
ipcRenderer.on('receive-lyric', (event, data) => {
|
||||
try {
|
||||
lyricData.value = JSON.parse(data);
|
||||
console.log('lyricData.value', lyricData.value);
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
const theme = ref('dark');
|
||||
const checkTheme = () => {
|
||||
if (theme.value === 'light') {
|
||||
theme.value = 'dark';
|
||||
} else {
|
||||
theme.value = 'light';
|
||||
}
|
||||
};
|
||||
// const drag = (event: MouseEvent) => {
|
||||
// windowData.electronAPI.dragStart(event);
|
||||
// };
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lyric-window {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@apply overflow-hidden text-gray-600 hover:bg-gray-400 hover:bg-opacity-75;
|
||||
&:hover .lyric-bar {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply text-xl hover:text-white;
|
||||
}
|
||||
|
||||
.lyric-bar {
|
||||
background-color: #b1b1b1;
|
||||
@apply flex flex-col justify-center items-center;
|
||||
width: 100vw;
|
||||
height: 100px;
|
||||
opacity: 0;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.lyric-bar-hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.buttons {
|
||||
width: 100vw;
|
||||
height: 100px;
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
.button-move {
|
||||
-webkit-app-region: drag;
|
||||
cursor: move;
|
||||
}
|
||||
.music-buttons {
|
||||
@apply mx-6;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
.iconfont {
|
||||
@apply text-2xl hover:text-green-500 transition;
|
||||
}
|
||||
|
||||
@apply flex items-center;
|
||||
|
||||
> div {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
&-play {
|
||||
@apply flex justify-center items-center w-12 h-12 rounded-full mx-4 hover:bg-green-500 transition;
|
||||
background: #383838;
|
||||
}
|
||||
}
|
||||
.check-theme {
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lyric {
|
||||
text-shadow: 0 0 10px #fff;
|
||||
font-size: 4vw;
|
||||
@apply font-bold m-0 p-0 whitespace-nowrap select-none pointer-events-none;
|
||||
}
|
||||
|
||||
.lyric-current {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.lyric-next {
|
||||
color: #999;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.lyric-window.dark {
|
||||
.lyric {
|
||||
text-shadow: none;
|
||||
}
|
||||
.lyric-current {
|
||||
color: #fff;
|
||||
}
|
||||
.lyric-next {
|
||||
color: #cecece;
|
||||
}
|
||||
}
|
||||
// .lyric-box {
|
||||
// writing-mode: vertical-rl;
|
||||
// }
|
||||
</style>
|
||||
@@ -5,10 +5,22 @@
|
||||
</div>
|
||||
<n-scrollbar :size="100">
|
||||
<div class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
|
||||
<div class="mv-item" v-for="(item, index) in mvList" :key="item.id"
|
||||
:class="setAnimationClass('animate__bounceIn')" :style="setAnimationDelay(index, 30)">
|
||||
<div
|
||||
v-for="(item, index) in mvList"
|
||||
:key="item.id"
|
||||
class="mv-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(index, 30)"
|
||||
>
|
||||
<div class="mv-item-img" @click="handleShowMv(item)">
|
||||
<n-image class="mv-item-img-img" :src="getImgUrl((item.cover), '200y112')" lazy preview-disabled width="200" height="112" />
|
||||
<n-image
|
||||
class="mv-item-img-img"
|
||||
:src="getImgUrl(item.cover, '200y112')"
|
||||
lazy
|
||||
preview-disabled
|
||||
width="200"
|
||||
height="112"
|
||||
/>
|
||||
<div class="top">
|
||||
<div class="play-count">{{ formatNumber(item.playCount) }}</div>
|
||||
<i class="iconfont icon-videofill"></i>
|
||||
@@ -35,39 +47,39 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { getTopMv, getMvUrl } from '@/api/mv';
|
||||
import { IMvItem } from '@/type/mv';
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl, formatNumber } from "@/utils";
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const showMv = ref(false)
|
||||
const mvList = ref<Array<IMvItem>>([])
|
||||
const playMvItem = ref<IMvItem>()
|
||||
const playMvUrl = ref<string>()
|
||||
const store = useStore()
|
||||
import { getMvUrl, getTopMv } from '@/api/mv';
|
||||
import { IMvItem } from '@/type/mv';
|
||||
import { formatNumber, getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
const showMv = ref(false);
|
||||
const mvList = ref<Array<IMvItem>>([]);
|
||||
const playMvItem = ref<IMvItem>();
|
||||
const playMvUrl = ref<string>();
|
||||
const store = useStore();
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await getTopMv(30)
|
||||
mvList.value = res.data.data
|
||||
console.log('mvList.value', mvList.value)
|
||||
})
|
||||
const res = await getTopMv(30);
|
||||
mvList.value = res.data.data;
|
||||
console.log('mvList.value', mvList.value);
|
||||
});
|
||||
|
||||
const handleShowMv = async (item: IMvItem) => {
|
||||
store.commit('setIsPlay', false)
|
||||
store.commit('setPlayMusic', false)
|
||||
showMv.value = true
|
||||
const res = await getMvUrl(item.id)
|
||||
store.commit('setIsPlay', false);
|
||||
store.commit('setPlayMusic', false);
|
||||
showMv.value = true;
|
||||
const res = await getMvUrl(item.id);
|
||||
playMvItem.value = item;
|
||||
playMvUrl.value = res.data.data.url
|
||||
}
|
||||
playMvUrl.value = res.data.data.url;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
showMv.value = false
|
||||
showMv.value = false;
|
||||
if (store.state.playMusicUrl) {
|
||||
store.commit('setIsPlay', true)
|
||||
store.commit('setIsPlay', true);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -157,4 +169,5 @@ const close = () => {
|
||||
.mv-detail-title:hover {
|
||||
@apply top-0;
|
||||
}
|
||||
}</style>
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,74 +1,63 @@
|
||||
<template>
|
||||
<div class="search-page">
|
||||
<n-layout
|
||||
class="hot-search"
|
||||
:class="setAnimationClass('animate__fadeInDown')"
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
<div class="title">热搜列表</div>
|
||||
<div class="hot-search-list">
|
||||
<template v-for="(item, index) in hotSearchData?.data">
|
||||
<div
|
||||
:class="setAnimationClass('animate__bounceInLeft')"
|
||||
:style="setAnimationDelay(index, 10)"
|
||||
class="hot-search-item"
|
||||
@click.stop="clickHotKeyword(item.searchWord)"
|
||||
>
|
||||
<span
|
||||
class="hot-search-item-count"
|
||||
:class="{ 'hot-search-item-count-3': index < 3 }"
|
||||
>{{ index + 1 }}</span>
|
||||
{{ item.searchWord }}
|
||||
</div>
|
||||
<div class="search-page">
|
||||
<n-layout class="hot-search" :class="setAnimationClass('animate__fadeInDown')" :native-scrollbar="false">
|
||||
<div class="title">热搜列表</div>
|
||||
<div class="hot-search-list">
|
||||
<template v-for="(item, index) in hotSearchData?.data" :key="index">
|
||||
<div
|
||||
:class="setAnimationClass('animate__bounceInLeft')"
|
||||
:style="setAnimationDelay(index, 10)"
|
||||
class="hot-search-item"
|
||||
@click.stop="clickHotKeyword(item.searchWord)"
|
||||
>
|
||||
<span class="hot-search-item-count" :class="{ 'hot-search-item-count-3': index < 3 }">{{ index + 1 }}</span>
|
||||
{{ item.searchWord }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</n-layout>
|
||||
<!-- 搜索到的歌曲列表 -->
|
||||
<n-layout class="search-list" :class="setAnimationClass('animate__fadeInUp')" :native-scrollbar="false">
|
||||
<div class="title">{{ hotKeyword }}</div>
|
||||
<div class="search-list-box">
|
||||
<template v-if="searchDetail">
|
||||
<div
|
||||
v-for="(item, index) in searchDetail?.songs"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInRight')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<song-item :item="item" @play="handlePlay" />
|
||||
</div>
|
||||
<template v-for="(list, key) in searchDetail">
|
||||
<template v-if="key.toString() !== 'songs'">
|
||||
<div
|
||||
v-for="(item, index) in list"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInRight')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SearchItem :item="item" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</n-layout>
|
||||
<!-- 搜索到的歌曲列表 -->
|
||||
<n-layout
|
||||
class="search-list"
|
||||
:class="setAnimationClass('animate__fadeInUp')"
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
<div class="title">{{ hotKeyword }}</div>
|
||||
<div class="search-list-box">
|
||||
<template v-if="searchDetail">
|
||||
<div
|
||||
v-for="(item, index) in searchDetail?.songs"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInRight')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SongItem :item="item" @play="handlePlay"/>
|
||||
</div>
|
||||
<template v-for="(list, key) in searchDetail">
|
||||
<template v-if="key.toString() !== 'songs'">
|
||||
<div
|
||||
v-for="(item, index) in list"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInRight')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SearchItem :item="item"/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</n-layout>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</n-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getSearch } from "@/api/search";
|
||||
import type { IHotSearch } from "@/type/search";
|
||||
import { getHotSearch } from "@/api/home";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import SongItem from "@/components/common/SongItem.vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useDateFormat } from '@vueuse/core'
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { getHotSearch } from '@/api/home';
|
||||
import { getSearch } from '@/api/search';
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import type { IHotSearch } from '@/type/search';
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -78,105 +67,104 @@ const searchType = ref(Number(route.query.type) || 1);
|
||||
// 热搜列表
|
||||
const hotSearchData = ref<IHotSearch>();
|
||||
const loadHotSearch = async () => {
|
||||
const { data } = await getHotSearch();
|
||||
hotSearchData.value = data;
|
||||
const { data } = await getHotSearch();
|
||||
hotSearchData.value = data;
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
loadHotSearch();
|
||||
loadHotSearch();
|
||||
});
|
||||
|
||||
const hotKeyword = ref(route.query.keyword || "搜索列表");
|
||||
const hotKeyword = ref(route.query.keyword || '搜索列表');
|
||||
const clickHotKeyword = (keyword: string) => {
|
||||
hotKeyword.value = keyword;
|
||||
router.push({
|
||||
path: "/search",
|
||||
query: {
|
||||
keyword: keyword,
|
||||
type: 1
|
||||
},
|
||||
});
|
||||
// isHotSearchList.value = false;
|
||||
hotKeyword.value = keyword;
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
keyword,
|
||||
type: 1,
|
||||
},
|
||||
});
|
||||
// isHotSearchList.value = false;
|
||||
};
|
||||
|
||||
const dateFormat = (time:any) => useDateFormat(time, 'YYYY.MM.DD').value
|
||||
const dateFormat = (time: any) => useDateFormat(time, 'YYYY.MM.DD').value;
|
||||
const loadSearch = async (keywords: any) => {
|
||||
hotKeyword.value = keywords;
|
||||
searchDetail.value = undefined;
|
||||
if (!keywords) return;
|
||||
const { data } = await getSearch({keywords, type:searchType.value});
|
||||
hotKeyword.value = keywords;
|
||||
searchDetail.value = undefined;
|
||||
if (!keywords) return;
|
||||
const { data } = await getSearch({ keywords, type: searchType.value });
|
||||
|
||||
const songs = data.result.songs || [];
|
||||
const albums = data.result.albums || [];
|
||||
const songs = data.result.songs || [];
|
||||
const albums = data.result.albums || [];
|
||||
|
||||
// songs map 替换属性
|
||||
songs.map((item: any) => {
|
||||
item.picUrl = item.al.picUrl;
|
||||
item.song = item;
|
||||
item.artists = item.ar;
|
||||
});
|
||||
albums.map((item: any) => {
|
||||
item.desc = `${item.artist.name } ${ item.company } ${dateFormat(item.publishTime)}`;
|
||||
});
|
||||
searchDetail.value = {
|
||||
songs,
|
||||
albums
|
||||
}
|
||||
// songs map 替换属性
|
||||
songs.forEach((item: any) => {
|
||||
item.picUrl = item.al.picUrl;
|
||||
item.song = item;
|
||||
item.artists = item.ar;
|
||||
});
|
||||
albums.forEach((item: any) => {
|
||||
item.desc = `${item.artist.name} ${item.company} ${dateFormat(item.publishTime)}`;
|
||||
});
|
||||
searchDetail.value = {
|
||||
songs,
|
||||
albums,
|
||||
};
|
||||
};
|
||||
|
||||
loadSearch(route.query.keyword);
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
async newParams => {
|
||||
searchType.value = Number(newParams.type || 1)
|
||||
loadSearch(newParams.keyword);
|
||||
}
|
||||
)
|
||||
() => route.query,
|
||||
async (newParams) => {
|
||||
searchType.value = Number(newParams.type || 1);
|
||||
loadSearch(newParams.keyword);
|
||||
},
|
||||
);
|
||||
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
const tracks = searchDetail.value?.songs || []
|
||||
store.commit('setPlayList', tracks)
|
||||
}
|
||||
const handlePlay = () => {
|
||||
const tracks = searchDetail.value?.songs || [];
|
||||
store.commit('setPlayList', tracks);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-page {
|
||||
@apply flex h-full;
|
||||
@apply flex h-full;
|
||||
}
|
||||
.hot-search {
|
||||
@apply mr-4 rounded-xl flex-1 overflow-hidden;
|
||||
background-color: #0d0d0d;
|
||||
animation-duration: 0.2s;
|
||||
min-width: 400px;
|
||||
height: 100%;
|
||||
&-list{
|
||||
@apply pb-28;
|
||||
}
|
||||
&-item {
|
||||
@apply px-4 py-3 text-lg hover:bg-gray-700 rounded-xl cursor-pointer;
|
||||
&-count {
|
||||
@apply text-green-500 inline-block ml-3 w-8;
|
||||
&-3 {
|
||||
@apply text-red-600 font-bold inline-block ml-3 w-8;
|
||||
}
|
||||
}
|
||||
@apply mr-4 rounded-xl flex-1 overflow-hidden;
|
||||
background-color: #0d0d0d;
|
||||
animation-duration: 0.2s;
|
||||
min-width: 400px;
|
||||
height: 100%;
|
||||
&-list {
|
||||
@apply pb-28;
|
||||
}
|
||||
&-item {
|
||||
@apply px-4 py-3 text-lg hover:bg-gray-700 rounded-xl cursor-pointer;
|
||||
&-count {
|
||||
@apply text-green-500 inline-block ml-3 w-8;
|
||||
&-3 {
|
||||
@apply text-red-600 font-bold inline-block ml-3 w-8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-list {
|
||||
@apply flex-1 rounded-xl;
|
||||
background-color: #0d0d0d;
|
||||
height: 100%;
|
||||
&-box{
|
||||
@apply pb-28;
|
||||
}
|
||||
@apply flex-1 rounded-xl;
|
||||
background-color: #0d0d0d;
|
||||
height: 100%;
|
||||
&-box {
|
||||
@apply pb-28;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-gray-200 text-xl font-bold my-2 mx-4;
|
||||
@apply text-gray-200 text-xl font-bold my-2 mx-4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="set-item-title">代理</div>
|
||||
<div class="set-item-content">无法听音乐时打开</div>
|
||||
</div>
|
||||
<n-switch v-model:value="setData.isProxy"/>
|
||||
<n-switch v-model:value="setData.isProxy" />
|
||||
</div>
|
||||
<div class="set-item">
|
||||
<div>
|
||||
@@ -30,35 +30,36 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import store from '@/store'
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const setData = ref(store.state.setData)
|
||||
const router = useRouter()
|
||||
import store from '@/store';
|
||||
|
||||
const setData = ref(store.state.setData);
|
||||
const router = useRouter();
|
||||
|
||||
const handelCancel = () => {
|
||||
router.back()
|
||||
}
|
||||
router.back();
|
||||
};
|
||||
|
||||
const windowData = window as any
|
||||
const windowData = window as any;
|
||||
|
||||
const handleSave = () => {
|
||||
store.commit('setSetData', setData.value)
|
||||
windowData.electronAPI.restart()
|
||||
}
|
||||
store.commit('setSetData', setData.value);
|
||||
windowData.electronAPI.restart();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.set-page{
|
||||
.set-page {
|
||||
@apply flex flex-col justify-center items-center pt-8;
|
||||
}
|
||||
.set-item{
|
||||
.set-item {
|
||||
@apply w-3/5 flex justify-between items-center mb-4;
|
||||
.set-item-title{
|
||||
.set-item-title {
|
||||
@apply text-gray-200 text-base;
|
||||
}
|
||||
.set-item-content{
|
||||
.set-item-content {
|
||||
@apply text-gray-400 text-sm;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,89 +1,87 @@
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from "vuex";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getUserDetail, getUserPlaylist, getUserRecord } from "@/api/user";
|
||||
import type { IUserDetail } from "@/type/user";
|
||||
import { computed, ref } from "vue";
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl } from "@/utils";
|
||||
import { getListDetail } from '@/api/list'
|
||||
import SongItem from "@/components/common/SongItem.vue";
|
||||
import MusicList from "@/components/MusicList.vue";
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { getListDetail } from '@/api/list';
|
||||
import { getUserDetail, getUserPlaylist, getUserRecord } from '@/api/user';
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import MusicList from '@/components/MusicList.vue';
|
||||
import type { Playlist } from '@/type/listDetail';
|
||||
import PlayBottom from "@/components/common/PlayBottom.vue";
|
||||
import type { IUserDetail } from '@/type/user';
|
||||
import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const userDetail = ref<IUserDetail>()
|
||||
const playList = ref<any[]>([])
|
||||
const recordList = ref()
|
||||
let user = store.state.user
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const userDetail = ref<IUserDetail>();
|
||||
const playList = ref<any[]>([]);
|
||||
const recordList = ref();
|
||||
let { user } = store.state;
|
||||
|
||||
const loadPage = async () => {
|
||||
if (!user) {
|
||||
router.push("/login")
|
||||
return
|
||||
router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: userData } = await getUserDetail(user.userId)
|
||||
userDetail.value = userData
|
||||
const { data: userData } = await getUserDetail(user.userId);
|
||||
userDetail.value = userData;
|
||||
|
||||
const { data: playlistData } = await getUserPlaylist(user.userId)
|
||||
playList.value = playlistData.playlist
|
||||
const { data: playlistData } = await getUserPlaylist(user.userId);
|
||||
playList.value = playlistData.playlist;
|
||||
|
||||
const { data: recordData } = await getUserRecord(user.userId)
|
||||
recordList.value = recordData.allData
|
||||
}
|
||||
const { data: recordData } = await getUserRecord(user.userId);
|
||||
recordList.value = recordData.allData;
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
const localUser = localStorage.getItem('user')
|
||||
store.state.user = localUser ? JSON.parse(localUser) : null
|
||||
user = store.state.user
|
||||
loadPage()
|
||||
})
|
||||
const localUser = localStorage.getItem('user');
|
||||
store.state.user = localUser ? JSON.parse(localUser) : null;
|
||||
user = store.state.user;
|
||||
loadPage();
|
||||
});
|
||||
|
||||
|
||||
const isShowList = ref(false)
|
||||
const list = ref<Playlist>()
|
||||
const isShowList = ref(false);
|
||||
const list = ref<Playlist>();
|
||||
// 展示歌单
|
||||
const showPlaylist = async (id: number) => {
|
||||
const { data } = await getListDetail(id)
|
||||
isShowList.value = true
|
||||
list.value = data.playlist
|
||||
}
|
||||
const { data } = await getListDetail(id);
|
||||
isShowList.value = true;
|
||||
list.value = data.playlist;
|
||||
};
|
||||
|
||||
// 格式化歌曲列表项
|
||||
const formatDetail = computed(() => (detail: any) => {
|
||||
let song = {
|
||||
const song = {
|
||||
artists: detail.ar,
|
||||
name: detail.al.name,
|
||||
id: detail.al.id,
|
||||
}
|
||||
};
|
||||
|
||||
detail.song = song
|
||||
detail.picUrl = detail.al.picUrl
|
||||
return detail
|
||||
})
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
const tracks = recordList.value || []
|
||||
store.commit('setPlayList', tracks)
|
||||
}
|
||||
detail.song = song;
|
||||
detail.picUrl = detail.al.picUrl;
|
||||
return detail;
|
||||
});
|
||||
|
||||
const handlePlay = () => {
|
||||
const tracks = recordList.value || [];
|
||||
store.commit('setPlayList', tracks);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="user-page" @click.stop="isShowList = false">
|
||||
<div
|
||||
class="left"
|
||||
v-if="userDetail"
|
||||
class="left"
|
||||
:class="setAnimationClass('animate__fadeInLeft')"
|
||||
:style="{ backgroundImage: `url(${getImgUrl(user.backgroundUrl)})` }"
|
||||
>
|
||||
<div class="page">
|
||||
<div class="user-name">{{ user.nickname }}</div>
|
||||
<div class="user-info">
|
||||
<n-avatar round :size="50" :src="getImgUrl(user.avatarUrl,'50y50')" />
|
||||
<n-avatar round :size="50" :src="getImgUrl(user.avatarUrl, '50y50')" />
|
||||
<div class="user-info-list">
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.profile.followeds }}</div>
|
||||
@@ -104,24 +102,14 @@ const handlePlay = (item: any) => {
|
||||
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
|
||||
<div class="play-list-title">创建的歌单</div>
|
||||
<n-scrollbar>
|
||||
<div
|
||||
class="play-list-item"
|
||||
v-for="(item,index) in playList"
|
||||
:key="index"
|
||||
@click="showPlaylist(item.id)"
|
||||
>
|
||||
<n-image
|
||||
:src="getImgUrl( item.coverImgUrl, '50y50')"
|
||||
class="play-list-item-img"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div v-for="(item, index) in playList" :key="index" class="play-list-item" @click="showPlaylist(item.id)">
|
||||
<n-image :src="getImgUrl(item.coverImgUrl, '50y50')" class="play-list-item-img" lazy preview-disabled />
|
||||
<div class="play-list-item-info">
|
||||
<div class="play-list-item-name">{{ item.name }}</div>
|
||||
<div class="play-list-item-count">{{ item.trackCount }}首,播放{{ item.playCount }}次</div>
|
||||
</div>
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,20 +119,20 @@ const handlePlay = (item: any) => {
|
||||
<div class="record-list">
|
||||
<n-scrollbar>
|
||||
<div
|
||||
class="record-item"
|
||||
v-for="(item, index) in recordList"
|
||||
:key="item.song.id"
|
||||
class="record-item"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SongItem class="song-item" :item="formatDetail(item.song)" @play="handlePlay"/>
|
||||
<song-item class="song-item" :item="formatDetail(item.song)" @play="handlePlay" />
|
||||
<div class="play-count">{{ item.playCount }}次</div>
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<MusicList v-if="list" v-model:show="isShowList" :name="list.name" :song-list="list.tracks" />
|
||||
<music-list v-if="list" v-model:show="isShowList" :name="list.name" :song-list="list.tracks" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -222,5 +210,4 @@ const handlePlay = (item: any) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
// import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
@@ -10,7 +10,7 @@ import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
VueDevTools(),
|
||||
// VueDevTools(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
|
||||
Reference in New Issue
Block a user