Merge branch 'master' into electron

This commit is contained in:
kunkka 2020-10-26 10:57:02 +08:00
commit ed0dbc842a
42 changed files with 826 additions and 331 deletions

View File

@ -26,6 +26,8 @@
- 🖥️ 支持 PWA可在 Chrome/Edge 里点击地址栏右边的 安装到电脑
- 🙉 支持显示歌曲和专辑的 Explicit 标志
- 📺 MV 播放
- ✔️ 每日自动签到(手机端和电脑端同时签到)
- 🌚 Light/Dark Mode 自动切换
- 🚫🤝 无任何社交功能
- 🛠 更多特性开发中
@ -60,14 +62,9 @@ npm run build
## ☑️ Todo
- 中文支持
- Dark Mode
- 歌词
- 私人 FM
- 播放记录
- 无限播放模式(播放完列表后自动播放相似歌曲)
查看 Todo 请访问本项目的 [Projects](https://github.com/qier222/YesPlayMusic/projects/1)
欢迎提 issue 和 pull request。
欢迎提 Issue 和 Pull request。
## 📜 开源许可

View File

@ -50,11 +50,36 @@ export default {
<style lang="scss">
@import url("https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,500;0,600;0,700;0,800;0,900;1,500;1,600;1,700;1,800;1,900&display=swap");
:root {
--color-body-bg: #ffffff;
--color-text: #000;
--color-primary: #335eea;
--color-primary-bg: #eaeffd;
--color-secondary: #7a7a7b;
--color-secondary-bg: #f5f5f7;
--color-navbar-bg: rgba(255, 255, 255, 0.86);
}
[data-theme="dark"] {
--color-body-bg: #222222;
--color-text: #ffffff;
--color-primary: #335eea;
--color-primary-bg: #bbcdff;
--color-secondary: #7a7a7b;
--color-secondary-bg: #323232;
--color-navbar-bg: #335eea;
--color-navbar-bg: rgba(34, 34, 34, 0.86);
}
#app {
font-family: "Barlow", -apple-system, BlinkMacSystemFont, Helvetica Neue,
PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC,
WenQuanYi Micro Hei, sans-serif;
width: 100%;
transition: all 0.4s;
}
body {
background-color: var(--color-body-bg);
}
html {
@ -102,12 +127,13 @@ a {
::-webkit-scrollbar-track {
background: transparent;
border-left: 1px solid rgba(128, 128, 128, 0.18);
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: rgb(216, 216, 216);
background: var(--color-secondary-bg);
}
.slide-up-enter-active,

View File

@ -1,6 +1,11 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
/**
* 获取专辑内容
* 说明 : 调用此接口 , 传入专辑 id, 可获得专辑内容
* @param {number} id
*/
export function getAlbum(id) {
return request({
url: "/album",
@ -14,10 +19,18 @@ export function getAlbum(id) {
});
}
/**
* 全部新碟
* 说明 : 登录后调用此接口 ,可获取全部新碟
* - limit - 返回数量 , 默认为 30
* - offset - 偏移数量用于分页 , :( 页数 -1)*30, 其中 30 limit 的值 , 默认为 0
* - area - ALL:全部,ZH:华语,EA:欧美,KR:韩国,JP:日本
* @param {Object} params
* @param {number} params.limit
* @param {number=} params.offset
* @param {string} params.area
*/
export function newAlbums(params) {
// limit : 返回数量 , 默认为 30
// offset : 偏移数量,用于分页 , 如 :( 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
// area : ALL:全部,ZH:华语,EA:欧美,KR:韩国,JP:日本
return request({
url: "/album/new",
method: "get",

View File

@ -1,6 +1,11 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
/**
* 获取歌手单曲
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手部分信息和热门歌曲
* @param {number} id - 歌手 id, 可由搜索接口获得
*/
export function getArtist(id) {
return request({
url: "/artists",
@ -14,10 +19,18 @@ export function getArtist(id) {
});
}
/**
* 获取歌手专辑
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手专辑内容
* - id: 歌手 id
* - limit: 取出数量 , 默认为 50
* - offset: 偏移数量 , 用于分页 , :( 页数 -1)*50, 其中 50 limit 的值 , 默认为 0
* @param {Object} params
* @param {number} params.id
* @param {number=} params.limit
* @param {number=} params.offset
*/
export function getArtistAlbum(params) {
// 必选参数 : id: 歌手 id
// 可选参数 : limit: 取出数量 , 默认为 50
// offset: 偏移数量 , 用于分页 , 如 :( 页数 -1)*50, 其中 50 为 limit 的值 , 默认 为 0
return request({
url: "/artist/album",
method: "get",
@ -25,12 +38,17 @@ export function getArtistAlbum(params) {
});
}
/**
* 歌手榜
* 说明 : 调用此接口 , 可获取排行榜中的歌手榜
* - type : 地区
* 1: 华语
* 2: 欧美
* 3: 韩国
* 4: 日本
* @param {number=} type
*/
export function toplistOfArtists(type = null) {
// type : 地区
// 1: 华语
// 2: 欧美
// 3: 韩国
// 4: 日本
return request({
url: "/toplist/artist",
method: "get",
@ -39,7 +57,11 @@ export function toplistOfArtists(type = null) {
},
});
}
/**
* 获取歌手 mv
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手 mv 信息 , 具体 mv 播放地址可调 /mv传入此接口获得的 mvid 来拿到 , : /artist/mv?id=6452,/mv?mvid=5461064
* @param {number} id 歌手 id, 可由搜索接口获得
*/
export function artistMv(id) {
return request({
url: "/artist/mv",

View File

@ -1,25 +1,35 @@
import request from "@/utils/request";
/**
* 手机登录
* - phone: 手机号码
* - password: 密码
* - countrycode: 国家码用于国外手机号登录例如美国传入1
* - md5_password: md5加密后的密码,传入后 password 将失效
* @param {Object} params
* @param {string} params.phone
* @param {string} params.password
* @param {string=} params.countrycode
* @param {string=} params.md5_password
*/
export function loginWithPhone(params) {
//必选参数 :
// phone: 手机号码
// password: 密码
// 可选参数 :
// countrycode: 国家码用于国外手机号登录例如美国传入1
// md5_password: md5加密后的密码,传入后 password 将失效
return request({
url: "/login/cellphone",
method: "post",
params,
});
}
/**
* 邮箱登录
* - email: 163 网易邮箱
* - password: 密码
* - md5_password: md5加密后的密码,传入后 password 将失效
* @param {Object} params
* @param {string} params.email
* @param {string} params.password
* @param {string=} params.md5_password
*/
export function loginWithEmail(params) {
// 必选参数 :
// email: 163 网易邮箱
// password: 密码
// 可选参数 :
// md5_password: md5加密后的密码,传入后 password 将失效
return request({
url: "/login",
method: "post",
@ -27,13 +37,22 @@ export function loginWithEmail(params) {
});
}
export function loginRefresh() {
/**
* 刷新登录
* 说明 : 调用此接口 , 可刷新登录状态
* - 调用例子 : /login/refresh
*/
export function refreshCookie() {
return request({
url: "/login/refresh",
method: "post",
});
}
/**
* 退出登录
* 说明 : 调用此接口 , 可退出登录
*/
export function logout() {
return request({
url: "/logout",

View File

@ -1,5 +1,12 @@
import request from "@/utils/request";
/**
* 获取 mv 数据
* 说明 : 调用此接口 , 传入 mvid ( 在搜索音乐的时候传 type=1004 获得 ) , 可获取对应 MV 数据 , 数据包含 mv 名字 , 歌手 , 发布时间 , mv 视频地址等数据 ,
* 其中 mv 视频 网易做了防盗链处理 , 可能不能直接播放 , 需要播放的话需要调用 ' mv 地址' 接口
* - 调用例子 : /mv/detail?mvid=5436712
* @param {number} mvid mv id
*/
export function mvDetail(mvid) {
return request({
url: "/mv/detail",
@ -10,16 +17,29 @@ export function mvDetail(mvid) {
});
}
/**
* mv 地址
* 说明 : 调用此接口 , 传入 mv id,可获取 mv 播放地址
* - id: mv id
* - r: 分辨率,默认1080,可从 /mv/detail 接口获取分辨率列表
* - 调用例子 : /mv/url?id=5436712 /mv/url?id=10896407&r=1080
* @param {Object} params
* @param {number} params.id
* @param {number=} params.r
*/
export function mvUrl(params) {
// 必选参数 : id: mv id
// 可选参数 : r: 分辨率,默认1080,可从 /mv/detail 接口获取分辨率列表
return request({
url: "/mv/url",
method: "get",
params,
});
}
/**
* 相似 mv
* 说明 : 调用此接口 , 传入 mvid 可获取相似 mv
* @param {number} mvid
*/
export function simiMv(mvid) {
return request({
url: "/simi/mv",

View File

@ -1,6 +1,21 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
/**
* 搜索
* 说明 : 调用此接口 , 传入搜索关键词可以搜索该音乐 / 专辑 / 歌手 / 歌单 / 用户 , 关键词可以多个 , 以空格隔开 ,
* " 周杰伦 搁浅 "( 不需要登录 ), 搜索获取的 mp3url 不能直接用 , 可通过 /song/url 接口传入歌曲 id 获取具体的播放链接
* - keywords : 关键词
* - limit : 返回数量 , 默认为 30
* - offset : 偏移数量用于分页 , : :( 页数 -1)*30, 其中 30 limit 的值 , 默认为 0
* - type: 搜索类型默认为 1 即单曲 , 取值意义 : 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频, 1018:综合
* - 调用例子 : /search?keywords= /cloudsearch?keywords=()
* @param {Object} params
* @param {string} params.keywords
* @param {number=} params.limit
* @param {number=} params.offset
* @param {number=} params.type
*/
export function search(params) {
return request({
url: "/search",

View File

@ -1,23 +1,44 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
/**
* 推荐歌单
* 说明 : 调用此接口 , 可获取推荐歌单
* - limit: 取出数量 , 默认为 30 (不支持 offset)
* - 调用例子 : /personalized?limit=1
* @param {Object} params
* @param {number=} params.limit
*/
export function recommendPlaylist(params) {
// limit: 取出数量 , 默认为 30
return request({
url: "/personalized",
method: "get",
params,
});
}
/**
* 获取每日推荐歌单
* 说明 : 调用此接口 , 可获得每日推荐歌单 ( 需要登录 )
* @param {Object} params
* @param {number=} params.limit
*/
export function dailyRecommendPlaylist(params) {
// limit: 取出数量 , 默认为 30
return request({
url: "/recommend/resource",
method: "get",
params,
});
}
/**
* 获取歌单详情
* 说明 : 歌单能看到歌单名字, 但看不到具体歌单内容 , 调用此接口 , 传入歌单 id, 可以获取对应歌单内的所有的音乐(未登录状态只能获取不完整的歌单,登录后是完整的)
* 但是返回的trackIds是完整的tracks 则是不完整的可拿全部 trackIds 请求一次 song/detail 接口
* 获取所有歌曲的详情 (https://github.com/Binaryify/NeteaseCloudMusicApi/issues/452)
* - id : 歌单 id
* - s : 歌单最近的 s 个收藏者, 默认为8
* @param {number} id
* @param {boolean=} noCache
*/
export function getPlaylistDetail(id, noCache = false) {
let params = { id };
if (noCache) params.timestamp = new Date().getTime();
@ -30,11 +51,18 @@ export function getPlaylistDetail(id, noCache = false) {
return data;
});
}
/**
* 获取精品歌单
* 说明 : 调用此接口 , 可获取精品歌单
* - cat: tag, 比如 " 华语 "" 古风 " " 欧美 "" 流行 ", 默认为 "全部", 可从精品歌单标签列表接口获取(/playlist/highquality/tags)
* - limit: 取出歌单数量 , 默认为 20
* - before: 分页参数,取上一页最后一个歌单的 updateTime 获取下一页数据
* @param {Object} params
* @param {string} params.cat
* @param {number=} params.limit
* @param {number} params.before
*/
export function highQualityPlaylist(params) {
// 可选参数: cat: tag, 比如 " 华语 "、" 古风 " 、" 欧美 "、" 流行 ", 默认为 "全部", 可从精品歌单标签列表接口获取(/playlist/highquality / tags)
// limit: 取出歌单数量 , 默认为 20
// before: 分页参数,取上一页最后一个歌单的 updateTime 获取下一页数据
return request({
url: "/top/playlist/highquality",
method: "get",
@ -42,11 +70,18 @@ export function highQualityPlaylist(params) {
});
}
/**
* 歌单 ( 网友精选碟 )
* 说明 : 调用此接口 , 可获取网友精选碟歌单
* - order: 可选值为 'new' 'hot', 分别对应最新和最热 , 默认为 'hot'
* - cat: tag, 比如 " 华语 "" 古风 " " 欧美 "" 流行 ", 默认为 "全部",可从歌单分类接口获取(/playlist/catlist)
* - limit: 取出歌单数量 , 默认为 50
* @param {Object} params
* @param {string} params.order
* @param {string} params.cat
* @param {number=} params.limit
*/
export function topPlaylist(params) {
// 可选参数 : order: 可选值为 'new' 和 'hot', 分别对应最新和最热 , 默认为 'hot'
// cat:cat: tag, 比如 " 华语 "、" 古风 " 、" 欧美 "、" 流行 ", 默认为 "全部",可从歌单分类接口获取(/playlist/catlist)
// limit: 取出歌单数量 , 默认为 50
// offset: 偏移数量 , 用于分页 , 如 :( 评论页数 -1)*50, 其中 50 为 limit 的值
return request({
url: "/top/playlist",
method: "get",
@ -54,23 +89,36 @@ export function topPlaylist(params) {
});
}
/**
* 歌单分类
* 说明 : 调用此接口,可获取歌单分类,包含 category 信息
*/
export function playlistCatlist() {
return request({
url: "/playlist/catlist",
method: "get",
});
}
/**
* 所有榜单
* 说明 : 调用此接口,可获取所有榜单 接口地址 : /toplist
*/
export function toplists() {
return request({
url: "/toplist",
method: "get",
});
}
/**
* 收藏/取消收藏歌单
* 说明 : 调用此接口, 传入类型和歌单 id 可收藏歌单或者取消收藏歌单
* - t : 类型,1:收藏,2:取消收藏
* - id : 歌单 id
* @param {Object} params
* @param {number} params.t
* @param {number} params.id
*/
export function subscribePlaylist(params) {
// 必选参数 :
// t : 类型,1:收藏,2:取消收藏 id : 歌单 id
return request({
url: "/playlist/subscribe",
method: "get",

View File

@ -1,7 +1,12 @@
import store from "@/store";
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
import store from "@/store";
/**
* 获取音乐 url
* 说明 : 使用歌单详情接口后 , 能得到的音乐的 id, 但不能得到的音乐 url, 调用此接口, 传入的音乐 id( 可多个 , 用逗号隔开 ), 可以获取对应的音乐的 url,
* !!!未登录状态返回试听片段(返回字段包含被截取的正常歌曲的开始时间和结束时间)
* @param {string} id - 音乐的 id例如 id=405998841,33894312
*/
export function getMP3(id) {
let br =
store.state.settings?.musicQuality !== undefined
@ -16,37 +21,44 @@ export function getMP3(id) {
},
});
}
export function getTrackDetail(id) {
/**
* 获取歌曲详情
* 说明 : 调用此接口 , 传入音乐 id(支持多个 id, , 隔开), 可获得歌曲详情(注意:歌曲封面现在需要通过专辑内容接口获取)
* @param {string} ids - 音乐 id, 例如 ids=405998841,33894312
*/
export function getTrackDetail(ids) {
return request({
url: "/song/detail",
method: "get",
params: {
ids: id,
ids,
},
}).then((data) => {
data.songs = mapTrackPlayableStatus(data.songs);
return data;
});
}
/**
* 获取歌词
* 说明 : 调用此接口 , 传入音乐 id 可获得对应音乐的歌词 ( 不需要登录 )
* @param {number} id - 音乐 id
*/
export function getLyric(id) {
return request({
url: "/lyric",
method: "get",
params: {
id: id,
id,
},
});
}
/**
* 新歌速递
* 说明 : 调用此接口 , 可获取新歌速递
* @param {number} type - 地区类型 id, 对应以下: 全部:0 华语:7 欧美:96 日本:8 韩国:16
*/
export function topSong(type) {
// type: 地区类型 id,对应以下:
// 全部:0
// 华语:7
// 欧美:96
// 日本:8
// 韩国:16
return request({
url: "/top/song",
method: "get",
@ -55,10 +67,16 @@ export function topSong(type) {
},
});
}
/**
* 喜欢音乐
* 说明 : 调用此接口 , 传入音乐 id, 可喜欢该音乐
* - id - 歌曲 id
* - like - 默认为 true 即喜欢 , 若传 false, 则取消喜欢
* @param {Object} params
* @param {number} params.id
* @param {boolean=} [params.like]
*/
export function likeATrack(params) {
// 必选参数: id: 歌曲 id
// 可选参数 : like: 布尔值 , 默认为 true 即喜欢 , 若传 false, 则取消喜欢
params.timestamp = new Date().getTime();
return request({
url: "/like",
@ -67,9 +85,18 @@ export function likeATrack(params) {
});
}
/**
* 听歌打卡
* 说明 : 调用此接口 , 传入音乐 id, 来源 id歌曲时间 time更新听歌排行数据
* - id - 歌曲 id
* - sourceid - 歌单或专辑 id
* - time - 歌曲播放时间,单位为秒
* @param {Object} params
* @param {number} params.id
* @param {number} params.sourceid
* @param {number=} params.time
*/
export function scrobble(params) {
// 必选参数 : id: 歌曲 id, sourceid: 歌单或专辑 id
// 可选参数 : time: 歌曲播放时间,单位为秒
params.timestamp = new Date().getTime();
return request({
url: "/scrobble",

View File

@ -1,19 +1,11 @@
import request from "@/utils/request";
export function login(params) {
// 必选参数 :
// phone: 手机号码
// password: 密码
// 可选参数 :
// countrycode: 国家码用于国外手机号登陆例如美国传入1
// md5_password: md5加密后的密码,传入后 password 将失效
return request({
url: "/login/cellphone",
method: "get",
params,
});
}
/**
* 获取用户详情
* 说明 : 登录后调用此接口 , 传入用户 id, 可以获取用户详情
* - uid : 用户 id
* @param {number} uid
*/
export function userDetail(uid) {
return request({
url: "/user/detail",
@ -24,9 +16,18 @@ export function userDetail(uid) {
});
}
/**
* 获取用户歌单
* 说明 : 登录后调用此接口 , 传入用户 id, 可以获取用户歌单
* - uid : 用户 id
* - limit : 返回数量 , 默认为 30
* - offset : 偏移数量用于分页 , :( 页数 -1)*30, 其中 30 limit 的值 , 默认为 0
* @param {Object} params
* @param {number} params.uid
* @param {number} params.limit
* @param {number=} params.offset
*/
export function userPlaylist(params) {
// limit : 返回数量 , 默认为 30
// offset : 偏移数量,用于分页 , 如 :( 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
return request({
url: "/user/playlist",
method: "get",
@ -34,6 +35,12 @@ export function userPlaylist(params) {
});
}
/**
* 喜欢音乐列表
* 说明 : 调用此接口 , 传入用户 id, 可获取已喜欢音乐id列表(id数组)
* - uid: 用户 id
* @param {number} uid
*/
export function userLikedSongsIDs(uid) {
return request({
url: "/likelist",
@ -44,3 +51,14 @@ export function userLikedSongsIDs(uid) {
},
});
}
export function dailySignin(type = 0) {
//可选参数 : type: 签到类型 , 默认 0, 其中 0 为安卓端签到 ,1 为 web/PC 签到
return request({
url: "/daily_signin",
method: "post",
params: {
type,
},
});
}

View File

@ -1,6 +1,6 @@
/* rail style */
.vue-slider-rail {
background-color: #eee;
background-color: rgba(128, 128, 128, 0.18);
border-radius: 15px;
}
@ -54,7 +54,8 @@
/* volume style */
.volume-control .vue-slider-process {
background-color: rgba(0, 0, 0, 0.8);
opacity: 0.8;
background-color: var(--color-text);
border-radius: 15px;
}

View File

@ -19,7 +19,7 @@ button {
border-radius: 25%;
transition: 0.2s;
.svg-icon {
color: rgba(0, 0, 0, 0.88);
color: var(--color-text);
height: 16px;
width: 16px;
}
@ -27,7 +27,7 @@ button {
margin-left: 0;
}
&:hover {
background: #f5f5f7;
background: var(--color-secondary-bg);
}
&:active {
transform: scale(0.92);

View File

@ -54,8 +54,8 @@ button {
justify-content: center;
font-size: 18px;
font-weight: 600;
background-color: rgba(51, 94, 234, 0.1);
color: #335eea;
background-color: var(--color-primary-bg);
color: var(--color-primary);
margin-right: 12px;
transition: 0.2s;
.svg-icon {
@ -70,8 +70,8 @@ button {
}
}
button.grey {
background-color: #f5f5f7;
color: rgba(0, 0, 0, 0.5);
background-color: var(--color-secondary-bg);
color: var(--color-secondary);
}
button.transparent {
background-color: transparent;

View File

@ -163,6 +163,7 @@ export default {
filter: blur(16px) opacity(0.6);
z-index: -1;
height: 208px;
transform: scale(0.98);
}
.play-button {
opacity: 0;

View File

@ -129,13 +129,13 @@ export default {
.item {
margin: 12px 12px 24px 12px;
color: var(--color-text);
.text {
width: 208px;
margin-top: 8px;
.name {
font-size: 16px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
line-height: 20px;
display: -webkit-box;
@ -145,7 +145,7 @@ export default {
}
.info {
font-size: 12px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
line-height: 18px;
display: -webkit-box;
-webkit-box-orient: vertical;
@ -170,7 +170,8 @@ export default {
}
.explicit-symbol {
color: rgba(0, 0, 0, 0.28);
opacity: 0.28;
color: var(--color-text);
float: right;
.svg-icon {
margin-bottom: -3px;
@ -178,7 +179,8 @@ export default {
}
.lock-icon {
color: rgba(0, 0, 0, 0.28);
opacity: 0.28;
color: var(--color-text);
margin-right: 4px;
// float: right;
.svg-icon {
@ -189,7 +191,8 @@ export default {
.play-count {
font-weight: 600;
color: rgba(0, 0, 0, 0.58);
opacity: 0.58;
color: var(--color-text);
font-size: 12px;
.svg-icon {
margin-right: 3px;

View File

@ -76,11 +76,12 @@ export default {
.mv {
margin: 12px 12px 24px 12px;
width: 204px;
color: var(--color-text);
.title {
font-size: 16px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
@ -88,7 +89,7 @@ export default {
}
.artist {
font-size: 12px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;

View File

@ -24,7 +24,10 @@
>
</div>
<div class="right-part">
<a href="https://github.com/qier222/YesPlayMusic" target="blank"
<a
href="https://github.com/qier222/YesPlayMusic"
target="blank"
v-if="settings.showGithubIcon !== false"
><svg-icon icon-class="github" class="github"
/></a>
<div class="search-box">
@ -47,6 +50,7 @@
<script>
import ButtonIcon from "@/components/ButtonIcon.vue";
import { mapState } from "vuex";
export default {
name: "Navbar",
@ -60,6 +64,9 @@ export default {
langs: ["zh-CN", "en"],
};
},
computed: {
...mapState(["settings"]),
},
methods: {
go(where) {
if (where === "back") this.$router.go(-1);
@ -96,7 +103,10 @@ nav {
left: 10vw;
}
backdrop-filter: saturate(180%) blur(30px);
background-color: rgba(255, 255, 255, 0.86);
// background: var(--color-body-bg);
// background-color: rgba(255, 255, 255, 0.86);
background-color: var(--color-navbar-bg);
z-index: 100;
}
@ -120,15 +130,15 @@ nav {
text-decoration: none;
border-radius: 6px;
padding: 6px 10px;
color: black;
color: var(--color-text);
transition: 0.2s;
margin: {
right: 12px;
left: 12px;
}
&:hover {
background: #eaeffd;
color: #335eea;
background: var(--color-primary-bg);
color: var(--color-primary);
}
&:active {
transform: scale(0.92);
@ -136,7 +146,7 @@ nav {
}
}
a.active {
color: #335eea;
color: var(--color-primary);
}
}
@ -156,7 +166,7 @@ nav {
display: flex;
align-items: center;
height: 32px;
background: rgba(0, 0, 0, 0.06);
background: var(--color-secondary-bg);
border-radius: 8px;
width: 200px;
}
@ -164,7 +174,8 @@ nav {
.svg-icon {
height: 15px;
width: 15px;
color: #aaaaaa;
color: var(--color-text);
opacity: 0.28;
margin: {
left: 8px;
right: 4px;
@ -178,13 +189,15 @@ nav {
width: 96%;
font-weight: 600;
margin-top: -1px;
color: var(--color-text);
}
.active {
background: #eaeffd;
background: var(--color-primary-bg);
input,
.svg-icon {
color: #335eea;
opacity: 1;
color: var(--color-primary);
}
}
}
@ -198,6 +211,7 @@ nav {
margin-right: 16px;
height: 24px;
width: 24px;
color: var(--color-text);
}
}
</style>

View File

@ -36,7 +36,8 @@
</span>
</div>
</div>
<div class="like-button" v-show="isLoggedIn">
<!-- 账号登录才会显示 like 图标 -->
<div class="like-button" v-show="accountLogin">
<button-icon
@click.native="likeCurrentSong"
:title="$t('player.like')"
@ -118,7 +119,7 @@
<script>
import { updateMediaSessionMetaData } from "@/utils/mediaSession";
import { mapState, mapMutations, mapActions } from "vuex";
import { isLoggedIn } from "@/utils/auth";
import { isAccountLoggedIn } from "@/utils/auth";
import { userLikedSongsIDs } from "@/api/user";
import { likeATrack } from "@/api/track";
import "@/assets/css/slider.css";
@ -144,14 +145,14 @@ export default {
setInterval(() => {
this.progress = ~~this.howler.seek();
}, 1000);
if (this.isLoggedIn) {
if (isAccountLoggedIn()) {
userLikedSongsIDs(this.settings.user.userId).then((data) => {
this.updateLikedSongs(data.ids);
});
}
},
computed: {
...mapState(["player", "howler", "settings", "liked"]),
...mapState(["player", "howler", "settings", "liked", "accountLogin"]),
currentTrack() {
return this.player.currentTrack;
},
@ -174,9 +175,6 @@ export default {
let max = ~~(this.currentTrack.dt / 1000);
return max > 1 ? max - 1 : max;
},
isLoggedIn() {
return isLoggedIn();
},
},
methods: {
...mapMutations([
@ -296,7 +294,8 @@ export default {
justify-content: space-around;
height: 64px;
backdrop-filter: saturate(180%) blur(30px);
background-color: rgba(255, 255, 255, 0.86);
// background-color: rgba(255, 255, 255, 0.86);
background-color: var(--color-navbar-bg);
z-index: 100;
}
@ -336,7 +335,8 @@ export default {
.name {
font-weight: 600;
font-size: 16px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
color: var(--color-text);
margin-bottom: 4px;
cursor: pointer;
display: -webkit-box;
@ -350,7 +350,8 @@ export default {
}
.artist {
font-size: 12px;
color: rgba(0, 0, 0, 0.58);
opacity: 0.58;
color: var(--color-text);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
@ -396,7 +397,7 @@ export default {
}
}
.active .svg-icon {
color: #335eea;
color: var(--color-primary);
}
.volume-control {
margin-left: 4px;

View File

@ -60,7 +60,7 @@
<div></div>
</div>
<div class="actions" v-if="!isTracklist">
<button v-if="isLoggedIn" @click="likeThisSong">
<button v-if="accountLogin" @click="likeThisSong">
<svg-icon
icon-class="heart"
:style="{
@ -78,7 +78,7 @@
</template>
<script>
import { isLoggedIn } from "@/utils/auth";
import { mapState } from "vuex";
import ArtistsInLine from "@/components/ArtistsInLine.vue";
import ExplicitSymbol from "@/components/ExplicitSymbol.vue";
@ -93,6 +93,7 @@ export default {
return { focus: false, trackStyle: {} };
},
computed: {
...mapState(["accountLogin"]),
imgUrl() {
if (this.track.al !== undefined) return this.track.al.picUrl;
if (this.track.album !== undefined) return this.track.album.picUrl;
@ -127,9 +128,6 @@ export default {
if (this.isPlaying) trackClass.push("playing");
return trackClass;
},
isLoggedIn() {
return isLoggedIn();
},
},
methods: {
goToAlbum() {
@ -161,7 +159,7 @@ button {
.svg-icon {
height: 16px;
width: 16px;
color: #335eea;
color: var(--color-primary);
}
&:active {
transform: scale(0.92);
@ -182,12 +180,16 @@ button {
border-radius: 8px;
margin: 0 20px 0 10px;
width: 12px;
color: rgba(0, 0, 0, 0.58);
color: var(--color-text);
cursor: default;
span {
opacity: 0.58;
}
}
.explicit-symbol {
color: rgba(0, 0, 0, 0.28);
opacity: 0.28;
color: var(--color-text);
.svg-icon {
margin-bottom: -3px;
}
@ -223,7 +225,7 @@ button {
.title {
font-size: 18px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
color: var(--color-text);
cursor: default;
padding-right: 16px;
display: -webkit-box;
@ -235,13 +237,14 @@ button {
margin-right: 2px;
font-weight: 500;
font-size: 14px;
color: rgba(0, 0, 0, 0.72);
opacity: 0.72;
}
}
.artist {
margin-top: 2px;
font-size: 13px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
color: var(--color-text);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
@ -249,7 +252,7 @@ button {
a {
span {
margin-right: 3px;
color: rgba(0, 0, 0, 0.8);
opacity: 0.8;
}
&:hover {
text-decoration: underline;
@ -262,7 +265,8 @@ button {
flex: 1;
display: flex;
font-size: 16px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
color: var(--color-text);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
@ -276,10 +280,12 @@ button {
justify-content: flex-end;
margin-right: 10px;
font-variant-numeric: tabular-nums;
opacity: 0.88;
color: var(--color-text);
}
&:hover {
transition: all 0.3s;
background: #f5f5f7;
background: var(--color-secondary-bg);
}
}
.track.disable {
@ -292,7 +298,7 @@ button {
.time,
.no,
.featured {
color: rgba(0, 0, 0, 0.28) !important;
opacity: 0.28 !important;
}
&:hover {
background: none;
@ -327,24 +333,22 @@ button {
}
.track.playing {
background: #eaeffd;
color: #335eea;
background: var(--color-primary-bg);
color: var(--color-primary);
.title,
.album {
color: #335eea;
.album,
.time {
color: var(--color-primary);
}
.title .featured,
.artist {
color: #335eea;
.artist,
.explicit-symbol {
color: var(--color-primary);
opacity: 0.88;
}
.no span {
color: #335eea;
color: var(--color-primary);
opacity: 0.78;
}
.explicit-symbol {
color: #335eea;
opacity: 0.88;
}
}
</style>

View File

@ -108,5 +108,11 @@ export default {
high: "High",
lossless: "Lossless",
},
appearance: {
text: "Appearance",
auto: "Auto",
light: "Light",
dark: "Dark",
},
},
};

View File

@ -6,9 +6,6 @@ export default {
library: "资料库",
search: "搜索",
},
footer: {
settings: "设置",
},
home: {
recommendPlaylist: "推荐歌单",
recommendArtist: "推荐歌手",
@ -34,7 +31,7 @@ export default {
albums: "专辑",
withAlbums: "张专辑",
artist: "歌手",
videos: "个视频",
videos: "个MV",
},
album: {
released: "发行于",
@ -103,7 +100,7 @@ export default {
songs: "首歌",
},
settings: {
settings: "选项",
settings: "设置",
logout: "登出",
language: "语言",
musicQuality: {
@ -113,5 +110,11 @@ export default {
high: "极高",
lossless: "无损",
},
appearance: {
text: "外观",
auto: "自动",
light: "浅色",
dark: "深色",
},
},
};

View File

@ -8,6 +8,7 @@ import "@/assets/icons";
import "@/utils/filters";
import { initMediaSession } from "@/utils/mediaSession";
import "./registerServiceWorker";
import { dailyTask } from "@/utils/common";
import * as Sentry from "@sentry/browser";
import { Vue as VueIntegration } from "@sentry/integrations";
@ -23,7 +24,6 @@ Vue.config.productionTip = false;
initMediaSession();
if (process.env.VUE_APP_ENABLE_SENTRY === "true") {
console.log("VUE_APP_ENABLE_SENTRY");
Sentry.init({
dsn:
"https://30aaa25152974f48971912a394ab6bc3@o436528.ingest.sentry.io/5477409",
@ -41,6 +41,8 @@ if (process.env.VUE_APP_ENABLE_SENTRY === "true") {
});
}
dailyTask();
new Vue({
i18n,
store,

View File

@ -3,7 +3,7 @@ import VueRouter from "vue-router";
import store from "@/store";
import NProgress from "nprogress";
import "@/assets/css/nprogress.css";
import Cookies from "js-cookie";
import { isLooseLoggedIn } from "@/utils/auth";
NProgress.configure({ showSpinner: false, trickleSpeed: 100 });
@ -12,7 +12,7 @@ const routes = [
{
path: "/",
name: "home",
component: () => import("@/views/home"),
component: () => import("@/views/home.vue"),
meta: {
keepAlive: true,
},
@ -20,32 +20,32 @@ const routes = [
{
path: "/login",
name: "login",
component: () => import("@/views/login"),
component: () => import("@/views/login.vue"),
},
{
path: "/login/username",
name: "loginUsername",
component: () => import("@/views/loginUsername"),
component: () => import("@/views/loginUsername.vue"),
},
{
path: "/login/account",
name: "loginAccount",
component: () => import("@/views/loginAccount"),
component: () => import("@/views/loginAccount.vue"),
},
{
path: "/playlist/:id",
name: "playlist",
component: () => import("@/views/playlist"),
component: () => import("@/views/playlist.vue"),
},
{
path: "/album/:id",
name: "album",
component: () => import("@/views/album"),
component: () => import("@/views/album.vue"),
},
{
path: "/artist/:id",
name: "artist",
component: () => import("@/views/artist"),
component: () => import("@/views/artist.vue"),
meta: {
keepAlive: true,
},
@ -53,12 +53,12 @@ const routes = [
{
path: "/mv/:id",
name: "mv",
component: () => import("@/views/mv"),
component: () => import("@/views/mv.vue"),
},
{
path: "/next",
name: "next",
component: () => import("@/views/next"),
component: () => import("@/views/next.vue"),
meta: {
keepAlive: true,
},
@ -66,17 +66,17 @@ const routes = [
{
path: "/search",
name: "search",
component: () => import("@/views/search"),
component: () => import("@/views/search.vue"),
},
{
path: "/new-album",
name: "newAlbum",
component: () => import("@/views/newAlbum"),
component: () => import("@/views/newAlbum.vue"),
},
{
path: "/explore",
name: "explore",
component: () => import("@/views/explore"),
component: () => import("@/views/explore.vue"),
meta: {
keepAlive: true,
},
@ -84,7 +84,7 @@ const routes = [
{
path: "/library",
name: "library",
component: () => import("@/views/library"),
component: () => import("@/views/library.vue"),
meta: {
requireLogin: true,
keepAlive: true,
@ -93,7 +93,7 @@ const routes = [
{
path: "/library/liked-songs",
name: "likedSongs",
component: () => import("@/views/playlist"),
component: () => import("@/views/playlist.vue"),
meta: {
requireLogin: true,
},
@ -101,7 +101,7 @@ const routes = [
{
path: "/settings",
name: "settings",
component: () => import("@/views/settings"),
component: () => import("@/views/settings.vue"),
},
];
const router = new VueRouter({
@ -116,17 +116,15 @@ const router = new VueRouter({
});
router.beforeEach((to, from, next) => {
// 需要登录的逻辑
if (to.meta.requireLogin) {
if (store.state.settings.user.nickname === undefined) {
next({ path: "/login" });
}
if (
Cookies.get("MUSIC_U") === undefined &&
Cookies.get("loginMode") === "account"
) {
next({ path: "/login" });
} else {
if (isLooseLoggedIn()) {
next();
} else {
next({ path: "/login" });
}
} else {
next();

View File

@ -1,6 +1,6 @@
import { updateMediaSessionMetaData } from "@/utils/mediaSession";
import { getTrackDetail, scrobble, getMP3 } from "@/api/track";
import { isLoggedIn } from "@/utils/auth";
import { isAccountLoggedIn } from "@/utils/auth";
import { updateHttps } from "@/utils/common";
export default {
@ -41,8 +41,7 @@ export default {
dispatch("nextTrack");
});
}
if (isLoggedIn) {
if (isAccountLoggedIn()) {
getMP3(track.id).then((data) => {
// 未知情况下会没有返回数据导致报错,增加防范逻辑
if (data.data[0]) {

View File

@ -5,6 +5,7 @@ import mutations from "./mutations";
import actions from "./actions";
import initState from "./initState";
import { Howl, Howler } from "howler";
import { changeAppearance } from "@/utils/common";
if (localStorage.getItem("appVersion") === null) {
localStorage.setItem("player", JSON.stringify(initState.player));
@ -45,4 +46,13 @@ if ([undefined, null].includes(store.state.settings.lang)) {
localStorage.setItem("settings", JSON.stringify(store.state.settings));
}
changeAppearance(store.state.settings.appearance);
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => {
if (store.state.settings.appearance === "auto") {
changeAppearance(store.state.settings.appearance);
}
});
export default store;

View File

@ -1,5 +1,7 @@
const initState = {
howler: null,
accountLogin: false,
usernameLogin: false,
liked: {
songs: [],
},
@ -85,7 +87,11 @@ const initState = {
id: 0,
},
lang: null,
appearance: "auto",
musicQuality: 320000,
showGithubIcon: true,
showPlaylistsByAppleMusic: true,
lastRefreshCookieDate: 0,
},
};

View File

@ -81,6 +81,16 @@ export default {
return track;
});
},
updateAccountLogin(state, status) {
state.accountLogin = status;
},
updateUsernameLogin(state, status) {
state.usernameLogin = status;
},
updateLogout() {
this.commit("updateAccountLogin", false);
this.commit("updateUsernameLogin", false);
},
updateUser(state, user) {
state.settings.user = user;
},
@ -106,4 +116,7 @@ export default {
changeMusicQuality(state, value) {
state.settings.musicQuality = value;
},
updateSettings(state, { key, value }) {
state.settings[key] = value;
},
};

View File

@ -1,5 +1,7 @@
export default {
howler: null,
accountLogin: false,
usernameLogin: false,
liked: {
songs: [],
},

View File

@ -4,10 +4,38 @@ import store from "@/store";
export function doLogout() {
logout();
// 移除前端本地用来认证登录的字段
Cookies.remove("loginMode");
// 网易云的接口会自动移除该 cookies
// Cookies.remove("MUSIC_U");
// 更新状态仓库中的用户信息
store.commit("updateUser", { id: 0 });
// 更新状态仓库中的登录状态
store.commit("updateLogout");
}
// MUSIC_U 只有在账户登录的情况下才有
export function isLoggedIn() {
return Cookies.get("MUSIC_U") !== undefined ? true : false;
}
// 账号登录
export function isAccountLoggedIn() {
return (
Cookies.get("MUSIC_U") !== undefined &&
Cookies.get("loginMode") === "account"
);
}
// 用户名搜索(用户数据为只读)
export function isUsernameLoggedIn() {
return (
Cookies.get("MUSIC_U") === undefined &&
Cookies.get("loginMode") === "username"
);
}
// 账户登录或者用户名搜索都判断为登录,宽松检查
export function isLooseLoggedIn() {
return isAccountLoggedIn() || isUsernameLoggedIn();
}

View File

@ -1,4 +1,7 @@
import { isLoggedIn } from "./auth";
import { isAccountLoggedIn } from "./auth";
import { refreshCookie } from "@/api/auth";
import { dailySignin } from "@/api/user";
import dayjs from "dayjs";
import store from "@/store";
export function isTrackPlayable(track) {
@ -7,7 +10,7 @@ export function isTrackPlayable(track) {
reason: "",
};
if (track.fee === 1 || track.privilege?.fee === 1) {
if (isLoggedIn && store.state.settings.user.vipType === 11) {
if (isAccountLoggedIn() && store.state.settings.user.vipType === 11) {
result.playable = true;
} else {
result.playable = false;
@ -75,3 +78,32 @@ export function updateHttps(url) {
if (!url) return "";
return url.replace(/^http:/, "https:");
}
export function dailyTask() {
let lastDate = store.state.settings.lastRefreshCookieDate;
if (
isAccountLoggedIn() &&
(lastDate === undefined || lastDate !== dayjs().date())
) {
console.log("execute dailyTask");
store.commit("updateSettings", {
key: "lastRefreshCookieDate",
value: dayjs().date(),
});
refreshCookie();
dailySignin(0);
dailySignin(1);
}
}
export function changeAppearance(appearance) {
if (appearance === "auto" || appearance === undefined) {
appearance = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
document.body.setAttribute("data-theme", appearance);
document
.querySelector('meta[name="theme-color"]')
.setAttribute("content", appearance === "dark" ? "#222" : "#fff");
}

View File

@ -60,8 +60,8 @@
More by
<router-link :to="`/artist/${album.artist.id}`"
>{{ album.artist.name }}
</router-link></div
>
</router-link>
</div>
<div>
<CoverRow
type="album"
@ -200,6 +200,7 @@ export default {
justify-content: center;
flex: 1;
margin-left: 56px;
color: var(--color-text);
.title {
font-size: 56px;
font-weight: 700;
@ -208,7 +209,7 @@ export default {
}
.artist {
font-size: 18px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
margin-top: 24px;
a {
font-weight: 600;
@ -216,13 +217,13 @@ export default {
}
.date-and-count {
font-size: 14px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
margin-top: 2px;
}
.description {
user-select: none;
font-size: 14px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
margin-top: 24px;
display: -webkit-box;
-webkit-box-orient: vertical;
@ -230,8 +231,8 @@ export default {
overflow: hidden;
cursor: pointer;
&:hover {
transition: color 0.3s;
color: rgba(0, 0, 0, 0.88);
transition: opacity 0.3s;
opacity: 0.88;
}
}
}
@ -281,7 +282,8 @@ export default {
}
.explicit-symbol {
color: rgba(0, 0, 0, 0.28);
opacity: 0.28;
color: var(--color-text);
margin-right: 4px;
.svg-icon {
margin-bottom: -3px;
@ -292,22 +294,25 @@ export default {
margin-top: 36px;
margin-bottom: 36px;
font-size: 12px;
color: rgba(0, 0, 0, 0.48);
opacity: 0.48;
color: var(--color-text);
div {
margin-bottom: 8px;
margin-bottom: 4px;
}
.album-time {
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
}
}
.more-by {
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-top: 1px solid rgba(128, 128, 128, 0.18);
padding-top: 22px;
.section-title {
font-size: 22px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
color: var(--color-text);
margin-bottom: 8px;
}
}

View File

@ -195,6 +195,7 @@ export default {
display: flex;
align-items: center;
margin-bottom: 72px;
color: var(--color-text);
img {
height: 192px;
width: 192px;
@ -209,13 +210,13 @@ export default {
.artist {
font-size: 18px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
margin-top: 24px;
}
.statistics {
font-size: 14px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
margin-top: 2px;
}
@ -234,12 +235,14 @@ export default {
.section-title {
font-weight: 600;
font-size: 22px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
color: var(--color-text);
margin-bottom: 16px;
margin-top: 46px;
}
.latest-release {
color: var(--color-text);
.release {
display: flex;
}
@ -258,17 +261,16 @@ export default {
.name {
font-size: 18px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 8px;
}
.date {
font-size: 14px;
color: rgba(0, 0, 0, 0.78);
opacity: 0.78;
}
.type {
margin-top: 2px;
font-size: 12px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
}
}
@ -281,11 +283,12 @@ export default {
margin-top: 8px;
border-radius: 6px;
font-size: 12px;
color: rgba(0, 0, 0, 0.78);
opacity: 0.78;
color: var(--color-secondary);
font-weight: 600;
&:hover {
background: #f5f5f7;
color: rgba(0, 0, 0, 0.96);
opacity: 1;
// background: var(--color-primary-bg);
}
}
}

View File

@ -161,6 +161,7 @@ export default {
<style lang="scss" scoped>
h1 {
color: var(--color-text);
font-size: 56px;
}
.buttons {
@ -178,19 +179,18 @@ h1 {
font-weight: 600;
font-size: 18px;
border-radius: 10px;
color: rgb(0, 0, 0);
background-color: #f5f5f7;
color: rgba(0, 0, 0, 0.68);
background-color: var(--color-secondary-bg);
color: var(--color-secondary);
transition: 0.2s;
&:hover {
background-color: rgba(51, 94, 234, 0.1);
color: #335eea;
background-color: var(--color-primary-bg);
color: var(--color-primary);
}
}
.button.active {
background-color: rgba(51, 94, 234, 0.1);
color: #335eea;
background-color: var(--color-primary-bg);
color: var(--color-primary);
}
.playlists {

View File

@ -1,6 +1,6 @@
<template>
<div class="home" v-show="show">
<div class="index-row">
<div class="index-row" v-if="settings.showPlaylistsByAppleMusic !== false">
<div class="title"> by Apple Music </div>
<CoverRow
:type="'playlist'"
@ -54,7 +54,7 @@
:color="'grey'"
@click.native="goTo('/settings')"
>
{{ $t("footer.settings") }}
{{ $t("settings.settings") }}
</ButtonTwoTone>
</footer>
</div>
@ -66,6 +66,8 @@ import { toplistOfArtists } from "@/api/artist";
import { byAppleMusic } from "@/utils/staticPlaylist";
import { newAlbums } from "@/api/album";
import NProgress from "nprogress";
import { mapState } from "vuex";
import CoverRow from "@/components/CoverRow.vue";
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
@ -88,6 +90,7 @@ export default {
};
},
computed: {
...mapState(["settings"]),
byAppleMusic() {
return byAppleMusic;
},
@ -158,40 +161,11 @@ export default {
margin-bottom: 20px;
font-size: 28px;
font-weight: 700;
color: var(--color-text);
a {
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.68);
}
}
.item {
margin: 12px 12px 24px 12px;
.text {
width: 208px;
margin-top: 8px;
.name {
font-size: 16px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
line-height: 20px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.info {
font-size: 12px;
color: rgba(0, 0, 0, 0.68);
line-height: 18px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
// margin-top: 4px;
}
opacity: 0.68;
}
}

View File

@ -58,7 +58,7 @@
import { mapState } from "vuex";
import { getTrackDetail, getLyric } from "@/api/track";
import { userDetail, userPlaylist } from "@/api/user";
import { randomNum } from "@/utils/common";
import { randomNum, dailyTask } from "@/utils/common";
import { getPlaylistDetail } from "@/api/playlist";
import { playPlaylistByID } from "@/utils/play";
import NProgress from "nprogress";
@ -98,6 +98,7 @@ export default {
},
activated() {
this.loadData();
dailyTask();
},
computed: {
...mapState(["settings"]),
@ -185,6 +186,7 @@ export default {
<style lang="scss" scoped>
h1 {
font-size: 42px;
color: var(--color-text);
.head {
height: 44px;
margin-right: 12px;
@ -219,25 +221,21 @@ h1 {
transition: all 0.4s;
box-sizing: border-box;
background: #eaeffd;
// background: linear-gradient(-30deg, #60a6f7, #4364f7, #0052d4);
// color: white;
// background: linear-gradient(149.46deg, #450af5, #8e8ee5 99.16%);
background: var(--color-primary-bg);
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--color-primary);
.title {
font-size: 24px;
font-weight: 700;
color: #335eea;
}
.sub-title {
font-size: 15px;
margin-top: 2px;
color: #335eea;
}
button {
@ -247,16 +245,14 @@ h1 {
align-items: center;
height: 44px;
width: 44px;
// background: rgba(255, 255, 255, 1);
background: #335eea;
background: var(--color-primary);
border-radius: 50%;
transition: 0.2s;
box-shadow: 0 6px 12px -4px rgba(0, 0, 0, 0.2);
cursor: default;
.svg-icon {
// color: #3f63f5;
color: #eaeffd;
color: var(--color-primary-bg);
margin-left: 4px;
height: 16px;
width: 16px;
@ -276,7 +272,8 @@ h1 {
display: flex;
flex-wrap: wrap;
font-size: 14px;
color: rgba(51, 94, 234, 0.88);
opacity: 0.88;
color: var(--color-primary);
p {
margin-top: 2px;
}
@ -286,7 +283,8 @@ h1 {
.playlists {
margin-top: 54px;
.title {
color: rgba(0, 0, 0, 0.88);
color: var(--color-text);
opacity: 0.88;
margin-bottom: 8px;
font-size: 24px;
font-weight: 600;

View File

@ -112,9 +112,9 @@ export default {
NProgress.done();
},
methods: {
...mapMutations(["updateUser", "updateUserInfo"]),
...mapMutations(["updateUser", "updateUserInfo", "updateAccountLogin"]),
afterLogin() {
// Cookies.set("MUSIC_U", true, { expires: 3650 });
this.updateAccountLogin(true);
Cookies.set("loginMode", "account", { expires: 3650 });
userPlaylist({
uid: this.$store.state.settings.user.userId,
@ -127,18 +127,34 @@ export default {
this.$router.push({ path: "/library" });
});
},
validatePhone() {
if (
this.countryCode === "" ||
this.phone === "" ||
this.password === ""
) {
alert("国家区号、手机或密码不正确");
this.processing = false;
return false;
}
return true;
},
validateEmail() {
const emailReg = /^[A-Za-z0-9]+([_][A-Za-z0-9]+)*@([A-Za-z0-9]+\.)+[A-Za-z]{2,6}$/;
if (
this.email === "" ||
this.password === "" ||
!emailReg.test(this.email)
) {
alert("邮箱或密码不正确");
return false;
}
return true;
},
login() {
this.processing = true;
if (this.mode === "phone") {
if (
this.countryCode === "" ||
this.phone === "" ||
this.password === ""
) {
alert("国家区号、手机或密码不正确");
this.processing = false;
return;
}
this.processing = this.validatePhone();
loginWithPhone({
countrycode: this.countryCode.replace("+", "").replace(/\s/g, ""),
phone: this.phoneNumber.replace(/\s/g, ""),
@ -156,16 +172,7 @@ export default {
alert(error);
});
} else {
let emailReg = /^[A-Za-z0-9]+([_][A-Za-z0-9]+)*@([A-Za-z0-9]+\.)+[A-Za-z]{2,6}$/;
if (
this.email === "" ||
this.password === "" ||
!emailReg.test(this.email)
) {
alert("邮箱或密码不正确");
this.processing = false;
return;
}
this.processing = this.validateEmail();
loginWithEmail({
email: this.email.replace(/\s/g, ""),
password: "fakePassword",
@ -199,6 +206,7 @@ export default {
font-size: 24px;
font-weight: 700;
margin-bottom: 48px;
color: var(--color-text);
}
.section-1 {
@ -209,11 +217,6 @@ export default {
height: 64px;
margin: 20px;
}
.svg-icon {
height: 24px;
width: 24px;
color: rgba(82, 82, 82, 0.28);
}
}
.section-2 {
@ -226,12 +229,13 @@ export default {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
color: var(--color-text);
.container {
display: flex;
align-items: center;
height: 46px;
background: rgba(0, 0, 0, 0.06);
background: var(--color-secondary-bg);
border-radius: 8px;
width: 300px;
}
@ -258,7 +262,12 @@ export default {
width: 100%;
font-weight: 600;
margin-top: -1px;
color: rgba(0, 0, 0, 0.88);
color: var(--color-text);
}
input::placeholder {
color: var(--color-text);
opacity: 0.38;
}
input#countryCode {
@ -269,10 +278,10 @@ export default {
}
.active {
background: #eaeffd;
background: var(--color-primary-bg);
input,
.svg-icon {
color: #335eea;
color: var(--color-primary);
}
}
}
@ -283,8 +292,8 @@ export default {
justify-content: center;
font-size: 20px;
font-weight: 600;
background-color: rgba(51, 94, 234, 0.1);
color: #335eea;
background-color: var(--color-primary-bg);
color: var(--color-primary);
border-radius: 8px;
margin-top: 24px;
transition: 0.2s;
@ -304,17 +313,19 @@ export default {
a {
cursor: pointer;
font-size: 13px;
color: rgba(0, 0, 0, 0.68);
color: var(--color-text);
opacity: 0.68;
}
}
.notice {
width: 300px;
border-top: 1px solid rgba(0, 0, 0, 0.18);
border-top: 1px solid rgba(128, 128, 128);
margin-top: 48px;
padding-top: 12px;
font-size: 12px;
color: rgba(0, 0, 0, 0.48);
color: var(--color-text);
opacity: 0.48;
}
@keyframes loading {
@ -339,7 +350,7 @@ button.loading {
.loading span {
width: 6px;
height: 6px;
background-color: #335eea;
background-color: var(--color-primary);
border-radius: 50%;
margin: 0 2px;
animation: loading 1.4s infinite both;

View File

@ -41,8 +41,9 @@
<ButtonTwoTone
@click.native="confirm"
v-show="activeUser.nickname !== undefined"
>{{ $t("login.confirm") }}</ButtonTwoTone
>
{{ $t("login.confirm") }}
</ButtonTwoTone>
</div>
</div>
</template>
@ -73,7 +74,7 @@ export default {
NProgress.done();
},
methods: {
...mapMutations(["updateUser", "updateUserInfo"]),
...mapMutations(["updateUser", "updateUserInfo", "updateUsernameLogin"]),
search() {
if (!this.keyword) return;
search({ keywords: this.keyword, limit: 9, type: 1002 }).then((data) => {
@ -83,6 +84,7 @@ export default {
},
confirm() {
this.updateUser(this.activeUser);
this.updateUsernameLogin(true);
Cookies.set("loginMode", "username", { expires: 3650 });
userPlaylist({
uid: this.activeUser.userId,
@ -105,6 +107,7 @@ export default {
<style lang="scss" scoped>
.login {
display: flex;
color: var(--color-text);
}
.title {
@ -119,7 +122,7 @@ export default {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: rgba(0, 0, 0, 0.78);
opacity: 0.78;
}
}
@ -130,13 +133,13 @@ export default {
height: 48px;
border-radius: 11px;
width: 326px;
background: #eaeffd;
background: var(--color-primary-bg);
}
.svg-icon {
height: 22px;
width: 22px;
color: #335eea;
color: var(--color-primary);
margin: {
left: 12px;
right: 8px;
@ -151,9 +154,10 @@ export default {
width: 115%;
font-weight: 600;
margin-top: -1px;
color: #335eea;
color: var(--color-primary);
&::placeholder {
color: #335eeac4;
color: var(--color-primary);
opacity: 0.78;
}
}
}
@ -185,15 +189,15 @@ export default {
margin-left: 12px;
}
&:hover {
background: #f5f5f7;
background: var(--color-secondary-bg);
}
}
.user.active {
transition: 0.2s;
background: #eaeffd;
.name {
color: #335eea;
background: var(--color-primary-bg);
.nickname {
color: var(--color-primary);
}
}
</style>

View File

@ -127,19 +127,20 @@ export default {
.video-info {
margin-top: 12px;
color: var(--color-text);
.title {
font-size: 24px;
font-weight: 600;
}
.artist {
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
margin-top: 2px;
font-weight: 600;
}
.info {
font-size: 12px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
margin-top: 12px;
}
}
@ -149,7 +150,8 @@ export default {
.section-title {
font-size: 18px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
color: var(--color-text);
opacity: 0.88;
}
}
</style>

View File

@ -106,5 +106,6 @@ h1 {
margin-top: 36px;
margin-bottom: 18px;
cursor: default;
color: var(--color-text);
}
</style>

View File

@ -46,7 +46,7 @@
</ButtonTwoTone>
<ButtonTwoTone
v-if="
isLoggedIn && playlist.creator.userId !== settings.user.userId
accountLogin && playlist.creator.userId !== settings.user.userId
"
shape="round"
:iconClass="playlist.subscribed ? 'heart-solid' : 'heart'"
@ -92,7 +92,6 @@ import NProgress from "nprogress";
import { getPlaylistDetail, subscribePlaylist } from "@/api/playlist";
import { playAList } from "@/utils/play";
import { getTrackDetail } from "@/api/track";
import { isLoggedIn } from "@/utils/auth";
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import TrackList from "@/components/TrackList.vue";
@ -132,10 +131,7 @@ export default {
window.removeEventListener("scroll", this.handleScroll, true);
},
computed: {
...mapState(["player", "settings"]),
isLoggedIn() {
return isLoggedIn();
},
...mapState(["player", "settings", "accountLogin"]),
isLikeSongsPage() {
return this.$route.name === "likedSongs";
},
@ -230,20 +226,24 @@ export default {
.title {
font-size: 36px;
font-weight: 700;
color: var(--color-text);
}
.artist {
font-size: 18px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
color: var(--color-text);
margin-top: 24px;
}
.date-and-count {
font-size: 14px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
color: var(--color-text);
margin-top: 2px;
}
.description {
font-size: 14px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
color: var(--color-text);
margin-top: 24px;
display: -webkit-box;
-webkit-box-orient: vertical;
@ -251,8 +251,8 @@ export default {
overflow: hidden;
cursor: pointer;
&:hover {
transition: color 0.3s;
color: rgba(0, 0, 0, 0.88);
transition: opacity 0.3s;
opacity: 0.88;
}
}
.buttons {
@ -303,6 +303,7 @@ export default {
.user-info {
h1 {
font-size: 42px;
color: var(--color-text);
.avatar {
height: 44px;
margin-right: 12px;

View File

@ -192,15 +192,17 @@ export default {
h1 {
margin-top: -10px;
margin-bottom: 0;
color: var(--color-text);
span {
color: rgba(0, 0, 0, 0.58);
opacity: 0.58;
}
}
.section-title {
font-weight: 600;
font-size: 22px;
color: rgba(0, 0, 0, 0.88);
opacity: 0.88;
color: var(--color-text);
margin-bottom: 16px;
margin-top: 46px;
}
@ -219,6 +221,7 @@ h1 {
padding-right: 48px;
font-size: 16px;
font-weight: 600;
color: var(--color-text);
.artist {
display: flex;
align-items: center;
@ -236,6 +239,7 @@ h1 {
.albums-list {
display: flex;
color: var(--color-text);
.album {
img {
height: 128px;
@ -249,7 +253,6 @@ h1 {
.name {
margin-top: 6px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
font-size: 14px;
width: 128px;
display: -webkit-box;
@ -259,7 +262,7 @@ h1 {
}
.artist {
font-size: 12px;
color: rgba(0, 0, 0, 0.68);
opacity: 0.68;
}
}
}

File diff suppressed because one or more lines are too long