初始提交

This commit is contained in:
lyswhut
2019-08-16 17:27:23 +08:00
parent 4dc34a48f4
commit 14f7bd10a3
112 changed files with 24294 additions and 2 deletions
+160
View File
@@ -0,0 +1,160 @@
<template lang="pug">
#container(v-if="isProd" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
core-aside#left
#right
core-toolbar#toolbar
core-view#view
core-player#player
core-icons
material-version-modal(v-show="version.showModal")
#container(v-else :class="theme")
core-aside#left
#right
core-toolbar#toolbar
core-view#view
core-player#player
core-icons
material-version-modal(v-show="version.showModal")
</template>
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn } from '../common/icp'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
const win = require('electron').remote.getCurrentWindow()
const body = document.body
export default {
data() {
return {
isProd: process.env.NODE_ENV === 'production',
}
},
computed: {
...mapGetters(['electronStore', 'setting', 'theme', 'version']),
...mapGetters('list', ['defaultList']),
...mapGetters('download', {
downloadList: 'list',
}),
},
mounted() {
this.init()
},
watch: {
setting: {
handler(n) {
this.electronStore.set('setting', n)
},
deep: true,
},
defaultList: {
handler(n) {
this.electronStore.set('list.defaultList', n)
},
deep: true,
},
downloadList: {
handler(n) {
this.electronStore.set('download.list', n)
},
deep: true,
},
},
methods: {
...mapActions(['getVersionInfo']),
...mapMutations(['setNewVersion', 'setVersionVisible']),
...mapMutations('list', ['initDefaultList']),
...mapMutations('download', ['updateDownloadList']),
init() {
if (this.isProd) {
body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
rendererOn('update-downloaded', () => {
this.getVersionInfo().then(body => {
this.setNewVersion(body)
this.$nextTick(() => {
this.setVersionVisible(true)
})
})
})
this.initData()
},
enableIgnoreMouseEvents() {
win.setIgnoreMouseEvents(false)
// console.log('content enable')
},
dieableIgnoreMouseEvents() {
// console.log('content disable')
win.setIgnoreMouseEvents(true, { forward: true })
},
initData() { // 初始化数据
this.initPlayList() // 初始化播放列表
this.initDownloadList() // 初始化下载列表
},
initPlayList() {
let defaultList = this.electronStore.get('list.defaultList')
if (defaultList) {
defaultList.list.forEach(m => {
m.typeUrl = {}
})
this.initDefaultList(defaultList)
}
},
initDownloadList() {
let downloadList = this.electronStore.get('download.list')
if (downloadList) {
this.updateDownloadList(downloadList)
}
},
},
beforeDestroy() {
if (this.isProd) {
body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
},
}
</script>
<style lang="less">
@import './assets/styles/index.less';
@import './assets/styles/layout.less';
body {
// background-color: #fff;
padding: @shadow-app;
user-select: none;
height: 100vh;
box-sizing: border-box;
}
#container {
position: relative;
display: flex;
height: 100%;
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
// background-color: #fff;
border-radius: 4px;
overflow: hidden;
}
#left {
flex: none;
width: @width-app-left;
}
#right {
flex: auto;
display: flex;
flex-flow: column nowrap;
}
#toolbar, #player {
flex: none;
}
#view {
flex: auto;
height: 0;
}
</style>
+689
View File
@@ -0,0 +1,689 @@
@keyframes flipInX {
from {
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
animation-timing-function: ease-in;
opacity: 0;
}
40% {
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
animation-timing-function: ease-in;
}
60% {
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
opacity: 1;
}
80% {
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
}
to {
transform: perspective(400px);
}
}
@keyframes flipOutX {
from {
transform: perspective(400px);
}
30% {
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
opacity: 1;
}
to {
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
opacity: 0;
}
}
@keyframes flipInY {
from {
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
animation-timing-function: ease-in;
opacity: 0;
}
40% {
transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
animation-timing-function: ease-in;
}
60% {
transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
opacity: 1;
}
80% {
transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
}
to {
transform: perspective(400px);
}
}
@keyframes flipOutY {
from {
transform: perspective(400px);
}
30% {
transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
opacity: 1;
}
to {
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
opacity: 0;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes bounceIn {
from,
20%,
40%,
60%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
transform: scale3d(0.97, 0.97, 0.97);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
@keyframes bounceOut {
20% {
transform: scale3d(0.9, 0.9, 0.9);
}
50%,
55% {
opacity: 1;
transform: scale3d(1.1, 1.1, 1.1);
}
to {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
}
@keyframes lightSpeedIn {
from {
transform: translate3d(100%, 0, 0) skewX(-30deg);
opacity: 0;
}
60% {
transform: skewX(20deg);
opacity: 1;
}
80% {
transform: skewX(-5deg);
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes lightSpeedOut {
from {
opacity: 1;
}
to {
transform: translate3d(100%, 0, 0) skewX(30deg);
opacity: 0;
}
}
@keyframes rotateIn {
from {
transform-origin: center;
transform: rotate3d(0, 0, 1, -200deg);
opacity: 0;
}
to {
transform-origin: center;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInDownLeft {
from {
transform-origin: left bottom;
transform: rotate3d(0, 0, 1, -45deg);
opacity: 0;
}
to {
transform-origin: left bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInDownRight {
from {
transform-origin: right bottom;
transform: rotate3d(0, 0, 1, 45deg);
opacity: 0;
}
to {
transform-origin: right bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInUpLeft {
from {
transform-origin: left bottom;
transform: rotate3d(0, 0, 1, 45deg);
opacity: 0;
}
to {
transform-origin: left bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInUpRight {
from {
transform-origin: right bottom;
transform: rotate3d(0, 0, 1, -90deg);
opacity: 0;
}
to {
transform-origin: right bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateOut {
from {
transform-origin: center;
opacity: 1;
}
to {
transform-origin: center;
transform: rotate3d(0, 0, 1, 200deg);
opacity: 0;
}
}
@keyframes rotateOutDownLeft {
from {
transform-origin: left bottom;
opacity: 1;
}
to {
transform-origin: left bottom;
transform: rotate3d(0, 0, 1, 45deg);
opacity: 0;
}
}
@keyframes rotateOutDownRight {
from {
transform-origin: right bottom;
opacity: 1;
}
to {
transform-origin: right bottom;
transform: rotate3d(0, 0, 1, -45deg);
opacity: 0;
}
}
@keyframes rotateOutUpLeft {
from {
transform-origin: left bottom;
opacity: 1;
}
to {
transform-origin: left bottom;
transform: rotate3d(0, 0, 1, -45deg);
opacity: 0;
}
}
@keyframes rotateOutUpRight {
from {
transform-origin: right bottom;
opacity: 1;
}
to {
transform-origin: right bottom;
transform: rotate3d(0, 0, 1, 90deg);
opacity: 0;
}
}
@keyframes rollIn {
from {
opacity: 0;
transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes rollOut {
from {
opacity: 1;
}
to {
opacity: 0;
transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
}
}
@keyframes zoomIn {
from {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
50% {
opacity: 1;
}
}
@keyframes zoomInDown {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInLeft {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInRight {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInUp {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomOut {
from {
opacity: 1;
}
50% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
to {
opacity: 0;
}
}
@keyframes zoomOutDown {
40% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
to {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
transform-origin: center bottom;
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomOutLeft {
40% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
}
to {
opacity: 0;
transform: scale(0.1) translate3d(-2000px, 0, 0);
transform-origin: left center;
}
}
@keyframes zoomOutRight {
40% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
}
to {
opacity: 0;
transform: scale(0.1) translate3d(2000px, 0, 0);
transform-origin: right center;
}
}
@keyframes zoomOutUp {
40% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
to {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
transform-origin: center bottom;
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes slideInDown {
from {
transform: translate3d(0, -100%, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInLeft {
from {
transform: translate3d(-100%, 0, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInRight {
from {
transform: translate3d(100%, 0, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInUp {
from {
transform: translate3d(0, 100%, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideOutDown {
from {
transform: translate3d(0, 0, 0);
}
to {
visibility: hidden;
transform: translate3d(0, 100%, 0);
}
}
@keyframes slideOutLeft {
from {
transform: translate3d(0, 0, 0);
}
to {
visibility: hidden;
transform: translate3d(-100%, 0, 0);
}
}
@keyframes slideOutRight {
from {
transform: translate3d(0, 0, 0);
}
to {
visibility: hidden;
transform: translate3d(100%, 0, 0);
}
}
@keyframes slideOutUp {
from {
transform: translate3d(0, 0, 0);
}
to {
visibility: hidden;
transform: translate3d(0, -100%, 0);
}
}
.flipInX {
backface-visibility: visible !important;
animation-name: flipInX;
}
.flipInY {
backface-visibility: visible !important;
animation-name: flipInY;
}
.fadeIn {
animation-name: fadeIn;
}
.bounceIn {
animation-duration: 0.75s;
animation-name: bounceIn;
}
.lightSpeedIn {
animation-name: lightSpeedIn;
animation-timing-function: ease-out;
}
.rotateIn {
animation-name: rotateIn;
}
.rotateInDownLeft {
animation-name: rotateInDownLeft;
}
.rotateInDownRight {
animation-name: rotateInDownRight;
}
.rotateInUpLeft {
animation-name: rotateInUpLeft;
}
.rotateInUpRight {
animation-name: rotateInUpRight;
}
.rollIn {
animation-name: rollIn;
}
.zoomIn {
animation-name: zoomIn;
}
.zoomInDown {
animation-name: zoomInDown;
}
.zoomInLeft {
animation-name: zoomInLeft;
}
.zoomInRight {
animation-name: zoomInRight;
}
.zoomInUp {
animation-name: zoomInUp;
}
.slideInDown {
animation-name: slideInDown;
}
.slideInLeft {
animation-name: slideInLeft;
}
.slideInRight {
animation-name: slideInRight;
}
.slideInUp {
animation-name: slideInUp;
}
.flipOutX {
animation-duration: 0.75s;
animation-name: flipOutX;
backface-visibility: visible !important;
}
.flipOutY {
animation-duration: 0.75s;
backface-visibility: visible !important;
animation-name: flipOutY;
}
.fadeOut {
animation-name: fadeOut;
}
.bounceOut {
animation-duration: 0.75s;
animation-name: bounceOut;
}
.lightSpeedOut {
animation-name: lightSpeedOut;
animation-timing-function: ease-in;
}
.rotateOut {
animation-name: rotateOut;
}
.rotateOutDownLeft {
animation-name: rotateOutDownLeft;
}
.rotateOutDownRight {
animation-name: rotateOutDownRight;
}
.rotateOutUpLeft {
animation-name: rotateOutUpLeft;
}
.rotateOutUpRight {
animation-name: rotateOutUpRight;
}
.hinge {
animation-duration: 2s;
animation-name: hinge;
}
.rollOut {
animation-name: rollOut;
}
.zoomOut {
animation-name: zoomOut;
}
.zoomOutDown {
animation-name: zoomOutDown;
}
.zoomOutLeft {
animation-name: zoomOutLeft;
}
.zoomOutRight {
animation-name: zoomOutRight;
}
.zoomOutUp {
animation-name: zoomOutUp;
}
.slideOutDown {
animation-name: slideOutDown;
}
.slideOutLeft {
animation-name: slideOutLeft;
}
.slideOutRight {
animation-name: slideOutRight;
}
.slideOutUp {
animation-name: slideOutUp;
}
.animated {
animation-duration: 0.5s;
animation-fill-mode: both;
}
.animated-slow {
animation-duration: 0.8s;
animation-fill-mode: both;
}
.animated-fast {
animation-duration: 0.3s;
animation-fill-mode: both;
}
+325
View File
@@ -0,0 +1,325 @@
@red-50: #ffebee;
@red-100: #ffcdd2;
@red-200: #ef9a9a;
@red-300: #e57373;
@red-400: #ef5350;
@red-500: #f44336;
@red-600: #e53935;
@red-700: #d32f2f;
@red-800: #c62828;
@red-900: #b71c1c;
@red-A100: #ff8a80;
@red-A200: #ff5252;
@red-A400: #ff1744;
@red-A700: #d50000;
@red: @red-500;
@pink-50: #fce4ec;
@pink-100: #f8bbd0;
@pink-200: #f48fb1;
@pink-300: #f06292;
@pink-400: #ec407a;
@pink-500: #e91e63;
@pink-600: #d81b60;
@pink-700: #c2185b;
@pink-800: #ad1457;
@pink-900: #880e4f;
@pink-A100: #ff80ab;
@pink-A200: #ff4081;
@pink-A400: #f50057;
@pink-A700: #c51162;
@pink: @pink-500;
@purple-50: #f3e5f5;
@purple-100: #e1bee7;
@purple-200: #ce93d8;
@purple-300: #ba68c8;
@purple-400: #ab47bc;
@purple-500: #9c27b0;
@purple-600: #8e24aa;
@purple-700: #7b1fa2;
@purple-800: #6a1b9a;
@purple-900: #4a148c;
@purple-A100: #ea80fc;
@purple-A200: #e040fb;
@purple-A400: #d500f9;
@purple-A700: #aa00ff;
@purple: @purple-500;
@deep-purple-50: #ede7f6;
@deep-purple-100: #d1c4e9;
@deep-purple-200: #b39ddb;
@deep-purple-300: #9575cd;
@deep-purple-400: #7e57c2;
@deep-purple-500: #673ab7;
@deep-purple-600: #5e35b1;
@deep-purple-700: #512da8;
@deep-purple-800: #4527a0;
@deep-purple-900: #311b92;
@deep-purple-A100: #b388ff;
@deep-purple-A200: #7c4dff;
@deep-purple-A400: #651fff;
@deep-purple-A700: #6200ea;
@deep-purple: @deep-purple-500;
@indigo-50: #e8eaf6;
@indigo-100: #c5cae9;
@indigo-200: #9fa8da;
@indigo-300: #7986cb;
@indigo-400: #5c6bc0;
@indigo-500: #3f51b5;
@indigo-600: #3949ab;
@indigo-700: #303f9f;
@indigo-800: #283593;
@indigo-900: #1a237e;
@indigo-A100: #8c9eff;
@indigo-A200: #536dfe;
@indigo-A400: #3d5afe;
@indigo-A700: #304ffe;
@indigo: @indigo-500;
@blue-50: #e3f2fd;
@blue-100: #bbdefb;
@blue-200: #90caf9;
@blue-300: #64b5f6;
@blue-400: #42a5f5;
@blue-500: #2196f3;
@blue-600: #1e88e5;
@blue-700: #1976d2;
@blue-800: #1565c0;
@blue-900: #0d47a1;
@blue-A100: #82b1ff;
@blue-A200: #448aff;
@blue-A400: #2979ff;
@blue-A700: #2962ff;
@blue: @blue-500;
@light-blue-50: #e1f5fe;
@light-blue-100: #b3e5fc;
@light-blue-200: #81d4fa;
@light-blue-300: #4fc3f7;
@light-blue-400: #29b6f6;
@light-blue-500: #03a9f4;
@light-blue-600: #039be5;
@light-blue-700: #0288d1;
@light-blue-800: #0277bd;
@light-blue-900: #01579b;
@light-blue-A100: #80d8ff;
@light-blue-A200: #40c4ff;
@light-blue-A400: #00b0ff;
@light-blue-A700: #0091ea;
@light-blue: @light-blue-500;
@cyan-50: #e0f7fa;
@cyan-100: #b2ebf2;
@cyan-200: #80deea;
@cyan-300: #4dd0e1;
@cyan-400: #26c6da;
@cyan-500: #00bcd4;
@cyan-600: #00acc1;
@cyan-700: #0097a7;
@cyan-800: #00838f;
@cyan-900: #006064;
@cyan-A100: #84ffff;
@cyan-A200: #18ffff;
@cyan-A400: #00e5ff;
@cyan-A700: #00b8d4;
@cyan: @cyan-500;
@teal-50: #e0f2f1;
@teal-100: #b2dfdb;
@teal-200: #80cbc4;
@teal-300: #4db6ac;
@teal-400: #26a69a;
@teal-500: #009688;
@teal-600: #00897b;
@teal-700: #00796b;
@teal-800: #00695c;
@teal-900: #004d40;
@teal-A100: #a7ffeb;
@teal-A200: #64ffda;
@teal-A400: #1de9b6;
@teal-A700: #00bfa5;
@teal: @teal-500;
@green-50: #e8f5e9;
@green-100: #c8e6c9;
@green-200: #a5d6a7;
@green-300: #81c784;
@green-400: #66bb6a;
@green-500: #4caf50;
@green-600: #43a047;
@green-700: #388e3c;
@green-800: #2e7d32;
@green-900: #1b5e20;
@green-A100: #b9f6ca;
@green-A200: #69f0ae;
@green-A400: #00e676;
@green-A700: #00c853;
@green: @green-500;
@light-green-50: #f1f8e9;
@light-green-100: #dcedc8;
@light-green-200: #c5e1a5;
@light-green-300: #aed581;
@light-green-400: #9ccc65;
@light-green-500: #8bc34a;
@light-green-600: #7cb342;
@light-green-700: #689f38;
@light-green-800: #558b2f;
@light-green-900: #33691e;
@light-green-A100: #ccff90;
@light-green-A200: #b2ff59;
@light-green-A400: #76ff03;
@light-green-A700: #64dd17;
@light-green: @light-green-500;
@lime-50: #f9fbe7;
@lime-100: #f0f4c3;
@lime-200: #e6ee9c;
@lime-300: #dce775;
@lime-400: #d4e157;
@lime-500: #cddc39;
@lime-600: #c0ca33;
@lime-700: #afb42b;
@lime-800: #9e9d24;
@lime-900: #827717;
@lime-A100: #f4ff81;
@lime-A200: #eeff41;
@lime-A400: #c6ff00;
@lime-A700: #aeea00;
@lime: @lime-500;
@yellow-50: #fffde7;
@yellow-100: #fff9c4;
@yellow-200: #fff59d;
@yellow-300: #fff176;
@yellow-400: #ffee58;
@yellow-500: #fec60a;
@yellow-600: #fdd835;
@yellow-700: #fbc02d;
@yellow-800: #f9a825;
@yellow-900: #f57f17;
@yellow-A100: #ffff8d;
@yellow-A200: #ffff00;
@yellow-A400: #ffea00;
@yellow-A700: #ffd600;
@yellow: @yellow-700;
@amber-50: #fff8e1;
@amber-100: #ffecb3;
@amber-200: #ffe082;
@amber-300: #ffd54f;
@amber-400: #ffca28;
@amber-500: #ffc107;
@amber-600: #ffb300;
@amber-700: #ffa000;
@amber-800: #ff8f00;
@amber-900: #ff6f00;
@amber-A100: #ffe57f;
@amber-A200: #ffd740;
@amber-A400: #ffc400;
@amber-A700: #ffab00;
@amber: @amber-500;
@orange-50: #fff3e0;
@orange-100: #ffe0b2;
@orange-200: #ffcc80;
@orange-300: #ffb74d;
@orange-400: #ffa726;
@orange-500: #ff9800;
@orange-600: #fb8c00;
@orange-700: #f57c00;
@orange-800: #ef6c00;
@orange-900: #e65100;
@orange-A100: #ffd180;
@orange-A200: #ffab40;
@orange-A400: #ff9100;
@orange-A700: #ff6d00;
@orange: @orange-500;
@deep-orange-50: #fbe9e7;
@deep-orange-100: #ffccbc;
@deep-orange-200: #ffab91;
@deep-orange-300: #ff8a65;
@deep-orange-400: #ff7043;
@deep-orange-500: #ff5722;
@deep-orange-600: #f4511e;
@deep-orange-700: #e64a19;
@deep-orange-800: #d84315;
@deep-orange-900: #bf360c;
@deep-orange-A100: #ff9e80;
@deep-orange-A200: #ff6e40;
@deep-orange-A400: #ff3d00;
@deep-orange-A700: #dd2c00;
@deep-orange: @deep-orange-500;
@brown-50: #efebe9;
@brown-100: #d7ccc8;
@brown-200: #bcaaa4;
@brown-300: #a1887f;
@brown-400: #8d6e63;
@brown-500: #795548;
@brown-600: #6d4c41;
@brown-700: #5d4037;
@brown-800: #4e342e;
@brown-900: #3e2723;
@brown-A100: #d7ccc8;
@brown-A200: #bcaaa4;
@brown-A400: #8d6e63;
@brown-A700: #5d4037;
@brown: @brown-500;
@grey-50: #fafafa;
@grey-100: #f5f5f5;
@grey-200: #eeeeee;
@grey-300: #e0e0e0;
@grey-400: #bdbdbd;
@grey-500: #9e9e9e; @rgb-grey-500: "158, 158, 158";
@grey-600: #757575;
@grey-700: #616161;
@grey-800: #424242;
@grey-900: #212121;
@grey-A100: #f5f5f5;
@grey-A200: #eeeeee;
@grey-A400: #bdbdbd;
@grey-A700: #616161;
@grey: @grey-500;
@blue-grey-50: #eceff1;
@blue-grey-100: #cfd8dc;
@blue-grey-200: #b0bec5;
@blue-grey-300: #90a4ae;
@blue-grey-400: #78909c;
@blue-grey-500: #607d8b;
@blue-grey-600: #546e7a;
@blue-grey-700: #455a64;
@blue-grey-800: #37474f;
@blue-grey-900: #263238;
@blue-grey-A100: #cfd8dc;
@blue-grey-A200: #b0bec5;
@blue-grey-A400: #78909c;
@blue-grey-A700: #455a64;
@blue-grey: @blue-grey-500;
@black: #000000; @rgb-black: "0,0,0";
@white: #ffffff; @rgb-white: "255,255,255";
+165
View File
@@ -0,0 +1,165 @@
@import './reset.less';
@import './animate.less';
*, *::after, *::before {
-webkit-user-drag: none;
}
.nobreak {
white-space: nowrap;
}
.auto-hidden {
.mixin-ellipsis-1;
}
.break {
word-break: break-all;
}
table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
overflow: hidden;
color: rgba(0, 0, 0, 0.87);
th {
font-size: 12px;
text-align: left;
height: 28px;
line-height: 28px;
padding: 5px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
tbody {
tr {
border-top: 1px solid #e0e0e0;
// border-top: 1px solid rgba(0, 0, 0, 0.12);
transition: background-color 0.2s ease;
&:hover {
background-color: #eee;
}
&:first-child {
border-top: none;
}
td {
padding: 5px;
position: relative;
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 13px;
line-height: 1.3;
vertical-align: middle;
}
}
}
}
.badge {
display: inline-block;
padding: 0.25em 0.4em;
font-size: .7em;
// font-weight: 700;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 2px;
&.badge-light {
background-color: #f8f9fa;
}
&.badge-secondary {
color: #fff;
background-color: #6c757d;
}
&.badge-info {
color: #fff;
background-color: #4baed5;
}
&.badge-warning {
color: #fff;
background-color: #ffa45a;
}
&.badge-danger {
color: #fff;
background-color: #ff705a;
}
&.badge-success {
color: #fff;
background-color: #32bc63;
}
}
small {
font-size: .8em;
}
strong {
font-weight: bold;
}
svg {
transition: @transition-theme;
transition-property: fill;
}
.hover {
cursor: pointer;
transition: color .2s ease;
&:hover {
color: @color-theme;
}
&:active {
color: @color-theme-active;
}
}
.scroll {
&::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: rgba(0, 0, 0, 0);
}
&::-webkit-scrollbar-track {
background-color: @color-scrollbar-track;
border-radius: 3px;
// background-color: rgba(0, 0, 0, 0.1);
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: @color-scrollbar-thumb;
// background-color: rgba(0, 0, 0, 0.2);
transition: all 0.4s ease;
}
&::-webkit-scrollbar-thumb:hover {
border-radius: 3px;
background-color: @color-scrollbar-thumb-hover;
// background-color: rgba(0, 0, 0, 0.4);
transition: all 0.4s ease;
}
}
each(@themes, {
#container.@{value} {
.hover {
&:hover {
color: ~'@{color-@{value}-theme}';
}
&:active {
color: ~'@{color-@{value}-theme-active}';
}
}
.scroll {
&::-webkit-scrollbar {
background-color: rgba(0, 0, 0, 0);
}
&::-webkit-scrollbar-track {
background-color: ~'@{color-@{value}-scrollbar-track}';
}
&::-webkit-scrollbar-thumb {
background-color: ~'@{color-@{value}-scrollbar-thumb}';
}
&::-webkit-scrollbar-thumb:hover {
background-color: ~'@{color-@{value}-scrollbar-thumb-hover}';
}
}
}
})
+30
View File
@@ -0,0 +1,30 @@
@import './variables.less';
/*自动隐藏文字*/
.mixin-ellipsis-1() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.mixin-ellipsis() {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
white-space: normal !important;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.mixin-ellipsis-2() {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
white-space: normal !important;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
+43
View File
@@ -0,0 +1,43 @@
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
+288
View File
@@ -0,0 +1,288 @@
@import './colors.less';
@themes: green, yellow, blue, red, purple, orange, grey;
// Colors
// @color-theme: #03a678;
@color-theme: #4daf7c;
@color-theme-hover: fadeout(lighten(@color-theme, 10%), 30%);
@color-theme-active: fadeout(darken(@color-theme, 20%), 60%);
@color-theme-font: #fff;
@color-theme-font-label: lighten(@color-theme, 35%);
@color-theme_2: #fff;
@color-theme_2-hover: fadeout(lighten(@color-theme, 10%), 70%);
@color-theme_2-active: fadeout(darken(@color-theme, 5%), 70%);
@color-theme_2-font: darken(@color-theme_2, 70%);
@color-theme_2-font-label: lighten(@color-theme, 40%);
@color-btn: fadeout(darken(@color-theme, 5%), 15%);
@color-btn-background: fadeout(lighten(@color-theme, 35%), 70%);
@color-pagination-background: fadeout(lighten(@color-theme, 45%), 30%);
@color-pagination-hover: fadeout(lighten(@color-theme, 10%), 70%);
@color-pagination-active: fadeout(darken(@color-theme, 10%), 70%);
@color-pagination-select: fadeout(lighten(@color-theme, 10%), 50%);
@color-search-form-background: fadeout(lighten(@color-theme, 35%), 10%);
// @color-search-list-background: fadeout(lighten(@color-theme, 35%), 10%);
@color-search-list-hover: fadeout(darken(@color-theme, 10%), 70%);
@color-scrollbar-track: fadeout(@color-theme, 80%);
@color-scrollbar-thumb: fadeout(@color-theme, 60%);
@color-scrollbar-thumb-hover: fadeout(@color-theme, 40%);
@color-player-pic-c1: fadeout(@color-theme_2, 50%);
@color-player-pic-c2: darken(@color-theme_2, 30%);
@color-player-progress: darken(@color-theme_2, 6%);
@color-player-progress-bar1: darken(@color-theme_2, 12%);
@color-player-progress-bar2: lighten(@color-theme, 12%);
@color-player-status-text: lighten(@color-theme_2-font, 10%);
@color-tab-btn-background: fadeout(lighten(@color-theme, 10%), 80%);
@color-tab-border-top: fadeout(lighten(@color-theme, 5%), 50%);
@color-tab-border-bottom: lighten(@color-theme, 5%);
@color-minBtn: #85c43b;
@color-maxBtn: #e7aa36;
@color-closeBtn: #ea6e4d;
@color-green-theme: #4daf7c;
@color-green-theme-hover: fadeout(lighten(@color-green-theme, 10%), 30%);
@color-green-theme-active: fadeout(darken(@color-green-theme, 20%), 60%);
@color-green-theme-font: #fff;
@color-green-theme-font-label: lighten(@color-green-theme, 35%);
@color-green-theme_2: #fff;
@color-green-theme_2-hover: fadeout(lighten(@color-green-theme, 10%), 70%);
@color-green-theme_2-active: fadeout(darken(@color-green-theme, 5%), 70%);
@color-green-theme_2-font: darken(@color-green-theme_2, 70%);
@color-green-theme_2-font-label: lighten(@color-green-theme, 40%);
@color-green-btn: fadeout(darken(@color-green-theme, 5%), 15%);
@color-green-btn-background: fadeout(lighten(@color-green-theme, 35%), 70%);
@color-green-pagination-background: fadeout(lighten(@color-green-theme, 45%), 30%);
@color-green-pagination-hover: fadeout(lighten(@color-green-theme, 10%), 70%);
@color-green-pagination-active: fadeout(darken(@color-green-theme, 10%), 70%);
@color-green-pagination-select: fadeout(lighten(@color-green-theme, 10%), 50%);
@color-green-search-form-background: fadeout(lighten(@color-green-theme, 35%), 10%);
@color-green-search-list-hover: fadeout(darken(@color-green-theme, 10%), 70%);
@color-green-scrollbar-track: fadeout(@color-green-theme, 80%);
@color-green-scrollbar-thumb: fadeout(@color-green-theme, 60%);
@color-green-scrollbar-thumb-hover: fadeout(@color-green-theme, 40%);
@color-green-player-pic-c1: fadeout(@color-green-theme_2, 50%);
@color-green-player-pic-c2: darken(@color-green-theme_2, 30%);
@color-green-player-progress: darken(@color-green-theme_2, 6%);
@color-green-player-progress-bar1: darken(@color-green-theme_2, 12%);
@color-green-player-progress-bar2: lighten(@color-green-theme, 12%);
@color-green-player-status-text: lighten(@color-green-theme_2-font, 10%);
@color-green-tab-btn-background: fadeout(lighten(@color-green-theme, 10%), 80%);
@color-green-tab-border-top: fadeout(lighten(@color-green-theme, 5%), 50%);
@color-green-tab-border-bottom: lighten(@color-green-theme, 5%);
@color-yellow-theme: #f2d35b;
@color-yellow-theme-hover: fadeout(lighten(@color-yellow-theme, 10%), 30%);
@color-yellow-theme-active: fadeout(darken(@color-yellow-theme, 20%), 60%);
@color-yellow-theme-font: #fff;
@color-yellow-theme-font-label: lighten(@color-yellow-theme, 35%);
@color-yellow-theme_2: #fff;
@color-yellow-theme_2-hover: fadeout(lighten(@color-yellow-theme, 10%), 70%);
@color-yellow-theme_2-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
@color-yellow-theme_2-font: darken(@color-yellow-theme_2, 70%);
@color-yellow-theme_2-font-label: lighten(@color-yellow-theme, 40%);
@color-yellow-btn: fadeout(darken(@color-yellow-theme, 5%), 15%);
@color-yellow-btn-background: fadeout(lighten(@color-yellow-theme, 25%), 70%);
@color-yellow-pagination-background: fadeout(lighten(@color-yellow-theme, 30%), 30%);
@color-yellow-pagination-hover: fadeout(lighten(@color-yellow-theme, 5%), 70%);
@color-yellow-pagination-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
@color-yellow-pagination-select: fadeout(lighten(@color-yellow-theme, 5%), 50%);
@color-yellow-search-form-background: fadeout(lighten(@color-yellow-theme, 35%), 10%);
@color-yellow-search-list-hover: fadeout(darken(@color-yellow-theme, 10%), 70%);
@color-yellow-scrollbar-track: fadeout(@color-yellow-theme, 80%);
@color-yellow-scrollbar-thumb: fadeout(@color-yellow-theme, 60%);
@color-yellow-scrollbar-thumb-hover: fadeout(@color-yellow-theme, 40%);
@color-yellow-player-pic-c1: fadeout(@color-yellow-theme_2, 50%);
@color-yellow-player-pic-c2: darken(@color-yellow-theme_2, 30%);
@color-yellow-player-progress: darken(@color-yellow-theme_2, 6%);
@color-yellow-player-progress-bar1: darken(@color-yellow-theme_2, 12%);
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 12%);
@color-yellow-player-status-text: lighten(@color-yellow-theme_2-font, 10%);
@color-yellow-tab-btn-background: fadeout(lighten(@color-yellow-theme, 10%), 80%);
@color-yellow-tab-border-top: fadeout(lighten(@color-yellow-theme, 5%), 50%);
@color-yellow-tab-border-bottom: lighten(@color-yellow-theme, 5%);
@color-orange-theme: #f5ab35;
@color-orange-theme-hover: fadeout(lighten(@color-orange-theme, 10%), 30%);
@color-orange-theme-active: fadeout(darken(@color-orange-theme, 20%), 60%);
@color-orange-theme-font: #fff;
@color-orange-theme-font-label: lighten(@color-orange-theme, 35%);
@color-orange-theme_2: #fff;
@color-orange-theme_2-hover: fadeout(lighten(@color-orange-theme, 10%), 70%);
@color-orange-theme_2-active: fadeout(darken(@color-orange-theme, 5%), 70%);
@color-orange-theme_2-font: darken(@color-orange-theme_2, 70%);
@color-orange-theme_2-font-label: lighten(@color-orange-theme, 40%);
@color-orange-btn: fadeout(darken(@color-orange-theme, 5%), 15%);
@color-orange-btn-background: fadeout(lighten(@color-orange-theme, 35%), 70%);
@color-orange-pagination-background: fadeout(lighten(@color-orange-theme, 35%), 30%);
@color-orange-pagination-hover: fadeout(lighten(@color-orange-theme, 10%), 70%);
@color-orange-pagination-active: fadeout(darken(@color-orange-theme, 10%), 70%);
@color-orange-pagination-select: fadeout(lighten(@color-orange-theme, 10%), 50%);
@color-orange-search-form-background: fadeout(lighten(@color-orange-theme, 35%), 10%);
@color-orange-search-list-hover: fadeout(darken(@color-orange-theme, 10%), 70%);
@color-orange-scrollbar-track: fadeout(@color-orange-theme, 80%);
@color-orange-scrollbar-thumb: fadeout(@color-orange-theme, 60%);
@color-orange-scrollbar-thumb-hover: fadeout(@color-orange-theme, 40%);
@color-orange-player-pic-c1: fadeout(@color-orange-theme_2, 50%);
@color-orange-player-pic-c2: darken(@color-orange-theme_2, 30%);
@color-orange-player-progress: darken(@color-orange-theme_2, 6%);
@color-orange-player-progress-bar1: darken(@color-orange-theme_2, 12%);
@color-orange-player-progress-bar2: lighten(@color-orange-theme, 12%);
@color-orange-player-status-text: lighten(@color-orange-theme_2-font, 10%);
@color-orange-tab-btn-background: fadeout(lighten(@color-orange-theme, 10%), 80%);
@color-orange-tab-border-top: fadeout(lighten(@color-orange-theme, 5%), 50%);
@color-orange-tab-border-bottom: lighten(@color-orange-theme, 5%);
@color-blue-theme: #3498db;
@color-blue-theme-hover: fadeout(lighten(@color-blue-theme, 10%), 30%);
@color-blue-theme-active: fadeout(darken(@color-blue-theme, 20%), 60%);
@color-blue-theme-font: #fff;
@color-blue-theme-font-label: lighten(@color-blue-theme, 35%);
@color-blue-theme_2: #fff;
@color-blue-theme_2-hover: fadeout(lighten(@color-blue-theme, 10%), 70%);
@color-blue-theme_2-active: fadeout(darken(@color-blue-theme, 5%), 70%);
@color-blue-theme_2-font: darken(@color-blue-theme_2, 70%);
@color-blue-theme_2-font-label: lighten(@color-blue-theme, 40%);
@color-blue-btn: fadeout(darken(@color-blue-theme, 5%), 15%);
@color-blue-btn-background: fadeout(lighten(@color-blue-theme, 35%), 70%);
@color-blue-pagination-background: fadeout(lighten(@color-blue-theme, 40%), 30%);
@color-blue-pagination-hover: fadeout(lighten(@color-blue-theme, 15%), 70%);
@color-blue-pagination-active: fadeout(darken(@color-blue-theme, 15%), 70%);
@color-blue-pagination-select: fadeout(lighten(@color-blue-theme, 15%), 50%);
@color-blue-search-form-background: fadeout(lighten(@color-blue-theme, 35%), 10%);
@color-blue-search-list-hover: fadeout(darken(@color-blue-theme, 10%), 70%);
@color-blue-scrollbar-track: fadeout(@color-blue-theme, 80%);
@color-blue-scrollbar-thumb: fadeout(@color-blue-theme, 60%);
@color-blue-scrollbar-thumb-hover: fadeout(@color-blue-theme, 40%);
@color-blue-player-pic-c1: fadeout(@color-blue-theme_2, 50%);
@color-blue-player-pic-c2: darken(@color-blue-theme_2, 30%);
@color-blue-player-progress: darken(@color-blue-theme_2, 6%);
@color-blue-player-progress-bar1: darken(@color-blue-theme_2, 12%);
@color-blue-player-progress-bar2: lighten(@color-blue-theme, 12%);
@color-blue-player-status-text: lighten(@color-blue-theme_2-font, 10%);
@color-blue-tab-btn-background: fadeout(lighten(@color-blue-theme, 10%), 80%);
@color-blue-tab-border-top: fadeout(lighten(@color-blue-theme, 5%), 50%);
@color-blue-tab-border-bottom: lighten(@color-blue-theme, 5%);
@color-red-theme: #d64541;
@color-red-theme-hover: fadeout(lighten(@color-red-theme, 10%), 30%);
@color-red-theme-active: fadeout(darken(@color-red-theme, 20%), 60%);
@color-red-theme-font: #fff;
@color-red-theme-font-label: lighten(@color-red-theme, 35%);
@color-red-theme_2: #fff;
@color-red-theme_2-hover: fadeout(lighten(@color-red-theme, 10%), 70%);
@color-red-theme_2-active: fadeout(darken(@color-red-theme, 5%), 70%);
@color-red-theme_2-font: darken(@color-red-theme_2, 70%);
@color-red-theme_2-font-label: lighten(@color-red-theme, 40%);
@color-red-btn: fadeout(darken(@color-red-theme, 5%), 15%);
@color-red-btn-background: fadeout(lighten(@color-red-theme, 35%), 70%);
@color-red-pagination-background: fadeout(lighten(@color-red-theme, 40%), 30%);
@color-red-pagination-hover: fadeout(lighten(@color-red-theme, 15%), 70%);
@color-red-pagination-active: fadeout(darken(@color-red-theme, 15%), 70%);
@color-red-pagination-select: fadeout(lighten(@color-red-theme, 15%), 50%);
@color-red-search-form-background: fadeout(lighten(@color-red-theme, 35%), 10%);
@color-red-search-list-hover: fadeout(darken(@color-red-theme, 10%), 70%);
@color-red-scrollbar-track: fadeout(@color-red-theme, 80%);
@color-red-scrollbar-thumb: fadeout(@color-red-theme, 60%);
@color-red-scrollbar-thumb-hover: fadeout(@color-red-theme, 40%);
@color-red-player-pic-c1: fadeout(@color-red-theme_2, 50%);
@color-red-player-pic-c2: darken(@color-red-theme_2, 30%);
@color-red-player-progress: darken(@color-red-theme_2, 6%);
@color-red-player-progress-bar1: darken(@color-red-theme_2, 12%);
@color-red-player-progress-bar2: lighten(@color-red-theme, 12%);
@color-red-player-status-text: lighten(@color-red-theme_2-font, 10%);
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 25%), 70%);
@color-red-tab-border-bottom: lighten(@color-red-theme, 35%);
@color-red-tab-btn-background: fadeout(lighten(@color-red-theme, 10%), 80%);
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 5%), 50%);
@color-red-tab-border-bottom: lighten(@color-red-theme, 5%);
@color-purple-theme: #9b59b6;
@color-purple-theme-hover: fadeout(lighten(@color-purple-theme, 10%), 30%);
@color-purple-theme-active: fadeout(darken(@color-purple-theme, 20%), 60%);
@color-purple-theme-font: #fff;
@color-purple-theme-font-label: lighten(@color-purple-theme, 35%);
@color-purple-theme_2: #fff;
@color-purple-theme_2-hover: fadeout(lighten(@color-purple-theme, 10%), 70%);
@color-purple-theme_2-active: fadeout(darken(@color-purple-theme, 5%), 70%);
@color-purple-theme_2-font: darken(@color-purple-theme_2, 70%);
@color-purple-theme_2-font-label: lighten(@color-purple-theme, 40%);
@color-purple-btn: fadeout(darken(@color-purple-theme, 5%), 15%);
@color-purple-btn-background: fadeout(lighten(@color-purple-theme, 35%), 70%);
@color-purple-pagination-background: fadeout(lighten(@color-purple-theme, 40%), 30%);
@color-purple-pagination-hover: fadeout(lighten(@color-purple-theme, 15%), 70%);
@color-purple-pagination-active: fadeout(darken(@color-purple-theme, 15%), 70%);
@color-purple-pagination-select: fadeout(lighten(@color-purple-theme, 15%), 50%);
@color-purple-search-form-background: fadeout(lighten(@color-purple-theme, 35%), 10%);
@color-purple-search-list-hover: fadeout(darken(@color-purple-theme, 10%), 70%);
@color-purple-scrollbar-track: fadeout(@color-purple-theme, 80%);
@color-purple-scrollbar-thumb: fadeout(@color-purple-theme, 60%);
@color-purple-scrollbar-thumb-hover: fadeout(@color-purple-theme, 40%);
@color-purple-player-pic-c1: fadeout(@color-purple-theme_2, 50%);
@color-purple-player-pic-c2: darken(@color-purple-theme_2, 30%);
@color-purple-player-progress: darken(@color-purple-theme_2, 6%);
@color-purple-player-progress-bar1: darken(@color-purple-theme_2, 12%);
@color-purple-player-progress-bar2: lighten(@color-purple-theme, 12%);
@color-purple-player-status-text: lighten(@color-purple-theme_2-font, 10%);
@color-purple-tab-btn-background: fadeout(lighten(@color-purple-theme, 10%), 80%);
@color-purple-tab-border-top: fadeout(lighten(@color-purple-theme, 5%), 50%);
@color-purple-tab-border-bottom: lighten(@color-purple-theme, 5%);
@color-grey-theme: #6c7a89;
@color-grey-theme-hover: fadeout(lighten(@color-grey-theme, 10%), 30%);
@color-grey-theme-active: fadeout(darken(@color-grey-theme, 20%), 60%);
@color-grey-theme-font: #fff;
@color-grey-theme-font-label: lighten(@color-grey-theme, 35%);
@color-grey-theme_2: #fff;
@color-grey-theme_2-hover: fadeout(lighten(@color-grey-theme, 10%), 70%);
@color-grey-theme_2-active: fadeout(darken(@color-grey-theme, 5%), 70%);
@color-grey-theme_2-font: darken(@color-grey-theme_2, 70%);
@color-grey-theme_2-font-label: lighten(@color-grey-theme, 40%);
@color-grey-btn: fadeout(darken(@color-grey-theme, 5%), 15%);
@color-grey-btn-background: fadeout(lighten(@color-grey-theme, 35%), 70%);
@color-grey-pagination-background: fadeout(lighten(@color-grey-theme, 45%), 30%);
@color-grey-pagination-hover: fadeout(lighten(@color-grey-theme, 10%), 70%);
@color-grey-pagination-active: fadeout(darken(@color-grey-theme, 10%), 70%);
@color-grey-pagination-select: fadeout(lighten(@color-grey-theme, 10%), 50%);
@color-grey-search-form-background: fadeout(lighten(@color-grey-theme, 35%), 10%);
@color-grey-search-list-hover: fadeout(darken(@color-grey-theme, 10%), 70%);
@color-grey-scrollbar-track: fadeout(@color-grey-theme, 80%);
@color-grey-scrollbar-thumb: fadeout(@color-grey-theme, 60%);
@color-grey-scrollbar-thumb-hover: fadeout(@color-grey-theme, 40%);
@color-grey-player-pic-c1: fadeout(@color-grey-theme_2, 50%);
@color-grey-player-pic-c2: darken(@color-grey-theme_2, 30%);
@color-grey-player-progress: darken(@color-grey-theme_2, 6%);
@color-grey-player-progress-bar1: darken(@color-grey-theme_2, 12%);
@color-grey-player-progress-bar2: lighten(@color-grey-theme, 12%);
@color-grey-player-status-text: lighten(@color-grey-theme_2-font, 10%);
@color-grey-tab-btn-background: fadeout(lighten(@color-grey-theme, 10%), 80%);
@color-grey-tab-border-top: fadeout(lighten(@color-grey-theme, 5%), 50%);
@color-grey-tab-border-bottom: lighten(@color-grey-theme, 5%);
// Width
@width-app-left: 180px;
// Height
@height-toolbar: 50px;
@height-player: 55px;
// Shadow
@shadow-app: 8px;
// Radius
@radius-progress-border: 5px;
@transition-theme: .4s ease;
+149
View File
@@ -0,0 +1,149 @@
<template lang="pug">
div(:class="$style.aside")
div(:class="$style.logo")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' height='100%' viewBox='0 0 127 61' space='preserve')
use(xlink:href='#icon-logo')
div(:class="$style.menu")
dl
dt 在线音乐
dd
router-link(:active-class="$style.active" to="search") 搜索
dd
router-link(:active-class="$style.active" to="leaderboard") 排行榜
//- dd
router-link(:active-class="$style.active" to="recommend") 歌单
dl
dt 我的音乐
dd
router-link(:active-class="$style.active" to="list") {{defaultList.name}}
router-link(:active-class="$style.active" v-for="item in userList" :to="`list?id=${item._id}`" :key="item._id") {{item.name}}
dl
dt 其他
dd
router-link(:active-class="$style.active" to="download") 下载管理
dd
router-link(:active-class="$style.active" to="setting") 设置
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
},
data() {
return {
active: 'search',
}
},
computed: {
...mapGetters('list', ['defaultList', 'userList']),
},
methods: {},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.aside {
// box-shadow: 0 0 5px rgba(0, 0, 0, .3);
transition: @transition-theme;
transition-property: background-color;
background-color: @color-theme;
// background-color: @color-aside-background;
// border-right: 2px solid @color-theme;
-webkit-app-region: drag;
-webkit-user-select: none;
display: flex;
flex-flow: column nowrap;
}
.logo {
box-sizing: border-box;
padding: 20px;
height: 100px;
color: @color-theme-font;
flex: none;
}
.menu {
flex: auto;
padding: 10px;
dl {
-webkit-app-region: no-drag;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
dt {
font-size: 11px;
transition: @transition-theme;
transition-property: color;
color: @color-theme-font-label;
}
dd a {
display: block;
box-sizing: border-box;
text-decoration: none;
position: relative;
padding: 10px;
margin: 5px 0;
// border-left: 5px solid transparent;
transition: @transition-theme;
transition-property: color;
color: @color-theme-font;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
border-radius: 4px;
&.active {
// border-left-color: @color-theme-active;
background-color: @color-theme-active;
}
&:hover:not(.active) {
background-color: @color-theme-hover;
}
}
}
}
each(@themes, {
:global(#container.@{value}) {
.aside {
background-color: ~'@{color-@{value}-theme}';
}
.logo {
color: ~'@{color-@{value}-theme-font}';
}
.menu {
dl {
dt {
color: ~'@{color-@{value}-theme-font-label}';
}
dd a {
color: ~'@{color-@{value}-theme-font}';
&.active {
background-color: ~'@{color-@{value}-theme-active}';
}
&:hover:not(.active) {
background-color: ~'@{color-@{value}-theme-hover}';
}
}
}
}
}
})
</style>
File diff suppressed because one or more lines are too long
+606
View File
@@ -0,0 +1,606 @@
<template lang="pug">
div(:class="$style.player")
div(:class="$style.left")
img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError")
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' height='100%' viewBox='0 0 60 60' space='preserve')
use(:xlink:href='`#${$style.iconPic}`')
div(:class="$style.right")
div(:class="$style.column1")
div(:class="$style.container")
div(:class="$style.title") {{title}}
div(:class="$style.playBtn" @click='handleNext' title="下一首")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :title="isPlay ? '暂停' : '播放'" @click='togglePlay')
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play')
div(:class="$style.column2" @click='setProgess' ref="dom_progress")
div(:class="$style.progress")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ width: progress + '%' }")
div(:class="[$style.progressBar, $style.progressBar2]" :style="{ width: (progress * 100 || 0) + '%' }")
div(:class="$style.column3")
span {{nowPlayTimeStr}}
span(:class="$style.statusText") {{status}}
span {{maxPlayTimeStr}}
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;")
defs
g(:id="$style.iconPic")
path(d='M29,0C12.984,0,0,12.984,0,29c0,16.016,12.984,29,29,29s29-12.984,29-29C58,12.984,45.016,0,29,0zM29,36.08c-3.91,0-7.08-3.17-7.08-7.08c0-3.91,3.17-7.08,7.08-7.08s7.08,3.17,7.08,7.08C36.08,32.91,32.91,36.08,29,36.08z')
path(:class="$style.c1" d='M6.487,22.932c-0.077,0-0.156-0.009-0.234-0.027c-0.537-0.13-0.868-0.67-0.739-1.206c0.946-3.935,2.955-7.522,5.809-10.376s6.441-4.862,10.376-5.809c0.536-0.127,1.077,0.202,1.206,0.739c0.129,0.536-0.202,1.076-0.739,1.206c-3.575,0.859-6.836,2.685-9.429,5.277s-4.418,5.854-5.277,9.429C7.349,22.624,6.938,22.932,6.487,22.932z')
path(:class="$style.c1" d='M36.066,52.514c-0.451,0-0.861-0.308-0.972-0.767c-0.129-0.536,0.202-1.076,0.739-1.206c3.576-0.859,6.837-2.685,9.43-5.277s4.418-5.854,5.277-9.429c0.129-0.538,0.668-0.868,1.206-0.739c0.537,0.13,0.868,0.67,0.739,1.206c-0.946,3.935-2.955,7.522-5.809,10.376s-6.441,4.862-10.377,5.809C36.223,52.505,36.144,52.514,36.066,52.514z')
path(:class="$style.c1" d='M11.313,24.226c-0.075,0-0.151-0.008-0.228-0.026c-0.538-0.125-0.873-0.663-0.747-1.2c0.72-3.09,2.282-5.904,4.52-8.141c2.236-2.237,5.051-3.8,8.141-4.52c0.535-0.131,1.075,0.209,1.2,0.747c0.126,0.537-0.209,1.075-0.747,1.2c-2.725,0.635-5.207,2.014-7.18,3.986s-3.352,4.455-3.986,7.18C12.179,23.914,11.768,24.226,11.313,24.226z')
path(:class="$style.c1" d='M34.773,47.688c-0.454,0-0.865-0.312-0.973-0.773c-0.126-0.537,0.209-1.075,0.747-1.2c2.725-0.635,5.207-2.014,7.18-3.986s3.352-4.455,3.986-7.18c0.125-0.538,0.662-0.88,1.2-0.747c0.538,0.125,0.873,0.663,0.747,1.2c-0.72,3.09-2.282,5.904-4.52,8.141c-2.236,2.237-5.051,3.8-8.141,4.52C34.925,47.68,34.849,47.688,34.773,47.688z')
path(:class="$style.c1" d='M16.14,25.519c-0.071,0-0.143-0.008-0.215-0.023c-0.539-0.118-0.881-0.651-0.763-1.19c0.997-4.557,4.586-8.146,9.143-9.143c0.537-0.116,1.071,0.222,1.19,0.763c0.118,0.539-0.224,1.072-0.763,1.19c-3.796,0.831-6.786,3.821-7.617,7.617C17.013,25.2,16.6,25.519,16.14,25.519z')
path(:class="$style.c1" d='M33.48,42.861c-0.46,0-0.873-0.318-0.976-0.786c-0.118-0.539,0.224-1.072,0.763-1.19c3.796-0.831,6.786-3.821,7.617-7.617c0.118-0.541,0.65-0.881,1.19-0.763c0.539,0.118,0.881,0.651,0.763,1.19c-0.997,4.557-4.586,8.146-9.143,9.143C33.623,42.854,33.552,42.861,33.48,42.861z')
path(:class="$style.c2" d='M29,38.08c-5.007,0-9.08-4.073-9.08-9.08s4.073-9.08,9.08-9.08s9.08,4.073,9.08,9.08S34.007,38.08,29,38.08z M29,23.92c-2.801,0-5.08,2.279-5.08,5.08s2.279,5.08,5.08,5.08s5.08-2.279,5.08-5.08S31.801,23.92,29,23.92z')
</template>
<script>
import Lyric from 'lrc-file-parser'
import { rendererSend } from '../../../common/icp'
import { formatPlayTime2, getRandom, checkPath } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex'
export default {
data() {
return {
show: true,
audio: null,
nowPlayTime: 0,
maxPlayTime: 0,
isPlay: false,
status: '^-^',
musicInfo: {
songmid: null,
img: null,
lrc: null,
url: null,
name: '^',
singer: '^',
},
pregessWidth: 0,
lyric: {
lrc: null,
text: '',
line: 0,
},
}
},
computed: {
...mapGetters(['setting']),
...mapGetters('player', ['list', 'playIndex', 'changePlay', 'listId']),
// pic() {
// return this.musicInfo.img ? this.musicInfo.img : ''
// },
title() {
return this.musicInfo.name
? `${this.musicInfo.name} - ${this.musicInfo.singer}`
: ''
},
nowPlayTimeStr() {
return this.nowPlayTime ? formatPlayTime2(this.nowPlayTime) : '00:00'
},
maxPlayTimeStr() {
return this.maxPlayTime ? formatPlayTime2(this.maxPlayTime) : '00:00'
},
progress() {
// return 50
return this.nowPlayTime / this.maxPlayTime || 0
},
},
mounted() {
this.setProgessWidth()
this.init()
},
watch: {
changePlay(n) {
if (!n) return
this.resetChangePlay()
if (this.playIndex < 0) return
this.stopPlay()
this.play()
},
'setting.player.togglePlayMethod'(n) {
this.audio.loop = n === 'singleLoop'
},
list(n, o) {
if (n === o) {
let index = this.listId == 'download'
? n.findIndex(s => s.musicInfo.songmid === this.musicInfo.songmid)
: n.findIndex(s => s.songmid === this.musicInfo.songmid)
if (index < 0) {
this.handleRemoveMusic()
if (n.length) this.handleNext()
} else {
this.fixPlayIndex(index)
}
// console.log(this.playIndex)
}
},
progress(n, o) {
if (n.toFixed(2) === o.toFixed(2)) return
this.sendProgressEvent(n, 'normal')
},
},
methods: {
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
...mapMutations('player', [
'setPlayIndex',
'fixPlayIndex',
'resetChangePlay',
]),
init() {
this.audio = document.createElement('audio')
this.audio.controls = false
this.audio.autoplay = true
this.audio.loop = this.setting.player.togglePlayMethod === 'singleLoop'
this.audio.addEventListener('playing', () => {
console.log('开始播放')
this.status = '播放中...'
this.startPlay()
})
this.audio.addEventListener('pause', () => {
console.log('暂停播放')
this.lyric.lrc.pause()
this.stopPlay()
this.status = '暂停播放'
})
this.audio.addEventListener('ended', () => {
console.log('播放完毕')
this.stopPlay()
this.status = '播放完毕'
this.handleNext()
})
this.audio.addEventListener('error', () => {
if (!this.musicInfo.songmid) return
console.log('出错')
this.stopPlay()
this.status = '加载出错'
this.sendProgressEvent(this.progress, 'error')
// let urls = this.player_info.targetSong.urls
// if (urls && urls.some((url, index) => {
// if (this.musicInfo.musicUrl.includes(url)) {
// let newUrl = urls[index + 1]
// if (!newUrl) return false
// this.musicInfo.musicUrl = this.musicInfo.musicUrl.replace(url, newUrl)
// // this.musicInfo.musicUrl = newUrl ? this.musicInfo.musicUrl.replace(url, newUrl) : this.setFormTag(this.musicInfo.musicUrl.replace(url, urls[0]))
// return true
// }
// })) {
// this.audio.src = this.musicInfo.musicUrl
// // console.log(this.musicInfo.musicUrl)
// } else {
// this.handleNext()
// }
this.handleNext()
})
this.audio.addEventListener('loadeddata', () => {
this.maxPlayTime = this.audio.duration
this.status = '音乐加载中...'
})
// this.audio.addEventListener('loadstart', () => {
// this.status = '开始加载音乐信息...'
// })
this.audio.addEventListener('canplay', () => {
console.log('加载完成开始播放')
// if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
this.status = '音乐加载中...'
})
// this.audio.addEventListener('canplaythrough', () => {
// console.log('音乐加载完毕')
// // if (this.musicInfo.lyric.orgLrc) this.musicInfo.lyric.lrc.play(this.audio.currentTime * 1000)
// this.status = '播放中...'
// })
// this.audio.addEventListener('emptied', () => {
// console.log('媒介资源元素突然为空,网络错误 or 切换歌曲?')
// this.status = '媒介资源元素突然为空,网络错误?'
// })
this.audio.addEventListener('timeupdate', () => {
this.nowPlayTime = this.audio.currentTime
})
this.audio.addEventListener('waiting', () => {
// this.musicInfo.lyric.lrc.pause()
this.stopPlay()
this.status = '缓冲中...'
})
this.lyric.lrc = new Lyric({
onPlay: (line, text) => {
this.lyric.text = text
this.lyric.line = line
this.status = text
// console.log(line, text)
},
offset: 150,
})
},
play() {
console.log('play', this.playIndex)
let targetSong = this.list[this.playIndex]
if (this.listId == 'download') {
if (!checkPath(targetSong.filePath) || !targetSong.isComplate || /\.ape$/.test(targetSong.filePath)) {
return this.list.length == 1 ? null : this.handleNext()
}
this.musicInfo.songmid = targetSong.musicInfo.songmid
this.musicInfo.singer = targetSong.musicInfo.singer
this.musicInfo.name = targetSong.musicInfo.name
this.audio.src = targetSong.filePath
// console.log(targetSong.filePath)
this.setImg(targetSong.musicInfo)
this.setLrc(targetSong.musicInfo)
} else {
this.musicInfo.songmid = targetSong.songmid
this.musicInfo.singer = targetSong.singer
this.musicInfo.name = targetSong.name
this.setUrl(targetSong)
this.setImg(targetSong)
this.setLrc(targetSong)
}
},
handleNext() {
// if (this.list.listName === null) return
if (!this.list.length) return
let index
switch (this.setting.player.togglePlayMethod) {
case 'listLoop':
index = this.hanldeListLoop()
break
case 'random':
index = this.hanldeListRandom()
break
case 'list':
index = this.hanldeListNext()
break
default:
return
}
if (index < 0) return
this.setPlayIndex(index)
},
hanldeListLoop() {
return this.playIndex === this.list.length - 1 ? 0 : this.playIndex + 1
},
hanldeListNext() {
return this.playIndex === this.list.length - 1 ? -1 : this.playIndex + 1
},
hanldeListRandom() {
return getRandom(0, this.list.length)
},
startPlay() {
this.isPlay = true
if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
this.setAppName()
this.sendProgressEvent(this.progress, 'normal')
},
stopPlay() {
this.isPlay = false
this.lyric.lrc.pause()
this.sendProgressEvent(this.progress, 'paused')
this.clearAppName()
},
setProgess(e) {
this.audio.currentTime =
(e.offsetX / this.pregessWidth) * this.maxPlayTime
if (!this.isPlay) this.audio.play()
},
setProgessWidth() {
this.pregessWidth = parseInt(
window.getComputedStyle(this.$refs.dom_progress, null).width
)
},
togglePlay() {
if (!this.audio.src) return
this.isPlay ? this.audio.pause() : this.audio.play()
},
imgError(e) {
// e.target.src = 'https://y.gtimg.cn/music/photo_new/T002R500x500M000002BMEC42fM8S3.jpg'
this.musicInfo.img = null
},
getPlayType(highQuality, songInfo) {
let type = songInfo._types['192k'] ? '192k' : '128k'
if (highQuality && songInfo._types['320k']) type = '320k'
return type
},
setUrl(targetSong) {
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
this.musicInfo.url = targetSong.typeUrl[type]
let urlP = this.musicInfo.url
? Promise.resolve()
: this.getUrl({ musicInfo: targetSong, type }).then(() => {
this.musicInfo.url = targetSong.typeUrl[type]
})
urlP.then(() => {
this.audio.src = this.musicInfo.url
})
},
setImg(targetSong) {
this.musicInfo.img = targetSong.img
if (!this.musicInfo.img) {
this.getPic(targetSong).then(() => {
this.musicInfo.img = targetSong.img
})
}
},
setLrc(targetSong) {
this.musicInfo.lrc = targetSong.lyric
let lrcP = this.musicInfo.lrc
? Promise.resolve()
: this.getLrc(targetSong).then(() => {
this.musicInfo.lrc = targetSong.lyric
})
lrcP
.then(() => {
this.lyric.lrc.setLyric(this.musicInfo.lrc)
if (this.isPlay) this.lyric.lrc.play(this.audio.currentTime * 1000)
})
.catch(err => {
this.status = err.message
})
},
handleRemoveMusic() {
this.stopPlay()
this.audio.src = null
this.audio.removeAttribute('src')
this.status = '^-^'
this.musicInfo.img = null
this.musicInfo.name = this.musicInfo.singer = '^'
this.musicInfo.songmid = null
this.musicInfo.lrc = null
this.musicInfo.url = null
this.nowPlayTime = 0
this.maxPlayTime = 0
this.fixPlayIndex(this.playIndex - 1)
},
sendProgressEvent(status, mode) {
// console.log(status)
rendererSend('progress', {
status: status < 0.01 ? 0.01 : status,
mode: mode || 'normal',
})
},
setAppName() {
// rendererSend('appName', {
// name: `${this.musicInfo.name} - ${this.musicInfo.singer}`,
// })
},
clearAppName() {
// rendererSend('appName')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.player {
height: @height-player;
// background-color: rgb(245, 245, 245);
transition: @transition-theme;
transition-property: background-color, border-color;
background-color: @color-theme_2;
border-top: 2px solid @color-theme;
box-sizing: border-box;
display: flex;
z-index: 1;
* {
box-sizing: border-box;
}
}
.left {
width: @height-player;
color: @color-theme;
transition: @transition-theme;
transition-property: color;
flex: none;
svg {
fill: currentColor;
}
img {
max-width: 100%;
max-height: 100%;
transition: @transition-theme;
transition-property: border-color;
border: 2px solid @color-theme_2;
}
}
.right {
flex: auto;
// margin-left: 10px;
padding: 5px 10px;
display: flex;
flex-flow: column nowrap;
}
.column1 {
flex: auto;
position: relative;
font-size: 16px;
.container {
position: absolute;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
}
.title {
flex: 1 1 0;
width: 0;
padding-right: 5px;
font-size: 14px;
line-height: 18px;
.mixin-ellipsis-1;
}
.play-btn {
+ .play-btn {
margin-left: 10px;
}
flex: none;
height: 95%;
width: 20px;
align-self: center;
// margin-top: -2px;
transition: @transition-theme;
transition-property: color;
color: @color-theme;
transition: opacity 0.2s ease;
opacity: 1;
cursor: pointer;
svg {
fill: currentColor;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:active {
opacity: 0.7;
}
}
.column2 {
flex: none;
padding: 3px 0;
cursor: pointer;
}
.progress {
width: 100%;
height: 0.15rem;
border-radius: 0.2rem;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
// background-color: #f5f5f5;
position: relative;
border-radius: @radius-progress-border;
}
.progress-bar {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 100%;
border-radius: @radius-progress-border;
}
.progress-bar1 {
transition-duration: 0.6s;
background-color: @color-player-progress-bar1;
}
.progress-bar2 {
transition-duration: 0.2s;
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
.column3 {
transition: @transition-theme;
transition-property: color;
color: @color-theme_2-font;
flex: none;
font-size: 12px;
display: flex;
padding-top: 2px;
// justify-content: space-between;
align-items: center;
}
.status-text {
font-size: 0.98em;
transition: @transition-theme;
transition-property: color;
color: @color-player-status-text;
.mixin-ellipsis-1;
padding: 0 5px;
flex: 1 1 0;
text-align: center;
line-height: 1.2;
width: 0;
}
#icon-pic {
color: @color-theme;
.c1 {
transition: @transition-theme;
transition-property: fill;
fill: @color-player-pic-c1;
}
.c2 {
transition: @transition-theme;
transition-property: fill;
fill: @color-player-pic-c2;
}
}
each(@themes, {
:global(#container.@{value}) {
.player {
background-color: ~'@{color-@{value}-theme_2}';
border-top-color: ~'@{color-@{value}-theme}';
}
.left {
color: ~'@{color-@{value}-theme}';
img {
border-color: ~'@{color-@{value}-theme_2}';
}
}
.play-btn {
color: ~'@{color-@{value}-theme}';
svg {
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3));
}
}
.progress {
background-color: ~'@{color-@{value}-player-progress}';
}
.progress-bar1 {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.column3 {
color: ~'@{color-@{value}-theme_2-font}';
}
.status-text {
color: ~'@{color-@{value}-player-status-text}';
}
#icon-pic {
color: ~'@{color-@{value}-theme}';
.c1 {
fill: ~'@{color-@{value}-player-pic-c1}';
}
.c2 {
fill: ~'@{color-@{value}-player-pic-c2}';
}
}
}
})
</style>
+128
View File
@@ -0,0 +1,128 @@
<template lang="pug">
div(:class="$style.toolbar")
//- img(v-if="icon")
//- h1 {{title}}
material-search-input(:class="$style.input")
div(:class="$style.control")
button(type="button" :class="$style.min" title="最小化" @click="min")
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" title="关闭" @click="close")
</template>
<script>
import { rendererSend } from 'common/icp'
// import { mapGetters } from 'vuex'
export default {
// props: {
// color: {
// type: String,
// default: color,
// },
// icon: {
// type: Boolean,
// default: false,
// },
// },
computed: {
// ...mapGetters(['theme']),
},
methods: {
min() {
rendererSend('min')
},
max() {
rendererSend('max')
},
close() {
rendererSend('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.toolbar {
display: flex;
height: @height-toolbar;
justify-content: flex-end;
background-color: @color-theme_2;
align-items: center;
padding-left: 15px;
-webkit-app-region: drag;
z-index: 1;
position: relative;
}
.input {
-webkit-app-region: no-drag;
}
each(@themes, {
:global(#container.@{value}) {
.toolbar {
background-color: ~'@{color-@{value}-theme_2}';
}
}
})
// img {
// flex: none;
// height: 100%;
// }
// h1 {
// text-align: center;
// padding: 8px;
// flex: auto;
// -webkit-app-region: drag;
// -webkit-user-select: none;
// }
.control {
display: flex;
height: 100%;
-webkit-app-region: no-drag;
button {
width: @height-toolbar;
background: none;
border: none;
display: flex;
justify-content: center;
align-items: center;
outline: none;
padding: 0;
cursor: pointer;
&:after {
content: ' ';
display: block;
border-radius: 50%;
width: 13px;
height: 13px;
transition: background-color 0.2s ease-in-out;
}
&.min:after {
background-color: @color-minBtn;
}
&.max:after {
background-color: @color-maxBtn;
}
&.close:after {
background-color: @color-closeBtn;
}
&.min:hover:after {
background-color: lighten(@color-minBtn, 10%);
}
&.max:hover:after {
background-color: lighten(@color-maxBtn, 10%);
}
&.close:hover:after {
background-color: lighten(@color-closeBtn, 10%);
}
}
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<template lang="pug">
div(:class="$style.view")
transition(enter-active-class="animated-fast fadeIn"
leave-active-class="animated-fast fadeOut")
router-view
//- core-title-bar
//- router-view
</template>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.view {
position: relative;
background-color: @color-theme_2;
> * {
position: absolute;
width: 100%;
}
// background: #fff;
// overflow: hidden;
}
each(@themes, {
:global(#container.@{value}) {
.view {
background-color: ~'@{color-@{value}-theme_2}';
}
}
})
</style>
+17
View File
@@ -0,0 +1,17 @@
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
'./', true, /\.vue$/
)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
)
Vue.component(componentName, componentConfig.default || componentConfig)
})
+59
View File
@@ -0,0 +1,59 @@
<template lang="pug">
button(:class="[$style.btn, min ? $style.min : '']" @click="$emit('click', $event)")
slot
</template>
<script>
export default {
props: {
min: {
type: Boolean,
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.btn {
display: inline-block;
border: none;
border-radius: 3px;
cursor: pointer;
padding: 8px 15px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
background-color: @color-btn-background;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.min {
padding: 3px 8px;
font-size: 12px;
}
each(@themes, {
:global(#container.@{value}) {
.btn {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
})
</style>
@@ -0,0 +1,160 @@
<template lang="pug">
div(:class="$style.checkbox")
input(:type="need ? 'radio' : 'checkbox'" :id="id" :value="target" :name="name" @change="change" v-model="val")
label(:for="id" :class="$style.content")
div
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check')
span
slot
</template>
<script>
export default {
props: {
target: {},
value: {},
id: {
type: String,
required: true,
},
name: {
type: String,
},
need: {
type: Boolean,
default: false,
},
},
data() {
return {
val: false,
}
},
watch: {
value(n) {
if (this.target && n !== this.target) {
this.val = this.need
? n === this.target
? this.target
: false
: n === this.target
} else if (n !== this.val) {
this.val = n
}
},
},
mounted() {
if (this.target) {
this.val = this.need
? this.value === this.target
? this.target
: false
: this.value === this.target
} else {
this.val = this.value
}
},
methods: {
change() {
let val = this.target == null ? this.val : this.val ? this.target : null
this.$emit('input', val)
this.$emit('change', val)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.checkbox {
display: inline-block;
// font-size: 56px;
> input {
display: none;
&:checked {
+ .content {
> div {
&:after {
border-color: @color-theme;
}
svg {
transform: scale(1);
opacity: 1;
}
}
}
}
}
}
.content {
display: flex;
justify-content: center;
> div {
flex: none;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
cursor: pointer;
display: flex;
color: @color-theme;
// border: 1px solid #ccc;
&:after {
position: absolute;
content: ' ';
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid #ccc;
transition: border-color 0.2s ease;
border-radius: 15%;
}
svg {
transition: 0.2s ease;
transition-property: transform, opacity;
transform: scale(0.5);
opacity: 0;
}
}
> span {
flex: auto;
line-height: 1;
margin-left: 5px;
cursor: pointer;
}
}
each(@themes, {
:global(#container.@{value}) {
.checkbox {
> input {
&:checked {
+ .content {
> div {
&:after {
border-color: ~'@{color-@{value}-theme}';
}
}
}
}
}
}
.content {
> div {
color: ~'@{color-@{value}-theme}';
// border: 1px solid #ccc;
&:after {
border-color: #ccc;
}
}
}
}
})
</style>
@@ -0,0 +1,94 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| {{ info.name }}
br
| {{ info.singer }}
material-btn(:class="$style.btn" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
},
bgClose: {
type: Boolean,
default: true,
},
},
computed: {
info() {
return this.musicInfo || {}
},
},
methods: {
handleClick(type) {
this.$emit('select', type)
},
handleClose() {
this.$emit('close')
},
getTypeName(type) {
switch (type) {
case 'flac':
case 'ape':
return '无损音质'
case '320k':
return '高品音质'
case '192k':
case '128k':
return '普通音质'
}
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 300px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
margin-bottom: 15px;
}
}
.btn {
display: block;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>
@@ -0,0 +1,106 @@
<template lang="pug">
div(:class="$style.btns")
button(type="button" v-if="playBtn" title="播放" @click.stop="handleClick('play')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve')
use(xlink:href='#icon-testPlay')
button(type="button" v-if="downloadBtn" title="下载" @click.stop="handleClick('download')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve')
use(xlink:href='#icon-download')
button(type="button" title="添加" v-if="userInfo" @click.stop="handleClick('add')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
button(type="button" v-if="removeBtn" title="移除" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
button(type="button" v-if="searchBtn" title="搜索" @click.stop="handleClick('search')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
use(xlink:href='#icon-search')
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: {
index: {
type: Number,
required: true,
},
removeBtn: {
type: Boolean,
default: true,
},
downloadBtn: {
type: Boolean,
default: true,
},
playBtn: {
type: Boolean,
default: true,
},
searchBtn: {
type: Boolean,
default: false,
},
},
computed: {
...mapGetters(['userInfo']),
},
methods: {
handleClick(action) {
this.$emit('btn-click', { action, index: this.index })
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.btns {
button {
background-color: transparent;
border: none;
border-radius: 3px;
margin-right: 5px;
cursor: pointer;
padding: 4px 7px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
line-height: 0;
&:last-child {
margin-right: 0;
}
svg {
height: 1.2em;
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
}
each(@themes, {
:global(#container.@{value}) {
.btns {
button {
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
}
})
</style>
+199
View File
@@ -0,0 +1,199 @@
<template lang="pug">
transition(enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut")
div(:class="$style.modal" v-show="show" @click="bgClose && close()")
transition(:enter-active-class="inClass"
:leave-active-class="outClass")
div(:class="$style.content" v-show="show" @click.stop)
header(:class="$style.header")
button(type="button" @click="close")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
slot
</template>
<script>
import { getRandom } from '../../utils'
import { mapGetters } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
bgClose: {
type: Boolean,
default: false,
},
},
data() {
return {
animateIn: [
'flipInX',
'flipInY',
'fadeIn',
'bounceIn',
'lightSpeedIn',
'rotateInDownLeft',
'rotateInDownRight',
'rotateInUpLeft',
'rotateInUpRight',
'rollIn',
'zoomIn',
'zoomInDown',
'zoomInLeft',
'zoomInRight',
'zoomInUp',
'slideInDown',
'slideInLeft',
'slideInRight',
'slideInUp',
],
animateOut: [
'flipOutX',
'flipOutY',
'fadeOut',
'bounceOut',
'lightSpeedOut',
'rotateOutDownLeft',
'rotateOutDownRight',
'rotateOutUpLeft',
'rotateOutUpRight',
'rollOut',
'zoomOut',
'zoomOutDown',
'zoomOutLeft',
'zoomOutRight',
'zoomOutUp',
'slideOutDown',
'slideOutLeft',
'slideOutRight',
'slideOutUp',
'hinge',
],
inClass: 'animated flipInX',
outClass: 'animated flipOutX',
unwatchFn: null,
}
},
computed: {
...mapGetters(['setting']),
},
watch: {
'setting.randomAnimate': {
handler(n) {
if (n) {
this.unwatchFn = this.$watch('show', function(n) {
if (n) {
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
}
})
} else {
this.unwatchFn && this.unwatchFn()
}
},
immediate: true,
},
},
mounted() {
},
methods: {
close() {
this.$emit('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.modal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .3);
display: grid;
align-items: center;
justify-items: center;
z-index: 99;
}
.content {
border-radius: 5px;
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
overflow: hidden;
max-height: 70%;
max-width: 70%;
position: relative;
display: flex;
flex-flow: column nowrap;
> * {
background-color: @color-theme_2;
}
}
.header {
flex: none;
background-color: @color-theme;
display: flex;
justify-content: flex-end;
button {
border: none;
cursor: pointer;
padding: 4px 7px;
background-color: transparent;
color: @color-theme-font-label;
outline: none;
transition: background-color 0.2s ease;
line-height: 0;
svg {
height: .7em;
}
&:hover {
background-color: @color-theme-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
}
each(@themes, {
:global(#container.@{value}) {
.modal {
background-color: rgba(0, 0, 0, .3);
}
.content {
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
> * {
background-color: ~'@{color-@{value}-theme_2}';
}
}
.header {
background-color: ~'@{color-@{value}-theme}';
button {
color: ~'@{color-@{value}-theme-font-label}';
&:hover {
background-color: ~'@{color-@{value}-theme-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
}
})
</style>
@@ -0,0 +1,227 @@
<template lang="pug">
div(:class="$style.pagination" v-if="allPage > 1")
ul
li(v-if="page===1" :class="$style.disabled")
span
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-left')
li(v-else)
button(type="button" @click="handleClick(page - 1)" title="上一页")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-left')
li(v-if="allPage > btnLength && page > pageEvg+1" :class="$style.first")
button(type="button" @click="handleClick(1)" title="第 1 页")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-first')
li(v-for="(p, index) in pages" :key="index" :class="{[$style.active] : p == page}")
span(v-if="p === page" v-text="page")
button(v-else type="button" @click="handleClick(p)" v-text="p" :title="`第 ${p} 页`")
li(v-if="allPage > btnLength && allPage - page > pageEvg" :class="$style.last")
button(type="button" @click="handleClick(allPage)" :title="`第 ${allPage} 页`")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-last')
li(v-if="page===allPage" :class="$style.disabled")
span
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-right')
li(v-else)
button(type="button" @click="handleClick(page + 1)" title="下一页")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-right')
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: {
count: {
type: Number,
default: 0,
},
limit: {
type: Number,
default: 10,
},
page: {
type: Number,
default: 1,
},
btnLength: {
type: Number,
default: 7,
},
},
data() {
return {
pageArr: [],
}
},
computed: {
...mapGetters(['userInfo']),
allPage() {
return Math.ceil(this.count / this.limit) || 1
},
pageEvg() {
return Math.floor(this.btnLength / 2)
},
pages() {
if (this.allPage <= this.btnLength) return this.pageArr
let start =
this.page - this.pageEvg > 1
? this.allPage - this.page < this.pageEvg + 1
? this.allPage - (this.btnLength - 1)
: this.page - this.pageEvg
: 1
let end =
this.page + this.pageEvg <= this.btnLength
? this.btnLength
: this.page + this.pageEvg <= this.allPage
? this.page + this.pageEvg
: this.allPage
// console.log(start-1);
// console.log(end);
// console.log(this.pageArr.slice(start-1, end-1));
return this.pageArr.slice(start - 1, end)
},
},
watch: {
allPage() {
this.initPageArr()
},
},
methods: {
initPageArr() {
this.pageArr = []
for (let i = 1; i <= this.allPage; i++) this.pageArr.push(i)
},
handleClick(page) {
this.$emit('btn-click', page)
},
},
mounted() {
this.initPageArr()
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.pagination {
display: inline-block;
background-color: @color-pagination-background;
// border-top-left-radius: 8px;
border-radius: 4px;
ul {
display: flex;
flex-flow: row nowrap;
// border: .0625rem solid @theme_color2;
// border-radius: .3125rem;
li {
// margin-right: @padding;
color: @color-theme;
// border: .0625rem solid @theme_line;
// border-radius: .3125rem;
transition: 0.4s ease;
transition-property: all;
line-height: 1;
display: flex;
// border-right: none;
svg {
height: 1em;
}
span,
button {
display: block;
padding: 7px 12px;
line-height: 1;
color: @color-theme;
font-size: 13px;
}
&.active {
span {
background-color: @color-pagination-select;
}
}
button {
background-color: transparent;
border: none;
cursor: pointer;
outline: none;
transition: background-color .3s ease;
&:hover {
background-color: @color-pagination-hover;
}
&:active {
background-color: @color-pagination-active;
}
}
&.disabled {
span {
opacity: .3;
}
}
&:first-child {
span, button {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
// border-right: .0625rem solid @theme_line;
}
&:last-child {
span, button {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
// border-right: .0625rem solid @theme_line;
}
&:first-child, &:last-child, &.first, &.last {
span,
button {
line-height: 0;
}
}
}
}
}
each(@themes, {
:global(#container.@{value}) {
.pagination {
background-color: ~'@{color-@{value}-pagination-background}';
ul {
li {
color: ~'@{color-@{value}-theme}';
span,
button {
color: ~'@{color-@{value}-theme}';
}
&.active {
span {
background-color: ~'@{color-@{value}-pagination-select}';
}
}
button {
&:hover {
background-color: ~'@{color-@{value}-pagination-hover}';
}
&:active {
background-color: ~'@{color-@{value}-pagination-active}';
}
}
}
}
}
}
})
</style>
@@ -0,0 +1,232 @@
<template lang="pug">
div(:class="[$style.search, focus ? $style.active : '']")
div(:class="$style.form")
input(placeholder="Search for something..." v-model.trim="text"
@focus="handleFocus" @blur="handleBlur" @input="handleInput"
@keyup.enter="handleSearch")
button(type="button" @click="handleSearch")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
use(xlink:href='#icon-search')
//- transition(name="custom-classes-transition"
//- enter-active-class="animated flipInX"
//- leave-active-class="animated flipOutX")
div(:class="$style.list" :style="listStyle")
ul(ref="dom_list")
li(v-for="(item, index) in list" :key="item" @click="handleTemplistClick(index)")
span {{item}}
</template>
<script>
import { mapGetters } from 'vuex'
import music from '../../utils/music'
export default {
data() {
return {
isShow: false,
text: '',
list: [],
index: null,
focus: false,
listStyle: {
height: 0,
},
}
},
computed: {
...mapGetters(['source']),
...mapGetters('search', ['info']),
},
watch: {
list(n) {
this.$nextTick(() => {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
})
},
'info.text'(n) {
if (n !== this.text) this.text = n
},
},
methods: {
handleTemplistClick(index) {
this.text = this.list[index]
this.handleSearch()
},
handleFocus() {
this.focus = true
if (this.text) this.handleInput()
this.showList()
},
handleBlur() {
setTimeout(() => {
this.focus = false
this.hideList()
}, 200)
},
handleSearch() {
this.hideList()
this.$router.push({
path: 'search',
query: {
text: this.text,
},
})
},
handleInput() {
if (this.text === '') {
this.list.splice(0, this.list.length)
music[this.source.id].tempSearch.cancelTempSearch()
return
}
if (!this.isShow) this.showList()
music[this.source.id].tempSearch.search(this.text).then(list => {
this.list = list
}).catch(() => {})
},
showList() {
this.isShow = true
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
},
hideList() {
this.isShow = false
this.listStyle.height = 0
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.search {
position: absolute;
left: 15px;
top: 13px;
border-radius: 3px;
transition: box-shadow .4s ease, background-color @transition-theme;
display: flex;
flex-flow: column nowrap;
width: 240px;
background-color: @color-search-form-background;
&.active {
box-shadow: 0 1px 5px 0 rgba(0,0,0,.2);
.form {
input {
border-bottom-left-radius: 0;
}
button {
border-bottom-right-radius: 0;
}
}
}
.form {
display: flex;
height: @height-toolbar / 2;
position: relative;
input {
flex: auto;
// border: 1px solid;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
background-color: transparent;
// border-bottom: 2px solid @color-theme;
// border-color: @color-theme;
border: none;
outline: none;
// height: @height-toolbar * .7;
padding: 0 5px;
overflow: hidden;
&::placeholder {
color: @color-btn;
}
}
button {
flex: none;
border: none;
// background-color: @color-search-form-background;
background-color: transparent;
outline: none;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
cursor: pointer;
height: 100%;
padding: 5px 7px;
color: @color-btn;
transition: background-color .2s ease;
&:hover {
background-color: @color-theme-hover;
}
&:active {
background-color: @color-theme-active;
}
}
}
.list {
// background-color: @color-search-form-background;
font-size: 13px;
transition: .3s ease;
height: 0;
transition-property: height;
overflow: hidden;
li {
cursor: pointer;
padding: 8px 5px;
transition: background-color .2s ease;
line-height: 1.3;
span {
.mixin-ellipsis-2;
}
&:hover {
background-color: @color-search-list-hover;
}
&:last-child {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
}
}
}
each(@themes, {
:global(#container.@{value}) {
.search {
background-color: ~'@{color-@{value}-search-form-background}';
&.active {
box-shadow: 0 1px 5px 0 rgba(0,0,0,.2);
}
.form {
input {
&::placeholder {
color: ~'@{color-@{value}-btn}';
}
}
button {
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme-active}';
}
}
}
.list {
li {
&:hover {
background-color: ~'@{color-@{value}-search-list-hover}';
}
}
}
}
}
})
</style>
+170
View File
@@ -0,0 +1,170 @@
<template lang="pug">
div(:class="$style.select")
div(:class="$style.label" ref="dom_btn" @click="handleShow") {{value ? itemName ? list.find(l => l.id == value).name : value : ''}}
ul(:class="$style.list" ref="dom_list" :style="listStyle")
li(v-for="item in list" @click="handleClick(itemKey ? item[itemKey] : item)") {{itemName ? item[itemName] : item}}
</template>
<script>
// import { isChildren } from '../../utils'
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
value: {
type: [String, Number],
},
itemName: {
type: String,
},
itemKey: {
type: String,
},
},
data() {
return {
show: false,
listStyle: {
height: 0,
opacity: 0,
},
}
},
watch: {
show(n) {
this.$nextTick(() => {
if (n) {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
this.listStyle.opacity = 1
} else {
this.listStyle.height = 0
this.listStyle.opacity = 0
}
})
},
},
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeDestroy() {
document.removeEventListener('click', this.handleHide)
},
methods: {
handleHide(e) {
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && e.target == this.$refs.dom_btn) return
setTimeout(() => {
this.show = false
}, 50)
},
handleClick(item) {
if (item === this.value) return
this.$emit('input', item)
this.$emit('change', item)
},
handleShow() {
this.show = !this.show
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.select {
font-size: 12px;
position: relative;
}
.label {
padding: 8px 15px;
// background-color: @color-btn-background;
transition: background-color @transition-theme;
border-top: 2px solid @color-tab-border-bottom;
border-left: 2px solid @color-tab-border-bottom;
box-sizing: border-box;
text-align: center;
border-top-left-radius: 3px;
color: @color-btn;
cursor: pointer;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.list {
position: absolute;
top: 100%;
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-left: 2px solid @color-tab-border-bottom;
border-bottom-left-radius: 3px;
background-color: @color-theme_2;
overflow: hidden;
opacity: 0;
transition: .25s ease;
transition-property: height, opacity;
z-index: 10;
li {
cursor: pointer;
padding: 8px 15px;
// color: @color-btn;
text-align: center;
outline: none;
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
}
each(@themes, {
:global(#container.@{value}) {
.label {
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
}
})
</style>
+221
View File
@@ -0,0 +1,221 @@
<template lang="pug">
div.scroll(:class="$style.tab")
//- div(:class="$style.content")
ul
li(v-for="item in list" :key="itemKey ? item[itemKey] : item" :class="value === (itemKey ? item[itemKey] : item) ? $style.active : ''")
button(type="button"
@click="handleClick(itemKey ? item[itemKey] : item)") {{ itemName ? item[itemName] : item }}
//- div(:class="$style.control")
div(:class="$style.left")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-left')
div(:class="$style.right")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-right')
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
value: {
type: [String, Number],
},
itemName: {
type: String,
},
itemKey: {
type: String,
},
},
methods: {
handleClick(item) {
if (item === this.value) return
this.$emit('input', item)
this.$emit('change', item)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.tab {
// overflow: hidden;
// display: flex;
// flex-flow: row nowrap;
overflow-x: auto;
ul {
display: flex;
flex: none;
border-bottom: 2px solid @color-tab-border-bottom;
transition: border-bottom-color @transition-theme;
li {
position: relative;
flex: none;
margin-bottom: -2px;
border-top: 2px solid @color-tab-border-top;
border-left: 2px solid @color-tab-btn-background;
border-right: 2px solid @color-tab-btn-background;
// box-sizing: border-box;
transition: border-color @transition-theme;
margin-left: -2px;
&::after {
content: ' ';
display: block;
width: 50%;
height: 2px;
position: absolute;
bottom: 0;
left: 0;
background-color: @color-tab-border-bottom;
transition: width @transition-theme;
}
&::before {
content: ' ';
display: block;
width: 50%;
height: 2px;
position: absolute;
bottom: 0;
right: 0;
background-color: @color-tab-border-bottom;
transition: width @transition-theme;
}
&:first-child {
border-left: none;
margin-left: 0;
button {
border-top-left-radius: 3px;
// border-bottom-left-radius: 3px;
}
}
&:last-child {
border-right: 2px solid @color-tab-border-top;
border-top-right-radius: 3px;
button {
border-top-right-radius: 3px;
// border-bottom-right-radius: 3px;
}
}
button {
display: inline-block;
border: none;
cursor: pointer;
padding: 5px 10px 7px;
font-size: 12px;
// color: @color-btn;
outline: none;
transition: background-color @transition-theme;
background-color: @color-tab-btn-background;
}
&:hover {
// border-left-color: @color-theme_2-hover;
// border-right-color: @color-theme_2-hover;
button {
background-color: @color-theme_2-hover;
}
}
&:active {
// border-left-color: @color-theme_2-active;
// border-right-color: @color-theme_2-active;
button {
background-color: @color-theme_2-active;
}
}
&.active {
border-bottom-color: @color-theme_2;
border-top-color: @color-tab-border-bottom;
border-left-color: @color-tab-border-bottom;
border-right-color: @color-tab-border-bottom;
&::after {
width: 0;
}
&::before {
width: 0;
}
button {
background-color: @color-theme_2;
}
}
}
}
}
// .control {
// flex: none;
// border-bottom: 2px solid @color-tab-border-bottom;
// width: 1em;
// svg {
// width: .85em;
// }
// }
// .left, .right {
// line-height: 0;
// }
// .content {
// flex: auto;
// overflow: hidden;
// }
each(@themes, {
:global(#container.@{value}) {
.tab {
ul {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
li {
border-top-color: ~'@{color-@{value}-tab-border-top}';
border-left-color: ~'@{color-@{value}-tab-btn-background}';
border-right-color: ~'@{color-@{value}-tab-btn-background}';
&::after {
background-color: ~'@{color-@{value}-tab-border-bottom}';
}
&::before {
background-color: ~'@{color-@{value}-tab-border-bottom}';
}
&:last-child {
border-right-color: ~'@{color-@{value}-tab-border-top}';
}
button {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-tab-btn-background}';
}
&:hover {
// border-left-color: ~'@{color-@{value}-theme_2-hover}';
// border-right-color: ~'@{color-@{value}-theme_2-hover}';
button {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
}
&:active {
// border-left-color: ~'@{color-@{value}-theme_2-active}';
// border-right-color: ~'@{color-@{value}-theme_2-active}';
button {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
&.active {
border-bottom-color: ~'@{color-@{value}-theme_2}';
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
button {
background-color: ~'@{color-@{value}-theme_2}';
}
}
}
}
}
}
})
</style>
@@ -0,0 +1,156 @@
<template lang="pug">
material-modal(:show="version.showModal" @close="handleClose")
main(:class="$style.main" v-if="version.newVersion")
h2 🚀程序更新🚀
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 版本变化:
p(v-html="version.newVersion.desc")
div(:class="$style.history" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
p 新版本已下载完毕,
p
| 你可以选择
strong 立即重启更新
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleClick") 立即重启更新
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from '../../../common/icp'
import { checkVersion } from '../../utils'
export default {
computed: {
...mapGetters(['version']),
history() {
if (!this.version.newVersion) return []
let arr = []
let currentVer = this.version.version
this.version.newVersion.history.forEach(ver => {
if (checkVersion(currentVer, ver.version)) arr.push(ver)
})
return arr
},
},
methods: {
...mapMutations(['setVersionVisible']),
handleClose() {
this.setVersionVisible(false)
},
handleClick(event) {
this.handleClose()
event.target.disabled = true
rendererSend('quit-update')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
position: relative;
padding: 15px;
max-width: 500px;
min-width: 300px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
// overflow-y: auto;
* {
box-sizing: border-box;
}
h2 {
flex: 0 0 none;
font-size: 16px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
margin-bottom: 15px;
}
h3 {
font-size: 14px;
line-height: 1.3;
}
}
.info {
flex: 1 1 auto;
font-size: 13px;
line-height: 1.5;
overflow-y: auto;
height: 100%;
}
.current {
> p {
padding-left: 15px;
}
}
.history {
h3 {
padding-top: 15px;
}
.item {
+ .item {
padding-top: 15px;
}
h4 {
font-weight: 700;
}
> p {
padding-left: 10px;
}
}
}
.footer {
flex: 0 0 none;
.desc {
font-size: 12px;
padding: 10px 0;
color: @color-theme;
line-height: 1.2;
strong {
text-decoration: underline;
}
}
}
.btn {
display: block;
width: 100%;
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.footer {
.desc {
color: ~'@{color-@{value}-theme}';
}
}
}
})
</style>
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
development: {
},
production: {
},
ajax: {
timeout: 15000, // ajax请求超时时间
},
}
+27
View File
@@ -0,0 +1,27 @@
import Vue from 'vue'
// import { sync } from 'vuex-router-sync'
// Components
import './components'
// Plugins
import './plugins'
import App from './App'
import router from './route'
import store from './store'
// sync(store, router)
if (!process.env.IS_WEB) {
}
Vue.config.productionTip = false
new Vue({
router,
store,
el: '#root',
render: h => h(App),
})
+1
View File
@@ -0,0 +1 @@
// import './axios'
+56
View File
@@ -0,0 +1,56 @@
import Vue from 'vue'
import Router from 'vue-router'
import paths from './paths'
function route(path, view, name, meta) {
return {
name: name || view,
path,
meta,
component: (resovle) => import(
`../views/${view}.vue`
).then(resovle),
}
}
Vue.use(Router)
const router = new Router({
mode: 'hash',
routes: paths.map(path => route(path.path, path.view, path.name, path.meta)).concat([
{ path: '*', redirect: '/search' },
]),
linkActiveClass: 'active-link',
linkExactActiveClass: 'exact-active-link',
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (savedPosition) {
resolve(savedPosition)
} else {
const position = {}
// new navigation.
// scroll to anchor by returning the selector
if (to.hash) {
position.selector = to.hash
}
// check if any matched route config has meta that requires scrolling to top
if (to.matched.some(m => m.meta.scrollToTop)) {
// cords will be used if no selector is provided,
// or if the selector didn't match any element.
position.x = 0
position.y = 0
}
// if the returned position is falsy or an empty object,
// will retain current scroll position.
resolve(position)
}
}, 500)
})
},
})
export default router
+40
View File
@@ -0,0 +1,40 @@
export default [
// {
// path: '/',
// // redirect: '/app',
// // props: true,
// component: () => import('../views/Dashboard.vue'),
// name: 'Dashboard',
// alias: '/dashboard'
// }
{
path: '/search',
name: 'search',
view: 'Search',
},
{
path: '/leaderboard',
name: 'leaderboard',
view: 'Leaderboard',
},
{
path: '/recommend',
name: 'recommend',
view: 'Recommend',
},
{
path: '/list',
name: 'list',
view: 'List',
},
{
path: '/download',
name: 'download',
view: 'Download',
},
{
path: '/setting',
name: 'setting',
view: 'Setting',
},
]
+14
View File
@@ -0,0 +1,14 @@
// import api from 'api/connom'
import { httpGet } from '../utils/request'
import { author, name } from '../../../package.json'
export default {
getVersionInfo() {
return new Promise((resolve, reject) => {
httpGet(`https://raw.githubusercontent.com/${author}/${name}/master/publish/version.json`, (err, resp, body) => {
if (err) return reject(err)
resolve(body)
})
})
},
}
+34
View File
@@ -0,0 +1,34 @@
import music from '../utils/music'
export default {
theme(state) {
return (state.themes[state.setting.themeId] && state.themes[state.setting.themeId].class) || ''
},
themes(state) {
return {
active: state.setting.themeId,
list: state.themes,
}
},
source(state) {
return music.sources.find(s => s.id === state.setting.sourceId) || music.sources[0]
},
sources(state) {
return {
active: state.setting.sourceId,
list: music.sources,
}
},
userInfo(state) {
return state.userInfo
},
setting(state) {
return state.setting
},
electronStore(state) {
return state.electronStore
},
version(state) {
return state.version
},
}
+46
View File
@@ -0,0 +1,46 @@
import Vue from 'vue'
import Vuex from 'vuex'
// import { createPersistedState, createSharedMutations } from 'vuex-electron'
import defaultState from './state'
import mutations from './mutations'
import modules from './modules'
import getters from './getters'
import actions from './actions'
Vue.use(Vuex)
const isDev = process.env.NODE_ENV === 'development'
const store = new Vuex.Store({
strict: isDev,
state: defaultState,
modules,
mutations,
getters,
actions,
// plugins: [createPersistedState(), createSharedMutations()],
})
if (module.hot) {
module.hot.accept([
'./state',
'./mutations',
'./actions',
'./getters',
], () => {
const newState = require('./state').default
const newMutations = require('./mutations').default
const newActions = require('./actions').default
const newGetters = require('./getters').default
store.hotUpdate({
state: newState,
mutations: newMutations,
getters: newGetters,
actions: newActions,
})
})
}
export default store
+274
View File
@@ -0,0 +1,274 @@
import download from '../../utils/download'
import fs from 'fs'
import path from 'path'
import music from '../../utils/music'
// state
const state = {
list: [],
}
const dls = {}
// getters
const getters = {
list: state => state.list || [],
dls: () => dls || {},
}
const checkPath = path => {
try {
if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true })
} catch (error) {
return error.message
}
return false
}
const getExt = type => {
switch (type) {
case '128k':
case '192k':
case '320k':
return 'mp3'
case 'ape':
return 'ape'
case 'flac':
return 'flac'
}
}
const checkList = (list, musicInfo, type) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && s.type === type)
const refreshUrl = downloadInfo => {
return music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type)
}
// actions
const actions = {
createDownload({ state, rootState }, { musicInfo, type }) {
if (checkList(state.list, musicInfo, type)) return
let ext = getExt(type)
const downloadInfo = {
isComplate: false,
isDownloading: false,
statusText: '任务初始化中',
url: null,
fileName: `${rootState.setting.download.fileName
.replace('歌名', musicInfo.name)
.replace('歌手', musicInfo.singer)}.${ext}`,
progress: {
downloaded: 0,
total: 0,
progress: 0,
},
type,
ext,
musicInfo,
key: `${musicInfo.songmid}${ext}`,
}
downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName)
if (dls[downloadInfo.key]) {
dls[downloadInfo.key].stop().finally(() => {
this.dispatch('download/addTask', downloadInfo)
})
} else {
// console.log(downloadInfo)
this.dispatch('download/addTask', downloadInfo)
}
},
addTask({ commit, rootState }, downloadInfo) {
commit('addTask', downloadInfo)
let msg = checkPath(rootState.setting.download.savePath)
if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
const options = {
url: downloadInfo.url,
path: rootState.setting.download.savePath,
fileName: downloadInfo.fileName,
method: 'get',
override: true,
onEnd() {
commit('onEnd', downloadInfo)
console.log('on complate')
},
onError(err) {
console.log(err)
if (err.message.includes('Response status was')) {
const code = err.message.replace(/Response status was (\d+)$/, '$1')
switch (code) {
case '401':
case '403':
case '410':
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
refreshUrl(downloadInfo).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
dls[downloadInfo.key].__initProtocol(result.url)
dls[downloadInfo.key].resume()
}).catch(err => {
console.log(err)
})
return
default:
break
}
}
commit('onError', downloadInfo)
},
onStateChanged(state) {
console.log(state)
},
onDownload() {
commit('onDownload', downloadInfo)
console.log('on download')
},
onProgress(status) {
commit('onProgress', { downloadInfo, status })
console.log(status)
},
onPause() {
commit('pauseTask', downloadInfo)
},
onResume() {
commit('resumeTask', downloadInfo)
},
}
let p = options.url ? Promise.resolve() : refreshUrl(downloadInfo).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
options.url = result.url
})
p.then(() => {
dls[downloadInfo.key] = download(options)
})
},
removeTask({ commit, state }, index) {
let info = state.list[index]
if (state.list[index].isDownloading) {
dls[info.key].stop().finally(() => {
delete dls[info.key]
})
}
commit('removeTask', index)
if (dls[info.key]) delete dls[info.key]
},
resumeTask({ commit, rootState }, downloadInfo) {
let msg = checkPath(rootState.setting.download.savePath)
if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
const options = {
url: downloadInfo.url,
path: rootState.setting.download.savePath,
fileName: downloadInfo.fileName,
method: 'get',
override: true,
onEnd() {
commit('onEnd', downloadInfo)
console.log('on complate')
},
onError(err) {
commit('onError', downloadInfo)
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
refreshUrl(downloadInfo).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
dls[downloadInfo.key].__initProtocol(result.url)
dls[downloadInfo.key].resume()
}).catch(err => {
console.log(err)
})
console.log(err)
},
onStateChanged(state) {
console.log(state)
},
onDownload() {
commit('onDownload', downloadInfo)
console.log('on download')
},
onProgress(status) {
commit('onProgress', { downloadInfo, status })
console.log(status)
},
onPause() {
commit('pauseTask', downloadInfo)
},
onResume() {
commit('resumeTask', downloadInfo)
},
}
let p = options.url ? Promise.resolve() : refreshUrl(downloadInfo).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
options.url = result.url
})
if (fs.existsSync(downloadInfo.filePath)) {
options.resumeInfo = {
totalFileSize: downloadInfo.progress.total,
}
}
p.then(() => {
dls[downloadInfo.key] = download(options)
})
},
}
// mitations
const mutations = {
addTask(state, downloadInfo) {
state.list.push(downloadInfo)
},
removeTask(state, index) {
state.list.splice(index, 1)
},
pauseTask(state, downloadInfo) {
downloadInfo.isDownloading = false
},
resumeTask(state, downloadInfo) {
downloadInfo.statusText = '恢复下载'
},
setStatusText(state, { downloadInfo, index, text }) {
if (downloadInfo) {
downloadInfo.statusText = text
} else {
state.list[index].statusText = text
}
},
onEnd(state, downloadInfo) {
downloadInfo.isComplate = true
downloadInfo.isDownloading = false
downloadInfo.statusText = '下载完成'
},
onError(state, downloadInfo) {
downloadInfo.isDownloading = false
downloadInfo.statusText = '任务出错'
},
onDownload(state, downloadInfo) {
downloadInfo.isDownloading = true
downloadInfo.statusText = '正在下载'
},
onProgress(state, { downloadInfo, status }) {
downloadInfo.progress.progress = status.progress
downloadInfo.progress.downloaded = status.downloaded
downloadInfo.progress.total = status.total
},
setTotal(state, { order, downloadInfo }) {
downloadInfo.order = order
},
updateDownloadList(state, list) {
state.list = list
},
updateUrl(state, { downloadInfo, url }) {
downloadInfo.url = url
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
+26
View File
@@ -0,0 +1,26 @@
// https://vuex.vuejs.org/en/modules.html
const requireModule = require.context('./', true, /\.js$/)
const modules = {}
requireModule.keys().forEach(fileName => {
if (fileName === './index.js') return
const path = fileName.replace(/(\.\/|\.js)/g, '')
if (/\//.test(path)) {
// Replace ./ and .js
const [moduleName, imported] = path.split('/')
if (!modules[moduleName]) {
modules[moduleName] = {
namespaced: true,
}
}
modules[moduleName][imported] = requireModule(fileName).default
} else {
modules[path] = requireModule(fileName).default
}
})
export default modules
+63
View File
@@ -0,0 +1,63 @@
import music from '../../utils/music'
const sourceList = {}
const sources = []
for (const source of music.sources) {
const leaderboard = music[source.id].leaderboard
if (!leaderboard) continue
sourceList[source.id] = leaderboard.list
sources.push(source)
}
// state
const state = {
list: [],
total: 0,
page: 1,
limit: 30,
key: null,
}
// getters
const getters = {
sourceInfo: () => ({ sources, sourceList }),
list(state) {
return state.list
},
info(state) {
return {
total: state.total,
limit: state.limit,
page: state.page,
}
},
}
// actions
const actions = {
getList({ state, rootState, commit }, page) {
let source = rootState.setting.leaderboard.source
let tabId = rootState.setting.leaderboard.tabId
let key = `${source}${tabId}${page}}`
if (state.list.length && state.key == key) return true
return music[source].leaderboard.getList(tabId, page).then(result => commit('setList', { result, key }))
},
}
// mitations
const mutations = {
setList(state, { result, key }) {
state.list = result.list
state.total = result.total
state.limit = result.limit
state.page = result.page
state.key = key
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
+47
View File
@@ -0,0 +1,47 @@
// state
const state = {
defaultList: {
name: '试听列表',
list: [],
},
userList: [],
}
// getters
const getters = {
defaultList: state => state.defaultList || {},
userList: state => state.userList,
}
// actions
const actions = {
}
// mitations
const mutations = {
initDefaultList(state, data) {
state.defaultList = data
},
setDefaultList(state, list) {
state.defaultList.list = list
},
defaultListAdd(state, musicInfo) {
if (state.defaultList.list.some(s => s.songmid === musicInfo.songmid)) return
state.defaultList.list.push(musicInfo)
},
defaultListRemove(state, index) {
state.defaultList.list.splice(index, 1)
},
defaultListClear(state) {
state.defaultList.list.length = 0
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
+68
View File
@@ -0,0 +1,68 @@
import music from '../../utils/music'
// state
const state = {
list: [],
listId: null,
playIndex: -1,
changePlay: false,
}
// getters
const getters = {
list: state => state.list || [],
listId: state => state.listId,
changePlay: satte => satte.changePlay,
playIndex: state => state.playIndex,
}
// actions
const actions = {
getUrl({ commit, state }, { musicInfo, type }) {
return music[musicInfo.source].getMusicUrl(musicInfo, type).then(result => commit('setUrl', { musicInfo, url: result.url, type }))
},
getPic({ commit, state }, musicInfo) {
return music[musicInfo.source].getPic(musicInfo).then(url => commit('getPic', { musicInfo, url }))
},
getLrc({ commit, state }, musicInfo) {
return music[musicInfo.source].getLyric(musicInfo).then(lrc => commit('setLrc', { musicInfo, lrc }))
},
}
// mitations
const mutations = {
setUrl(state, datas) {
datas.musicInfo.typeUrl[datas.type] = datas.url
},
getPic(state, datas) {
datas.musicInfo.img = datas.url
},
setLrc(state, datas) {
datas.musicInfo.lyric = datas.lrc
},
setList(state, { list, listId, index }) {
state.list = list
state.listId = listId
state.playIndex = index
state.changePlay = true
},
setPlayIndex(state, index) {
state.playIndex = index
state.changePlay = true
},
fixPlayIndex(state, index) {
state.playIndex = index
},
resetChangePlay(state) {
state.changePlay = false
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
+53
View File
@@ -0,0 +1,53 @@
import music from '../../utils/music'
// state
const state = {
list: [],
text: '',
page: 1,
limit: 30,
allPage: 1,
total: 0,
}
// getters
const getters = {
list: state => state.list || [],
limit: state => state.limit,
info: state => ({ page: state.page, text: state.text }),
listInfo: state => ({ allPage: state.allPage, total: state.total }),
}
// actions
const actions = {
search({ commit, rootState }, { text, page, limit }) {
return music[rootState.setting.sourceId].musicSearch.search(text, page, limit)
.then(data => commit('setList', { list: data.list, allPage: data.allPage, total: data.total, text, page }))
},
}
// mitations
const mutations = {
setList(state, datas) {
state.list = datas.list
state.total = datas.total
state.allPage = datas.allPage
state.page = datas.page
state.text = datas.text
},
clearList(state) {
state.list.length = 0
state.page = 0
state.allPage = 0
state.total = 0
state.text = ''
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
+25
View File
@@ -0,0 +1,25 @@
export default {
setTheme(state, val) {
state.setting.themeId = val
},
setSource(state, val) {
state.setting.sourceId = val
},
setSetting(state, val) {
state.setting = val
},
setLeaderboard(state, { tabId, source }) {
if (tabId != null) state.setting.leaderboard.tabId = tabId
if (source != null) state.setting.leaderboard.source = source
},
setNewVersion(state, val) {
val.history.forEach(ver => {
ver.desc = ver.desc.replace(/\n/g, '<br>')
})
val.desc = val.desc.replace(/\n/g, '<br>')
state.version.newVersion = val
},
setVersionVisible(state, val) {
state.version.showModal = val
},
}
+56
View File
@@ -0,0 +1,56 @@
// const isDev = process.env.NODE_ENV === 'development'
import Store from 'electron-store'
import { updateSetting } from '../utils'
import { version } from '../../../package.json'
let electronStore = new Store()
const setting = updateSetting(electronStore.get('setting'))
electronStore.set('setting', setting)
export default {
themes: [
{
id: 0,
name: '绿意盎然',
class: 'green',
},
{
id: 1,
name: '蓝田生玉',
class: 'blue',
},
{
id: 2,
name: '信口雌黄',
class: 'yellow',
},
{
id: 3,
name: '橙黄橘绿',
class: 'orange',
},
{
id: 4,
name: '热情似火',
class: 'red',
},
{
id: 5,
name: '重斤球紫',
class: 'purple',
},
{
id: 6,
name: '灰常美丽',
class: 'grey',
},
],
version: {
version,
newVersion: null,
showModal: false,
},
userInfo: null,
setting,
electronStore,
}
+93
View File
@@ -0,0 +1,93 @@
import { DownloaderHelper } from 'node-downloader-helper'
// import { pauseResumeTimer } from './util'
import { sizeFormate } from '../index'
import { debugDownload } from '../env'
// these are the default options
// const options = {
// method: 'GET', // Request Method Verb
// // Custom HTTP Header ex: Authorization, User-Agent
// headers: {},
// fileName: '', // Custom filename when saved
// override: false, // if true it will override the file, otherwise will append '(number)' to the end of file
// forceResume: false, // If the server does not return the "accept-ranges" header, can be force if it does support it
// // httpRequestOptions: {}, // Override the http request options
// // httpsRequestOptions: {}, // Override the https request options, ex: to add SSL Certs
// }
export default ({
url,
path,
fileName,
method = 'get',
headers,
override,
forceResume,
// resumeTime = 5000,
onEnd = () => {},
onError = () => {},
onStateChanged = () => {},
onDownload = () => {},
onPause = () => {},
onResume = () => {},
onProgress = () => {},
resumeInfo,
} = {}) => {
const dl = new DownloaderHelper(url, path, {
fileName,
method,
headers,
override,
forceResume,
})
dl.on('end', () => {
onEnd()
debugDownload && console.log('Download Completed')
}).on('error', err => {
onError(err)
dl.resume()
console.log('Download failed, Attempting Retry')
debugDownload && console.error('Something happend', err)
}).on('stateChanged', state => {
onStateChanged(state)
debugDownload && console.log('State: ', state)
}).on('download', () => {
onDownload()
// pauseResumeTimer(dl, resumeTime)
}).on('progress', stats => {
const progress = stats.progress.toFixed(2)
const speed = sizeFormate(stats.speed)
onProgress({
progress,
speed,
downloaded: stats.downloaded,
total: stats.total,
})
if (debugDownload) {
const downloaded = sizeFormate(stats.downloaded)
const total = sizeFormate(stats.total)
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
}
}).on('pause', () => {
onPause()
debugDownload && console.log('paused')
}).on('resume', () => {
onResume()
debugDownload && console.log('resume')
})
debugDownload && console.log('Downloading: ', url)
if (resumeInfo) {
dl.__total = resumeInfo.totalFileSize // <--- Workaround
// dl.__filePath = resumeInfo.filePath // <--- Workaround
dl.__isResumable = true // <--- Workaround
dl.resume()
} else {
dl.start()
}
return dl
}
+24
View File
@@ -0,0 +1,24 @@
import { DH_STATES } from 'node-downloader-helper'
export const pauseResumeTimer = (_dl, wait) => {
setTimeout(() => {
if (_dl.state === DH_STATES.FINISHED || _dl.state === DH_STATES.FAILED) {
return
}
_dl
.pause()
.then(() => console.log(`Paused for ${wait / 1000} seconds`))
.then(() =>
setTimeout(() => {
if (!_dl.isResumable()) {
console.warn(
"This URL doesn't support resume, it will start from the beginning"
)
}
return _dl.resume()
}, wait)
)
}, wait)
}
+5
View File
@@ -0,0 +1,5 @@
const isDev = process.env.NODE_ENV === 'development'
export const debug = isDev && true
export const debugRequest = isDev && false
export const debugDownload = isDev && false
+196
View File
@@ -0,0 +1,196 @@
import fs from 'fs'
import { shell, remote } from 'electron'
import path from 'path'
import os from 'os'
/**
* 获取两个数之间的随机整数,大于等于min,小于max
* @param {*} min
* @param {*} max
*/
export const getRandom = (min, max) => Math.floor(Math.random() * (max - min)) + min
export const sizeFormate = size => {
// https://gist.github.com/thomseddon/3511330
if (!size) return '0 b'
let units = ['b', 'kB', 'MB', 'GB', 'TB']
let number = Math.floor(Math.log(size) / Math.log(1024))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
}
export const formatPlayTime = time => {
let m = parseInt(time / 60)
let s = parseInt(time % 60)
return m === 0 && s === 0 ? '--/--' : (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
}
export const formatPlayTime2 = time => {
let m = parseInt(time / 60)
let s = parseInt(time % 60)
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
}
export const b64DecodeUnicode = str => {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(window.atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
}
export const decodeName = str => str.replace(/&apos;/g, '\'')
export const scrollTo = (element, to, duration = 300, fn = function() {}) => {
const start = element.scrollTop || element.scrollY
const change = to - start
const increment = 10
if (!change) {
fn()
return
}
let currentTime = 0; let val
const easeInOutQuad = (t, b, c, d) => {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(0, val)
} else {
element.scrollTop = val
}
if (currentTime < duration) {
setTimeout(animateScroll, increment)
} else {
fn()
}
}
animateScroll()
}
/**
* 检查路径是否存在
* @param {*} path
*/
export const checkPath = path => fs.existsSync(path)
/**
* 在资源管理器中打开目录
* @param {*} 选项
*/
export const openSelectDir = options => remote.dialog.showOpenDialog(remote.getCurrentWindow(), options)
/**
* 在资源管理器中打开目录
* @param {*} 选项
*/
export const openSaveDir = options => remote.dialog.showSaveDialog(remote.getCurrentWindow(), options)
/**
* 在资源管理器中打开目录
* @param {*} dir
*/
export const openDirInExplorer = dir => {
shell.showItemInFolder(dir)
}
export const checkVersion = (currentVer, targetVer) => {
// console.log(currentVer)
// console.log(targetVer)
currentVer = currentVer.split('.')
targetVer = targetVer.split('.')
let maxLen = Math.max(currentVer.length, targetVer.length)
if (currentVer.length < maxLen) {
for (let index = 0, len = maxLen - currentVer.length; index < len; index++) {
currentVer.push(0)
}
}
if (targetVer.length < maxLen) {
for (let index = 0, len = maxLen - targetVer.length; index < len; index++) {
targetVer.push(0)
}
}
for (let index = 0; index < currentVer.length; index++) {
if (parseInt(currentVer[index]) < parseInt(targetVer[index])) return true
if (parseInt(currentVer[index]) > parseInt(targetVer[index])) return false
}
return false
}
export const isObject = item => item && typeof item === 'object' && !Array.isArray(item)
/**
* 对象深度合并
* 注意:循环引用的对象会出现死循环
* @param {} target 要合并源对象
* @param {} source 要合并目标对象
*/
export const objectDeepMerge = (target, source) => {
let base = {}
Object.keys(source).forEach(item => {
if (Array.isArray(source[item])) {
let arr = Array.isArray(target[item]) ? target[item] : []
target[item] = arr.concat(source[item])
return
} else if (isObject(source[item])) {
if (!isObject(target[item])) target[item] = {}
objectDeepMerge(target[item], source[item])
return
}
base[item] = source[item]
})
Object.assign(target, base)
}
/**
* 判断是否父子元素
* @param {*} parent
* @param {*} children
*/
export const isChildren = (parent, children) => {
return children.parentNode ? children.parentNode === parent ? true : isChildren(parent, children.parentNode) : false
}
export const updateSetting = setting => {
const defaultVersion = '1.0.1'
const defaultSetting = {
version: defaultVersion,
player: {
togglePlayMethod: 'listLoop',
highQuality: false,
},
list: {
isShowAlbumName: true,
},
download: {
savePath: path.join(os.homedir(), 'Desktop'),
fileName: '歌名 - 歌手',
},
leaderboard: {
source: 'kw',
tabId: 'kwbiaosb',
},
themeId: 0,
sourceId: 'kw',
randomAnimate: true,
}
const overwriteSetting = {
version: defaultVersion,
sourceId: 'kw',
}
if (!setting) {
setting = defaultSetting
} else if (checkVersion(setting.version, defaultSetting.version)) {
objectDeepMerge(defaultSetting, setting)
objectDeepMerge(defaultSetting, overwriteSetting)
setting = defaultSetting
}
return setting
}
+28
View File
@@ -0,0 +1,28 @@
import kw from './kw'
import kg from './kg'
import tx from './tx'
import wy from './wy'
export default {
sources: [
{
name: '酷我音乐',
id: 'kw',
},
{
name: '酷狗音乐',
id: 'kg',
},
{
name: 'QQ音乐',
id: 'tx',
},
{
name: '网易音乐',
id: 'wy',
},
],
kw,
kg,
tx,
wy,
}
+7
View File
@@ -0,0 +1,7 @@
import leaderboard from './leaderboard'
const kg = {
leaderboard,
}
export default kg
+125
View File
@@ -0,0 +1,125 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime } from '../../index'
export default {
list: [
{
id: 'kgtop500',
name: '酷狗TOP500',
bangid: '8888',
},
{
id: 'kgwlhgb',
name: '网络红歌榜',
bangid: '23784',
},
{
id: 'kgbsb',
name: '飙升榜',
bangid: '6666',
},
{
id: 'kgfxb',
name: '分享榜',
bangid: '21101',
},
{
id: 'kgcyyb',
name: '纯音乐榜',
bangid: '33164',
},
{
id: 'kggfjqb',
name: '古风金曲榜',
bangid: '33161',
},
{
id: 'kgyyjqb',
name: '粤语金曲榜',
bangid: '33165',
},
{
id: 'kgomjqb',
name: '欧美金曲榜',
bangid: '33166',
},
// {
// id: 'kgdyrgb',
// name: '电音热歌榜',
// bangid: '33160',
// },
// {
// id: 'kgjdrgb',
// name: 'DJ热歌榜',
// bangid: '24971',
// },
// {
// id: 'kghyxgb',
// name: '华语新歌榜',
// bangid: '31308',
// },
],
getUrl(p, id) {
return `http://www2.kugou.kugou.com/yueku/v9/rank/home/${p}-${id}.html`
},
regExps: {
total: /total: '(\d+)',/,
page: /page: '(\d+)',/,
limit: /pagesize: '(\d+)',/,
listData: /global\.features = (\[.+\]);/,
},
_cancelIndex: null,
_cancelPromiseCancelFn: null,
getData(url) {
if (this._cancelIndex != null) {
cancelHttp(this._cancelIndex)
this._cancelPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._cancelPromiseCancelFn = reject
this._cancelIndex = httpGet(url, (err, resp, body) => {
this._cancelIndex = null
this._cancelPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
filterData(rawList) {
return rawList.map(item => ({
singer: item.singername,
name: item.songname,
albumName: item.album_name,
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: formatPlayTime(item.duration / 1000),
img: null,
lrc: null,
typeUrl: {},
}))
},
getList(id, page) {
let type = this.list.find(s => s.id === id)
if (!type) return Promise.reject()
return this.getData(this.getUrl(page, type.bangid)).then(html => {
let total = html.match(this.regExps.total)
if (total) total = parseInt(RegExp.$1)
page = html.match(this.regExps.page)
if (page) page = parseInt(RegExp.$1)
let limit = html.match(this.regExps.limit)
if (limit) limit = parseInt(RegExp.$1)
let listData = html.match(this.regExps.listData)
if (listData) listData = this.filterData(JSON.parse(RegExp.$1))
return {
total,
list: listData,
limit,
page,
}
})
},
}
+121
View File
@@ -0,0 +1,121 @@
import { httpGet, cancelHttp } from '../../request'
import tempSearch from './tempSearch'
import musicSearch from './musicSearch'
import { formatSinger } from './util'
import leaderboard from './leaderboard'
import lyric from './lyric'
const kw = {
_musicInfoIndex: null,
_musicInfoPromiseCancelFn: null,
_musicPicIndex: null,
_musicPicPromiseCancelFn: null,
// context: null,
// init(context) {
// if (this.isInited) return
// this.isInited = true
// this.context = context
// // this.musicSearch.search('我又想你了').then(res => {
// // console.log(res)
// // })
// // this.getMusicUrl('62355680', '320k').then(url => {
// // console.log(url)
// // })
// },
tempSearch,
musicSearch,
leaderboard,
getLyric(songInfo) {
// let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer
return lyric.getLyric(songInfo.songmid)
},
handleMusicInfo(songInfo) {
return this.getMusicInfo(songInfo).then(info => {
// console.log(JSON.stringify(info))
songInfo.name = info.name
songInfo.singer = formatSinger(info.artist)
songInfo.img = info.pic
songInfo.albumName = info.album
return songInfo
// return Object.assign({}, songInfo, {
// name: info.name,
// singer: formatSinger(info.artist),
// img: info.pic,
// albumName: info.album,
// })
})
},
getMusicUrl(songInfo, type) {
return new Promise((resolve, reject) => {
httpGet(`https://v1.itooi.cn/kuwo/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, (err, resp, body) => {
if (err) {
console.log(err)
return this.getMusicUrl(songInfo, type)
}
body.code === 200 ? resolve({ type, url: body.data }) : reject(new Error(body.msg))
})
})
},
getMusicInfo(songInfo) {
if (this._musicInfoIndex != null) {
cancelHttp(this._musicInfoIndex)
this._musicInfoPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._musicInfoPromiseCancelFn = reject
this._musicInfoIndex = httpGet(`http://www.kuwo.cn/api/www/music/musicInfo?mid=${songInfo.songmid}`, (err, resp, body) => {
this._musicInfoIndex = null
this._musicInfoPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
body.code === 200 ? resolve(body.data) : reject(new Error(body.msg))
})
})
},
getMusicUrls(musicInfo, cb) {
let tasks = []
let songId = musicInfo.songmid
musicInfo.types.forEach(type => {
tasks.push(kw.getMusicUrl(songId, type.type))
})
Promise.all(tasks).then(urlInfo => {
let typeUrl = {}
urlInfo.forEach(info => {
typeUrl[info.type] = info.url
})
cb(typeUrl)
})
},
getPic(songInfo) {
if (this._musicPicIndex != null) {
cancelHttp(this._musicPicIndex)
this._musicPicPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._musicPicPromiseCancelFn = reject
this._musicPicIndex = httpGet(`https://v1.itooi.cn/kuwo/pic?id=${songInfo.songmid}&isRedirect=0`, (err, resp, body) => {
this._musicPicIndex = null
this._musicPicPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
console.log(body)
body.code === 200 ? resolve(body.data) : reject(new Error(body.msg))
})
})
},
}
export default kw
+190
View File
@@ -0,0 +1,190 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime } from '../../index'
export default {
list: [
{
id: 'kwbiaosb',
name: '飙升榜',
bangid: 93,
},
{
id: 'kwregb',
name: '热歌榜',
bangid: 16,
},
{
id: 'kwhuiyb',
name: '会员榜',
bangid: 145,
},
{
id: 'kwdouyb',
name: '抖音榜',
bangid: 158,
},
{
id: 'kwqsb',
name: '趋势榜',
bangid: 187,
},
{
id: 'kwhuaijb',
name: '怀旧榜',
bangid: 26,
},
{
id: 'kwhuayb',
name: '华语榜',
bangid: 104,
},
{
id: 'kwyueyb',
name: '粤语榜',
bangid: 182,
},
{
id: 'kwoumb',
name: '欧美榜',
bangid: 22,
},
{
id: 'kwhanyb',
name: '韩语榜',
bangid: 184,
},
{
id: 'kwriyb',
name: '日语榜',
bangid: 183,
},
],
getUrl: (p, l, id) => `http://kbangserver.kuwo.cn/ksong.s?from=pc&fmt=json&pn=${p - 1}&rn=${l}&type=bang&data=content&id=${id}&show_copyright_off=0&pcmp4=1&isbang=1`,
getUrl2: (p, l, id) => `http://www.kuwo.cn/api/www/bang/bang/musicList?bangId=${id}&pn=${p}&rn=${l}`,
regExps: {
},
limit: 30,
_cancelIndex: null,
_cancelPromiseCancelFn: null,
_cancelIndex2: null,
_cancelPromiseCancelFn2: null,
getData(url) {
if (this._cancelIndex != null) {
cancelHttp(this._cancelIndex)
this._cancelPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._cancelPromiseCancelFn = reject
this._cancelIndex = httpGet(url, (err, resp, body) => {
this._cancelIndex = null
this._cancelPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
getData2(url) {
if (this._cancelIndex2 != null) {
cancelHttp(this._cancelIndex2)
this._cancelPromiseCancelFn2(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._cancelPromiseCancelFn2 = reject
this._cancelIndex2 = httpGet(url, (err, resp, body) => {
this._cancelIndex2 = null
this._cancelPromiseCancelFn2 = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
filterData(rawList, rawList2) {
return rawList.map((item, inedx) => {
let formats = item.formats.split('|')
let types = []
let _types = {}
if (formats.indexOf('MP3128')) {
types.push({ type: '128k', size: null })
_types['128k'] = {
size: null,
}
}
if (formats.indexOf('MP3192')) {
types.push({ type: '192k', size: null })
_types['192k'] = {
size: null,
}
}
if (formats.indexOf('MP3H')) {
types.push({ type: '320k', size: null })
_types['320k'] = {
size: null,
}
}
if (formats.indexOf('AL')) {
types.push({ type: 'ape', size: null })
_types['ape'] = {
size: null,
}
}
if (formats.indexOf('ALFLAC')) {
types.push({ type: 'flac', size: null })
_types['flac'] = {
size: null,
}
}
types.reverse()
return {
singer: item.artist,
name: item.name,
albumName: item.album,
albumId: item.albumid,
songmid: item.id,
source: 'kw',
interval: formatPlayTime(rawList2[inedx].duration),
img: item.pic,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
loadData(p1, p2, page, bangid) {
return Promise.all([p1, p2]).then(([data1, data2]) => {
// console.log(data1, data2)
if (!data1.musiclist.length) {
return this.loadData(this.getData(this.getUrl(page, this.limit, bangid)),
data2.data.musicList.length
? Promise.resolve(data2)
: this.getData2(this.getUrl2(page, this.limit, bangid)), page, bangid)
}
if (!data2.data.musicList.length) {
return this.loadData(Promise.resolve(data1), this.getData2(this.getUrl2(page, this.limit, bangid)), page, bangid)
}
return Promise.resolve([data1, data2])
})
},
getList(id, page) {
let type = this.list.find(s => s.id === id)
if (!type) return Promise.reject()
return this.loadData(this.getData(this.getUrl(page, this.limit, type.bangid)), this.getData2(this.getUrl2(page, this.limit, type.bangid)), page, type.bangid).then(([data1, data2]) => {
// console.log(data1.musiclist, data2.data)
let total = parseInt(data1.num)
let list = this.filterData(data1.musiclist, data2.data.musicList)
return {
total,
list,
limit: this.limit,
page,
}
})
},
}
+31
View File
@@ -0,0 +1,31 @@
import { httpGet, cancelHttp } from '../../request'
export default {
_musicLrcIndex: null,
_musicLrcPromiseCancelFn: null,
formatTime(time) {
let m = parseInt(time / 60)
let s = (time % 60).toFixed(2)
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
},
getLyric(songId) {
if (this._musicLrcIndex != null) {
cancelHttp(this._musicLrcIndex)
this._musicLrcPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._musicLrcPromiseCancelFn = reject
this._musicLrcIndex = httpGet(`https://v1.itooi.cn/kuwo/lrc?id=${songId}`, (err, resp, body) => {
this._musicLrcIndex = null
this._musicLrcPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
// console.log(body.data)
// console.log(this.transformLrc(body.data))
resolve(body)
})
})
},
}
+144
View File
@@ -0,0 +1,144 @@
// import '../../polyfill/array.find'
// import jshtmlencode from 'js-htmlencode'
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime, decodeName } from '../../index'
// import { debug } from '../../utils/env'
import { formatSinger } from './util'
export default {
regExps: {
mInfo: /bitrate:(\d+),format:(\w+),size:([\w.]+)/,
},
_musicSearchIndex: null,
_musicSearchPromiseCancelFn: null,
limit: 30,
total: 0,
page: 0,
allPage: 1,
// cancelFn: null,
musicSearch(str, page) {
if (this._musicSearchIndex != null) {
cancelHttp(this._musicSearchIndex)
this._musicSearchPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._musicSearchPromiseCancelFn = reject
this._musicSearchIndex = httpGet(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${this.limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`, (err, resp, body) => {
this._musicSearchIndex = null
this._musicSearchPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
// getImg(songId) {
// return httpGet(`http://player.kuwo.cn/webmusic/sj/dtflagdate?flag=6&rid=MUSIC_${songId}`)
// },
// getLrc(songId) {
// return httpGet(`http://mobile.kuwo.cn/mpage/html5/songinfoandlrc?mid=${songId}&flag=0`)
// },
handleResult(rawData) {
const result = []
for (let i = 0; i < rawData.length; i++) {
const info = rawData[i]
let songId = info.MUSICRID.replace('MUSIC_', '')
// const format = (info.FORMATS || info.formats).split('|')
if (!info.MINFO) {
console.log('mInfo is undefined')
return null
}
const types = []
const _types = {}
let infoArr = info.MINFO.split(';')
infoArr.forEach(info => {
info = info.match(this.regExps.mInfo)
if (info) {
switch (info[2]) {
case 'flac':
types.push({ type: 'flac', size: info[3] })
_types.flac = {
size: info[3].toLocaleUpperCase(),
}
break
case 'ape':
types.push({ type: 'ape', size: info[3] })
_types.ape = {
size: info[3].toLocaleUpperCase(),
}
break
case 'mp3':
switch (info[1]) {
case '320':
types.push({ type: '320k', size: info[3] })
_types['320k'] = {
size: info[3].toLocaleUpperCase(),
}
break
case '192':
types.push({ type: '192k', size: info[3] })
_types['192k'] = {
size: info[3].toLocaleUpperCase(),
}
break
case '128':
types.push({ type: '128k', size: info[3] })
_types['128k'] = {
size: info[3].toLocaleUpperCase(),
}
break
}
break
}
}
})
types.reverse()
let interval = parseInt(info.DURATION)
result.push({
name: decodeName(info.SONGNAME),
singer: formatSinger(decodeName(info.ARTIST)),
source: 'kw',
// img = (info.album.name === '' || info.album.name === '空')
// ? `http://player.kuwo.cn/webmusic/sj/dtflagdate?flag=6&rid=MUSIC_160911.jpg`
// : `https://y.gtimg.cn/music/photo_new/T002R500x500M000${info.album.mid}.jpg`
songmid: songId,
albumId: decodeName(info.ALBUMID || ''),
interval: Number.isNaN(interval) ? 0 : formatPlayTime(interval),
albumName: info.ALBUM ? decodeName(info.ALBUM) : '',
lyric: null,
img: null,
types,
_types,
typeUrl: {},
})
}
return result
},
search(str, page = 1, { limit }) {
if (limit != null) this.limit = limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, { limit })
let list = this.handleResult(result.abslist)
if (list == null) return this.search(str, page, { limit })
this.total = parseInt(result.TOTAL)
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
return Promise.resolve({
list,
allPage: this.allPage,
total: this.total,
})
})
},
}
+43
View File
@@ -0,0 +1,43 @@
import { httpGet, cancelHttp } from '../../request'
import { decodeName } from '../../index'
export default {
regExps: {
relWord: /RELWORD=(.+)/,
},
_musicTempSearchIndex: null,
_musicTempSearchPromiseCancelFn: null,
tempSearch(str) {
if (this._musicTempSearchIndex != null) {
cancelHttp(this._musicTempSearchIndex)
this._musicTempSearchPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._musicTempSearchPromiseCancelFn = reject
this._musicTempSearchIndex = httpGet(`http://www.kuwo.cn/api/www/search/searchKey?key=${encodeURIComponent(str)}`, (err, resp, body) => {
this._musicTempSearchIndex = null
this._musicTempSearchPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
handleResult(rawData) {
return rawData.map(info => {
let matchResult = info.match(this.regExps.relWord)
return matchResult ? decodeName(matchResult[1]) : ''
})
},
cancelTempSearch() {
if (this._musicTempSearchIndex != null) {
cancelHttp(this._musicTempSearchIndex)
this._musicTempSearchPromiseCancelFn(new Error('取消http请求'))
}
},
search(str) {
return this.tempSearch(str).then(result => this.handleResult(result.data))
},
}
+2
View File
@@ -0,0 +1,2 @@
export const formatSinger = rawData => rawData.replace(/&/g, '、')
+7
View File
@@ -0,0 +1,7 @@
import leaderboard from './leaderboard'
const tx = {
leaderboard,
}
export default tx
+203
View File
@@ -0,0 +1,203 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime, sizeFormate } from '../../index'
export default {
limit: 300,
list: [
{
id: 'txlxzsb',
name: '流行榜',
bangid: 4,
},
{
id: 'txrgb',
name: '热歌榜',
bangid: 26,
},
{
id: 'txwlhgb',
name: '网络榜',
bangid: 28,
},
{
id: 'txdyb',
name: '抖音榜',
bangid: 60,
},
{
id: 'txndb',
name: '内地榜',
bangid: 5,
},
{
id: 'txxgb',
name: '香港榜',
bangid: 59,
},
{
id: 'txtwb',
name: '台湾榜',
bangid: 61,
},
{
id: 'txoumb',
name: '欧美榜',
bangid: 3,
},
{
id: 'txhgb',
name: '韩国榜',
bangid: 16,
},
{
id: 'txrbb',
name: '日本榜',
bangid: 17,
},
// {
// id: 'txtybb',
// name: 'YouTube榜',
// bangid: 128,
// },
],
getUrl(id, period, limit) {
return `https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&platform=yqq.json&needNewCode=0&data=${encodeURIComponent(JSON.stringify({
comm: {
cv: 1602,
ct: 20,
},
toplist: {
module: 'musicToplist.ToplistInfoServer',
method: 'GetDetail',
param: {
topid: id,
num: limit,
period,
},
},
}))}`
},
regExps: {
periodList: /<i class="play_cover__btn c_tx_link js_icon_play" data-listkey=".+?" data-listname=".+?" data-tid=".+?" data-date=".+?" .+?<\/i>/g,
period: /data-listname="(.+?)" data-tid=".*?\/(.+?)" data-date="(.+?)" .+?<\/i>/,
},
periods: {},
periodUrl: 'https://c.y.qq.com/node/pc/wk_v15/top.html',
_cancelIndex: null,
_cancelPromiseCancelFn: null,
getData(url) {
if (this._cancelIndex != null) {
cancelHttp(this._cancelIndex)
this._cancelPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._cancelPromiseCancelFn = reject
this._cancelIndex = httpGet(url, (err, resp, body) => {
this._cancelIndex = null
this._cancelPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.name)
})
return arr.join('、')
},
filterData(rawList) {
// console.log(rawList)
return rawList.map(item => {
let types = []
let _types = {}
if (item.file.size_128mp3 !== 0) {
let size = sizeFormate(item.file.size_128mp3)
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
if (item.file.size_320mp3 !== 0) {
let size = sizeFormate(item.file.size_320mp3)
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
}
if (item.file.size_ape !== 0) {
let size = sizeFormate(item.file.size_ape)
types.push({ type: 'ape', size })
_types.ape = {
size,
}
}
if (item.file.size_flac !== 0) {
let size = sizeFormate(item.file.size_flac)
types.push({ type: 'flac', size })
_types.flac = {
size,
}
}
types.reverse()
return {
singer: this.getSinger(item.singer),
name: item.title,
albumName: item.album.title,
albumId: item.album.mid,
source: 'tx',
interval: formatPlayTime(item.interval),
songId: item.id,
albumMid: item.album.mid,
strMediaMid: item.file.media_mid,
songmid: item.mid,
img: (item.album.name === '' || item.album.name === '空')
? `https://y.gtimg.cn/music/photo_new/T001R500x500M000${item.singer[0].mid}.jpg`
: `https://y.gtimg.cn/music/photo_new/T002R500x500M000${item.album.mid}.jpg`,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
getPeriods(bangid) {
return this.getData(this.periodUrl).then(html => {
let result = html.match(this.regExps.periodList)
if (!result) return Promise.reject()
result.forEach(item => {
let result = item.match(this.regExps.period)
if (!result) return
this.periods[result[2]] = {
name: result[1],
bangid: result[2],
period: result[3],
}
})
const info = this.periods[bangid]
return info && info.period
})
},
getList(id, page) {
let type = this.list.find(s => s.id === id)
if (!type) return Promise.reject()
let info = this.periods[type.bangid]
let p = info ? Promise.resolve(info.period) : this.getPeriods(type.bangid)
return p.then(period => {
return this.getData(this.getUrl(type.bangid, period, this.limit)).then(data => {
// console.log(data)
if (data.code !== 0) return Promise.reject()
return {
total: data.toplist.data.songInfoList.length,
list: this.filterData(data.toplist.data.songInfoList),
limit: this.limit,
page: 1,
}
})
})
},
}
+7
View File
@@ -0,0 +1,7 @@
import leaderboard from './leaderboard'
const wy = {
leaderboard,
}
export default wy
+123
View File
@@ -0,0 +1,123 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime } from '../../index'
export default {
limit: 300,
list: [
{
id: 'wybsb',
name: '飙升榜',
bangid: '19723756',
},
{
id: 'wyrgb',
name: '热歌榜',
bangid: '3778678',
},
{
id: 'wyxgb',
name: '新歌榜',
bangid: '3779629',
},
{
id: 'wyycb',
name: '原创榜',
bangid: '2884035',
},
{
id: 'wygdb',
name: '古典榜',
bangid: '71384707',
},
{
id: 'wydouyb',
name: '抖音榜',
bangid: '2250011882',
},
{
id: 'wyhyb',
name: '韩语榜',
bangid: '745956260',
},
{
id: 'wydianyb',
name: '电音榜',
bangid: '1978921795',
},
{
id: 'wydjb',
name: '电竞榜',
bangid: '2006508653',
},
{
id: 'wyktvbb',
name: 'KTV唛榜',
bangid: '21845217',
},
],
getUrl(id) {
return `https://music.163.com/discover/toplist?id=${id}`
},
regExps: {
list: /<textarea id="song-list-pre-data" style="display:none;">(.+?)<\/textarea>/,
},
_cancelIndex: null,
_cancelPromiseCancelFn: null,
getData(url) {
if (this._cancelIndex != null) {
cancelHttp(this._cancelIndex)
this._cancelPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._cancelPromiseCancelFn = reject
this._cancelIndex = httpGet(url, (err, resp, body) => {
this._cancelIndex = null
this._cancelPromiseCancelFn = null
if (err) {
console.log(err)
reject(err)
}
resolve(body)
})
})
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.name)
})
return arr.join('、')
},
filterData(rawList) {
// console.log(rawList)
return rawList.map(item => {
return {
singer: this.getSinger(item.artists),
name: item.name,
albumName: item.album.name,
albumId: item.album.id,
source: 'wy',
interval: formatPlayTime(item.duration / 1000),
songmid: item.id,
img: item.album.picUrl,
lrc: null,
typeUrl: {},
}
})
},
getList(id, page) {
let type = this.list.find(s => s.id === id)
if (!type) return Promise.reject()
return this.getData(this.getUrl(type.bangid)).then(html => {
let result = html.match(this.regExps.list)
if (!result) return Promise.reject()
const list = this.filterData(JSON.parse(RegExp.$1))
return {
total: list.length,
list,
limit: this.limit,
page,
}
})
},
}
+175
View File
@@ -0,0 +1,175 @@
import request from 'request'
// import progress from 'request-progress'
import { debugRequest } from './env'
// import fs from 'fs'
const tasks = []
const fatchData = (url, method, options, callback) => {
let index = pushTask(tasks, request(url, {
method,
headers: options.headers,
Origin: options.origin,
data: options.data,
json: options.format === undefined || options.format === 'json',
}, (err, resp, body) => {
tasks[index] = null
if (err) return callback(err, null)
callback(null, resp, body)
}))
return index
}
/**
* 取消请求
* @param {*} index
*/
export const cancelHttp = index => {
if (index == null) return
console.log('cancel: ', index)
let r = tasks[index]
if (r == null) return
r.abort()
tasks[index] = null
}
const pushTask = (tasks, newTask) => {
for (const [index, task] of tasks.entries()) {
if (task == null) {
tasks[index] = newTask
return index
}
}
tasks.push(newTask)
return tasks.length - 1
}
/**
* http 请求
* @param {*} url 地址
* @param {*} options 选项
* @param {*} cb 回调
* @return {Number} index 用于取消请求
*/
export const http = (url, options, cb) => {
if (typeof options === 'function') {
cb = options
options = {}
}
// 默认选项
if (options.method == null) options.method = 'get'
debugRequest && console.log(`\n---send request------${url}------------`)
return fatchData(url, options.method, options, (err, resp, body) => {
// options.isShowProgress && window.api.hideProgress()
debugRequest && console.log(`\n---response------${url}------------`)
debugRequest && console.log(JSON.stringify(body))
if (err) {
debugRequest && console.log(JSON.stringify(err))
}
cb(err, resp, body)
})
}
/**
* http get 请求
* @param {*} url 地址
* @param {*} options 选项
* @param {*} callback 回调
* @return {Number} index 用于取消请求
*/
export const httpGet = (url, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
// options.isShowProgress && window.api.showProgress({
// title: options.progressMsg || '请求中',
// modal: true,
// })
debugRequest && console.log(`\n---send request-------${url}------------`)
return fatchData(url, 'get', options, function(err, resp, body) {
// options.isShowProgress && window.api.hideProgress()
debugRequest && console.log(`\n---response------${url}------------`)
debugRequest && console.log(JSON.stringify(body))
if (err) {
debugRequest && console.log(JSON.stringify(err))
}
callback(err, resp, body)
})
}
/**
* http post 请求
* @param {*} url 请求地址
* @param {*} data 提交的数据
* @param {*} options 选项
* @param {*} callback 回调
* @return {Number} index 用于取消请求
*/
export const httpPost = (url, data, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
// options.isShowProgress && window.api.showProgress({
// title: options.progressMsg || '请求中',
// modal: true,
// })
options.data = data
debugRequest && console.log(`\n---send request-------${url}------------`)
return fatchData(url, 'post', options, function(err, resp, body) {
// options.isShowProgress && window.api.hideProgress()
debugRequest && console.log(`\n---response------${url}------------`)
debugRequest && console.log(JSON.stringify(body))
if (err) {
debugRequest && console.log(JSON.stringify(err))
}
callback(err, resp, body)
})
}
/**
* http jsonp 请求
* @param {*} url 请求地址
* @param {*} options 选项
* options.jsonpCallback 回调
* @param {*} callback 回调
* @return {Number} index 用于取消请求
*/
export const http_jsonp = (url, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
let jsonpCallback = 'jsonpCallback'
if (url.indexOf('?') < 0) url += '?'
url += `&${options.jsonpCallback}=${jsonpCallback}`
options.format = 'script'
// options.isShowProgress && window.api.showProgress({
// title: options.progressMsg || '请求中',
// modal: true,
// })
debugRequest && console.log(`\n---send request-------${url}------------`)
return fatchData(url, 'get', options, function(err, resp, body) {
// options.isShowProgress && window.api.hideProgress()
debugRequest && console.log(`\n---response------${url}------------`)
debugRequest && console.log(JSON.stringify(body))
if (err) {
debugRequest && console.log(JSON.stringify(err))
} else {
body = JSON.parse(body.replace(new RegExp(`^${jsonpCallback}\\(({.*})\\)$`), '$1'))
}
callback(err, resp, body)
})
}
+153
View File
@@ -0,0 +1,153 @@
<template lang="pug">
div(:class="$style.download")
//- transition
div(:class="$style.content" v-if="list.length")
div(:class="$style.thead")
table
thead
tr
th.nobreak(style="width: 30%;") 歌曲名
th.nobreak(style="width: 25%;") 进度
th.nobreak(style="width: 15%;") 状态
th.nobreak(style="width: 10%;") 品质
th.nobreak(style="width: 20%;") 操作
div.scroll(v-if="list.length" :class="$style.tbody")
table
tbody
tr(v-for='(item, index) in list' :key='item.key' @click="handleDoubleClick(index)" :class="isPlayList && playIndex === index ? $style.active : ''")
td.break(style="width: 30%;") {{item.musicInfo.name}} - {{item.musicInfo.singer}}
td.break(style="width: 25%;") {{item.progress.progress}}%
td.break(style="width: 15%;") {{item.statusText}}
td.break(style="width: 10%;") {{item.type.toUpperCase()}}
td(style="width: 20%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :downloadBtn="false" @btn-click="handleBtnClick")
div(:class="$style.noItem" v-else)
</template>
<script>
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { checkPath } from '../utils'
export default {
name: 'Download',
data() {
return {
clickTime: window.performance.now(),
clickIndex: -1,
}
},
computed: {
...mapGetters('download', ['list', 'dls']),
...mapGetters('player', ['listId', 'playIndex']),
isPlayList() {
return this.listId == 'download'
},
},
methods: {
...mapActions('download', ['removeTask', 'resumeTask']),
...mapMutations('player', ['setList']),
pauseTask(index) {
let info = this.list[index]
this.dls[info.key].pause()
},
startTask(index) {
let info = this.list[index]
let dl = this.dls[info.key]
dl ? dl.resume() : this.resumeTask(info)
},
handleDoubleClick(index) {
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
this.handleClick(index)
this.clickTime = 0
this.clickIndex = -1
},
handleClick(index) {
let info = this.list[index]
if (info.isComplate) {
this.handlePlay(index)
} else if (info.isDownloading) {
this.pauseTask(index)
} else {
this.startTask(index)
}
},
handlePlay(index) {
if (!checkPath(this.list[index].filePath)) return
this.setList({ list: this.list, listId: 'download', index })
},
handleBtnClick(info) {
switch (info.action) {
case 'play':
this.handlePlay(info.index)
break
case 'remove':
this.removeTask(info.index)
break
}
},
},
}
</script>
<style lang="less" module>
@import '../assets/styles/layout.less';
.download {
overflow: hidden;
height: 100%;
// .noItem {
// }
}
.content {
height: 100%;
font-size: 14px;
display: flex;
flex-flow: column nowrap;
// table {
// position: relative;
// thead {
// position: fixed;
// width: 100%;
// th {
// width: 100%;
// }
// }
// }
}
.thead {
flex: none;
}
.tbody {
flex: auto;
overflow-y: auto;
td {
font-size: 12px;
}
tr {
&.active {
color: @color-theme;
}
}
}
each(@themes, {
:global(#container.@{value}) {
.tbody {
tr {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
}
}
})
</style>
+228
View File
@@ -0,0 +1,228 @@
<template lang="pug">
div(:class="$style.leaderboard")
div(:class="$style.header")
material-tab(:class="$style.tab" :list="types" item-key="id" item-name="name" v-model="tabId")
material-select(:class="$style.select" :list="sourceInfo.sources" item-key="id" item-name="name" v-model="source")
div(:class="$style.content")
div(v-if="list.length" :class="$style.list")
div(:class="$style.thead")
table
thead
tr
th.nobreak(style="width: 27%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
th.nobreak(style="width: 25%;") 专辑
th.nobreak(style="width: 18%;") 操作
th.nobreak(style="width: 10%;") 时长
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)")
td.break(style="width: 27%;")
| {{item.name}}
//- span.badge.badge-info(v-if="item._types['320k']") 高品质
//- span.badge.badge-success(v-if="item._types.ape || item._types.flac") 无损
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 25%;") {{item.albumName}}
td(style="width: 18%;")
material-list-buttons(:index="index" :search-btn="true" :play-btn="item.source == 'kw'" :download-btn="item.source == 'kw'" :remove-btn="false" @btn-click="handleBtnClick")
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
//- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)') +
td(style="width: 10%;") {{item.interval}}
div(:class="$style.pagination")
material-pagination(:count="info.total" :limit="info.limit" :page="info.page" @btn-click="handleTogglePage")
material-download-modal(:show="showDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="showDownload = false")
</template>
<script>
import { mapGetters, mapMutations, mapActions } from 'vuex'
import { scrollTo } from '../utils'
// import music from '../utils/music'
export default {
name: 'Leaderboard',
data() {
return {
tabId: null,
source: null,
page: 1,
clickTime: 0,
clickIndex: -1,
showDownload: false,
musicInfo: null,
}
},
computed: {
...mapGetters(['setting']),
...mapGetters('leaderboard', ['sourceInfo', 'list', 'info']),
...mapGetters('list', ['defaultList']),
types() {
return this.source ? this.sourceInfo.sourceList[this.source] : []
},
},
watch: {
tabId(n, o) {
this.setLeaderboard({ tabId: n })
if (!o && this.page !== 1) return
this.getList(1).then(() => {
this.page = this.info.page
scrollTo(this.$refs.dom_scrollContent, 0)
})
},
source(n, o) {
this.setLeaderboard({ source: n })
if (o) this.tabId = this.types[0] && this.types[0].id
},
},
mounted() {
this.source = this.setting.leaderboard.source
this.tabId = this.setting.leaderboard.tabId
this.page = this.info.page
},
methods: {
...mapMutations(['setLeaderboard']),
...mapActions('leaderboard', ['getList']),
...mapActions('download', ['createDownload']),
...mapMutations('list', ['defaultListAdd']),
...mapMutations('player', ['setList']),
handleDoubleClick(index) {
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
this.source == 'kw' ? this.testPlay(index) : this.handleSearch(index)
this.clickTime = 0
this.clickIndex = -1
},
handleBtnClick(info) {
switch (info.action) {
case 'download':
this.musicInfo = this.list[info.index]
this.$nextTick(() => {
this.showDownload = true
})
break
case 'play':
this.testPlay(info.index)
break
case 'search':
this.handleSearch(info.index)
break
// case 'add':
// break
}
},
testPlay(index) {
let targetSong = this.list[index]
this.defaultListAdd(targetSong)
let targetIndex = this.defaultList.list.findIndex(
s => s.songmid === targetSong.songmid
)
if (targetIndex > -1) {
this.setList({
list: this.defaultList.list,
listId: 'test',
index: targetIndex,
})
}
},
handleSearch(index) {
const info = this.list[index]
this.$router.push({
path: 'search',
query: {
text: `${info.name} - ${info.singer}`,
},
})
},
handleTogglePage(page) {
this.getList(page).then(() => {
this.page = this.info.page
this.$nextTick(() => {
scrollTo(this.$refs.dom_scrollContent, 0)
})
})
},
handleAddDownload(type) {
this.createDownload({ musicInfo: this.musicInfo, type })
this.showDownload = false
},
},
}
</script>
<style lang="less" module>
@import '../assets/styles/layout.less';
.leaderboard {
height: 100%;
display: flex;
flex-flow: column nowrap;
}
.header {
flex: none;
width: 100%;
display: flex;
flex-flow: row nowrap;
}
.tab {
flex: auto;
}
.select {
flex: none;
width: 80px;
}
.content {
flex: auto;
display: flex;
overflow: hidden;
flex-flow: column nowrap;
}
.list {
position: relative;
height: 100%;
font-size: 14px;
display: flex;
flex-flow: column nowrap;
// table {
// position: relative;
// thead {
// position: fixed;
// width: 100%;
// th {
// width: 100%;
// }
// }
// }
}
.thead {
flex: none;
}
.tbody {
flex: auto;
overflow-y: auto;
td {
font-size: 12px;
:global(.badge) {
margin-right: 3px;
&:first-child {
margin-left: 3px;
}
&:last-child {
margin-right: 0;
}
}
}
}
.pagination {
text-align: center;
padding: 15px 0;
// left: 50%;
// transform: translateX(-50%);
}
</style>
+192
View File
@@ -0,0 +1,192 @@
<template lang="pug">
div(:class="$style.list")
//- transition
div(v-if="list.length" :class="$style.content")
div(:class="$style.thead")
table
thead
tr
th.nobreak(style="width: 25%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
th.nobreak(style="width: 25%;" v-if="setting.list.isShowAlbumName") 专辑
th.nobreak(style="width: 20%;") 操作
th.nobreak(style="width: 10%;") 时长
div.scroll(:class="$style.tbody")
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)" :class="isPlayList && playIndex === index ? $style.active : ''")
td.break(style="width: 25%;") {{item.name}}
//- span.badge.badge-light(v-if="item._types['128k']") 128K
//- span.badge.badge-light(v-if="item._types['192k']") 192K
//- span.badge.badge-secondary(v-if="item._types['320k']") 320K
//- span.badge.badge-info(v-if="item._types.ape") APE
//- span.badge.badge-success(v-if="item._types.flac") FLAC
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 25%;" v-if="setting.list.isShowAlbumName") {{item.albumName}}
td(style="width: 20%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" @btn-click="handleBtnClick")
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
//- button.btn-secondary(type='button' @click.stop='handleRemove(index)') 删除
//- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)') +
td(style="width: 10%;") {{item.interval}}
div(:class="$style.noItem" v-else)
material-download-modal(:show="showDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="showDownload = false")
</template>
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
export default {
name: 'List',
data() {
return {
clickTime: window.performance.now(),
clickIndex: -1,
showDownload: false,
musicInfo: null,
}
},
computed: {
...mapGetters(['userInfo', 'setting']),
...mapGetters('list', ['defaultList', 'userList']),
...mapGetters('player', ['listId', 'playIndex']),
isPlayList() {
return this.listId != 'download' && (
((!this.$route.query.id || this.$route.query.id == 'test') && this.listId == 'test') ||
this.$route.query.id == this.listId
)
},
list() {
return !this.$route.query.id || this.$route.query.id == 'test'
? this.defaultList.list
: this.userList.find(l => l._id == this.$route.query.id) || []
},
},
// beforeRouteUpdate(to, from, next) {
// // if (to.query.id === undefined) return
// // if (to.query.text === '') {
// // this.clearList()
// // } else {
// // this.text = to.query.text
// // this.page = 1
// // this.handleSearch(this.text, this.page)
// // }
// next()
// },
// mounted() {
// console.log('mounted')
// if (this.$route.query.text === undefined) {
// let info = this.$store.getters['search/info']
// this.text = info.text
// this.page = info.page
// } else if (this.$route.query.text === '') {
// this.clearList()
// } else {
// this.text = this.$route.query.text
// this.page = 1
// this.handleSearch(this.text, this.page)
// }
// },
methods: {
...mapMutations('list', ['defaultListRemove']),
...mapMutations('player', ['setList']),
...mapActions('download', ['createDownload']),
handleDoubleClick(index) {
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
this.testPlay(index)
this.clickTime = 0
this.clickIndex = -1
},
testPlay(index) {
this.setList({ list: this.list, listId: 'test', index })
},
handleRemove(index) {
this.defaultListRemove(index)
},
handleBtnClick(info) {
switch (info.action) {
case 'download':
this.musicInfo = this.list[info.index]
this.$nextTick(() => {
this.showDownload = true
})
break
case 'play':
this.testPlay(info.index)
break
case 'add':
break
case 'remove':
this.handleRemove(info.index)
break
}
},
handleAddDownload(type) {
this.createDownload({ musicInfo: this.musicInfo, type })
this.showDownload = false
},
},
}
</script>
<style lang="less" module>
@import '../assets/styles/layout.less';
.list {
overflow: hidden;
height: 100%;
// .noItem {
// }
}
.content {
height: 100%;
font-size: 14px;
display: flex;
flex-flow: column nowrap;
// table {
// position: relative;
// thead {
// position: fixed;
// width: 100%;
// th {
// width: 100%;
// }
// }
// }
}
.thead {
flex: none;
}
.tbody {
flex: auto;
overflow-y: auto;
td {
font-size: 12px;
}
tr {
&.active {
color: @color-theme;
}
}
}
each(@themes, {
:global(#container.@{value}) {
.tbody {
tr {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
}
}
})
</style>
+20
View File
@@ -0,0 +1,20 @@
<template lang="pug">
div
h2 推荐
</template>
<script>
export default {
name: 'About',
data() {
return {
count: 0,
}
},
methods: {
add() {
this.count++
},
},
}
</script>
+189
View File
@@ -0,0 +1,189 @@
<template lang="pug">
div(:class="$style.search")
//- transition
div(v-if="list.length" :class="$style.list")
div(:class="$style.thead")
table
thead
tr
th.nobreak(style="width: 30%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
th.nobreak(style="width: 25%;") 专辑
th.nobreak(style="width: 15%;") 操作
th.nobreak(style="width: 10%;") 时长
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)")
td.break(style="width: 30%;")
| {{item.name}}
span.badge.badge-info(v-if="item._types['320k']") 高品质
span.badge.badge-success(v-if="item._types.ape || item._types.flac") 无损
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 25%;") {{item.albumName}}
td(style="width: 15%;")
material-list-buttons(:index="index" :remove-btn="false" @btn-click="handleBtnClick")
td(style="width: 10%;") {{item.interval}}
div(:class="$style.pagination")
material-pagination(:count="listInfo.total" :limit="limit" :page="page" @btn-click="handleTogglePage")
div(v-else :class="$style.noItem")
material-download-modal(:show="showDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="showDownload = false")
</template>
<script>
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { scrollTo } from '../utils'
// import music from '../utils/music'
export default {
name: 'Search',
data() {
return {
page: 0,
text: '',
clickTime: 0,
clickIndex: -1,
showDownload: false,
musicInfo: null,
}
},
beforeRouteUpdate(to, from, next) {
if (to.query.text === undefined) return
if (to.query.text === '') {
this.clearList()
} else {
this.text = to.query.text
this.page = 1
this.handleSearch(this.text, this.page)
}
next()
},
mounted() {
// console.log('mounted')
if (this.$route.query.text === undefined) {
let info = this.$store.getters['search/info']
this.text = info.text
this.page = info.page
} else if (this.$route.query.text === '') {
this.clearList()
} else {
this.text = this.$route.query.text
this.page = 1
this.handleSearch(this.text, this.page)
}
},
computed: {
...mapGetters(['userInfo']),
...mapGetters('search', ['list', 'limit', 'listInfo']),
...mapGetters('list', ['defaultList']),
},
methods: {
...mapActions('search', ['search']),
...mapActions('download', ['createDownload']),
...mapMutations('search', ['clearList', 'setPage']),
...mapMutations('list', ['defaultListAdd']),
...mapMutations('player', ['setList']),
handleSearch(text, page) {
this.search({ text, page, limit: this.limit }).then(data => {
this.page = page
this.$nextTick(() => {
scrollTo(this.$refs.dom_scrollContent, 0)
})
})
},
handleDoubleClick(index) {
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
this.testPlay(index)
this.clickTime = 0
this.clickIndex = -1
},
handleBtnClick(info) {
switch (info.action) {
case 'download':
this.musicInfo = this.list[info.index]
this.$nextTick(() => {
this.showDownload = true
})
break
case 'play':
this.testPlay(info.index)
break
case 'add':
break
}
},
openDownloadModal() {},
testPlay(index) {
let targetSong = this.list[index]
this.defaultListAdd(targetSong)
let targetIndex = this.defaultList.list.findIndex(
s => s.songmid === targetSong.songmid
)
if (targetIndex > -1) {
this.setList({
list: this.defaultList.list,
listId: 'test',
index: targetIndex,
})
}
},
handleTogglePage(page) {
this.handleSearch(this.text, page)
},
handleAddDownload(type) {
this.createDownload({ musicInfo: this.musicInfo, type })
this.showDownload = false
},
},
}
</script>
<style lang="less" module>
@import '../assets/styles/layout.less';
.search {
overflow: hidden;
height: 100%;
}
.list {
position: relative;
height: 100%;
font-size: 14px;
display: flex;
flex-flow: column nowrap;
}
.thead {
flex: none;
}
.tbody {
flex: auto;
overflow-y: auto;
td {
font-size: 12px;
:global(.badge) {
margin-right: 3px;
&:first-child {
margin-left: 3px;
}
&:last-child {
margin-right: 0;
}
}
}
}
.pagination {
text-align: center;
padding: 15px 0;
// left: 50%;
// transform: translateX(-50%);
}
// .noItem {
// }
</style>
+445
View File
@@ -0,0 +1,445 @@
<template lang="pug">
div.scroll(:class="$style.setting")
dl
dt 基本设置
dd
h3 主题颜色
div
ul(:class="$style.theme")
li(v-for="theme in themes.list" :key="theme.id" @click="current_setting.themeId = theme.id" :class="[theme.class, themes.active == theme.id ? $style.active : '']")
span
| {{theme.name}}
dd(title='弹出层的动画效果')
h3 弹出层随机动画
div
material-checkbox(id="setting_animate" v-model="current_setting.randomAnimate") 是否启用
dt 播放设置
dd(title="都不选时播放完当前歌曲就停止播放")
h3 歌曲切换方式
div
material-checkbox(:id="`setting_player_togglePlay_${item.value}`" :class="$style.gap" :target="item.value" :key="item.value"
v-model="current_setting.player.togglePlayMethod" v-for="item in togglePlayMethods") {{item.name}}
dd(title='启用时将优先播放320K品质的歌曲')
h3 优先播放高品质音乐
div
material-checkbox(id="setting_player_highQuality" v-model="current_setting.player.highQuality") 是否启用
dt 下载设置
dd(title='下载歌曲保存的路径')
h3 下载路径
div
p
| 当前下载路径:
span.auto-hidden.hover(title="点击打开当前路径" :class="$style.savePath" @click="handleOpenDir(current_setting.download.savePath)") {{current_setting.download.savePath}}
p
material-btn(:class="$style.btn" min @click="handleChangeSavePath") 更改
dd(title='下载歌曲时的命名方式')
h3 文件命名方式
div
material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gap" name="setting_download_musicName" :target="item.value" :key="item.value" need
v-model="current_setting.download.fileName" v-for="item in musicNames") {{item.name}}
dt 列表设置
dd(title='播放列表是否显示专辑栏')
h3 专辑栏
div
material-checkbox(id="setting_list_showalbum" v-model="current_setting.list.isShowAlbumName") 是否显示专辑栏
dt 备份与恢复
dd
h3 部分数据
div
material-btn(:class="[$style.btn, $style.gap]" min @click="handleImportPlayList") 导入列表
material-btn(:class="[$style.btn, $style.gap]" min @click="handleExportPlayList") 导出列表
material-btn(:class="[$style.btn, $style.gap]" min @click="handleImportSetting") 导入设置
material-btn(:class="[$style.btn, $style.gap]" min @click="handleExportSetting") 导出设置
dd
h3 所有数据(设置与试听列表)
div
material-btn(:class="[$style.btn, $style.gap]" min @click="handleImportAllData") 导入
material-btn(:class="[$style.btn, $style.gap]" min @click="handleExportAllData") 导出
dt 关于洛雪音乐
dd
p
small 当前版本:
| {{version.version}}
p
small By:
| 落雪无痕
p
small
| 本软件仅用于学习交流使用,禁止将本软件用于
strong 非法用途
| 或
strong 商业用途
| 。
p
small
| 使用本软件造成的一切后果由
strong 使用者
| 承担!
p
small
| 本软件的部分接口使用自 https://github.com/messoer ,非常感谢
strong @messoer
| !
p
small 若有问题可 mail to:lyswhut@qq.com
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { openDirInExplorer, openSelectDir, openSaveDir, updateSetting } from '../utils'
import fs from 'fs'
export default {
name: 'Setting',
computed: {
...mapGetters(['setting', 'themes', 'version']),
...mapGetters('list', ['defaultList']),
},
data() {
return {
current_setting: {
version: null,
player: {
togglePlayMethod: 'random',
highQuality: false,
},
list: {
isShowAlbumName: true,
},
download: {
savePath: 'C:\\',
fileName: '歌名 - 歌手',
},
themeId: 0,
sourceId: 0,
randomAnimate: true,
},
togglePlayMethods: [
{
name: '列表循环',
value: 'listLoop',
},
{
name: '列表随机',
value: 'random',
},
{
name: '顺序播放',
value: 'list',
},
{
name: '单曲循环',
value: 'singleLoop',
},
],
musicNames: [
{
name: '歌名 - 歌手',
value: '歌名 - 歌手',
},
{
name: '歌手 - 歌名',
value: '歌手 - 歌名',
},
{
name: '歌名',
value: '歌名',
},
],
}
},
watch: {
current_setting: {
handler(n, o) {
if (!o.version) return
this.setSetting(JSON.parse(JSON.stringify(n)))
},
deep: true,
},
},
mounted() {
this.init()
},
methods: {
...mapMutations(['setSetting']),
...mapMutations('list', ['setDefaultList']),
init() {
this.current_setting = JSON.parse(JSON.stringify(this.setting))
},
handleChangeSavePath() {
openSelectDir({
title: '选择歌曲保存路径',
defaultPath: this.current_setting.download.savePath,
properties: ['openDirectory'],
}).then(result => {
if (result.canceled) return
this.current_setting.download.savePath = result.filePaths[0]
})
},
handleOpenDir(dir) {
openDirInExplorer(dir)
},
importSetting(path) {
let setting
try {
setting = JSON.parse(fs.readFileSync(path, 'utf8'))
} catch (error) {
return
}
if (setting.type !== 'setting') return
this.setSetting(updateSetting(setting.data))
this.init()
},
exportSetting(path) {
console.log(path)
const data = {
type: 'setting',
data: this.setting,
}
fs.writeFile(path, JSON.stringify(data, null, 2), 'utf8', err => {
console.log(err)
})
},
importPlayList(path) {
let defautlList
try {
defautlList = JSON.parse(fs.readFileSync(path, 'utf8'))
} catch (error) {
return
}
if (defautlList.type !== 'defautlList') return
this.setDefaultList(defautlList.data.list)
},
exportPlayList(path) {
const data = {
type: 'defautlList',
data: this.defaultList,
}
fs.writeFile(path, JSON.stringify(data, null, 2), 'utf8', err => {
console.log(err)
})
},
importAllData(path) {
let allData
try {
allData = JSON.parse(fs.readFileSync(path, 'utf8'))
} catch (error) {
return
}
if (allData.type !== 'allData') return
this.setSetting(updateSetting(allData.setting))
this.init()
this.setDefaultList(allData.defaultList.list)
},
exportAllData(path) {
let allData = {
type: 'allData',
setting: this.setting,
defaultList: this.defaultList,
}
fs.writeFile(path, JSON.stringify(allData, null, 2), 'utf8', err => {
console.log(err)
})
},
handleImportAllData() {
openSelectDir({
title: '选择备份文件',
properties: ['openFile'],
filters: [
{ name: 'Setting', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] },
],
}).then(result => {
if (result.canceled) return
this.importAllData(result.filePaths[0])
})
},
handleExportAllData() {
openSaveDir({
title: '选择备份保存位置',
defaultPath: 'lx_datas.json',
}).then(result => {
if (result.canceled) return
this.exportAllData(result.filePath)
})
},
handleImportSetting() {
openSelectDir({
title: '选择配置文件',
properties: ['openFile'],
filters: [
{ name: 'Setting', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] },
],
}).then(result => {
if (result.canceled) return
this.importSetting(result.filePaths[0])
})
},
handleExportSetting() {
openSaveDir({
title: '选择设置保存位置',
defaultPath: 'lx_setting.json',
}).then(result => {
if (result.canceled) return
this.exportSetting(result.filePath)
})
},
handleImportPlayList() {
openSelectDir({
title: '选择列表文件',
properties: ['openFile'],
filters: [
{ name: 'Play List', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] },
],
}).then(result => {
if (result.canceled) return
this.importPlayList(result.filePaths[0])
})
},
handleExportPlayList() {
openSaveDir({
title: '选择设置保存位置',
defaultPath: 'lx_list.json',
}).then(result => {
if (result.canceled) return
this.exportPlayList(result.filePath)
})
},
},
}
</script>
<style lang="less" module>
@import '../assets/styles/layout.less';
.setting {
padding: 0 15px 30px 15px;
font-size: 14px;
box-sizing: border-box;
overflow-y: auto;
height: 100%;
dt {
border-left: 5px solid @color-theme;
padding: 3px 7px;
margin: 30px 0 15px;
+ dd h3 {
margin-top: 0;
}
}
dd {
margin-left: 15px;
// font-size: 13px;
> div {
padding: 0 15px;
}
}
h3 {
font-size: 12px;
margin: 25px 0 15px;
}
p {
padding: 3px 0;
.btn {
+ .btn {
margin-left: 10px;
}
}
}
}
.gap {
+ .gap {
margin-left: 20px;
}
}
.theme {
display: flex;
// padding: 0 15px;
li {
display: flex;
flex-flow: column nowrap;
align-items: center;
cursor: pointer;
// color: @color-theme;
margin-right: 30px;
transition: color .3s ease;
&:last-child {
margin-right: 0;
}
span {
display: block;
width: 36px;
height: 36px;
margin-bottom: 5px;
border: 2px solid transparent;
padding: 2px;
transition: border-color .3s ease;
border-radius: 5px;
&:after {
display: block;
content: ' ';
width: 100%;
height: 100%;
border-radius: 4px;
}
}
each(@themes, {
&:global(.@{value}) {
span {
&:after {
background-color: ~'@{color-@{value}-theme}';
}
}
}
})
}
}
.save-path {
font-size: 12px;
}
each(@themes, {
:global(#container.@{value}) {
.tbody {
tr {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
}
.setting {
dt {
border-left-color: ~'@{color-@{value}-theme}';
}
}
.theme {
display: flex;
li {
&.active {
&:global(.@{value}) {
color: ~'@{color-@{value}-theme}';
span {
border-color: ~'@{color-@{value}-theme}';
}
}
}
}
}
}
})
</style>