Compare commits
12 Commits
v2.0.0-bet
...
v2.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eed76966c4 | ||
|
|
b095e4eb36 | ||
|
|
3dbdf3e613 | ||
|
|
5ceca058a7 | ||
|
|
a8e867bbf9 | ||
|
|
4cb8eb0213 | ||
|
|
461f216cab | ||
|
|
2756313e4a | ||
|
|
e802a2f574 | ||
|
|
a45940b104 | ||
|
|
ac0ac5f4ea | ||
|
|
883b6d13a5 |
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
Dockerfile*
|
||||||
|
docker-compose*
|
||||||
|
.dockerignore
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
.vscode
|
||||||
|
dist
|
||||||
@@ -21,8 +21,6 @@ RENDERER_VITE_SITE_ANTHOR = "無名"
|
|||||||
RENDERER_VITE_SITE_KEYWORDS = "SPlayer,云音乐,播放器,在线音乐,在线播放器,音乐播放器"
|
RENDERER_VITE_SITE_KEYWORDS = "SPlayer,云音乐,播放器,在线音乐,在线播放器,音乐播放器"
|
||||||
RENDERER_VITE_SITE_DES = "一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能"
|
RENDERER_VITE_SITE_DES = "一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能"
|
||||||
RENDERER_VITE_SITE_URL = "imsyy.top"
|
RENDERER_VITE_SITE_URL = "imsyy.top"
|
||||||
RENDERER_VITE_SITE_LOGO = "/images/logo/favicon.svg"
|
|
||||||
RENDERER_VITE_SITE_APPLE_LOGO = "/images/logo/favicon-apple.png"
|
|
||||||
|
|
||||||
# Cookie
|
# Cookie
|
||||||
## 咪咕音乐 Cookie
|
## 咪咕音乐 Cookie
|
||||||
|
|||||||
5
.npmrc
@@ -1,2 +1,5 @@
|
|||||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
|
registry=https://registry.npmmirror.com
|
||||||
|
disturl=https://registry.npmmirror.com/-/binary/node
|
||||||
|
# ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
|
||||||
|
ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/
|
||||||
shamefully-hoist=true
|
shamefully-hoist=true
|
||||||
|
|||||||
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# build
|
||||||
|
FROM node:18-alpine as builder
|
||||||
|
|
||||||
|
RUN apk update && apk add --no-cache git
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN [ ! -e ".env" ] && cp .env.example .env || true
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# nginx
|
||||||
|
FROM nginx:1.20.2-alpine as app
|
||||||
|
|
||||||
|
COPY --from=builder /app/out/renderer /usr/share/nginx/html
|
||||||
|
|
||||||
|
COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
RUN apk add --no-cache npm
|
||||||
|
|
||||||
|
RUN npm install -g NeteaseCloudMusicApi
|
||||||
|
|
||||||
|
CMD nginx && npx NeteaseCloudMusicApi
|
||||||
235
README.md
@@ -1,15 +1,5 @@
|
|||||||
> [!IMPORTANT]
|
|
||||||
>
|
|
||||||
> ## 严肃警告
|
|
||||||
>
|
|
||||||
> - 请务必遵守 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 许可协议
|
|
||||||
> - 在您的修改、演绎、分发或派生项目中,必须同样采用 **AGPL-3.0** 许可协议,**并在适当的位置包含本项目的许可和版权信息**
|
|
||||||
> - **禁止用于售卖或其他商业用途**,如若发现,作者保留追究法律责任的权利
|
|
||||||
> - 若发现未遵守 **AGPL-3.0** 许可协议的行为,**本项目将永久停更**
|
|
||||||
> - 感谢您的尊重与理解
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img alt="logo" height="80" src="./public/images/logo/favicon.png" />
|
<img alt="logo" height="80" src="./public/images/icons/favicon.png" />
|
||||||
<h2>SPlayer</h2>
|
<h2>SPlayer</h2>
|
||||||
<p>一个简约的音乐播放器</p>
|
<p>一个简约的音乐播放器</p>
|
||||||
<img alt="main" src="./screenshots/main.png" />
|
<img alt="main" src="./screenshots/main.png" />
|
||||||
@@ -18,6 +8,16 @@
|
|||||||
|
|
||||||
## 说明
|
## 说明
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> ### 严肃警告
|
||||||
|
>
|
||||||
|
> - 请务必遵守 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 许可协议
|
||||||
|
> - 在您的修改、演绎、分发或派生项目中,必须同样采用 **AGPL-3.0** 许可协议,**并在适当的位置包含本项目的许可和版权信息**
|
||||||
|
> - **禁止用于售卖或其他商业用途**,如若发现,作者保留追究法律责任的权利
|
||||||
|
> - 若发现未遵守 **AGPL-3.0** 许可协议的行为,**本项目将永久停更**
|
||||||
|
> - 感谢您的尊重与理解
|
||||||
|
|
||||||
- 本项目采用 [Vue 3](https://cn.vuejs.org/) 全家桶和 [Naïve UI](https://www.naiveui.com/) 组件库及 [Electron](https://www.electronjs.org/zh/docs/latest/) 开发
|
- 本项目采用 [Vue 3](https://cn.vuejs.org/) 全家桶和 [Naïve UI](https://www.naiveui.com/) 组件库及 [Electron](https://www.electronjs.org/zh/docs/latest/) 开发
|
||||||
- 支持网页端与客户端,由于设备有限,目前仅适配 `Win`,其他平台可自行构建
|
- 支持网页端与客户端,由于设备有限,目前仅适配 `Win`,其他平台可自行构建
|
||||||
- ~~仅对移动端做了基础适配,**不保证功能全部可用**~~
|
- ~~仅对移动端做了基础适配,**不保证功能全部可用**~~
|
||||||
@@ -120,6 +120,35 @@
|
|||||||
|
|
||||||
[Dev Workflow](https://github.com/imsyy/SPlayer/actions/workflows/build.yml)
|
[Dev Workflow](https://github.com/imsyy/SPlayer/actions/workflows/build.yml)
|
||||||
|
|
||||||
|
## ⚙️ Docker 部署
|
||||||
|
|
||||||
|
> 安装及配置 `Docker` 将不在此处说明,请自行解决
|
||||||
|
|
||||||
|
### 本地构建
|
||||||
|
|
||||||
|
> 请尽量拉取最新分支后使用本地构建方式,在线部署的仓库可能更新不及时
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建
|
||||||
|
docker build -t splayer .
|
||||||
|
|
||||||
|
# 运行
|
||||||
|
docker run -d --name SPlayer -p 7899:7899 splayer
|
||||||
|
# 或使用 Docker Compose
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在线部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 拉取
|
||||||
|
docker pull imsyy/splayer:2.0.0-beta.5
|
||||||
|
# 运行
|
||||||
|
docker run -d --name SPlayer -p 7899:7899 imsyy/splayer:2.0.0-beta.5
|
||||||
|
```
|
||||||
|
|
||||||
|
以上步骤成功后,将会在本地 [localhost:7899](http://localhost:7899/) 启动,如需更换端口,请自行修改命令行中的端口号
|
||||||
|
|
||||||
## ⚙️ Vercel 部署
|
## ⚙️ Vercel 部署
|
||||||
|
|
||||||
> 其他部署平台大致相同,在此不做说明
|
> 其他部署平台大致相同,在此不做说明
|
||||||
@@ -132,6 +161,7 @@
|
|||||||
```js
|
```js
|
||||||
RENDERER_VITE_SERVER_URL = "https://example.com";
|
RENDERER_VITE_SERVER_URL = "https://example.com";
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 将 `Build and Output Settings` 中的 `Output Directory` 改为 `out/renderer`
|
5. 将 `Build and Output Settings` 中的 `Output Directory` 改为 `out/renderer`
|
||||||
|
|
||||||

|

|
||||||
@@ -185,11 +215,11 @@
|
|||||||
5. 复制 `/.env.example` 文件并重命名为 `/.env` 并修改配置
|
5. 复制 `/.env.example` 文件并重命名为 `/.env` 并修改配置
|
||||||
6. 打包客户端,请依据你的系统类型来选择,打包成功后,会输出安装包或可执行文件在 `/dist` 目录中,可自行安装
|
6. 打包客户端,请依据你的系统类型来选择,打包成功后,会输出安装包或可执行文件在 `/dist` 目录中,可自行安装
|
||||||
|
|
||||||
| 命令 | 系统类型 |
|
| 命令 | 系统类型 |
|
||||||
| --- | --- |
|
| ------------------ | -------- |
|
||||||
| `pnpm build:win` | Windows |
|
| `pnpm build:win` | Windows |
|
||||||
| `pnpm build:linux` | Linux |
|
| `pnpm build:linux` | Linux |
|
||||||
| `pnpm build:mac` | MacOS |
|
| `pnpm build:mac` | MacOS |
|
||||||
|
|
||||||
## 😘 鸣谢
|
## 😘 鸣谢
|
||||||
|
|
||||||
@@ -221,3 +251,176 @@
|
|||||||
4. **免责声明:** 根据 AGPL-3.0,本项目不提供任何明示或暗示的担保。请详细阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 以了解完整的免责声明内容
|
4. **免责声明:** 根据 AGPL-3.0,本项目不提供任何明示或暗示的担保。请详细阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 以了解完整的免责声明内容
|
||||||
5. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
|
5. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
|
||||||
6. **许可证链接:** 请阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 了解更多详情
|
6. **许可证链接:** 请阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 了解更多详情
|
||||||
|
|
||||||
|
## 📂 目录结构
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>查看目录结构详情</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> ChatGPT 写的,如有错误,请见谅
|
||||||
|
|
||||||
|
```dir
|
||||||
|
├── auto-imports.d.ts # 自动导入
|
||||||
|
├── components.d.ts # 自动导入
|
||||||
|
├── docker-compose.yml # Docker Compose
|
||||||
|
├── Dockerfile # Docker
|
||||||
|
├── electron # Electron
|
||||||
|
│ ├── main # Electron 主进程
|
||||||
|
│ │ ├── index.js # 主进程入口
|
||||||
|
│ │ ├── mainIpcMain.js # 主进程与渲染进程通信
|
||||||
|
│ │ ├── startMainServer.js # 启动主进程服务器
|
||||||
|
│ │ ├── startNcmServer.js # 启动网易云音乐服务
|
||||||
|
│ │ └── utils # 主进程工具函数
|
||||||
|
│ │ ├── checkUpdates.js # 检查更新
|
||||||
|
│ │ ├── createGlobalShortcut.js # 创建全局快捷键
|
||||||
|
│ │ ├── createSystemTray.js # 创建系统托盘
|
||||||
|
│ │ ├── getNeteaseMusicUrl.js # 解灰
|
||||||
|
│ │ ├── kwDES.js # DES加密算法
|
||||||
|
│ │ └── readDirAsync.js # 异步读取目录
|
||||||
|
│ └── preload # Electron 预加载脚本
|
||||||
|
│ └── index.js # 预加载脚本入口文件
|
||||||
|
├── electron-builder.yml # Electron Builder
|
||||||
|
├── electron.vite.config.js # Electron Vite
|
||||||
|
├── index.html # 主页面 HTML
|
||||||
|
├── LICENSE # 项目许可证
|
||||||
|
├── nginx.conf # Nginx 配置
|
||||||
|
├── src # 项目源代码
|
||||||
|
│ ├── api # API 相关
|
||||||
|
│ │ ├── ./..
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ ├── assets # 静态资源
|
||||||
|
│ │ ├── emoji.json # 表情数据
|
||||||
|
│ │ ├── icon.json # 图标数据
|
||||||
|
│ │ └── themeColor.json # 主题颜色数据
|
||||||
|
│ ├── components # 组件目录
|
||||||
|
│ │ ├── Cover # 封面相关组件目录
|
||||||
|
│ │ │ ├── CoverDropdown.vue # 封面下拉组件
|
||||||
|
│ │ │ ├── MainCover.vue # 主封面组件
|
||||||
|
│ │ │ ├── SpecialCoverCard.vue # 特殊封面卡片组件
|
||||||
|
│ │ │ └── SpecialCover.vue # 特殊封面组件
|
||||||
|
│ │ ├── Global # 全局组件目录
|
||||||
|
│ │ │ ├── MainLayout.vue # 主布局组件
|
||||||
|
│ │ │ ├── Menu.vue # 菜单组件
|
||||||
|
│ │ │ ├── Pagination.vue # 分页组件
|
||||||
|
│ │ │ ├── Playlist.vue # 歌单组件
|
||||||
|
│ │ │ ├── Provider.vue # 全局化配置组件
|
||||||
|
│ │ │ └── SvgIcon.vue # SVG 图标组件
|
||||||
|
│ │ ├── List # 列表组件目录
|
||||||
|
│ │ │ ├── CommentList.vue # 评论列表组件
|
||||||
|
│ │ │ ├── SongListDropdown.vue # 歌曲下拉组件
|
||||||
|
│ │ │ └── SongList.vue # 歌曲列表组件
|
||||||
|
│ │ ├── Modal # 弹窗相关组件目录
|
||||||
|
│ │ │ ├── AddPlaylist.vue # 添加歌单组件
|
||||||
|
│ │ │ ├── CloudSongMatch.vue # 云盘歌曲匹配组件
|
||||||
|
│ │ │ ├── CreatePlaylist.vue # 创建歌单组件
|
||||||
|
│ │ │ ├── DownloadSong.vue # 下载歌曲组件
|
||||||
|
│ │ │ ├── LoginPhone.vue # 手机登录组件
|
||||||
|
│ │ │ ├── LoginQRCode.vue # 二维码登录组件
|
||||||
|
│ │ │ ├── Login.vue # 登录组件
|
||||||
|
│ │ │ ├── PlaylistUpdate.vue # 歌单编辑组件
|
||||||
|
│ │ │ └── UpCloudSong.vue # 上传云盘歌曲组件
|
||||||
|
│ │ ├── Nav # 导航相关组件目录
|
||||||
|
│ │ │ ├── MainNav.vue # 主导航组件
|
||||||
|
│ │ │ └── UserData.vue # 用户数据组件
|
||||||
|
│ │ ├── Player # 播放器相关组件目录
|
||||||
|
│ │ │ ├── CountDown.vue # 倒计时组件
|
||||||
|
│ │ │ ├── FullPlayer.vue # 全屏播放器组件
|
||||||
|
│ │ │ ├── Lyric.vue # 歌词组件
|
||||||
|
│ │ │ ├── MainControl.vue # 主控制组件
|
||||||
|
│ │ │ ├── PlayerControl.vue # 播放器控制组件
|
||||||
|
│ │ │ ├── PlayerCover.vue # 播放器封面组件
|
||||||
|
│ │ │ └── PrivateFm.vue # 私人 FM 组件
|
||||||
|
│ │ ├── Search # 搜索相关组件
|
||||||
|
│ │ │ ├── SearchHot.vue # 热门搜索组件
|
||||||
|
│ │ │ ├── SearchInp.vue # 搜索输入组件
|
||||||
|
│ │ │ └── SearchSuggestions.vue # 搜索建议组件
|
||||||
|
│ │ └── WinDom # 窗口 DOM 相关组件
|
||||||
|
│ │ └── TitleBar.vue # 标题栏组件
|
||||||
|
│ ├── main.js # Vue 应用的入口文件
|
||||||
|
│ ├── router # Vue Router 相关文件夹
|
||||||
|
│ │ ├── index.js # Vue Router 入口文件
|
||||||
|
│ │ └── routes.js # 路由配置文件
|
||||||
|
│ ├── stores # Vuex Store 相关文件夹
|
||||||
|
│ │ ├── indexedDB.js # IndexedDB 数据库相关文件
|
||||||
|
│ │ ├── index.js # Vuex Store 入口文件
|
||||||
|
│ │ ├── musicData.js # 音乐数据相关文件
|
||||||
|
│ │ ├── siteData.js # 网站数据相关文件
|
||||||
|
│ │ ├── siteSettings.js # 网站设置相关文件
|
||||||
|
│ │ └── siteStatus.js # 网站状态相关文件
|
||||||
|
│ ├── style # 样式相关文件夹
|
||||||
|
│ │ ├── animate.scss # 动画样式文件
|
||||||
|
│ │ └── main.scss # 主样式文件
|
||||||
|
│ ├── utils # 工具函数文件夹
|
||||||
|
│ │ ├── auth.js # 认证相关函数
|
||||||
|
│ │ ├── base64.js # Base64编码解码相关函数
|
||||||
|
│ │ ├── color-utils.js # 颜色工具函数
|
||||||
|
│ │ ├── cover-color.js # 封面颜色相关函数
|
||||||
|
│ │ ├── debounce.js # 防抖函数
|
||||||
|
│ │ ├── formatData.js # 数据格式化函数
|
||||||
|
│ │ ├── formRules.js # 表单验证规则
|
||||||
|
│ │ ├── globalEvents.js # 全局事件处理函数
|
||||||
|
│ │ ├── globalShortcut.js # 全局快捷键相关函数
|
||||||
|
│ │ ├── helper.js # 辅助函数
|
||||||
|
│ │ ├── parseLyric.js # 解析歌词函数
|
||||||
|
│ │ ├── Player.js # 播放器控制相关函数
|
||||||
|
│ │ ├── request.js # 网络请求相关函数
|
||||||
|
│ │ ├── throttle.js # 节流函数
|
||||||
|
│ │ ├── timeTools.js # 时间工具函数
|
||||||
|
│ │ └── userSignIn.js # 用户登录相关函数
|
||||||
|
│ └── views # Vue组件文件夹
|
||||||
|
│ ├── Artist # 艺术家相关组件
|
||||||
|
│ │ ├── albums.vue # 艺术家专辑组件
|
||||||
|
│ │ ├── hot.vue # 艺术家热门组件
|
||||||
|
│ │ ├── index.vue # 艺术家主组件
|
||||||
|
│ │ ├── songs.vue # 艺术家歌曲组件
|
||||||
|
│ │ └── videos.vue # 艺术家视频组件
|
||||||
|
│ ├── Cloud.vue # 云盘组件
|
||||||
|
│ ├── Comment.vue # 评论组件
|
||||||
|
│ ├── DailySongs.vue # 每日推荐组件
|
||||||
|
│ ├── Discover # 发现音乐相关组件
|
||||||
|
│ │ ├── artists.vue # 发现音乐艺术家组件
|
||||||
|
│ │ ├── index.vue # 发现音乐主组件
|
||||||
|
│ │ ├── new.vue # 发现音乐新歌组件
|
||||||
|
│ │ ├── playlists.vue # 发现音乐歌单组件
|
||||||
|
│ │ └── toplists.vue # 发现音乐排行榜组件
|
||||||
|
│ ├── History.vue # 历史记录组件
|
||||||
|
│ ├── Home.vue # 主页组件
|
||||||
|
│ ├── Like # 我喜欢的相关组件
|
||||||
|
│ │ ├── albums.vue # 我喜欢的专辑组件
|
||||||
|
│ │ ├── artists.vue # 我喜欢的艺术家组件
|
||||||
|
│ │ ├── index.vue # 我喜欢的主组件
|
||||||
|
│ │ ├── playlists.vue # 我喜欢的歌单组件
|
||||||
|
│ │ └── videos.vue # 我喜欢的视频组件
|
||||||
|
│ ├── List # 列表相关组件
|
||||||
|
│ │ ├── album.vue # 专辑组件
|
||||||
|
│ │ └── playlist.vue # 歌单组件
|
||||||
|
│ │ └── dj.vue # 电台组件
|
||||||
|
│ ├── Local # 本地音乐相关组件
|
||||||
|
│ │ ├── albums.vue # 本地音乐专辑组件
|
||||||
|
│ │ ├── artists.vue # 本地音乐艺术家组件
|
||||||
|
│ │ ├── index.vue # 本地音乐主组件
|
||||||
|
│ │ └── songs.vue # 本地音乐歌曲组件
|
||||||
|
│ ├── Player.vue # 视频播放器组件
|
||||||
|
│ ├── Dj # 电台相关组件
|
||||||
|
│ │ └── index.vue # 电台主组件
|
||||||
|
│ │ └── type.vue # 电台分类组件
|
||||||
|
│ ├── Search # 搜索相关组件
|
||||||
|
│ │ ├── albums.vue # 搜索专辑组件
|
||||||
|
│ │ ├── artists.vue # 搜索艺术家组件
|
||||||
|
│ │ ├── index.vue # 搜索主组件
|
||||||
|
│ │ ├── playlists.vue # 搜索歌单组件
|
||||||
|
│ │ ├── songs.vue # 搜索歌曲组件
|
||||||
|
│ │ └── videos.vue # 搜索视频组件
|
||||||
|
│ │ └── djs.vue # 搜索电台组件
|
||||||
|
│ ├── Setting # 设置相关组件
|
||||||
|
│ │ └── index.vue # 设置主组件
|
||||||
|
│ ├── Song.vue
|
||||||
|
│ ├── State
|
||||||
|
│ │ ├── 403.vue
|
||||||
|
│ │ ├── 404.vue
|
||||||
|
│ │ └── 500.vue
|
||||||
|
│ └── Test.vue
|
||||||
|
└── vercel.json # Vercel 部署配置
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|||||||
5
components.d.ts
vendored
@@ -27,11 +27,11 @@ declare module 'vue' {
|
|||||||
NAlert: typeof import('naive-ui')['NAlert']
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NBackTop: typeof import('naive-ui')['NBackTop']
|
NBackTop: typeof import('naive-ui')['NBackTop']
|
||||||
|
NBadge: typeof import('naive-ui')['NBadge']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NCard: typeof import('naive-ui')['NCard']
|
NCard: typeof import('naive-ui')['NCard']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
|
||||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
NDivider: typeof import('naive-ui')['NDivider']
|
||||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||||
@@ -44,6 +44,7 @@ declare module 'vue' {
|
|||||||
NGi: typeof import('naive-ui')['NGi']
|
NGi: typeof import('naive-ui')['NGi']
|
||||||
NGlobalStyle: typeof import('naive-ui')['NGlobalStyle']
|
NGlobalStyle: typeof import('naive-ui')['NGlobalStyle']
|
||||||
NGrid: typeof import('naive-ui')['NGrid']
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
|
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NH4: typeof import('naive-ui')['NH4']
|
NH4: typeof import('naive-ui')['NH4']
|
||||||
@@ -67,6 +68,7 @@ declare module 'vue' {
|
|||||||
NPagination: typeof import('naive-ui')['NPagination']
|
NPagination: typeof import('naive-ui')['NPagination']
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
NPopover: typeof import('naive-ui')['NPopover']
|
||||||
NProgress: typeof import('naive-ui')['NProgress']
|
NProgress: typeof import('naive-ui')['NProgress']
|
||||||
|
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||||
NRadio: typeof import('naive-ui')['NRadio']
|
NRadio: typeof import('naive-ui')['NRadio']
|
||||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||||
NResult: typeof import('naive-ui')['NResult']
|
NResult: typeof import('naive-ui')['NResult']
|
||||||
@@ -83,6 +85,7 @@ declare module 'vue' {
|
|||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
NText: typeof import('naive-ui')['NText']
|
NText: typeof import('naive-ui')['NText']
|
||||||
NThing: typeof import('naive-ui')['NThing']
|
NThing: typeof import('naive-ui')['NThing']
|
||||||
|
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||||
Pagination: typeof import('./src/components/Global/Pagination.vue')['default']
|
Pagination: typeof import('./src/components/Global/Pagination.vue')['default']
|
||||||
PlayerControl: typeof import('./src/components/Player/PlayerControl.vue')['default']
|
PlayerControl: typeof import('./src/components/Player/PlayerControl.vue')['default']
|
||||||
PlayerCover: typeof import('./src/components/Player/PlayerCover.vue')['default']
|
PlayerCover: typeof import('./src/components/Player/PlayerCover.vue')['default']
|
||||||
|
|||||||
12
docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
SPlayer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
image: splayer
|
||||||
|
container_name: SPlayer
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
ports:
|
||||||
|
- 7899:7899
|
||||||
|
restart: always
|
||||||
@@ -19,13 +19,11 @@ asarUnpack:
|
|||||||
# Windows 平台配置
|
# Windows 平台配置
|
||||||
win:
|
win:
|
||||||
# 可执行文件名
|
# 可执行文件名
|
||||||
executableName: splayer
|
executableName: SPlayer
|
||||||
# 应用程序的图标文件路径
|
# 应用程序的图标文件路径
|
||||||
icon: public/images/logo/favicon_256.png
|
icon: public/images/icons/favicon-512x512.png
|
||||||
# 构建类型
|
# 构建类型
|
||||||
target: nsis
|
target: nsis
|
||||||
# 管理员权限
|
|
||||||
requestedExecutionLevel: highestAvailable
|
|
||||||
# NSIS 安装器配置
|
# NSIS 安装器配置
|
||||||
nsis:
|
nsis:
|
||||||
# 一键式安装程序还是辅助安装程序
|
# 一键式安装程序还是辅助安装程序
|
||||||
@@ -42,12 +40,16 @@ nsis:
|
|||||||
allowElevation: true
|
allowElevation: true
|
||||||
# 是否允许用户更改安装目录
|
# 是否允许用户更改安装目录
|
||||||
allowToChangeInstallationDirectory: true
|
allowToChangeInstallationDirectory: true
|
||||||
|
# 安装包图标
|
||||||
|
installerIcon: public/images/icons/favicon.ico
|
||||||
|
# 卸载命令图标
|
||||||
|
uninstallerIcon: public/images/icons/favicon.ico
|
||||||
# macOS 平台配置
|
# macOS 平台配置
|
||||||
mac:
|
mac:
|
||||||
# 可执行文件名
|
# 可执行文件名
|
||||||
executableName: splayer
|
executableName: SPlayer
|
||||||
# 应用程序的图标文件路径
|
# 应用程序的图标文件路径
|
||||||
icon: public/images/logo/favicon_512.png
|
icon: public/images/icons/favicon-512x512.png
|
||||||
# 权限继承的文件路径
|
# 权限继承的文件路径
|
||||||
entitlementsInherit: build/entitlements.mac.plist
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
# 扩展信息,如权限描述
|
# 扩展信息,如权限描述
|
||||||
@@ -67,9 +69,9 @@ dmg:
|
|||||||
# Linux 平台配置
|
# Linux 平台配置
|
||||||
linux:
|
linux:
|
||||||
# 可执行文件名
|
# 可执行文件名
|
||||||
executableName: splayer
|
executableName: SPlayer
|
||||||
# 应用程序的图标文件路径
|
# 应用程序的图标文件路径
|
||||||
icon: public/images/logo/favicon_256.png
|
icon: public/images/icons/favicon-512x512.png
|
||||||
# 构建类型
|
# 构建类型
|
||||||
target:
|
target:
|
||||||
- AppImage
|
- AppImage
|
||||||
|
|||||||
@@ -106,12 +106,22 @@ export default defineConfig(({ mode }) => {
|
|||||||
background_color: "#efefef",
|
background_color: "#efefef",
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: "/images/logo/favicon.png",
|
src: "/images/icons/favicon-32x32.png",
|
||||||
sizes: "200x200",
|
sizes: "32x32",
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "/images/logo/favicon_512.png",
|
src: "/images/icons/favicon-96x96.png",
|
||||||
|
sizes: "96x96",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/icons/favicon-256x256.png",
|
||||||
|
sizes: "256x256",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/icons/favicon-512x512.png",
|
||||||
sizes: "512x512",
|
sizes: "512x512",
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
},
|
},
|
||||||
@@ -147,12 +157,6 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
win: {
|
|
||||||
icon: resolve(__dirname, "/public/images/logo/favicon.png"),
|
|
||||||
},
|
|
||||||
linux: {
|
|
||||||
icon: resolve(__dirname, "/public/images/logo/favicon.png"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { app, protocol, shell, BrowserWindow, globalShortcut } from "electron";
|
import { app, protocol, shell, BrowserWindow, globalShortcut, nativeImage } from "electron";
|
||||||
import { platform, optimizer, is } from "@electron-toolkit/utils";
|
import { platform, optimizer, is } from "@electron-toolkit/utils";
|
||||||
import { startNcmServer } from "@main/startNcmServer";
|
import { startNcmServer } from "@main/startNcmServer";
|
||||||
import { startMainServer } from "@main/startMainServer";
|
import { startMainServer } from "@main/startMainServer";
|
||||||
import { configureAutoUpdater } from "@main/utils/checkUpdates";
|
import { configureAutoUpdater } from "@main/utils/checkUpdates";
|
||||||
import createSystemInfo from "@main/utils/createSystemInfo";
|
import createSystemTray from "@main/utils/createSystemTray";
|
||||||
import createGlobalShortcut from "@main/utils/createGlobalShortcut";
|
import createGlobalShortcut from "@main/utils/createGlobalShortcut";
|
||||||
import mainIpcMain from "@main/mainIpcMain";
|
import mainIpcMain from "@main/mainIpcMain";
|
||||||
import Store from "electron-store";
|
import Store from "electron-store";
|
||||||
@@ -15,7 +15,7 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
|
|||||||
|
|
||||||
// 配置 log
|
// 配置 log
|
||||||
log.transports.file.resolvePathFn = () =>
|
log.transports.file.resolvePathFn = () =>
|
||||||
join(app.getPath("documents"), "/SPlayer/splayer-log.txt");
|
join(app.getPath("documents"), "/SPlayer/SPlayer-log.txt");
|
||||||
// 设置日志文件的最大大小为 2 MB
|
// 设置日志文件的最大大小为 2 MB
|
||||||
log.transports.file.maxSize = 2 * 1024 * 1024;
|
log.transports.file.maxSize = 2 * 1024 * 1024;
|
||||||
// 绑定 console 事件
|
// 绑定 console 事件
|
||||||
@@ -93,7 +93,7 @@ class MainProcess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册应用协议
|
// 注册应用协议
|
||||||
app.setAsDefaultProtocolClient("splayer");
|
app.setAsDefaultProtocolClient("SPlayer");
|
||||||
// 应用程序准备好之前注册
|
// 应用程序准备好之前注册
|
||||||
protocol.registerSchemesAsPrivileged([
|
protocol.registerSchemesAsPrivileged([
|
||||||
{ scheme: "app", privileges: { secure: true, standard: true } },
|
{ scheme: "app", privileges: { secure: true, standard: true } },
|
||||||
@@ -107,6 +107,7 @@ class MainProcess {
|
|||||||
createWindow() {
|
createWindow() {
|
||||||
// 创建浏览器窗口
|
// 创建浏览器窗口
|
||||||
this.mainWindow = new BrowserWindow({
|
this.mainWindow = new BrowserWindow({
|
||||||
|
title: app.getName() || "SPlayer",
|
||||||
width: this.store.get("windowSize.width") || 1280, // 窗口宽度
|
width: this.store.get("windowSize.width") || 1280, // 窗口宽度
|
||||||
height: this.store.get("windowSize.height") || 740, // 窗口高度
|
height: this.store.get("windowSize.height") || 740, // 窗口高度
|
||||||
minHeight: 700, // 最小高度
|
minHeight: 700, // 最小高度
|
||||||
@@ -117,7 +118,7 @@ class MainProcess {
|
|||||||
titleBarStyle: "customButtonsOnHover", // Macos 隐藏菜单栏
|
titleBarStyle: "customButtonsOnHover", // Macos 隐藏菜单栏
|
||||||
autoHideMenuBar: true, // 失去焦点后自动隐藏菜单栏
|
autoHideMenuBar: true, // 失去焦点后自动隐藏菜单栏
|
||||||
// 图标配置
|
// 图标配置
|
||||||
icon: join(__dirname, "../../public/images/logo/favicon.png"),
|
icon: nativeImage.createFromPath(join(__dirname, "../../public/images/icons/favicon.png")),
|
||||||
// 预加载
|
// 预加载
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
// devTools: is.dev, //是否开启 DevTools
|
// devTools: is.dev, //是否开启 DevTools
|
||||||
@@ -125,6 +126,7 @@ class MainProcess {
|
|||||||
sandbox: false,
|
sandbox: false,
|
||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
hardwareAcceleration: true,
|
hardwareAcceleration: true,
|
||||||
|
nodeIntegration: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,8 +135,6 @@ class MainProcess {
|
|||||||
this.mainWindow.show();
|
this.mainWindow.show();
|
||||||
// mainWindow.maximize();
|
// mainWindow.maximize();
|
||||||
this.store.set("windowSize", this.mainWindow.getBounds());
|
this.store.set("windowSize", this.mainWindow.getBounds());
|
||||||
// 创建系统信息
|
|
||||||
createSystemInfo(this.mainWindow);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 主窗口事件
|
// 主窗口事件
|
||||||
@@ -166,6 +166,8 @@ class MainProcess {
|
|||||||
configureAutoUpdater();
|
configureAutoUpdater();
|
||||||
// 引入主 Ipc
|
// 引入主 Ipc
|
||||||
mainIpcMain(this.mainWindow);
|
mainIpcMain(this.mainWindow);
|
||||||
|
// 系统托盘
|
||||||
|
createSystemTray(this.mainWindow);
|
||||||
// 注册快捷键
|
// 注册快捷键
|
||||||
createGlobalShortcut(this.mainWindow);
|
createGlobalShortcut(this.mainWindow);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,55 +1,58 @@
|
|||||||
import { join } from "path";
|
|
||||||
import { platform } from "@electron-toolkit/utils";
|
|
||||||
import { Tray, Menu, app, ipcMain, nativeImage, nativeTheme } from "electron";
|
import { Tray, Menu, app, ipcMain, nativeImage, nativeTheme } from "electron";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
// 当前播放歌曲数据
|
// 当前歌曲数据
|
||||||
let playSongName = "当前暂无播放歌曲";
|
let playSongName = "当前暂无播放歌曲";
|
||||||
let playSongState = false;
|
let playSongState = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建系统自定义信息
|
* 创建系统托盘
|
||||||
* @param {BrowserWindow} win - 程序窗口
|
* @param {BrowserWindow} win - 程序窗口
|
||||||
*/
|
*/
|
||||||
const createSystemInfo = (win) => {
|
const createSystemTray = (win) => {
|
||||||
// 弹出列表
|
|
||||||
app.setUserTasks([]);
|
|
||||||
// 系统托盘
|
// 系统托盘
|
||||||
const mainTray = new Tray(join(__dirname, "../../public/images/logo/favicon.png"));
|
const mainTray = new Tray(
|
||||||
// 默认托盘菜单
|
nativeImage
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(createTrayMenu(win)));
|
.createFromPath(
|
||||||
// 给托盘图标设置气球提示
|
join(
|
||||||
|
__dirname,
|
||||||
|
process.platform === "win32"
|
||||||
|
? "../../public/images/icons/favicon.ico"
|
||||||
|
: "../../public/images/icons/favicon-32x32.png",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.resize({
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// 应用内菜单
|
||||||
|
Menu.setApplicationMenu(createTrayMenu(win));
|
||||||
|
// 默认名称
|
||||||
|
win.setTitle(app.getName());
|
||||||
|
mainTray.setTitle(app.getName());
|
||||||
mainTray.setToolTip(app.getName());
|
mainTray.setToolTip(app.getName());
|
||||||
// 自定义任务栏缩略图
|
// 左键事件
|
||||||
createThumbar(win);
|
mainTray.on("click", () => win.show());
|
||||||
// 歌曲数据改变时
|
// 托盘菜单
|
||||||
|
mainTray.setContextMenu(createTrayMenu(win));
|
||||||
|
// 系统主题改变
|
||||||
|
nativeTheme.on("updated", () => {
|
||||||
|
mainTray.setContextMenu(createTrayMenu(win));
|
||||||
|
});
|
||||||
|
// 播放歌曲改变
|
||||||
ipcMain.on("songNameChange", (_, val) => {
|
ipcMain.on("songNameChange", (_, val) => {
|
||||||
playSongName = val;
|
playSongName = val;
|
||||||
// 托盘图标标题
|
|
||||||
mainTray.setToolTip(val);
|
|
||||||
// 更改应用标题
|
|
||||||
win.setTitle(val);
|
win.setTitle(val);
|
||||||
|
mainTray.setTitle(val);
|
||||||
|
mainTray.setToolTip(val);
|
||||||
|
mainTray.setContextMenu(createTrayMenu(win));
|
||||||
});
|
});
|
||||||
|
// 播放状态改变
|
||||||
ipcMain.on("songStateChange", (_, val) => {
|
ipcMain.on("songStateChange", (_, val) => {
|
||||||
playSongState = val;
|
playSongState = val;
|
||||||
createThumbar(win);
|
mainTray.setContextMenu(createTrayMenu(win));
|
||||||
});
|
});
|
||||||
// 监听系统主题改变
|
|
||||||
nativeTheme.on("updated", () => {
|
|
||||||
createThumbar(win);
|
|
||||||
});
|
|
||||||
// 左键事件
|
|
||||||
mainTray.on("click", () => {
|
|
||||||
// 显示窗口
|
|
||||||
win.show();
|
|
||||||
});
|
|
||||||
// 右键事件
|
|
||||||
mainTray.on("right-click", () => {
|
|
||||||
mainTray.popUpContextMenu(Menu.buildFromTemplate(createTrayMenu(win)));
|
|
||||||
});
|
|
||||||
// linux 右键菜单
|
|
||||||
if (platform.isLinux) {
|
|
||||||
mainTray.setContextMenu(Menu.buildFromTemplate(createTrayMenu(win)));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成图标
|
// 生成图标
|
||||||
@@ -60,8 +63,8 @@ const createIcon = (name) => {
|
|||||||
return nativeImage
|
return nativeImage
|
||||||
.createFromPath(
|
.createFromPath(
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? join(__dirname, `../../public/images/icon/${name}-dark.png`)
|
? join(__dirname, `../../public/images/icons/${name}-dark.png`)
|
||||||
: join(__dirname, `../../public/images/icon/${name}-light.png`),
|
: join(__dirname, `../../public/images/icons/${name}-light.png`),
|
||||||
)
|
)
|
||||||
.resize({ width: 16, height: 16 });
|
.resize({ width: 16, height: 16 });
|
||||||
};
|
};
|
||||||
@@ -69,7 +72,7 @@ const createIcon = (name) => {
|
|||||||
// 生成右键菜单
|
// 生成右键菜单
|
||||||
const createTrayMenu = (win) => {
|
const createTrayMenu = (win) => {
|
||||||
// 返回菜单
|
// 返回菜单
|
||||||
return [
|
return Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
label: playSongName,
|
label: playSongName,
|
||||||
icon: createIcon("open"),
|
icon: createIcon("open"),
|
||||||
@@ -113,7 +116,9 @@ const createTrayMenu = (win) => {
|
|||||||
label: "全局设置",
|
label: "全局设置",
|
||||||
icon: createIcon("setting"),
|
icon: createIcon("setting"),
|
||||||
click: () => {
|
click: () => {
|
||||||
win.webContents.send("setting");
|
win.show();
|
||||||
|
win.focus();
|
||||||
|
win.webContents.send("open-setting");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -128,35 +133,7 @@ const createTrayMenu = (win) => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// 自定义任务栏缩略图 - Win
|
|
||||||
const createThumbar = (win) => {
|
|
||||||
win.setThumbarButtons([]);
|
|
||||||
win.setThumbarButtons([
|
|
||||||
{
|
|
||||||
tooltip: "上一曲",
|
|
||||||
icon: createIcon("prev"),
|
|
||||||
click: () => {
|
|
||||||
win.webContents.send("playNextOrPrev", "prev");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: playSongState ? "暂停" : "播放",
|
|
||||||
icon: createIcon(playSongState ? "pause" : "play"),
|
|
||||||
click() {
|
|
||||||
win.webContents.send("playOrPause");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: "下一曲",
|
|
||||||
icon: createIcon("next"),
|
|
||||||
click: () => {
|
|
||||||
win.webContents.send("playNextOrPrev", "next");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createSystemInfo;
|
export default createSystemTray;
|
||||||
@@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="%RENDERER_VITE_SITE_LOGO%" />
|
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png">
|
||||||
<link rel="apple-touch-icon" href="%RENDERER_VITE_SITE_APPLE_LOGO%" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png">
|
||||||
<link rel="bookmark" href="%RENDERER_VITE_SITE_APPLE_LOGO%" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png">
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="200x200" href="%RENDERER_VITE_SITE_APPLE_LOGO%" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>%RENDERER_VITE_SITE_TITLE%</title>
|
<title>%RENDERER_VITE_SITE_TITLE%</title>
|
||||||
<meta name="apple-mobile-web-app-title" content="%RENDERER_VITE_SITE_TITLE%" />
|
<meta name="apple-mobile-web-app-title" content="%RENDERER_VITE_SITE_TITLE%" />
|
||||||
<meta name="author" content="%RENDERER_VITE_SITE_ANTHOR%" />
|
<meta name="author" content="%RENDERER_VITE_SITE_ANTHOR%" />
|
||||||
<meta name="keywords" content="%RENDERER_VITE_SITE_KEYWORDS%" />
|
<meta name="keywords" content="%RENDERER_VITE_SITE_KEYWORDS%" />
|
||||||
<meta name="description" content="%RENDERER_VITE_SITE_DES%" />
|
<meta name="description" content="%RENDERER_VITE_SITE_DES%" />
|
||||||
|
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
28
nginx.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
server {
|
||||||
|
gzip on;
|
||||||
|
listen 7899;
|
||||||
|
listen [::]:7899;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @rewrites {
|
||||||
|
rewrite ^(.*)$ /index.html last;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_buffers 16 32k;
|
||||||
|
proxy_buffer_size 128k;
|
||||||
|
proxy_busy_buffers_size 128k;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Host $remote_addr;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
proxy_pass http://localhost:3000/;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "splayer",
|
"name": "splayer",
|
||||||
"version": "2.0.0-beta.4",
|
"version": "2.0.0-beta.5",
|
||||||
"description": "A minimalist music player",
|
"description": "A minimalist music player",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "imsyy",
|
"author": "imsyy",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
|
||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "chcp 65001 && electron-vite dev --watch",
|
"dev": "electron-vite dev --watch",
|
||||||
"build": "electron-vite build",
|
"build": "electron-vite build",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"build:win": "npm run build && electron-builder --win --config",
|
"build:win": "npm run build && electron-builder --win --config",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"@electron-toolkit/preload": "^2.0.0",
|
"@electron-toolkit/preload": "^2.0.0",
|
||||||
"@electron-toolkit/utils": "^2.0.0",
|
"@electron-toolkit/utils": "^2.0.0",
|
||||||
"@material/material-color-utilities": "^0.2.7",
|
"@material/material-color-utilities": "^0.2.7",
|
||||||
"NeteaseCloudMusicApi": "git+https://github.com/imsyy/NeteaseCloudMusicApi.git",
|
"NeteaseCloudMusicApi": "^4.14.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"colorthief": "^2.4.0",
|
"colorthief": "^2.4.0",
|
||||||
"electron-dl": "^3.5.1",
|
"electron-dl": "^3.5.1",
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"pinia-plugin-persistedstate": "^3.2.0",
|
"pinia-plugin-persistedstate": "^3.2.0",
|
||||||
"plyr": "^3.7.8",
|
"plyr": "^3.7.8",
|
||||||
"qrcode.vue": "^3.4.1",
|
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.2.4",
|
||||||
"vue-slider-component": "4.1.0-beta.7"
|
"vue-slider-component": "4.1.0-beta.7"
|
||||||
@@ -59,7 +58,7 @@
|
|||||||
"electron-vite": "^1.0.29",
|
"electron-vite": "^1.0.29",
|
||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"naive-ui": "^2.35.0",
|
"naive-ui": "^2.36.0",
|
||||||
"prettier": "^3.0.2",
|
"prettier": "^3.0.2",
|
||||||
"sass": "^1.66.1",
|
"sass": "^1.66.1",
|
||||||
"terser": "^5.19.2",
|
"terser": "^5.19.2",
|
||||||
|
|||||||
87
pnpm-lock.yaml
generated
@@ -15,8 +15,8 @@ dependencies:
|
|||||||
specifier: ^0.2.7
|
specifier: ^0.2.7
|
||||||
version: 0.2.7
|
version: 0.2.7
|
||||||
NeteaseCloudMusicApi:
|
NeteaseCloudMusicApi:
|
||||||
specifier: git+https://github.com/imsyy/NeteaseCloudMusicApi.git
|
specifier: ^4.14.0
|
||||||
version: github.com/imsyy/NeteaseCloudMusicApi/fa5c733895bfe6cbf55380c2d7072f367bf6a09b
|
version: 4.14.0
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
@@ -62,9 +62,6 @@ dependencies:
|
|||||||
plyr:
|
plyr:
|
||||||
specifier: ^3.7.8
|
specifier: ^3.7.8
|
||||||
version: 3.7.8
|
version: 3.7.8
|
||||||
qrcode.vue:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1(vue@3.3.4)
|
|
||||||
screenfull:
|
screenfull:
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2
|
version: 6.0.2
|
||||||
@@ -110,8 +107,8 @@ devDependencies:
|
|||||||
specifier: ^9.17.0
|
specifier: ^9.17.0
|
||||||
version: 9.17.0(eslint@8.47.0)
|
version: 9.17.0(eslint@8.47.0)
|
||||||
naive-ui:
|
naive-ui:
|
||||||
specifier: ^2.35.0
|
specifier: ^2.36.0
|
||||||
version: 2.35.0(vue@3.3.4)
|
version: 2.36.0(vue@3.3.4)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
@@ -2240,6 +2237,29 @@ packages:
|
|||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/NeteaseCloudMusicApi@4.14.0:
|
||||||
|
resolution: {integrity: sha512-NUnojWdggaybe9fzI5xUkvEAIWw3p4PXpf0fbcqw1tvpsSjGY+RsCcBb6GA2VuEnVbiMLxN7WBAKxvivi/6mqQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
axios: 1.4.0
|
||||||
|
crypto-js: 4.2.0
|
||||||
|
express: 4.18.2
|
||||||
|
express-fileupload: 1.4.0
|
||||||
|
md5: 2.3.0
|
||||||
|
music-metadata: 7.13.4
|
||||||
|
node-forge: 1.3.1
|
||||||
|
pac-proxy-agent: 7.0.1
|
||||||
|
qrcode: 1.5.3
|
||||||
|
safe-decode-uri-component: 1.2.1
|
||||||
|
tunnel: 0.0.6
|
||||||
|
xml2js: 0.6.2
|
||||||
|
yargs: 17.7.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/accepts@1.3.8:
|
/accepts@1.3.8:
|
||||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -5180,8 +5200,8 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/naive-ui@2.35.0(vue@3.3.4):
|
/naive-ui@2.36.0(vue@3.3.4):
|
||||||
resolution: {integrity: sha512-PdnLpOip1LQaKs5+rXLZoPDPQkTq26TnHWeABvUA2eOQjtHxE4+TQvj0Jq/W8clM2On/7jptoGmenLt48G3Bhg==}
|
resolution: {integrity: sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5198,12 +5218,12 @@ packages:
|
|||||||
highlight.js: 11.8.0
|
highlight.js: 11.8.0
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
seemly: 0.3.6
|
seemly: 0.3.8
|
||||||
treemate: 0.3.11
|
treemate: 0.3.11
|
||||||
vdirs: 0.1.8(vue@3.3.4)
|
vdirs: 0.1.8(vue@3.3.4)
|
||||||
vooks: 0.2.12(vue@3.3.4)
|
vooks: 0.2.12(vue@3.3.4)
|
||||||
vue: 3.3.4
|
vue: 3.3.4
|
||||||
vueuc: 0.4.51(vue@3.3.4)
|
vueuc: 0.4.54(vue@3.3.4)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/nanoid@3.3.6:
|
/nanoid@3.3.6:
|
||||||
@@ -5676,14 +5696,6 @@ packages:
|
|||||||
escape-goat: 2.1.1
|
escape-goat: 2.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/qrcode.vue@3.4.1(vue@3.3.4):
|
|
||||||
resolution: {integrity: sha512-wq/zHsifH4FJ1GXQi8/wNxD1KfQkckIpjK1KPTc/qwYU5/Bkd4me0w4xZSg6EXk6xLBkVDE0zxVagewv5EMAVA==}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.0.0
|
|
||||||
dependencies:
|
|
||||||
vue: 3.3.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/qrcode@1.5.3:
|
/qrcode@1.5.3:
|
||||||
resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
|
resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -6013,8 +6025,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==}
|
resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/seemly@0.3.6:
|
/seemly@0.3.8:
|
||||||
resolution: {integrity: sha512-lEV5VB8BUKTo/AfktXJcy+JeXns26ylbMkIUco8CYREsQijuz4mrXres2Q+vMLdwkuLxJdIPQ8IlCIxLYm71Yw==}
|
resolution: {integrity: sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/semver-compare@1.0.0:
|
/semver-compare@1.0.0:
|
||||||
@@ -6980,8 +6992,8 @@ packages:
|
|||||||
'@vue/server-renderer': 3.3.4(vue@3.3.4)
|
'@vue/server-renderer': 3.3.4(vue@3.3.4)
|
||||||
'@vue/shared': 3.3.4
|
'@vue/shared': 3.3.4
|
||||||
|
|
||||||
/vueuc@0.4.51(vue@3.3.4):
|
/vueuc@0.4.54(vue@3.3.4):
|
||||||
resolution: {integrity: sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==}
|
resolution: {integrity: sha512-2LED7h1BSnCRPBI6AlSIf+1Yte1shN+Vb2gpspO5wHI7zWzbcq4bAu2f9nFh5yXIUKdzqmLvzRsOXDl4TrDyCw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.0.11
|
vue: ^3.0.11
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6989,7 +7001,7 @@ packages:
|
|||||||
'@juggle/resize-observer': 3.4.0
|
'@juggle/resize-observer': 3.4.0
|
||||||
css-render: 0.15.12
|
css-render: 0.15.12
|
||||||
evtd: 0.2.4
|
evtd: 0.2.4
|
||||||
seemly: 0.3.6
|
seemly: 0.3.8
|
||||||
vdirs: 0.1.8(vue@3.3.4)
|
vdirs: 0.1.8(vue@3.3.4)
|
||||||
vooks: 0.2.12(vue@3.3.4)
|
vooks: 0.2.12(vue@3.3.4)
|
||||||
vue: 3.3.4
|
vue: 3.3.4
|
||||||
@@ -7305,30 +7317,3 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
github.com/imsyy/NeteaseCloudMusicApi/fa5c733895bfe6cbf55380c2d7072f367bf6a09b:
|
|
||||||
resolution: {tarball: https://codeload.github.com/imsyy/NeteaseCloudMusicApi/tar.gz/fa5c733895bfe6cbf55380c2d7072f367bf6a09b}
|
|
||||||
name: NeteaseCloudMusicApi
|
|
||||||
version: 4.13.8
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
hasBin: true
|
|
||||||
prepare: true
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
axios: 1.4.0
|
|
||||||
crypto-js: 4.2.0
|
|
||||||
express: 4.18.2
|
|
||||||
express-fileupload: 1.4.0
|
|
||||||
md5: 2.3.0
|
|
||||||
music-metadata: 7.13.4
|
|
||||||
node-forge: 1.3.1
|
|
||||||
pac-proxy-agent: 7.0.1
|
|
||||||
qrcode: 1.5.3
|
|
||||||
safe-decode-uri-component: 1.2.1
|
|
||||||
tunnel: 0.0.6
|
|
||||||
xml2js: 0.6.2
|
|
||||||
yargs: 17.7.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|||||||
BIN
public/images/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/images/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/icons/favicon-192x192.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
public/images/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/images/icons/favicon-512x512.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/images/icons/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
public/images/icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
25
public/images/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M2400 4444 c-14 -2 -63 -8 -110 -14 -407 -50 -737 -184 -1077 -436
|
||||||
|
-124 -93 -348 -309 -406 -394 -100 -144 -190 -286 -223 -352 -101 -200 -199
|
||||||
|
-506 -226 -707 -17 -127 -22 -436 -8 -546 40 -338 161 -687 327 -942 138 -213
|
||||||
|
226 -319 372 -450 237 -211 471 -353 761 -460 147 -54 156 -57 330 -88 383
|
||||||
|
-71 516 -67 965 25 76 16 288 91 370 132 28 13 66 31 85 38 39 16 158 88 270
|
||||||
|
164 242 164 474 410 628 668 254 422 362 920 308 1408 -16 139 -23 172 -73
|
||||||
|
344 -80 273 -188 496 -346 711 -104 143 -302 348 -404 421 l-48 34 -192 0
|
||||||
|
c-184 0 -193 -1 -203 -21 -8 -14 -11 -271 -10 -882 1 -474 -1 -896 -5 -937
|
||||||
|
-29 -306 -228 -596 -507 -737 -207 -104 -426 -126 -663 -66 -168 43 -300 125
|
||||||
|
-453 283 -118 122 -203 310 -223 495 -10 86 -3 271 11 320 41 135 58 180 95
|
||||||
|
248 103 193 330 376 545 441 157 47 364 49 513 5 38 -11 78 -19 90 -17 l22 3
|
||||||
|
-3 635 c-2 349 -7 638 -10 642 -22 22 -413 47 -502 32z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 55 KiB |
35
src/App.vue
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Provider>
|
<Provider>
|
||||||
<!-- 主框架 -->
|
<!-- 主框架 -->
|
||||||
<n-layout class="all-layout">
|
<n-layout :class="['all-layout', { 'full-player': showFullPlayer }]">
|
||||||
<!-- 导航栏 -->
|
<!-- 导航栏 -->
|
||||||
<n-layout-header bordered>
|
<n-layout-header bordered>
|
||||||
<MainNav />
|
<MainNav />
|
||||||
@@ -79,11 +79,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { darkTheme, NButton } from "naive-ui";
|
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { darkTheme, NButton } from "naive-ui";
|
||||||
import { musicData, siteStatus, siteSettings } from "@/stores";
|
import { musicData, siteStatus, siteSettings } from "@/stores";
|
||||||
import { initPlayer } from "@/utils/Player";
|
|
||||||
import { checkPlatform } from "@/utils/helper";
|
import { checkPlatform } from "@/utils/helper";
|
||||||
|
import { initPlayer } from "@/utils/Player";
|
||||||
|
import userSignIn from "@/utils/userSignIn";
|
||||||
import globalShortcut from "@/utils/globalShortcut";
|
import globalShortcut from "@/utils/globalShortcut";
|
||||||
import globalEvents from "@/utils/globalEvents";
|
import globalEvents from "@/utils/globalEvents";
|
||||||
import packageJson from "@/../package.json";
|
import packageJson from "@/../package.json";
|
||||||
@@ -92,7 +93,7 @@ const router = useRouter();
|
|||||||
const music = musicData();
|
const music = musicData();
|
||||||
const status = siteStatus();
|
const status = siteStatus();
|
||||||
const settings = siteSettings();
|
const settings = siteSettings();
|
||||||
const { autoPlay, showSider } = storeToRefs(settings);
|
const { autoPlay, showSider, autoSignIn } = storeToRefs(settings);
|
||||||
const { showPlayBar, asideMenuCollapsed, showFullPlayer } = storeToRefs(status);
|
const { showPlayBar, asideMenuCollapsed, showFullPlayer } = storeToRefs(status);
|
||||||
|
|
||||||
// 公告数据
|
// 公告数据
|
||||||
@@ -111,9 +112,9 @@ if ("serviceWorker" in navigator) {
|
|||||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||||
if (checkPlatform.electron()) {
|
if (checkPlatform.electron()) {
|
||||||
$notification.create({
|
$notification.create({
|
||||||
title: "🎉 更新提醒",
|
title: "🎉 有更新啦",
|
||||||
content: "检测到软件有更新,是否重新启动软件以应用更新?",
|
content: "检测到软件内资源有更新,是否重新启动软件以应用更新?",
|
||||||
meta: "v " + (packageJson.version || "1.0.0"),
|
meta: "当前版本 v " + (packageJson.version || "1.0.0"),
|
||||||
action: () =>
|
action: () =>
|
||||||
h(
|
h(
|
||||||
NButton,
|
NButton,
|
||||||
@@ -129,14 +130,14 @@ if ("serviceWorker" in navigator) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
onAfterLeave: () => {
|
onAfterLeave: () => {
|
||||||
$message.info("已取消本次更新,将在下次启动软件后生效", {
|
$message.info("已取消本次更新,更新将在下次启动软件后生效", {
|
||||||
duration: 6000,
|
duration: 6000,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.info("站点已更新,刷新后生效");
|
console.info("站点资源有更新,请刷新以应用更新");
|
||||||
$message.info("站点已更新,刷新后生效", {
|
$message.info("站点资源有更新,请刷新以应用更新", {
|
||||||
closable: true,
|
closable: true,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
@@ -178,17 +179,19 @@ const handleKeyUp = (event) => {
|
|||||||
globalShortcut(event, router);
|
globalShortcut(event, router);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
// 挂载方法
|
// 挂载方法
|
||||||
window.$canNotConnect = canNotConnect;
|
window.$canNotConnect = canNotConnect;
|
||||||
// 主播放器
|
// 主播放器
|
||||||
initPlayer(autoPlay.value);
|
await initPlayer(autoPlay.value);
|
||||||
// 全局事件
|
// 全局事件
|
||||||
globalEvents(router);
|
globalEvents(router);
|
||||||
// 键盘监听
|
// 键盘监听
|
||||||
if (!checkPlatform.electron()) {
|
if (!checkPlatform.electron()) {
|
||||||
window.addEventListener("keyup", handleKeyUp);
|
window.addEventListener("keyup", handleKeyUp);
|
||||||
}
|
}
|
||||||
|
// 自动签到
|
||||||
|
if (autoSignIn.value) await userSignIn();
|
||||||
// 显示公告
|
// 显示公告
|
||||||
showAnnouncements();
|
showAnnouncements();
|
||||||
});
|
});
|
||||||
@@ -201,7 +204,9 @@ onUnmounted(() => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.all-layout {
|
.all-layout {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: transform 0.3s;
|
transition:
|
||||||
|
transform 0.3s,
|
||||||
|
opacity 0.3s;
|
||||||
.n-layout-header {
|
.n-layout-header {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -227,5 +232,9 @@ onUnmounted(() => {
|
|||||||
bottom: 80px;
|
bottom: 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.full-player {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
136
src/api/dj.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import axios from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台部分
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电台 - 分类
|
||||||
|
*/
|
||||||
|
export const getDjCatelist = () => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/catelist",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电台 - 推荐
|
||||||
|
*/
|
||||||
|
export const getDjRecommend = () => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/recommend",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台个性推荐
|
||||||
|
*/
|
||||||
|
export const getDjPersonalRec = () => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/personalize/recommend",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电台 - 推荐类型
|
||||||
|
*/
|
||||||
|
export const getDjCategoryRec = () => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/category/recommend",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私人 DJ
|
||||||
|
*/
|
||||||
|
export const getPrivateDj = () => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/aidj/content/rcmd",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台 - 类别热门电台
|
||||||
|
* @param {string} cateId - 类别 id
|
||||||
|
* @param {number} [limit=50] - 返回数量,默认 50
|
||||||
|
* @param {number} [offset=0] - 偏移数量,默认 0
|
||||||
|
*/
|
||||||
|
export const getRadioHot = (cateId, limit = 50, offset = 0) => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/radio/hot",
|
||||||
|
params: {
|
||||||
|
cateId,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台 - 分类推荐
|
||||||
|
* @param {string} type - 类别 id
|
||||||
|
*/
|
||||||
|
export const getRecType = (type) => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/recommend/type",
|
||||||
|
params: {
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台 - 详情
|
||||||
|
* @param {string} rid - 电台 的 id
|
||||||
|
*/
|
||||||
|
export const getDjDetail = (rid) => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/detail",
|
||||||
|
params: {
|
||||||
|
rid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台 - 节目
|
||||||
|
* @param {string} rid - 电台 的 id
|
||||||
|
* @param {number} [limit=50] - 返回数量,默认 50
|
||||||
|
* @param {number} [offset=0] - 偏移数量,默认 0
|
||||||
|
*/
|
||||||
|
export const getDjProgram = (rid, limit = 50, offset = 0) => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/program",
|
||||||
|
params: {
|
||||||
|
rid,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电台 - 订阅
|
||||||
|
* @param {number} rid - 电台 的 id
|
||||||
|
* @param {number} t - 操作类型,1为收藏,0为取消收藏
|
||||||
|
*/
|
||||||
|
export const likeDj = (rid, t) => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/sub",
|
||||||
|
params: {
|
||||||
|
rid,
|
||||||
|
t,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -119,6 +119,19 @@ export const getUserMv = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户电台的订阅列表
|
||||||
|
*/
|
||||||
|
export const getUserDj = () => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/dj/sublist",
|
||||||
|
params: {
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户喜欢的音乐列表
|
* 获取用户喜欢的音乐列表
|
||||||
* @param {string} uid 用户的id
|
* @param {string} uid 用户的id
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"album": "M12 11a1 1 0 0 0-1 1a1 1 0 0 0 1 1a1 1 0 0 0 1-1a1 1 0 0 0-1-1m0 5.5c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5s4.5 2 4.5 4.5s-2 4.5-4.5 4.5M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z",
|
"album": "M12 11a1 1 0 0 0-1 1a1 1 0 0 0 1 1a1 1 0 0 0 1-1a1 1 0 0 0-1-1m0 5.5c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5s4.5 2 4.5 4.5s-2 4.5-4.5 4.5M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z",
|
||||||
"chevron-up": "M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6l1.41 1.41Z",
|
"chevron-up": "M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6l1.41 1.41Z",
|
||||||
|
"chevron-down": "M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z",
|
||||||
"play": "M9.525 18.025q-.5.325-1.012.038T8 17.175V6.825q0-.6.513-.888t1.012.038l8.15 5.175q.45.3.45.85t-.45.85l-8.15 5.175Z",
|
"play": "M9.525 18.025q-.5.325-1.012.038T8 17.175V6.825q0-.6.513-.888t1.012.038l8.15 5.175q.45.3.45.85t-.45.85l-8.15 5.175Z",
|
||||||
"play-circle": "M10 16.5v-9l6 4.5M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z",
|
"play-circle": "M10 16.5v-9l6 4.5M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z",
|
||||||
"play-next": "m10 16.5l6-4.5l-6-4.5M22 12c0-5.54-4.46-10-10-10c-1.17 0-2.3.19-3.38.56l.7 1.94c.85-.34 1.74-.53 2.68-.53c4.41 0 8.03 3.62 8.03 8.03c0 4.41-3.62 8.03-8.03 8.03c-4.41 0-8.03-3.62-8.03-8.03c0-.94.19-1.88.53-2.72l-1.94-.66C2.19 9.7 2 10.83 2 12c0 5.54 4.46 10 10 10s10-4.46 10-10M5.47 3.97c.85 0 1.53.71 1.53 1.5C7 6.32 6.32 7 5.47 7c-.79 0-1.5-.68-1.5-1.53c0-.79.71-1.5 1.5-1.5Z",
|
"play-next": "m10 16.5l6-4.5l-6-4.5M22 12c0-5.54-4.46-10-10-10c-1.17 0-2.3.19-3.38.56l.7 1.94c.85-.34 1.74-.53 2.68-.53c4.41 0 8.03 3.62 8.03 8.03c0 4.41-3.62 8.03-8.03 8.03c-4.41 0-8.03-3.62-8.03-8.03c0-.94.19-1.88.53-2.72l-1.94-.66C2.19 9.7 2 10.83 2 12c0 5.54 4.46 10 10 10s10-4.46 10-10M5.47 3.97c.85 0 1.53.71 1.53 1.5C7 6.32 6.32 7 5.47 7c-.79 0-1.5-.68-1.5-1.53c0-.79.71-1.5 1.5-1.5Z",
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
"favorite-rounded": "M12 20.325q-.35 0-.713-.125t-.637-.4l-1.725-1.575q-2.65-2.425-4.788-4.813T2 8.15Q2 5.8 3.575 4.225T7.5 2.65q1.325 0 2.5.562t2 1.538q.825-.975 2-1.538t2.5-.562q2.35 0 3.925 1.575T22 8.15q0 2.875-2.125 5.275T15.05 18.25l-1.7 1.55q-.275.275-.637.4t-.713.125Z",
|
"favorite-rounded": "M12 20.325q-.35 0-.713-.125t-.637-.4l-1.725-1.575q-2.65-2.425-4.788-4.813T2 8.15Q2 5.8 3.575 4.225T7.5 2.65q1.325 0 2.5.562t2 1.538q.825-.975 2-1.538t2.5-.562q2.35 0 3.925 1.575T22 8.15q0 2.875-2.125 5.275T15.05 18.25l-1.7 1.55q-.275.275-.637.4t-.713.125Z",
|
||||||
"history": "M12 21q-3.15 0-5.575-1.913T3.275 14.2q-.1-.375.15-.687t.675-.363q.4-.05.725.15t.45.6q.6 2.25 2.475 3.675T12 19q2.925 0 4.963-2.038T19 12q0-2.925-2.038-4.963T12 5q-1.725 0-3.225.8T6.25 8H8q.425 0 .713.288T9 9q0 .425-.288.713T8 10H4q-.425 0-.713-.288T3 9V5q0-.425.288-.713T4 4q.425 0 .713.288T5 5v1.35q1.275-1.6 3.113-2.475T12 3q1.875 0 3.513.713t2.85 1.924q1.212 1.213 1.925 2.85T21 12q0 1.875-.713 3.513t-1.924 2.85q-1.213 1.212-2.85 1.925T12 21Zm1-9.4l2.5 2.5q.275.275.275.7t-.275.7q-.275.275-.7.275t-.7-.275l-2.8-2.8q-.15-.15-.225-.337T11 11.975V8q0-.425.288-.713T12 7q.425 0 .713.288T13 8v3.6Z",
|
"history": "M12 21q-3.15 0-5.575-1.913T3.275 14.2q-.1-.375.15-.687t.675-.363q.4-.05.725.15t.45.6q.6 2.25 2.475 3.675T12 19q2.925 0 4.963-2.038T19 12q0-2.925-2.038-4.963T12 5q-1.725 0-3.225.8T6.25 8H8q.425 0 .713.288T9 9q0 .425-.288.713T8 10H4q-.425 0-.713-.288T3 9V5q0-.425.288-.713T4 4q.425 0 .713.288T5 5v1.35q1.275-1.6 3.113-2.475T12 3q1.875 0 3.513.713t2.85 1.924q1.212 1.213 1.925 2.85T21 12q0 1.875-.713 3.513t-1.924 2.85q-1.213 1.212-2.85 1.925T12 21Zm1-9.4l2.5 2.5q.275.275.275.7t-.275.7q-.275.275-.7.275t-.7-.275l-2.8-2.8q-.15-.15-.225-.337T11 11.975V8q0-.425.288-.713T12 7q.425 0 .713.288T13 8v3.6Z",
|
||||||
"delete": "M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6v12Z",
|
"delete": "M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6v12Z",
|
||||||
|
"delete-sweep": "M15 16h4v2h-4zm0-8h7v2h-7zm0 4h6v2h-6zM3 18c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2V8H3zM14 5h-3l-1-1H6L5 5H2v2h12z",
|
||||||
"fire": "M17.66 11.2c-.23-.3-.51-.56-.77-.82c-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32c-2.59 2.08-3.61 5.75-2.39 8.9c.04.1.08.2.08.33c0 .22-.15.42-.35.5c-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5c.14.6.41 1.2.71 1.73c1.08 1.73 2.95 2.97 4.96 3.22c2.14.27 4.43-.12 6.07-1.6c1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6c-1.12.4-2.24-.16-2.9-.82c1.19-.28 1.9-1.16 2.11-2.05c.17-.8-.15-1.46-.28-2.23c-.12-.74-.1-1.37.17-2.06c.19.38.39.76.63 1.06c.77 1 1.98 1.44 2.24 2.8c.04.14.06.28.06.43c.03.82-.33 1.72-.93 2.27Z",
|
"fire": "M17.66 11.2c-.23-.3-.51-.56-.77-.82c-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32c-2.59 2.08-3.61 5.75-2.39 8.9c.04.1.08.2.08.33c0 .22-.15.42-.35.5c-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5c.14.6.41 1.2.71 1.73c1.08 1.73 2.95 2.97 4.96 3.22c2.14.27 4.43-.12 6.07-1.6c1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6c-1.12.4-2.24-.16-2.9-.82c1.19-.28 1.9-1.16 2.11-2.05c.17-.8-.15-1.46-.28-2.23c-.12-.74-.1-1.37.17-2.06c.19.38.39.76.63 1.06c.77 1 1.98 1.44 2.24 2.8c.04.14.06.28.06.43c.03.82-.33 1.72-.93 2.27Z",
|
||||||
"search-rounded": "M9.5 16q-2.725 0-4.612-1.888T3 9.5q0-2.725 1.888-4.612T9.5 3q2.725 0 4.612 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l5.6 5.6q.275.275.275.7t-.275.7q-.275.275-.7.275t-.7-.275l-5.6-5.6q-.75.6-1.725.95T9.5 16Zm0-2q1.875 0 3.188-1.313T14 9.5q0-1.875-1.313-3.188T9.5 5Q7.625 5 6.312 6.313T5 9.5q0 1.875 1.313 3.188T9.5 14Z",
|
"search-rounded": "M9.5 16q-2.725 0-4.612-1.888T3 9.5q0-2.725 1.888-4.612T9.5 3q2.725 0 4.612 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l5.6 5.6q.275.275.275.7t-.275.7q-.275.275-.7.275t-.7-.275l-5.6-5.6q-.75.6-1.725.95T9.5 16Zm0-2q1.875 0 3.188-1.313T14 9.5q0-1.875-1.313-3.188T9.5 5Q7.625 5 6.312 6.313T5 9.5q0 1.875 1.313 3.188T9.5 14Z",
|
||||||
"search-off": "m7 17.7l1.4 1.425q.15.15.35.15t.35-.15q.15-.15.15-.363T9.1 18.4L7.7 17l1.425-1.425q.15-.15.15-.35t-.15-.35q-.15-.15-.35-.15t-.35.15L7 16.3l-1.425-1.425q-.15-.15-.35-.15t-.35.15q-.15.15-.15.35t.15.35L6.3 17l-1.425 1.425q-.15.15-.15.35t.15.35q.15.15.35.15t.35-.15L7 17.7ZM7 22q-2.075 0-3.538-1.463T2 17q0-2.075 1.463-3.538T7 12q2.075 0 3.538 1.463T12 17q0 2.075-1.463 3.538T7 22Zm7.2-7.4q-.3-.325-.638-.663T12.9 13.3q.95-.6 1.525-1.6T15 9.5q0-1.875-1.313-3.188T10.5 5Q8.625 5 7.312 6.313T6 9.5q0 .15.013.288t.037.287q-.45.05-.987.2t-.963.35q-.05-.275-.075-.55T4 9.5q0-2.725 1.888-4.612T10.5 3q2.725 0 4.612 1.888T17 9.5q0 1.075-.338 2.038t-.937 1.762l5.575 5.6q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275l-5.7-5.7Z",
|
"search-off": "m7 17.7l1.4 1.425q.15.15.35.15t.35-.15q.15-.15.15-.363T9.1 18.4L7.7 17l1.425-1.425q.15-.15.15-.35t-.15-.35q-.15-.15-.35-.15t-.35.15L7 16.3l-1.425-1.425q-.15-.15-.35-.15t-.35.15q-.15.15-.15.35t.15.35L6.3 17l-1.425 1.425q-.15.15-.15.35t.15.35q.15.15.35.15t.35-.15L7 17.7ZM7 22q-2.075 0-3.538-1.463T2 17q0-2.075 1.463-3.538T7 12q2.075 0 3.538 1.463T12 17q0 2.075-1.463 3.538T7 22Zm7.2-7.4q-.3-.325-.638-.663T12.9 13.3q.95-.6 1.525-1.6T15 9.5q0-1.875-1.313-3.188T10.5 5Q8.625 5 7.312 6.313T6 9.5q0 .15.013.288t.037.287q-.45.05-.987.2t-.963.35q-.05-.275-.075-.55T4 9.5q0-2.725 1.888-4.612T10.5 3q2.725 0 4.612 1.888T17 9.5q0 1.075-.338 2.038t-.937 1.762l5.575 5.6q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275l-5.7-5.7Z",
|
||||||
|
|||||||
@@ -64,11 +64,15 @@
|
|||||||
<div class="cover-content">
|
<div class="cover-content">
|
||||||
<n-text class="name">{{ item.name }}</n-text>
|
<n-text class="name">{{ item.name }}</n-text>
|
||||||
<!-- 创建者 -->
|
<!-- 创建者 -->
|
||||||
<n-text v-if="item.creator" class="creator" depth="3">
|
<n-text v-if="item?.creator" class="creator" depth="3">
|
||||||
{{ item.creator?.nickname || item.creator || "未知" }}
|
{{ item.creator?.nickname || item.creator || "未知" }}
|
||||||
</n-text>
|
</n-text>
|
||||||
|
<!-- 电台简介 -->
|
||||||
|
<n-text v-if="item?.rcmdText" class="creator" depth="3">
|
||||||
|
{{ item.rcmdText || "未知简介" }}
|
||||||
|
</n-text>
|
||||||
<!-- 歌手 -->
|
<!-- 歌手 -->
|
||||||
<div v-if="item.artists" class="artists">
|
<div v-if="item.artists && type !== 'dj'" class="artists">
|
||||||
<n-text
|
<n-text
|
||||||
v-for="ar in item.artists"
|
v-for="ar in item.artists"
|
||||||
:key="ar.id"
|
:key="ar.id"
|
||||||
@@ -198,6 +202,14 @@ const jumpLink = (data, type) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "dj":
|
||||||
|
router.push({
|
||||||
|
path: "/dj",
|
||||||
|
query: {
|
||||||
|
id: data?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -260,7 +272,9 @@ const jumpLink = (data, type) => {
|
|||||||
top: -80px;
|
top: -80px;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
|
background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition:
|
transition:
|
||||||
|
|||||||
@@ -140,12 +140,12 @@ const menuOptions = computed(() => [
|
|||||||
RouterLink,
|
RouterLink,
|
||||||
{
|
{
|
||||||
to: {
|
to: {
|
||||||
name: "record",
|
name: "dj-hot",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => ["播客电台"],
|
() => ["播客电台"],
|
||||||
),
|
),
|
||||||
key: "record",
|
key: "dj-hot",
|
||||||
icon: renderIcon("record"),
|
icon: renderIcon("record"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-drawer
|
<n-drawer
|
||||||
v-model:show="playListShow"
|
v-model:show="playListShow"
|
||||||
:class="showFullPlayer ? 'main-playlist full-player' : 'main-playlist'"
|
:class="['main-playlist', { 'full-player': showFullPlayer }]"
|
||||||
:style="{
|
:style="{
|
||||||
'--cover-main-color': `rgb(${coverTheme?.light?.shadeTwo})` || '#efefef',
|
'--cover-main-color': `rgb(${coverTheme?.light?.shadeTwo})` || '#efefef',
|
||||||
'--cover-second-color': `rgba(${coverTheme?.light?.shadeTwo}, 0.14)` || '#efefef14',
|
'--cover-second-color': `rgba(${coverTheme?.light?.shadeTwo}, 0.14)` || '#efefef14',
|
||||||
@@ -21,28 +21,100 @@
|
|||||||
</n-text>
|
</n-text>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 提示 -->
|
<!-- 歌曲列表 -->
|
||||||
<n-alert v-if="playList?.length >= 400" class="alert" :show-icon="false">
|
<div class="list">
|
||||||
当前歌曲过多,无法自动定位,请手动查找
|
<Transition name="fade" mode="out-in">
|
||||||
</n-alert>
|
<n-virtual-list
|
||||||
|
v-if="playList?.length"
|
||||||
|
ref="playListRef"
|
||||||
|
:item-size="76"
|
||||||
|
:items="playListData"
|
||||||
|
:default-scroll-index="playIndex"
|
||||||
|
style="max-height: calc(100vh - 158px)"
|
||||||
|
>
|
||||||
|
<template #default="{ item, index }">
|
||||||
|
<div
|
||||||
|
:id="`songs-${index}`"
|
||||||
|
:key="item.id"
|
||||||
|
:class="[
|
||||||
|
'songs-item',
|
||||||
|
{ play: playSongData?.id === item?.id, player: showFullPlayer },
|
||||||
|
]"
|
||||||
|
@click.stop="playSong(item, index)"
|
||||||
|
@dblclick.stop="playSong(item, index)"
|
||||||
|
>
|
||||||
|
<!-- 序号 -->
|
||||||
|
<n-text v-if="playSongData?.id !== item?.id" class="num" depth="3">
|
||||||
|
{{ index + 1 }}
|
||||||
|
</n-text>
|
||||||
|
<n-icon v-else class="play" size="18">
|
||||||
|
<SvgIcon icon="music-note" />
|
||||||
|
</n-icon>
|
||||||
|
<!-- 信息 -->
|
||||||
|
<div class="info">
|
||||||
|
<!-- 歌曲名 -->
|
||||||
|
<n-text class="name" depth="2">{{ item?.name || "未知曲目" }}</n-text>
|
||||||
|
<!-- 歌手 -->
|
||||||
|
<div v-if="Array.isArray(item?.artists)" class="artist">
|
||||||
|
<n-text v-for="ar in item.artists" :key="ar.id" depth="3" class="ar">
|
||||||
|
{{ ar.name }}
|
||||||
|
</n-text>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="playMode === 'dj'" class="artist">
|
||||||
|
<n-text class="ar"> 电台节目 </n-text>
|
||||||
|
</div>
|
||||||
|
<div v-else class="artist">
|
||||||
|
<n-text class="ar"> {{ item?.artists || "未知艺术家" }} </n-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 删除 -->
|
||||||
|
<n-icon class="delete" size="18" @click.stop="removeSong(index)">
|
||||||
|
<SvgIcon icon="delete" />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-virtual-list>
|
||||||
|
<n-empty
|
||||||
|
v-else
|
||||||
|
description="播放列表暂无歌曲,快去添加吧"
|
||||||
|
class="tip"
|
||||||
|
style="margin-top: 60px"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<!-- 操作 -->
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<n-data-table
|
<n-grid v-if="playList?.length" :cols="2" x-gap="16" class="controls">
|
||||||
v-if="playList?.length"
|
<n-gi>
|
||||||
class="pl-list"
|
<!-- 定位歌曲 -->
|
||||||
:columns="columns"
|
<n-button
|
||||||
:data="playList"
|
size="large"
|
||||||
:bordered="false"
|
tag="div"
|
||||||
:bottom-bordered="false"
|
strong
|
||||||
:max-height="playList?.length >= 400 ? 'calc(100vh - 162px)' : '100%'"
|
secondary
|
||||||
virtual-scroll
|
@click="playListRef?.scrollTo({ index: playIndex })"
|
||||||
/>
|
>
|
||||||
<n-empty
|
<template #icon>
|
||||||
v-else
|
<n-icon>
|
||||||
description="播放列表暂无歌曲,快去添加吧"
|
<SvgIcon icon="location" />
|
||||||
class="tip"
|
</n-icon>
|
||||||
style="margin-top: 60px"
|
</template>
|
||||||
size="large"
|
当前播放
|
||||||
/>
|
</n-button>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<!-- 清空列表 -->
|
||||||
|
<n-button size="large" tag="div" strong secondary @click="cleanPlaylists">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<SvgIcon icon="delete-sweep" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
清空列表
|
||||||
|
</n-button>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
</Transition>
|
</Transition>
|
||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
@@ -54,12 +126,25 @@ import { storeToRefs } from "pinia";
|
|||||||
import { musicData, siteStatus } from "@/stores";
|
import { musicData, siteStatus } from "@/stores";
|
||||||
import { initPlayer, fadePlayOrPause, changePlayIndex, soundStop } from "@/utils/Player";
|
import { initPlayer, fadePlayOrPause, changePlayIndex, soundStop } from "@/utils/Player";
|
||||||
import SvgIcon from "@/components/Global/SvgIcon";
|
import SvgIcon from "@/components/Global/SvgIcon";
|
||||||
|
import debounce from "@/utils/debounce";
|
||||||
|
|
||||||
const music = musicData();
|
const music = musicData();
|
||||||
const status = siteStatus();
|
const status = siteStatus();
|
||||||
const { playSongData, playList, playIndex, playMode } = storeToRefs(music);
|
const { playSongData, playList, playIndex, playMode } = storeToRefs(music);
|
||||||
const { coverTheme, showFullPlayer, playListShow } = storeToRefs(status);
|
const { coverTheme, showFullPlayer, playListShow } = storeToRefs(status);
|
||||||
|
|
||||||
|
const playListRef = ref(null);
|
||||||
|
|
||||||
|
// 播放列表数据
|
||||||
|
const playListData = computed(() => {
|
||||||
|
return playList.value?.[0]
|
||||||
|
? playList.value.slice().map((v, i) => {
|
||||||
|
v.key = `${i}`;
|
||||||
|
return v;
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
});
|
||||||
|
|
||||||
// 抽屉开启
|
// 抽屉开启
|
||||||
const playlistOpen = () => {
|
const playlistOpen = () => {
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
@@ -71,73 +156,8 @@ const playlistOpen = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 表格数据
|
|
||||||
const columns = computed(() => [
|
|
||||||
{
|
|
||||||
key: "songs",
|
|
||||||
className: "songs-item",
|
|
||||||
render(song, index) {
|
|
||||||
return createSongs(song, index);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 列表歌曲模块
|
|
||||||
const createSongs = (song, index) => {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
id: `pl-song-${index}`,
|
|
||||||
class: {
|
|
||||||
songs: true,
|
|
||||||
play: playSongData.value?.id === song?.id,
|
|
||||||
player: showFullPlayer.value,
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
playSong(song, index);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[
|
|
||||||
// 序号
|
|
||||||
playSongData.value?.id !== song?.id
|
|
||||||
? h(NText, { class: "num", depth: "3" }, () => [index + 1])
|
|
||||||
: h(NIcon, { class: "play", size: "18" }, () => [h(SvgIcon, { icon: "music-note" })]),
|
|
||||||
// 信息
|
|
||||||
h("div", { class: "info" }, [
|
|
||||||
// 名称
|
|
||||||
h(NText, { class: "name", depth: "2" }, () => [song?.name || "未知曲目"]),
|
|
||||||
// 歌手
|
|
||||||
Array.isArray(song.artists)
|
|
||||||
? h(
|
|
||||||
"div",
|
|
||||||
{ class: "artist" },
|
|
||||||
song.artists.map((ar) =>
|
|
||||||
h(NText, { class: "ar", depth: "3", key: ar.id }, () => [ar.name]),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: h("div", { class: "artist" }, [
|
|
||||||
h(NText, { class: "ar", depth: "3" }, () => [song.artists || "未知艺术家"]),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
// 移除
|
|
||||||
h(
|
|
||||||
NIcon,
|
|
||||||
{
|
|
||||||
class: "delete",
|
|
||||||
size: "18",
|
|
||||||
onClick: (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
removeSong(index);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
() => [h(SvgIcon, { icon: "delete" })],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 播放歌曲
|
// 播放歌曲
|
||||||
const playSong = async (song, index) => {
|
const playSong = debounce(async (song, index) => {
|
||||||
// 更改模式
|
// 更改模式
|
||||||
playMode.value = "normal";
|
playMode.value = "normal";
|
||||||
// 更改播放索引
|
// 更改播放索引
|
||||||
@@ -150,25 +170,41 @@ const playSong = async (song, index) => {
|
|||||||
console.log("与当前播放歌曲不一致");
|
console.log("与当前播放歌曲不一致");
|
||||||
playSongData.value = song;
|
playSongData.value = song;
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
initPlayer(true);
|
await initPlayer(true);
|
||||||
}
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// 清空列表
|
||||||
|
const cleanPlaylists = () => {
|
||||||
|
soundStop();
|
||||||
|
playIndex.value = 0;
|
||||||
|
playList.value = [];
|
||||||
|
playSongData.value = {};
|
||||||
|
playListShow.value = false;
|
||||||
|
showFullPlayer.value = false;
|
||||||
|
$message.success("已清空播放列表");
|
||||||
};
|
};
|
||||||
|
|
||||||
// 移除歌曲
|
// 移除歌曲
|
||||||
const removeSong = async (index) => {
|
const removeSong = async (index) => {
|
||||||
if (index < playIndex.value) {
|
// 若删除时仅剩一首
|
||||||
playIndex.value--;
|
if (playList.value.length === 1) {
|
||||||
} else if (index === playIndex.value) {
|
cleanPlaylists();
|
||||||
// 如果删除的是当前播放歌曲,则下一曲
|
return false;
|
||||||
|
}
|
||||||
|
// 若为当前播放
|
||||||
|
if (index === playIndex.value) {
|
||||||
|
playList.value.splice(index, 1);
|
||||||
changePlayIndex("next", true);
|
changePlayIndex("next", true);
|
||||||
}
|
}
|
||||||
playList.value.splice(index, 1);
|
// 若为当前播放之前
|
||||||
// 检查当前播放歌曲的索引是否超出了列表范围
|
else if (index < playIndex.value) {
|
||||||
if (playIndex.value >= playList.value.length) {
|
playIndex.value--;
|
||||||
playIndex.value = 0;
|
playList.value.splice(index, 1);
|
||||||
playList.value = [];
|
}
|
||||||
playSongData.value = {};
|
// 若大于当前播放
|
||||||
soundStop();
|
else if (index > playIndex.value) {
|
||||||
|
playList.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -180,146 +216,132 @@ const removeSong = async (index) => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert {
|
.list {
|
||||||
height: 48px;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 12px;
|
overflow: hidden;
|
||||||
}
|
.songs-item {
|
||||||
.pl-list {
|
display: flex;
|
||||||
:deep(.n-data-table-thead) {
|
flex-direction: row;
|
||||||
display: none;
|
align-items: center;
|
||||||
}
|
justify-content: space-between;
|
||||||
:deep(.n-data-table-tbody) {
|
height: 64px;
|
||||||
.songs-item {
|
border-radius: 8px;
|
||||||
padding: 0;
|
margin-bottom: 12px;
|
||||||
.songs {
|
padding: 8px;
|
||||||
display: flex;
|
border: 1px solid transparent;
|
||||||
flex-direction: row;
|
box-sizing: border-box;
|
||||||
align-items: center;
|
background-color: var(--n-close-color-hover);
|
||||||
justify-content: space-between;
|
transition:
|
||||||
border-radius: 8px;
|
transform 0.3s,
|
||||||
margin-bottom: 12px;
|
border-color 0.3s,
|
||||||
padding: 8px;
|
box-shadow 0.3s,
|
||||||
border: 1px solid transparent;
|
background-color 0.3s;
|
||||||
background-color: var(--n-border-color-modal);
|
cursor: pointer;
|
||||||
transition:
|
.num,
|
||||||
transform 0.3s,
|
.play {
|
||||||
border-color 0.3s,
|
width: 30px;
|
||||||
box-shadow 0.3s,
|
height: 30px;
|
||||||
background-color 0.3s;
|
min-width: 30px;
|
||||||
cursor: pointer;
|
border-radius: 8px;
|
||||||
.num,
|
margin-right: 16px;
|
||||||
.play {
|
display: flex;
|
||||||
width: 30px;
|
align-items: center;
|
||||||
height: 30px;
|
justify-content: center;
|
||||||
min-width: 30px;
|
}
|
||||||
border-radius: 8px;
|
.info {
|
||||||
margin-right: 16px;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: center;
|
align-items: flex-start;
|
||||||
}
|
.artist {
|
||||||
.info {
|
margin-top: 2px;
|
||||||
width: 100%;
|
font-size: 13px;
|
||||||
display: flex;
|
display: -webkit-box;
|
||||||
flex-direction: column;
|
-webkit-box-orient: vertical;
|
||||||
align-items: flex-start;
|
-webkit-line-clamp: 1;
|
||||||
.artist {
|
overflow: hidden;
|
||||||
margin-top: 2px;
|
word-break: break-all;
|
||||||
font-size: 13px;
|
.ar {
|
||||||
display: -webkit-box;
|
font-size: 12px;
|
||||||
-webkit-box-orient: vertical;
|
display: inline-flex;
|
||||||
-webkit-line-clamp: 1;
|
&::after {
|
||||||
overflow: hidden;
|
content: "/";
|
||||||
word-break: break-all;
|
margin: 0 4px;
|
||||||
.ar {
|
}
|
||||||
font-size: 12px;
|
&:last-child {
|
||||||
display: inline-flex;
|
&::after {
|
||||||
&::after {
|
display: none;
|
||||||
content: "/";
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
&::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.delete {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10px;
|
|
||||||
margin-right: 6px;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--n-close-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.play {
|
|
||||||
background-color: var(--main-second-color);
|
|
||||||
border-color: var(--main-color);
|
|
||||||
a,
|
|
||||||
span,
|
|
||||||
.n-icon {
|
|
||||||
color: var(--main-color) !important;
|
|
||||||
}
|
|
||||||
.artist {
|
|
||||||
.ar {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.player {
|
|
||||||
background-color: var(--cover-second-color);
|
|
||||||
a,
|
|
||||||
span,
|
|
||||||
.n-icon {
|
|
||||||
color: var(--cover-main-color) !important;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
&.play {
|
|
||||||
background-color: var(--cover-second-color);
|
|
||||||
border-color: var(--cover-main-color);
|
|
||||||
a,
|
|
||||||
span,
|
|
||||||
.n-icon {
|
|
||||||
color: var(--cover-main-color) !important;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--cover-main-color);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--main-color);
|
|
||||||
box-shadow:
|
|
||||||
0 1px 2px -2px var(--main-boxshadow-color),
|
|
||||||
0 3px 6px 0 var(--main-boxshadow-color),
|
|
||||||
0 5px 12px 4px var(--main-boxshadow-hover-color);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.995);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.n-data-table-tr {
|
.delete {
|
||||||
&:last-child {
|
display: flex;
|
||||||
.songs {
|
align-items: center;
|
||||||
margin-bottom: 0;
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--n-close-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.play {
|
||||||
|
background-color: var(--main-second-color);
|
||||||
|
border-color: var(--main-color);
|
||||||
|
a,
|
||||||
|
span,
|
||||||
|
.n-icon {
|
||||||
|
color: var(--main-color) !important;
|
||||||
|
}
|
||||||
|
.artist {
|
||||||
|
.ar {
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.player {
|
||||||
|
background-color: var(--cover-second-color);
|
||||||
|
a,
|
||||||
|
span,
|
||||||
|
.n-icon {
|
||||||
|
color: var(--cover-main-color) !important;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
&.play {
|
||||||
|
background-color: var(--cover-second-color);
|
||||||
|
border-color: var(--cover-main-color);
|
||||||
|
a,
|
||||||
|
span,
|
||||||
|
.n-icon {
|
||||||
|
color: var(--cover-main-color) !important;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--cover-main-color);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--main-color);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.995);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tip {
|
.controls {
|
||||||
border-radius: 8px;
|
height: 40px;
|
||||||
|
margin-top: 16px;
|
||||||
|
.n-button {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -336,6 +358,10 @@ const removeSong = async (index) => {
|
|||||||
}
|
}
|
||||||
.n-scrollbar-content {
|
.n-scrollbar-content {
|
||||||
padding: 16px !important;
|
padding: 16px !important;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
&.full-player {
|
&.full-player {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
<div v-if="showTitle" class="song-list-header">
|
<div v-if="showTitle" class="song-list-header">
|
||||||
<n-text class="num" depth="3"> # </n-text>
|
<n-text class="num" depth="3"> # </n-text>
|
||||||
<n-text :class="{ info: true, 'has-cover': data[0].cover && showCover }" depth="3">
|
<n-text :class="{ info: true, 'has-cover': data[0].cover && showCover }" depth="3">
|
||||||
歌曲
|
{{ type === "song" ? "歌曲" : "声音" }}
|
||||||
</n-text>
|
</n-text>
|
||||||
<n-text v-if="data[0].album && showAlbum" class="album" depth="3"> 专辑 </n-text>
|
<n-text v-if="data[0].album && showAlbum" class="album" depth="3"> 专辑 </n-text>
|
||||||
|
<n-text v-if="data[0].updateTime && type === 'dj'" class="update" depth="3">
|
||||||
|
更新日期
|
||||||
|
</n-text>
|
||||||
|
<n-text v-if="data[0].playCount && type === 'dj'" class="count" depth="3"> 播放量 </n-text>
|
||||||
<n-text v-if="data[0].duration" class="duration" depth="3"> 时长 </n-text>
|
<n-text v-if="data[0].duration" class="duration" depth="3"> 时长 </n-text>
|
||||||
<n-text v-if="data[0].size" class="size" depth="3"> 大小 </n-text>
|
<n-text v-if="data[0].size" class="size" depth="3"> 大小 </n-text>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,7 +33,7 @@
|
|||||||
hoverable
|
hoverable
|
||||||
@dblclick.stop="playSong(data, item, songsIndex + index)"
|
@dblclick.stop="playSong(data, item, songsIndex + index)"
|
||||||
@contextmenu="
|
@contextmenu="
|
||||||
songListDropdownRef?.openDropdown($event, data, item, songsIndex + index, sourceId)
|
songListDropdownRef?.openDropdown($event, data, item, songsIndex + index, sourceId, type)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<!-- 序号 -->
|
<!-- 序号 -->
|
||||||
@@ -122,6 +126,9 @@
|
|||||||
{{ ar.name }}
|
{{ ar.name }}
|
||||||
</n-text>
|
</n-text>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="type === 'dj'" class="artist">
|
||||||
|
<n-text class="ar"> 电台节目 </n-text>
|
||||||
|
</div>
|
||||||
<div v-else class="artist">
|
<div v-else class="artist">
|
||||||
<n-text class="ar"> {{ item.artists || "未知艺术家" }} </n-text>
|
<n-text class="ar"> {{ item.artists || "未知艺术家" }} </n-text>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,7 +136,7 @@
|
|||||||
<n-text v-if="item.alia" class="alia" depth="3">{{ item.alia }}</n-text>
|
<n-text v-if="item.alia" class="alia" depth="3">{{ item.alia }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<!-- 专辑 -->
|
<!-- 专辑 -->
|
||||||
<template v-if="showAlbum">
|
<template v-if="showAlbum && type !== 'dj'">
|
||||||
<n-text
|
<n-text
|
||||||
v-if="item.album"
|
v-if="item.album"
|
||||||
class="album"
|
class="album"
|
||||||
@@ -140,7 +147,7 @@
|
|||||||
<n-text v-else class="album">未知专辑</n-text>
|
<n-text v-else class="album">未知专辑</n-text>
|
||||||
</template>
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<div class="action">
|
<div v-if="type !== 'dj'" class="action">
|
||||||
<!-- 喜欢歌曲 -->
|
<!-- 喜欢歌曲 -->
|
||||||
<n-icon
|
<n-icon
|
||||||
:depth="dataStore.getSongIsLike(item?.id) ? 0 : 3"
|
:depth="dataStore.getSongIsLike(item?.id) ? 0 : 3"
|
||||||
@@ -158,6 +165,14 @@
|
|||||||
/>
|
/>
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 更新日期 -->
|
||||||
|
<n-text v-if="type === 'dj' && item.updateTime" class="update" depth="3">
|
||||||
|
{{ getTimestampTime(item.updateTime, false) }}
|
||||||
|
</n-text>
|
||||||
|
<!-- 播放量 -->
|
||||||
|
<n-text v-if="type === 'dj' && item.playCount" class="count" depth="3">
|
||||||
|
{{ item.playCount }}次
|
||||||
|
</n-text>
|
||||||
<!-- 时长 -->
|
<!-- 时长 -->
|
||||||
<n-text v-if="item.duration" class="duration" depth="3">{{ item.duration }}</n-text>
|
<n-text v-if="item.duration" class="duration" depth="3">{{ item.duration }}</n-text>
|
||||||
<n-text v-else class="duration"> -- </n-text>
|
<n-text v-else class="duration"> -- </n-text>
|
||||||
@@ -212,18 +227,24 @@ import { storeToRefs } from "pinia";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { siteData, siteSettings, musicData } from "@/stores";
|
import { siteData, siteSettings, musicData } from "@/stores";
|
||||||
import { initPlayer, fadePlayOrPause, addSongToNext } from "@/utils/Player";
|
import { initPlayer, fadePlayOrPause, addSongToNext } from "@/utils/Player";
|
||||||
|
import { getTimestampTime } from "@/utils/timeTools";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicData();
|
const music = musicData();
|
||||||
const dataStore = siteData();
|
const dataStore = siteData();
|
||||||
const settings = siteSettings();
|
const settings = siteSettings();
|
||||||
const { userData } = storeToRefs(dataStore);
|
const { userData } = storeToRefs(dataStore);
|
||||||
const { loadSize } = storeToRefs(settings);
|
const { loadSize, playSearch } = storeToRefs(settings);
|
||||||
const { playList, playIndex, playSongData, playSongSource, playHeartbeatMode, playMode } =
|
const { playList, playIndex, playSongData, playSongSource, playHeartbeatMode, playMode } =
|
||||||
storeToRefs(music);
|
storeToRefs(music);
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
// 列表类型
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: "song",
|
||||||
|
},
|
||||||
// 列表数据
|
// 列表数据
|
||||||
data: {
|
data: {
|
||||||
type: [Array, String],
|
type: [Array, String],
|
||||||
@@ -292,7 +313,7 @@ const checkHasPlaying = (isScoll = null) => {
|
|||||||
const playSong = async (data, song, index) => {
|
const playSong = async (data, song, index) => {
|
||||||
console.log(data, song, index);
|
console.log(data, song, index);
|
||||||
// 更改模式
|
// 更改模式
|
||||||
playMode.value = "normal";
|
playMode.value = props.type === "song" ? "normal" : "dj";
|
||||||
// 检查当前页面
|
// 检查当前页面
|
||||||
const isPage = router.currentRoute.value.matched?.[0].path || null;
|
const isPage = router.currentRoute.value.matched?.[0].path || null;
|
||||||
// 是否关闭心动模式
|
// 是否关闭心动模式
|
||||||
@@ -303,7 +324,12 @@ const playSong = async (data, song, index) => {
|
|||||||
fadePlayOrPause();
|
fadePlayOrPause();
|
||||||
} else {
|
} else {
|
||||||
// 若为特殊状态
|
// 若为特殊状态
|
||||||
if (isPage === "/search" || isPage === "/history" || playHeartbeatMode.value) {
|
if (
|
||||||
|
(isPage === "/search" && !playSearch.value) ||
|
||||||
|
isPage === "/history" ||
|
||||||
|
playHeartbeatMode.value
|
||||||
|
) {
|
||||||
|
console.log("仅播放当前歌曲");
|
||||||
addSongToNext(song, true);
|
addSongToNext(song, true);
|
||||||
} else {
|
} else {
|
||||||
// 添加播放列表
|
// 添加播放列表
|
||||||
@@ -314,7 +340,7 @@ const playSong = async (data, song, index) => {
|
|||||||
console.log("与当前播放歌曲不一致");
|
console.log("与当前播放歌曲不一致");
|
||||||
playSongData.value = song;
|
playSongData.value = song;
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
initPlayer(true);
|
await initPlayer(true);
|
||||||
}
|
}
|
||||||
// 附加来源
|
// 附加来源
|
||||||
playSongSource.value = Number(props.sourceId);
|
playSongSource.value = Number(props.sourceId);
|
||||||
@@ -371,6 +397,14 @@ onBeforeUnmount(() => {
|
|||||||
.has-cover {
|
.has-cover {
|
||||||
margin-right: 66px;
|
margin-right: 66px;
|
||||||
}
|
}
|
||||||
|
.update {
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.duration {
|
.duration {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -519,6 +553,14 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.update {
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.duration {
|
.duration {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const renderIcon = (icon, size, translate = 0) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 歌曲信息
|
// 歌曲信息
|
||||||
const renderSong = (song) => {
|
const renderSong = (song, isSong) => {
|
||||||
return () =>
|
return () =>
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
@@ -75,26 +75,28 @@ const renderSong = (song) => {
|
|||||||
h(NImage, { src: song?.coverSize?.s || song?.cover, class: "cover" }),
|
h(NImage, { src: song?.coverSize?.s || song?.cover, class: "cover" }),
|
||||||
h("div", { class: "song-detail" }, [
|
h("div", { class: "song-detail" }, [
|
||||||
h(NText, { class: "name" }, () => [song?.name || "未知曲目"]),
|
h(NText, { class: "name" }, () => [song?.name || "未知曲目"]),
|
||||||
song.artists && Array.isArray(song.artists)
|
isSong
|
||||||
? h(
|
? song.artists && Array.isArray(song.artists)
|
||||||
"div",
|
? h(
|
||||||
{ class: "all-ar" },
|
"div",
|
||||||
song.artists.map((ar) =>
|
{ class: "all-ar" },
|
||||||
h(NText, { key: ar.id, class: "ar", depth: 3 }, () => [ar.name]),
|
song.artists.map((ar) =>
|
||||||
),
|
h(NText, { key: ar.id, class: "ar", depth: 3 }, () => [ar.name]),
|
||||||
)
|
),
|
||||||
: h(
|
)
|
||||||
"div",
|
: h(
|
||||||
{ class: "all-ar" },
|
"div",
|
||||||
h(NText, { class: "ar", depth: 3 }, () => [song.artists || "未知艺术家"]),
|
{ class: "all-ar" },
|
||||||
),
|
h(NText, { class: "ar", depth: 3 }, () => [song.artists || "未知艺术家"]),
|
||||||
|
)
|
||||||
|
: h(NText, { class: "ar", depth: 3 }, () => ["电台节目"]),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开右键菜单
|
// 打开右键菜单
|
||||||
const openDropdown = (e, data, song, index, sourceId) => {
|
const openDropdown = (e, data, song, index, sourceId, type) => {
|
||||||
try {
|
try {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropdownShow.value = false;
|
dropdownShow.value = false;
|
||||||
@@ -106,7 +108,9 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
);
|
);
|
||||||
// 当前状态
|
// 当前状态
|
||||||
const isFm = playMode.value === "fm";
|
const isFm = playMode.value === "fm";
|
||||||
|
const isSong = type === "song";
|
||||||
const isLocalSong = song?.path ? true : false;
|
const isLocalSong = song?.path ? true : false;
|
||||||
|
const isHasMv = song.mv && song.mv !== 0 ? true : false;
|
||||||
const isCloud = router.currentRoute.value.name === "cloud";
|
const isCloud = router.currentRoute.value.name === "cloud";
|
||||||
const isUserPlaylist = sourceId !== 0 && userPlaylistsData.some((pl) => pl.id == sourceId);
|
const isUserPlaylist = sourceId !== 0 && userPlaylistsData.some((pl) => pl.id == sourceId);
|
||||||
// 生成菜单
|
// 生成菜单
|
||||||
@@ -117,7 +121,7 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
key: "song-data",
|
key: "song-data",
|
||||||
type: "render",
|
type: "render",
|
||||||
show: !isLocalSong,
|
show: !isLocalSong,
|
||||||
render: renderSong(song),
|
render: renderSong(song, isSong),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "line-song",
|
key: "line-song",
|
||||||
@@ -137,9 +141,10 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
{
|
{
|
||||||
key: "next-play",
|
key: "next-play",
|
||||||
label: "下一首播放",
|
label: "下一首播放",
|
||||||
show: playSongData.value?.id !== song.id && !isFm,
|
show: isSong && playMode.value !== "dj" && playSongData.value?.id !== song.id && !isFm,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
playMode.value = "song";
|
||||||
addSongToNext(song);
|
addSongToNext(song);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -148,7 +153,7 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
{
|
{
|
||||||
key: "add-pl",
|
key: "add-pl",
|
||||||
label: "添加到歌单",
|
label: "添加到歌单",
|
||||||
show: song?.path ? false : true,
|
show: isSong && !isLocalSong,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addPlaylistRef.value?.openAddToPlaylist(song?.id);
|
addPlaylistRef.value?.openAddToPlaylist(song?.id);
|
||||||
@@ -159,7 +164,7 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
{
|
{
|
||||||
key: "comment",
|
key: "comment",
|
||||||
label: "查看评论",
|
label: "查看评论",
|
||||||
show: song?.path ? false : true,
|
show: isSong && !isLocalSong,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -175,7 +180,7 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
{
|
{
|
||||||
key: "mv",
|
key: "mv",
|
||||||
label: "观看 MV",
|
label: "观看 MV",
|
||||||
show: song.mv && song.mv !== 0 ? true : false,
|
show: isSong && isHasMv,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -196,7 +201,7 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "copy",
|
key: "copy",
|
||||||
label: "复制歌曲 ID",
|
label: `复制${isSong ? "歌曲" : "节目"} ID`,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const songId = song?.id?.toString();
|
const songId = song?.id?.toString();
|
||||||
@@ -207,11 +212,13 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "share",
|
key: "share",
|
||||||
label: "分享歌曲链接",
|
label: `分享${isSong ? "歌曲" : "节目"}链接`,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const shareUrl = `https://music.163.com/song?id=${song?.id?.toString()}`;
|
const shareUrl = isSong
|
||||||
copyData(shareUrl, "复制歌曲链接");
|
? `https://music.163.com/song?id=${song?.id?.toString()}`
|
||||||
|
: `https://music.163.com/#/dj?id=${song?.id?.toString()}`;
|
||||||
|
copyData(shareUrl, `复制${isSong ? "歌曲" : "节目"}链接`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
icon: renderIcon("share"),
|
icon: renderIcon("share"),
|
||||||
@@ -301,7 +308,7 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
|||||||
{
|
{
|
||||||
key: "download",
|
key: "download",
|
||||||
label: "下载歌曲",
|
label: "下载歌曲",
|
||||||
show: !isLocalSong,
|
show: isSong && !isLocalSong,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
downloadSongRef.value?.openDownloadModal(song);
|
downloadSongRef.value?.openDownloadModal(song);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-image
|
<n-image
|
||||||
:src="item.coverImgUrl.replace(/^http:/, 'https:') + '?param=100y100'"
|
:src="item?.coverSize?.s || '/images/pic/album.jpg?assest'"
|
||||||
class="cover"
|
class="cover"
|
||||||
preview-disabled
|
preview-disabled
|
||||||
lazy
|
lazy
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<n-thing :title="item.name">
|
<n-thing :title="item.name">
|
||||||
<template #description>
|
<template #description>
|
||||||
<n-text depth="3" class="size">{{ item.trackCount }} 首音乐</n-text>
|
<n-text depth="3" class="size">{{ item.count }} 首音乐</n-text>
|
||||||
</template>
|
</template>
|
||||||
</n-thing>
|
</n-thing>
|
||||||
</n-list-item>
|
</n-list-item>
|
||||||
@@ -87,7 +87,7 @@ const addToPlayList = async (pid, tracks, index) => {
|
|||||||
$message.success("添加歌曲至歌单成功");
|
$message.success("添加歌曲至歌单成功");
|
||||||
if (index === 0) await data.setUserLikeSongs();
|
if (index === 0) await data.setUserLikeSongs();
|
||||||
} else {
|
} else {
|
||||||
$message.error("添加失败,请重试");
|
$message.error(result?.message || "添加失败,请重试");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ import { isLogin } from "@/utils/auth";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { siteData, siteSettings } from "@/stores";
|
import { siteData, siteSettings } from "@/stores";
|
||||||
import { getSongDetail, getSongDownload } from "@/api/song";
|
import { getSongDetail, getSongDownload } from "@/api/song";
|
||||||
import { downloadFile } from "@/utils/helper";
|
import { downloadFile, checkPlatform } from "@/utils/helper";
|
||||||
import formatData from "@/utils/formatData";
|
import formatData from "@/utils/formatData";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -97,7 +97,7 @@ const toSongDownload = async (song, br) => {
|
|||||||
// 获取下载数据
|
// 获取下载数据
|
||||||
const result = await getSongDownload(song?.id, br);
|
const result = await getSongDownload(song?.id, br);
|
||||||
// 开始下载
|
// 开始下载
|
||||||
if (!downloadPath.value) {
|
if (!downloadPath.value && checkPlatform.electron()) {
|
||||||
$notification["warning"]({
|
$notification["warning"]({
|
||||||
content: "缺少配置",
|
content: "缺少配置",
|
||||||
meta: "请前往设置页配置默认下载目录",
|
meta: "请前往设置页配置默认下载目录",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
>
|
>
|
||||||
<div class="login-content">
|
<div class="login-content">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<img class="logo" src="/images/logo/favicon.png?asset" alt="logo" />
|
<img class="logo" src="/images/icons/favicon.png?asset" alt="logo" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 登录方式 -->
|
<!-- 登录方式 -->
|
||||||
<n-tabs class="login-tabs" default-value="login-qr" type="segment" animated>
|
<n-tabs class="login-tabs" default-value="login-qr" type="segment" animated>
|
||||||
@@ -45,12 +45,15 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { siteData } from "@/stores";
|
import { siteData, siteSettings } from "@/stores";
|
||||||
import { getLoginState, refreshLogin } from "@/api/login";
|
import { getLoginState, refreshLogin } from "@/api/login";
|
||||||
import { setCookies, toLogout, isLogin } from "@/utils/auth";
|
import { setCookies, toLogout, isLogin } from "@/utils/auth";
|
||||||
|
import userSignIn from "@/utils/userSignIn";
|
||||||
|
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
|
const settings = siteSettings();
|
||||||
const { userData } = storeToRefs(data);
|
const { userData } = storeToRefs(data);
|
||||||
|
const { autoSignIn } = storeToRefs(settings);
|
||||||
|
|
||||||
// 登录数据
|
// 登录数据
|
||||||
const loginModalShow = ref(false);
|
const loginModalShow = ref(false);
|
||||||
@@ -83,6 +86,9 @@ const setLoginData = async (loginData) => {
|
|||||||
setCookies(loginData.cookie);
|
setCookies(loginData.cookie);
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
await data.setUserProfile();
|
await data.setUserProfile();
|
||||||
|
await data.setDailySongsData();
|
||||||
|
// 签到
|
||||||
|
if (autoSignIn.value) await userSignIn();
|
||||||
// 更改状态
|
// 更改状态
|
||||||
data.userLoginStatus = true;
|
data.userLoginStatus = true;
|
||||||
$message.success("登录成功");
|
$message.success("登录成功");
|
||||||
|
|||||||
@@ -2,17 +2,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-qr">
|
<div class="login-qr">
|
||||||
<div class="qr-img">
|
<div class="qr-img">
|
||||||
<n-skeleton v-if="!qrImg" class="qr" />
|
<Transition name="fade" mode="out-in">
|
||||||
<QrcodeVue
|
<n-qr-code
|
||||||
v-else
|
v-if="qrImg"
|
||||||
:class="['qr', qrStatusCode === 802 ? 'hidden' : null]"
|
:value="qrImg"
|
||||||
:value="qrImg"
|
:class="['qr', qrStatusCode === 802 ? 'hidden' : null]"
|
||||||
:size="180"
|
:size="156"
|
||||||
:margin="4"
|
:icon-size="30"
|
||||||
level="L"
|
icon-src="/images/icons/favicon.png?asset"
|
||||||
foreground="#000"
|
error-correction-level="H"
|
||||||
background="#fff"
|
/>
|
||||||
/>
|
<n-skeleton v-else class="qr" />
|
||||||
|
</Transition>
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="qrStatusCode === 802" class="refresh" @click="getQrData">
|
<div v-if="qrStatusCode === 802" class="refresh" @click="getQrData">
|
||||||
<n-icon size="22">
|
<n-icon size="22">
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getQrKey, checkQr } from "@/api/login";
|
import { getQrKey, checkQr } from "@/api/login";
|
||||||
import QrcodeVue from "qrcode.vue";
|
|
||||||
|
|
||||||
const emit = defineEmits(["setLoginData"]);
|
const emit = defineEmits(["setLoginData"]);
|
||||||
|
|
||||||
@@ -133,6 +133,7 @@ onBeforeUnmount(() => {
|
|||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
width: 180px;
|
width: 180px;
|
||||||
|
box-sizing: border-box;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
&.hidden {
|
&.hidden {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
:class="['logo', status.asideMenuCollapsed ? 'collapsed' : null]"
|
:class="['logo', status.asideMenuCollapsed ? 'collapsed' : null]"
|
||||||
@click="router.push('/')"
|
@click="router.push('/')"
|
||||||
>
|
>
|
||||||
<n-avatar class="logo-img" src="/images/logo/favicon.png?asset" />
|
<n-avatar class="logo-img" src="/images/icons/favicon.png?asset" />
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<n-text v-if="!status.asideMenuCollapsed && showSider" class="site-name">
|
<n-text v-if="!status.asideMenuCollapsed && showSider" class="site-name">
|
||||||
{{ siteName }}
|
{{ siteName }}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { useRouter } from "vue-router";
|
|||||||
import { NIcon, NText, NNumberAnimation, NButton } from "naive-ui";
|
import { NIcon, NText, NNumberAnimation, NButton } from "naive-ui";
|
||||||
import { siteData, siteSettings } from "@/stores";
|
import { siteData, siteSettings } from "@/stores";
|
||||||
import SvgIcon from "@/components/Global/SvgIcon";
|
import SvgIcon from "@/components/Global/SvgIcon";
|
||||||
|
import userSignIn from "@/utils/userSignIn";
|
||||||
|
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -61,9 +62,6 @@ const userMenuShow = ref(false);
|
|||||||
// 登录弹窗
|
// 登录弹窗
|
||||||
const loginRef = ref(null);
|
const loginRef = ref(null);
|
||||||
|
|
||||||
// 是否签到
|
|
||||||
const signInStatus = ref(false);
|
|
||||||
|
|
||||||
// 图标渲染
|
// 图标渲染
|
||||||
const renderIcon = (icon) => {
|
const renderIcon = (icon) => {
|
||||||
return () => h(NIcon, null, () => h(SvgIcon, { icon }));
|
return () => h(NIcon, null, () => h(SvgIcon, { icon }));
|
||||||
@@ -71,14 +69,28 @@ const renderIcon = (icon) => {
|
|||||||
|
|
||||||
// 数量统计模块
|
// 数量统计模块
|
||||||
const createUserNumber = (num, text, duration = 1000) => {
|
const createUserNumber = (num, text, duration = 1000) => {
|
||||||
return h("div", { className: "user-pl" }, [
|
return h(
|
||||||
h(NNumberAnimation, { from: 0, to: num, duration }),
|
"div",
|
||||||
h(NText, { depth: 3, style: { fontSize: "12px" } }, () => [text]),
|
{
|
||||||
]);
|
className: "user-pl",
|
||||||
|
onclick: () => {
|
||||||
|
userMenuShow.value = false;
|
||||||
|
router.push(
|
||||||
|
`/like/${text === "歌单" ? "playlists?" : text === "专辑" ? "albums" : "artists"}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h(NNumberAnimation, { from: 0, to: num, duration }),
|
||||||
|
h(NText, { depth: 3, style: { fontSize: "12px" } }, () => [text]),
|
||||||
|
],
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成导航栏用户信息
|
// 生成导航栏用户信息
|
||||||
const createUserData = () => {
|
const createUserData = () => {
|
||||||
|
// 是否签到
|
||||||
|
const signInStatus = sessionStorage.getItem("lastSignInDate") ? true : false;
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "nav-user-data" },
|
{ className: "nav-user-data" },
|
||||||
@@ -96,12 +108,14 @@ const createUserData = () => {
|
|||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
round: true,
|
round: true,
|
||||||
renderIcon: renderIcon(signInStatus.value ? "calendar-check" : "calendar-badge"),
|
renderIcon: renderIcon(signInStatus ? "calendar-check" : "calendar-badge"),
|
||||||
onclick: () => {
|
disabled: signInStatus,
|
||||||
$message.warning("施工中( 新建文件夹 )");
|
onclick: async () => {
|
||||||
|
userMenuShow.value = false;
|
||||||
|
await userSignIn();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => [signInStatus.value ? "Lv." + userData.value.detail?.level || 1 : "立即签到"],
|
() => [signInStatus ? "今日已签到" : "立即签到"],
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
@@ -219,6 +233,7 @@ const userMenuSelect = (key) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
.user-pl {
|
.user-pl {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
'no-sider': !showSider,
|
'no-sider': !showSider,
|
||||||
}"
|
}"
|
||||||
content-style="padding: 0"
|
content-style="padding: 0"
|
||||||
@dblclick.stop="showFullPlayer = true"
|
@dblclick.stop="openFullPlayer"
|
||||||
>
|
>
|
||||||
<!-- 进度条 -->
|
<!-- 进度条 -->
|
||||||
<vue-slider
|
<vue-slider
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<div class="player">
|
<div class="player">
|
||||||
<!-- 歌曲信息 -->
|
<!-- 歌曲信息 -->
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="cover" @click.stop="showFullPlayer = true">
|
<div class="cover" @click.stop="openFullPlayer">
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<n-image
|
<n-image
|
||||||
:key="music.getPlaySongData?.id"
|
:key="music.getPlaySongData?.id"
|
||||||
@@ -62,9 +62,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="song-info">
|
<div class="song-info">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<n-text class="text">{{ music.getPlaySongData?.name || "未知曲目" }}</n-text>
|
<n-text class="text">
|
||||||
|
{{ music.getPlaySongData?.name || "未知曲目" }}
|
||||||
|
</n-text>
|
||||||
<!-- 喜欢歌曲 -->
|
<!-- 喜欢歌曲 -->
|
||||||
<n-icon
|
<n-icon
|
||||||
|
v-if="playMode !== 'dj'"
|
||||||
class="favorite"
|
class="favorite"
|
||||||
@click.stop="
|
@click.stop="
|
||||||
data.changeLikeList(
|
data.changeLikeList(
|
||||||
@@ -85,7 +88,7 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
<!-- 更多操作 -->
|
<!-- 更多操作 -->
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
v-if="!music.getPlaySongData?.path"
|
v-if="playMode !== 'dj' && !music.getPlaySongData?.path"
|
||||||
:options="songMoreOptions"
|
:options="songMoreOptions"
|
||||||
:show-arrow="true"
|
:show-arrow="true"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
@@ -100,7 +103,8 @@
|
|||||||
<!-- 歌手 -->
|
<!-- 歌手 -->
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
((!playState || !bottomLyricShow) && playSongLyric.lrc?.length) ||
|
((!playState || !bottomLyricShow || playMode === 'dj') &&
|
||||||
|
playSongLyric.lrc?.length) ||
|
||||||
playSongLyricIndex === -1
|
playSongLyricIndex === -1
|
||||||
"
|
"
|
||||||
class="artist"
|
class="artist"
|
||||||
@@ -119,6 +123,7 @@
|
|||||||
{{ ar.name }}
|
{{ ar.name }}
|
||||||
</n-text>
|
</n-text>
|
||||||
</template>
|
</template>
|
||||||
|
<div v-else-if="playMode === 'dj'" class="ar">电台节目</div>
|
||||||
<n-text v-else class="ar">
|
<n-text v-else class="ar">
|
||||||
{{ music.getPlaySongData?.artists || "未知艺术家" }}
|
{{ music.getPlaySongData?.artists || "未知艺术家" }}
|
||||||
</n-text>
|
</n-text>
|
||||||
@@ -194,110 +199,121 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
<!-- 功能区 -->
|
<!-- 功能区 -->
|
||||||
<div class="menu">
|
<Transition name="fade" mode="out-in">
|
||||||
<!-- 时间进度 -->
|
<div :key="playMode" class="menu">
|
||||||
<div class="time">
|
<!-- 时间进度 -->
|
||||||
<n-text class="played" depth="3">{{ playTimeData.played }}</n-text>
|
<div class="time">
|
||||||
<n-text depth="3">{{ playTimeData.durationTime }}</n-text>
|
<n-text class="played" depth="3">{{ playTimeData.played }}</n-text>
|
||||||
</div>
|
<n-text depth="3">{{ playTimeData.durationTime }}</n-text>
|
||||||
<!-- 播放模式 -->
|
|
||||||
<n-dropdown
|
|
||||||
v-if="playMode === 'normal'"
|
|
||||||
:options="playModeOptions"
|
|
||||||
:show-arrow="true"
|
|
||||||
trigger="hover"
|
|
||||||
@select="playModeChange"
|
|
||||||
>
|
|
||||||
<div class="mode" @click.stop @dblclick.stop>
|
|
||||||
<n-icon size="22">
|
|
||||||
<SvgIcon
|
|
||||||
:icon="
|
|
||||||
playHeartbeatMode
|
|
||||||
? 'heartbit'
|
|
||||||
: playSongMode === 'normal'
|
|
||||||
? 'repeat-list'
|
|
||||||
: playSongMode === 'random'
|
|
||||||
? 'shuffle'
|
|
||||||
: 'repeat-song'
|
|
||||||
"
|
|
||||||
isSpecial
|
|
||||||
/>
|
|
||||||
</n-icon>
|
|
||||||
</div>
|
</div>
|
||||||
</n-dropdown>
|
<!-- 播放模式 -->
|
||||||
<!-- 倍速 -->
|
<n-dropdown
|
||||||
<n-popover :show-arrow="false" trigger="hover" placement="top-end" raw>
|
v-if="playMode !== 'fm'"
|
||||||
<template #trigger>
|
:options="playModeOptions"
|
||||||
<div class="speed" @click.stop="(playRate = 1), setRate(1)" @dblclick.stop>
|
:show-arrow="true"
|
||||||
<n-icon v-if="playRate === 1" size="22">
|
trigger="hover"
|
||||||
<SvgIcon icon="speed-rounded" />
|
@select="playModeChange"
|
||||||
</n-icon>
|
|
||||||
<n-text v-else class="speed-text">{{ playRate }}x</n-text>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<!-- 倍速调整 -->
|
|
||||||
<div class="slider-content">
|
|
||||||
<n-slider
|
|
||||||
v-model:value="playRate"
|
|
||||||
:tooltip="false"
|
|
||||||
:min="0.1"
|
|
||||||
:max="2"
|
|
||||||
:step="0.1"
|
|
||||||
:marks="{
|
|
||||||
0.1: '减速',
|
|
||||||
1: '正常',
|
|
||||||
2: '加速',
|
|
||||||
}"
|
|
||||||
style="width: 220px"
|
|
||||||
@update:value="setRate"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</n-popover>
|
|
||||||
<!-- 音量 -->
|
|
||||||
<n-popover trigger="hover" :show-arrow="false" raw>
|
|
||||||
<template #trigger>
|
|
||||||
<n-icon class="volume" size="22" @click.stop="setVolumeMute" @wheel="changeVolume">
|
|
||||||
<SvgIcon v-if="playVolume === 0" icon="no-sound-rounded" />
|
|
||||||
<SvgIcon v-else-if="playVolume > 0 && playVolume < 0.4" icon="volume-mute-rounded" />
|
|
||||||
<SvgIcon
|
|
||||||
v-else-if="playVolume >= 0.4 && playVolume < 0.7"
|
|
||||||
icon="volume-down-rounded"
|
|
||||||
/>
|
|
||||||
<SvgIcon v-else icon="volume-up-rounded" />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
<!-- 音量调整 -->
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
padding: '10px 0',
|
|
||||||
width: '50px',
|
|
||||||
}"
|
|
||||||
class="slider-content"
|
|
||||||
@wheel="changeVolume"
|
|
||||||
>
|
>
|
||||||
<n-slider
|
<div class="mode" @click.stop @dblclick.stop>
|
||||||
v-model:value="playVolume"
|
<n-icon size="22">
|
||||||
:tooltip="false"
|
<SvgIcon
|
||||||
:min="0"
|
:icon="
|
||||||
:max="1"
|
playHeartbeatMode
|
||||||
:step="0.01"
|
? 'heartbit'
|
||||||
vertical
|
: playSongMode === 'normal'
|
||||||
style="height: 120px"
|
? 'repeat-list'
|
||||||
@update:value="setVolume"
|
: playSongMode === 'random'
|
||||||
/>
|
? 'shuffle'
|
||||||
<n-text class="slider-num" depth="3">{{ (playVolume * 100).toFixed(0) }}%</n-text>
|
: 'repeat-song'
|
||||||
</div>
|
"
|
||||||
</n-popover>
|
isSpecial
|
||||||
<!-- 播放列表 -->
|
/>
|
||||||
<n-icon
|
</n-icon>
|
||||||
v-if="playMode === 'normal'"
|
</div>
|
||||||
class="playlist"
|
</n-dropdown>
|
||||||
size="22"
|
<!-- 倍速 -->
|
||||||
@click.stop="playListShow = !playListShow"
|
<n-popover :show-arrow="false" trigger="hover" placement="top-end" raw>
|
||||||
>
|
<template #trigger>
|
||||||
<SvgIcon icon="queue-music-rounded" />
|
<div class="speed" @click.stop="(playRate = 1), setRate(1)" @dblclick.stop>
|
||||||
</n-icon>
|
<n-icon v-if="playRate === 1" size="22">
|
||||||
</div>
|
<SvgIcon icon="speed-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
<n-text v-else class="speed-text">{{ playRate }}x</n-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 倍速调整 -->
|
||||||
|
<div class="slider-content">
|
||||||
|
<n-slider
|
||||||
|
v-model:value="playRate"
|
||||||
|
:tooltip="false"
|
||||||
|
:min="0.1"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
:marks="{
|
||||||
|
0.1: '减速',
|
||||||
|
1: '正常',
|
||||||
|
2: '加速',
|
||||||
|
}"
|
||||||
|
style="width: 220px"
|
||||||
|
@update:value="setRate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-popover>
|
||||||
|
<!-- 音量 -->
|
||||||
|
<n-popover trigger="hover" :show-arrow="false" raw>
|
||||||
|
<template #trigger>
|
||||||
|
<n-icon class="volume" size="22" @click.stop="setVolumeMute" @wheel="changeVolume">
|
||||||
|
<SvgIcon v-if="playVolume === 0" icon="no-sound-rounded" />
|
||||||
|
<SvgIcon
|
||||||
|
v-else-if="playVolume > 0 && playVolume < 0.4"
|
||||||
|
icon="volume-mute-rounded"
|
||||||
|
/>
|
||||||
|
<SvgIcon
|
||||||
|
v-else-if="playVolume >= 0.4 && playVolume < 0.7"
|
||||||
|
icon="volume-down-rounded"
|
||||||
|
/>
|
||||||
|
<SvgIcon v-else icon="volume-up-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
<!-- 音量调整 -->
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
padding: '10px 0',
|
||||||
|
width: '50px',
|
||||||
|
}"
|
||||||
|
class="slider-content"
|
||||||
|
@wheel="changeVolume"
|
||||||
|
>
|
||||||
|
<n-slider
|
||||||
|
v-model:value="playVolume"
|
||||||
|
:tooltip="false"
|
||||||
|
:min="0"
|
||||||
|
:max="1"
|
||||||
|
:step="0.01"
|
||||||
|
vertical
|
||||||
|
style="height: 120px"
|
||||||
|
@update:value="setVolume"
|
||||||
|
/>
|
||||||
|
<n-text class="slider-num" depth="3">{{ (playVolume * 100).toFixed(0) }}%</n-text>
|
||||||
|
</div>
|
||||||
|
</n-popover>
|
||||||
|
<!-- 播放列表 -->
|
||||||
|
<n-badge
|
||||||
|
v-if="playMode !== 'fm'"
|
||||||
|
:value="playList?.length ?? 0"
|
||||||
|
:show="showPlaylistCount"
|
||||||
|
:max="999"
|
||||||
|
:style="{
|
||||||
|
marginRight: showPlaylistCount ? '12px' : null,
|
||||||
|
}"
|
||||||
|
class="playlist"
|
||||||
|
>
|
||||||
|
<n-icon size="22" @click.stop="playListShow = !playListShow">
|
||||||
|
<SvgIcon icon="queue-music-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
</n-badge>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<!-- 添加到歌单 -->
|
<!-- 添加到歌单 -->
|
||||||
<AddPlaylist ref="addPlaylistRef" />
|
<AddPlaylist ref="addPlaylistRef" />
|
||||||
@@ -345,7 +361,7 @@ const {
|
|||||||
playSongLyric,
|
playSongLyric,
|
||||||
} = storeToRefs(music);
|
} = storeToRefs(music);
|
||||||
const { playLoading, playState, playListShow, showPlayBar, showFullPlayer } = storeToRefs(status);
|
const { playLoading, playState, playListShow, showPlayBar, showFullPlayer } = storeToRefs(status);
|
||||||
const { showYrc, bottomLyricShow, showSider } = storeToRefs(settings);
|
const { showYrc, bottomLyricShow, showSider, showPlaylistCount } = storeToRefs(settings);
|
||||||
|
|
||||||
// 子组件
|
// 子组件
|
||||||
const addPlaylistRef = ref(null);
|
const addPlaylistRef = ref(null);
|
||||||
@@ -450,6 +466,15 @@ const songTimeSliderUpdate = (val) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 开启播放器
|
||||||
|
const openFullPlayer = () => {
|
||||||
|
if (playMode.value === "dj") {
|
||||||
|
$message.warning("当前为电台模式,无法开启播放器");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
showFullPlayer.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
// 上下曲切换
|
// 上下曲切换
|
||||||
const changePlayIndexDebounce = debounce(async (type, id) => {
|
const changePlayIndexDebounce = debounce(async (type, id) => {
|
||||||
// 垃圾桶
|
// 垃圾桶
|
||||||
@@ -533,12 +558,12 @@ watch(
|
|||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
.vue-slider-process {
|
.vue-slider-process {
|
||||||
background-color: var(--main-color);
|
background-color: var(--main-color);
|
||||||
transition: none !important;
|
// transition: none !important;
|
||||||
}
|
}
|
||||||
.vue-slider-dot {
|
.vue-slider-dot {
|
||||||
width: 12px !important;
|
width: 12px !important;
|
||||||
height: 12px !important;
|
height: 12px !important;
|
||||||
transition: none !important;
|
// transition: none !important;
|
||||||
}
|
}
|
||||||
.vue-slider-dot-handle {
|
.vue-slider-dot-handle {
|
||||||
transition: box-shadow 0.3s;
|
transition: box-shadow 0.3s;
|
||||||
@@ -671,6 +696,7 @@ watch(
|
|||||||
.lrc-text {
|
.lrc-text {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
transition: opacity 0.1s ease-in-out;
|
||||||
.space {
|
.space {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
@@ -720,18 +746,7 @@ watch(
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
.time {
|
transition: opacity 0.1s;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-right: 4px;
|
|
||||||
.played {
|
|
||||||
&::after {
|
|
||||||
content: "/";
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.n-icon {
|
.n-icon {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@@ -753,6 +768,18 @@ watch(
|
|||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
.played {
|
||||||
|
&::after {
|
||||||
|
content: "/";
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.speed {
|
.speed {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -774,6 +801,19 @@ watch(
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.playlist {
|
||||||
|
transition: margin 0.3s;
|
||||||
|
&.count {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.n-badge-sup) {
|
||||||
|
background: var(--main-boxshadow-color);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
.n-base-slot-machine {
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.show-bar {
|
&.show-bar {
|
||||||
|
|||||||
@@ -346,12 +346,12 @@ const controlMove = (e) => {
|
|||||||
background-color: var(--cover-second-color);
|
background-color: var(--cover-second-color);
|
||||||
.vue-slider-process {
|
.vue-slider-process {
|
||||||
background-color: var(--cover-main-color);
|
background-color: var(--cover-main-color);
|
||||||
transition: none !important;
|
// transition: none !important;
|
||||||
}
|
}
|
||||||
.vue-slider-dot {
|
.vue-slider-dot {
|
||||||
width: 10px !important;
|
width: 10px !important;
|
||||||
height: 10px !important;
|
height: 10px !important;
|
||||||
transition: none !important;
|
// transition: none !important;
|
||||||
.vue-slider-dot-handle {
|
.vue-slider-dot-handle {
|
||||||
background-color: var(--cover-main-color);
|
background-color: var(--cover-main-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,12 @@
|
|||||||
<SvgIcon icon="account-music" />
|
<SvgIcon icon="account-music" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<div v-if="privateFmSong?.artists" class="all-ar">
|
<div v-if="privateFmSong?.artists" class="all-ar">
|
||||||
<span v-for="ar in privateFmSong.artists" :key="ar.id" class="ar">
|
<span
|
||||||
|
v-for="ar in privateFmSong.artists"
|
||||||
|
:key="ar.id"
|
||||||
|
class="ar"
|
||||||
|
@click.stop="router.push(`/artist?id=${ar.id}`)"
|
||||||
|
>
|
||||||
{{ ar.name }}
|
{{ ar.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const searchInpRef = ref(null);
|
|||||||
const searchInputValue = ref("");
|
const searchInputValue = ref("");
|
||||||
|
|
||||||
// 搜索框输入限制
|
// 搜索框输入限制
|
||||||
const noSideSpace = (value) => !value.startsWith(" ") && !value.endsWith(" ");
|
const noSideSpace = (value) => !value.startsWith(" ");
|
||||||
|
|
||||||
// 搜索框 focus
|
// 搜索框 focus
|
||||||
const searchInputFocus = () => {
|
const searchInputFocus = () => {
|
||||||
@@ -63,6 +63,7 @@ const searchInputFocus = () => {
|
|||||||
|
|
||||||
// 添加搜索历史
|
// 添加搜索历史
|
||||||
const setSearchHistory = (name) => {
|
const setSearchHistory = (name) => {
|
||||||
|
if (!name || !name?.trim()) return false;
|
||||||
const index = data.searchHistory.indexOf(name);
|
const index = data.searchHistory.indexOf(name);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
data.searchHistory.splice(index, 1);
|
data.searchHistory.splice(index, 1);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { checkPlatform } from "@/utils/helper";
|
import { checkPlatform } from "@/utils/helper";
|
||||||
import { isLogin } from "@/utils/auth";
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
// 首页
|
// 首页
|
||||||
@@ -9,7 +8,7 @@ const routes = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: "主页",
|
title: "主页",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/home.vue"),
|
component: () => import("@/views/Home.vue"),
|
||||||
},
|
},
|
||||||
// 搜索
|
// 搜索
|
||||||
{
|
{
|
||||||
@@ -46,6 +45,11 @@ const routes = [
|
|||||||
name: "sea-playlists",
|
name: "sea-playlists",
|
||||||
component: () => import("@/views/Search/playlists.vue"),
|
component: () => import("@/views/Search/playlists.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "djs",
|
||||||
|
name: "sea-djs",
|
||||||
|
component: () => import("@/views/Search/djs.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 发现音乐
|
// 发现音乐
|
||||||
@@ -87,7 +91,17 @@ const routes = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: "视频播放器",
|
title: "视频播放器",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/player.vue"),
|
component: () => import("@/views/Player.vue"),
|
||||||
|
},
|
||||||
|
// 每日推荐
|
||||||
|
{
|
||||||
|
path: "/daily-songs",
|
||||||
|
name: "daily-songs",
|
||||||
|
meta: {
|
||||||
|
title: "每日推荐",
|
||||||
|
needLogin: true,
|
||||||
|
},
|
||||||
|
component: () => import("@/views/DailySongs.vue"),
|
||||||
},
|
},
|
||||||
// 评论
|
// 评论
|
||||||
{
|
{
|
||||||
@@ -96,7 +110,7 @@ const routes = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: "评论",
|
title: "评论",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/comment.vue"),
|
component: () => import("@/views/Comment.vue"),
|
||||||
},
|
},
|
||||||
// 最近播放
|
// 最近播放
|
||||||
{
|
{
|
||||||
@@ -105,7 +119,7 @@ const routes = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: "最近播放",
|
title: "最近播放",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/history.vue"),
|
component: () => import("@/views/History.vue"),
|
||||||
},
|
},
|
||||||
// 我的云盘
|
// 我的云盘
|
||||||
{
|
{
|
||||||
@@ -113,17 +127,9 @@ const routes = [
|
|||||||
name: "cloud",
|
name: "cloud",
|
||||||
meta: {
|
meta: {
|
||||||
title: "我的云盘",
|
title: "我的云盘",
|
||||||
|
needLogin: true,
|
||||||
},
|
},
|
||||||
component: () => import("@/views/cloud.vue"),
|
component: () => import("@/views/Cloud.vue"),
|
||||||
beforeEnter: (_, __, next) => {
|
|
||||||
if (isLogin()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
if (typeof $changeLogin !== "undefined") $changeLogin();
|
|
||||||
$message.error("请登录后使用");
|
|
||||||
$loadingBar.error();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// 歌单
|
// 歌单
|
||||||
{
|
{
|
||||||
@@ -140,17 +146,9 @@ const routes = [
|
|||||||
name: "like-songs",
|
name: "like-songs",
|
||||||
meta: {
|
meta: {
|
||||||
title: "歌单",
|
title: "歌单",
|
||||||
|
needLogin: true,
|
||||||
},
|
},
|
||||||
component: () => import("@/views/List/playlist.vue"),
|
component: () => import("@/views/List/playlist.vue"),
|
||||||
beforeEnter: (_, __, next) => {
|
|
||||||
if (isLogin()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
if (typeof $changeLogin !== "undefined") $changeLogin();
|
|
||||||
$message.error("请登录后使用");
|
|
||||||
$loadingBar.error();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// 专辑
|
// 专辑
|
||||||
{
|
{
|
||||||
@@ -161,6 +159,15 @@ const routes = [
|
|||||||
},
|
},
|
||||||
component: () => import("@/views/List/album.vue"),
|
component: () => import("@/views/List/album.vue"),
|
||||||
},
|
},
|
||||||
|
// 播客电台
|
||||||
|
{
|
||||||
|
path: "/dj",
|
||||||
|
name: "dj",
|
||||||
|
meta: {
|
||||||
|
title: "播客电台",
|
||||||
|
},
|
||||||
|
component: () => import("@/views/List/dj.vue"),
|
||||||
|
},
|
||||||
// 歌手
|
// 歌手
|
||||||
{
|
{
|
||||||
path: "/artist",
|
path: "/artist",
|
||||||
@@ -199,17 +206,9 @@ const routes = [
|
|||||||
name: "like",
|
name: "like",
|
||||||
meta: {
|
meta: {
|
||||||
title: "我的收藏",
|
title: "我的收藏",
|
||||||
|
needLogin: true,
|
||||||
},
|
},
|
||||||
component: () => import("@/views/Like/index.vue"),
|
component: () => import("@/views/Like/index.vue"),
|
||||||
beforeEnter: (_, __, next) => {
|
|
||||||
if (isLogin()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
if (typeof $changeLogin !== "undefined") $changeLogin();
|
|
||||||
$message.error("请登录后使用");
|
|
||||||
$loadingBar.error();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
redirect: "/like/albums",
|
redirect: "/like/albums",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -232,6 +231,11 @@ const routes = [
|
|||||||
name: "like-playlists",
|
name: "like-playlists",
|
||||||
component: () => import("@/views/Like/playlists.vue"),
|
component: () => import("@/views/Like/playlists.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "djs",
|
||||||
|
name: "like-djs",
|
||||||
|
component: () => import("@/views/Like/djs.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 本地歌曲
|
// 本地歌曲
|
||||||
@@ -240,16 +244,10 @@ const routes = [
|
|||||||
name: "local",
|
name: "local",
|
||||||
meta: {
|
meta: {
|
||||||
title: "本地歌曲",
|
title: "本地歌曲",
|
||||||
|
needLogin: true,
|
||||||
show: checkPlatform.electron(),
|
show: checkPlatform.electron(),
|
||||||
},
|
},
|
||||||
component: () => import("@/views/Local/index.vue"),
|
component: () => import("@/views/Local/index.vue"),
|
||||||
beforeEnter: (to, from, next) => {
|
|
||||||
if (checkPlatform.electron()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
next("/403");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
redirect: "/local/songs",
|
redirect: "/local/songs",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -271,20 +269,21 @@ const routes = [
|
|||||||
},
|
},
|
||||||
// 播客
|
// 播客
|
||||||
{
|
{
|
||||||
path: "/record",
|
path: "/dj-hot",
|
||||||
name: "record",
|
name: "dj-hot",
|
||||||
meta: {
|
meta: {
|
||||||
title: "播客",
|
title: "热门播客",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/Record/index.vue"),
|
component: () => import("@/views/Dj/index.vue"),
|
||||||
redirect: "/record/hot",
|
},
|
||||||
children: [
|
// 播客 -分类
|
||||||
{
|
{
|
||||||
path: "hot",
|
path: "/dj-type",
|
||||||
name: "record-hot",
|
name: "dj-type",
|
||||||
component: () => import("@/views/Record/hot.vue"),
|
meta: {
|
||||||
},
|
title: "播客分类",
|
||||||
],
|
},
|
||||||
|
component: () => import("@/views/Dj/type.vue"),
|
||||||
},
|
},
|
||||||
// 全局设置
|
// 全局设置
|
||||||
{
|
{
|
||||||
@@ -302,7 +301,7 @@ const routes = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: "全局设置",
|
title: "全局设置",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/song.vue"),
|
component: () => import("@/views/Song.vue"),
|
||||||
},
|
},
|
||||||
// 测试页面
|
// 测试页面
|
||||||
{
|
{
|
||||||
@@ -311,7 +310,7 @@ const routes = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: "测试页面",
|
title: "测试页面",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/test.vue"),
|
component: () => import("@/views/Test.vue"),
|
||||||
},
|
},
|
||||||
// 状态页
|
// 状态页
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
// 当前模式
|
// 当前模式
|
||||||
// normal 正常 / fm 私人 FM
|
// normal 正常 / fm 私人 FM / dj 电台
|
||||||
playMode: "normal",
|
playMode: "normal",
|
||||||
// normal 顺序播放 / random 随机播放 / repeat 单曲循环
|
// normal 顺序播放 / random 随机播放 / repeat 单曲循环
|
||||||
playSongMode: "normal",
|
playSongMode: "normal",
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import {
|
|||||||
getUserArtist,
|
getUserArtist,
|
||||||
getUserAlbum,
|
getUserAlbum,
|
||||||
getUserMv,
|
getUserMv,
|
||||||
|
getUserDj,
|
||||||
} from "@/api/user";
|
} from "@/api/user";
|
||||||
import { isLogin } from "@/utils/auth";
|
import { isLogin } from "@/utils/auth";
|
||||||
|
import formatData from "@/utils/formatData";
|
||||||
import throttle from "@/utils/throttle";
|
import throttle from "@/utils/throttle";
|
||||||
|
|
||||||
const useSiteDataStore = defineStore("siteData", {
|
const useSiteDataStore = defineStore("siteData", {
|
||||||
@@ -34,6 +36,7 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
artists: [],
|
artists: [],
|
||||||
albums: [],
|
albums: [],
|
||||||
mvs: [],
|
mvs: [],
|
||||||
|
djs: [],
|
||||||
},
|
},
|
||||||
// 每日推荐
|
// 每日推荐
|
||||||
dailySongsData: {
|
dailySongsData: {
|
||||||
@@ -56,38 +59,31 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 获取每日推荐
|
// 获取每日推荐
|
||||||
async setDailySongsData() {
|
async setDailySongsData(refresh = false) {
|
||||||
try {
|
try {
|
||||||
if (!isLogin()) {
|
if (!isLogin()) {
|
||||||
this.dailySongsData = { timestamp: null, data: [] };
|
this.dailySongsData = { timestamp: null, data: [] };
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const data = this.dailySongsData.data;
|
const songsData = this.dailySongsData.data;
|
||||||
const timestamp = this.dailySongsData.timestamp;
|
const timestamp = this.dailySongsData.timestamp;
|
||||||
if (data[0] && timestamp) {
|
// 下一天六点
|
||||||
console.log("触发日推缓存");
|
const nextDay6AM = new Date(timestamp);
|
||||||
const currentTime = new Date().getTime();
|
nextDay6AM.setDate(nextDay6AM.getDate() + 1);
|
||||||
const storedTime = parseInt(timestamp, 10);
|
nextDay6AM.setHours(6, 0, 0, 0);
|
||||||
const nextDay6AM = new Date(storedTime);
|
// 是否小于今日 6:00
|
||||||
nextDay6AM.setHours(6, 0, 0, 0);
|
const originalHour = new Date(timestamp).getHours();
|
||||||
if (currentTime <= nextDay6AM.getTime()) {
|
const isAfter6AM =
|
||||||
return true;
|
new Date(timestamp).toDateString() === new Date().toDateString() && originalHour >= 6;
|
||||||
}
|
if (!refresh && songsData?.[0] && isAfter6AM && timestamp <= nextDay6AM.getTime()) {
|
||||||
|
console.log("日推缓存未过期,不更新");
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const res = await getDailyRec();
|
const res = await getDailyRec();
|
||||||
const data = res.data.dailySongs;
|
const songsData = formatData(res.data.dailySongs, "song");
|
||||||
const currentTime = new Date().getTime();
|
console.log("日推缓存不存在或已过期", songsData);
|
||||||
const formatData = data.map((v) => {
|
this.dailySongsData = { timestamp: new Date().getTime(), data: songsData };
|
||||||
return {
|
if (refresh) $message.success("日推更新成功");
|
||||||
id: v.id,
|
|
||||||
name: v.name,
|
|
||||||
artist: v.ar,
|
|
||||||
album: v.al,
|
|
||||||
cover: v.al.picUrl.replace(/^http:/, "https:"),
|
|
||||||
reason: v?.reason,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.dailySongsData = { timestamp: currentTime, data: formatData };
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error, "每日推荐加载失败");
|
showError(error, "每日推荐加载失败");
|
||||||
@@ -131,11 +127,11 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
this.setUserLikeArtists(),
|
this.setUserLikeArtists(),
|
||||||
this.setUserLikeAlbums(),
|
this.setUserLikeAlbums(),
|
||||||
this.setUserLikeMvs(),
|
this.setUserLikeMvs(),
|
||||||
|
this.setUserLikeDjs(),
|
||||||
];
|
];
|
||||||
await Promise.all(allUserLikeResult);
|
await Promise.all(allUserLikeResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户信息加载失败:", error);
|
showError(error, "用户信息加载失败");
|
||||||
$message.error("用户信息加载失败");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 获取用户喜欢歌曲
|
// 获取用户喜欢歌曲
|
||||||
@@ -147,8 +143,7 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
this.userLikeData.songs = res.ids;
|
this.userLikeData.songs = res.ids;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户喜欢歌曲加载失败:", error);
|
showError(error, "用户喜欢歌曲加载失败");
|
||||||
$message.error("用户喜欢歌曲加载失败");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 获取用户喜欢歌单
|
// 获取用户喜欢歌单
|
||||||
@@ -160,11 +155,10 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
const number = createdPlaylistCount + subPlaylistCount ?? 50;
|
const number = createdPlaylistCount + subPlaylistCount ?? 50;
|
||||||
// 获取数据
|
// 获取数据
|
||||||
getUserPlaylist(this.userData.userId, number).then((res) => {
|
getUserPlaylist(this.userData.userId, number).then((res) => {
|
||||||
this.userLikeData.playlists = res.playlist;
|
this.userLikeData.playlists = formatData(res.playlist);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户喜欢歌单加载失败:", error);
|
showError(error, "用户喜欢歌单加载失败");
|
||||||
$message.error("用户喜欢歌单加载失败");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改用户喜欢歌手
|
// 更改用户喜欢歌手
|
||||||
@@ -173,11 +167,10 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
if (!isLogin()) return false;
|
if (!isLogin()) return false;
|
||||||
// 获取数据
|
// 获取数据
|
||||||
getUserArtist().then((res) => {
|
getUserArtist().then((res) => {
|
||||||
this.userLikeData.artists = res.data;
|
this.userLikeData.artists = formatData(res.data, "artist");
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户喜欢歌手加载失败:", error);
|
showError(error, "用户喜欢歌手加载失败");
|
||||||
$message.error("用户喜欢歌手加载失败");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改用户喜欢专辑
|
// 更改用户喜欢专辑
|
||||||
@@ -191,15 +184,13 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
// 获取数据
|
// 获取数据
|
||||||
while (totalCount === null || offset < totalCount) {
|
while (totalCount === null || offset < totalCount) {
|
||||||
const res = await getUserAlbum(50, offset);
|
const res = await getUserAlbum(50, offset);
|
||||||
res.data.forEach((v) => {
|
const albumsData = formatData(res.data, "album");
|
||||||
this.userLikeData.albums.push(v);
|
this.userLikeData.albums = this.userLikeData.albums.concat(albumsData);
|
||||||
});
|
|
||||||
totalCount = res.count;
|
totalCount = res.count;
|
||||||
offset += 50;
|
offset += 50;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户喜欢专辑加载失败:", error);
|
showError(error, "用户喜欢专辑加载失败");
|
||||||
$message.error("用户喜欢专辑加载失败");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改用户喜欢视频
|
// 更改用户喜欢视频
|
||||||
@@ -208,11 +199,22 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
if (!isLogin()) return false;
|
if (!isLogin()) return false;
|
||||||
// 获取数据
|
// 获取数据
|
||||||
getUserMv().then((res) => {
|
getUserMv().then((res) => {
|
||||||
this.userLikeData.mvs = res.data;
|
this.userLikeData.mvs = formatData(res.data, "mv");
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户喜欢歌手加载失败:", error);
|
showError(error, "用户喜欢歌手加载失败");
|
||||||
$message.error("用户喜欢歌手加载失败");
|
}
|
||||||
|
},
|
||||||
|
// 更改用户喜欢电台
|
||||||
|
async setUserLikeDjs() {
|
||||||
|
try {
|
||||||
|
if (!isLogin()) return false;
|
||||||
|
// 获取数据
|
||||||
|
getUserDj().then((res) => {
|
||||||
|
this.userLikeData.djs = formatData(res.djRadios, "dj");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showError(error, "用户喜欢电台加载失败");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 查找歌曲是否处于喜欢列表
|
// 查找歌曲是否处于喜欢列表
|
||||||
@@ -236,7 +238,7 @@ const useSiteDataStore = defineStore("siteData", {
|
|||||||
// 输出错误
|
// 输出错误
|
||||||
const showError = (error, msg, show = true) => {
|
const showError = (error, msg, show = true) => {
|
||||||
console.error(msg, error);
|
console.error(msg, error);
|
||||||
if (show) $message.error(msg);
|
if (show && typeof $message !== "undefined") $message.error(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 移入移除喜欢列表
|
// 移入移除喜欢列表
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const useSiteSettingsStore = defineStore("siteSettings", {
|
|||||||
bottomLyricShow: true, // 底栏歌词显示
|
bottomLyricShow: true, // 底栏歌词显示
|
||||||
playerBackgroundType: "blur", // 播放器背景类别 animation 流动 / blur 模糊
|
playerBackgroundType: "blur", // 播放器背景类别 animation 流动 / blur 模糊
|
||||||
memorySeek: true, // 记忆上次播放位置
|
memorySeek: true, // 记忆上次播放位置
|
||||||
|
playSearch: false, // 是否播放全部搜索结果
|
||||||
|
showPlaylistCount: true, // 是否显示播放列表数量
|
||||||
// 数量部分
|
// 数量部分
|
||||||
loadSize: 50, // 每页加载数量
|
loadSize: 50, // 每页加载数量
|
||||||
// 歌词部分
|
// 歌词部分
|
||||||
|
|||||||
21
src/style/animate.scss
vendored
@@ -106,3 +106,24 @@
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fade-spacing {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
letter-spacing: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-down {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,3 +168,16 @@ body,
|
|||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// layout-toggle-bar
|
||||||
|
.n-layout-toggle-bar {
|
||||||
|
height: 44px !important;
|
||||||
|
top: calc(50% - 22px) !important;
|
||||||
|
.n-layout-toggle-bar__top,
|
||||||
|
.n-layout-toggle-bar__bottom {
|
||||||
|
height: 24px !important;
|
||||||
|
}
|
||||||
|
.n-layout-toggle-bar__bottom {
|
||||||
|
top: 20px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ export const initPlayer = async (playNow = false) => {
|
|||||||
const music = musicData();
|
const music = musicData();
|
||||||
const status = siteStatus();
|
const status = siteStatus();
|
||||||
const settings = siteSettings();
|
const settings = siteSettings();
|
||||||
const { playList, playIndex } = music;
|
const { playList, playIndex, playMode } = music;
|
||||||
// 当前播放歌曲数据
|
// 当前播放歌曲数据
|
||||||
const playSongData = music.getPlaySongData;
|
const playSongData = music.getPlaySongData;
|
||||||
|
// 若为电台则更改 id
|
||||||
|
playSongData.id = playMode === "dj" ? playSongData.mainTrackId : playSongData.id;
|
||||||
// 是否为本地歌曲
|
// 是否为本地歌曲
|
||||||
const isLocalSong = playSongData?.path ? true : false;
|
const isLocalSong = playSongData?.path ? true : false;
|
||||||
// 获取封面
|
// 获取封面
|
||||||
@@ -42,6 +44,8 @@ export const initPlayer = async (playNow = false) => {
|
|||||||
const cover = isLocalSong ? music.playSongData?.localCover : playSongData?.coverSize;
|
const cover = isLocalSong ? music.playSongData?.localCover : playSongData?.coverSize;
|
||||||
// 歌词归位
|
// 歌词归位
|
||||||
music.playSongLyricIndex = -1;
|
music.playSongLyricIndex = -1;
|
||||||
|
// 若为 fm 模式,则清除当前歌曲信息
|
||||||
|
if (playMode === "fm") music.playSongData = {};
|
||||||
// 在线歌曲
|
// 在线歌曲
|
||||||
if (!isLocalSong) {
|
if (!isLocalSong) {
|
||||||
// 获取歌曲信息
|
// 获取歌曲信息
|
||||||
@@ -57,7 +61,7 @@ export const initPlayer = async (playNow = false) => {
|
|||||||
createPlayer(url);
|
createPlayer(url);
|
||||||
}
|
}
|
||||||
// 无法正常获取播放地址
|
// 无法正常获取播放地址
|
||||||
else if (checkPlatform.electron() && settings.useUnmServer) {
|
else if (checkPlatform.electron() && playMode !== "dj" && settings.useUnmServer) {
|
||||||
const url = await getFromUnblockMusic(playSongData, status, playNow);
|
const url = await getFromUnblockMusic(playSongData, status, playNow);
|
||||||
if (url) {
|
if (url) {
|
||||||
status.playUseOtherSource = true;
|
status.playUseOtherSource = true;
|
||||||
@@ -98,9 +102,9 @@ export const initPlayer = async (playNow = false) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获取歌词
|
// 获取歌词
|
||||||
getSongLyricData(isLocalSong, playSongData);
|
if (playMode !== "dj") getSongLyricData(isLocalSong, playSongData);
|
||||||
// 初始化媒体会话控制
|
// 初始化媒体会话控制
|
||||||
initMediaSession(playSongData, isLocalSong, cover);
|
initMediaSession(playSongData, cover, isLocalSong, playMode === "dj");
|
||||||
// 获取图片主色
|
// 获取图片主色
|
||||||
getColorMainColor(isLocalSong, cover);
|
getColorMainColor(isLocalSong, cover);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -239,13 +243,14 @@ export const createPlayer = async (src, autoPlay = true) => {
|
|||||||
status.playLoading = false;
|
status.playLoading = false;
|
||||||
// 发送歌曲名
|
// 发送歌曲名
|
||||||
if (checkPlatform.electron()) {
|
if (checkPlatform.electron()) {
|
||||||
const songName =
|
const songName = playSongData.name || "未知曲目";
|
||||||
playSongData.name +
|
const songArtist =
|
||||||
" - " +
|
music.playMode === "dj"
|
||||||
(Array.isArray(playSongData.artists)
|
? "电台节目"
|
||||||
|
: Array.isArray(playSongData.artists)
|
||||||
? playSongData.artists.map((ar) => ar.name).join(" / ")
|
? playSongData.artists.map((ar) => ar.name).join(" / ")
|
||||||
: playSongData.artists || "未知歌手");
|
: playSongData.artists || "未知歌手";
|
||||||
electron.ipcRenderer.send("songNameChange", songName);
|
electron.ipcRenderer.send("songNameChange", songName + " - " + songArtist);
|
||||||
}
|
}
|
||||||
// 听歌打卡
|
// 听歌打卡
|
||||||
if (isLogin() && !playSongData?.path) {
|
if (isLogin() && !playSongData?.path) {
|
||||||
@@ -611,13 +616,17 @@ const getSongLyricData = async (islocal, data) => {
|
|||||||
* @param {string} islocal - 是否为本地歌曲
|
* @param {string} islocal - 是否为本地歌曲
|
||||||
* @param {string} cover - 封面图像的URL或数据
|
* @param {string} cover - 封面图像的URL或数据
|
||||||
*/
|
*/
|
||||||
const initMediaSession = async (data, islocal, cover) => {
|
const initMediaSession = async (data, cover, islocal, isDj) => {
|
||||||
if ("mediaSession" in navigator) {
|
if ("mediaSession" in navigator) {
|
||||||
// 歌曲信息
|
// 歌曲信息
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: data.name,
|
title: data.name,
|
||||||
artist: islocal ? data.artists : data.artists?.map((a) => a.name)?.join(" & "),
|
artist: isDj
|
||||||
album: islocal ? data.album : data.album.name,
|
? "电台节目"
|
||||||
|
: islocal
|
||||||
|
? data.artists
|
||||||
|
: data.artists?.map((a) => a.name)?.join(" & "),
|
||||||
|
album: isDj ? "电台节目" : islocal ? data.album : data.album.name,
|
||||||
artwork: islocal
|
artwork: islocal
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ export const isLogin = () => {
|
|||||||
/**
|
/**
|
||||||
* 退出用户登录
|
* 退出用户登录
|
||||||
*/
|
*/
|
||||||
export const toLogout = (show = true) => {
|
export const toLogout = async (show = true) => {
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
// 去除 cookie
|
// 去除 cookie
|
||||||
logOut();
|
await logOut();
|
||||||
removeCookie("MUSIC_U");
|
removeCookie("MUSIC_U");
|
||||||
removeCookie("__csrf");
|
removeCookie("__csrf");
|
||||||
|
sessionStorage.clear();
|
||||||
// 去除用户信息
|
// 去除用户信息
|
||||||
data.userLoginStatus = false;
|
data.userLoginStatus = false;
|
||||||
data.userData = {};
|
data.userData = {};
|
||||||
@@ -62,5 +63,9 @@ export const toLogout = (show = true) => {
|
|||||||
albums: [],
|
albums: [],
|
||||||
mvs: [],
|
mvs: [],
|
||||||
};
|
};
|
||||||
|
data.dailySongsData = {
|
||||||
|
timestamp: null,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
if (show) $message.success("成功退出登录");
|
if (show) $message.success("成功退出登录");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const formatData = (data, type = "playlist", noTracks = false) => {
|
|||||||
(v.picUrl ||
|
(v.picUrl ||
|
||||||
v.coverUrl ||
|
v.coverUrl ||
|
||||||
v.coverImgUrl ||
|
v.coverImgUrl ||
|
||||||
|
v.imgurl ||
|
||||||
v.cover ||
|
v.cover ||
|
||||||
(v.album && v.album.picUrl) ||
|
(v.album && v.album.picUrl) ||
|
||||||
(v.al && (v.al.picUrl || v.al.xInfo?.picUrl)));
|
(v.al && (v.al.picUrl || v.al.xInfo?.picUrl)));
|
||||||
@@ -108,6 +109,24 @@ const formatData = (data, type = "playlist", noTracks = false) => {
|
|||||||
duration: v.duration || v.durationms,
|
duration: v.duration || v.durationms,
|
||||||
playCount: v.playCount || v.playTime,
|
playCount: v.playCount || v.playTime,
|
||||||
};
|
};
|
||||||
|
// dj
|
||||||
|
case "dj":
|
||||||
|
return {
|
||||||
|
id: v.id || v.vid,
|
||||||
|
mainTrackId: v.mainTrackId,
|
||||||
|
name: v.name,
|
||||||
|
creator: v.dj,
|
||||||
|
count: v.programCount,
|
||||||
|
desc: v.copywriter || v.lastProgramName || v.desc,
|
||||||
|
cover,
|
||||||
|
coverSize,
|
||||||
|
tags: { id: v.categoryId, name: v.category },
|
||||||
|
rcmdText: v.rcmdtext || v.rcmdText,
|
||||||
|
playCount: v.playCount || v.listenerCount,
|
||||||
|
createTime: v.createTime,
|
||||||
|
updateTime: v.lastProgramCreateTime || v.scheduledPublishTime,
|
||||||
|
duration: getSongTime(v.duration),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ const globalEvents = (router) => {
|
|||||||
changePlayIndex(val, true);
|
changePlayIndex(val, true);
|
||||||
});
|
});
|
||||||
// 全局设置
|
// 全局设置
|
||||||
electron.ipcRenderer.on("setting", () => {
|
electron.ipcRenderer.on("open-setting", () => {
|
||||||
if (router) router.push("/setting");
|
if (router) router.push("/setting");
|
||||||
|
const status = siteStatus();
|
||||||
|
status.showFullPlayer = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,8 @@ export const getCacheData = async (key, time, request, params) => {
|
|||||||
* @returns {Promise<string>} 返回封面的 Blob URL,如果没有封面数据则返回默认 URL
|
* @returns {Promise<string>} 返回封面的 Blob URL,如果没有封面数据则返回默认 URL
|
||||||
*/
|
*/
|
||||||
export const getLocalCoverData = async (path, isAlbum = false) => {
|
export const getLocalCoverData = async (path, isAlbum = false) => {
|
||||||
let blobUrl = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let blobUrl = null;
|
||||||
const coverData = await electron.ipcRenderer.invoke("getMusicCover", path);
|
const coverData = await electron.ipcRenderer.invoke("getMusicCover", path);
|
||||||
if (coverData) {
|
if (coverData) {
|
||||||
// 将 Uint8Array 数据转换为 Blob
|
// 将 Uint8Array 数据转换为 Blob
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const parseLrc = (lyrics, isTrim = true) => {
|
|||||||
// 匹配时间轴和歌词文本的正则表达式
|
// 匹配时间轴和歌词文本的正则表达式
|
||||||
const regex = /^\[([^\]]+)\]\s*(.+?)\s*$/;
|
const regex = /^\[([^\]]+)\]\s*(.+?)\s*$/;
|
||||||
// 匹配歌曲信息的正则表达式
|
// 匹配歌曲信息的正则表达式
|
||||||
const infoRegex = /\].*[::-]/;
|
// const infoRegex = /\].*[::-]/;
|
||||||
// 将歌词字符串按行分割为数组
|
// 将歌词字符串按行分割为数组
|
||||||
const lines = lyrics.split("\n");
|
const lines = lyrics.split("\n");
|
||||||
// 对每一行进行转换
|
// 对每一行进行转换
|
||||||
@@ -102,7 +102,7 @@ const parseLrc = (lyrics, isTrim = true) => {
|
|||||||
// 转换时间轴和歌词文本为对象
|
// 转换时间轴和歌词文本为对象
|
||||||
.map((line) => {
|
.map((line) => {
|
||||||
// 过滤掉包含信息的文本
|
// 过滤掉包含信息的文本
|
||||||
if (infoRegex.test(line)) return null;
|
// if (infoRegex.test(line)) return null;
|
||||||
// 继续解析
|
// 继续解析
|
||||||
const [, time, text] = line.match(regex);
|
const [, time, text] = line.match(regex);
|
||||||
const parts = time.split(":");
|
const parts = time.split(":");
|
||||||
@@ -154,11 +154,11 @@ const parseYrc = (lyrics) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 去除歌曲信息
|
// 去除歌曲信息
|
||||||
const contentInfoReg = /\s*[^::\n]*[::]\s*.+/;
|
// const contentInfoReg = /\s*[^::\n]*[::]\s*.+/;
|
||||||
const contentFilter = content.replace(/\(\d+,\d+,\d+\)/g, "");
|
// const contentFilter = content.replace(/\(\d+,\d+,\d+\)/g, "");
|
||||||
if (!contentFilter || contentInfoReg.test(contentFilter)) {
|
// if (!contentFilter || contentInfoReg.test(contentFilter)) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
// 对歌词内容中的时间戳和歌词内容分离
|
// 对歌词内容中的时间戳和歌词内容分离
|
||||||
const contentArray = content
|
const contentArray = content
|
||||||
.split(/(\([1-9]\d*,[1-9]\d*,\d*\)[^(]*)/g)
|
.split(/(\([1-9]\d*,[1-9]\d*,\d*\)[^(]*)/g)
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ axios.interceptors.response.use(
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// 处理其他状态码或错误条件
|
// 处理其他状态码或错误条件
|
||||||
$canNotConnect(error);
|
|
||||||
console.error("未处理的错误:", error.message);
|
console.error("未处理的错误:", error.message);
|
||||||
}
|
}
|
||||||
// 继续传递错误
|
// 继续传递错误
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ export const getSongTime = (mss) => {
|
|||||||
* @param {number} mss - 时间戳
|
* @param {number} mss - 时间戳
|
||||||
* @returns {string} - 日期字符串
|
* @returns {string} - 日期字符串
|
||||||
*/
|
*/
|
||||||
export const getTimestampTime = (mss) => {
|
export const getTimestampTime = (mss, showYear = true) => {
|
||||||
const date = new Date(parseInt(mss));
|
const date = new Date(parseInt(mss));
|
||||||
const y = date.getFullYear();
|
const y = date.getFullYear();
|
||||||
const m = `0${date.getMonth() + 1}`.slice(-2);
|
const m = `0${date.getMonth() + 1}`.slice(-2);
|
||||||
const d = `0${date.getDate()}`.slice(-2);
|
const d = `0${date.getDate()}`.slice(-2);
|
||||||
return `${y}-${m}-${d}`;
|
return showYear ? `${y}-${m}-${d}` : `${m}-${d}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
47
src/utils/userSignIn.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { userDailySignin } from "@/api/user";
|
||||||
|
import { siteSettings } from "@/stores";
|
||||||
|
import { isLogin } from "@/utils/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户签到
|
||||||
|
* https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1387
|
||||||
|
* 云贝签到本质上就是 Android 客户端每日签到
|
||||||
|
*/
|
||||||
|
const userSignIn = async () => {
|
||||||
|
const settings = siteSettings();
|
||||||
|
try {
|
||||||
|
if (!isLogin()) return false;
|
||||||
|
const today = new Date().toLocaleDateString();
|
||||||
|
const lastSignInDate = sessionStorage.getItem("lastSignInDate");
|
||||||
|
if (lastSignInDate !== today) {
|
||||||
|
const result = await userDailySignin(1);
|
||||||
|
console.log("签到结果:", result);
|
||||||
|
sessionStorage.setItem("lastSignInDate", today);
|
||||||
|
if (result.status === 400) {
|
||||||
|
return console.log("重复签到");
|
||||||
|
}
|
||||||
|
$notification["success"]({
|
||||||
|
content: "签到通知",
|
||||||
|
meta: "🎉 每日签到成功",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("今日已签到");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.request.status === 400) {
|
||||||
|
console.log("重复签到");
|
||||||
|
sessionStorage.setItem("lastSignInDate", new Date().toLocaleDateString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
settings.autoSignIn = false;
|
||||||
|
console.error("签到过程中发生错误:", error);
|
||||||
|
$notification["error"]({
|
||||||
|
content: "签到通知",
|
||||||
|
meta: "签到过程中发生错误,已关闭自动签到,详细信息可查看控制台输出,请及时向开发者报告",
|
||||||
|
duration: 8000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default userSignIn;
|
||||||
@@ -58,20 +58,25 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<SongList v-if="!searchValue" :data="userCloudData" :showPrivilege="false" />
|
<div v-if="userCloudData !== 'empty'" class="list">
|
||||||
<SongList v-else-if="searchData?.length" :data="searchData" :showPrivilege="false" />
|
<Transition name="fade" mode="out-in">
|
||||||
<n-empty
|
<SongList v-if="!searchValue" :data="userCloudData" :showPrivilege="false" />
|
||||||
v-else
|
<SongList v-else-if="searchData?.length" :data="searchData" :showPrivilege="false" />
|
||||||
:description="`搜不到关于 ${searchValue} 的任何歌曲呀`"
|
<n-empty
|
||||||
style="margin-top: 60px"
|
v-else
|
||||||
size="large"
|
:description="`搜不到关于 ${searchValue} 的任何歌曲呀`"
|
||||||
>
|
style="margin-top: 60px"
|
||||||
<template #icon>
|
size="large"
|
||||||
<n-icon>
|
>
|
||||||
<SvgIcon icon="search-off" />
|
<template #icon>
|
||||||
</n-icon>
|
<n-icon>
|
||||||
</template>
|
<SvgIcon icon="search-off" />
|
||||||
</n-empty>
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-empty>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<n-empty v-else class="empty" description="你还有任何歌曲,快去上传吧" />
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -87,7 +92,7 @@ import formatData from "@/utils/formatData";
|
|||||||
|
|
||||||
const music = musicData();
|
const music = musicData();
|
||||||
const indexedDB = indexedDBData();
|
const indexedDB = indexedDBData();
|
||||||
const { playList, playIndex, playSongData, playHeartbeatMode } = storeToRefs(music);
|
const { playList, playIndex, playSongData, playHeartbeatMode, playMode } = storeToRefs(music);
|
||||||
|
|
||||||
// 云盘数据
|
// 云盘数据
|
||||||
const userCloudSpace = ref([]);
|
const userCloudSpace = ref([]);
|
||||||
@@ -125,6 +130,11 @@ const getUserCloudData = async (isOnce = false) => {
|
|||||||
(res.size / Math.pow(1024, 3)).toFixed(2),
|
(res.size / Math.pow(1024, 3)).toFixed(2),
|
||||||
(res.maxSize / Math.pow(1024, 3)).toFixed(0),
|
(res.maxSize / Math.pow(1024, 3)).toFixed(0),
|
||||||
];
|
];
|
||||||
|
if (res.count === 0) {
|
||||||
|
console.log("云盘为空");
|
||||||
|
userCloudData.value = "empty";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isOnce) break;
|
if (isOnce) break;
|
||||||
}
|
}
|
||||||
// 展平并赋值
|
// 展平并赋值
|
||||||
@@ -155,18 +165,21 @@ const playAllSongs = async () => {
|
|||||||
if (!userCloudData.value || !Object.keys(userCloudData.value).length) return false;
|
if (!userCloudData.value || !Object.keys(userCloudData.value).length) return false;
|
||||||
// 关闭心动模式
|
// 关闭心动模式
|
||||||
playHeartbeatMode.value = false;
|
playHeartbeatMode.value = false;
|
||||||
|
// 更改模式和歌单
|
||||||
|
playMode.value = "normal";
|
||||||
|
playList.value = userCloudData.value.slice();
|
||||||
// 是否处于歌单内
|
// 是否处于歌单内
|
||||||
const songId = playSongData.value?.id;
|
const songId = playSongData.value?.id;
|
||||||
const isHas = userCloudData.value.some((song) => song.id === songId);
|
const existingIndex = userCloudData.value.findIndex((song) => song.id === songId);
|
||||||
// 若不处于
|
// 若不处于
|
||||||
if (!isHas) {
|
if (existingIndex === -1 || !songId) {
|
||||||
playList.value = userCloudData.value;
|
|
||||||
playSongData.value = userCloudData.value[0];
|
playSongData.value = userCloudData.value[0];
|
||||||
playIndex.value = 0;
|
playIndex.value = 0;
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
initPlayer(true);
|
await initPlayer(true);
|
||||||
} else {
|
} else {
|
||||||
// 播放
|
playSongData.value = userCloudData.value[existingIndex];
|
||||||
|
playIndex.value = existingIndex;
|
||||||
fadePlayOrPause();
|
fadePlayOrPause();
|
||||||
}
|
}
|
||||||
$message.info("已开始播放", { showIcon: false });
|
$message.info("已开始播放", { showIcon: false });
|
||||||
@@ -177,9 +190,9 @@ const goBuy = () => {
|
|||||||
window.open("https://music.163.com/#/store/product/detail?id=34001");
|
window.open("https://music.163.com/#/store/product/detail?id=34001");
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onMounted(async () => {
|
||||||
getUserCloudDataList();
|
await getUserCloudDataList();
|
||||||
getUserCloudData();
|
await getUserCloudData();
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
151
src/views/DailySongs.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<!-- 每日推荐 -->
|
||||||
|
<template>
|
||||||
|
<div class="daily-songs">
|
||||||
|
<div class="title">
|
||||||
|
<n-text class="name">每日推荐</n-text>
|
||||||
|
<div class="tip">
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<n-text :key="showTime" depth="3">
|
||||||
|
根据你的音乐口味 ·
|
||||||
|
{{ showTime && updatedTime ? "更新于 " + updatedTime : "每日 6:00 更新" }}
|
||||||
|
</n-text>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<!-- 操作 -->
|
||||||
|
<n-space class="control">
|
||||||
|
<n-button size="large" tag="div" round strong secondary @click="playAllSongs">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<SvgIcon icon="play-arrow-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
播放全部
|
||||||
|
</n-button>
|
||||||
|
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
|
||||||
|
<n-button size="large" tag="div" circle strong secondary>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<SvgIcon icon="format-list-bulleted" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
<!-- 列表 -->
|
||||||
|
<SongList :data="dailySongsData.data" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { NIcon } from "naive-ui";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { musicData, siteData } from "@/stores";
|
||||||
|
import { fadePlayOrPause, initPlayer } from "@/utils/Player";
|
||||||
|
import SvgIcon from "@/components/Global/SvgIcon";
|
||||||
|
|
||||||
|
const data = siteData();
|
||||||
|
const music = musicData();
|
||||||
|
const { dailySongsData } = storeToRefs(data);
|
||||||
|
const { playList, playIndex, playSongData, playHeartbeatMode, playMode } = storeToRefs(music);
|
||||||
|
|
||||||
|
const showTime = ref(false);
|
||||||
|
const showTimeOut = ref(null);
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => h(NIcon, null, { default: () => h(SvgIcon, { icon }, null) });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取更新时间戳
|
||||||
|
const updatedTime = computed(() => {
|
||||||
|
const timestamp = dailySongsData.value.timestamp;
|
||||||
|
if (!timestamp) return null;
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||||
|
const day = date.getDate().toString().padStart(2, "0");
|
||||||
|
const hours = date.getHours().toString().padStart(2, "0");
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||||
|
return `${month}/${day} ${hours}:${minutes}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更多操作数据
|
||||||
|
const moreOptions = computed(() => [
|
||||||
|
{
|
||||||
|
label: "更新日推",
|
||||||
|
key: "refresh",
|
||||||
|
props: {
|
||||||
|
onclick: async () => {
|
||||||
|
await data.setDailySongsData(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon("refresh"),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 播放歌单全部歌曲
|
||||||
|
const playAllSongs = async () => {
|
||||||
|
if (!dailySongsData.value.data) return false;
|
||||||
|
// 关闭心动模式
|
||||||
|
playHeartbeatMode.value = false;
|
||||||
|
// 更改模式和歌单
|
||||||
|
playMode.value = "normal";
|
||||||
|
playList.value = dailySongsData.value.data.slice();
|
||||||
|
// 是否处于歌单内
|
||||||
|
const songId = music.getPlaySongData?.id;
|
||||||
|
const existingIndex = dailySongsData.value.data.findIndex((song) => song.id === songId);
|
||||||
|
// 若不处于
|
||||||
|
if (existingIndex === -1 || !songId) {
|
||||||
|
console.log("不在歌单内");
|
||||||
|
playSongData.value = dailySongsData.value.data[0];
|
||||||
|
playIndex.value = 0;
|
||||||
|
// 初始化播放器
|
||||||
|
await initPlayer(true);
|
||||||
|
} else {
|
||||||
|
console.log("处于歌单内");
|
||||||
|
playSongData.value = dailySongsData.value.data[existingIndex];
|
||||||
|
playIndex.value = existingIndex;
|
||||||
|
// 播放
|
||||||
|
fadePlayOrPause();
|
||||||
|
}
|
||||||
|
$message.info("已开始播放", { showIcon: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
showTimeOut.value = setTimeout(() => {
|
||||||
|
showTime.value = true;
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearTimeout(showTimeOut.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.daily-songs {
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 30vh;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.name {
|
||||||
|
font-size: 55px;
|
||||||
|
font-weight: bold;
|
||||||
|
animation: fade-spacing 1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 6px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-down 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
animation-delay: 0.5s;
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
176
src/views/Dj/index.vue
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<!-- 播客 / 电台 -->
|
||||||
|
<template>
|
||||||
|
<div class="dj">
|
||||||
|
<!-- 分类 -->
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<n-grid
|
||||||
|
v-if="djCatData"
|
||||||
|
:collapsed="gridCollapsed"
|
||||||
|
class="dj-cat"
|
||||||
|
x-gap="20"
|
||||||
|
y-gap="20"
|
||||||
|
cols="3 s:4 m:5 l:6 xl:7"
|
||||||
|
responsive="screen"
|
||||||
|
>
|
||||||
|
<n-gi v-for="(item, index) in djCatData" :key="index">
|
||||||
|
<n-card
|
||||||
|
class="cat"
|
||||||
|
hoverable
|
||||||
|
@click="
|
||||||
|
router.push({
|
||||||
|
path: '/dj-type',
|
||||||
|
query: {
|
||||||
|
type: item.id,
|
||||||
|
name: item.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<n-text>{{ item.name }}</n-text>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi class="suffix" suffix #="{ overflow }">
|
||||||
|
<n-card class="cat" hoverable @click="gridCollapsed = !gridCollapsed">
|
||||||
|
<n-icon size="24" depth="3">
|
||||||
|
<SvgIcon :icon="overflow ? 'chevron-down' : 'chevron-up'" />
|
||||||
|
</n-icon>
|
||||||
|
<n-text>{{ overflow ? "查看全部" : "收起标签" }}</n-text>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
<n-grid
|
||||||
|
v-else
|
||||||
|
class="dj-cat"
|
||||||
|
x-gap="20"
|
||||||
|
y-gap="20"
|
||||||
|
cols="2 s:3 m:4 l:5 xl:6"
|
||||||
|
responsive="screen"
|
||||||
|
collapsed
|
||||||
|
>
|
||||||
|
<n-gi v-for="item in 20" :key="item">
|
||||||
|
<n-card class="cat" hoverable>
|
||||||
|
<n-skeleton style="height: 20px" text round />
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</Transition>
|
||||||
|
<!-- 推荐 -->
|
||||||
|
<div class="recommend">
|
||||||
|
<!-- 热门推荐 -->
|
||||||
|
<n-h3 class="title" prefix="bar">
|
||||||
|
<n-text class="name">热门推荐</n-text>
|
||||||
|
</n-h3>
|
||||||
|
<MainCover :data="djRecommendData" type="dj" />
|
||||||
|
<!-- 分类推荐 -->
|
||||||
|
<n-grid x-gap="20" y-gap="20" :cols="2">
|
||||||
|
<n-gi>
|
||||||
|
<div class="light-green" />
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
<div v-for="(item, index) in djCatRecData" :key="index" class="type">
|
||||||
|
<n-h3 class="title" prefix="bar" @click="router.push(`/dj-type?type=${item.categoryId}`)">
|
||||||
|
<n-text class="name">{{ item.categoryName }}</n-text>
|
||||||
|
<n-icon class="more" depth="3">
|
||||||
|
<SvgIcon icon="chevron-right" />
|
||||||
|
</n-icon>
|
||||||
|
</n-h3>
|
||||||
|
<MainCover :data="formatData(item.radios, 'dj')" type="dj" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getDjCatelist, getDjRecommend, getDjCategoryRec } from "@/api/dj";
|
||||||
|
import { getCacheData } from "@/utils/helper";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import formatData from "@/utils/formatData";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 栅格数据
|
||||||
|
const gridCollapsed = ref(true);
|
||||||
|
|
||||||
|
// 电台数据
|
||||||
|
const djCatData = ref(null);
|
||||||
|
const djRecommendData = ref(null);
|
||||||
|
const djCatRecData = ref(null);
|
||||||
|
|
||||||
|
// 获取电台分类
|
||||||
|
const getDjCatelistData = async () => {
|
||||||
|
try {
|
||||||
|
const result = await getCacheData("djCat", 30, getDjCatelist);
|
||||||
|
// 解构赋值
|
||||||
|
djCatData.value = result.categories.map(({ name, id }) => ({ name, id }));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取电台分类出现错误", error);
|
||||||
|
$message.error("电台分类获取失败,请重试");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取推荐电台
|
||||||
|
const getDjRecommendData = async () => {
|
||||||
|
try {
|
||||||
|
const [recRes, catRecRes] = await Promise.all([getDjRecommend(), getDjCategoryRec()]);
|
||||||
|
console.log(recRes, catRecRes);
|
||||||
|
djRecommendData.value = formatData(recRes.djRadios, "dj");
|
||||||
|
djCatRecData.value = catRecRes.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取推荐电台出错:", error);
|
||||||
|
$message.error("获取推荐电台出现错误");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
await getDjCatelistData();
|
||||||
|
await getDjRecommendData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dj {
|
||||||
|
.dj-cat {
|
||||||
|
.cat {
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
:deep(.n-card__content) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.recommend {
|
||||||
|
margin-top: 20px;
|
||||||
|
.title {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-top: 28px;
|
||||||
|
padding-left: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
.more {
|
||||||
|
font-size: 26px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(4px);
|
||||||
|
transition:
|
||||||
|
opacity 0.3s,
|
||||||
|
transform 0.3s;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.more {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
78
src/views/Dj/type.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!-- 播客 - 分类 -->
|
||||||
|
<template>
|
||||||
|
<div class="dj-type">
|
||||||
|
<div class="type-title">
|
||||||
|
<n-button class="back" secondary strong round @click="router.push('/dj-hot')">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon size="24" depth="3">
|
||||||
|
<SvgIcon icon="chevron-left" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
返回全部
|
||||||
|
</n-button>
|
||||||
|
<n-h1 class="title">{{ djName }}</n-h1>
|
||||||
|
</div>
|
||||||
|
<!-- 分类 -->
|
||||||
|
<n-tabs class="tabs" type="segment" animated>
|
||||||
|
<!-- 类别热门 -->
|
||||||
|
<n-tab-pane name="type-hot" tab="热门" display-directive="show">
|
||||||
|
<MainCover :data="djHotData" type="dj" />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="type-rec" tab="推荐" display-directive="show">
|
||||||
|
<MainCover :data="djRecData" type="dj" />
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { getRadioHot, getRecType } from "@/api/dj";
|
||||||
|
import formatData from "@/utils/formatData";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 电台数据
|
||||||
|
const djId = ref(router.currentRoute.value.query.type);
|
||||||
|
const djName = ref(router.currentRoute.value.query.name || "未知分类");
|
||||||
|
const djHotData = ref(null);
|
||||||
|
const djRecData = ref(null);
|
||||||
|
|
||||||
|
// 获取电台分类数据
|
||||||
|
const getRadioTypeData = async (id) => {
|
||||||
|
try {
|
||||||
|
const [recHot, recRec] = await Promise.all([getRadioHot(id), getRecType(id)]);
|
||||||
|
console.log(recHot, recRec);
|
||||||
|
// 热门数据
|
||||||
|
djHotData.value = formatData(recHot.djRadios, "dj");
|
||||||
|
// 推荐数据
|
||||||
|
djRecData.value = formatData(recRec.djRadios, "dj");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取电台分类出错:", error);
|
||||||
|
$message.error("获取电台分类出现错误");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getRadioTypeData(djId.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dj-type {
|
||||||
|
.title {
|
||||||
|
margin: 16px 0 20px 0;
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: bold;
|
||||||
|
.back {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
:deep(.n-tab-pane) {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,14 +8,6 @@
|
|||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="historyPlaylist?.length" class="list">
|
<div v-if="historyPlaylist?.length" class="list">
|
||||||
<n-space class="menu">
|
<n-space class="menu">
|
||||||
<n-button type="primary" round strong secondary @click="playAllSongs">
|
|
||||||
<template #icon>
|
|
||||||
<n-icon>
|
|
||||||
<SvgIcon icon="play-arrow-rounded" />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
播放全部
|
|
||||||
</n-button>
|
|
||||||
<n-button round strong secondary @click="cleanHistory">
|
<n-button round strong secondary @click="cleanHistory">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon>
|
<n-icon>
|
||||||
@@ -38,19 +30,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { musicData } from "@/stores";
|
import { musicData } from "@/stores";
|
||||||
import { fadePlayOrPause } from "@/utils/Player";
|
|
||||||
|
|
||||||
const music = musicData();
|
const music = musicData();
|
||||||
const { playList, historyPlaylist, playIndex } = storeToRefs(music);
|
const { historyPlaylist } = storeToRefs(music);
|
||||||
|
|
||||||
// 播放全部
|
|
||||||
const playAllSongs = async () => {
|
|
||||||
console.log(historyPlaylist.value);
|
|
||||||
playList.value = historyPlaylist.value;
|
|
||||||
playIndex.value = 0;
|
|
||||||
fadePlayOrPause();
|
|
||||||
$message.info("已开始播放", { showIcon: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 清除播放历史
|
// 清除播放历史
|
||||||
const cleanHistory = () => {
|
const cleanHistory = () => {
|
||||||
@@ -54,13 +54,15 @@ import { storeToRefs } from "pinia";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getGreetings } from "@/utils/timeTools";
|
import { getGreetings } from "@/utils/timeTools";
|
||||||
import { getDailyRec, getPersonalized, getTopArtists, getNewAlbum } from "@/api/recommend";
|
import { getDailyRec, getPersonalized, getTopArtists, getNewAlbum } from "@/api/recommend";
|
||||||
import { siteData } from "@/stores";
|
import { siteData, siteSettings } from "@/stores";
|
||||||
import { getCacheData } from "@/utils/helper";
|
import { getCacheData } from "@/utils/helper";
|
||||||
import { isLogin } from "@/utils/auth";
|
import { isLogin } from "@/utils/auth";
|
||||||
import formatData from "@/utils/formatData";
|
import formatData from "@/utils/formatData";
|
||||||
|
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const settings = siteSettings();
|
||||||
|
const { showSider } = storeToRefs(settings);
|
||||||
const { userLikeData, dailySongsData } = storeToRefs(data);
|
const { userLikeData, dailySongsData } = storeToRefs(data);
|
||||||
|
|
||||||
// 每日推荐
|
// 每日推荐
|
||||||
@@ -71,7 +73,8 @@ const dailySongsCoverData = computed(() => {
|
|||||||
};
|
};
|
||||||
if (isLogin() && dailySongsData.value.data?.length) {
|
if (isLogin() && dailySongsData.value.data?.length) {
|
||||||
const randomIndex = Math.floor(Math.random() * dailySongsData.value.data.length);
|
const randomIndex = Math.floor(Math.random() * dailySongsData.value.data.length);
|
||||||
dailySongsCover.cover = dailySongsData.value.data[randomIndex]?.cover + "?param=100y100";
|
dailySongsCover.cover =
|
||||||
|
dailySongsData.value.data[randomIndex]?.coverSize?.s || "/images/pic/like.jpg";
|
||||||
return dailySongsCover;
|
return dailySongsCover;
|
||||||
}
|
}
|
||||||
dailySongsCover.cover = "/images/pic/cover-2.jpg";
|
dailySongsCover.cover = "/images/pic/cover-2.jpg";
|
||||||
@@ -85,8 +88,7 @@ const likeSongsCoverData = computed(() => {
|
|||||||
desc: "发现你独特的音乐品味",
|
desc: "发现你独特的音乐品味",
|
||||||
};
|
};
|
||||||
if (isLogin() && userLikeData.value.playlists?.length) {
|
if (isLogin() && userLikeData.value.playlists?.length) {
|
||||||
const coverUrl = userLikeData.value.playlists[0].coverImgUrl;
|
likeSongsCover.cover = userLikeData.value.playlists[0]?.coverSize?.s || "/images/pic/like.jpg";
|
||||||
likeSongsCover.cover = coverUrl + "?param=100y100";
|
|
||||||
return likeSongsCover;
|
return likeSongsCover;
|
||||||
}
|
}
|
||||||
likeSongsCover.cover = "/images/pic/like.jpg";
|
likeSongsCover.cover = "/images/pic/like.jpg";
|
||||||
@@ -98,13 +100,14 @@ const recommendData = ref({
|
|||||||
playlist: {
|
playlist: {
|
||||||
name: "推荐歌单",
|
name: "推荐歌单",
|
||||||
loadingNum: 12,
|
loadingNum: 12,
|
||||||
|
columns: showSider.value ? undefined : "2 s:3 m:4 l:5 xl:6",
|
||||||
data: [],
|
data: [],
|
||||||
to: "/discover/playlists",
|
to: "/discover/playlists",
|
||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
name: "歌手推荐",
|
name: "歌手推荐",
|
||||||
type: "artist",
|
type: "artist",
|
||||||
columns: "3 s:4 m:5 l:6",
|
columns: showSider.value ? "3 s:4 m:5 l:6" : "3 sm:4 m:5 l:6",
|
||||||
loadingNum: 6,
|
loadingNum: 6,
|
||||||
data: [],
|
data: [],
|
||||||
to: "/discover/artists",
|
to: "/discover/artists",
|
||||||
@@ -177,7 +180,7 @@ const jumpPage = (key, id) => {
|
|||||||
router.push("/like-songs");
|
router.push("/like-songs");
|
||||||
break;
|
break;
|
||||||
case "daily-songs":
|
case "daily-songs":
|
||||||
$message.warning("施工中( 新建文件夹 )");
|
router.push("/daily-songs");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="userLikeData.albums?.length" class="list">
|
<div v-if="userLikeData.albums?.length" class="list">
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<MainCover :data="likeAlbumsData" type="album" />
|
<MainCover :data="userLikeData.albums" type="album" />
|
||||||
</div>
|
</div>
|
||||||
<n-empty
|
<n-empty
|
||||||
v-else
|
v-else
|
||||||
@@ -19,13 +19,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { siteData } from "@/stores";
|
import { siteData } from "@/stores";
|
||||||
import formatData from "@/utils/formatData";
|
|
||||||
|
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
const { userLikeData } = storeToRefs(data);
|
const { userLikeData } = storeToRefs(data);
|
||||||
|
|
||||||
// 处理专辑数据
|
|
||||||
const likeAlbumsData = computed(() => {
|
|
||||||
return formatData(userLikeData.value.albums, "album");
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="userLikeData.artists?.length" class="list">
|
<div v-if="userLikeData.artists?.length" class="list">
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<MainCover :data="likeArtistsData" type="artist" columns="3 s:4 m:5 l:6" />
|
<MainCover :data="userLikeData.artists" type="artist" columns="3 s:4 m:5 l:6" />
|
||||||
</div>
|
</div>
|
||||||
<n-empty
|
<n-empty
|
||||||
v-else
|
v-else
|
||||||
@@ -19,13 +19,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { siteData } from "@/stores";
|
import { siteData } from "@/stores";
|
||||||
import formatData from "@/utils/formatData";
|
|
||||||
|
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
const { userLikeData } = storeToRefs(data);
|
const { userLikeData } = storeToRefs(data);
|
||||||
|
|
||||||
// 处理歌手数据
|
|
||||||
const likeArtistsData = computed(() => {
|
|
||||||
return formatData(userLikeData.value.artists, "artist");
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
25
src/views/Like/djs.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="like-videos">
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div v-if="userLikeData.djs?.length" class="list">
|
||||||
|
<!-- 列表 -->
|
||||||
|
<MainCover :data="userLikeData.djs" type="dj" />
|
||||||
|
</div>
|
||||||
|
<n-empty
|
||||||
|
v-else
|
||||||
|
description="当前暂无收藏电台"
|
||||||
|
class="tip"
|
||||||
|
style="margin-top: 60px"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { siteData } from "@/stores";
|
||||||
|
|
||||||
|
const data = siteData();
|
||||||
|
const { userLikeData } = storeToRefs(data);
|
||||||
|
</script>
|
||||||
@@ -1,12 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="like">
|
<div class="like">
|
||||||
<n-h1 class="title">我的收藏</n-h1>
|
<n-h1 class="title">我的收藏</n-h1>
|
||||||
|
<!-- 数据统计 -->
|
||||||
|
<div class="num">
|
||||||
|
<!-- 专辑 -->
|
||||||
|
<div class="num-item">
|
||||||
|
<n-icon size="18">
|
||||||
|
<SvgIcon icon="album" />
|
||||||
|
</n-icon>
|
||||||
|
<n-number-animation :from="0" :to="userLikeData.albums?.length ?? 0" />
|
||||||
|
张专辑
|
||||||
|
</div>
|
||||||
|
<!-- 歌单 -->
|
||||||
|
<div class="num-item">
|
||||||
|
<n-icon size="18">
|
||||||
|
<SvgIcon icon="queue-music-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
<n-number-animation :from="0" :to="userLikeData.playlists?.length ?? 0" />
|
||||||
|
个歌单
|
||||||
|
</div>
|
||||||
|
<!-- 歌手 -->
|
||||||
|
<div class="num-item">
|
||||||
|
<n-icon size="18">
|
||||||
|
<SvgIcon icon="account-music" />
|
||||||
|
</n-icon>
|
||||||
|
<n-number-animation :from="0" :to="userLikeData.artists?.length ?? 0" />
|
||||||
|
位歌手
|
||||||
|
</div>
|
||||||
|
<!-- 视频 -->
|
||||||
|
<div class="num-item">
|
||||||
|
<n-icon size="18">
|
||||||
|
<SvgIcon icon="video" />
|
||||||
|
</n-icon>
|
||||||
|
<n-number-animation :from="0" :to="userLikeData.mvs?.length ?? 0" />
|
||||||
|
个视频
|
||||||
|
</div>
|
||||||
|
<!-- 电台 -->
|
||||||
|
<div class="num-item">
|
||||||
|
<n-icon size="18">
|
||||||
|
<SvgIcon icon="record" />
|
||||||
|
</n-icon>
|
||||||
|
<n-number-animation :from="0" :to="userLikeData.djs?.length ?? 0" />
|
||||||
|
个电台
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- 标签页 -->
|
<!-- 标签页 -->
|
||||||
<n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange">
|
<n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange">
|
||||||
<n-tab name="like-albums"> 专辑 </n-tab>
|
<n-tab name="like-albums"> 专辑 </n-tab>
|
||||||
<n-tab name="like-playlists"> 歌单 </n-tab>
|
<n-tab name="like-playlists"> 歌单 </n-tab>
|
||||||
<n-tab name="like-artists"> 歌手 </n-tab>
|
<n-tab name="like-artists"> 歌手 </n-tab>
|
||||||
<n-tab name="like-videos"> 视频 </n-tab>
|
<n-tab name="like-videos"> 视频 </n-tab>
|
||||||
|
<n-tab name="like-djs"> 电台 </n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<!-- 路由页面 -->
|
<!-- 路由页面 -->
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
@@ -20,9 +64,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { siteData } from "@/stores";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const data = siteData();
|
||||||
|
const { userLikeData } = storeToRefs(data);
|
||||||
|
|
||||||
// 默认选中
|
// 默认选中
|
||||||
const tabValue = ref(router.currentRoute.value?.name ?? "like-albums");
|
const tabValue = ref(router.currentRoute.value?.name ?? "like-albums");
|
||||||
@@ -34,6 +82,12 @@ const tabChange = (val) => {
|
|||||||
path: `/like/${routerPath}`,
|
path: `/like/${routerPath}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
watch(
|
||||||
|
() => router.currentRoute.value,
|
||||||
|
(val) => (tabValue.value = val?.name ?? "like-playlists"),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -43,6 +97,30 @@ const tabChange = (val) => {
|
|||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.num {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.num-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
&::after {
|
||||||
|
content: "/";
|
||||||
|
margin: 0 8px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.tabs {
|
.tabs {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
v-for="(item, index) in ['我创建的', '我收藏的']"
|
v-for="(item, index) in ['我创建的', '我收藏的']"
|
||||||
:key="index"
|
:key="index"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:type="index === plTypeChoose ? 'primary' : 'default'"
|
:class="['tag', { choose: index === plTypeChoose }]"
|
||||||
class="tag"
|
|
||||||
round
|
round
|
||||||
@click="plTypeChange(index)"
|
@click="plTypeChange(index)"
|
||||||
>
|
>
|
||||||
@@ -20,12 +19,16 @@
|
|||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="plTypeChoose === 0" class="list">
|
<div v-if="plTypeChoose === 0" class="list">
|
||||||
<MainCover
|
<MainCover
|
||||||
:data="likePlaylistsData.filter((playlist) => playlist.userId === userData?.userId)"
|
:data="
|
||||||
|
userLikeData.playlists.filter((playlist) => playlist.userId === userData?.userId)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="list">
|
<div v-else class="list">
|
||||||
<MainCover
|
<MainCover
|
||||||
:data="likePlaylistsData.filter((playlist) => playlist.userId !== userData?.userId)"
|
:data="
|
||||||
|
userLikeData.playlists.filter((playlist) => playlist.userId !== userData?.userId)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -45,7 +48,6 @@
|
|||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { siteData } from "@/stores";
|
import { siteData } from "@/stores";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import formatData from "@/utils/formatData";
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
@@ -54,11 +56,6 @@ const { userData, userLikeData } = storeToRefs(data);
|
|||||||
// 默认分类
|
// 默认分类
|
||||||
const plTypeChoose = ref(Number(router.currentRoute.value.query?.type) || 0);
|
const plTypeChoose = ref(Number(router.currentRoute.value.query?.type) || 0);
|
||||||
|
|
||||||
// 处理视频数据
|
|
||||||
const likePlaylistsData = computed(() => {
|
|
||||||
return formatData(userLikeData.value.playlists);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 默认分类变化
|
// 默认分类变化
|
||||||
const plTypeChange = (type) => {
|
const plTypeChange = (type) => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -100,6 +97,10 @@ watch(
|
|||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
&.choose {
|
||||||
|
background-color: var(--main-second-color);
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="userLikeData.mvs?.length" class="list">
|
<div v-if="userLikeData.mvs?.length" class="list">
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<MainCover :data="likeVideosData" columns="1 s:2 m:3 l:4 xl:5" type="mv" />
|
<MainCover :data="userLikeData.mvs" columns="1 s:2 m:3 l:4 xl:5" type="mv" />
|
||||||
</div>
|
</div>
|
||||||
<n-empty
|
<n-empty
|
||||||
v-else
|
v-else
|
||||||
@@ -19,13 +19,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { siteData } from "@/stores";
|
import { siteData } from "@/stores";
|
||||||
import formatData from "@/utils/formatData";
|
|
||||||
|
|
||||||
const data = siteData();
|
const data = siteData();
|
||||||
const { userLikeData } = storeToRefs(data);
|
const { userLikeData } = storeToRefs(data);
|
||||||
|
|
||||||
// 处理视频数据
|
|
||||||
const likeVideosData = computed(() => {
|
|
||||||
return formatData(userLikeData.value.mvs, "mv");
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ const playAllSongs = async () => {
|
|||||||
playSongData.value = albumData.value[0];
|
playSongData.value = albumData.value[0];
|
||||||
playIndex.value = 0;
|
playIndex.value = 0;
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
initPlayer(true);
|
await initPlayer(true);
|
||||||
} else {
|
} else {
|
||||||
console.log("处于专辑内");
|
console.log("处于专辑内");
|
||||||
playSongData.value = albumData.value[existingIndex];
|
playSongData.value = albumData.value[existingIndex];
|
||||||
|
|||||||
544
src/views/List/dj.vue
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
<!-- 电台播客页面 -->
|
||||||
|
<template>
|
||||||
|
<div v-if="djId" class="dj">
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div v-if="djDetail && Object.keys(djDetail)?.length" class="detail">
|
||||||
|
<div class="cover">
|
||||||
|
<!-- 封面 -->
|
||||||
|
<n-image
|
||||||
|
:src="djDetail.coverSize.l"
|
||||||
|
:previewed-img-props="{ style: { borderRadius: '8px' } }"
|
||||||
|
:preview-src="djDetail.cover"
|
||||||
|
class="cover-img"
|
||||||
|
show-toolbar-tooltip
|
||||||
|
@load="
|
||||||
|
(e) => {
|
||||||
|
e.target.style.opacity = 1;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="cover-loading">
|
||||||
|
<img class="loading-img" src="/images/pic/song.jpg?assest" alt="song" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-image>
|
||||||
|
<!-- 封面背板 -->
|
||||||
|
<n-image :src="djDetail.coverSize.m" class="cover-shadow" preview-disabled />
|
||||||
|
</div>
|
||||||
|
<div class="data">
|
||||||
|
<!-- 名称 -->
|
||||||
|
<n-text class="name">
|
||||||
|
{{ djDetail.name || "未知电台" }}
|
||||||
|
</n-text>
|
||||||
|
<div class="creator">
|
||||||
|
<n-avatar
|
||||||
|
:src="(djDetail.creator?.avatarUrl + '?param=300y$300').replace(/^http:/, 'https:')"
|
||||||
|
fallback-src="/images/pic/avatar.jpg?assest"
|
||||||
|
round
|
||||||
|
/>
|
||||||
|
<n-text class="nickname">{{ djDetail.creator?.nickname || "未知创建者" }}</n-text>
|
||||||
|
<n-text v-if="djDetail.createTime" class="create-time" depth="3">
|
||||||
|
{{ getTimestampTime(djDetail.createTime) }} 创建
|
||||||
|
</n-text>
|
||||||
|
<!-- 标签 -->
|
||||||
|
<n-tag
|
||||||
|
v-if="djDetail?.tags"
|
||||||
|
:bordered="false"
|
||||||
|
class="tags"
|
||||||
|
round
|
||||||
|
@click="
|
||||||
|
router.push({
|
||||||
|
path: '/dj-type',
|
||||||
|
query: {
|
||||||
|
type: djDetail.tags.id,
|
||||||
|
name: djDetail.tags.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ djDetail.tags.name }}
|
||||||
|
</n-tag>
|
||||||
|
</div>
|
||||||
|
<!-- 简介 -->
|
||||||
|
<n-ellipsis
|
||||||
|
v-if="djDetail.desc"
|
||||||
|
:tooltip="false"
|
||||||
|
class="description"
|
||||||
|
expand-trigger="click"
|
||||||
|
line-clamp="2"
|
||||||
|
>
|
||||||
|
<n-text depth="3">{{ djDetail.desc }}</n-text>
|
||||||
|
</n-ellipsis>
|
||||||
|
<n-text v-else class="description">太懒了吧,连简介都没写</n-text>
|
||||||
|
<!-- 数量 -->
|
||||||
|
<n-space class="num">
|
||||||
|
<div v-if="djDetail?.count" class="num-item">
|
||||||
|
<n-icon depth="3" size="18">
|
||||||
|
<SvgIcon icon="music-note" />
|
||||||
|
</n-icon>
|
||||||
|
<n-text depth="3">{{ djDetail.count }}</n-text>
|
||||||
|
</div>
|
||||||
|
<div v-if="djDetail?.updateTime" class="num-item">
|
||||||
|
<n-icon depth="3" size="18">
|
||||||
|
<SvgIcon icon="clock" />
|
||||||
|
</n-icon>
|
||||||
|
<n-text depth="3">{{ getTimestampTime(djDetail.updateTime) }} 更新</n-text>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="detail">
|
||||||
|
<n-skeleton class="cover" />
|
||||||
|
<div class="data">
|
||||||
|
<n-skeleton :repeat="4" text />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
<!-- 功能区 -->
|
||||||
|
<n-space class="menu" justify="space-between">
|
||||||
|
<n-space class="left">
|
||||||
|
<n-button
|
||||||
|
:disabled="djData === 'empty'"
|
||||||
|
type="primary"
|
||||||
|
class="play"
|
||||||
|
tag="div"
|
||||||
|
circle
|
||||||
|
strong
|
||||||
|
secondary
|
||||||
|
@click="playAllSongs"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon size="32">
|
||||||
|
<SvgIcon icon="play-arrow-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
<n-button size="large" tag="div" round strong secondary @click="likeOrDislike(djId)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<SvgIcon
|
||||||
|
:icon="isLikeOrDislike(djId) ? 'favorite-outline-rounded' : 'favorite-rounded'"
|
||||||
|
/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ isLikeOrDislike(djId) ? "订阅电台" : "取消订阅" }}
|
||||||
|
</n-button>
|
||||||
|
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
|
||||||
|
<n-button size="large" tag="div" circle strong secondary>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<SvgIcon icon="format-list-bulleted" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</n-space>
|
||||||
|
<n-space class="right">
|
||||||
|
<!-- 模糊搜索 -->
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<n-input
|
||||||
|
v-if="djData !== 'empty' && djData?.length"
|
||||||
|
v-model:value="searchValue"
|
||||||
|
:input-props="{ autoComplete: false }"
|
||||||
|
class="search"
|
||||||
|
placeholder="模糊搜索"
|
||||||
|
clearable
|
||||||
|
@input="localSearch"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon size="18">
|
||||||
|
<SvgIcon icon="search-rounded" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</Transition>
|
||||||
|
</n-space>
|
||||||
|
</n-space>
|
||||||
|
<!-- 列表 -->
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div v-if="djData !== 'empty'" class="list">
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div v-if="!searchValue" class="song-list">
|
||||||
|
<SongList :data="djData" type="dj" />
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
v-if="djData?.length"
|
||||||
|
:totalCount="totalCount"
|
||||||
|
:pageNumber="pageNumber"
|
||||||
|
@pageNumberChange="pageNumberChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SongList v-else-if="searchData?.length" :data="searchData" type="dj" />
|
||||||
|
<n-empty
|
||||||
|
v-else
|
||||||
|
:description="`搜不到关于 ${searchValue} 的任何节目`"
|
||||||
|
style="margin-top: 60px"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<SvgIcon icon="search-off" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-empty>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<n-empty v-else class="empty" description="这个电台还没有节目哦" />
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<div v-else class="title">
|
||||||
|
<n-text class="key">参数不完整</n-text>
|
||||||
|
<n-button class="back" strong secondary @click="router.go(-1)"> 返回上一页 </n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { NIcon } from "naive-ui";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { musicData, siteData, siteSettings } from "@/stores";
|
||||||
|
import { getDjDetail, getDjProgram, likeDj } from "@/api/dj";
|
||||||
|
import { fuzzySearch } from "@/utils/helper";
|
||||||
|
import { isLogin } from "@/utils/auth";
|
||||||
|
import { getTimestampTime } from "@/utils/timeTools";
|
||||||
|
import { fadePlayOrPause, initPlayer } from "@/utils/Player";
|
||||||
|
import debounce from "@/utils/debounce";
|
||||||
|
import formatData from "@/utils/formatData";
|
||||||
|
import SvgIcon from "@/components/Global/SvgIcon";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const data = siteData();
|
||||||
|
const music = musicData();
|
||||||
|
const settings = siteSettings();
|
||||||
|
const { userLikeData } = storeToRefs(data);
|
||||||
|
const { loadSize } = storeToRefs(settings);
|
||||||
|
const { playList, playIndex, playSongData, playHeartbeatMode, playMode } = storeToRefs(music);
|
||||||
|
|
||||||
|
// 电台数据
|
||||||
|
const djId = ref(router.currentRoute.value.query.id);
|
||||||
|
const pageNumber = ref(Number(router.currentRoute.value.query?.page) || 1);
|
||||||
|
const djDetail = ref(null);
|
||||||
|
const djData = ref(null);
|
||||||
|
|
||||||
|
// 模糊搜索数据
|
||||||
|
const searchValue = ref(null);
|
||||||
|
const searchData = ref([]);
|
||||||
|
const totalCount = ref(0);
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => h(NIcon, null, { default: () => h(SvgIcon, { icon }, null) });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更多操作数据
|
||||||
|
const moreOptions = [
|
||||||
|
{
|
||||||
|
label: "打开源页面链接",
|
||||||
|
key: "open",
|
||||||
|
props: {
|
||||||
|
onclick: () => {
|
||||||
|
const id = djId.value;
|
||||||
|
if (id) window.open(`https://music.163.com/#/djradio?id=${id}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon("link"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 获取电台信息
|
||||||
|
const getDjDetailData = async (id) => {
|
||||||
|
try {
|
||||||
|
if (!id) return false;
|
||||||
|
// 清空数据
|
||||||
|
djDetail.value = null;
|
||||||
|
djData.value = null;
|
||||||
|
// 获取数据
|
||||||
|
const detail = await getDjDetail(id);
|
||||||
|
// 基础信息
|
||||||
|
djDetail.value = formatData(detail.data, "dj")[0];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取电台信息出错:", error);
|
||||||
|
$message.error("获取电台信息出现错误");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取电台全部节目
|
||||||
|
const getDjProgramData = async (id, limit = loadSize.value, offset = 0) => {
|
||||||
|
try {
|
||||||
|
djData.value = [];
|
||||||
|
const result = await getDjProgram(id, limit, offset);
|
||||||
|
console.log(result);
|
||||||
|
// 数据总数
|
||||||
|
totalCount.value = result.count;
|
||||||
|
if (totalCount.value === 0) return (djData.value = "empty");
|
||||||
|
// 处理数据
|
||||||
|
djData.value = formatData(result.programs, "dj");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取电台节目错误:", error);
|
||||||
|
$message.error("获取电台节目出现错误");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播放电台全部节目
|
||||||
|
const playAllSongs = async () => {
|
||||||
|
if (!djData.value) return false;
|
||||||
|
// 关闭心动模式
|
||||||
|
playHeartbeatMode.value = false;
|
||||||
|
// 更改模式和电台
|
||||||
|
playMode.value = "dj";
|
||||||
|
playList.value = djData.value.slice();
|
||||||
|
// 是否处于电台内
|
||||||
|
const songId = music.getPlaySongData?.id;
|
||||||
|
const existingIndex = djData.value.findIndex((song) => song.id === songId);
|
||||||
|
// 若不处于
|
||||||
|
if (existingIndex === -1 || !songId) {
|
||||||
|
console.log("不在电台内");
|
||||||
|
playSongData.value = djData.value[0];
|
||||||
|
playIndex.value = 0;
|
||||||
|
// 初始化播放器
|
||||||
|
await initPlayer(true);
|
||||||
|
} else {
|
||||||
|
console.log("处于电台内");
|
||||||
|
playSongData.value = djData.value[existingIndex];
|
||||||
|
playIndex.value = existingIndex;
|
||||||
|
// 播放
|
||||||
|
fadePlayOrPause();
|
||||||
|
}
|
||||||
|
$message.info("已开始播放", { showIcon: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 节目模糊搜索
|
||||||
|
const localSearch = debounce((val) => {
|
||||||
|
const searchValue = val?.trim();
|
||||||
|
// 是否为空
|
||||||
|
if (!searchValue || searchValue === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 返回结果
|
||||||
|
const result = fuzzySearch(searchValue, djData.value);
|
||||||
|
searchData.value = result;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// 判断收藏还是取消
|
||||||
|
const isLikeOrDislike = (id) => {
|
||||||
|
const djs = userLikeData.value.djs;
|
||||||
|
if (djs.length) {
|
||||||
|
return !djs.some((item) => item.id === Number(id));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 订阅 / 取消订阅电台
|
||||||
|
const likeOrDislike = debounce(async (id) => {
|
||||||
|
try {
|
||||||
|
if (!isLogin()) return $message.warning("请登录后使用");
|
||||||
|
const type = isLikeOrDislike(id) ? 1 : 0;
|
||||||
|
const result = await likeDj(id, type);
|
||||||
|
if (result.code === 200) {
|
||||||
|
$message.success((type === 1 ? "订阅" : "取消订阅") + "成功");
|
||||||
|
// 更新用户电台
|
||||||
|
await data.setUserLikeDjs();
|
||||||
|
} else {
|
||||||
|
$message.error((type === 1 ? "订阅" : "取消订阅") + "失败,请重试");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("订阅出错:", error);
|
||||||
|
$message.error("订阅操作出现错误");
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// 页数变化
|
||||||
|
const pageNumberChange = (page) => {
|
||||||
|
router.push({
|
||||||
|
path: "/dj",
|
||||||
|
query: { id: djId.value, page },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
watch(
|
||||||
|
() => router.currentRoute.value,
|
||||||
|
async (val) => {
|
||||||
|
if (val.name === "dj") {
|
||||||
|
// 更改参数
|
||||||
|
pageNumber.value = Number(val.query?.page) || 1;
|
||||||
|
djId.value = val.query?.id;
|
||||||
|
// 调用接口
|
||||||
|
await getDjDetailData(djId.value);
|
||||||
|
await getDjProgramData(
|
||||||
|
djId.value,
|
||||||
|
loadSize.value,
|
||||||
|
(pageNumber.value - 1) * settings.loadSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getDjDetailData(djId.value);
|
||||||
|
await getDjProgramData(djId.value, loadSize.value, (pageNumber.value - 1) * settings.loadSize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dj {
|
||||||
|
.detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.cover {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
min-width: 200px;
|
||||||
|
margin-right: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
.cover-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1;
|
||||||
|
transition:
|
||||||
|
filter 0.3s,
|
||||||
|
transform 0.3s;
|
||||||
|
:deep(img) {
|
||||||
|
width: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.35s ease-in-out;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cover-shadow {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: blur(16px) opacity(0.6);
|
||||||
|
transform: scale(0.92, 0.96);
|
||||||
|
z-index: 0;
|
||||||
|
background-size: cover;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.data {
|
||||||
|
width: 100%;
|
||||||
|
.name {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
.n-avatar {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.nickname {
|
||||||
|
transition: color 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create-time {
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.tags {
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 16px;
|
||||||
|
line-height: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 0.3s,
|
||||||
|
background-color 0.3s,
|
||||||
|
color 0.3s;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--main-second-color);
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.num {
|
||||||
|
margin-top: 12px;
|
||||||
|
.num-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
// color: var(--main-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
margin-top: 12px;
|
||||||
|
.n-text {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.n-skeleton) {
|
||||||
|
&:first-child {
|
||||||
|
width: 60%;
|
||||||
|
margin-top: 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
height: 30px;
|
||||||
|
margin-top: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu {
|
||||||
|
align-items: center;
|
||||||
|
margin: 26px 0;
|
||||||
|
.left {
|
||||||
|
align-items: center;
|
||||||
|
.play {
|
||||||
|
--n-width: 46px;
|
||||||
|
--n-height: 46px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
.search {
|
||||||
|
height: 40px;
|
||||||
|
width: 130px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 40px;
|
||||||
|
transition:
|
||||||
|
width 0.3s,
|
||||||
|
background-color 0.3s;
|
||||||
|
&.n-input--focus {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.key {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.back {
|
||||||
|
width: 98px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -364,7 +364,7 @@ const playAllSongs = async () => {
|
|||||||
playSongData.value = playListData.value[0];
|
playSongData.value = playListData.value[0];
|
||||||
playIndex.value = 0;
|
playIndex.value = 0;
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
initPlayer(true);
|
await initPlayer(true);
|
||||||
} else {
|
} else {
|
||||||
console.log("处于歌单内");
|
console.log("处于歌单内");
|
||||||
playSongData.value = playListData.value[existingIndex];
|
playSongData.value = playListData.value[existingIndex];
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-if="allAlbumData"
|
v-if="allAlbumData"
|
||||||
:style="{
|
:style="{
|
||||||
height: `calc(100vh - ${
|
height: `calc(100vh - ${
|
||||||
Object.keys(music.playSongData)?.length && status.showPlayBar ? 345 : 265
|
Object.keys(music.playSongData)?.length && status.showPlayBar ? 380 : 300
|
||||||
}px)`,
|
}px)`,
|
||||||
}"
|
}"
|
||||||
class="local-album"
|
class="local-album"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
class="local-artists"
|
class="local-artists"
|
||||||
:style="{
|
:style="{
|
||||||
height: `calc(100vh - ${
|
height: `calc(100vh - ${
|
||||||
Object.keys(music.playSongData)?.length && status.showPlayBar ? 345 : 265
|
Object.keys(music.playSongData)?.length && status.showPlayBar ? 380 : 300
|
||||||
}px)`,
|
}px)`,
|
||||||
}"
|
}"
|
||||||
type="card"
|
type="card"
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<!-- 播客 - 热门 -->
|
|
||||||
<template>
|
|
||||||
<div class="record-hot">114514</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<!-- 播客 -->
|
|
||||||
<template>
|
|
||||||
<div class="record">新建文件夹...</div>
|
|
||||||
</template>
|
|
||||||