diff --git a/README.md b/README.md index a8fa474..2d6782a 100644 --- a/README.md +++ b/README.md @@ -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。 ## 📜 开源许可 diff --git a/src/App.vue b/src/App.vue index 8d1f41f..f177602 100644 --- a/src/App.vue +++ b/src/App.vue @@ -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, diff --git a/src/api/album.js b/src/api/album.js index 5149b03..214128e 100644 --- a/src/api/album.js +++ b/src/api/album.js @@ -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", diff --git a/src/api/artist.js b/src/api/artist.js index 259dadf..0a5ed88 100644 --- a/src/api/artist.js +++ b/src/api/artist.js @@ -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", diff --git a/src/api/auth.js b/src/api/auth.js index da40e6a..461ac01 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -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", diff --git a/src/api/mv.js b/src/api/mv.js index b114afc..d00b095 100644 --- a/src/api/mv.js +++ b/src/api/mv.js @@ -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", diff --git a/src/api/others.js b/src/api/others.js index 0582e9e..f63423a 100644 --- a/src/api/others.js +++ b/src/api/others.js @@ -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", diff --git a/src/api/playlist.js b/src/api/playlist.js index 584675d..d643b50 100644 --- a/src/api/playlist.js +++ b/src/api/playlist.js @@ -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", diff --git a/src/api/track.js b/src/api/track.js index f3244fd..11876c7 100644 --- a/src/api/track.js +++ b/src/api/track.js @@ -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", diff --git a/src/api/user.js b/src/api/user.js index edabc43..dbb3a05 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -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, + }, + }); +} diff --git a/src/assets/css/slider.css b/src/assets/css/slider.css index 87a51cd..58e197d 100644 --- a/src/assets/css/slider.css +++ b/src/assets/css/slider.css @@ -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; } diff --git a/src/components/ButtonIcon.vue b/src/components/ButtonIcon.vue index 3f93ecf..12d6af6 100644 --- a/src/components/ButtonIcon.vue +++ b/src/components/ButtonIcon.vue @@ -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); diff --git a/src/components/ButtonTwoTone.vue b/src/components/ButtonTwoTone.vue index 576116c..48bec5b 100644 --- a/src/components/ButtonTwoTone.vue +++ b/src/components/ButtonTwoTone.vue @@ -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; diff --git a/src/components/Cover.vue b/src/components/Cover.vue index c074211..f821543 100644 --- a/src/components/Cover.vue +++ b/src/components/Cover.vue @@ -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; diff --git a/src/components/CoverRow.vue b/src/components/CoverRow.vue index 66f7d1f..7579dc0 100644 --- a/src/components/CoverRow.vue +++ b/src/components/CoverRow.vue @@ -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; diff --git a/src/components/MvRow.vue b/src/components/MvRow.vue index e80c9b5..5811f49 100644 --- a/src/components/MvRow.vue +++ b/src/components/MvRow.vue @@ -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; diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 9bd5669..84d696e 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -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> diff --git a/src/components/Player.vue b/src/components/Player.vue index 12f661e..5311eda 100644 --- a/src/components/Player.vue +++ b/src/components/Player.vue @@ -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; diff --git a/src/components/TrackListItem.vue b/src/components/TrackListItem.vue index bb574ae..1513541 100644 --- a/src/components/TrackListItem.vue +++ b/src/components/TrackListItem.vue @@ -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> diff --git a/src/locale/lang/en.js b/src/locale/lang/en.js index 051e7b3..f43b6a1 100644 --- a/src/locale/lang/en.js +++ b/src/locale/lang/en.js @@ -108,5 +108,11 @@ export default { high: "High", lossless: "Lossless", }, + appearance: { + text: "Appearance", + auto: "Auto", + light: "Light", + dark: "Dark", + }, }, }; diff --git a/src/locale/lang/zh-CN.js b/src/locale/lang/zh-CN.js index d14746d..2dd4dad 100644 --- a/src/locale/lang/zh-CN.js +++ b/src/locale/lang/zh-CN.js @@ -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: "深色", + }, }, }; diff --git a/src/main.js b/src/main.js index e296e80..1974c5e 100644 --- a/src/main.js +++ b/src/main.js @@ -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, diff --git a/src/router/index.js b/src/router/index.js index 3d228b2..3b0ff37 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -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(); diff --git a/src/store/actions.js b/src/store/actions.js index 58d0b2b..6a28df9 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -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]) { diff --git a/src/store/index.js b/src/store/index.js index f69ef0a..5b10180 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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; diff --git a/src/store/initState.js b/src/store/initState.js index 70c45a0..9f0c01f 100644 --- a/src/store/initState.js +++ b/src/store/initState.js @@ -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, }, }; diff --git a/src/store/mutations.js b/src/store/mutations.js index 0dde032..2e4b811 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -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; + }, }; diff --git a/src/store/state.js b/src/store/state.js index d2d5dfd..8a5d99e 100644 --- a/src/store/state.js +++ b/src/store/state.js @@ -1,5 +1,7 @@ export default { howler: null, + accountLogin: false, + usernameLogin: false, liked: { songs: [], }, diff --git a/src/utils/auth.js b/src/utils/auth.js index 239ddcb..eb3c9bb 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -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(); +} diff --git a/src/utils/common.js b/src/utils/common.js index 575e634..3663147 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -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"); +} diff --git a/src/views/album.vue b/src/views/album.vue index 4c9f472..5c3f409 100644 --- a/src/views/album.vue +++ b/src/views/album.vue @@ -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; } } diff --git a/src/views/artist.vue b/src/views/artist.vue index 71e0cdc..481799b 100644 --- a/src/views/artist.vue +++ b/src/views/artist.vue @@ -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); } } } diff --git a/src/views/explore.vue b/src/views/explore.vue index b73b5e5..8ed611c 100644 --- a/src/views/explore.vue +++ b/src/views/explore.vue @@ -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 { diff --git a/src/views/home.vue b/src/views/home.vue index f38f3ab..d354d1e 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -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; } } diff --git a/src/views/library.vue b/src/views/library.vue index efa986e..e961559 100644 --- a/src/views/library.vue +++ b/src/views/library.vue @@ -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; diff --git a/src/views/loginAccount.vue b/src/views/loginAccount.vue index 2958bc0..c4ede97 100644 --- a/src/views/loginAccount.vue +++ b/src/views/loginAccount.vue @@ -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; diff --git a/src/views/loginUsername.vue b/src/views/loginUsername.vue index 0ffd895..8a50950 100644 --- a/src/views/loginUsername.vue +++ b/src/views/loginUsername.vue @@ -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> diff --git a/src/views/mv.vue b/src/views/mv.vue index 48bdd16..ffd3e55 100644 --- a/src/views/mv.vue +++ b/src/views/mv.vue @@ -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> diff --git a/src/views/next.vue b/src/views/next.vue index df5cb2b..045c22d 100644 --- a/src/views/next.vue +++ b/src/views/next.vue @@ -106,5 +106,6 @@ h1 { margin-top: 36px; margin-bottom: 18px; cursor: default; + color: var(--color-text); } </style> diff --git a/src/views/playlist.vue b/src/views/playlist.vue index cf3d9c2..e58b685 100644 --- a/src/views/playlist.vue +++ b/src/views/playlist.vue @@ -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; diff --git a/src/views/search.vue b/src/views/search.vue index b08f5cc..ece7683 100644 --- a/src/views/search.vue +++ b/src/views/search.vue @@ -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; } } } diff --git a/src/views/settings.vue b/src/views/settings.vue index 7277fd5..54517e8 100644 --- a/src/views/settings.vue +++ b/src/views/settings.vue @@ -11,9 +11,10 @@ ><img class="cvip" src="data:image/png;base64,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" - />黑胶VIP + /> + <span class="text">黑胶VIP</span> </span> - <span v-else>{{ settings.user.signature }}</span> + <span class="text" v-else>{{ settings.user.signature }}</span> </div> </div> </div> @@ -36,6 +37,22 @@ </select> </div> </div> + <div class="item"> + <div class="left"> + <div class="title"> {{ $t("settings.appearance.text") }} </div> + </div> + <div class="right"> + <select v-model="appearance"> + <option value="auto">{{ $t("settings.appearance.auto") }}</option> + <option value="light" + >🌞 {{ $t("settings.appearance.light") }}</option + > + <option value="dark" + >🌚 {{ $t("settings.appearance.dark") }}</option + > + </select> + </div> + </div> <div class="item"> <div class="left"> <div class="title"> {{ $t("settings.musicQuality.text") }} </div> @@ -57,6 +74,38 @@ </select> </div> </div> + <div class="item"> + <div class="left"> + <div class="title"> Show Github Icon </div> + </div> + <div class="right"> + <div class="toggle"> + <input + type="checkbox" + name="show-github-icon" + id="show-github-icon" + v-model="showGithubIcon" + /> + <label for="show-github-icon"></label> + </div> + </div> + </div> + <div class="item"> + <div class="left"> + <div class="title"> Show Playlists by Apple Music</div> + </div> + <div class="right"> + <div class="toggle"> + <input + type="checkbox" + name="show-playlists-by-apple-music" + id="show-playlists-by-apple-music" + v-model="showPlaylistsByAppleMusic" + /> + <label for="show-playlists-by-apple-music"></label> + </div> + </div> + </div> </div> </div> </template> @@ -64,6 +113,8 @@ <script> import { mapState } from "vuex"; import { doLogout } from "@/utils/auth"; +import { changeAppearance } from "@/utils/common"; + export default { name: "settings", computed: { @@ -77,14 +128,52 @@ export default { this.$store.commit("changeLang", lang); }, }, + appearance: { + get() { + if (this.settings.appearance === undefined) return "auto"; + return this.settings.appearance; + }, + set(value) { + this.$store.commit("updateSettings", { + key: "appearance", + value, + }); + changeAppearance(value); + }, + }, musicQuality: { get() { + if (this.settings.appearance === undefined) return 320000; return this.settings.musicQuality; }, set(value) { this.$store.commit("changeMusicQuality", value); }, }, + showGithubIcon: { + get() { + if (this.settings.showGithubIcon === undefined) return true; + return this.settings.showGithubIcon; + }, + set(value) { + this.$store.commit("updateSettings", { + key: "showGithubIcon", + value, + }); + }, + }, + showPlaylistsByAppleMusic: { + get() { + if (this.settings.showPlaylistsByAppleMusic === undefined) return true; + return this.settings.showPlaylistsByAppleMusic; + }, + set(value) { + this.$store.commit("updateSettings", { + key: "showPlaylistsByAppleMusic", + value, + }); + }, + }, }, methods: { logout() { @@ -107,13 +196,15 @@ export default { h2 { margin-top: 48px; font-size: 36px; + color: var(--color-text); } .user { display: flex; align-items: center; justify-content: space-between; - background: #f5f5f7; + background: var(--color-secondary-bg); + color: var(--color-text); padding: 16px 20px; border-radius: 16px; img.avatar { @@ -134,12 +225,13 @@ h2 { .nickname { font-size: 20px; font-weight: 600; - color: rgba(0, 0, 0, 0.88); margin-bottom: 2px; } .extra-info { font-size: 13px; - color: rgba(0, 0, 0, 0.68); + .text { + opacity: 0.68; + } .vip { display: flex; align-items: center; @@ -160,17 +252,20 @@ h2 { text-decoration: none; border-radius: 10px; padding: 8px 12px; - color: rgba(0, 0, 0, 0.68); + opacity: 0.68; + color: var(--color-text); transition: 0.2s; margin: { right: 12px; left: 12px; } &:hover { + opacity: 1; background: #eaeffd; color: #335eea; } &:active { + opacity: 1; transform: scale(0.92); transition: 0.2s; } @@ -181,12 +276,14 @@ h2 { .item { margin: 24px 0; display: flex; + align-items: center; justify-content: space-between; + color: var(--color-text); .title { font-size: 18px; font-weight: 600; - color: rgba(0, 0, 0, 0.88); + opacity: 0.88; } select { @@ -195,14 +292,81 @@ h2 { border: none; padding: 8px 12px 8px 12px; border-radius: 8px; - color: rgba(0, 0, 0, 0.88); - background: #f5f5f7; + color: var(--color-text); + background: var(--color-secondary-bg); appearance: none; &:focus { outline: none; - color: #335eea; - background: #eaeffd; + color: var(--color-primary); + background: var(--color-primary-bg); } } } + +.beforeAnimation { + -webkit-transition: 0.2s cubic-bezier(0.24, 0, 0.5, 1); + transition: 0.2s cubic-bezier(0.24, 0, 0.5, 1); +} +.afterAnimation { + box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 0px 0 hsla(0, 0%, 0%, 0.04), + 0 4px 9px hsla(0, 0%, 0%, 0.13), 0 3px 3px hsla(0, 0%, 0%, 0.05); + -webkit-transition: 0.35s cubic-bezier(0.54, 1.6, 0.5, 1); + transition: 0.35s cubic-bezier(0.54, 1.6, 0.5, 1); +} +.toggle { + margin: auto; +} +.toggle input { + opacity: 0; + position: absolute; +} +.toggle input + label { + position: relative; + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-transition: 0.4s ease; + transition: 0.4s ease; + height: 32px; + width: 68px; + background: var(--color-secondary-bg); + border-radius: 8px; +} +.toggle input + label:before { + content: ""; + position: absolute; + display: block; + -webkit-transition: 0.2s cubic-bezier(0.24, 0, 0.5, 1); + transition: 0.2s cubic-bezier(0.24, 0, 0.5, 1); + height: 32px; + width: 68px; + top: 0; + left: 0; + border-radius: 8px; +} +.toggle input + label:after { + content: ""; + position: absolute; + display: block; + box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.02), 0 4px 0px 0 hsla(0, 0%, 0%, 0.01), + 0 4px 9px hsla(0, 0%, 0%, 0.08), 0 3px 3px hsla(0, 0%, 0%, 0.03); + -webkit-transition: 0.35s cubic-bezier(0.54, 1.6, 0.5, 1); + transition: 0.35s cubic-bezier(0.54, 1.6, 0.5, 1); + background: #fff; + height: 20px; + width: 28px; + top: 6px; + left: 6px; + border-radius: 6px; +} +.toggle input:checked + label:before { + background: var(--color-primary); + -webkit-transition: width 0.2s cubic-bezier(0, 0, 0, 0.1); + transition: width 0.2s cubic-bezier(0, 0, 0, 0.1); +} +.toggle input:checked + label:after { + left: 34px; +} </style>