mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-09 02:30:52 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3522011224 | ||
|
|
820597e903 | ||
|
|
f8efbe8ec6 | ||
|
|
67d42a2291 | ||
|
|
7ab43d2e9e | ||
|
|
a59351adf7 | ||
|
|
ad5d5458f1 | ||
|
|
adb539fbde | ||
|
|
ecd7a56df0 | ||
|
|
2dbf5dbf03 | ||
|
|
492164d008 | ||
|
|
8da7fdabe5 | ||
|
|
a2c49d354e | ||
|
|
c7c1143cb4 | ||
|
|
f5d097e975 | ||
|
|
d04aeef40b | ||
|
|
a504b914fe | ||
|
|
62d414d659 | ||
|
|
6c57e77969 | ||
|
|
70139e3ca4 | ||
|
|
4dde40ac60 | ||
|
|
be83a79b05 | ||
|
|
a77afb57fd | ||
|
|
cd11db63eb | ||
|
|
f81127432e | ||
|
|
4466713d1a | ||
|
|
73c915d184 | ||
|
|
7e6788a057 | ||
|
|
19140cd680 | ||
|
|
a1780bc9d4 | ||
|
|
bb1b07e0b3 | ||
|
|
7cb1b5fc7c | ||
|
|
f70aa9e0a0 | ||
|
|
6c8229a21d | ||
|
|
9211dcd3bb | ||
|
|
043ad5906b | ||
|
|
cf598f1c9c |
@@ -1,4 +1,4 @@
|
||||
VITE_API = http://110.42.251.190:9898
|
||||
VITE_API_MT = http://mt.myalger.top
|
||||
VITE_API_MUSIC = http://myalger.top:4000
|
||||
VITE_API_MUSIC = http://110.42.251.190:4100
|
||||
VITE_API_PROXY = http://110.42.251.190:9856
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@ node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
dist_electron
|
||||
.idea
|
||||
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
dist.zip
|
||||
139
app.js
Normal file
139
app.js
Normal file
@@ -0,0 +1,139 @@
|
||||
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')
|
||||
|
||||
let mainWin = null
|
||||
function createWindow() {
|
||||
mainWin = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 780,
|
||||
frame: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: path.join(__dirname, '/electron/preload.js'),
|
||||
},
|
||||
})
|
||||
const win = mainWin
|
||||
win.setMinimumSize(1200, 780)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
win.webContents.openDevTools({ mode: 'detach' })
|
||||
win.loadURL('http://localhost:4678/')
|
||||
} else {
|
||||
win.loadURL(`file://${__dirname}/dist/index.html`)
|
||||
}
|
||||
const image = nativeImage.createFromPath(path.join(__dirname, 'public/icon.png'))
|
||||
const tray = new Tray(image)
|
||||
|
||||
// 创建一个上下文菜单
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '显示',
|
||||
click: () => {
|
||||
win.show()
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '退出',
|
||||
click: () => {
|
||||
win.destroy()
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
// 设置系统托盘图标的上下文菜单
|
||||
tray.setContextMenu(contextMenu)
|
||||
|
||||
// 当系统托盘图标被点击时,切换窗口的显示/隐藏
|
||||
tray.on('click', () => {
|
||||
if (win.isVisible()) {
|
||||
win.hide()
|
||||
} else {
|
||||
win.show()
|
||||
}
|
||||
})
|
||||
|
||||
const set = store.get('set')
|
||||
// store.set('set', setJson)
|
||||
|
||||
if (!set) {
|
||||
store.set('set', setJson)
|
||||
}
|
||||
}
|
||||
|
||||
// 限制只能启动一个应用
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('ready',()=>{
|
||||
globalShortcut.register('CommandOrControl+Alt+Shift+M', () => {
|
||||
if (mainWin.isVisible()) {
|
||||
mainWin.hide()
|
||||
} else {
|
||||
mainWin.show()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
globalShortcut.unregisterAll()
|
||||
})
|
||||
|
||||
ipcMain.on('minimize-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.minimize()
|
||||
})
|
||||
|
||||
ipcMain.on('maximize-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
if (win.isMaximized()) {
|
||||
win.unmaximize()
|
||||
} else {
|
||||
win.maximize()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('close-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.destroy()
|
||||
})
|
||||
|
||||
ipcMain.on('drag-start', (event, data) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.webContents.beginFrameSubscription((frameBuffer) => {
|
||||
event.reply('frame-buffer', frameBuffer)
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.on('mini-tray', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.hide()
|
||||
})
|
||||
|
||||
// 重启
|
||||
ipcMain.on('restart', () => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
})
|
||||
|
||||
const store = new Store();
|
||||
|
||||
// 定义ipcRenderer监听事件
|
||||
ipcMain.on('setStore', (_, key, value) => {
|
||||
store.set(key, value)
|
||||
})
|
||||
|
||||
ipcMain.on('getStore', (_, key) => {
|
||||
let value = store.get(key)
|
||||
_.returnValue = value || ""
|
||||
})
|
||||
70
auto-imports.d.ts
vendored
Normal file
70
auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useDialog: typeof import('naive-ui')['useDialog']
|
||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
||||
const useMessage: typeof import('naive-ui')['useMessage']
|
||||
const useNotification: typeof import('naive-ui')['useNotification']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
39
components.d.ts
vendored
Normal file
39
components.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
MPop: typeof import('./src/components/common/MPop.vue')['default']
|
||||
MusicList: typeof import('./src/components/MusicList.vue')['default']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']
|
||||
PlayListsItem: typeof import('./src/components/common/PlayListsItem.vue')['default']
|
||||
PlaylistType: typeof import('./src/components/PlaylistType.vue')['default']
|
||||
RecommendAlbum: typeof import('./src/components/RecommendAlbum.vue')['default']
|
||||
RecommendSinger: typeof import('./src/components/RecommendSinger.vue')['default']
|
||||
RecommendSonglist: typeof import('./src/components/RecommendSonglist.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SearchItem: typeof import('./src/components/common/SearchItem.vue')['default']
|
||||
SongItem: typeof import('./src/components/common/SongItem.vue')['default']
|
||||
}
|
||||
}
|
||||
18
electron.config.json
Normal file
18
electron.config.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"appId": "com.alger.music",
|
||||
"productName": "AlgerMusic",
|
||||
"directories": {
|
||||
"output": "dist_electron"
|
||||
},
|
||||
"files": ["dist/**/*", "package.json", "app.js", "electron/**/*"],
|
||||
"win": {
|
||||
"icon": "public/icon.png",
|
||||
"target": "nsis",
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "installer/installer.nsh",
|
||||
"to": "$INSTDIR"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
25
electron/preload.js
Normal file
25
electron/preload.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
minimize: () => ipcRenderer.send('minimize-window'),
|
||||
maximize: () => ipcRenderer.send('maximize-window'),
|
||||
close: () => ipcRenderer.send('close-window'),
|
||||
dragStart: (data) => ipcRenderer.send('drag-start', data),
|
||||
miniTray: () => ipcRenderer.send('mini-tray'),
|
||||
restart: () => ipcRenderer.send('restart'),
|
||||
})
|
||||
|
||||
const electronHandler = {
|
||||
ipcRenderer: {
|
||||
setStoreValue: (key, value) => {
|
||||
ipcRenderer.send("setStore", key, value)
|
||||
},
|
||||
|
||||
getStoreValue(key) {
|
||||
const resp = ipcRenderer.sendSync("getStore", key)
|
||||
return resp
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', electronHandler)
|
||||
5
electron/set.json
Normal file
5
electron/set.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"isProxy": false,
|
||||
"author": "alger"
|
||||
}
|
||||
49
index.html
49
index.html
@@ -1,24 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>网抑云 | algerkong</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//at.alicdn.com/t/font_2685283_5bo4ekd5wh.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="./public/css/animate.css" />
|
||||
<style>
|
||||
:root {
|
||||
--animate-delay: 0.5s;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>网抑云 | algerkong</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="./public/icon/iconfont.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="./public/css/animate.css" />
|
||||
<link rel="stylesheet" href="./public/css/base.css" />
|
||||
<style>
|
||||
:root {
|
||||
--animate-delay: 0.5s;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
package.json
20
package.json
@@ -1,16 +1,27 @@
|
||||
{
|
||||
"version": "0.0.0",
|
||||
"name": "alger-music",
|
||||
"version": "1.3.0",
|
||||
"description": "这是一个用于音乐播放的应用程序。",
|
||||
"author": "Alger <algerkc@qq.com>",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
"serve": "vite preview",
|
||||
"es": "vite && electron .",
|
||||
"start": "set NODE_ENV=development&&electron .",
|
||||
"e:b": "electron-builder --config ./electron.config.json",
|
||||
"eb": "vite build && e:b"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss7-compat": "^2.2.4",
|
||||
"@vue/runtime-core": "^3.3.4",
|
||||
"@vueuse/core": "^10.7.1",
|
||||
"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",
|
||||
"sass": "^1.35.2",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4",
|
||||
@@ -23,8 +34,11 @@
|
||||
"@vicons/antd": "^0.10.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"naive-ui": "^2.34.4",
|
||||
"electron": "^28.0.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"typescript": "^4.3.2",
|
||||
"unplugin-auto-import": "^0.17.2",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vfonts": "^0.1.0",
|
||||
"vite": "^4.4.7",
|
||||
"vite-plugin-vue-devtools": "1.0.0-beta.5",
|
||||
|
||||
3
public/css/base.css
Normal file
3
public/css/base.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body{
|
||||
background-color: #000;
|
||||
}
|
||||
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
283
public/icon/iconfont.css
Normal file
283
public/icon/iconfont.css
Normal file
@@ -0,0 +1,283 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2685283 */
|
||||
src: url('iconfont.woff2?t=1703643214551') format('woff2'),
|
||||
url('iconfont.woff?t=1703643214551') format('woff'),
|
||||
url('iconfont.ttf?t=1703643214551') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-list:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.icon-maxsize:before {
|
||||
content: "\e692";
|
||||
}
|
||||
|
||||
.icon-close:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.icon-minisize:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.icon-shuaxin:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.icon-icon_error:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.icon-a-3User:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.icon-Chat:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.icon-Category:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.icon-Document:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.icon-Heart:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.icon-Hide:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.icon-Home:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icon-a-Image2:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.icon-Profile:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.icon-Search:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.icon-Paper:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.icon-Play:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.icon-Setting:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.icon-a-TicketStar:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-a-VolumeOff:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.icon-a-VolumeUp:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.icon-a-VolumeDown:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.icon-stop:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.icon-next:before {
|
||||
content: "\e6a9";
|
||||
}
|
||||
|
||||
.icon-prev:before {
|
||||
content: "\e6ac";
|
||||
}
|
||||
|
||||
.icon-play:before {
|
||||
content: "\e6aa";
|
||||
}
|
||||
|
||||
.icon-xiasanjiaoxing:before {
|
||||
content: "\e642";
|
||||
}
|
||||
|
||||
.icon-videofill:before {
|
||||
content: "\e7c7";
|
||||
}
|
||||
|
||||
.icon-favorfill:before {
|
||||
content: "\e64b";
|
||||
}
|
||||
|
||||
.icon-favor:before {
|
||||
content: "\e64c";
|
||||
}
|
||||
|
||||
.icon-loading:before {
|
||||
content: "\e64f";
|
||||
}
|
||||
|
||||
.icon-search:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.icon-likefill:before {
|
||||
content: "\e668";
|
||||
}
|
||||
|
||||
.icon-like:before {
|
||||
content: "\e669";
|
||||
}
|
||||
|
||||
.icon-notificationfill:before {
|
||||
content: "\e66a";
|
||||
}
|
||||
|
||||
.icon-notification:before {
|
||||
content: "\e66b";
|
||||
}
|
||||
|
||||
.icon-evaluate:before {
|
||||
content: "\e672";
|
||||
}
|
||||
|
||||
.icon-homefill:before {
|
||||
content: "\e6bb";
|
||||
}
|
||||
|
||||
.icon-link:before {
|
||||
content: "\e6bf";
|
||||
}
|
||||
|
||||
.icon-roundaddfill:before {
|
||||
content: "\e6d8";
|
||||
}
|
||||
|
||||
.icon-roundadd:before {
|
||||
content: "\e6d9";
|
||||
}
|
||||
|
||||
.icon-add:before {
|
||||
content: "\e6da";
|
||||
}
|
||||
|
||||
.icon-appreciatefill:before {
|
||||
content: "\e6e3";
|
||||
}
|
||||
|
||||
.icon-forwardfill:before {
|
||||
content: "\e6ea";
|
||||
}
|
||||
|
||||
.icon-voicefill:before {
|
||||
content: "\e6f0";
|
||||
}
|
||||
|
||||
.icon-wefill:before {
|
||||
content: "\e6f4";
|
||||
}
|
||||
|
||||
.icon-keyboard:before {
|
||||
content: "\e71b";
|
||||
}
|
||||
|
||||
.icon-picfill:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
|
||||
.icon-markfill:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.icon-presentfill:before {
|
||||
content: "\e732";
|
||||
}
|
||||
|
||||
.icon-peoplefill:before {
|
||||
content: "\e735";
|
||||
}
|
||||
|
||||
.icon-read:before {
|
||||
content: "\e742";
|
||||
}
|
||||
|
||||
.icon-backwardfill:before {
|
||||
content: "\e74d";
|
||||
}
|
||||
|
||||
.icon-playfill:before {
|
||||
content: "\e74f";
|
||||
}
|
||||
|
||||
.icon-all:before {
|
||||
content: "\e755";
|
||||
}
|
||||
|
||||
.icon-hotfill:before {
|
||||
content: "\e757";
|
||||
}
|
||||
|
||||
.icon-recordfill:before {
|
||||
content: "\e7a4";
|
||||
}
|
||||
|
||||
.icon-full:before {
|
||||
content: "\e7bc";
|
||||
}
|
||||
|
||||
.icon-favor_fill_light:before {
|
||||
content: "\e7ec";
|
||||
}
|
||||
|
||||
.icon-round_favor_fill:before {
|
||||
content: "\e80a";
|
||||
}
|
||||
|
||||
.icon-round_location_fill:before {
|
||||
content: "\e80b";
|
||||
}
|
||||
|
||||
.icon-round_like_fill:before {
|
||||
content: "\e80c";
|
||||
}
|
||||
|
||||
.icon-round_people_fill:before {
|
||||
content: "\e80d";
|
||||
}
|
||||
|
||||
.icon-round_skin_fill:before {
|
||||
content: "\e80e";
|
||||
}
|
||||
|
||||
.icon-broadcast_fill:before {
|
||||
content: "\e81d";
|
||||
}
|
||||
|
||||
.icon-card_fill:before {
|
||||
content: "\e81f";
|
||||
}
|
||||
|
||||
1
public/icon/iconfont.js
Normal file
1
public/icon/iconfont.js
Normal file
File diff suppressed because one or more lines are too long
478
public/icon/iconfont.json
Normal file
478
public/icon/iconfont.json
Normal file
@@ -0,0 +1,478 @@
|
||||
{
|
||||
"id": "2685283",
|
||||
"name": "music",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "1111849",
|
||||
"name": "list",
|
||||
"font_class": "list",
|
||||
"unicode": "e603",
|
||||
"unicode_decimal": 58883
|
||||
},
|
||||
{
|
||||
"icon_id": "1306794",
|
||||
"name": "maxsize",
|
||||
"font_class": "maxsize",
|
||||
"unicode": "e692",
|
||||
"unicode_decimal": 59026
|
||||
},
|
||||
{
|
||||
"icon_id": "4437591",
|
||||
"name": "close",
|
||||
"font_class": "close",
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "5383753",
|
||||
"name": "minisize",
|
||||
"font_class": "minisize",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "13075017",
|
||||
"name": "刷新",
|
||||
"font_class": "shuaxin",
|
||||
"unicode": "e627",
|
||||
"unicode_decimal": 58919
|
||||
},
|
||||
{
|
||||
"icon_id": "24457556",
|
||||
"name": "icon_error",
|
||||
"font_class": "icon_error",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "24492642",
|
||||
"name": "3 User",
|
||||
"font_class": "a-3User",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "24492643",
|
||||
"name": "Chat",
|
||||
"font_class": "Chat",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "24492646",
|
||||
"name": "Category",
|
||||
"font_class": "Category",
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "24492661",
|
||||
"name": "Document",
|
||||
"font_class": "Document",
|
||||
"unicode": "e607",
|
||||
"unicode_decimal": 58887
|
||||
},
|
||||
{
|
||||
"icon_id": "24492662",
|
||||
"name": "Heart",
|
||||
"font_class": "Heart",
|
||||
"unicode": "e608",
|
||||
"unicode_decimal": 58888
|
||||
},
|
||||
{
|
||||
"icon_id": "24492665",
|
||||
"name": "Hide",
|
||||
"font_class": "Hide",
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "24492667",
|
||||
"name": "Home",
|
||||
"font_class": "Home",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "24492678",
|
||||
"name": "Image 2",
|
||||
"font_class": "a-Image2",
|
||||
"unicode": "e60b",
|
||||
"unicode_decimal": 58891
|
||||
},
|
||||
{
|
||||
"icon_id": "24492684",
|
||||
"name": "Profile",
|
||||
"font_class": "Profile",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "24492685",
|
||||
"name": "Search",
|
||||
"font_class": "Search",
|
||||
"unicode": "e60d",
|
||||
"unicode_decimal": 58893
|
||||
},
|
||||
{
|
||||
"icon_id": "24492687",
|
||||
"name": "Paper",
|
||||
"font_class": "Paper",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "24492690",
|
||||
"name": "Play",
|
||||
"font_class": "Play",
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "24492698",
|
||||
"name": "Setting",
|
||||
"font_class": "Setting",
|
||||
"unicode": "e610",
|
||||
"unicode_decimal": 58896
|
||||
},
|
||||
{
|
||||
"icon_id": "24492708",
|
||||
"name": "Ticket Star",
|
||||
"font_class": "a-TicketStar",
|
||||
"unicode": "e611",
|
||||
"unicode_decimal": 58897
|
||||
},
|
||||
{
|
||||
"icon_id": "24492712",
|
||||
"name": "Volume Off",
|
||||
"font_class": "a-VolumeOff",
|
||||
"unicode": "e612",
|
||||
"unicode_decimal": 58898
|
||||
},
|
||||
{
|
||||
"icon_id": "24492713",
|
||||
"name": "Volume Up",
|
||||
"font_class": "a-VolumeUp",
|
||||
"unicode": "e613",
|
||||
"unicode_decimal": 58899
|
||||
},
|
||||
{
|
||||
"icon_id": "24492714",
|
||||
"name": "Volume Down",
|
||||
"font_class": "a-VolumeDown",
|
||||
"unicode": "e614",
|
||||
"unicode_decimal": 58900
|
||||
},
|
||||
{
|
||||
"icon_id": "18875422",
|
||||
"name": "暂停 停止 灰色",
|
||||
"font_class": "stop",
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "15262786",
|
||||
"name": "1_music82",
|
||||
"font_class": "next",
|
||||
"unicode": "e6a9",
|
||||
"unicode_decimal": 59049
|
||||
},
|
||||
{
|
||||
"icon_id": "15262807",
|
||||
"name": "1_music83",
|
||||
"font_class": "prev",
|
||||
"unicode": "e6ac",
|
||||
"unicode_decimal": 59052
|
||||
},
|
||||
{
|
||||
"icon_id": "15262830",
|
||||
"name": "1_music81",
|
||||
"font_class": "play",
|
||||
"unicode": "e6aa",
|
||||
"unicode_decimal": 59050
|
||||
},
|
||||
{
|
||||
"icon_id": "15367",
|
||||
"name": "下三角形",
|
||||
"font_class": "xiasanjiaoxing",
|
||||
"unicode": "e642",
|
||||
"unicode_decimal": 58946
|
||||
},
|
||||
{
|
||||
"icon_id": "1096518",
|
||||
"name": "video_fill",
|
||||
"font_class": "videofill",
|
||||
"unicode": "e7c7",
|
||||
"unicode_decimal": 59335
|
||||
},
|
||||
{
|
||||
"icon_id": "29930",
|
||||
"name": "favor_fill",
|
||||
"font_class": "favorfill",
|
||||
"unicode": "e64b",
|
||||
"unicode_decimal": 58955
|
||||
},
|
||||
{
|
||||
"icon_id": "29931",
|
||||
"name": "favor",
|
||||
"font_class": "favor",
|
||||
"unicode": "e64c",
|
||||
"unicode_decimal": 58956
|
||||
},
|
||||
{
|
||||
"icon_id": "29934",
|
||||
"name": "loading",
|
||||
"font_class": "loading",
|
||||
"unicode": "e64f",
|
||||
"unicode_decimal": 58959
|
||||
},
|
||||
{
|
||||
"icon_id": "29947",
|
||||
"name": "search",
|
||||
"font_class": "search",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "30417",
|
||||
"name": "like_fill",
|
||||
"font_class": "likefill",
|
||||
"unicode": "e668",
|
||||
"unicode_decimal": 58984
|
||||
},
|
||||
{
|
||||
"icon_id": "30418",
|
||||
"name": "like",
|
||||
"font_class": "like",
|
||||
"unicode": "e669",
|
||||
"unicode_decimal": 58985
|
||||
},
|
||||
{
|
||||
"icon_id": "30419",
|
||||
"name": "notification_fill",
|
||||
"font_class": "notificationfill",
|
||||
"unicode": "e66a",
|
||||
"unicode_decimal": 58986
|
||||
},
|
||||
{
|
||||
"icon_id": "30420",
|
||||
"name": "notification",
|
||||
"font_class": "notification",
|
||||
"unicode": "e66b",
|
||||
"unicode_decimal": 58987
|
||||
},
|
||||
{
|
||||
"icon_id": "30434",
|
||||
"name": "evaluate",
|
||||
"font_class": "evaluate",
|
||||
"unicode": "e672",
|
||||
"unicode_decimal": 58994
|
||||
},
|
||||
{
|
||||
"icon_id": "33519",
|
||||
"name": "home_fill",
|
||||
"font_class": "homefill",
|
||||
"unicode": "e6bb",
|
||||
"unicode_decimal": 59067
|
||||
},
|
||||
{
|
||||
"icon_id": "34922",
|
||||
"name": "link",
|
||||
"font_class": "link",
|
||||
"unicode": "e6bf",
|
||||
"unicode_decimal": 59071
|
||||
},
|
||||
{
|
||||
"icon_id": "38744",
|
||||
"name": "round_add_fill",
|
||||
"font_class": "roundaddfill",
|
||||
"unicode": "e6d8",
|
||||
"unicode_decimal": 59096
|
||||
},
|
||||
{
|
||||
"icon_id": "38746",
|
||||
"name": "round_add",
|
||||
"font_class": "roundadd",
|
||||
"unicode": "e6d9",
|
||||
"unicode_decimal": 59097
|
||||
},
|
||||
{
|
||||
"icon_id": "38747",
|
||||
"name": "add",
|
||||
"font_class": "add",
|
||||
"unicode": "e6da",
|
||||
"unicode_decimal": 59098
|
||||
},
|
||||
{
|
||||
"icon_id": "43903",
|
||||
"name": "appreciate_fill",
|
||||
"font_class": "appreciatefill",
|
||||
"unicode": "e6e3",
|
||||
"unicode_decimal": 59107
|
||||
},
|
||||
{
|
||||
"icon_id": "52506",
|
||||
"name": "forward_fill",
|
||||
"font_class": "forwardfill",
|
||||
"unicode": "e6ea",
|
||||
"unicode_decimal": 59114
|
||||
},
|
||||
{
|
||||
"icon_id": "55448",
|
||||
"name": "voice_fill",
|
||||
"font_class": "voicefill",
|
||||
"unicode": "e6f0",
|
||||
"unicode_decimal": 59120
|
||||
},
|
||||
{
|
||||
"icon_id": "61146",
|
||||
"name": "we_fill",
|
||||
"font_class": "wefill",
|
||||
"unicode": "e6f4",
|
||||
"unicode_decimal": 59124
|
||||
},
|
||||
{
|
||||
"icon_id": "90847",
|
||||
"name": "keyboard",
|
||||
"font_class": "keyboard",
|
||||
"unicode": "e71b",
|
||||
"unicode_decimal": 59163
|
||||
},
|
||||
{
|
||||
"icon_id": "127305",
|
||||
"name": "pic_fill",
|
||||
"font_class": "picfill",
|
||||
"unicode": "e72c",
|
||||
"unicode_decimal": 59180
|
||||
},
|
||||
{
|
||||
"icon_id": "143738",
|
||||
"name": "mark_fill",
|
||||
"font_class": "markfill",
|
||||
"unicode": "e730",
|
||||
"unicode_decimal": 59184
|
||||
},
|
||||
{
|
||||
"icon_id": "143740",
|
||||
"name": "present_fill",
|
||||
"font_class": "presentfill",
|
||||
"unicode": "e732",
|
||||
"unicode_decimal": 59186
|
||||
},
|
||||
{
|
||||
"icon_id": "158873",
|
||||
"name": "people_fill",
|
||||
"font_class": "peoplefill",
|
||||
"unicode": "e735",
|
||||
"unicode_decimal": 59189
|
||||
},
|
||||
{
|
||||
"icon_id": "176313",
|
||||
"name": "read",
|
||||
"font_class": "read",
|
||||
"unicode": "e742",
|
||||
"unicode_decimal": 59202
|
||||
},
|
||||
{
|
||||
"icon_id": "212324",
|
||||
"name": "backward_fill",
|
||||
"font_class": "backwardfill",
|
||||
"unicode": "e74d",
|
||||
"unicode_decimal": 59213
|
||||
},
|
||||
{
|
||||
"icon_id": "212328",
|
||||
"name": "play_fill",
|
||||
"font_class": "playfill",
|
||||
"unicode": "e74f",
|
||||
"unicode_decimal": 59215
|
||||
},
|
||||
{
|
||||
"icon_id": "240126",
|
||||
"name": "all",
|
||||
"font_class": "all",
|
||||
"unicode": "e755",
|
||||
"unicode_decimal": 59221
|
||||
},
|
||||
{
|
||||
"icon_id": "240128",
|
||||
"name": "hot_fill",
|
||||
"font_class": "hotfill",
|
||||
"unicode": "e757",
|
||||
"unicode_decimal": 59223
|
||||
},
|
||||
{
|
||||
"icon_id": "747747",
|
||||
"name": "record_fill",
|
||||
"font_class": "recordfill",
|
||||
"unicode": "e7a4",
|
||||
"unicode_decimal": 59300
|
||||
},
|
||||
{
|
||||
"icon_id": "1005712",
|
||||
"name": "full",
|
||||
"font_class": "full",
|
||||
"unicode": "e7bc",
|
||||
"unicode_decimal": 59324
|
||||
},
|
||||
{
|
||||
"icon_id": "1512759",
|
||||
"name": "favor_fill_light",
|
||||
"font_class": "favor_fill_light",
|
||||
"unicode": "e7ec",
|
||||
"unicode_decimal": 59372
|
||||
},
|
||||
{
|
||||
"icon_id": "4110741",
|
||||
"name": "round_favor_fill",
|
||||
"font_class": "round_favor_fill",
|
||||
"unicode": "e80a",
|
||||
"unicode_decimal": 59402
|
||||
},
|
||||
{
|
||||
"icon_id": "4110743",
|
||||
"name": "round_location_fill",
|
||||
"font_class": "round_location_fill",
|
||||
"unicode": "e80b",
|
||||
"unicode_decimal": 59403
|
||||
},
|
||||
{
|
||||
"icon_id": "4110745",
|
||||
"name": "round_like_fill",
|
||||
"font_class": "round_like_fill",
|
||||
"unicode": "e80c",
|
||||
"unicode_decimal": 59404
|
||||
},
|
||||
{
|
||||
"icon_id": "4110746",
|
||||
"name": "round_people_fill",
|
||||
"font_class": "round_people_fill",
|
||||
"unicode": "e80d",
|
||||
"unicode_decimal": 59405
|
||||
},
|
||||
{
|
||||
"icon_id": "4110750",
|
||||
"name": "round_skin_fill",
|
||||
"font_class": "round_skin_fill",
|
||||
"unicode": "e80e",
|
||||
"unicode_decimal": 59406
|
||||
},
|
||||
{
|
||||
"icon_id": "11778953",
|
||||
"name": "broadcast_fill",
|
||||
"font_class": "broadcast_fill",
|
||||
"unicode": "e81d",
|
||||
"unicode_decimal": 59421
|
||||
},
|
||||
{
|
||||
"icon_id": "12625085",
|
||||
"name": "card_fill",
|
||||
"font_class": "card_fill",
|
||||
"unicode": "e81f",
|
||||
"unicode_decimal": 59423
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
public/icon/iconfont.ttf
Normal file
BIN
public/icon/iconfont.ttf
Normal file
Binary file not shown.
BIN
public/icon/iconfont.woff
Normal file
BIN
public/icon/iconfont.woff
Normal file
Binary file not shown.
BIN
public/icon/iconfont.woff2
Normal file
BIN
public/icon/iconfont.woff2
Normal file
Binary file not shown.
BIN
public/icon1.png
Normal file
BIN
public/icon1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
59
src/App.vue
59
src/App.vue
@@ -1,22 +1,37 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<n-config-provider :theme="darkTheme">
|
||||
<router-view></router-view>
|
||||
</n-config-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { darkTheme } from 'naive-ui'
|
||||
|
||||
|
||||
</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 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>
|
||||
|
||||
@@ -1,37 +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 } });
|
||||
}
|
||||
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 } });
|
||||
}
|
||||
@@ -7,6 +7,11 @@ 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 } })
|
||||
|
||||
30
src/api/mv.ts
Normal file
30
src/api/mv.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { IData } from '@/type'
|
||||
import { IMvItem, IMvUrlData } from '@/type/mv'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 获取 mv 排行
|
||||
export const getTopMv = (limit: number) => {
|
||||
return request.get<IData<Array<IMvItem>>>('/top/mv', {
|
||||
params: {
|
||||
limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取 mv 数据
|
||||
export const getMvDetail = (mvid: string) => {
|
||||
return request.get('/mv/detail', {
|
||||
params: {
|
||||
mvid,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取 mv 地址
|
||||
export const getMvUrl = (id: Number) => {
|
||||
return request.get<IData<IMvUrlData>>('/mv/url', {
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import request from "@/utils/request"
|
||||
import { ISearchDetail } from "@/type/search"
|
||||
|
||||
interface IParams {
|
||||
keywords: string
|
||||
type: number
|
||||
}
|
||||
// 搜索内容
|
||||
export const getSearch = (keywords: any) => {
|
||||
return request.get<any>("/cloudsearch", {
|
||||
params: { keywords: keywords, type: 1 },
|
||||
export const getSearch = (params: IParams) => {
|
||||
return request.get<any>('/cloudsearch', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
75
src/components/MusicList.vue
Normal file
75
src/components/MusicList.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<n-drawer :show="show" height="70vh" placement="bottom" :drawer-style="{ backgroundColor: 'transparent' }">
|
||||
<div class="music-page">
|
||||
<i class="iconfont icon-icon_error music-close" @click="close"></i>
|
||||
<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" />
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</n-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStore } from 'vuex'
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
import SongItem from "@/components/common/SongItem.vue";
|
||||
import PlayBottom from './common/PlayBottom.vue';
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
name: string;
|
||||
songList: any[]
|
||||
}>()
|
||||
const emit = defineEmits(['update:show'])
|
||||
|
||||
const formatDetail = computed(() => (detail: any) => {
|
||||
let 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 = props.songList || []
|
||||
store.commit('setPlayList', tracks)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
emit('update:show', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.music {
|
||||
&-page {
|
||||
@apply px-8 w-full h-full bg-black bg-opacity-75 rounded-t-2xl;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
&-title {
|
||||
@apply text-lg font-bold text-white p-4;
|
||||
}
|
||||
|
||||
&-close {
|
||||
@apply absolute top-4 right-8 cursor-pointer text-white text-3xl;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<!-- 歌单分类列表 -->
|
||||
<div class="play-list-type">
|
||||
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">歌单分类</div>
|
||||
<n-layout>
|
||||
<div>
|
||||
<template v-for="(item, index) in playlistCategory?.sub" :key="item.name">
|
||||
<span
|
||||
class="play-list-type-item"
|
||||
@@ -24,7 +24,7 @@
|
||||
"
|
||||
@click="isShowAllPlaylistCategory = !isShowAllPlaylistCategory"
|
||||
>{{ !isShowAllPlaylistCategory ? "显示全部" : "隐藏一些" }}</div>
|
||||
</n-layout>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
class="recommend-album-list-item"
|
||||
:class="setAnimationClass('animate__backInUp')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<n-image
|
||||
class="recommend-album-list-item-img"
|
||||
@@ -19,6 +20,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<MusicList v-model:show="showMusic" :name="albumName" :song-list="songList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,6 +29,7 @@ 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";
|
||||
|
||||
|
||||
const albumData = ref<IAlbumNew>()
|
||||
@@ -35,6 +38,20 @@ const loadAlbumList = async () => {
|
||||
albumData.value = data
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAlbumList()
|
||||
})
|
||||
|
||||
@@ -66,7 +66,7 @@ const toSearchSinger = (keyword: string) => {
|
||||
.recommend-singer {
|
||||
&-list {
|
||||
@apply flex;
|
||||
height: 350px;
|
||||
height: 280px;
|
||||
}
|
||||
&-item {
|
||||
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getRecommendMusic } from '@/api/home'
|
||||
import type { IRecommendMusic } from '@/type/music'
|
||||
import { setAnimationClass, setAnimationDelay } from '@/utils'
|
||||
@@ -45,8 +44,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
const musicIndex = (recommendMusic.value?.result.findIndex((music: any) => music.id == item.id) || 0) + 1
|
||||
store.commit('setPlayList', recommendMusic.value?.result.slice(musicIndex))
|
||||
store.commit('setPlayList', recommendMusic.value?.result)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
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" @click="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;
|
||||
}
|
||||
}
|
||||
<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>
|
||||
15
src/components/common/PlayBottom.vue
Normal file
15
src/components/common/PlayBottom.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="bottom" v-if="isPlay"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStore } from 'vuex';
|
||||
const store = useStore()
|
||||
const isPlay = computed(() => store.state.isPlay as boolean)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bottom{
|
||||
@apply h-28;
|
||||
}
|
||||
</style>
|
||||
0
src/components/common/PlayListsItem.vue
Normal file
0
src/components/common/PlayListsItem.vue
Normal file
70
src/components/common/SearchItem.vue
Normal file
70
src/components/common/SearchItem.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="search-item" @click="handleClick">
|
||||
<div class="search-item-img">
|
||||
<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>
|
||||
|
||||
<MusicList v-model:show="showMusic" :name="item.name" :song-list="songList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getImgUrl } from '@/utils'
|
||||
import type {Album} from '@/type/album'
|
||||
import { getAlbum } from '@/api/list';
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
picUrl: string
|
||||
name: string
|
||||
desc: string
|
||||
type: string
|
||||
[key: string]: any
|
||||
}
|
||||
}>()
|
||||
|
||||
const songList = ref([])
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.search-item{
|
||||
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition;
|
||||
margin: 0 10px;
|
||||
.search-item-img{
|
||||
@apply w-12 h-12 mr-4 rounded-2xl overflow-hidden;
|
||||
}
|
||||
.search-item-info{
|
||||
&-name{
|
||||
@apply text-white text-sm text-center;
|
||||
}
|
||||
&-artist{
|
||||
@apply text-gray-400 text-xs text-center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,18 +1,19 @@
|
||||
<template>
|
||||
<div class="recommend-music-list-item">
|
||||
<div class="song-item" :class="{'song-mini': mini}">
|
||||
<n-image
|
||||
v-if="item.picUrl "
|
||||
:src="getImgUrl( item.picUrl, '40y40')"
|
||||
class="recommend-music-list-item-img"
|
||||
class="song-item-img"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div class="recommend-music-list-item-content">
|
||||
<div class="recommend-music-list-item-content-title">
|
||||
<div class="song-item-content">
|
||||
<div class="song-item-content-title">
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">{{
|
||||
item.name
|
||||
}}</n-ellipsis>
|
||||
</div>
|
||||
<div class="recommend-music-list-item-content-name">
|
||||
<div class="song-item-content-name">
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">
|
||||
<span
|
||||
v-for="(artists, artistsindex) in item.song.artists"
|
||||
@@ -25,12 +26,12 @@
|
||||
</n-ellipsis>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommend-music-list-item-operating">
|
||||
<div class="recommend-music-list-item-operating-like">
|
||||
<div class="song-item-operating">
|
||||
<div class="song-item-operating-like">
|
||||
<i class="iconfont icon-likefill"></i>
|
||||
</div>
|
||||
<div
|
||||
class="recommend-music-list-item-operating-play bg-black"
|
||||
class="song-item-operating-play bg-black"
|
||||
:class="isPlaying ? 'bg-green-600' : ''"
|
||||
@click="playMusicEvent(item)"
|
||||
>
|
||||
@@ -44,14 +45,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from 'vuex'
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { computed } from 'vue'
|
||||
import { getImgUrl } from '@/utils'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
const props = withDefaults(defineProps<{
|
||||
item: SongResult
|
||||
mini?: boolean
|
||||
}>(), {
|
||||
mini: false
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
@@ -71,16 +71,21 @@ const emits = defineEmits(['play'])
|
||||
const playMusicEvent = (item: any) => {
|
||||
store.commit('setPlay', item)
|
||||
store.commit('setIsPlay', true)
|
||||
store.state.playListIndex = 0
|
||||
emits('play', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
// 配置文字不可选中
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
.recommend-music-list-item {
|
||||
.song-item {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition;
|
||||
&-img {
|
||||
@apply w-12 h-12 rounded-2xl mr-4;
|
||||
@@ -96,7 +101,7 @@ const playMusicEvent = (item: any) => {
|
||||
}
|
||||
}
|
||||
&-operating {
|
||||
@apply flex items-center pl-4 rounded-full border border-gray-700;
|
||||
@apply flex items-center pl-4 rounded-full border border-gray-700 ml-4;
|
||||
background-color: #0d0d0d;
|
||||
.iconfont {
|
||||
@apply text-xl;
|
||||
@@ -113,4 +118,35 @@ const playMusicEvent = (item: any) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.song-mini{
|
||||
@apply p-2 rounded-2xl;
|
||||
.song-item{
|
||||
@apply p-0;
|
||||
&-img{
|
||||
@apply w-10 h-10 mr-2;
|
||||
}
|
||||
&-content{
|
||||
@apply flex-1;
|
||||
&-title{
|
||||
@apply text-sm;
|
||||
}
|
||||
&-name{
|
||||
@apply text-xs;
|
||||
}
|
||||
}
|
||||
&-operating{
|
||||
@apply pl-2;
|
||||
.iconfont{
|
||||
@apply text-base;
|
||||
}
|
||||
&-like{
|
||||
@apply mr-1;
|
||||
}
|
||||
&-play{
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
65
src/const/bar-const.ts
Normal file
65
src/const/bar-const.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export const USER_SET_OPTIONS = [
|
||||
// {
|
||||
// label: '打卡',
|
||||
// key: 'card',
|
||||
// },
|
||||
// {
|
||||
// label: '听歌升级',
|
||||
// key: 'card_music',
|
||||
// },
|
||||
// {
|
||||
// label: '歌曲次数',
|
||||
// key: 'listen',
|
||||
// },
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout',
|
||||
},
|
||||
{
|
||||
label: '设置',
|
||||
key: 'set',
|
||||
},
|
||||
]
|
||||
|
||||
export const SEARCH_TYPES = [
|
||||
{
|
||||
label: '单曲',
|
||||
key: 1,
|
||||
},
|
||||
{
|
||||
label: '专辑',
|
||||
key: 10,
|
||||
},
|
||||
{
|
||||
label: '歌手',
|
||||
key: 100,
|
||||
},
|
||||
{
|
||||
label: '歌单',
|
||||
key: 1000,
|
||||
},
|
||||
{
|
||||
label: '用户',
|
||||
key: 1002,
|
||||
},
|
||||
{
|
||||
label: 'MV',
|
||||
key: 1004,
|
||||
},
|
||||
{
|
||||
label: '歌词',
|
||||
key: 1006,
|
||||
},
|
||||
{
|
||||
label: '电台',
|
||||
key: 1009,
|
||||
},
|
||||
{
|
||||
label: '视频',
|
||||
key: 1014,
|
||||
},
|
||||
{
|
||||
label: '综合',
|
||||
key: 1018,
|
||||
},
|
||||
]
|
||||
10
src/electron.d.ts
vendored
Normal file
10
src/electron.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
minimize: () => void
|
||||
maximize: () => void
|
||||
close: () => void
|
||||
dragStart: () => void
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/hooks/MusicHistoryHook.ts
Normal file
39
src/hooks/MusicHistoryHook.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// musicHistoryHooks
|
||||
import { RemovableRef, useLocalStorage } from '@vueuse/core'
|
||||
import type { SongResult } from '@/type/music'
|
||||
|
||||
export const useMusicHistory = () => {
|
||||
const musicHistory = useLocalStorage<SongResult[]>('musicHistory', [])
|
||||
|
||||
const addMusic = (music: SongResult) => {
|
||||
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])
|
||||
} else {
|
||||
musicHistory.value.unshift({ ...music, count: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
const delMusic = (music: any) => {
|
||||
const index = musicHistory.value.findIndex((item) => item.id === music.id)
|
||||
if (index !== -1) {
|
||||
musicHistory.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
const musicList = ref(musicHistory.value)
|
||||
watch(
|
||||
() => musicHistory.value,
|
||||
() => {
|
||||
musicList.value = musicHistory.value
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
musicHistory,
|
||||
musicList,
|
||||
addMusic,
|
||||
delMusic,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getMusicLrc } from '@/api/music'
|
||||
import { ILyric } from '@/type/lyric'
|
||||
import { ref } from 'vue'
|
||||
import { getIsMc } from '@/utils'
|
||||
|
||||
interface ILrcData {
|
||||
text: string
|
||||
@@ -57,7 +57,7 @@ const loadLrc = async (playMusicId: number): Promise<void> => {
|
||||
}
|
||||
|
||||
// 歌词矫正时间Correction time
|
||||
const correctionTime = ref(0)
|
||||
const correctionTime = ref(0.4)
|
||||
|
||||
// 增加矫正时间
|
||||
const addCorrectionTime = (time: number) => {
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
@tailwind utilities;
|
||||
|
||||
.n-image img {
|
||||
@apply bg-gray-900;
|
||||
background-color: #111111;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,22 +1,31 @@
|
||||
<template>
|
||||
<div class="layout-page">
|
||||
<div class="layout-main">
|
||||
<div class="flex">
|
||||
<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" :native-scrollbar="false">
|
||||
<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 }">
|
||||
<!-- <keep-alive>
|
||||
<component :is="Component" v-if="$route.meta.keepAlive" />
|
||||
<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 :is="Component" v-if="!$route.meta.keepAlive" />-->
|
||||
|
||||
<component :is="Component" />
|
||||
<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>
|
||||
@@ -29,22 +38,78 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
// import { AppMenu, PlayBar, SearchBar } from './components';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
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 route = useRoute()
|
||||
|
||||
const audio = {
|
||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement
|
||||
}
|
||||
|
||||
const windowData = window as any
|
||||
const isElectron = computed(() => {
|
||||
return !!windowData.electronAPI
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 监听音乐是否播放
|
||||
watch(
|
||||
() => play.value,
|
||||
value => {
|
||||
if (value && audio.value) {
|
||||
audioPlay()
|
||||
} else {
|
||||
audioPause()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
document.onkeyup = (e) => {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
playMusicEvent()
|
||||
}
|
||||
}
|
||||
// 按下键盘按钮监听
|
||||
document.onkeydown = (e) => {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const audioPlay = () => {
|
||||
if (audio.value) {
|
||||
audio.value.play()
|
||||
}
|
||||
}
|
||||
|
||||
const audioPause = () => {
|
||||
if (audio.value) {
|
||||
audio.value.pause()
|
||||
}
|
||||
}
|
||||
|
||||
const playMusicEvent = async () => {
|
||||
if (play.value) {
|
||||
store.commit('setPlayMusic', false)
|
||||
} else {
|
||||
store.commit('setPlayMusic', true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -55,24 +120,25 @@ const menus = store.state.menus;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
@apply bg-black rounded-lg text-white shadow-xl 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 pt-6 pr-6 flex-1 box-border;
|
||||
height: 100vh;
|
||||
@apply flex-1 box-border flex flex-col;
|
||||
height: 100%;
|
||||
&-content {
|
||||
@apply rounded-2xl;
|
||||
height: calc(100vh - 60px);
|
||||
margin-bottom: 90px;
|
||||
}
|
||||
&-page {
|
||||
margin: 20px 0;
|
||||
@apply box-border flex-1 overflow-hidden;
|
||||
}
|
||||
}
|
||||
:deep(.n-scrollbar-content){
|
||||
@apply pr-3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="app-menu">
|
||||
<div class="app-menu-header">
|
||||
<div class="app-menu-logo">
|
||||
<img src="@/assets/logo.png" class="w-9 h-9 mt-2" alt="logo" />
|
||||
<img src="/icon.png" class="w-9 h-9" alt="logo" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-menu-list">
|
||||
@@ -13,9 +13,9 @@
|
||||
<i
|
||||
class="iconfont app-menu-item-icon"
|
||||
:style="iconStyle(index)"
|
||||
:class="item.mate.icon"
|
||||
:class="item.meta.icon"
|
||||
></i>
|
||||
<span v-if="isText" class="app-menu-item-text ml-3">{{ item.mate.title }}</span>
|
||||
<span v-if="isText" class="app-menu-item-text ml-3">{{ item.meta.title }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,7 +24,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from "@vue/runtime-core";
|
||||
import { useRoute } from "vue-router";
|
||||
const props = defineProps({
|
||||
isText: {
|
||||
@@ -67,7 +66,7 @@ const iconStyle = (index: any) => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-menu {
|
||||
@apply flex-col items-center justify-center p-6;
|
||||
@apply flex-col items-center justify-center px-6;
|
||||
max-width: 100px;
|
||||
}
|
||||
.app-menu-item-link,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
: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="music-img">
|
||||
<n-image
|
||||
ref="PicImgRef"
|
||||
@@ -25,7 +26,7 @@
|
||||
</div>
|
||||
<n-layout
|
||||
class="music-lrc"
|
||||
style="height: 550px"
|
||||
style="height: 55vh"
|
||||
ref="lrcSider"
|
||||
:native-scrollbar="false"
|
||||
@mouseover="mouseOverLayout"
|
||||
@@ -42,9 +43,10 @@
|
||||
</template>
|
||||
</n-layout>
|
||||
<!-- 时间矫正 -->
|
||||
<div class="music-content-time"></div>
|
||||
<n-button @click="reduceCorrectionTime(0.2)">-0.2</n-button>
|
||||
<n-button @click="addCorrectionTime(0.2)">+0.2</n-button>
|
||||
<div class="music-content-time">
|
||||
<n-button @click="reduceCorrectionTime(0.2)">-</n-button>
|
||||
<n-button @click="addCorrectionTime(0.2)">+</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-drawer>
|
||||
@@ -53,7 +55,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { getImgUrl } from '@/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import {
|
||||
lrcArray,
|
||||
@@ -82,6 +83,7 @@ const emit = defineEmits(['update:musicFull'])
|
||||
|
||||
// 播放的音乐信息
|
||||
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)
|
||||
@@ -107,6 +109,30 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@keyframes round {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.drawer-back{
|
||||
@apply absolute bg-cover bg-center opacity-70;
|
||||
filter: blur(80px) brightness(80%);
|
||||
z-index: -1;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
animation: round 20s linear infinite;
|
||||
}
|
||||
|
||||
.drawer-back.paused {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
#drawer-target {
|
||||
@apply top-0 left-0 absolute w-full h-full overflow-hidden rounded px-24 pt-24 pb-48 flex items-center;
|
||||
backdrop-filter: blur(20px);
|
||||
@@ -117,8 +143,8 @@ defineExpose({
|
||||
@apply flex-1 flex justify-center mr-24;
|
||||
|
||||
.img {
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
@apply rounded-xl;
|
||||
}
|
||||
}
|
||||
@@ -135,9 +161,14 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
.music-content-time{
|
||||
display: none;
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
|
||||
.music-lrc {
|
||||
background-color: inherit;
|
||||
width: 800px;
|
||||
width: 500px;
|
||||
height: 550px;
|
||||
|
||||
&-text {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 展开全屏 -->
|
||||
<music-full ref="MusicFullRef" v-model:musicFull="musicFull" :audio="(audio 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
|
||||
@@ -56,43 +56,60 @@
|
||||
></n-slider>
|
||||
</div>
|
||||
<div class="audio-button">
|
||||
<n-tooltip trigger="hover">
|
||||
<!-- <n-tooltip trigger="hover" :z-index="9999999">
|
||||
<template #trigger>
|
||||
<i class="iconfont icon-likefill"></i>
|
||||
</template>
|
||||
喜欢
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
</n-tooltip> -->
|
||||
<!-- <n-tooltip trigger="hover" :z-index="9999999">
|
||||
<template #trigger>
|
||||
<i class="iconfont icon-Play" @click="parsingMusic"></i>
|
||||
</template>
|
||||
解析播放
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
</n-tooltip> -->
|
||||
<!-- <n-tooltip trigger="hover" :z-index="9999999">
|
||||
<template #trigger>
|
||||
<i class="iconfont icon-full" @click="setMusicFull"></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">
|
||||
<template #trigger>
|
||||
<i class="iconfont icon-list"></i>
|
||||
</template>
|
||||
播放列表
|
||||
</n-tooltip>
|
||||
</template>
|
||||
<div class="music-play-list">
|
||||
<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>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</n-popover>
|
||||
</div>
|
||||
<!-- 播放音乐 -->
|
||||
<audio ref="audio" :src="playMusicUrl" :autoplay="play"></audio>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SongResult } from '@/type/music'
|
||||
import { secondToMinute, getImgUrl } from '@/utils'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { setAnimationClass } from '@/utils'
|
||||
import { getParsingMusicUrl } from '@/api/music'
|
||||
import {
|
||||
loadLrc,
|
||||
nowTime,
|
||||
allTime,
|
||||
allTime
|
||||
} from '@/hooks/MusicHook'
|
||||
import MusicFull from './MusicFull.vue'
|
||||
import SongItem from '@/components/common/SongItem.vue'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@@ -100,91 +117,20 @@ const store = useStore()
|
||||
const playMusic = computed(() => store.state.playMusic as SongResult)
|
||||
// 是否播放
|
||||
const play = computed(() => store.state.play as boolean)
|
||||
// 播放链接
|
||||
const ProxyUrl =
|
||||
import.meta.env.VITE_API_PROXY + '' || 'http://110.42.251.190:9856'
|
||||
const playMusicUrl = ref('')
|
||||
|
||||
const playList = computed(() => store.state.playList as SongResult[])
|
||||
|
||||
const audio = {
|
||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement
|
||||
}
|
||||
|
||||
watch(
|
||||
() => store.state.playMusicUrl,
|
||||
async (value, oldValue) => {
|
||||
const isUrlHasMc = location.href.includes('mc.')
|
||||
if (value && isUrlHasMc) {
|
||||
let playMusicUrl1 = value as string
|
||||
if (!ProxyUrl) {
|
||||
playMusicUrl.value = playMusicUrl1
|
||||
return
|
||||
}
|
||||
const url = new URL(playMusicUrl1)
|
||||
const pathname = url.pathname
|
||||
const subdomain = url.origin.split('.')[0].split('//')[1]
|
||||
playMusicUrl1 = `${ProxyUrl}/mc?m=${subdomain}&url=${pathname}`
|
||||
// console.log('playMusicUrl1', playMusicUrl1)
|
||||
// // 获取音频文件
|
||||
// const { data } = await axios.get(playMusicUrl1, {
|
||||
// responseType: 'blob'
|
||||
// })
|
||||
// const musicUrl = URL.createObjectURL(data)
|
||||
// console.log('musicUrl', musicUrl)
|
||||
// playMusicUrl.value = musicUrl
|
||||
playMusicUrl.value = playMusicUrl1
|
||||
console.log('playMusicUrl1', playMusicUrl1)
|
||||
setTimeout(() => {
|
||||
onAudio()
|
||||
store.commit('setPlayMusic', true)
|
||||
}, 100)
|
||||
} else {
|
||||
playMusicUrl.value = value
|
||||
}
|
||||
() => {
|
||||
loadLrc(playMusic.value.id)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
// 获取音乐播放Dom
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
// 监听音乐是否播放
|
||||
watch(
|
||||
() => play.value,
|
||||
(value, oldValue) => {
|
||||
if (value && audio.value) {
|
||||
audioPlay()
|
||||
onAudio()
|
||||
} else {
|
||||
audioPause()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => playMusicUrl.value,
|
||||
(value, oldValue) => {
|
||||
if (!value) {
|
||||
parsingMusic()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 抬起键盘按钮监听
|
||||
document.onkeyup = (e) => {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
playMusicEvent()
|
||||
}
|
||||
}
|
||||
|
||||
// 按下键盘按钮监听
|
||||
document.onkeydown = (e) => {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const audio = ref<HTMLAudioElement | null>(null)
|
||||
|
||||
|
||||
const audioPlay = () => {
|
||||
if (audio.value) {
|
||||
@@ -198,7 +144,6 @@ const audioPause = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 计算属性 获取当前播放时间的进度
|
||||
const timeSlider = computed({
|
||||
get: () => (nowTime.value / allTime.value) * 100,
|
||||
@@ -236,9 +181,18 @@ const onAudio = () => {
|
||||
audio.value.removeEventListener('ended', handleEnded)
|
||||
audio.value.addEventListener('timeupdate', handleGetAudioTime)
|
||||
audio.value.addEventListener('ended', handleEnded)
|
||||
// 监听音乐播放暂停
|
||||
audio.value.addEventListener('pause', () => {
|
||||
store.commit('setPlayMusic', false)
|
||||
})
|
||||
audio.value.addEventListener('play', () => {
|
||||
store.commit('setPlayMusic', true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onAudio()
|
||||
|
||||
function handleEnded() {
|
||||
store.commit('nextPlay')
|
||||
}
|
||||
@@ -277,22 +231,9 @@ const setMusicFull = () => {
|
||||
musicFull.value = !musicFull.value
|
||||
}
|
||||
|
||||
// 解析音乐
|
||||
const parsingMusic = async () => {
|
||||
const { data } = await getParsingMusicUrl(playMusic.value.id)
|
||||
store.state.playMusicUrl = data.data.url
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.musicPage-enter-active {
|
||||
animation: fadeInUp 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.musicPage-leave-active {
|
||||
animation: fadeOutDown 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
@@ -300,12 +241,10 @@ const parsingMusic = async () => {
|
||||
|
||||
.music-play-bar {
|
||||
@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: 99999999;
|
||||
backdrop-filter: blur(20px);
|
||||
background-color: rgba(0, 0, 0, 0.747);
|
||||
|
||||
.music-content {
|
||||
width: 200px;
|
||||
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 {
|
||||
width: 140px;
|
||||
@apply ml-4;
|
||||
|
||||
&-title {
|
||||
@@ -370,4 +309,19 @@ const parsingMusic = async () => {
|
||||
@apply text-2xl hover:text-green-500 transition cursor-pointer m-4;
|
||||
}
|
||||
}
|
||||
|
||||
.music-play{
|
||||
|
||||
&-list{
|
||||
height: 50vh;
|
||||
@apply relative rounded-3xl overflow-hidden;
|
||||
&-back{
|
||||
backdrop-filter: blur(20px);
|
||||
@apply absolute top-0 left-0 w-full h-full bg-gray-800 bg-opacity-75;
|
||||
}
|
||||
&-content{
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,166 +1,168 @@
|
||||
<template>
|
||||
<div class="search-box flex">
|
||||
<div class="search-box-input flex-1">
|
||||
<n-input
|
||||
size="large"
|
||||
round
|
||||
v-model:value="searchValue"
|
||||
:placeholder="hotSearchKeyword"
|
||||
class="border border-gray-600"
|
||||
@keydown.enter="search"
|
||||
>
|
||||
<template #prefix>
|
||||
<i class="iconfont icon-search"></i>
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
<div class="user-box">
|
||||
<n-dropdown trigger="hover" @select="selectItem" :options="options">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</n-dropdown>
|
||||
<n-avatar
|
||||
class="ml-2 cursor-pointer"
|
||||
circle
|
||||
size="large"
|
||||
:src="store.state.user.avatarUrl"
|
||||
v-if="store.state.user"
|
||||
/>
|
||||
<n-avatar
|
||||
class="ml-2 cursor-pointer"
|
||||
circle
|
||||
size="large"
|
||||
src="https://picsum.photos/200/300?random=1"
|
||||
@click="toLogin()"
|
||||
v-else
|
||||
>登录</n-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getSearchKeyword, getHotSearch } from '@/api/home';
|
||||
import { getUserDetail, logout } from '@/api/login';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import request from '@/utils/request_mt'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore();
|
||||
|
||||
// 推荐热搜词
|
||||
const hotSearchKeyword = ref("搜索点什么吧...")
|
||||
const hotSearchValue = ref("")
|
||||
const loadHotSearchKeyword = async () => {
|
||||
const { data } = await getSearchKeyword();
|
||||
hotSearchKeyword.value = data.data.showKeyword
|
||||
hotSearchValue.value = data.data.realkeyword
|
||||
}
|
||||
|
||||
store.state.user = JSON.parse(localStorage.getItem('user') || '{}')
|
||||
|
||||
const loadPage = async () => {
|
||||
const { data } = await getUserDetail()
|
||||
store.state.user = data.profile
|
||||
localStorage.setItem('user', JSON.stringify(data.profile))
|
||||
}
|
||||
|
||||
const toLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadHotSearchKeyword()
|
||||
loadPage()
|
||||
})
|
||||
|
||||
|
||||
// 搜索词
|
||||
const searchValue = ref("")
|
||||
const search = () => {
|
||||
|
||||
let value = searchValue.value
|
||||
if (value == "") {
|
||||
searchValue.value = hotSearchValue.value
|
||||
} else {
|
||||
router.push({
|
||||
path: "/search",
|
||||
query: {
|
||||
keyword: value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const value = 'Drive My Car'
|
||||
const options = [
|
||||
{
|
||||
label: '打卡',
|
||||
key: 'card'
|
||||
},
|
||||
{
|
||||
label: '听歌升级',
|
||||
key: 'card_music'
|
||||
},
|
||||
{
|
||||
label: '歌曲次数',
|
||||
key: 'listen'
|
||||
},
|
||||
{
|
||||
label: '登录',
|
||||
key: 'login'
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout'
|
||||
}
|
||||
]
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-box {
|
||||
@apply ml-6 flex text-lg justify-center items-center rounded-full pl-3 border border-gray-600;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.search-box-input {
|
||||
@apply relative;
|
||||
}
|
||||
<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>
|
||||
67
src/layout/components/TitleBar.vue
Normal file
67
src/layout/components/TitleBar.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div id="title-bar" @mousedown="drag">
|
||||
<div id="title">Alger Music</div>
|
||||
<div id="buttons">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDialog } from 'naive-ui'
|
||||
|
||||
const dialog = useDialog()
|
||||
const windowData = window as any
|
||||
|
||||
const minimize = () => {
|
||||
windowData.electronAPI.minimize()
|
||||
}
|
||||
|
||||
const maximize = () => {
|
||||
windowData.electronAPI.maximize()
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '确定要退出吗?',
|
||||
positiveText: '最小化',
|
||||
negativeText: '关闭',
|
||||
onPositiveClick: () => {
|
||||
windowData.electronAPI.miniTray()
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
windowData.electronAPI.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const drag = (event: MouseEvent) => {
|
||||
windowData.electronAPI.dragStart(event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#title-bar {
|
||||
-webkit-app-region: drag;
|
||||
@apply flex justify-between text-white px-6 py-2 select-none relative;
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
#buttons {
|
||||
@apply flex gap-4;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply hover:text-green-500;
|
||||
}
|
||||
</style>
|
||||
@@ -5,8 +5,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
lrcList: {
|
||||
type: Array,
|
||||
|
||||
38
src/main.ts
38
src/main.ts
@@ -1,19 +1,19 @@
|
||||
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 { 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");
|
||||
|
||||
@@ -1,54 +1,61 @@
|
||||
const layoutRouter = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
mate: {
|
||||
keepAlive: true,
|
||||
title: "首页",
|
||||
icon: "icon-Home",
|
||||
path: '/',
|
||||
name: 'home',
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'icon-Home',
|
||||
},
|
||||
component: () => import("@/views/home/index.vue"),
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
},
|
||||
{
|
||||
path: "/search",
|
||||
name: "search",
|
||||
mate: {
|
||||
title: "搜索",
|
||||
keepAlive: true,
|
||||
icon: "icon-Search",
|
||||
path: '/search',
|
||||
name: 'search',
|
||||
meta: {
|
||||
title: '搜索',
|
||||
noScroll: true,
|
||||
noKeepAlive: true,
|
||||
icon: 'icon-Search',
|
||||
},
|
||||
component: () => import("@/views/search/index.vue"),
|
||||
component: () => import('@/views/search/index.vue'),
|
||||
},
|
||||
{
|
||||
path: "/list",
|
||||
name: "list",
|
||||
mate: {
|
||||
title: "歌单",
|
||||
keepAlive: true,
|
||||
icon: "icon-Paper",
|
||||
path: '/list',
|
||||
name: 'list',
|
||||
meta: {
|
||||
title: '歌单',
|
||||
icon: 'icon-Paper',
|
||||
},
|
||||
component: () => import("@/views/list/index.vue"),
|
||||
component: () => import('@/views/list/index.vue'),
|
||||
},
|
||||
{
|
||||
path: "/user",
|
||||
name: "user",
|
||||
mate: {
|
||||
title: "用户",
|
||||
keepAlive: true,
|
||||
icon: "icon-Profile",
|
||||
path: '/mv',
|
||||
name: 'mv',
|
||||
meta: {
|
||||
title: 'MV',
|
||||
icon: 'icon-recordfill',
|
||||
},
|
||||
component: () => import("@/views/user/index.vue"),
|
||||
component: () => import('@/views/mv/index.vue'),
|
||||
},
|
||||
{
|
||||
path: "/test",
|
||||
name: "test",
|
||||
mate: {
|
||||
title: "用户",
|
||||
keepAlive: true,
|
||||
icon: "icon-Profile",
|
||||
path: '/history',
|
||||
name: 'history',
|
||||
meta: {
|
||||
title: '历史',
|
||||
icon: 'icon-a-TicketStar',
|
||||
},
|
||||
component: () => import("@/views/test/test.vue"),
|
||||
component: () => import('@/views/history/index.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
meta: {
|
||||
title: '用户',
|
||||
noKeepAlive: true,
|
||||
icon: 'icon-Profile',
|
||||
noScroll: true,
|
||||
},
|
||||
component: () => import('@/views/user/index.vue'),
|
||||
},
|
||||
]
|
||||
export default layoutRouter;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import AppLayout from '@/layout/AppLayout.vue'
|
||||
import homeRouter from '@/router/home'
|
||||
|
||||
let loginRouter = {
|
||||
const loginRouter = {
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
mate: {
|
||||
@@ -13,11 +13,22 @@ let loginRouter = {
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
}
|
||||
|
||||
const setRouter = {
|
||||
path: '/set',
|
||||
name: 'set',
|
||||
mate: {
|
||||
keepAlive: true,
|
||||
title: '设置',
|
||||
icon: 'icon-Home',
|
||||
},
|
||||
component: () => import('@/views/set/index.vue'),
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
children: [...homeRouter, loginRouter],
|
||||
children: [...homeRouter, loginRouter, setRouter],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
10
src/shims-vue.d.ts
vendored
10
src/shims-vue.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
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,7 +1,9 @@
|
||||
import { createStore } from "vuex";
|
||||
import { SongResult } from "@/type/music";
|
||||
import { getMusicUrl } from "@/api/music";
|
||||
import homeRouter from "@/router/home";
|
||||
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'
|
||||
|
||||
interface State {
|
||||
menus: any[]
|
||||
@@ -12,6 +14,7 @@ interface State {
|
||||
user: any
|
||||
playList: SongResult[]
|
||||
playListIndex: number
|
||||
setData: any
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
@@ -23,8 +26,13 @@ const state: State = {
|
||||
user: null,
|
||||
playList: [],
|
||||
playListIndex: 0,
|
||||
setData: null,
|
||||
}
|
||||
|
||||
const windowData = window as any
|
||||
|
||||
const musicHistory = useMusicHistory()
|
||||
|
||||
const mutations = {
|
||||
setMenus(state: State, menus: any[]) {
|
||||
state.menus = menus
|
||||
@@ -33,6 +41,7 @@ const mutations = {
|
||||
state.playMusic = playMusic
|
||||
state.playMusicUrl = await getSongUrl(playMusic.id)
|
||||
state.play = true
|
||||
musicHistory.addMusic(playMusic)
|
||||
},
|
||||
setIsPlay(state: State, isPlay: boolean) {
|
||||
state.isPlay = isPlay
|
||||
@@ -41,36 +50,62 @@ const mutations = {
|
||||
state.play = play
|
||||
},
|
||||
setPlayList(state: State, playList: SongResult[]) {
|
||||
state.playListIndex = 0
|
||||
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.playListIndex = (state.playListIndex + 1) % state.playList.length
|
||||
await updatePlayMusic(state)
|
||||
},
|
||||
async prevPlay(state: State) {
|
||||
if (state.playList.length === 0) {
|
||||
state.play = true
|
||||
return
|
||||
}
|
||||
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))
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const getSongUrl = async (id: number) => {
|
||||
const { data } = await getMusicUrl(id);
|
||||
console.log(data.data[0].url);
|
||||
|
||||
return data.data[0].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
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error', error)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
const store = createStore({
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
});
|
||||
})
|
||||
|
||||
export default store;
|
||||
export default store
|
||||
|
||||
@@ -3,31 +3,31 @@ export interface IAlbumNew {
|
||||
albums: Album[];
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
}
|
||||
|
||||
interface Artist2 {
|
||||
|
||||
4
src/type/index.ts
Normal file
4
src/type/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IData<T> {
|
||||
code: number
|
||||
data: T
|
||||
}
|
||||
@@ -6,43 +6,43 @@ export interface IList {
|
||||
total: number;
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
}
|
||||
|
||||
interface Subscriber {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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,196 +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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
112
src/type/mv.ts
Normal file
112
src/type/mv.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
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[]
|
||||
}
|
||||
|
||||
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[]
|
||||
}
|
||||
|
||||
interface Video {
|
||||
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
|
||||
}
|
||||
|
||||
interface Artist {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
// {
|
||||
// "id": 14686812,
|
||||
// "url": "http://vodkgeyttp8.vod.126.net/cloudmusic/e18b/core/aa57/6f56150a35613ef77fc70b253bea4977.mp4?wsSecret=84a301277e05143de1dd912d2a4dbb0d&wsTime=1703668700",
|
||||
// "r": 1080,
|
||||
// "size": 215391070,
|
||||
// "md5": "",
|
||||
// "code": 200,
|
||||
// "expi": 3600,
|
||||
// "fee": 0,
|
||||
// "mvFee": 0,
|
||||
// "st": 0,
|
||||
// "promotionVo": null,
|
||||
// "msg": ""
|
||||
// }
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
// 设置歌手背景图片
|
||||
export const setBackgroundImg = (url: String) => {
|
||||
return 'background-image:' + 'url(' + url + ')'
|
||||
@@ -27,12 +25,40 @@ export const secondToMinute = (s: number) => {
|
||||
return minuteStr + ':' + secondStr
|
||||
}
|
||||
|
||||
// 格式化数字 千,万, 百万, 千万,亿
|
||||
export const formatNumber = (num: any) => {
|
||||
num = num * 1
|
||||
if (num < 10000) {
|
||||
return num
|
||||
}
|
||||
if (num < 100000000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
}
|
||||
return (num / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
const windowData = window as any
|
||||
export const getIsMc = () => {
|
||||
return !!location.href.includes('mc.')
|
||||
if (!windowData.electron) {
|
||||
return false
|
||||
}
|
||||
if (windowData.electron.ipcRenderer.getStoreValue('set').isProxy) {
|
||||
return true
|
||||
}
|
||||
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
|
||||
}
|
||||
const PUrl = url.split('').join('+')
|
||||
return `${ProxyUrl}/mc?url=${PUrl}`
|
||||
}
|
||||
|
||||
export const getImgUrl = computed(() => (url: string, size: string) => {
|
||||
export const getImgUrl = computed(() => (url: string, size: string = '') => {
|
||||
const bdUrl = 'https://image.baidu.com/search/down?url='
|
||||
const imgUrl = encodeURIComponent(`${url}?param=${size}`)
|
||||
return getIsMc() ? `${bdUrl}${imgUrl}` : `${url}?param=${size}`
|
||||
return `${bdUrl}${imgUrl}`
|
||||
})
|
||||
|
||||
51
src/views/history/index.vue
Normal file
51
src/views/history/index.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="history-page">
|
||||
<div class="title">播放历史</div>
|
||||
<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)">
|
||||
<song-item class="history-item-content" :item="item" />
|
||||
<div class="history-item-count">
|
||||
{{ item.count }}
|
||||
</div>
|
||||
<div class="history-item-delete">
|
||||
<i class="iconfont icon-close" @click="delMusic(item)"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook'
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
|
||||
const {delMusic, musicList} = useMusicHistory();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.history-page{
|
||||
@apply h-full w-full pt-2;
|
||||
.title{
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
|
||||
.history-list-content{
|
||||
@apply px-4 mt-2;
|
||||
.history-item{
|
||||
@apply flex items-center justify-between;
|
||||
&-content{
|
||||
@apply flex-1;
|
||||
}
|
||||
&-count{
|
||||
@apply px-4 text-lg;
|
||||
}
|
||||
&-delete{
|
||||
@apply cursor-pointer rounded-full border-2 border-gray-400 w-8 h-8 flex justify-center items-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +1,19 @@
|
||||
<template>
|
||||
<n-layout class="main-page" :native-scrollbar="false">
|
||||
<!-- 推荐歌手 -->
|
||||
<recommend-singer />
|
||||
<div class="main-content">
|
||||
<!-- 歌单分类列表 -->
|
||||
<playlist-type />
|
||||
<!-- 本周最热音乐 -->
|
||||
<recommend-songlist />
|
||||
<!-- 推荐最新专辑 -->
|
||||
<recommend-album />
|
||||
</div>
|
||||
</n-layout>
|
||||
<div class="main-page">
|
||||
<!-- 推荐歌手 -->
|
||||
<recommend-singer />
|
||||
<div class="main-content">
|
||||
<!-- 歌单分类列表 -->
|
||||
<playlist-type />
|
||||
<!-- 本周最热音乐 -->
|
||||
<recommend-songlist />
|
||||
<!-- 推荐最新专辑 -->
|
||||
<recommend-album />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
const RecommendSinger = defineAsyncComponent(() => import("@/components/RecommendSinger.vue"));
|
||||
const PlaylistType = defineAsyncComponent(() => import("@/components/PlaylistType.vue"));
|
||||
const RecommendSonglist = defineAsyncComponent(() => import("@/components/RecommendSonglist.vue"));
|
||||
@@ -22,11 +21,10 @@ const RecommendAlbum = defineAsyncComponent(() => import("@/components/Recommend
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-page {
|
||||
@apply mt-4 h-full;
|
||||
.main-page{
|
||||
@apply h-full w-full;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@apply mt-6 flex;
|
||||
@apply mt-6 flex pb-28;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { getRecommendList, getListDetail, getListByTag, getListByCat } from '@/api/list'
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import type { IRecommendList, IRecommendItem } from "@/type/list";
|
||||
import { getRecommendList, getListDetail, getListByCat } from '@/api/list'
|
||||
import type { IRecommendItem } from "@/type/list";
|
||||
import type { IListDetail } from "@/type/listDetail";
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl } from "@/utils";
|
||||
import SongItem from "@/components/common/SongItem.vue";
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl, formatNumber } from "@/utils";
|
||||
import { useRoute } from 'vue-router';
|
||||
import MusicList from "@/components/MusicList.vue";
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
|
||||
|
||||
const store = useStore();
|
||||
const recommendList = ref()
|
||||
const showMusic = ref(false)
|
||||
|
||||
@@ -21,9 +18,6 @@ const selectRecommendItem = async (item: IRecommendItem) => {
|
||||
recommendItem.value = item
|
||||
listDetail.value = data
|
||||
}
|
||||
const closeMusic = () => {
|
||||
showMusic.value = false
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const listTitle = ref(route.query.type || "歌单列表");
|
||||
@@ -49,67 +43,29 @@ if (route.query.type) {
|
||||
watch(
|
||||
() => route.query,
|
||||
async newParams => {
|
||||
const params = {
|
||||
tag: newParams.type || '',
|
||||
limit: 30,
|
||||
before: 0
|
||||
if(newParams.type){
|
||||
const params = {
|
||||
tag: newParams.type || '',
|
||||
limit: 30,
|
||||
before: 0
|
||||
}
|
||||
loadList(newParams.type);
|
||||
}
|
||||
loadList(newParams.type);
|
||||
}
|
||||
)
|
||||
|
||||
const musicFullClass = computed(() => {
|
||||
if (recommendItem.value) {
|
||||
return setAnimationClass('animate__fadeInUp')
|
||||
} else {
|
||||
return setAnimationClass('animate__fadeOutDown')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 格式化数字 千,万, 百万, 千万,亿
|
||||
const formatNumber = (num: any) => {
|
||||
num = num * 1
|
||||
if (num < 10000) {
|
||||
return num
|
||||
}
|
||||
if (num < 100000000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
}
|
||||
return (num / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
|
||||
const formatDetail = computed(() => (detail: any) => {
|
||||
let 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 list = listDetail.value?.playlist.tracks || []
|
||||
console.log('list',list)
|
||||
console.log('item',item)
|
||||
const musicIndex = (list.findIndex((music: any) => music.id == item.id) || 0)
|
||||
store.commit('setPlayList', list.slice(musicIndex))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="list-page">
|
||||
<!-- 歌单列表 -->
|
||||
<n-layout class="recommend" :native-scrollbar="false" @click="showMusic = false">
|
||||
<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">
|
||||
<div
|
||||
class="recommend-item"
|
||||
@@ -122,6 +78,8 @@ const handlePlay = (item: any) => {
|
||||
<n-image
|
||||
class="recommend-item-img-img"
|
||||
:src="getImgUrl( (item.picUrl || item.coverImgUrl), '200y200')"
|
||||
width="200"
|
||||
height="200"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
@@ -133,50 +91,26 @@ const handlePlay = (item: any) => {
|
||||
<div class="recommend-item-title">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-layout>
|
||||
|
||||
<transition name="musicPage">
|
||||
<div class="music-page" v-if="showMusic">
|
||||
<i class="iconfont icon-icon_error music-close" @click="closeMusic()"></i>
|
||||
<div class="music-title">{{ recommendItem?.name }}</div>
|
||||
<!-- 歌单歌曲列表 -->
|
||||
<n-layout class="music-list" :native-scrollbar="false">
|
||||
<div
|
||||
v-for="(item, index) in listDetail?.playlist.tracks"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SongItem :item="formatDetail(item)" @play="handlePlay" />
|
||||
</div>
|
||||
</n-layout>
|
||||
</div>
|
||||
</transition>
|
||||
<PlayBottom/>
|
||||
</n-scrollbar>
|
||||
<MusicList v-if="listDetail?.playlist" v-model:show="showMusic" :name="listDetail?.playlist.name" :song-list="listDetail?.playlist.tracks" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-page {
|
||||
@apply relative h-full;
|
||||
}
|
||||
|
||||
.musicPage-enter-active {
|
||||
animation: fadeInUp 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.musicPage-leave-active {
|
||||
animation: fadeOutDown 0.8s ease-in-out;
|
||||
@apply relative h-full w-full;
|
||||
}
|
||||
|
||||
.recommend {
|
||||
@apply w-full h-full;
|
||||
@apply w-full h-full bg-none;
|
||||
&-title {
|
||||
@apply text-lg font-bold text-white py-4;
|
||||
@apply text-lg font-bold text-white pb-4;
|
||||
}
|
||||
|
||||
&-list {
|
||||
@apply grid gap-6 pb-28;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(13%, 1fr));
|
||||
}
|
||||
&-item {
|
||||
&-img {
|
||||
@@ -185,8 +119,7 @@ const handlePlay = (item: any) => {
|
||||
@apply hover:scale-110 transition-all duration-300 ease-in-out;
|
||||
}
|
||||
&-img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
@apply h-full w-full rounded-xl overflow-hidden;
|
||||
}
|
||||
.top {
|
||||
@apply absolute w-full h-full top-0 left-0 flex justify-center items-center transition-all duration-300 ease-in-out cursor-pointer;
|
||||
@@ -218,29 +151,4 @@ const handlePlay = (item: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
.music {
|
||||
&-page {
|
||||
// width: 100%;
|
||||
// height: 70%;
|
||||
// position: absolute;
|
||||
// background-color: #000000f0;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
// border-radius: 30px 30px 0 0;
|
||||
// animation-duration: 300ms;
|
||||
@apply w-full h-5/6 absolute bottom-0 left-0 bg-black rounded-t-3xl flex flex-col transition-all;
|
||||
}
|
||||
&-title {
|
||||
@apply text-lg font-bold text-white p-4;
|
||||
}
|
||||
|
||||
&-close {
|
||||
@apply absolute top-4 right-4 cursor-pointer text-white text-3xl;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: 594px;
|
||||
background-color: #00000000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,168 +1,170 @@
|
||||
<script lang="ts" setup>
|
||||
import { getQrKey, createQr, checkQr, getLoginStatus } from '@/api/login'
|
||||
import { onMounted } from '@vue/runtime-core';
|
||||
import { ref } from 'vue';
|
||||
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)
|
||||
}
|
||||
}, 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('/')
|
||||
}, 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 p-4 flex flex-col items-center justify-center p-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
<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>
|
||||
160
src/views/mv/index.vue
Normal file
160
src/views/mv/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="mv-list">
|
||||
<div class="mv-list-title">
|
||||
<h2>推荐MV</h2>
|
||||
</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 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" />
|
||||
<div class="top">
|
||||
<div class="play-count">{{ formatNumber(item.playCount) }}</div>
|
||||
<i class="iconfont icon-videofill"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mv-item-title">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
|
||||
<n-drawer :show="showMv" height="100vh" placement="bottom" :z-index="999999999">
|
||||
<div class="mv-detail">
|
||||
<video :src="playMvUrl" controls autoplay></video>
|
||||
<div class="mv-detail-title">
|
||||
<div class="title">{{ playMvItem?.name }}</div>
|
||||
<button @click="close">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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()
|
||||
|
||||
onMounted(async () => {
|
||||
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)
|
||||
playMvItem.value = item;
|
||||
playMvUrl.value = res.data.data.url
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
showMv.value = false
|
||||
if (store.state.playMusicUrl) {
|
||||
store.commit('setIsPlay', true)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mv-list {
|
||||
@apply relative h-full w-full;
|
||||
|
||||
&-title {
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
|
||||
&-content {
|
||||
@apply grid gap-6 pb-4 mt-2;
|
||||
grid-template-columns: repeat(auto-fill, minmax(14%, 1fr));
|
||||
}
|
||||
|
||||
.mv-item {
|
||||
@apply p-2 rounded-lg;
|
||||
background-color: #1f1f1f;
|
||||
&-img {
|
||||
@apply rounded-lg overflow-hidden relative;
|
||||
line-height: 0;
|
||||
|
||||
&:hover img {
|
||||
@apply hover:scale-110 transition-all duration-300 ease-in-out object-top;
|
||||
}
|
||||
|
||||
&-img {
|
||||
@apply w-full rounded-lg overflow-hidden;
|
||||
}
|
||||
|
||||
.top {
|
||||
@apply absolute w-full h-full top-0 left-0 flex justify-center items-center transition-all duration-300 ease-in-out cursor-pointer;
|
||||
background-color: #0000009b;
|
||||
opacity: 0;
|
||||
|
||||
i {
|
||||
font-size: 40px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
&:hover i {
|
||||
@apply transform scale-150 opacity-80;
|
||||
}
|
||||
|
||||
.play-count {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@apply p-2 text-sm text-white truncate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mv-detail {
|
||||
@apply w-full h-full bg-black relative;
|
||||
|
||||
&-title {
|
||||
@apply absolute w-full left-0 flex justify-between h-16 px-6 py-2 text-xl font-bold items-center z-50 transition-all duration-300 ease-in-out -top-24;
|
||||
background: linear-gradient(0, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%);
|
||||
button .icon-xiasanjiaoxing {
|
||||
@apply text-3xl;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@apply text-green-400;
|
||||
}
|
||||
}
|
||||
|
||||
video {
|
||||
@apply w-full h-full;
|
||||
}
|
||||
video:hover + .mv-detail-title {
|
||||
@apply top-0;
|
||||
}
|
||||
|
||||
.mv-detail-title:hover {
|
||||
@apply top-0;
|
||||
}
|
||||
}</style>
|
||||
@@ -33,13 +33,26 @@
|
||||
<div class="search-list-box">
|
||||
<template v-if="searchDetail">
|
||||
<div
|
||||
v-for="(item, index) in searchDetail?.result.songs"
|
||||
v-for="(item, index) in searchDetail?.songs"
|
||||
:key="item.id"
|
||||
:class="setAnimationClass('animate__bounceInRight')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SongItem :item="item" />
|
||||
<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>
|
||||
@@ -52,14 +65,15 @@ import type { IHotSearch } from "@/type/search";
|
||||
import { getHotSearch } from "@/api/home";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { setAnimationClass, setAnimationDelay } from "@/utils";
|
||||
import type { ISearchDetail } from "@/type/search";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import SongItem from "@/components/common/SongItem.vue";
|
||||
|
||||
import { useStore } from "vuex";
|
||||
import { useDateFormat } from '@vueuse/core'
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const searchDetail = ref<any>();
|
||||
const searchType = ref(Number(route.query.type) || 1);
|
||||
|
||||
// 热搜列表
|
||||
const hotSearchData = ref<IHotSearch>();
|
||||
@@ -80,20 +94,21 @@ const clickHotKeyword = (keyword: string) => {
|
||||
path: "/search",
|
||||
query: {
|
||||
keyword: keyword,
|
||||
type: 1
|
||||
},
|
||||
});
|
||||
// isHotSearchList.value = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const loadSearch = async (keyword: any) => {
|
||||
const dateFormat = (time:any) => useDateFormat(time, 'YYYY.MM.DD').value
|
||||
const loadSearch = async (keywords: any) => {
|
||||
hotKeyword.value = keywords;
|
||||
searchDetail.value = undefined;
|
||||
if (!keyword) return;
|
||||
const { data } = await getSearch(keyword);
|
||||
if (!keywords) return;
|
||||
const { data } = await getSearch({keywords, type:searchType.value});
|
||||
|
||||
const songs = data.result.songs;
|
||||
const songs = data.result.songs || [];
|
||||
const albums = data.result.albums || [];
|
||||
|
||||
// songs map 替换属性
|
||||
songs.map((item: any) => {
|
||||
@@ -101,7 +116,13 @@ const loadSearch = async (keyword: any) => {
|
||||
item.song = item;
|
||||
item.artists = item.ar;
|
||||
});
|
||||
searchDetail.value = data;
|
||||
albums.map((item: any) => {
|
||||
item.desc = `${item.artist.name } ${ item.company } ${dateFormat(item.publishTime)}`;
|
||||
});
|
||||
searchDetail.value = {
|
||||
songs,
|
||||
albums
|
||||
}
|
||||
};
|
||||
|
||||
loadSearch(route.query.keyword);
|
||||
@@ -109,9 +130,17 @@ loadSearch(route.query.keyword);
|
||||
watch(
|
||||
() => route.query,
|
||||
async newParams => {
|
||||
searchType.value = Number(newParams.type || 1)
|
||||
loadSearch(newParams.keyword);
|
||||
}
|
||||
)
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
const tracks = searchDetail.value?.songs || []
|
||||
store.commit('setPlayList', tracks)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -119,7 +148,7 @@ watch(
|
||||
@apply flex h-full;
|
||||
}
|
||||
.hot-search {
|
||||
@apply mt-3 mr-4 rounded-xl flex-1 overflow-hidden;
|
||||
@apply mr-4 rounded-xl flex-1 overflow-hidden;
|
||||
background-color: #0d0d0d;
|
||||
animation-duration: 0.2s;
|
||||
min-width: 400px;
|
||||
@@ -139,7 +168,7 @@ watch(
|
||||
}
|
||||
|
||||
.search-list {
|
||||
@apply mt-3 flex-1 rounded-xl;
|
||||
@apply flex-1 rounded-xl;
|
||||
background-color: #0d0d0d;
|
||||
height: 100%;
|
||||
&-box{
|
||||
|
||||
64
src/views/set/index.vue
Normal file
64
src/views/set/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="set-page">
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">代理</div>
|
||||
<div class="set-item-content">无法听音乐时打开</div>
|
||||
</div>
|
||||
<n-switch v-model:value="setData.isProxy"/>
|
||||
</div>
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">版本</div>
|
||||
<div class="set-item-content">当前已是最新版本</div>
|
||||
</div>
|
||||
<div>{{ setData.version }}</div>
|
||||
</div>
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">作者</div>
|
||||
<div class="set-item-content"></div>
|
||||
</div>
|
||||
<div>{{ setData.author }}</div>
|
||||
</div>
|
||||
|
||||
<div class="set-action">
|
||||
<n-button @click="handelCancel">取消</n-button>
|
||||
<n-button type="primary" @click="handleSave">保存并重启</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import store from '@/store'
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const setData = ref(store.state.setData)
|
||||
const router = useRouter()
|
||||
|
||||
const handelCancel = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const windowData = window as any
|
||||
|
||||
const handleSave = () => {
|
||||
store.commit('setSetData', setData.value)
|
||||
windowData.electronAPI.restart()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.set-page{
|
||||
@apply flex flex-col justify-center items-center pt-8;
|
||||
}
|
||||
.set-item{
|
||||
@apply w-3/5 flex justify-between items-center mb-4;
|
||||
.set-item-title{
|
||||
@apply text-gray-200 text-base;
|
||||
}
|
||||
.set-item-content{
|
||||
@apply text-gray-400 text-sm;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="box"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: url(http://p2.music.126.net/w_vuv9hBWq2hlJxJcmJrjg==/109951166115915081.jpg?param=500y500);
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
||||
@@ -7,17 +7,17 @@ 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 type { Playlist } from '@/type/listDetail';
|
||||
import PlayBottom from "@/components/common/PlayBottom.vue";
|
||||
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
const userDetail = ref<IUserDetail>()
|
||||
const playList = ref<any[]>([])
|
||||
const recordList = ref()
|
||||
const user = store.state.user
|
||||
let user = store.state.user
|
||||
|
||||
const loadPage = async () => {
|
||||
if (!user) {
|
||||
@@ -33,14 +33,18 @@ const loadPage = async () => {
|
||||
|
||||
const { data: recordData } = await getUserRecord(user.userId)
|
||||
recordList.value = recordData.allData
|
||||
|
||||
|
||||
}
|
||||
loadPage()
|
||||
|
||||
watchEffect(() => {
|
||||
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()
|
||||
const list = ref<Playlist>()
|
||||
// 展示歌单
|
||||
const showPlaylist = async (id: number) => {
|
||||
const { data } = await getListDetail(id)
|
||||
@@ -61,21 +65,11 @@ const formatDetail = computed(() => (detail: any) => {
|
||||
return detail
|
||||
})
|
||||
|
||||
const musicFullClass = computed(() => {
|
||||
if (isShowList.value) {
|
||||
return setAnimationClass('animate__fadeInUp')
|
||||
} else {
|
||||
return setAnimationClass('animate__fadeOutDown')
|
||||
}
|
||||
})
|
||||
|
||||
const handlePlay = (item: any) => {
|
||||
const tracks = list.value?.tracks || []
|
||||
const musicIndex = (tracks.findIndex((music: any) => music.id == item.id) || 0)
|
||||
store.commit('setPlayList', tracks.slice(musicIndex))
|
||||
const tracks = recordList.value || []
|
||||
store.commit('setPlayList', tracks)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -84,12 +78,12 @@ const handlePlay = (item: any) => {
|
||||
class="left"
|
||||
v-if="userDetail"
|
||||
:class="setAnimationClass('animate__fadeInLeft')"
|
||||
:style="{ backgroundImage: `url(${user.backgroundUrl})` }"
|
||||
: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="user.avatarUrl" />
|
||||
<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>
|
||||
@@ -109,78 +103,61 @@ const handlePlay = (item: any) => {
|
||||
|
||||
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
|
||||
<div class="play-list-title">创建的歌单</div>
|
||||
<div
|
||||
class="play-list-item"
|
||||
v-for="(item,index) in playList"
|
||||
:key="index"
|
||||
@click="showPlaylist(item.id)"
|
||||
>
|
||||
<n-image
|
||||
:src="getImgUrl( item.coverImgUrl, '')"
|
||||
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>
|
||||
<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 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>
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" :class="setAnimationClass('animate__fadeInRight')">
|
||||
<n-layout class="record-list" :native-scrollbar="false">
|
||||
<div class="title">听歌排行</div>
|
||||
<div
|
||||
class="record-item"
|
||||
v-for="(item, index) in recordList"
|
||||
:key="item.song.id"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SongItem class="song-item" :item="formatDetail(item.song)" />
|
||||
<div class="play-count">{{ item.playCount }}次</div>
|
||||
</div>
|
||||
</n-layout>
|
||||
</div>
|
||||
<transition name="musicPage">
|
||||
<div class="music-page" v-if="isShowList">
|
||||
<i class="iconfont icon-icon_error music-close" @click="isShowList = false"></i>
|
||||
<div class="music-title">{{ list?.name }}</div>
|
||||
<!-- 歌单歌曲列表 -->
|
||||
<n-layout class="music-list" :native-scrollbar="false">
|
||||
<div class="title">听歌排行</div>
|
||||
<div class="record-list">
|
||||
<n-scrollbar>
|
||||
<div
|
||||
v-for="(item, index) in list?.tracks"
|
||||
:key="item.id"
|
||||
class="record-item"
|
||||
v-for="(item, index) in recordList"
|
||||
:key="item.song.id"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
:style="setAnimationDelay(index, 50)"
|
||||
>
|
||||
<SongItem :item="formatDetail(item)" @play="handlePlay"/>
|
||||
<SongItem class="song-item" :item="formatDetail(item.song)" @play="handlePlay"/>
|
||||
<div class="play-count">{{ item.playCount }}次</div>
|
||||
</div>
|
||||
</n-layout>
|
||||
<PlayBottom/>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<MusicList v-if="list" v-model:show="isShowList" :name="list.name" :song-list="list.tracks" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.musicPage-enter-active {
|
||||
animation: fadeInUp 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.musicPage-leave-active {
|
||||
animation: fadeOutDown 0.8s ease-in-out;
|
||||
}
|
||||
.user-page {
|
||||
@apply flex;
|
||||
@apply flex h-full;
|
||||
.left {
|
||||
max-width: 600px;
|
||||
background-color: #0d0d0d;
|
||||
background-size: 100%;
|
||||
@apply flex-1 rounded-2xl overflow-hidden relative bg-no-repeat;
|
||||
@apply flex-1 rounded-2xl overflow-hidden relative bg-no-repeat h-full;
|
||||
.page {
|
||||
@apply p-4 w-full z-10;
|
||||
@apply p-4 w-full z-10 flex flex-col h-full;
|
||||
background-color: #0d0d0d66;
|
||||
}
|
||||
.user-name {
|
||||
@@ -204,9 +181,8 @@ const handlePlay = (item: any) => {
|
||||
.right {
|
||||
@apply flex-1 ml-4;
|
||||
.record-list {
|
||||
height: 750px;
|
||||
background-color: #0d0d0d;
|
||||
@apply rounded-2xl;
|
||||
@apply rounded-2xl h-full;
|
||||
.record-item {
|
||||
@apply flex items-center px-4;
|
||||
}
|
||||
@@ -226,7 +202,7 @@ const handlePlay = (item: any) => {
|
||||
}
|
||||
|
||||
.play-list {
|
||||
@apply mt-4 py-4 px-2 rounded-xl;
|
||||
@apply mt-4 py-4 px-2 rounded-xl flex-1 overflow-hidden;
|
||||
background-color: #000000;
|
||||
&-title {
|
||||
@apply text-lg text-white opacity-70;
|
||||
@@ -247,28 +223,4 @@ const handlePlay = (item: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
.music {
|
||||
&-page {
|
||||
width: 100%;
|
||||
height: 734px;
|
||||
position: absolute;
|
||||
background-color: #000000f0;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
border-radius: 30px 30px 0 0;
|
||||
animation-duration: 300ms;
|
||||
}
|
||||
&-title {
|
||||
@apply text-lg font-bold text-white p-4;
|
||||
}
|
||||
|
||||
&-close {
|
||||
@apply absolute top-4 right-4 cursor-pointer text-white text-3xl;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: 594px;
|
||||
background-color: #00000000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +1,37 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@": [
|
||||
"src"
|
||||
],
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
]
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"types": [
|
||||
"naive-ui/volar",
|
||||
"./auto-imports.d.ts",
|
||||
"./components.d.ts"
|
||||
],
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@": [
|
||||
"src"
|
||||
],
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,34 @@
|
||||
import { defineConfig } from "vite"
|
||||
import vue from "@vitejs/plugin-vue"
|
||||
import path from "path"
|
||||
import VueDevTools from "vite-plugin-vue-devtools"
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
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'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), VueDevTools()],
|
||||
plugins: [
|
||||
vue(),
|
||||
VueDevTools(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
{
|
||||
'naive-ui': [
|
||||
'useDialog',
|
||||
'useMessage',
|
||||
'useNotification',
|
||||
'useLoadingBar',
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [NaiveUiResolver()],
|
||||
}),
|
||||
],
|
||||
base: './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
@@ -13,6 +36,8 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0', //允许本机
|
||||
// 指定端口
|
||||
port: 4678,
|
||||
proxy: {
|
||||
// string shorthand
|
||||
'/mt': {
|
||||
@@ -27,10 +52,21 @@ export default defineConfig({
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
'/music': {
|
||||
target: 'http://myalger.top:4000',
|
||||
target: 'http://110.42.251.190:4100',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/music/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['vue', 'axios'],
|
||||
naiveui: ['naive-ui'],
|
||||
lodash: ['lodash'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user