Compare commits

...

24 commits

Author SHA1 Message Date
71817baad0 去除听歌打卡功能 2025-06-17 09:13:43 +08:00
f6735c9c26
Update README.md
Some checks failed
Release / release (macos-latest) (push) Has been cancelled
Release / release (ubuntu-22.04) (push) Has been cancelled
Release / release (windows-latest) (push) Has been cancelled
更换demo的url
2025-06-15 22:39:46 +08:00
708a1a8eb8 去除签到功能、邮箱和登录入口 2025-06-14 12:36:22 +08:00
runnableAir
70ab357799
fix: 随机模式下当前播放歌曲于列表中的位置出现错误和不同步的情况 (#2378)
* fix(Player.js): 随机模式下列表初始化时当前歌曲的下标异常

异常情况:假设歌曲A其在歌单中的位置为10,随机模式下双击该歌曲后,this.current != 10
原因:随机模式下,通过 indexOf 计算 current 时,调用的数组与真实的列表数组不一致

* fix(Player.js): 随机模式切换时未同步当前歌曲下标

* fix: 随机播放顺序与实际列表不一致

使用 filter 不保证返回的数组的元素顺序与传入的id 顺序对应
2025-05-30 19:59:52 +08:00
李洋
c7e69158d2
Update package.json (#2347)
将版本号从0.4.8改成了0.4.9(有点强迫症)
2025-03-03 19:08:59 +08:00
Yi-Jyun Pan
022009b140
chore: upgrade dependencies 2024-11-01 15:01:39 +08:00
Yi-Jyun Pan
6849632abe
chore: add AppKit framework for macOS 2024-11-01 15:00:45 +08:00
Yi-Jyun Pan
c929beaf6c
Merge branch 'feat-realip' 2024-11-01 14:55:35 +08:00
Yi-Jyun Pan
dfb09b3ccd
chore: add devenv configuration 2024-11-01 14:55:06 +08:00
CuSO4Deposit
9809a758b4
style: Reformat with Prettier (#2296) 2024-11-01 14:20:55 +08:00
Frz
c9739b2d0e
feat: use cover image for Discord RPC (#2294) 2024-11-01 14:20:30 +08:00
bestlaw66
25e274a4f8
docs: 宝塔面板部署教程 2024-11-01 14:19:23 +08:00
pan93412
fc1c8d8512
Merge pull request #2292 from Younglina/master
添加复制歌词功能
2024-11-01 14:18:45 +08:00
StarsEnd
e27879aa94 update: NeteaseCloudMusicApi (iPhone OS fix) 2024-10-16 00:16:23 +08:00
StarsEnd
d219e48541 fix: real-ip config disabled css style 2024-10-16 00:14:38 +08:00
StarsEnd33A2D17
107f5765b0 Update to the latest NeteaseCloudMusicApi 2024-10-08 20:29:05 +08:00
StarsEnd33A2D17
adafffd86b feat: add RealIP setting 2024-09-30 13:35:22 +08:00
Younglina
9d807d1d63 feat: 复制歌词 2024-08-27 13:21:01 +08:00
Younglina
481ba6bce3
fix: 相似歌手接口需要登录;未登录获取艺人热门歌曲没有图片 (#2286) 2024-08-13 18:20:52 +08:00
StarsEnd
df82c7cd22
feat(ui): add fullscreen button (#2276)
* feat(ui): add fullscreen button

* fix: fullscreen图标修改,添加exit icon
2024-08-13 14:28:05 +08:00
Younglina
bd5af9c721
fix: 默认值错误导致加载空节点 (#2280) 2024-08-13 14:26:02 +08:00
5unV
7cb063d511
fix: 纠正脏标的判断 (#2268) 2024-08-07 22:37:29 +08:00
Felix_SANA
904e61bee6
fix: 阻止搜索引擎爬虫爬取网站的内容 (#2239) 2024-08-07 22:36:52 +08:00
Younglina
24477694f8
fix: 手机号placeholder显示问题 (#2197) 2024-03-18 15:16:48 +08:00
32 changed files with 1298 additions and 601 deletions

4
.envrc Normal file
View file

@ -0,0 +1,4 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
export NIXPKGS_ALLOW_INSECURE=1
use devenv

9
.gitignore vendored
View file

@ -32,3 +32,12 @@ NeteaseCloudMusicApi-master.zip
# Local Netlify folder
.netlify
vercel.json
# Devenv
.devenv*
devenv.local.nix
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml

View file

@ -8,7 +8,7 @@
<p align="center">
高颜值的第三方网易云播放器
<br />
<a href="https://music.qier222.com" target="blank"><strong>🌎 访问DEMO</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://music.ineko.cc" target="blank"><strong>🌎 访问DEMO</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#%EF%B8%8F-安装" target="blank"><strong>📦️ 下载安装包</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://t.me/yesplaymusic" target="blank"><strong>💬 加入交流群</strong></a>
<br />
@ -16,7 +16,7 @@
</p>
</p>
[![Library][library-screenshot]](https://music.qier222.com)
[![Library][library-screenshot]](https://music.ineko.cc)
## 全新版本
@ -125,6 +125,16 @@ yarn run build
7. 将 `/dist` 目录下的文件上传到你的 Web 服务器
## ⚙️ 宝塔面板 docker应用商店 部署
1. 安装宝塔面板,前往[宝塔面板官网](https://www.bt.cn/new/download.html) ,选择正式版的脚本下载安装。
2. 安装后登录宝塔面板,在左侧导航栏中点击 Docker首次进入会提示安装Docker服务点击立即安装按提示完成安装
3. 安装完成后在应用商店中找到YesPlayMusic点击安装配置域名、端口等基本信息即可完成安装。
4. 安装后在浏览器输入上一步骤设置的域名即可访问。
## ⚙️ Docker 部署
1. 构建 Docker Image

132
devenv.lock Normal file
View file

@ -0,0 +1,132 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1730412360,
"owner": "cachix",
"repo": "devenv",
"rev": "45847cb1f14a6d8cfa86ea943703c54a8798ae7e",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1730272153,
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2d2a9ddbe3f2c00747398f3dc9b05f7f2ebb0f53",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1730327045,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "080166c15633801df010977d9d7474b4a6c549d7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nodejs16": {
"locked": {
"lastModified": 1700230496,
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a71323f68d4377d12c04a5410e214495ec598d4c",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a71323f68d4377d12c04a5410e214495ec598d4c",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1730302582,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "af8a16fe5c264f5e9e18bcee2859b40a656876cf",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"nodejs16": "nodejs16",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}

53
devenv.nix Normal file
View file

@ -0,0 +1,53 @@
{ pkgs, lib, config, inputs, ... }:
let
nodejs16 = import inputs.nodejs16 { system = pkgs.stdenv.system; };
in
{
# https://devenv.sh/basics/
env.GREET = "devenv";
# https://devenv.sh/packages/
packages = [ pkgs.git ] ++ lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk; [
frameworks.AppKit
]);
# https://devenv.sh/languages/
languages.javascript.enable = true;
languages.javascript.package = nodejs16.pkgs.nodejs_16;
languages.javascript.corepack.enable = true;
# languages.rust.enable = true;
# https://devenv.sh/processes/
# processes.cargo-watch.exec = "cargo-watch";
# https://devenv.sh/services/
# services.postgres.enable = true;
# https://devenv.sh/scripts/
scripts.hello.exec = ''
echo hello from $GREET
'';
enterShell = ''
hello
git --version
'';
# https://devenv.sh/tasks/
# tasks = {
# "myproj:setup".exec = "mytool build";
# "devenv:enterShell".after = [ "myproj:setup" ];
# };
# https://devenv.sh/tests/
enterTest = ''
echo "Running tests"
git --version | grep --color=auto "${pkgs.git.version}"
'';
# https://devenv.sh/pre-commit-hooks/
# pre-commit.hooks.shellcheck.enable = true;
# See full reference at https://devenv.sh/reference/options/
}

19
devenv.yaml Normal file
View file

@ -0,0 +1,19 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:nixos/nixpkgs/nixpkgs-unstable
nodejs16:
url: github:nixos/nixpkgs/a71323f68d4377d12c04a5410e214495ec598d4c
# https://github.com/cachix/devenv/issues/792#issuecomment-2043166453
impure: true
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend

View file

@ -1,6 +1,6 @@
{
"name": "yesplaymusic",
"version": "0.4.8",
"version": "0.4.9",
"private": true,
"description": "A third party music player for Netease Music",
"author": "qier222<qier222@outlook.com>",
@ -27,7 +27,7 @@
},
"dependencies": {
"@unblockneteasemusic/rust-napi": "^0.4.0",
"NeteaseCloudMusicApi": "^4.8.7",
"NeteaseCloudMusicApi": "^4.23.3",
"axios": "^0.26.1",
"change-case": "^4.1.2",
"cli-color": "^2.0.0",

View file

@ -1,2 +1,2 @@
User-agent: *
Disallow:
Disallow: /

View file

@ -1,5 +1,7 @@
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
import { isAccountLoggedIn } from '@/utils/auth';
import { getTrackDetail } from '@/api/track';
/**
* 获取歌手单曲
@ -14,7 +16,13 @@ export function getArtist(id) {
id,
timestamp: new Date().getTime(),
},
}).then(data => {
}).then(async data => {
if (!isAccountLoggedIn()) {
const trackIDs = data.hotSongs.map(t => t.id);
const tracks = await getTrackDetail(trackIDs.join(','));
data.hotSongs = tracks.songs;
return data;
}
data.hotSongs = mapTrackPlayableStatus(data.hotSongs);
return data;
});

View file

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" data-darkreader-inline-fill="" xmlns="http://www.w3.org/2000/svg">
<path id="path" d="M1 11L3 11L3 13C3 13.55 3.45 14 4 14C4.55 14 5 13.55 5 13L5 10C5 9.45 4.55 9 4 9L1 9C0.45 9 0 9.45 0 10C0 10.55 0.45 11 1 11ZM11 3L11 1C11 0.45 10.55 0 10 0C9.45 0 9 0.45 9 1L9 4C9 4.54 9.45 5 10 5L13 5C13.55 5 14 4.54 14 4C14 3.45 13.55 3 13 3L11 3Z"/>
</svg>

After

Width:  |  Height:  |  Size: 396 B

View file

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" data-darkreader-inline-fill="" xmlns="http://www.w3.org/2000/svg">
<path id="path" d="M1 9C0.45 9 0 9.45 0 10L0 13C0 13.55 0.45 14 1 14L4 14C4.55 14 5 13.55 5 13C5 12.45 4.55 12 4 12L2 12L2 10C2 9.45 1.55 9 1 9ZM9 1C9 1.54 9.45 2 10 2L12 2L12 4C12 4.54 12.45 5 13 5C13.55 5 14 4.54 14 4L14 1C14 0.45 13.55 0 13 0L10 0C9.45 0 9 0.45 9 1Z"/>
</svg>

After

Width:  |  Height:  |  Size: 396 B

View file

@ -50,7 +50,7 @@ export default {
props: {
items: { type: Array, required: true },
type: { type: String, required: true },
subText: { type: String, default: 'null' },
subText: { type: String, default: 'none' },
subTextFontSize: { type: String, default: '16px' },
showPlayCount: { type: Boolean, default: false },
columnNumber: { type: Number, default: 5 },
@ -96,7 +96,7 @@ export default {
return this.type === 'playlist' && item.privacy === 10;
},
isExplicit(item) {
return this.type === 'album' && item.mark === 1056768;
return this.type === 'album' && (item.mark & 1048576) === 1048576;
},
getTitleLink(item) {
return `/${this.type}/${item.id}`;

View file

@ -42,13 +42,15 @@
:exclude="$parent.albumObject.artist.name"
prefix="-"
/></span>
<span v-if="isAlbum && track.mark === 1318912" class="explicit-symbol"
<span
v-if="isAlbum && (track.mark & 1048576) === 1048576"
class="explicit-symbol"
><ExplicitSymbol
/></span>
</div>
<div v-if="!isAlbum" class="artist">
<span
v-if="track.mark === 1318912"
v-if="(track.mark & 1048576) === 1048576"
class="explicit-symbol before-artist"
><ExplicitSymbol :size="15"
/></span>

View file

@ -240,7 +240,7 @@ export function initIpcMain(win, store, trayEventEmitter) {
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
state: track.al.name,
endTimestamp: Date.now() + track.dt,
largeImageKey: 'logo',
largeImageKey: track.al.picUrl,
largeImageText: 'Listening ' + track.name,
smallImageKey: 'play',
smallImageText: 'Playing',
@ -252,7 +252,7 @@ export function initIpcMain(win, store, trayEventEmitter) {
client.updatePresence({
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
state: track.al.name,
largeImageKey: 'logo',
largeImageKey: track.al.picUrl,
largeImageText: 'YesPlayMusic',
smallImageKey: 'pause',
smallImageText: 'Pause',

View file

@ -244,6 +244,8 @@ export default {
minePlaylists: 'My Playlists',
likedPlaylists: 'Liked Playlists',
cardiacMode: 'Cardiac Mode',
copyLyric: 'Copy Lyric',
copyLyricWithTranslation: 'Copy Lyric With Translation',
},
toast: {
savedToPlaylist: 'Saved to playlist',

View file

@ -230,6 +230,8 @@ export default {
minePlaylists: 'My Playlists',
likedPlaylists: 'Liked Playlists',
cardiacMode: 'Cardiac Mode',
copyLyric: 'Copy Lyric',
copyLyricWithTranslation: 'Copy Lyric With Translation',
},
toast: {
savedToMyLikedSongs: 'Beğendiğim Müziklere Kaydet',

View file

@ -243,6 +243,8 @@ export default {
minePlaylists: '创建的歌单',
likedPlaylists: '收藏的歌单',
cardiacMode: '心动模式',
copyLyric: '复制歌词',
copyLyricWithTranslation: '复制歌词(含翻译)',
},
toast: {
savedToPlaylist: '已添加到歌单',

View file

@ -240,6 +240,8 @@ export default {
minePlaylists: '我建立的歌單',
likedPlaylists: '收藏的歌單',
cardiacMode: '心動模式',
copyLyric: '複製歌詞',
copyLyricWithTranslation: '複製歌詞(含翻譯)',
},
toast: {
savedToPlaylist: '已新增至歌單',

View file

@ -7,7 +7,6 @@ import i18n from '@/locale';
import '@/assets/icons';
import '@/utils/filters';
import './registerServiceWorker';
import { dailyTask } from '@/utils/common';
import '@/assets/css/global.scss';
import NProgress from 'nprogress';
import '@/assets/css/nprogress.css';
@ -38,7 +37,6 @@ Vue.use(
Vue.config.productionTip = false;
NProgress.configure({ showSpinner: false, trickleSpeed: 100 });
dailyTask();
new Vue({
i18n,

View file

@ -36,6 +36,8 @@ let localStorage = {
server: '',
port: null,
},
enableRealIP: false,
realIP: null,
shortcuts: shortcuts,
},
data: {

View file

@ -3,7 +3,7 @@ import { getArtist } from '@/api/artist';
import { trackScrobble, trackUpdateNowPlaying } from '@/api/lastfm';
import { fmTrash, personalFM } from '@/api/others';
import { getPlaylistDetail, intelligencePlaylist } from '@/api/playlist';
import { getLyric, getMP3, getTrackDetail, scrobble } from '@/api/track';
import { getLyric, getMP3, getTrackDetail } from '@/api/track';
import store from '@/store';
import { isAccountLoggedIn } from '@/utils/auth';
import { cacheTrackSource, getTrackSource } from '@/utils/db';
@ -130,6 +130,8 @@ export default class {
if (shuffle) {
this._shuffleTheList();
}
// 同步当前歌曲在列表中的下标
this.current = this.list.indexOf(this.currentTrackID);
}
get reversed() {
return this._reversed;
@ -307,11 +309,6 @@ export default class {
);
const trackDuration = ~~(track.dt / 1000);
time = completed ? trackDuration : ~~time;
scrobble({
id: track.id,
sourceid: this.playlistSource.id,
time,
});
if (
store.state.lastfm.key !== undefined &&
(time >= trackDuration / 2 || time >= 240)
@ -892,7 +889,7 @@ export default class {
if (autoPlayTrackID === 'first') {
this._replaceCurrentTrack(this.list[0]);
} else {
this.current = trackIDs.indexOf(autoPlayTrackID);
this.current = this.list.indexOf(autoPlayTrackID);
this._replaceCurrentTrack(autoPlayTrackID);
}
}

View file

@ -84,3 +84,30 @@ function trimContent(content) {
let t = content.trim();
return t.length < 1 ? content : t;
}
/**
* @param {string} lyric
*/
export async function copyLyric(lyric) {
const textToCopy = lyric;
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(textToCopy);
} catch (err) {
alert('复制失败,请手动复制!');
}
} else {
const tempInput = document.createElement('textarea');
tempInput.value = textToCopy;
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
} catch (err) {
alert('复制失败,请手动复制!');
}
document.body.removeChild(tempInput);
}
}

View file

@ -38,8 +38,15 @@ service.interceptors.request.use(function (config) {
config.params.realIP = '211.161.244.70';
}
// Force real_ip
const enableRealIP = JSON.parse(
localStorage.getItem('settings')
).enableRealIP;
const realIP = JSON.parse(localStorage.getItem('settings')).realIP;
if (process.env.VUE_APP_REAL_IP) {
config.params.realIP = process.env.VUE_APP_REAL_IP;
} else if (enableRealIP) {
config.params.realIP = realIP;
}
const proxy = JSON.parse(localStorage.getItem('settings')).proxyConfig;

View file

@ -28,7 +28,9 @@
<span v-else>Compilation by Various Artists</span>
</div>
<div class="date-and-count">
<span v-if="album.mark === 1056768" class="explicit-symbol"
<span
v-if="(album.mark & 1048576) === 1048576"
class="explicit-symbol"
><ExplicitSymbol
/></span>
<span :title="album.publishTime | formatDate">{{

View file

@ -185,6 +185,7 @@ import {
followAArtist,
similarArtists,
} from '@/api/artist';
import { getTrackDetail } from '@/api/track';
import locale from '@/locale';
import { isAccountLoggedIn } from '@/utils/auth';
import NProgress from 'nprogress';
@ -278,7 +279,7 @@ export default {
this.$parent.$refs.main.scrollTo({ top: 0 });
getArtist(id).then(data => {
this.artist = data.artist;
this.popularTracks = data.hotSongs;
this.setPopularTracks(data.hotSongs);
if (next !== undefined) next();
NProgress.done();
this.show = true;
@ -291,8 +292,16 @@ export default {
this.mvs = data.mvs;
this.hasMoreMV = data.hasMore;
});
similarArtists(id).then(data => {
this.similarArtists = data.artists;
if (isAccountLoggedIn()) {
similarArtists(id).then(data => {
this.similarArtists = data.artists;
});
}
},
setPopularTracks(hotSongs) {
const trackIDs = hotSongs.map(t => t.id);
getTrackDetail(trackIDs.join(',')).then(data => {
this.popularTracks = data.songs;
});
},
goToAlbum(id) {

View file

@ -120,10 +120,13 @@ export default {
setTimeout(() => {
if (!this.show) NProgress.start();
}, 1000);
this.activeCategory =
this.$route.query.category === undefined
? '全部'
: this.$route.query.category;
const queryCategory = this.$route.query.category;
if (queryCategory === undefined) {
this.playlists = [];
this.activeCategory = '全部';
} else {
this.activeCategory = queryCategory;
}
this.getPlaylist();
},
goToCategory(Category) {

View file

@ -215,7 +215,7 @@
<script>
import { mapActions, mapMutations, mapState } from 'vuex';
import { randomNum, dailyTask } from '@/utils/common';
import { randomNum } from '@/utils/common';
import { isAccountLoggedIn } from '@/utils/auth';
import { uploadSong } from '@/api/user';
import { getLyric } from '@/api/track';
@ -310,7 +310,6 @@ export default {
activated() {
this.$parent.$refs.scrollbar.restorePosition();
this.loadData();
dailyTask();
},
methods: {
...mapActions(['showToast']),

View file

@ -7,16 +7,19 @@
<div class="title">{{ $t('login.loginText') }}</div>
<div class="section-2">
<div v-show="mode === 'phone'" class="input-box">
<div class="container" :class="{ active: inputFocus === 'phone' }">
<div
class="container"
:class="{ active: ['phone', 'countryCode'].includes(inputFocus) }"
>
<svg-icon icon-class="mobile" />
<div class="inputs">
<input
id="countryCode"
v-model="countryCode"
:placeholder="
inputFocus === 'phone' ? '' : $t('login.countryCode')
inputFocus === 'countryCode' ? '' : $t('login.countryCode')
"
@focus="inputFocus = 'phone'"
@focus="inputFocus = 'countryCode'"
@blur="inputFocus = ''"
@keyup.enter="login"
/>
@ -87,14 +90,14 @@
</button>
</div>
<div class="other-login">
<a v-show="mode !== 'email'" @click="changeMode('email')">{{
<!-- <a v-show="mode !== 'email'" @click="changeMode('email')">{{
$t('login.loginWithEmail')
}}</a>
<span v-show="mode === 'qrCode'">|</span>
<a v-show="mode !== 'phone'" @click="changeMode('phone')">{{
$t('login.loginWithPhone')
}}</a>
<span v-show="mode !== 'qrCode'">|</span>
<span v-show="mode !== 'qrCode'">|</span> -->
<a v-show="mode !== 'qrCode'" @click="changeMode('qrCode')">
二维码登录
</a>

View file

@ -248,7 +248,11 @@
@dblclick="clickLyricLine(line.time, true)"
>
<div class="content">
<span v-if="line.contents[0]">{{ line.contents[0] }}</span>
<span
v-if="line.contents[0]"
@click.right="openLyricMenu($event, line, 0)"
>{{ line.contents[0] }}</span
>
<br />
<span
v-if="
@ -256,10 +260,26 @@
$store.state.settings.showLyricsTranslation
"
class="translation"
@click.right="openLyricMenu($event, line, 1)"
>{{ line.contents[1] }}</span
>
</div>
</div>
<ContextMenu v-if="!noLyric" ref="lyricMenu">
<div class="item" @click="copyLyric(false)">{{
$t('contextMenu.copyLyric')
}}</div>
<div
v-if="
rightClickLyric &&
rightClickLyric.contents[1] &&
$store.state.settings.showLyricsTranslation
"
class="item"
@click="copyLyric(true)"
>{{ $t('contextMenu.copyLyricWithTranslation') }}</div
>
</ContextMenu>
</div>
</transition>
</div>
@ -268,6 +288,12 @@
<svg-icon icon-class="arrow-down" />
</button>
</div>
<div class="close-button" style="left: 24px" @click="fullscreen">
<button>
<svg-icon v-if="isFullscreen" icon-class="fullscreen-exit" />
<svg-icon v-else icon-class="fullscreen" />
</button>
</div>
</div>
</transition>
</template>
@ -278,9 +304,10 @@
import { mapState, mapMutations, mapActions } from 'vuex';
import VueSlider from 'vue-slider-component';
import ContextMenu from '@/components/ContextMenu.vue';
import { formatTrackTime } from '@/utils/common';
import { getLyric } from '@/api/track';
import { lyricParser } from '@/utils/lyrics';
import { lyricParser, copyLyric } from '@/utils/lyrics';
import ButtonIcon from '@/components/ButtonIcon.vue';
import * as Vibrant from 'node-vibrant/dist/vibrant.worker.min.js';
import Color from 'color';
@ -293,6 +320,7 @@ export default {
components: {
VueSlider,
ButtonIcon,
ContextMenu,
},
data() {
return {
@ -305,6 +333,8 @@ export default {
minimize: true,
background: '',
date: this.formatTime(new Date()),
isFullscreen: !!document.fullscreenElement,
rightClickLyric: null,
};
},
computed: {
@ -435,6 +465,15 @@ export default {
this.getLyric();
this.getCoverColor();
this.initDate();
document.addEventListener('keydown', e => {
if (e.key === 'F11') {
e.preventDefault();
this.fullscreen();
}
});
document.addEventListener('fullscreenchange', () => {
this.isFullscreen = !!document.fullscreenElement;
});
},
beforeDestroy: function () {
if (this.timer) {
@ -466,6 +505,13 @@ export default {
second.padStart(2, '0')
);
},
fullscreen() {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.documentElement.requestFullscreen();
}
},
addToPlaylist() {
if (!isAccountLoggedIn()) {
this.showToast(locale.t('toast.needToLogin'));
@ -564,6 +610,21 @@ export default {
this.player.play();
}
},
openLyricMenu(e, lyric, idx) {
this.rightClickLyric = { ...lyric, idx };
this.$refs.lyricMenu.openMenu(e);
e.preventDefault();
},
copyLyric(withTranslation) {
if (this.rightClickLyric) {
const idx = this.rightClickLyric.idx;
if (!withTranslation) {
copyLyric(this.rightClickLyric.contents[idx]);
} else {
copyLyric(this.rightClickLyric.contents.join(' '));
}
}
},
setLyricsInterval() {
this.lyricsInterval = setInterval(() => {
const progress = this.player.seek(null, false) ?? 0;
@ -903,6 +964,7 @@ export default {
transform-origin: center left;
transform: scale(0.95);
transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
user-select: none;
span {
opacity: 0.28;

View file

@ -57,7 +57,9 @@ export default {
this.player.current + 1,
this.player.current + 100
);
return this.tracks.filter(t => trackIDs.includes(t.id));
return trackIDs
.map(tid => this.tracks.find(t => t.id === tid))
.filter(t => t);
},
playNextList() {
return this.player.playNextList;

View file

@ -641,6 +641,33 @@
<button @click="sendProxyConfig">更新代理</button>
</div>
</div>
<div v-if="isElectron">
<h3>Real IP</h3>
<div class="item">
<div class="left">
<div class="title"> Real IP </div>
</div>
<div class="right">
<div class="toggle">
<input
id="enable-real-ip"
v-model="enableRealIP"
type="checkbox"
name="enable-real-ip"
/>
<label for="enable-real-ip"></label>
</div>
</div>
</div>
<div id="real-ip" :class="{ disabled: !enableRealIP }">
<input
v-model="realIP"
class="text-input"
placeholder="IP地址"
:disabled="!enableRealIP"
/>
</div>
</div>
<div v-if="isElectron">
<h3>快捷键</h3>
@ -1124,6 +1151,28 @@ export default {
});
},
},
enableRealIP: {
get() {
return this.settings.enableRealIP || false;
},
set(value) {
this.$store.commit('updateSettings', {
key: 'enableRealIP',
value: value,
});
},
},
realIP: {
get() {
return this.settings.realIP || '';
},
set(value) {
this.$store.commit('updateSettings', {
key: 'realIP',
value: value,
});
},
},
proxyPort: {
get() {
return this.settings.proxyConfig?.port || '';
@ -1566,11 +1615,13 @@ input[type='number'] {
-moz-appearance: textfield;
}
#proxy-form {
#proxy-form,
#real-ip {
display: flex;
align-items: center;
}
#proxy-form.disabled {
#proxy-form.disabled,
#real-ip.disabled {
opacity: 0.47;
button:hover {
transform: unset;

1404
yarn.lock

File diff suppressed because it is too large Load diff