初始化项目
|
@ -0,0 +1,14 @@
|
|||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
.idea
|
||||
.vscode
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
|
||||
node_modules
|
||||
node_modules/**
|
||||
|
||||
package-lock.json
|
|
@ -0,0 +1,39 @@
|
|||
# Sonist 音乐播放器
|
||||
> 一个音乐播放器, 主打本地音乐播放。支持 自动歌词/自动封面/均衡器等常见功能。
|
||||
>> 同时利用酷狗音乐的API(**来源于网络,仅供学习使用**), 获取实时的云音乐(**仅免费的那部分,付费部分无法提供**)。
|
||||
|
||||
|
||||
界面预览
|
||||
|
||||
![demo](./demo1.jpg)
|
||||
|
||||
![demo](./demo2.jpg)
|
||||
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [x] 主界面框架
|
||||
- [ ] 酷狗音乐电台
|
||||
- [ ] 酷狗音乐排行榜
|
||||
- [ ] 酷狗歌手列表(完成20%)
|
||||
- [ ] 酷狗音乐MV
|
||||
- [ ] 试听列表
|
||||
- [ ] 本地音乐(50%)
|
||||
- [ ] 设置界面
|
||||
- [ ] 均衡器
|
||||
- [ ] 桌面歌词
|
||||
- [ ] 迷你模式
|
||||
- [ ] KTV模式
|
||||
- [ ] 多媒体快捷键
|
||||
- [ ] 酷狗账号直接登录(犹豫中)
|
||||
- [ ] 铃声制作(犹豫中)
|
||||
- [ ] 歌曲ID3信息修改(技术攻坚中)
|
||||
- [ ] 用户评论/点赞(取决于登陆功能是否开发)
|
||||
- [ ] 试听下载
|
||||
- [ ] 歌曲质量选择
|
||||
- [ ] 等你来建议
|
||||
|
||||
|
||||
## 捐助
|
||||
> 开发app其实挺辛苦的。 喜欢我的作品的童鞋, 可以给我打赏个几块钱茶水费, 感激不尽。
|
||||
>> 没钱的, 可以扫支付宝领红包, 也算支持我了。
|
|
@ -0,0 +1,188 @@
|
|||
@charset "UTF-8";
|
||||
/**
|
||||
* {sonist app style}
|
||||
* @authors yutent<yutent@doui.cc>
|
||||
* @date 2018/12/16 17:15:07
|
||||
*/
|
||||
|
||||
|
||||
@import "./var.scss";
|
||||
|
||||
@font-face {font-family: "sonist font";
|
||||
src: url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff')
|
||||
}
|
||||
|
||||
[class^="s-icon-"], [class*=" s-icon-"] {display:inline-block;font-family:"sonist font" !important;font-style:normal;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
|
||||
|
||||
.s-icon-all:before { content: "\e714"; }
|
||||
.s-icon-eq:before { content: "\e715"; }
|
||||
.s-icon-heart:before { content: "\e716"; }
|
||||
.s-icon-music:before { content: "\e717"; }
|
||||
.s-icon-prev:before { content: "\e718"; }
|
||||
.s-icon-play-list:before { content: "\e719"; }
|
||||
.s-icon-pause:before { content: "\e71a"; }
|
||||
.s-icon-play:before { content: "\e71b"; }
|
||||
.s-icon-next:before { content: "\e71c"; }
|
||||
.s-icon-mv:before { content: "\e71d"; }
|
||||
.s-icon-rank:before { content: "\e71e"; }
|
||||
.s-icon-singer:before { content: "\e71f"; }
|
||||
.s-icon-random:before { content: "\e720"; }
|
||||
.s-icon-radio:before { content: "\e721"; }
|
||||
.s-icon-single:before { content: "\e722"; }
|
||||
|
||||
.do-fn-drag {-webkit-app-region:drag;user-select: none;}
|
||||
.do-fn-nodrag {-webkit-app-region:no-drag;}
|
||||
|
||||
html {font-size:62.5%}
|
||||
body {line-height:1.5;background:#fff;font-size:1.4rem;color:nth($cd, 1)}
|
||||
|
||||
table {overflow:auto;display:table;width:100%;line-height:2.5rem;
|
||||
thead tr {height:4.5rem;border-bottom:.1rem solid nth($cp, 1)}
|
||||
thead th {padding:1rem .8rem;border:0;}
|
||||
tbody tr {height:auto;@include ts(background, .3s);
|
||||
&:hover {background:#f7f8fb;}
|
||||
}
|
||||
tbody td {padding:.9rem .8rem}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {width:8px;height:8px;background:#f7f8fb;}
|
||||
::-webkit-scrollbar:hover {background:#f3f5fb;}
|
||||
::-webkit-scrollbar-button {display:none;}
|
||||
::-webkit-scrollbar-thumb {background:nth($cp, 3);}
|
||||
::-webkit-scrollbar-thumb:hover {background:nth($ct, 1);}
|
||||
|
||||
|
||||
.do-mod-app {display:flex;position:fixed;left:0;top:0;width:100%;height:100%;
|
||||
|
||||
|
||||
.menubar {position:absolute;left:1.2rem;top:0;z-index:99;width:auto;height:3rem;padding:.9rem 0;
|
||||
|
||||
.item {display:inline-block;width:1.2rem;height:1.2rem;margin:0 .2rem;background:url(/images/btn_gray@2x.png) no-repeat;background-size:cover;}
|
||||
|
||||
&.focus {
|
||||
.quit {background-image:url(/images/btn_close_focus@2x.png);}
|
||||
.min {background-image:url(/images/btn_min_focus@2x.png);}
|
||||
.max {background-image:url(/images/btn_max_focus@2x.png);}
|
||||
}
|
||||
&:hover {
|
||||
.quit {background-image:url(/images/btn_close_hover@2x.png);}
|
||||
.min {background-image:url(/images/btn_min_hover@2x.png);}
|
||||
.max {background-image:url(/images/btn_max_hover@2x.png);}
|
||||
}
|
||||
}
|
||||
|
||||
.menubar-win {position:absolute;right:1.2rem;top:0;z-index:99;width:auto;height:4rem;padding:.9rem 0;line-height:1.8rem;
|
||||
|
||||
.item {display:inline-block;width:2.2rem;height:2.2rem;margin:0 .2rem;padding:.2rem;font-size:1.6rem;
|
||||
|
||||
&:hover {transform:scale(1.1)}
|
||||
&.opt {font-size:1.8rem;}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.sidebar {flex:0 1 22rem;position:relative;height:100%;background:nth($cp, 1);
|
||||
|
||||
// 用户信息
|
||||
.user-box {width:18rem;height:16.5rem;margin:4rem 2rem 0;text-align:center;
|
||||
|
||||
.avatar {overflow:hidden;width:12rem;height:12rem;margin:0 3rem;border:.6rem solid #fff;border-radius:50%;box-shadow:0 .5rem 1.5rem rgba(0, 0, 0, .15);}
|
||||
img {width:100%;height:100%;}
|
||||
.uname {line-height:2;font-weight:normal;}
|
||||
}
|
||||
|
||||
// 音乐菜单
|
||||
.music-box {width:100%;height:auto;padding:0 1.5rem;
|
||||
|
||||
dt.title {line-height:4rem;color:nth($cgr, 1)}
|
||||
dd.item {height:3rem;margin:.3rem 0;padding:0 .8rem;line-height:3rem;color:nth($cgr, 3);
|
||||
|
||||
.icon {float:left;width:3rem;height:3rem;padding:0 .5rem;font-size:2.4rem;}
|
||||
&.active {border-radius:.3rem;background:nth($ct, 1);color:#fff;}
|
||||
&.disabled {opacity:.25}
|
||||
}
|
||||
}
|
||||
|
||||
// 播放控制按钮
|
||||
.play-contrl {position:absolute;left:0;bottom:0;width:100%;height:8rem;background:rgba(255,255,255,.3);
|
||||
|
||||
.item {position:absolute;top:2rem;width:4rem;height:4rem;line-height:4rem;font-size:3.5rem;text-align:center;color:nth($ct, 2);@include ts();
|
||||
|
||||
&:hover {color:nth($ct, 1)}
|
||||
&:active {color:nth($ct, 3)}
|
||||
}
|
||||
.prev {left:2.5rem;}
|
||||
.play {left:50%;top:1.5rem;width:5rem;height:5rem;margin-left:-2.5rem;line-height:5rem;font-size:4.5rem;}
|
||||
.next {right:2.5rem;}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 主体样式
|
||||
.main {flex:1;display:flex;flex-flow:column wrap;
|
||||
|
||||
// 工具栏
|
||||
.tool-bar {flex:0 1 5rem;padding:1rem;
|
||||
|
||||
.search {position:relative;display:inline-block;line-height:3rem;}
|
||||
.icon {position:absolute;right:0;top:0;width:2.6rem;height:3rem;}
|
||||
input {width:20rem;padding:0 1.3rem;border-radius:1.5rem;}
|
||||
}
|
||||
|
||||
// 功能模块
|
||||
.module {flex:1;display:flex;flex-flow:column wrap;}
|
||||
|
||||
|
||||
|
||||
// 播放条
|
||||
.play-bar {position:relative;flex:0 1 8rem;display:flex;justify-content:center;align-items:center;background:#f5f6fc;
|
||||
|
||||
.song-stat {flex:1;height:8rem;margin:0 2rem 0 0;
|
||||
|
||||
canvas {display:flex;width:100%;height:100%;}
|
||||
}
|
||||
|
||||
.ctrl {position:relative;flex:0 1 3.5rem;height:3rem;line-height:3rem;text-align:center;color:nth($ct, 2);font-size:2rem;
|
||||
|
||||
&:hover {color:nth($ct, 1)}
|
||||
&:active {color:nth($ct, 3)}
|
||||
&.lrc {margin-right:2rem;font-size:1.6rem;}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@keyframes play {
|
||||
from {transform:rotate(0deg)}
|
||||
to {transform:rotate(360deg)}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
.do-mod-artist{position:relative;display:flex;width:100%;height:100%}.do-mod-artist .filter-box{flex:0 1 12rem;border-right:0.1rem solid #f3f5fb;text-align:right}.do-mod-artist .filter-box .item{width:100%;height:2.4rem;padding:0 1.2rem;line-height:2.4rem;color:#98acae}.do-mod-artist .filter-box .item.active{color:#3fc2a7;font-weight:bold}.do-mod-artist .filter-box .item:hover{padding-right:1.3rem;color:#3fc2a7}.do-mod-artist .filter-box .pipe{display:block;width:100%;height:.7rem}.do-mod-artist .list-box{overflow-y:auto;display:flex;flex-flow:row wrap;flex:1;padding:0 1rem}.do-mod-artist .list-box .item{display:flex;justify-content:center;align-items:center;flex:45%;height:7rem;margin:1rem 2.5%;padding:.5rem;background:#f3f5fb}.do-mod-artist .list-box .item img{flex:0 1 6rem;height:6rem}.do-mod-artist .list-box .item summary{flex:2;padding:0 1rem}.do-mod-artist .list-box .item strong{font-size:1.6rem}.do-mod-artist .list-box .item p{font-size:1.2rem;color:#98acae}.do-mod-artist .artist-box{position:absolute;left:0;top:0;z-index:9;width:100%;height:100%;background-size:cover;background-repeat:no-repeat;background-position:center center}.do-mod-artist .artist-box .content{display:flex;flex-flow:column wrap;width:100%;height:100%;padding:1.5rem 2.5rem;background:linear-gradient(to bottom, #fff 2%, rgba(255,255,255,0.75), #fff 98%);-webkit-backdrop-filter:blur(1rem);backdrop-filter:blur(1rem)}.do-mod-artist .artist-box .content .name{flex:0 1 3.6rem;font-size:1.4rem;font-style:italic;font-weight:normal}.do-mod-artist .artist-box .content .name a{text-decoration:underline;color:#3fc2a7}.do-mod-artist .artist-box .content .name i{color:#98acae}.do-mod-artist .artist-box .content .desc{flex:0 1 3rem;font-size:1.2rem;color:#98acae}.do-mod-artist .artist-box .content .desc span{padding:0 .5rem;text-decoration:underline;color:#3fc2a7}.do-mod-artist .artist-box .content .song-album{flex:1;display:flex;flex-flow:column wrap}.do-mod-artist .artist-box .content .tab{flex:0 1 3rem;display:flex;padding:0 .5rem;line-height:2.9rem;border-bottom:0.1rem solid #e8ebf4;text-align:center}.do-mod-artist .artist-box .content .tab .item{flex:0 0 7.5rem;height:3rem;margin:0 .3rem}.do-mod-artist .artist-box .content .tab .item.active{border-bottom:0.2rem solid #3fc2a7;color:#3fc2a7}.do-mod-artist .artist-box .content .tab .item.disabled{opacity:.25}.do-mod-local{flex:1;display:flex;flex-flow:column wrap}.do-mod-local .toolbar{flex:0 1 3rem;padding:0 1rem;line-height:2.9rem;border-bottom:0.1rem solid #e8ebf4}.do-mod-local .toolbar .refresh{margin-left:1rem;color:#3fc2a7;text-decoration:underline}.do-mod-local .table{overflow:auto;flex:1}.do-mod-local .table .stat{width:2.6rem;height:2.6rem;line-height:2.6rem}.do-mod-local .table .ac{text-align:center}.do-mod-local .table .active{color:#3fc2a7}.do-mod-local .table .active i{-webkit-animation:play 2s infinite linear;animation:play 2s infinite linear}.do-mod-search{flex:1;display:flex;flex-flow:column wrap}.do-mod-search .tabbar{flex:0 1 3rem;display:flex;padding:0 .5rem;line-height:2.9rem;border-bottom:0.1rem solid #e8ebf4;text-align:center}.do-mod-search .tabbar .item{flex:0 0 7.5rem;height:3rem;margin:0 .3rem;border:0.1rem solid #e8ebf4;background:#fff;color:#dae1e9}.do-mod-search .tabbar .item.active{border-bottom-color:transparent;color:#62778d}.do-mod-search .tabbar .item i{color:#ff5061}.do-mod-search .table{overflow:auto;flex:1}.do-mod-search .table .active{color:#3fc2a7}.do-mod-search .table .ac{text-align:center}.artist-desc-layer{width:60rem;height:30rem}.artist-desc-layer .layer-content{overflow-y:auto;height:85% !important;padding:1rem;line-height:2;text-indent:2em}
|
|
@ -0,0 +1,150 @@
|
|||
@charset "UTF-8";
|
||||
/**
|
||||
* 各模块的样式
|
||||
* @authors yutent<yutent@doui.cc>
|
||||
* @date 2018/12/24 17:11:35
|
||||
*/
|
||||
|
||||
@import "./var.scss";
|
||||
|
||||
|
||||
// 歌手模块
|
||||
.do-mod-artist {position:relative;display:flex;width:100%;height:100%;
|
||||
|
||||
.filter-box {flex:0 1 12rem;border-right:.1rem solid nth($cp, 1);text-align:right;
|
||||
|
||||
.item {width:100%;height:2.4rem;padding:0 1.2rem;line-height:2.4rem;color:nth($cgr, 1);
|
||||
|
||||
&.active {color:nth($ct,1);font-weight:bold}
|
||||
&:hover {padding-right:1.3rem;color:nth($ct,1);}
|
||||
}
|
||||
.pipe {display:block;width:100%;height:.7rem;}
|
||||
|
||||
}
|
||||
|
||||
.list-box {overflow-y:auto;display:flex;flex-flow:row wrap;flex:1;padding:0 1rem;
|
||||
|
||||
.item {display:flex;justify-content:center;align-items:center;flex:45%;height:7rem;margin:1rem 2.5%;padding:.5rem;background:nth($cp, 1);
|
||||
img {flex:0 1 6rem;height:6rem}
|
||||
summary {flex:2;padding:0 1rem;}
|
||||
strong {font-size:1.6rem}
|
||||
p {font-size:1.2rem;color:nth($cgr, 1)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* -------------------- 歌手&专辑样式 -------------------- */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
|
||||
.artist-box {
|
||||
position:absolute;left:0;top:0;z-index:9;width:100%;height:100%;
|
||||
background-size:cover;background-repeat:no-repeat;background-position:center center;
|
||||
|
||||
.content {display:flex;flex-flow:column wrap;width:100%;height:100%;padding:1.5rem 2.5rem;background:linear-gradient(to bottom,#fff 2%, rgba(255, 255, 255, .75), #fff 98%);backdrop-filter:blur(1rem);
|
||||
|
||||
|
||||
.name {flex:0 1 3.6rem;font-size:1.4rem;font-style:italic;font-weight:normal;
|
||||
a {text-decoration:underline;color:nth($ct, 1)}
|
||||
i {color:nth($cgr, 1)}
|
||||
}
|
||||
.desc {flex:0 1 3rem; font-size:1.2rem;color:nth($cgr, 1);
|
||||
span {padding:0 .5rem;text-decoration:underline;color:nth($ct, 1)}
|
||||
}
|
||||
|
||||
.song-album {flex:1;display:flex;flex-flow:column wrap;}
|
||||
.tab {flex:0 1 3rem;display:flex;padding:0 .5rem;line-height:2.9rem;border-bottom:.1rem solid nth($cp, 2);text-align:center;
|
||||
|
||||
.item {flex:0 0 7.5rem;height:3rem;margin:0 .3rem;
|
||||
|
||||
&.active {border-bottom:.2rem solid nth($ct, 1);color:nth($ct, 1);}
|
||||
&.disabled {opacity:.25}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 本地音乐模块
|
||||
.do-mod-local {flex:1;display:flex;flex-flow:column wrap;
|
||||
|
||||
.toolbar {flex:0 1 3rem;padding:0 1rem;line-height:2.9rem;border-bottom:.1rem solid nth($cp, 2);
|
||||
|
||||
.refresh {margin-left:1rem;color:nth($ct, 1);text-decoration:underline;}
|
||||
}
|
||||
|
||||
.table {overflow:auto;flex:1;
|
||||
|
||||
.stat {width:2.6rem;height:2.6rem;line-height:2.6rem;}
|
||||
.ac {text-align:center}
|
||||
|
||||
.active {color:nth($ct, 1);
|
||||
|
||||
i {animation: play 2s infinite linear;}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 搜索&试听模块
|
||||
.do-mod-search {flex:1;display:flex;flex-flow:column wrap;
|
||||
|
||||
.tabbar {flex:0 1 3rem;display:flex;padding:0 .5rem;line-height:2.9rem;border-bottom:.1rem solid nth($cp, 2);text-align:center;
|
||||
|
||||
.item {flex:0 0 7.5rem;height:3rem;margin:0 .3rem;border:.1rem solid nth($cp, 2);background:#fff;color:nth($cp, 3);
|
||||
|
||||
&.active {border-bottom-color:transparent;color:nth($cd, 1);}
|
||||
i {color:nth($cr, 1)}
|
||||
}
|
||||
}
|
||||
|
||||
.table {overflow:auto;flex:1;
|
||||
|
||||
.active {color:nth($ct, 1)}
|
||||
.ac {text-align:center}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.artist-desc-layer {width:60rem;height:30rem;
|
||||
|
||||
.layer-content {overflow-y:auto;height:85%!important;padding:1rem;line-height:2;text-indent:2em;}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
$ct: #3fc2a7 #19b491 #16967a;
|
||||
$cg: #58d68d #2ecc71 #27ae60;
|
||||
$cpp: #ac61ce #9b59b6 #8e44ad;
|
||||
$cb: #52a3de #2d8dd6 #2776b1;
|
||||
$cr: #ff5061 #eb3b48 #ce3742;
|
||||
$co: #ffb618 #f39c12 #e67e22;
|
||||
$cp: #f3f5fb #e8ebf4 #dae1e9;
|
||||
$cgr: #98acae #8a9b9c #748182;
|
||||
$cd: #62778d #526273 #425064;
|
||||
|
||||
@mixin ts($c: all, $t: .2s, $m: ease-in-out){
|
||||
transition:$c $t $m;
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* 播放器
|
||||
* @author yutent<yutent@doui.cc>
|
||||
* @date 2018/12/23 23:14:40
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const { exec } = require('child_process')
|
||||
const { EventEmitter } = require('events')
|
||||
const util = require('util')
|
||||
|
||||
class AudioPlayer {
|
||||
constructor() {
|
||||
this.__PLAYER__ = new Audio()
|
||||
this.__IS_PLAYED__ = false
|
||||
this.__LIST__ = [] // 播放列表
|
||||
this.__CURR__ = -1 // 当前播放的歌曲的id
|
||||
this.__PLAY_MODE__ = 'all' // all | single | random
|
||||
this.__PLAYER__.valume = 0.7
|
||||
|
||||
this.__init__()
|
||||
}
|
||||
|
||||
__init__() {
|
||||
this.__PLAYER__.addEventListener(
|
||||
'timeupdate',
|
||||
_ => {
|
||||
this.emit('play', this.__PLAYER__.currentTime)
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
this.__PLAYER__.addEventListener(
|
||||
'ended',
|
||||
_ => {
|
||||
this.emit('end')
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
static ID3(song) {
|
||||
let cmd = `ffprobe -v quiet -print_format json -show_entries format "${song}"`
|
||||
let pc = exec(cmd)
|
||||
let buf = []
|
||||
return new Promise((resolve, reject) => {
|
||||
pc.stdout.on('data', _ => {
|
||||
buf.push(_)
|
||||
})
|
||||
|
||||
pc.stderr.on('data', reject)
|
||||
|
||||
pc.stdout.on('close', _ => {
|
||||
let { format } = Buffer.from(buf)
|
||||
try {
|
||||
res = JSON.parse(res)
|
||||
resolve({
|
||||
title: format.tags.TITLE || format.tags.title,
|
||||
album: format.tags.ALBUM || format.tags.album,
|
||||
artist: format.tags.ARTIST || format.tags.artist,
|
||||
duration: +format.duration,
|
||||
size: +(format.size / 1024 / 1024).toFixed(2)
|
||||
})
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
get stat() {
|
||||
return this.__LIST__.length ? 'ready' : 'stop'
|
||||
}
|
||||
|
||||
get IS_MUTED() {
|
||||
return this.__PLAYER__.muted
|
||||
}
|
||||
|
||||
set valume(val) {
|
||||
this.__PLAYER__.valume = val / 100
|
||||
}
|
||||
|
||||
set mode(val = 'all') {
|
||||
this.__PLAY_MODE__ = val
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.__LIST__ = []
|
||||
}
|
||||
|
||||
push(songs) {
|
||||
this.__LIST__.push.apply(this.__LIST__, songs)
|
||||
}
|
||||
|
||||
// 上一首
|
||||
prev() {
|
||||
let id = this.__CURR__
|
||||
|
||||
switch (this.__PLAY_MODE__) {
|
||||
case 'all':
|
||||
id--
|
||||
if (id < 0) {
|
||||
id = this.__LIST__.length - 1
|
||||
}
|
||||
break
|
||||
case 'random':
|
||||
id = (Math.random() * this.__LIST__.length) >>> 0
|
||||
break
|
||||
// single
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
this.play(id)
|
||||
return Promise.resolve(this.__LIST__[id])
|
||||
}
|
||||
|
||||
// 下一首
|
||||
next() {
|
||||
let id = this.__CURR__
|
||||
|
||||
switch (this.__PLAY_MODE__) {
|
||||
case 'all':
|
||||
id++
|
||||
if (id >= this.__LIST__.length) {
|
||||
id = 0
|
||||
}
|
||||
break
|
||||
case 'random':
|
||||
id = (Math.random() * this.__LIST__.length) >>> 0
|
||||
break
|
||||
// single
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
this.play(id)
|
||||
return Promise.resolve(this.__LIST__[id])
|
||||
}
|
||||
|
||||
// 播放
|
||||
play(id) {
|
||||
// 播放列表里没有数据的话, 不作任何处理
|
||||
if (!this.__LIST__.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// 有ID的话,不管之前是否在播放,都切换歌曲
|
||||
if (id !== undefined) {
|
||||
let song = this.__LIST__[id]
|
||||
if (song) {
|
||||
this.__CURR__ = id
|
||||
this.__IS_PLAYED__ = true
|
||||
|
||||
this.__PLAYER__.pause()
|
||||
this.__PLAYER__.currentTime = 0
|
||||
this.__PLAYER__.src = song.path
|
||||
this.__PLAYER__.play()
|
||||
|
||||
return Promise.resolve(song)
|
||||
}
|
||||
return Promise.reject('song not found')
|
||||
} else {
|
||||
if (!this.__IS_PLAYED__) {
|
||||
this.__IS_PLAYED__ = true
|
||||
this.__PLAYER__.play()
|
||||
}
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停
|
||||
pause() {
|
||||
if (!this.__IS_PLAYED__) {
|
||||
return
|
||||
}
|
||||
this.__IS_PLAYED__ = false
|
||||
|
||||
this.__PLAYER__.pause()
|
||||
}
|
||||
|
||||
// 切换静音
|
||||
mute() {
|
||||
if (this.__CURR__ < 0) {
|
||||
return
|
||||
}
|
||||
this.__PLAYER__.muted = !this.__PLAYER__.muted
|
||||
}
|
||||
|
||||
// 跳到指定位置播放
|
||||
seek(time) {
|
||||
if (this.__CURR__ < 0) {
|
||||
return
|
||||
}
|
||||
this.__PLAYER__.pause()
|
||||
this.__PLAYER__.currentTime = time
|
||||
this.__PLAYER__.play()
|
||||
}
|
||||
}
|
||||
|
||||
util.inherits(AudioPlayer, EventEmitter)
|
||||
|
||||
export default AudioPlayer
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1 @@
|
|||
"use strict";function arraySum(t){var e=0;return t.forEach(function(t){e+=+t}),e}export const create=(t,e)=>{if(!t)return this.defafultImg;(!e||e<100)&&(e=100);var l,a=document.createElement("canvas"),r=a.getContext("2d"),c=t.slice(-3),i=t.slice(-9,-6),f=t.slice(0,8).match(/([\w]{1})/g),n=t.slice(8,16).match(/([\w]{1})/g),s=t.slice(16,24).match(/([\w]{1})/g),m=e/10;a.width=e,a.height=e,f=f.map(t=>(t=parseInt(t,16))%8),n=n.map(t=>(t=parseInt(t,16))%4),s=s.map(t=>(t=parseInt(t,16))%4),l=arraySum(f)>32?c:i,r.fillStyle="#"+c,r.fillRect(0,0,e,e);for(var u=1;u<9;u++){var o=f[u-1],h=n[u-1],p=s[u-1];o+h>8&&(o=8-h),r.fillStyle="#"+i,r.fillRect((h+1)*m,u*m,o*m,m),r.fillStyle="#"+i,r.fillRect((9-h-o)*m,u*m,o*m,m),r.fillStyle="#"+l,r.fillRect((p+1)*m,u*m,m,m),r.fillStyle="#"+l,r.fillRect((8-p)*m,u*m,m,m)}return a.toDataURL()};
|
|
@ -0,0 +1 @@
|
|||
.do-pager{display:block;height:auto;text-align:center;font-size:14px;color:#8a9b9c}.do-pager.mini{line-height:30px}.do-pager.mini .button,.do-pager.mini .page{min-width:30px;height:30px}.do-pager.medium{line-height:35px}.do-pager.medium .button,.do-pager.medium .page{min-width:35px;height:35px}.do-pager.large{line-height:40px}.do-pager.large .button,.do-pager.large .page{min-width:40px;height:40px}.do-pager .button,.do-pager .page{display:inline-block;border:0;color:#8a9b9c;text-decoration:none;cursor:pointer;vertical-align:top;font-size:14px;font-weight:100;-webkit-appearance:none;-moz-appearance:none;appearance:none}.do-pager .button{font-size:18px}.do-pager .curr,.do-pager .disabled{cursor:default}.do-pager.skin-1{width:100%}.do-pager.skin-1 .page,.do-pager.skin-1 .button,.do-pager.skin-1 .disabled,.do-pager.skin-1 .curr{padding:0 8px;margin:0 3px}.do-pager.skin-1 .curr{font-weight:bold;font-size:15px}.do-pager.skin-1 .page.disabled{min-width:0;padding:0;background:none;color:#8a9b9c}.do-pager.skin-1 .page.disabled:hover,.do-pager.skin-1 .page.disabled:active{background:none}.do-pager.skin-1 .page.curr{background:none;color:#8a9b9c}.do-pager.skin-1 .page.curr:hover,.do-pager.skin-1 .page.curr:active{background:none}.do-pager.skin-1 .button[disabled]{cursor:not-allowed}.do-pager.skin-1 .total-box,.do-pager.skin-1 .input-box{display:inline-block;padding:0 8px}.do-pager.skin-1 .input-box input{display:inline-block;width:40px;height:30px;padding:0 3px;font-size:14px;background:#fff;border:1px solid #ddd;text-align:center}.do-pager.skin-2{float:right;width:auto}.do-pager.skin-2 .page,.do-pager.skin-2 .button,.do-pager.skin-2 .disabled,.do-pager.skin-2 .curr{float:left;margin:0;padding:0 5px;color:#fff}.do-pager.skin-2 .page.disabled{display:none}.do-pager.skin-2 .button[disabled]{cursor:not-allowed}.do-pager.skin-2 .input-box{display:none}.do-pager.skin-2 .total-box{float:left;display:inline-block;padding:0 8px}.do-pager.plain .page,.do-pager.plain .button{background:#e8ebf4;color:#8a9b9c}.do-pager.plain .page:hover,.do-pager.plain .button:hover{background:#f3f5fb}.do-pager.plain .page:active,.do-pager.plain .button:active{background:#dae1e9}.do-pager.plain .button[disabled]{background:#e8ebf4}.do-pager.plain.skin-2 .curr{background:#dae1e9}.do-pager.grey .page,.do-pager.grey .button{background:#8a9b9c;color:#fff}.do-pager.grey .page:hover,.do-pager.grey .button:hover{background:#98acae}.do-pager.grey .page:active,.do-pager.grey .button:active{background:#748182}.do-pager.grey .button[disabled]{background:#8a9b9c}.do-pager.grey.skin-2 .curr{background:#748182}.do-pager.dark .page,.do-pager.dark .button{background:#526273;color:#fff}.do-pager.dark .page:hover,.do-pager.dark .button:hover{background:#526273}.do-pager.dark .page:active,.do-pager.dark .button:active{background:#425064}.do-pager.dark .button[disabled]{background:#526273}.do-pager.dark.skin-2 .curr{background:#425064}.do-pager.red .page,.do-pager.red .button{background:#eb3b48;color:#fff}.do-pager.red .page:hover,.do-pager.red .button:hover{background:#ff5061}.do-pager.red .page:active,.do-pager.red .button:active{background:#ce3742}.do-pager.red .button[disabled]{background:#eb3b48}.do-pager.red.skin-2 .curr{background:#ce3742}.do-pager.orange .page,.do-pager.orange .button{background:#f39c12;color:#fff}.do-pager.orange .page:hover,.do-pager.orange .button:hover{background:#ffb618}.do-pager.orange .page:active,.do-pager.orange .button:active{background:#e67e22}.do-pager.orange .button[disabled]{background:#f39c12}.do-pager.orange.skin-2 .curr{background:#e67e22}.do-pager.green .page,.do-pager.green .button{background:#2ecc71;color:#fff}.do-pager.green .page:hover,.do-pager.green .button:hover{background:#58d68d}.do-pager.green .page:active,.do-pager.green .button:active{background:#27ae60}.do-pager.green .button[disabled]{background:#2ecc71}.do-pager.green.skin-2 .curr{background:#27ae60}.do-pager.teal .page,.do-pager.teal .button{background:#19b491;color:#fff}.do-pager.teal .page:hover,.do-pager.teal .button:hover{background:#3fc2a7}.do-pager.teal .page:active,.do-pager.teal .button:active{background:#16967a}.do-pager.teal .button[disabled]{background:#19b491}.do-pager.teal.skin-2 .curr{background:#16967a}.do-pager.blue .page,.do-pager.blue .button{background:#2d8dd6;color:#fff}.do-pager.blue .page:hover,.do-pager.blue .button:hover{background:#52a3de}.do-pager.blue .page:active,.do-pager.blue .button:active{background:#2776b1}.do-pager.blue .button[disabled]{background:#2d8dd6}.do-pager.blue.skin-2 .curr{background:#2776b1}.do-pager.purple .page,.do-pager .button .page{background:#9b59b6;color:#fff}.do-pager.purple .page:hover,.do-pager .button .page:hover{background:#ac61ce}.do-pager.purple .page:active,.do-pager .button .page:active{background:#8e44ad}.do-pager.purple .button[disabled],.do-pager .button .button[disabled]{background:#9b59b6}.do-pager.purple.skin-2 .curr,.do-pager .button.skin-2 .curr{background:#8e44ad}
|
|
@ -0,0 +1,85 @@
|
|||
# 拖拽插件
|
||||
> 该插件可以让任意一个元素可以被拖拽,而不需要该元素是否具有定位属性。
|
||||
> 使用时,在目标元素上添加`:drag`属性即可以实现拖拽功能。
|
||||
|
||||
## 依赖
|
||||
> 依赖`Anot`框架
|
||||
|
||||
## 浏览器兼容性
|
||||
+ chrome
|
||||
+ firefox
|
||||
+ safari
|
||||
+ IE10+
|
||||
|
||||
|
||||
## 用法
|
||||
> 只需要在要拖拽的元素上添加`:drag`即可;
|
||||
> 如果要拖拽的元素不是当前元素,只需要给该属性增加一个值为想要拖拽元素的类名或ID。
|
||||
> 具体请看示例:
|
||||
> **注意:** `拖拽的元素不是本身时,只会往父级一级一级找相匹配的`
|
||||
|
||||
```html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
* {margin:0;padding:0}
|
||||
.box {width:200px;height:100px;background:#aaa;}
|
||||
.box .handle {width:200px;height:30px;background:#f30;}
|
||||
</style>
|
||||
</head>
|
||||
<body :controller="test">
|
||||
|
||||
<div class="box" :drag></div>
|
||||
|
||||
<div class="box">
|
||||
<div class="handle" :drag="box"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import Anot from 'lib/drag/index.js'
|
||||
Anot({
|
||||
$id: 'test'
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 额外参数
|
||||
|
||||
### `data-limit`
|
||||
> 用于限制元素的拖动范围,默认没有限制。 可选值为 "window"和"parent", 分别为 "限制在可视区"和"限制在父级元素的范围"
|
||||
|
||||
### `data-axis`
|
||||
> 用于限制拖动的方向, 默认值为 "xy",即不限制方向。可选值为 "x"和"y", 即只能在"x轴"或"y轴"方向拖动。
|
||||
|
||||
### `data-beforedrag`
|
||||
> 拖动前的回调,如果有设置回调方法, 则该回调的返回值,可决定该元素是否能被拖拽, 可用于在特殊场景下,临时禁用拖拽。
|
||||
> `注:`
|
||||
> 1. 该回调方法,会传入3个参数, 第1个为被拖拽的元素(dom对象), 第2个参数为 该元素的x轴绝对坐标, 第3个元素为y轴绝对坐标;
|
||||
> 2. 该回调方法, 返回false时, 本次拖拽将临时失效, 返回其他值,或没有返回值,则忽略。
|
||||
|
||||
|
||||
### `data-dragging`
|
||||
> 元素被拖动时的回调。
|
||||
> `注:`
|
||||
> 1.该回调方法,会传入3个参数, 第1个为被拖拽的元素(dom对象), 第2个参数为 该元素的x轴绝对坐标, 第3个元素为y轴绝对坐标;
|
||||
|
||||
|
||||
### `data-dragged`
|
||||
> 元素被拖动结束后的回调。
|
||||
> `注:`
|
||||
> 1. 该回调方法,会传入3个参数, 第1个为被拖拽的元素(dom对象), 第2个参数为 该元素的x轴绝对坐标, 第3个元素为y轴绝对坐标;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
"use strict";function getBindingCallback(e,t,i){var n=e.getAttribute(t);if(n)for(var a,o=0;a=i[o++];)if(a.hasOwnProperty(n)&&"function"==typeof a[n])return a[n]}Anot.ui.drag="1.0.0",Anot.directive("drag",{priority:1500,init:function(e){e.expr='"'+e.expr+'"';let t=document.documentMode?"move":"grab";window.sidebar?t="-moz-"+t:window.chrome&&(t="-webkit-"+t),Anot(e.element).css("cursor",t),e.beforedrag=getBindingCallback(e.element,"data-beforedrag",e.vmodels),e.dragging=getBindingCallback(e.element,"data-dragging",e.vmodels),e.dragged=getBindingCallback(e.element,"data-dragged",e.vmodels),e.overflow=!0,e.axis="xy",e.element.dataset.axis&&(e.axis=e.element.dataset.axis,delete e.element.dataset.axis),e.limit=!1,e.element.dataset.limit&&(e.limit=e.element.dataset.limit,e.overflow=!1,delete e.element.dataset.limit),delete e.element.dataset.beforedrag,delete e.element.dataset.dragging,delete e.element.dataset.dragged},update:function(e){let t,i,n,a,o,l,r,d,s,g,m,u,c,f,p,b=this,x=e?this.element.parentNode:this.element,v=Anot(this.element),h=Anot(document),w=null,y=null;for(;e&&x&&(x.classList||Anot.error(`${this.name}=${this.expr}, 解析异常[元素不存在]`),!x.classList.contains(e)&&x.id!==e);)x=x.parentNode;w=Anot(x),"parent"===this.limit&&(y=x.parentNode),v.bind("mousedown",function(e){let v=getComputedStyle(x),A=v.transform.replace(/matrix\((.*)\)/,"$1"),C=w.offset();if("0s"!==v.transitionDuration&&(p=v.transitionDuration,x.style.transitionDuration="0s"),(A="none"!==A?A.split(", "):[1,0,0,1,0,0])[4]-=0,A[5]-=0,t=A[4],i=A[5],c=h.scrollTop(),f=h.scrollLeft(),o=C.left-t-f,l=C.top-i-c,n=e.pageX,a=e.pageY,m=window.innerWidth,u=window.innerHeight,s=x.clientWidth,g=x.clientHeight,b.beforedrag){if(!1===b.beforedrag.call(b.vmodels[0],x,o+t,l+i))return}let k=[0,u-g,0,m-s];if("parent"===b.limit){let e=getComputedStyle(y).transform.replace(/matrix\((.*)\)/,"$1"),t=Anot(y).offset();e="none"!==e?e.split(", "):[1,0,0,1,0,0];let i=t.left-e[4]-f,n=t.top-e[5]-c;k=[n,n+y.clientHeight-g,i,i+y.clientWidth-s]}let D=h.bind("mousemove",function(e){e.preventDefault(),"y"!==b.axis&&(A[4]=e.pageX-n+t),"x"!==b.axis&&(A[5]=e.pageY-a+i),r=o+A[4],d=l+A[5],b.overflow||("y"!==b.axis&&(r<=k[2]&&(r=k[2],A[4]=r-o),r>=k[3]&&(r=k[3],A[4]=r-o)),"x"!==b.axis&&(d<=k[0]&&(d=k[0],A[5]=d-l),d>=k[1]&&(d=k[1],A[5]=d-l))),w.css({transform:"matrix("+A.join(", ")+")"}),b.dragging&&b.dragging.call(b.vmodels[0],x,r,d)}),B=h.bind("mouseup",function(e){h.unbind("mousemove",D),h.unbind("mouseup",B),x.style.transitionDuration=p,b.dragged&&b.dragged.call(b.vmodels[0],x,r,d,A[4],A[5])})})}});
|
|
@ -0,0 +1 @@
|
|||
importCss("/css/form.css");const log=console.log;Anot.ui.form="0.1.0",Anot.component("button",{__init__(e,s,t){s.text=this.text(),s.style={"border-radius":e.radius},this.classList.add("do-fn-noselect"),this.classList.add("do-button"),this.classList.add(e.color||"grey"),this.setAttribute(":click","onClick"),this.setAttribute(":class","{disabled: disabled}"),this.setAttribute(":css","style"),e.size&&this.classList.add(e.size),e.hasOwnProperty("disabled")&&(s.disabled=!0),delete e.disabled,delete e.color,delete e.size,t()},render(e){let s="";return this.props.icon&&(s=`<i class="do-button__icon do-icon-${this.props.icon}"></i>`),`${s}<span class="do-button__text" :text="text"></span>`},state:{text:"",disabled:!1,style:{}},props:{click:Anot.PropsTypes.isFunction()},skip:["style"],watch:{},methods:{onClick(){this.disabled||"function"==typeof this.props.click&&this.props.click(this.props.prop)}}}),Anot.component("radio",{__init__(e,s,t){e.hasOwnProperty("disabled")&&(s.disabled=!0),e.hasOwnProperty("checked")&&null===s.value&&(s.value=e.label),s.text=this.text(),s.checked=s.value===e.label,this.classList.add("do-radio"),this.classList.add("do-fn-noselect"),this.classList.add(e.color||"grey"),this.setAttribute(":class","{disabled: disabled, checked: checked}"),this.setAttribute(":click","onClick"),delete e.disabled,delete e.color,t()},render:()=>'\n <span class="do-radio__box"></span>\n <span class="do-radio__text" :text="text"></span>\n ',state:{value:null,text:"",checked:!1,disabled:!1},props:{label:""},watch:{value(e){this.checked=this.props.label===e}},methods:{onClick(){this.disabled||this.checked||(this.checked=!0,this.value=this.props.label)}}}),Anot.component("switch",{__init__(e,s,t){e.hasOwnProperty("disabled")&&(s.disabled=!0),e.hasOwnProperty("checked")&&null===s.value&&(s.value=!0),s.value=!!s.value,this.classList.add("do-switch"),this.classList.add("do-fn-noselect"),this.classList.add(e.color||"grey"),this.setAttribute(":class","{disabled: disabled, checked: value}"),this.setAttribute(":click","onClick"),delete e.disabled,delete e.color,t()},render:()=>'\n <span class="do-switch__label"><i class="do-switch__dot"></i></span>\n ',state:{value:null,disabled:!1},methods:{onClick(){this.disabled||(this.value=!this.value)}}}),Anot.component("checkbox",{__init__(e,s,t){Array.isArray(s.value)||(this.parentNode.removeChild(this),Anot.error("多选框的传入值必须一个数组",TypeError)),e.hasOwnProperty("disabled")&&(s.disabled=!0),e.hasOwnProperty("checked")&&Anot.Array.ensure(s.value,e.label),s.text=this.text(),s.checked=s.value.indexOf(e.label)>-1,this.classList.add("do-checkbox"),this.classList.add("do-fn-noselect"),this.classList.add(e.color||"grey"),this.setAttribute(":class","{disabled: disabled, checked: checked}"),this.setAttribute(":click","onClick"),delete e.disabled,delete e.color,t()},render:()=>'\n <span class="do-checkbox__box">\n <i class="do-icon-get" :visible="checked"></i>\n </span>\n <span class="do-checkbox__text" :text="text"></span>\n ',state:{value:[],text:"",checked:!1,disabled:!1},props:{label:""},watch:{"value.*"(e,s,t,i){this.checked=this.value.indexOf(this.props.label)>-1},"value.length"(e,s,t,i){this.checked=this.value.indexOf(this.props.label)>-1},value(e,s,t,i){this.checked=this.value.indexOf(this.props.label)>-1}},methods:{onClick(){if(this.disabled)return;let{label:e}=this.props,s=this.value.$model;for(let t in s)if(s[t]===e)return this.checked=!1,void this.value.removeAt.call(this.value,t);this.checked=!0,this.value.push(e)}}}),Anot.component("input",{__init__(e,s,t){e.hasOwnProperty("disabled")&&(s.disabled=!0),e.iconR&&(s.pos="right",e.icon=e.iconR,delete e.iconR),this.classList.add("do-input"),this.classList.add("do-fn-noselect"),this.classList.add(e.color||"grey"),e.icon&&this.classList.add("icon-"+s.pos),this.setAttribute(":class","{disabled: disabled, active: active}"),this.setAttribute(":css","{width: props.width}"),delete e.disabled,delete e.color,t()},render(){let{icon:e,placeholder:s}=this.props;return'\n <span \n class="do-input__holder"\n :class="{visible: !value || active}"\n :text="props.placeholder"></span>\n <input \n class="do-input__input"\n :attr="{disabled: disabled, type: props.type }"\n :duplex="value" \n :keyup="onKeyup"\n :blur="onBlur"\n :focus="onFocus" />'+(e?`<i class="do-input__icon do-icon-${e}"></i>`:"")},state:{pos:"left",value:"",disabled:!1,active:!1},skip:["pos"],props:{type:"text",width:180,placeholder:"",default:"",submit:Anot.PropsTypes.isFunction()},methods:{onFocus(){this.active=!0},onBlur(){this.active=!1},onKeyup(e){this.disabled||13===e.keyCode&&"function"==typeof this.props.submit&&this.props.submit()}}});export default Anot;
|
|
@ -0,0 +1,30 @@
|
|||
v1.0.0-base / 2017-09-20
|
||||
==================
|
||||
+ 统一字体图标
|
||||
+ 精简动画类型
|
||||
+ 优化样式
|
||||
|
||||
v0.0.4-base / 2017-04-20
|
||||
==================
|
||||
+ 优化offset的处理
|
||||
+ 优化样式
|
||||
|
||||
|
||||
v0.0.3-base / 2017-04-15
|
||||
==================
|
||||
+ 重构wrap方式创建弹窗实例的实现
|
||||
|
||||
|
||||
v0.0.2-base / 2017-04-13
|
||||
==================
|
||||
+ 修复:layer方式创建实例时,漏掉自身的bug;
|
||||
+ 修复layer.open()方法打开已有实例时不返回id的bug;
|
||||
+ 修复layer.close()方法关闭实例时,未修改实例状态的bug;
|
||||
+ 修改特殊模式下的实例的最小宽度为10px;
|
||||
+ 优化:layer方式创建实例的逻辑处理;
|
||||
+ 优化layer.alert()方法参数的处理;
|
||||
|
||||
|
||||
v0.0.1-base / 2017-04-06
|
||||
==================
|
||||
+ 完成layer base版移植
|
|
@ -0,0 +1 @@
|
|||
"use strict";importCss("/css/pager.css");function calculate({currPage:t,maxPageShow:e,totalPage:a}){let s=[],r=0,o=t<e/2?e-t:Math.floor(e/2);if(a<2)return s.push(1),s;t-o>1&&s.push("..."),a-t<o&&(r=o-a+t);for(let e=t-o-r;e<t+o+1&&e<=a;e++)e>0&&s.push(e);return t+o<a&&s.push("..."),s}function update(t,e){const{totalPage:a,props:{maxPageShow:s}}=e;e.currPage!==t&&(e.currPage=e.inputPage=t,"function"==typeof e.props.pageChanged&&e.props.pageChanged(t)),e.pageList.clear(),a>1?e.pageList.pushArray(calculate({currPage:t,totalPage:a,maxPageShow:s})):e.pageList.pushArray([1])}Anot.ui.pager="1.0.0";const tmpls={home:'<button class="do-icon-dbl-left button"\n :css="{\'border-radius\': props.radius}"\n :attr="{disabled: currPage === 1}"\n :data="{to: parseUrl(1)}"\n :click="go(1, $event)"></button>',end:'<button class="do-icon-dbl-right button"\n :css="{\'border-radius\': props.radius}"\n :attr="{disabled: currPage === totalPage}"\n :data="{to: parseUrl(totalPage)}"\n :click="go(totalPage, $event)"></button>',prev:'<button class="do-icon-left button"\n :css="{\'border-radius\': props.radius}"\n :attr="{disabled: currPage < 2}"\n :data="{to: parseUrl(currPage - 1)}"\n :click="go(currPage - 1, $event)"></button>',next:'<button class="do-icon-right button"\n :css="{\'border-radius\': props.radius}"\n :attr="{disabled: currPage >= totalPage}"\n :data="{to: parseUrl(currPage + 1)}"\n :click="go(currPage + 1, $event)"></button>',pager:'<button class="page"\n :for="pageList"\n :css="{\'border-radius\': props.radius}"\n :attr="{disabled: \'...\' === el || currPage === el}"\n :data="{to: parseUrl(el)}"\n :class="{disabled: \'...\' === el, curr: currPage === el}"\n :text="el"\n :click="go(el, $event)"></button>',curr:'<button class="page curr" :text="currPage"></button>',total:'<span class="total-box">共 {{totalPage}} 页 {{totalItem}} 条</span>',jumper:'<div class="input-box">前往\n <input type="text" :duplex="inputPage" :keyup="go(null, $event)"> 页\n </div>',slot:""};export default Anot.component("pager",{__init__:function(t,e,a){this.classList.add("do-pager"),this.classList.add("do-fn-noselect"),this.setAttribute(":class","{{classList.join(' ')}}"),t.theme=+t.theme||1,t.simpleMode&&(t.theme=1),e.classList=e.classList.concat("skin-"+t.theme,t.color||"plain",t.size||"mini"),t.total&&(e.totalItem=+t.total),t.pageSize&&(e.pageSize=+t.pageSize),t.layout||(t.layout="total,home,prev,pager,next,end,jumper"),2===t.theme&&(t.radius=null),delete t.total,delete t.pageSize,delete t.color,delete t.size,a()},render:function(t){let{layout:e,theme:a,simpleMode:s}=this.props;return s?e=["prev","curr","next"]:(e=e.replace(/\s/g,""),2===a&&(e=e.replace(/total|jumper/g,"")),e=e.split(",")),(e=e.map(e=>"slot"!==e?tmpls[e]||"":t&&t.extra?t.extra.join(""):void 0)).join("\n")},componentWillMount:function(){const{currPage:t,totalPage:e,props:a}=this;this.pageList.clear(),this.pageList.pushArray(calculate({currPage:t,totalPage:e,maxPageShow:a.maxPageShow}))},componentDidMount:function(){"function"==typeof this.props.created&&this.props.created(this)},state:{classList:[],currPage:1,totalItem:1,pageSize:20,inputPage:1,pageList:[]},computed:{totalPage:function(){return Math.ceil(this.totalItem/this.pageSize)}},props:{url:null,maxPageShow:5,simpleMode:!1,radius:3,pageChanged:Anot.PropsTypes.isFunction(),created:Anot.PropsTypes.isFunction()},skip:["classList"],methods:{parseUrl(t){return(t>>>=0)<1||!this.props.url||this.currPage===t?"":this.props.url.replace("{id}",t)},go(t,e){let{inputPage:a,totalPage:s,currPage:r}=this,o=e&&e.target||null;if(!(o&&o.disabled||r===t))if(t&&o){if("..."!==t){let e=o.dataset.to;e?location.href=e:t>>>=0,update(t,this)}}else if(null===t){if(a>>>=0,e&&13===e.keyCode){if(a<1||r===a)return this.inputPage=r;a>s&&(a=s),this.inputPage=a,update(a,this)}}else update(t>>>=0,this)},setSize(t){t=+t,this.pageSize!==t&&(this.pageSize=+t,update(1,this))},setTotal(t){t=+t,this.totalItem!==t&&(this.totalItem=+t,update(1,this))}}});
|
|
@ -0,0 +1 @@
|
|||
"use strict";function serialize(e,t,r){var o;if(Array.isArray(t))t.forEach(function(t,a){o=e?e+"["+(Array.isArray(t)?a:"")+"]":a,"object"==typeof t?serialize(o,t,r):r(o,t)});else for(var a in t)o=e?e+"["+a+"]":a,"object"==typeof t[a]?serialize(o,t[a],r):r(o,t[a])}var toS=Object.prototype.toString,doc=window.document,encode=encodeURIComponent,decode=decodeURIComponent,TagHooks=function(){this.option=doc.createElement("select"),this.thead=doc.createElement("table"),this.td=doc.createElement("tr"),this.area=doc.createElement("map"),this.tr=doc.createElement("tbody"),this.col=doc.createElement("colgroup"),this.legend=doc.createElement("fieldset"),this._default=doc.createElement("div"),this.g=doc.createElementNS("http://www.w3.org/2000/svg","svg"),this.optgroup=this.option,this.tbody=this.tfoot=this.colgroup=this.caption=this.thead,this.th=this.td},Format=function(){var e=this;this.tagHooks=new TagHooks,"circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use".replace(/,/g,function(t){e.tagHooks[t]=e.tagHooks.g}),this.rtagName=/<([\w:]+)/,this.rxhtml=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,this.scriptTypes={"text/javascript":1,"text/ecmascript":1,"application/ecmascript":1,"application/javascript":1},this.rhtml=/<|&#?\w+;/};Format.prototype={parseJS:function(code){if(code=(code+"").trim(),code)if(1===code.indexOf("use strict")){var script=doc.createElement("script");script.text=code,doc.head.appendChild(script).parentNode.removeChild(script)}else eval(code)},parseXML:function(e,t,r){try{t=(new DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&t.documentElement&&!t.getElementsByTagName("parsererror").length||console.error("Invalid XML: "+e),t},parseHTML:function(e){var t=doc.createDocumentFragment().cloneNode(!1);if("string"!=typeof e)return t;if(!this.rhtml.test(e))return t.appendChild(document.createTextNode(e)),t;e=e.replace(this.rxhtml,"<$1></$2>").trim();var r=(this.rtagName.exec(e)||["",""])[1].toLowerCase(),o=this.tagHooks[r]||this.tagHooks._default,a=null;o.innerHTML=e;var i=o.getElementsByTagName("script");if(i.length)for(var c,n=0;c=i[n++];)if(this.scriptTypes[c.type]){var s=doc.createElement("script").cloneNode(!1);c.attributes.forEach(function(e){s.setAttribute(e.name,e.value)}),s.text=c.text,c.parentNode.replaceChild(s,c)}for(;a=o.firstChild;)t.appendChild(a);return t},param:function(e){if(!e||"string"==typeof e||"number"==typeof e)return e;var t=[];return"object"==typeof e&&serialize("",e,function(e,r){/native code/.test(r)||(r="function"==typeof r?r():r,r="[object File]"!==toS.call(r)?encode(r):r,t.push(encode(e)+"="+r))}),t.join("&")},parseForm:function(e){for(var t,r={},o=0;t=e.elements[o++];)switch(t.type){case"select-one":case"select-multiple":if(t.name.length&&!t.disabled)for(var a,i=0;a=t.options[i++];)a.selected&&(r[t.name]=a.value||a.text);break;case"file":t.name.length&&!t.disabled&&(r[t.name]=t.files[0]);break;case void 0:case"submit":case"reset":case"button":break;case"radio":case"checkbox":if(!t.checked)break;default:t.name.length&&!t.disabled&&(r[t.name]=t.value)}return r},merge:function(e,t){if("object"!=typeof e||"object"!=typeof t)throw new TypeError("argument must be an object");if(Object.assign)return Object.assign(e,t);for(var r in t)e[r]=t[r];return e}};export default new Format;
|
|
@ -0,0 +1 @@
|
|||
const __STORE__={};function parse$And(_){let t="";for(let e in _){let i=_[e];switch(Anot.type(i)){case"object":if(i.$has){t+=`it.${e}.indexOf(${JSON.stringify(i.$has)}) > -1`;break}if(i.$in){t+=`${JSON.stringify(i.$in)}.indexOf(it.${e}) > -1`;break}if(i.$regex){t+=`${i.$regex}.test(it.${e})`;break}if(i.$lt||i.$lte){t+=`it.${e} <${i.$lte?"=":""} ${i.$lt||i.$lte}`,(i.$gt||i.$gte)&&(t+=` && it.${e} >${i.$gte?"=":""} ${i.$gt||i.$gte}`);break}if(i.$gt||i.$gte){t+=`it.${e} >${i.$gte?"=":""} ${i.$gt||i.$gte}`;break}if(i.$eq){t+=`it.${e} === ${i.$eq}`;break}default:t+=`it.${e} === ${JSON.stringify(_[e])}`}t+=" && "}return(t=t.slice(0,-4))||(t="true"),t}function parse$Or(_){let t="";return _.forEach(_=>{t+="(",t+=parse$And(_),t+=") || "}),t.slice(0,-4)}class AnotStore{constructor(_){Anot.hideProperty(this,"__name__",_),Anot.hideProperty(this,"__LAST_QUERY__",""),Anot.hideProperty(this,"__QUERY_HISTORY__",[]),__STORE__[_]||(__STORE__[_]=[],__STORE__[`${_}Dict`]={})}static collection(_){return new this(_)}__MAKE_FN__(_){let t="\n let result = [];\n let num = 0;\n for (let it of arr) {\n if(";return _.$or?t+=parse$Or(_.$or):t+=parse$And(_),t+="){\n result.push(it)\n num++\n if(limit > 0 && num >= limit){\n break\n }\n }\n }\n return result;",Function("arr","limit",t)}clear(_){this.__QUERY_HISTORY__=[],this.__LAST_QUERY__="",_&&(__STORE__[this.__name__]=[],__STORE__[`${this.__name__}Dict`]={})}getAll({filter:_,limit:t=[]}={}){const e=__STORE__[this.__name__];let i=[],r=!1;if(!e||!e.length)return i;if(t.length<1&&(t=[0]),t.length<2&&_&&(r=!0,t[0]>0&&t.unshift(0)),_){let n=JSON.stringify(_);if(this.__LAST_QUERY__===n)i=this.__QUERY_HISTORY__.slice.apply(this.__QUERY_HISTORY__,t);else{i=this.__MAKE_FN__(_)(e,r&&t[1]||0),r||(this.__LAST_QUERY__=n,this.__QUERY_HISTORY__=i,i=this.__QUERY_HISTORY__.slice.apply(this.__QUERY_HISTORY__,t))}}else i=e.slice.apply(e,t);return Anot.deepCopy(i)}get(_){const t=__STORE__[`${this.__name__}Dict`];return Anot.deepCopy(t[_])||null}count({filter:_}={}){return _?this.__LAST_QUERY__===JSON.stringify(_)?this.__QUERY_HISTORY__.length:this.getAll({filter:_,limit:[0]}).length:__STORE__[this.__name__].length}__INSERT__(_,t){let e=__STORE__[this.__name__],i=__STORE__[`${this.__name__}Dict`],r=_[t||"id"];i[r]?this.update(r,_):(e.push(_),i[r]=_)}insert(_,t){Array.isArray(_)||(_=[_]),_.forEach(_=>{this.__INSERT__(_,t)}),this.clear()}sort(_,t,e){let i="";t&&window.Intl&&(i+="\n let col = new Intl.Collator('zh')\n "),i+=e?"return arr.sort((b, a) => {":"return arr.sort((a, b) => {",i+=`\n let filter = function(val) {\n try {\n return val.${_} || ''\n } catch (err) {\n return ''\n }\n }\n `,t?window.Intl?i+="return col.compare(filter(a), filter(b))":i+="return (filter(a) + '').localeCompare(filter(b), 'zh')":i+="return filter(a) - filter(b)",i+="\n})",Function("arr",i).call(this,__STORE__[this.__name__]),this.clear()}update(_,t){let e=__STORE__[this.__name__],i=__STORE__[`${this.__name__}Dict`],r=i[_],n=e.indexOf(r);Object.assign(r,t),e.splice(n,1,r),i[_]=r}remove(_){let t=__STORE__[this.__name__],e=__STORE__[`${this.__name__}Dict`],i=e[_id],r=t.indexOf(i);t.splice(r,1),delete e[_id]}}Anot.store=window.store=AnotStore;export default AnotStore;
|
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 910 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 916 B |
After Width: | Height: | Size: 998 B |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
<link href="dist/css/reset-basic.css" rel="stylesheet">
|
||||
<link href="dist/css/elem-ui.css" rel="stylesheet">
|
||||
<link href="css/app.css" rel="stylesheet">
|
||||
<link href="css/modules.css" rel="stylesheet">
|
||||
<script>window.LIBS_BASE_URL = location.origin + '/dist'</script>
|
||||
<script type="module" src="js/app.js"></script>
|
||||
</head>
|
||||
<body class="do-fn-noselect">
|
||||
|
||||
<div class="do-mod-app" anot="app">
|
||||
|
||||
<nav class="menubar do-fn-nodrag" :if="theme === 1" :class="{focus: winFocus}">
|
||||
<i class="item quit" :click="quit(false)"></i>
|
||||
<i class="item min" :click="minimize"></i>
|
||||
<i class="item max" :click="maximize"></i>
|
||||
</nav>
|
||||
|
||||
<nav class="menubar-win do-fn-nodrag">
|
||||
<i class="item opt do-icon-menu-right"></i>
|
||||
<span :if="theme === 2">
|
||||
<i class="item do-icon-minimize" :click="minimize"></i>
|
||||
<i class="item do-icon-maximize" :click="maximize"></i>
|
||||
<i class="item do-icon-close" :click="quit(false)"></i>
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<aside class="sidebar do-fn-drag">
|
||||
|
||||
<div class="user-box">
|
||||
<div class="avatar">
|
||||
<img src="/images/avatar.jpg" alt="yutent">
|
||||
</div>
|
||||
<h2 class="uname">yutent</h2>
|
||||
</div>
|
||||
|
||||
<dl class="music-box">
|
||||
<dt class="title">酷狗在线</dt>
|
||||
<dd class="item disabled"
|
||||
:click="toggleModule('radio')"
|
||||
:class="{active: mod === 'radio'}">
|
||||
<i class="s-icon-radio"></i> 音乐电台
|
||||
</dd>
|
||||
<dd class="item"
|
||||
:click="toggleModule('rank')"
|
||||
:class="{active: mod === 'rank'}">
|
||||
<i class="s-icon-rank"></i> 排行榜
|
||||
</dd>
|
||||
<dd class="item"
|
||||
:click="toggleModule('artist')"
|
||||
:class="{active: mod === 'artist'}">
|
||||
<i class="s-icon-singer"></i> 歌手
|
||||
</dd>
|
||||
<dd class="item disabled"
|
||||
:click="toggleModule('mv')"
|
||||
:class="{active: mod === 'mv'}">
|
||||
<i class="s-icon-mv"></i> MV
|
||||
</dd>
|
||||
|
||||
<dt class="title">我的音乐</dt>
|
||||
<dd class="item"
|
||||
:click="toggleModule('search')"
|
||||
:class="{active: mod === 'search'}">
|
||||
<i class="s-icon-heart"></i> 试听列表
|
||||
</dd>
|
||||
<dd class="item"
|
||||
:click="toggleModule('local')"
|
||||
:class="{active: mod === 'local'}">
|
||||
<i class="s-icon-play-list"></i> 本地音乐
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div class="play-contrl">
|
||||
<span class="item prev s-icon-prev" :click="nextSong(-1)"></span>
|
||||
<span
|
||||
class="item play"
|
||||
:class="{'s-icon-play': !isPlaying, 's-icon-pause': isPlaying}"
|
||||
:click="play(null)">
|
||||
</span>
|
||||
<span class="item next s-icon-next" :click="nextSong(1)"></span>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
|
||||
<div class="main">
|
||||
|
||||
<div class="tool-bar do-fn-drag">
|
||||
<div class="search do-fn-nodrag">
|
||||
<input class="do-ui-input" value="">
|
||||
<i class="icon do-icon-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<content class="module" :include="views" data-cache="true"></content>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="play-bar">
|
||||
<div class="song-stat">
|
||||
<canvas ref="player"></canvas>
|
||||
</div>
|
||||
<span
|
||||
class="ctrl"
|
||||
:class="{
|
||||
's-icon-all': playMode === 0,
|
||||
's-icon-single': playMode === 1,
|
||||
's-icon-random': playMode === 2
|
||||
}"
|
||||
:click="togglePlayMode">
|
||||
</span>
|
||||
<span class="ctrl do-icon-unmute"></span>
|
||||
<span class="ctrl s-icon-eq"></span>
|
||||
<span class="ctrl lrc">词</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* 音乐APP接口
|
||||
* @author yutent<yutent@doui.cc>
|
||||
* @date 2018/12/24 16:02:00
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
import request from '/dist/request/index.js'
|
||||
|
||||
const log = console.log
|
||||
|
||||
const BASE_API_URI = 'http://mobilecdnbj.kugou.com'
|
||||
|
||||
const get = uri => {
|
||||
return request.get(BASE_API_URI + uri)
|
||||
}
|
||||
|
||||
const post = uri => {
|
||||
return request.post(BASE_API_URI + uri)
|
||||
}
|
||||
|
||||
export default {
|
||||
getLastHot100Artists() {
|
||||
return get('/api/v5/singer/list')
|
||||
.send({
|
||||
sort: 1,
|
||||
showtype: 1,
|
||||
sextype: 0,
|
||||
musician: 0,
|
||||
pagesize: 100,
|
||||
plat: 2,
|
||||
type: 0,
|
||||
page: 1
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
return JSON.parse(res.text)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getArtistList(sextype = 1, type = 1) {
|
||||
return get('/api/v5/singer/list')
|
||||
.send({
|
||||
showtype: 2,
|
||||
musician: 0,
|
||||
type,
|
||||
sextype
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
return JSON.parse(res.text)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getArtistInfo(singerid) {
|
||||
return get('/api/v3/singer/info')
|
||||
.send({ singerid })
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
return JSON.parse(res.text)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getArtistInfo(singerid) {
|
||||
return get('/api/v3/singer/info')
|
||||
.send({ singerid })
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
return JSON.parse(res.text)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getArtistSongs(singerid, page = 1) {
|
||||
return get('/api/v3/singer/song')
|
||||
.send({
|
||||
sorttype: 2,
|
||||
pagesize: 50,
|
||||
singerid,
|
||||
area_code: 1,
|
||||
page
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
return JSON.parse(res.text)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getArtistAlbums(singerid, page = 1) {
|
||||
return get('/api/v3/singer/album')
|
||||
.send({
|
||||
pagesize: 50,
|
||||
singerid,
|
||||
area_code: 1,
|
||||
page
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
return JSON.parse(res.text)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
/**
|
||||
* {sonist app}
|
||||
* @author yutent<yutent@doui.cc>
|
||||
* @date 2018/12/16 17:15:57
|
||||
*/
|
||||
|
||||
import '/dist/anot.next.js'
|
||||
import layer from '/dist/layer/index.js'
|
||||
import store from '/dist/store/index.js'
|
||||
import AudioPlayer from '/dist/audio/index.js'
|
||||
|
||||
import Api from '/js/api.js'
|
||||
|
||||
import Artist from '/js/modules/artist.js'
|
||||
import Local from '/js/modules/local.js'
|
||||
|
||||
const log = console.log
|
||||
|
||||
const fs = require('iofs')
|
||||
const path = require('path')
|
||||
const crypto = require('crypto.js')
|
||||
const { exec } = require('child_process')
|
||||
const {
|
||||
remote: { app }
|
||||
} = require('electron')
|
||||
|
||||
const HOME_PATH = app.getPath('appData')
|
||||
const APP_INI_PATH = path.join(HOME_PATH, 'app.ini')
|
||||
const MUSIC_DB_PATH = path.join(HOME_PATH, 'music.db')
|
||||
const PLAY_MODE = {
|
||||
0: 'all',
|
||||
1: 'single',
|
||||
2: 'random'
|
||||
}
|
||||
const FONTS_NAME =
|
||||
' Helvetica, Arial,"WenQuanYi Micro Hei","PingFang SC","Hiragino Sans GB","Segoe UI", "Microsoft Yahei", sans-serif'
|
||||
|
||||
// 本地音乐和试用音乐列表
|
||||
window.LS = store.collection('local')
|
||||
window.TS = store.collection('temp')
|
||||
// 音乐播放器
|
||||
window.SONIST = new AudioPlayer()
|
||||
|
||||
let appInit = fs.cat(APP_INI_PATH)
|
||||
let dbCache = fs.cat(MUSIC_DB_PATH)
|
||||
|
||||
dbCache = JSON.parse(dbCache)
|
||||
appInit = JSON.parse(appInit)
|
||||
LS.insert(dbCache)
|
||||
|
||||
dbCache = null
|
||||
|
||||
let list = fs.ls('/Volumes/extends/music')
|
||||
|
||||
let hasNew = false
|
||||
// list.forEach(it => {
|
||||
// let name = path.basename(it)
|
||||
// if (name.startsWith('.')) {
|
||||
// return
|
||||
// }
|
||||
// let hash = crypto.md5Sign(it)
|
||||
// if (LS.get(hash)) {
|
||||
// return
|
||||
// }
|
||||
// hasNew = true
|
||||
// AudioPlayer.ID3(it).then(tag => {
|
||||
// LS.insert({
|
||||
// id: hash,
|
||||
// title: tag.title,
|
||||
// album: tag.album,
|
||||
// artist: tag.artist,
|
||||
// path: `file://${it}`,
|
||||
// duration: tag.duration
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
if (hasNew) {
|
||||
setTimeout(() => {
|
||||
dbCache = JSON.stringify(LS.getAll(), '', 2)
|
||||
log(dbCache, MUSIC_DB_PATH)
|
||||
fs.echo(dbCache, MUSIC_DB_PATH)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
Anot({
|
||||
$id: 'app',
|
||||
state: {
|
||||
theme: 1, // 1:macos, 2: deepin
|
||||
winFocus: true,
|
||||
mod: 'local',
|
||||
playMode: Anot.ls('play-mode') >>> 0, // 0:all | 1:single | 2:random
|
||||
isPlaying: false,
|
||||
curr: {
|
||||
id: '',
|
||||
index: 0,
|
||||
title: '',
|
||||
artist: '',
|
||||
album: '',
|
||||
time: 0,
|
||||
duration: 0
|
||||
},
|
||||
|
||||
currTimeBar: '',
|
||||
currTimeBarPercent: 0,
|
||||
__DEG__: 0.01
|
||||
},
|
||||
skip: [],
|
||||
computed: {
|
||||
views() {
|
||||
if (!this.mod) {
|
||||
return
|
||||
}
|
||||
return '/views/' + this.mod + '.htm'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'curr.*'() {
|
||||
let { time, duration } = this.curr
|
||||
let x = time / duration
|
||||
|
||||
this.currTimeBar = `matrix(1, 0, 0, 1, ${x * this.__TB_WIDTH__}, 0)`
|
||||
this.currTimeBarPercent = 100 * x + '%'
|
||||
},
|
||||
mod(val) {
|
||||
this.activeModule(val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let canvas = this.$refs.player
|
||||
|
||||
// 画布放大4倍, 以解决模糊的问题
|
||||
this.__WIDTH__ = canvas.clientWidth * 4
|
||||
this.__HEIGHT__ = canvas.clientHeight * 4
|
||||
|
||||
canvas.width = this.__WIDTH__
|
||||
canvas.height = this.__HEIGHT__
|
||||
this.__CTX__ = canvas.getContext('2d')
|
||||
|
||||
// 修改歌曲进度
|
||||
canvas.addEventListener(
|
||||
'click',
|
||||
ev => {
|
||||
let rect = canvas.getBoundingClientRect()
|
||||
let aw = rect.width
|
||||
let ax = ev.pageX - rect.left
|
||||
let ay = ev.pageY - rect.top
|
||||
|
||||
log(aw, ax, ay)
|
||||
if (ax > 124 && ay > 55 && ay < 64) {
|
||||
let pp = (ax - 124) / (aw - 124)
|
||||
this.curr.time = pp * this.curr.duration
|
||||
log(pp, this.curr.time)
|
||||
SONIST.seek(this.curr.time)
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
// 设置循环模式
|
||||
SONIST.mode = PLAY_MODE[this.playMode]
|
||||
|
||||
SONIST.on('play', time => {
|
||||
this.curr.time = time
|
||||
})
|
||||
|
||||
SONIST.on('end', time => {
|
||||
this.nextSong(1)
|
||||
})
|
||||
|
||||
this.activeModule(this.mod)
|
||||
},
|
||||
methods: {
|
||||
quit() {},
|
||||
minimize() {},
|
||||
maximize() {},
|
||||
|
||||
activeModule(mod) {
|
||||
switch (mod) {
|
||||
case 'artist':
|
||||
Artist.__init__()
|
||||
break
|
||||
case 'local':
|
||||
Local.__init__()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
toggleModule(mod) {
|
||||
if (['radio', 'mv'].includes(mod)) {
|
||||
return
|
||||
}
|
||||
this.mod = mod
|
||||
},
|
||||
togglePlayMode() {
|
||||
let mod = this.playMode
|
||||
mod++
|
||||
if (mod > 2) {
|
||||
mod = 0
|
||||
}
|
||||
this.playMode = mod
|
||||
SONIST.mode = PLAY_MODE[mod]
|
||||
Anot.ls('play-mode', mod)
|
||||
},
|
||||
|
||||
draw() {
|
||||
let img1 = new Image()
|
||||
let img2 = new Image()
|
||||
let p1 = Promise.defer()
|
||||
let p2 = Promise.defer()
|
||||
let { title, artist, cover } = this.curr
|
||||
let play = this.isPlaying
|
||||
|
||||
img1.onload = p1.resolve
|
||||
img2.onload = p2.resolve
|
||||
img1.src = '/images/disk.png'
|
||||
img2.src = cover || '/images/album.png'
|
||||
|
||||
let rx = (play ? 112 : 40) + this.__HEIGHT__ / 2 // 旋转唱片的圆心坐标X
|
||||
let ry = this.__HEIGHT__ / 2 // 旋转唱片的圆心坐标Y
|
||||
let pw = this.__WIDTH__ - this.__HEIGHT__ - 180 // 进度条总长度
|
||||
let wl = this.__HEIGHT__ + 180 // 文字的坐标X
|
||||
const draw = () => {
|
||||
let { time, duration } = this.curr
|
||||
let pp = time / duration // 进度百分比
|
||||
time = Anot.filters.time(time)
|
||||
duration = Anot.filters.time(duration)
|
||||
|
||||
this.__CTX__.clearRect(0, 0, this.__WIDTH__, this.__HEIGHT__)
|
||||
this.__CTX__.save()
|
||||
|
||||
// 将原点移到唱片圆心, 旋转完再回到初始值
|
||||
this.__CTX__.translate(rx, ry)
|
||||
this.__CTX__.rotate(this.__DEG__ * Math.PI)
|
||||
this.__CTX__.translate(-rx, -ry)
|
||||
|
||||
this.__CTX__.drawImage(
|
||||
img1,
|
||||
play ? 112 : 40,
|
||||
0,
|
||||
this.__HEIGHT__,
|
||||
this.__HEIGHT__
|
||||
)
|
||||
|
||||
this.__CTX__.restore()
|
||||
|
||||
this.__CTX__.drawImage(img2, 0, 0, this.__HEIGHT__, this.__HEIGHT__)
|
||||
|
||||
// 歌曲标题和歌手
|
||||
this.__CTX__.fillStyle = '#62778d'
|
||||
this.__CTX__.font = '56px' + FONTS_NAME
|
||||
this.__CTX__.fillText(`${title} - ${artist}`, wl, 100)
|
||||
|
||||
// 时间
|
||||
this.__CTX__.fillStyle = '#98acae'
|
||||
this.__CTX__.font = '48px' + FONTS_NAME
|
||||
this.__CTX__.fillText(
|
||||
`${time} / ${duration}`,
|
||||
this.__WIDTH__ - 280,
|
||||
100
|
||||
)
|
||||
|
||||
// 歌词
|
||||
this.__CTX__.fillStyle = '#98acae'
|
||||
this.__CTX__.font = '48px' + FONTS_NAME
|
||||
this.__CTX__.fillText(`暂无歌词...`, wl, 180)
|
||||
|
||||
// 进度条
|
||||
this.__CTX__.fillStyle = '#dae1e9'
|
||||
this.__CTX__.fillRect(wl, 230, pw, 16)
|
||||
this.__CTX__.fillStyle = '#3fc2a7'
|
||||
this.__CTX__.fillRect(wl, 230, pw * pp, 16)
|
||||
|
||||
this.__DEG__ += 0.01
|
||||
}
|
||||
|
||||
Promise.all([p1.promise, p2.promise]).then(_ => {
|
||||
clearInterval(this.timer)
|
||||
if (play) {
|
||||
this.timer = setInterval(() => {
|
||||
draw(img1, img2, play, rx, ry)
|
||||
}, 20)
|
||||
} else {
|
||||
draw(img1, img2, play, rx, ry)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
nextSong(step) {
|
||||
let _p = null
|
||||
if (step > 0) {
|
||||
_p = SONIST.next()
|
||||
} else {
|
||||
_p = SONIST.prev()
|
||||
}
|
||||
this.isPlaying = false
|
||||
_p.then(it => {
|
||||
this.curr = {
|
||||
...it,
|
||||
time: 0,
|
||||
cover:
|
||||
'http://imge.kugou.com/stdmusic/480/20170906/20170906161516611883.jpg'
|
||||
}
|
||||
// 通知子模块歌曲已经改变
|
||||
this.$fire('child!curr', it.id)
|
||||
this.play()
|
||||
})
|
||||
},
|
||||
|
||||
pause() {
|
||||
this.isPlaying = false
|
||||
},
|
||||
|
||||
play(song) {
|
||||
// 有参数的,说明是播放回调通知
|
||||
// 此时仅更新播放控制条的信息即可
|
||||
if (song) {
|
||||
this.curr = {
|
||||
...song,
|
||||
time: 0,
|
||||
cover:
|
||||
'http://imge.kugou.com/stdmusic/480/20170906/20170906161516611883.jpg'
|
||||
}
|
||||
this.isPlaying = true
|
||||
} else {
|
||||
if (SONIST.stat === 'ready') {
|
||||
if (this.isPlaying) {
|
||||
SONIST.pause()
|
||||
} else {
|
||||
SONIST.play()
|
||||
}
|
||||
this.isPlaying = !this.isPlaying
|
||||
}
|
||||
}
|
||||
this.draw()
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* 歌手模块
|
||||
* @author yutent<yutent@doui.cc>
|
||||
* @date 2018/12/24 17:00:48
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
import Api from '/js/api.js'
|
||||
|
||||
const log = console.log
|
||||
|
||||
export default Anot({
|
||||
$id: 'artist',
|
||||
state: {
|
||||
filter: 'hot',
|
||||
list: [], //歌手列表
|
||||
display: 'artist', // list | artist | album
|
||||
artist: {
|
||||
avatar:
|
||||
'http://singerimg.kugou.com/uploadpic/softhead/240/20181023/20181023141706176.jpg',
|
||||
id: 3060,
|
||||
name: '薛之谦',
|
||||
info: '',
|
||||
songCount: 0,
|
||||
mvCount: 0,
|
||||
albumCount: 0
|
||||
},
|
||||
songList: [], //单曲列表
|
||||
albumList: [] //专辑列表
|
||||
},
|
||||
methods: {
|
||||
__init__() {
|
||||
// Api.getArtistList().then(json => {
|
||||
// log(json)
|
||||
// })
|
||||
// this.getHotArtist()
|
||||
this.getArtistInfo()
|
||||
},
|
||||
search(ev) {
|
||||
let target = ev.target
|
||||
if (target.tagName !== 'SECTION') {
|
||||
return
|
||||
}
|
||||
let key = target.dataset.key
|
||||
|
||||
this.filter = key
|
||||
|
||||
switch (key) {
|
||||
case 'hot':
|
||||
this.getHotArtist()
|
||||
break
|
||||
default:
|
||||
key = key.split(',')
|
||||
this.getArtistList.apply(this, key)
|
||||
}
|
||||
},
|
||||
|
||||
pickArtist(ev) {
|
||||
if (ev.target === ev.currentTarget) {
|
||||
return
|
||||
}
|
||||
let target = ev.target
|
||||
while (target.tagName !== 'LI') {
|
||||
target = target.parentNode
|
||||
}
|
||||
|
||||
let { index } = target.dataset
|
||||
|
||||
let artist = this.list[index]
|
||||
|
||||
this.artist.id = artist.id
|
||||
this.artist.name = artist.name
|
||||
this.artist.avatar = artist.avatar
|
||||
|
||||
this.display = 'artist'
|
||||
|
||||
this.getArtistInfo()
|
||||
},
|
||||
|
||||
showArtistInfo() {
|
||||
layer.open({
|
||||
type: 7,
|
||||
title: '歌手详细介绍',
|
||||
content: this.artist.info,
|
||||
fixed: true,
|
||||
maskClose: true,
|
||||
extraClass: 'artist-desc-layer'
|
||||
})
|
||||
},
|
||||
|
||||
toArtistListPage() {
|
||||
this.display = 'list'
|
||||
},
|
||||
|
||||
getArtistInfo() {
|
||||
Api.getArtistInfo(this.artist.id).then(json => {
|
||||
log(json)
|
||||
|
||||
this.artist.info = json.data.intro.replace(/\n/g, '<br>')
|
||||
this.artist.songCount = json.data.songcount
|
||||
this.artist.mvCount = json.data.mvcount
|
||||
this.artist.albumCount = json.data.albumcount
|
||||
})
|
||||
},
|
||||
|
||||
getHotArtist() {
|
||||
let cache = Anot.ss('hot-artist')
|
||||
if (cache) {
|
||||
cache = JSON.parse(cache)
|
||||
this.list.clear()
|
||||
this.list.pushArray(cache)
|
||||
} else {
|
||||
Api.getLastHot100Artists().then(json => {
|
||||
log(json)
|
||||
let list = json.data.info.map(it => {
|
||||
return {
|
||||
id: it.singerid,
|
||||
name: it.singername,
|
||||
avatar: it.imgurl.replace('{size}', '240'),
|
||||
fans: it.fanscount
|
||||
}
|
||||
})
|
||||
|
||||
Anot.ss('hot-artist', JSON.stringify(list))
|
||||
|
||||
this.list.clear()
|
||||
this.list.pushArray(list)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getArtistList(type, sextype) {
|
||||
// Api.getArtistList().then(json => {
|
||||
// log(json)
|
||||
// })
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* 本地音乐模块
|
||||
* @author yutent<yutent@doui.cc>
|
||||
* @date 2018/12/24 17:00:48
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
import Api from '/js/api.js'
|
||||
|
||||
const log = console.log
|
||||
|
||||
export default Anot({
|
||||
$id: 'local',
|
||||
state: {
|
||||
list: [],
|
||||
curr: ''
|
||||
},
|
||||
__APP__: null,
|
||||
mounted() {
|
||||
this.__APP__ = Anot.vmodels.app
|
||||
this.list = LS.getAll()
|
||||
let lastPlay = Anot.ls('last-play') || 0
|
||||
|
||||
SONIST.clear()
|
||||
SONIST.push(LS.getAll())
|
||||
SONIST.play(lastPlay).then(it => {
|
||||
this.__APP__.play(it)
|
||||
this.curr = it.id
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
'props.curr'(v) {
|
||||
this.curr = v
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
__init__() {},
|
||||
|
||||
play(idx) {
|
||||
SONIST.play(idx).then(it => {
|
||||
this.__APP__.play(it)
|
||||
this.curr = it.id
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 本地音乐模块
|
||||
* @author yutent<yutent@doui.cc>
|
||||
* @date 2018/12/24 17:00:48
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
import Api from '/js/api.js'
|
||||
|
||||
const log = console.log
|
||||
|
||||
export default Anot({
|
||||
$id: 'search',
|
||||
state: {
|
||||
filter: 'hot',
|
||||
list: [], //歌手列表
|
||||
display: 'artist', // list | artist | album
|
||||
artist: {
|
||||
avatar:
|
||||
'http://singerimg.kugou.com/uploadpic/softhead/240/20181023/20181023141706176.jpg',
|
||||
id: 3060,
|
||||
name: '薛之谦',
|
||||
info: '',
|
||||
songCount: 0,
|
||||
mvCount: 0,
|
||||
albumCount: 0
|
||||
},
|
||||
songList: [], //单曲列表
|
||||
albumList: [] //专辑列表
|
||||
},
|
||||
methods: {}
|
||||
})
|
|
@ -0,0 +1,58 @@
|
|||
const { app, BrowserWindow, protocol } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('iofs')
|
||||
const log = console.log
|
||||
|
||||
const ROOT = __dirname
|
||||
const HOME = app.getPath('home')
|
||||
const MIME_TYPES = {
|
||||
js: 'application/javascript',
|
||||
html: 'text/html',
|
||||
htm: 'text/html',
|
||||
css: 'text/css',
|
||||
jpg: 'image/jpg',
|
||||
png: 'image/png',
|
||||
gif: 'image/gif'
|
||||
}
|
||||
|
||||
let win = null
|
||||
|
||||
function createWindow() {
|
||||
// 创建浏览器窗口
|
||||
win = new BrowserWindow({
|
||||
title: 'sonist',
|
||||
width: 1024,
|
||||
height: 600,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
experimentalFeatures: true
|
||||
}
|
||||
})
|
||||
|
||||
// 然后加载应用的 index.html。
|
||||
win.loadURL('app://sonist/index.html')
|
||||
}
|
||||
app.commandLine.appendSwitch('--autoplay-policy', 'no-user-gesture-required')
|
||||
app.setPath('appData', path.resolve(HOME, '.sonist/'))
|
||||
protocol.registerStandardSchemes(['app'], { secure: true })
|
||||
|
||||
let appPath = app.getPath('appData')
|
||||
if (!fs.exists(appPath)) {
|
||||
fs.mkdir(appPath)
|
||||
fs.echo('{}', path.join(appPath, 'app.ini'))
|
||||
fs.echo('[]', path.join(appPath, 'music.db'))
|
||||
}
|
||||
|
||||
// 创建窗口
|
||||
app.on('ready', () => {
|
||||
protocol.registerBufferProtocol('app', (req, cb) => {
|
||||
let file = req.url.replace(/^app:\/\/sonist\//, '')
|
||||
let ext = path.extname(req.url).slice(1)
|
||||
let buf = fs.cat(path.resolve(ROOT, file))
|
||||
cb({ data: buf, mimeType: MIME_TYPES[ext] })
|
||||
})
|
||||
createWindow()
|
||||
win.webContents.openDevTools()
|
||||
})
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "sonist",
|
||||
"version": "1.0.0",
|
||||
"description": "Music Player",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron ."
|
||||
},
|
||||
"author": "yutent",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"crypto.js": "^1.1.6",
|
||||
"iofs": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^4.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<div class="do-mod-artist" anot="artist">
|
||||
|
||||
<div class="filter-box" :click="search">
|
||||
<section class="item" data-key="hot" :class="{active: filter === 'hot'}">热门歌手</section>
|
||||
<section class="item" data-key="0,0" :class="{active: filter === '0,0'}">入驻音乐人</section>
|
||||
|
||||
<span class="pipe"></span>
|
||||
|
||||
<section class="item" data-key="1,1" :class="{active: filter === '1,1'}">华语男歌手</section>
|
||||
<section class="item" data-key="1,2" :class="{active: filter === '1,2'}">华语女歌手</section>
|
||||
<section class="item" data-key="1,3" :class="{active: filter === '1.3'}">华语组合</section>
|
||||
|
||||
<span class="pipe"></span>
|
||||
|
||||
<section class="item" data-key="6,1" :class="{active: filter === '6,1'}">韩国男歌手</section>
|
||||
<section class="item" data-key="6,2" :class="{active: filter === '6,2'}">韩国女歌手</section>
|
||||
<section class="item" data-key="6,3" :class="{active: filter === '6,3'}">韩国组合</section>
|
||||
|
||||
<span class="pipe"></span>
|
||||
|
||||
<section class="item" data-key="5,1" :class="{active: filter === '5,1'}">日本男歌手</section>
|
||||
<section class="item" data-key="5,2" :class="{active: filter === '5,2'}">日本女歌手</section>
|
||||
<section class="item" data-key="5,3" :class="{active: filter === '5,3'}">日本组合</section>
|
||||
|
||||
<span class="pipe"></span>
|
||||
|
||||
<section class="item" data-key="2,1" :class="{active: filter === '2,1'}">欧美男歌手</section>
|
||||
<section class="item" data-key="2,2" :class="{active: filter === '2,2'}">欧美女歌手</section>
|
||||
<section class="item" data-key="2,3" :class="{active: filter === '2,3'}">欧美组合</section>
|
||||
|
||||
<span class="pipe"></span>
|
||||
|
||||
<section class="item" data-key="4,0" :class="{active: filter === '4,0'}">其他歌手</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<ul class="list-box" :click="pickArtist">
|
||||
<li class="item" :for="it in list" :data="{artist: it.id, index: $index}">
|
||||
<img :attr="{src: it.avatar, alt: it.name}" />
|
||||
<summary>
|
||||
<strong :text="it.name"></strong>
|
||||
<p>粉丝数: {{it.fans}}</p>
|
||||
</summary>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
class="artist-box"
|
||||
:if="display === 'artist'"
|
||||
:css="{'background-image': 'url(' + artist.avatar + ')'}">
|
||||
<div class="content">
|
||||
<h3 class="name">
|
||||
<i class="s-icon-singer"></i>
|
||||
<a :click="toArtistListPage">歌手列表</a>
|
||||
<i class="do-icon-right"></i>
|
||||
<span :text="artist.name"></span>
|
||||
</h3>
|
||||
<cite class="desc">
|
||||
介绍: {{artist.info | truncate(50)}} <span :click="showArtistInfo">详细</span>
|
||||
</cite>
|
||||
|
||||
<dl class="song-album">
|
||||
<dt class="tab">
|
||||
<span class="item active">单曲({{artist.songCount}})</span>
|
||||
<span class="item">专辑({{artist.albumCount}})</span>
|
||||
<span class="item disabled">MV({{artist.mvCount}})</span>
|
||||
</dt>
|
||||
<dd class="list">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr :for="it in songList">
|
||||
<td :text="it.title"></td>
|
||||
<td :text="it.album"></td>
|
||||
<td class="ac" :text="it.duration | time"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="album-box" :if="display === 'album'">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
|
@ -0,0 +1,31 @@
|
|||
<div class="do-mod-local" anot="local">
|
||||
<div class="toolbar">
|
||||
本地音乐({{list.length}}首)<span class="refresh">重新扫描</span>
|
||||
</div>
|
||||
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>歌名</th>
|
||||
<th>歌手</th>
|
||||
<th>专辑</th>
|
||||
<th>时长</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
:class="{active: it.id === curr}"
|
||||
:for="it in list"
|
||||
:dblclick="play($index)">
|
||||
<td class="ac"><i class="stat s-icon-music"></i></td>
|
||||
<td :text="it.title"></td>
|
||||
<td class="ac" :text="it.artist"></td>
|
||||
<td class="ac" :text="it.album"></td>
|
||||
<td class="ac" :text="it.duration | time"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
<div class="do-mod-search">
|
||||
<div class="tabbar">
|
||||
<span class="item active">我的音乐</span>
|
||||
<span class="item">汪苏泷 <i class="do-icon-close"></i></span>
|
||||
</div>
|
||||
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>歌名</th>
|
||||
<th>歌手</th>
|
||||
<th>专辑</th>
|
||||
<th>时长</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
:class="{active: it.id === curr.id}"
|
||||
:for="it in list"
|
||||
:dblclick="play(it, $index)">
|
||||
<td><i class="s-icon-music"></i></td>
|
||||
<td :text="it.title"></td>
|
||||
<td class="ac" :text="it.artist"></td>
|
||||
<td class="ac" :text="it.album"></td>
|
||||
<td class="ac" :text="it.duration | time"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|