🍙 清空项目文件 🍚

This commit is contained in:
Hunlongyu
2020-10-10 17:10:29 +08:00
parent 5f3a55e3a5
commit 780de8d426
167 changed files with 0 additions and 60048 deletions

View File

@@ -1,142 +0,0 @@
<template>
<div id="app" :class="appTheme">
<Aside />
<div class="zy-body">
<Frame />
<Film v-show="view === 'Film'" />
<Play v-show="view === 'Play'" />
<IPTV v-show="view === 'IPTV'" />
<Star v-show="view === 'Star'" />
<History v-show="view === 'History'" />
<Setting v-show="view === 'Setting'" />
</div>
<transition name="slide">
<Detail v-if="detail.show"/>
</transition>
<transition name="slide">
<Share v-if="share.show"/>
</transition>
<transition name="slide">
<EditSites v-if="editSites.show"/>
</transition>
</div>
</template>
<script>
import { setting } from './lib/dexie'
const { remote } = require('electron')
export default {
name: 'App',
data () {
return {
appTheme: 'theme-light',
winSizePosition: {
x: 0,
y: 0,
width: 0,
height: 0
}
}
},
created () {
// 窗口创建口,检查是否有窗口大小位置的记录,如果有的话,更新窗口位置及大小
setting.find().then(res => {
if (res.windowSizePosition) {
var win = remote.getCurrentWindow()
win.setBounds({
x: res.windowSizePosition.x,
y: res.windowSizePosition.y,
width: res.windowSizePosition.width,
height: res.windowSizePosition.height
})
}
})
var win = remote.getCurrentWindow()
this.winSizePosition = {
x: win.getPosition()[0],
y: win.getPosition()[1],
width: win.getSize()[0],
height: win.getSize()[1]
}
},
updated () {
// 本来想hook up到beforedestroy 但不工作
// 每当窗口更新时检查窗口大小及位置记录到setting数据库中
const win = remote.getCurrentWindow()
var newWinSizePosition = {
x: win.getPosition()[0],
y: win.getPosition()[1],
width: win.getSize()[0],
height: win.getSize()[1]
}
if (newWinSizePosition.x !== this.winSizePosition.x ||
newWinSizePosition.y !== this.winSizePosition.y ||
newWinSizePosition.width !== this.winSizePosition.width ||
newWinSizePosition.height !== this.winSizePosition.height) {
this.winSizePosition = newWinSizePosition
setting.find().then(res => {
res.windowSizePosition = newWinSizePosition
setting.update(res)
})
}
},
computed: {
view () {
return this.$store.getters.getView
},
detail () {
return this.$store.getters.getDetail
},
share () {
return this.$store.getters.getShare
},
setting () {
return this.$store.getters.getSetting
},
editSites () {
return this.$store.getters.getEditSites
}
},
watch: {
setting: {
handler () {
this.changeSetting()
},
deep: true
}
},
methods: {
changeSetting () {
this.appTheme = `theme-${this.setting.theme}`
}
}
}
</script>
<style lang="scss">
@import './assets/scss/theme.scss';
html, body, #app{
height: 100%;
border-radius: 0px;
}
#app {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: flex-start;
.zy-body{
flex: 1;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
padding: 0 20px 20px;
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -1,236 +0,0 @@
// svg
.zy-svg{
display: flex;
justify-content: center;
align-items: center;
svg{
width: 24px;
height: 24px;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
// select
.zy-select{
position: relative;
display: inline-block;
width: 200px;
height: 30px;
cursor: pointer;
border-radius: 3px;
user-select: none;
vertical-align: middle;
.vs-placeholder{
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 25px;
padding-right: 25px;
&::after{
display: inline-block;
margin-left: .255em;
vertical-align: .255em;
content: "";
border-top: .3em solid;
border-right: .3em solid transparent;
border-bottom: 0;
border-left: .3em solid transparent;
}
}
.vs-input{
height: 30px;
input{
border: none;
width: 200px;
height: 30px;
text-indent: 22px;
background-color: #ffffff00;
outline: none;
}
}
.vs-noAfter{
&::after{
display: none;
}
}
.vs-options{
z-index: 2;
width: 100%;
overflow-y: auto;
min-height: 0;
ul{
padding: 0;
margin: 0;
list-style: none;
li{
width: 100%;
height: 30px;
display: flex;
justify-content: flex-start;
align-items: center;
padding-left: 25px;
}
}
}
}
.zy-input{
position: relative;
display: inline-block;
white-space: nowrap;
width: 200px;
height: 30px;
cursor: pointer;
input{
vertical-align: bottom;
position: relative;
border: none;
width: 20px;
height: 15px;
background-color: #ffffff00;
text-indent: 10px;
}
}
.zy-highlighted{
color: var(--highlight-color);
}
// table
.zy-table{
display: flex;
flex-direction: column;
height: 100%;
font-size: 15px;
.tHeader{
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
min-height: 50px;
width: 100%;
border-bottom: 1px solid;
.btn{
user-select: none;
margin-left: 15px;
margin-right: 15px;
cursor: pointer;
font-size: 14px;
}
}
.tBody{
flex: 1;
border-bottom: 1px solid;
overflow: auto;
ul{
list-style: none;
padding: 0;
margin: 0;
li{
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: row;
height: 50px;
border-bottom: 1px solid;
cursor: pointer;
span{
display: flex;
font-size: 13px;
height: 50px;
line-height: 50px;
overflow: hidden;
margin-right: 5px;
&.name{
flex: 1;
min-width: 100px;
white-space: nowrap;
margin-left: 10px;
}
&.type{
width: 10%;
}
&.time{
width: 10%;
}
&.last{
width: 10%;
}
&.site{
width: 10%;
}
&.note{
width: 10%;
}
&.operate{
.btn{
width: 40px;
}
}
}
}
}
}
}
// scroll
.zy-scroll{
&::-webkit-scrollbar{
width: 5px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
position: absolute;
}
&::-webkit-scrollbar-track {
border-radius: 10px;
position: absolute;
}
}
// loading
.zy-loading{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.loader {
font-size: 8px;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
text-indent: -9999em;
animation: load4 1.3s infinite linear;
transform: translateZ(0);
}
@keyframes load4 {
0%,
100% {
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
}

View File

@@ -1,82 +0,0 @@
:root{
// general
--highlight-color: #38dd77;
// light
--l-c-0: #823aa0;
--l-c-1: #823aa011;
--l-c-2: #823aa022;
--l-c-3: #823aa033;
--l-c-5: #823aa055;
--l-c-8: #823aa088;
--l-c-9: #823aa099;
--l-fc-1: #808695;
--l-fc-2: #332f5c;
--l-fc-3: #823aa0;
--l-bgc-1: #ffffff;
--l-bgc-2: #f2f6f9;
--l-bsc: 0 1px 3px #8e8da233, 0 1px 2px #8e8da244;
--l-bsc-hover: 0 14px 28px #8e8da255, 0 10px 10px #8e8da244;
--l-bsc-2: 0 -4px 23px 0 #8e8da233;
--l-bsc-scroll: inset 0 0 5px #823aa000;
// dark
--d-c-0: #38dd77;
--d-c-1: #38dd7711;
--d-c-2: #38dd7722;
--d-c-3: #38dd7733;
--d-c-5: #38dd7755;
--d-c-8: #38dd7788;
--d-c-9: #38dd7799;
--d-fc-1: #808695;
--d-fc-2: #acacac;
--d-fc-3: #38dd77;
--d-bgc-1: #222222;
--d-bgc-2: #2f2f2f;
--d-bsc: 0 1px 3px #38dd7733, 0 1px 2px #38dd7744;
--d-bsc-hover: 0 14px 28px #38dd7755, 0 10px 10px #38dd7744;
--d-bsc-2: 0 -4px 23px 0 #38dd7733;
--d-bsc-scroll: inset 0 0 5px #38dd7705;
// green
--g-c-0: #EAEF9D;
--g-c-1: #EAEF9D11;
--g-c-2: #EAEF9D22;
--g-c-3: #EAEF9D33;
--g-c-5: #EAEF9D55;
--g-c-8: #EAEF9D88;
--g-c-9: #EAEF9D99;
--g-fc-1: #ffffff;
--g-fc-2: #d2dedc;
--g-fc-3: #C1D95C;
--g-bgc-1: #4baea0;
--g-bgc-2: #74b4ac;
--g-bsc: 0 1px 3px #e1ebe033, 0 1px 2px #e1ebe044;
--g-bsc-hover: 0 14px 28px #e1ebe055, 0 10px 10px #e1ebe044;
--g-bsc-2: 0 -4px 23px 0 #e1ebe033;
--g-bsc-scroll: inset 0 0 5px #e1ebe005;
// pink
--p-c-0: #f4f7f7;
--p-c-1: #f4f7f711;
--p-c-2: #f4f7f722;
--p-c-3: #f4f7f733;
--p-c-5: #f4f7f755;
--p-c-8: #f4f7f788;
--p-c-9: #f4f7f799;
--p-fc-1: #ffffff;
--p-fc-2: #FFFFF3;
--p-fc-3: #f15c5c;
--p-bgc-1: #ff8499;
--p-bgc-2: #fea1b2;
--p-bsc: 0 1px 3px #ef528533, 0 1px 2px #ef528544;
--p-bsc-hover: 0 14px 28px #ef528555, 0 10px 10px #ef528544;
--p-bsc-2: 0 -4px 23px 0 #ef528533;
--p-bsc-scroll: inset 0 0 5px #ef528505;
}
@import './theme/light.scss';
@import './theme/dark.scss';
@import './theme/green.scss';
@import './theme/pink.scss';
@import './style.scss';

View File

@@ -1,356 +0,0 @@
.theme-dark{
background-color: var(--d-bgc-1);
.zy-select{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
}
.vs-options{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--d-c-1);
}
&.active{
background-color: var(--d-c-3);
}
}
}
}
.vs-input{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
input{
color: var(--d-fc-1);
&::-webkit-input-placeholder{
color: var(--d-fc-1);
}
}
}
}
.zy-input{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
input{
color: var(--d-fc-1);
}
}
.zy-checkbox{
color: var(--d-fc-1);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
}
.zy-table{
color: var(--d-fc-2);
.tHeader{
border-bottom-color: var(--d-c-3);
.btn{
&:hover{
color: var(--d-fc-3)
}
}
}
.tBody{
border-bottom-color: var(--d-c-3);
ul{
li{
border-bottom-color: var(--d-c-2);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
}
span{
&.btn:hover{
color: var(--d-fc-3)
}
}
}
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--d-bsc-scroll);
background: var(--d-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--d-bsc-scroll);
background: var(--d-bgc-1);
}
}
}
.zy-loading{
.loader{
color: var(--d-c-3);
}
}
.zy-body{
background-color: var(--d-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--d-c-0);
}
&:hover{
background-color: var(--d-c-2);
}
&.active{
background-color: var(--d-bgc-2);
svg{
stroke: var(--d-c-0);
stroke-width: 2;
fill: var(--d-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
&.top{
background-color: #f3bab7;
}
}
}
.detail{
color: var(--d-fc-1) !important;
background-color:var(--d-bgc-1);
box-shadow: var(--d-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--d-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--d-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8, .operate{
border-color: var(--d-c-2);
}
.operate{
span{
&:hover{
color: var(--d-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--d-c-5);
&:hover{
color: var(--d-fc-2);
background-color: var(--d-c-1);
border-color: var(--d-c-8);
}
}
}
}
}
}
}
.film{
.body{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.show-img{
color: var(--d-fc-1);
.card{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
}
}
}
}
}
.play{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.title{
color: var(--d-fc-1);
.right {
svg {
fill: var(--d-fc-1);
}
}
}
.box{
.more{
span{
svg{
stroke: var(--d-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--d-c-8);
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
&.active{
svg{
stroke: var(--d-c-9);
stroke-width: 2;
fill: var(--d-c-3);
}
}
&.last-tip {
color: var(--d-fc-1);
font-size: 14px;
}
}
}
}
.list{
border: 1px solid var(--d-c-3);
background-color: var(--d-bgc-2);
.list-top{
color: var(--d-fc-2);
.list-top-close{
svg{
stroke: var(--d-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--d-c-8);
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--d-fc-1);
&.active{
background-color: var(--d-c-2);
color: var(--d-fc-3);
}
&:hover{
background-color: var(--d-c-3);
}
}
}
.list-history{
li{
.title{
color: var(--d-fc-1);
}
&.active{
background-color: var(--d-c-2);
.title{
color: var(--d-fc-3);
}
}
&:hover{
background-color: var(--d-c-3);
.detail-delete{
display: inline-block;
color: var(--d-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--d-c-2);
}
}
}
}
}
}
}
.star{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
}
.setting{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.info{
a{
color: var(--d-fc-1);
&:hover{
color: var(--d-fc-2);
}
}
}
.view, .search, .shortcut, .site{
.title{
color: var(--d-fc-1);
}
}
.theme{
.title{
color: var(--d-fc-1);
}
.theme-item{
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
.theme-name{
color: var(--d-fc-2)
}
}
.theme-name{
color: var(--d-fc-1);
}
}
}
.qrcode{
.title{
color: var(--d-fc-1);
}
.qrcode-item{
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
}
}
}
}
.share{
background-color: var(--d-bgc-1);
color: var(--d-fc-1);
border: 1px solid var(--d-c-8);
.right{
color: var(--d-fc-1);
.tops{
color: var(--d-fc-2);
}
}
.share-mask{
background-color: var(--d-bgc-1);
}
}
.history{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
}
}

View File

@@ -1,354 +0,0 @@
.theme-green{
background-color: var(--g-bgc-1);
.zy-select{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
}
.vs-options{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--g-c-1);
}
&.active{
background-color: var(--g-c-3);
}
}
}
}
.vs-input{
input{
color: var(--g-fc-1);
&::-webkit-input-placeholder{
color: var(--g-fc-1);
}
}
}
}
.zy-input{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
input{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
}
}
.zy-checkbox{
color: var(--g-fc-1);
}
.zy-table{
color: var(--g-fc-2);
.tHeader{
border-bottom-color: var(--g-c-3);
.btn{
&:hover{
color: var(--g-fc-3)
}
}
}
.tBody{
border-bottom-color: var(--g-c-3);
ul{
li{
border-bottom-color: var(--g-c-2);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
}
span{
&.btn:hover{
color: var(--g-fc-3)
}
}
}
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--g-bsc-scroll);
background: var(--g-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--g-bsc-scroll);
background: var(--g-bgc-1);
}
}
}
.zy-loading{
.loader{
color: var(--g-c-3);
}
}
.zy-body{
background-color: var(--g-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--g-c-0);
}
&:hover{
background-color: var(--g-c-2);
}
&.active{
background-color: var(--g-bgc-2);
svg{
stroke: var(--g-c-0);
stroke-width: 2;
fill: var(--g-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
&.top{
background-color: #f3bab7;
}
}
}
.detail{
color: var(--g-fc-1) !important;
background-color:var(--g-bgc-1);
box-shadow: var(--g-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--g-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--g-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8, .operate{
border-color: var(--g-c-2);
}
.operate{
span{
&:hover{
color: var(--g-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--g-c-5);
&:hover{
color: var(--g-fc-2);
background-color: var(--g-c-1);
border-color: var(--g-c-8);
}
}
}
}
}
}
}
.film{
.body{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
.show-img{
color: var(--g-fc-1);
.card{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
}
}
}
}
}
.play{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
.title{
color: var(--g-fc-1);
.right {
svg {
fill: var(--g-fc-1);
}
}
}
.box{
.more{
span{
svg{
stroke: var(--g-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--g-c-8);
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
&.active{
svg{
stroke: var(--g-c-9);
stroke-width: 2;
fill: var(--g-c-3);
}
}
&.last-tip {
color: var(--g-fc-1);
font-size: 14px;
}
}
}
}
.list{
border: 1px solid var(--g-c-3);
background-color: var(--g-bgc-2);
.list-top{
color: var(--g-fc-2);
.list-top-close{
svg{
stroke: var(--g-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--g-c-8);
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--g-fc-1);
&.active{
background-color: var(--g-c-2);
color: var(--g-fc-3);
}
&:hover{
background-color: var(--d-c-3);
}
}
}
.list-history{
li{
.title{
color: var(--g-fc-1);
}
&.active{
background-color: var(--g-c-2);
.title{
color: var(--g-fc-3);
}
}
&:hover{
background-color: var(--g-c-3);
.detail-delete{
display: inline-block;
color: var(--g-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--g-c-2);
}
}
}
}
}
}
}
.star{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
}
.setting{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
.info{
a{
color: var(--g-fc-1);
&:hover{
color: var(--g-fc-2);
}
}
}
.view, .search, .shortcut, .site{
.title{
color: var(--g-fc-1);
}
}
.theme{
.title{
color: var(--g-fc-1);
}
.theme-item{
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
.theme-name{
color: var(--g-fc-2)
}
}
.theme-name{
color: var(--g-fc-1);
}
}
}
.qrcode{
.title{
color: var(--g-fc-1);
}
.qrcode-item{
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
}
}
}
}
.share{
background-color: var(--g-bgc-1);
color: var(--g-fc-1);
border: 1px solid var(--g-c-8);
.right{
color: var(--g-fc-1);
.tops{
color: var(--g-fc-2);
}
}
.share-mask{
background-color: var(--g-bgc-1);
}
}
.history{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
}
}

View File

@@ -1,354 +0,0 @@
.theme-light{
background-color: var(--l-bgc-1);
.zy-select{
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
}
.vs-options{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--l-c-1);
}
&.active{
background-color: var(--l-c-3);
}
}
}
}
.vs-input{
input{
color: var(--l-fc-1);
&::-webkit-input-placeholder{
color: var(--l-fc-1);
}
}
}
}
.zy-input{
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
input{
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
}
}
.zy-checkbox{
color: var(--l-fc-1);
}
.zy-table{
color: var(--l-fc-2);
.tHeader{
border-bottom-color: var(--l-c-3);
.btn{
&:hover{
color: var(--l-fc-3)
}
}
}
.tBody{
border-bottom-color: var(--l-c-3);
ul{
li{
border-bottom-color: var(--l-c-2);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
}
span{
&.btn:hover{
color: var(--l-fc-3)
}
}
}
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--l-bsc-scroll);
background: var(--l-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--l-bsc-scroll);
background: var(--l-bgc-1);
}
}
}
.zy-loading{
.loader{
color: var(--l-c-3);
}
}
.zy-body{
background-color: var(--l-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--l-c-0);
}
&:hover{
background-color: var(--l-c-2);
}
&.active{
background-color: var(--l-bgc-2);
svg{
stroke: var(--l-c-0);
stroke-width: 2;
fill: var(--l-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
&.top{
background-color: #f3bab7;
}
}
}
.detail{
color: var(--l-fc-1) !important;
background-color:var(--l-bgc-1);
box-shadow: var(--l-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--l-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--l-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8, .operate{
border-color: var(--l-c-2);
}
.operate{
span{
&:hover{
color: var(--l-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--l-c-5);
&:hover{
color: var(--l-fc-2);
background-color: var(--l-c-1);
border-color: var(--l-c-8);
}
}
}
}
}
}
}
.film{
.body{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
.show-img{
color: var(--l-fc-1);
.card{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
}
}
}
}
}
.play{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
.title{
color: var(--l-fc-1);
.right {
svg {
fill: var(--l-fc-1);
}
}
}
.box{
.more{
span{
svg{
stroke: var(--l-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--l-c-8);
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
&.active{
svg{
stroke: var(--l-c-9);
stroke-width: 2;
fill: var(--l-c-3);
}
}
&.last-tip {
color: var(--l-fc-1);
font-size: 14px;
}
}
}
}
.list{
border: 1px solid var(--l-c-3);
background-color: var(--l-bgc-2);
.list-top{
color: var(--l-fc-2);
.list-top-close{
svg{
stroke: var(--l-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--l-c-8);
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--l-fc-1);
&.active{
background-color: var(--l-c-2);
color: var(--l-fc-3);
}
&:hover{
background-color: var(--d-c-3);
}
}
}
.list-history{
li{
.title{
color: var(--l-fc-1);
}
&.active{
background-color: var(--l-c-2);
.title{
color: var(--l-fc-3);
}
}
&:hover{
background-color: var(--l-c-3);
.detail-delete{
display: inline-block;
color: var(--l-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--l-c-2);
}
}
}
}
}
}
}
.star{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
}
.setting{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
.info{
a{
color: var(--l-fc-1);
&:hover{
color: var(--l-fc-2);
}
}
}
.view, .search, .shortcut, .site{
.title{
color: var(--l-fc-1);
}
}
.theme{
.title{
color: var(--l-fc-1);
}
.theme-item{
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
.theme-name{
color: var(--l-fc-2)
}
}
.theme-name{
color: var(--l-fc-1);
}
}
}
.qrcode{
.title{
color: var(--l-fc-1);
}
.qrcode-item{
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
}
}
}
}
.share{
background-color: var(--l-bgc-1);
color: var(--l-fc-1);
border: 1px solid var(--l-c-8);
.right{
color: var(--l-fc-1);
.tops{
color: var(--l-fc-2);
}
}
.share-mask{
background-color: var(--l-bgc-1);
}
}
.history{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
}
}

View File

@@ -1,353 +0,0 @@
.theme-pink{
background-color: var(--p-bgc-1);
.zy-select{
color: var(--p-fc-1);
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
}
.vs-options{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--p-c-1);
}
&.active{
background-color: var(--p-c-3);
}
}
}
}
.vs-input{
input{
color: var(--p-fc-1);
&::-webkit-input-placeholder{
color: var(--p-fc-1);
}
}
}
}
.zy-input{
color: var(--p-fc-1);
background-color: var(--p-bgc-1);
input{
color: var(--p-fc-1);
}
}
.zy-checkbox{
color: var(--p-fc-1);
}
.zy-table{
color: var(--p-fc-2);
.tHeader{
border-bottom-color: var(--p-c-3);
.btn{
&:hover{
color: var(--p-fc-3)
}
}
}
.tBody{
border-bottom-color: var(--p-c-3);
ul{
li{
border-bottom-color: var(--p-c-2);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
}
span{
&.btn:hover{
color: var(--p-fc-3)
}
}
}
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--p-bsc-scroll);
background: var(--p-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--p-bsc-scroll);
background: var(--p-bgc-1);
}
}
}
.zy-loading{
.loader{
color: var(--p-c-3);
}
}
.zy-body{
background-color: var(--p-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--p-c-0);
}
&:hover{
background-color: var(--p-c-2);
}
&.active{
background-color: var(--p-bgc-2);
svg{
stroke: var(--p-c-0);
stroke-width: 2;
fill: var(--p-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
&.top{
background-color: #f3bab7;
}
}
}
.detail{
color: var(--p-fc-1) !important;
background-color:var(--p-bgc-1);
box-shadow: var(--p-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--p-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--p-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8, .operate{
border-color: var(--p-c-2);
}
.operate{
span{
&:hover{
color: var(--p-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--p-c-5);
&:hover{
color: var(--p-fc-2);
background-color: var(--p-c-1);
border-color: var(--p-c-8);
}
}
}
}
}
}
}
.film{
.body{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
.show-img{
color: var(--p-fc-1);
.card{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
}
}
}
}
}
.play{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
.title{
color: var(--p-fc-1);
.right {
svg {
fill: var(--p-fc-1);
}
}
}
.box{
.more{
span{
svg{
stroke: var(--p-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--p-c-8);
stroke-width: 1.5;
fill: var(--p-c-2);
}
}
&.active{
svg{
stroke: var(--p-c-9);
stroke-width: 2;
fill: var(--p-c-3);
}
}
&.last-tip {
color: var(--p-fc-1);
font-size: 14px;
}
}
}
}
.list{
border: 1px solid var(--p-c-3);
background-color: var(--p-bgc-2);
.list-top{
color: var(--p-fc-2);
.list-top-close{
svg{
stroke: var(--p-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--p-c-8);
stroke-width: 1.5;
fill: var(--p-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--p-fc-1);
&.active{
background-color: var(--p-c-2);
color: var(--p-fc-3);
}
&:hover{
background-color: var(--d-c-3);
}
}
}
.list-history{
li{
.title{
color: var(--p-fc-1);
}
&.active{
background-color: var(--p-c-2);
.title{
color: var(--p-fc-3);
}
}
&:hover{
background-color: var(--p-c-3);
.detail-delete{
display: inline-block;
color: var(--p-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--p-c-2);
}
}
}
}
}
}
}
.star{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
}
.setting{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
.info{
a{
color: var(--p-fc-1);
&:hover{
color: var(--p-fc-2);
}
}
}
.view, .search, .shortcut, .site{
.title{
color: var(--p-fc-1);
}
}
.theme{
.title{
color: var(--p-fc-1);
}
.theme-item{
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
.theme-name{
color: var(--p-fc-2)
}
}
.theme-name{
color: var(--p-fc-1);
}
}
}
.qrcode{
.title{
color: var(--p-fc-1);
}
.qrcode-item{
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
}
}
}
}
.share{
background-color: var(--p-bgc-1);
color: var(--p-fc-1);
border: 1px solid var(--p-c-8);
.right{
color: var(--p-fc-1);
.tops{
color: var(--p-fc-2);
}
}
.share-mask{
background-color: var(--p-bgc-1);
}
}
.history{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
}
}

View File

@@ -1,140 +0,0 @@
'use strict'
import './lib/site/server'
import { app, protocol, BrowserWindow, globalShortcut, ipcMain } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// 允许跨域
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
let win
let mini
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
function createWindow () {
win = new BrowserWindow({
width: 1080,
height: 720,
frame: false,
resizable: true,
webPreferences: {
webSecurity: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
win.loadURL('app://./index.html')
}
win.on('closed', () => {
win = null
})
}
function createMini () {
mini = new BrowserWindow({
width: 550,
miniWidth: 860,
height: 340,
miniHeight: 180,
frame: false,
resizable: true,
webPreferences: {
webSecurity: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
mini.loadURL(process.env.WEBPACK_DEV_SERVER_URL + 'mini')
if (!process.env.IS_TEST) mini.webContents.openDevTools()
} else {
createProtocol('app')
mini.loadURL('app://./mini.html')
}
mini.on('closed', () => {
mini = null
})
}
if (process.platform === 'darwin') {
app.dock.show()
}
if (process.platform === 'Linux') {
app.disableHardwareAcceleration()
}
app.allowRendererProcessReuse = true
app.on('window-all-closed', () => {
app.quit()
})
app.on('activate', () => {
if (win === null) {
createWindow()
}
})
ipcMain.on('mini', () => {
createMini()
win.hide()
})
ipcMain.on('win', () => {
mini.destroy()
win.show()
win.webContents.send('miniClosed')
})
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
}
})
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
globalShortcut.register('Alt+Space', () => {
if (win) {
win.isFocused() ? win.blur() : win.focus()
}
if (mini) {
mini.isFocused() ? mini.blur() : mini.focus()
}
})
})
}
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}

View File

@@ -1,116 +0,0 @@
<template>
<div class="aside">
<span :class="[view === 'Film' ? 'active ': ''] + 'zy-svg'" @click="changeView('Film')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="apertureIconTitle">
<title id="apertureIconTitle">电影</title>
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"></path>
<g stroke-linecap="round">
<path d="M3 16H14.3164"></path>
<path d="M4.03589 6.20575L9.68257 15.9861"></path>
<path d="M13.0359 2.20575L7.37891 12.004"></path>
<path d="M10.9641 21.7942L16.6146 12.0074"></path>
<path d="M19.9641 17.7942L14.3086 7.99866"></path>
<path d="M21 7.98721H9.71844"></path>
</g>
</svg>
</span>
<span :class="[view === 'IPTV' ? 'active ': ''] + 'zy-svg'" @click="changeView('IPTV')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="tvIconTitle">
<title id="tvIconTitle">电视直播</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
<polyline stroke-linejoin="round" points="8 4 12 7.917 16 4"></polyline>
</svg>
</span>
<span :class="[view === 'Play' ? 'active ': ''] + 'zy-svg'" @click="changeView('Play')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="playIconTitle">
<title id="playIconTitle">播放</title>
<path d="M20 12L5 21V3z"></path>
</svg>
</span>
<span :class="[view === 'Star' ? 'active ': ''] + 'zy-svg'" @click="changeView('Star')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="favouriteIconTitle">
<title id="favouriteIconTitle">收藏</title>
<path d="M12,21 L10.55,19.7051771 C5.4,15.1242507 2,12.1029973 2,8.39509537 C2,5.37384196 4.42,3 7.5,3 C9.24,3 10.91,3.79455041 12,5.05013624 C13.09,3.79455041 14.76,3 16.5,3 C19.58,3 22,5.37384196 22,8.39509537 C22,12.1029973 18.6,15.1242507 13.45,19.7149864 L12,21 Z"></path>
</svg>
</span>
<span :class="[view === 'History' ? 'active ': ''] + 'zy-svg'" @click="changeView('History')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="timeIconTitle">
<title id="timeIconTitle">历史记录</title>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 5 12 12 16 16"></polyline>
</svg>
</span>
<span :class="[view === 'Setting' ? 'active ': ''] + 'zy-svg'" @click="changeView('Setting')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="settingsIconTitle">
<title id="settingsIconTitle">设置</title>
<path d="M5.03506429,12.7050339 C5.01187484,12.4731696 5,12.2379716 5,12 C5,11.7620284 5.01187484,11.5268304 5.03506429,11.2949661 L3.20577137,9.23205081 L5.20577137,5.76794919 L7.9069713,6.32070904 C8.28729123,6.0461342 8.69629298,5.80882212 9.12862533,5.61412402 L10,3 L14,3 L14.8713747,5.61412402 C15.303707,5.80882212 15.7127088,6.0461342 16.0930287,6.32070904 L18.7942286,5.76794919 L20.7942286,9.23205081 L18.9649357,11.2949661 C18.9881252,11.5268304 19,11.7620284 19,12 C19,12.2379716 18.9881252,12.4731696 18.9649357,12.7050339 L20.7942286,14.7679492 L18.7942286,18.2320508 L16.0930287,17.679291 C15.7127088,17.9538658 15.303707,18.1911779 14.8713747,18.385876 L14,21 L10,21 L9.12862533,18.385876 C8.69629298,18.1911779 8.28729123,17.9538658 7.9069713,17.679291 L5.20577137,18.2320508 L3.20577137,14.7679492 L5.03506429,12.7050339 Z"></path>
<circle cx="12" cy="12" r="1"></circle>
</svg>
</span>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name: 'Aside',
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
editSites: {
get () {
return this.$store.getters.getEditSites
},
set (val) {
this.SET_EDITSITES(val)
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_EDITSITES']),
changeView (e) {
this.view = e
// ChangeView 的时候关闭Detail和EditSites页面
this.detail = {
show: false
}
if (this.editSites.show === true) {
this.editSites = {
show: false
}
}
}
}
}
</script>
<style lang="scss" scoped>
.aside{
width: 60px;
height: 100%;
display: flex;
user-select: none;
align-items: center;
flex-direction: column;
justify-content: center;
-webkit-app-region: drag;
span{
width: 60px;
height: 60px;
cursor: pointer;
-webkit-app-region: no-drag;
}
}
</style>

View File

@@ -1,486 +0,0 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<span class="detail-title">详情</span>
<span class="detail-close zy-svg" @click="close">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="detail-body zy-scroll" v-show="!loading">
<div class="info">
<div class="info-left">
<img :src="info.pic" alt="">
</div>
<div class="info-right">
<div class="name">{{info.name}}</div>
<div class="director" v-show="info.director">导演: {{info.director}}</div>
<div class="actor" v-show="info.actor">主演: {{info.actor}}</div>
<div class="type" v-show="info.type">类型: {{info.type}}</div>
<div class="area" v-show="info.area">地区: {{info.area}}</div>
<div class="lang" v-show="info.lang">语言: {{info.lang}}</div>
<div class="year" v-show="info.year">上映: {{info.year}}</div>
<div class="last" v-show="info.last">更新: {{info.last}}</div>
<div class="note" v-show="info.note">备注: {{info.note}}</div>
<div class="rate" v-show="info.rate">豆瓣评分: {{info.rate}}</div>
</div>
</div>
<div class="operate">
<span @click="playEvent(0)">播放</span>
<span @click="starEvent">收藏</span>
<span @click="downloadEvent">下载</span>
<span @click="shareEvent">分享</span>
<span @click="doubanLinkEvent">豆瓣</span>
<span @click="togglePlayOnlineEvent">
<input type="checkbox" v-model="playOnline"> 播放在线高清视频
</span>
<span>
<select v-model="selectedOnlineSite" class="vs-options">
<option disabled value="">Please select one</option>
<option v-for="(i, j) in onlineSites" :key="j">{{i}}</option>
</select>
</span>
</div>
<div class="desc" v-show="info.des">{{info.des}}</div>
<div class="m3u8">
<div class="box">
<span v-for="(i, j) in m3u8List" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
</div>
</div>
</div>
<div class="detail-mask zy-loading" v-show="loading">
<div class="loader"></div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import zy from '../lib/site/tools'
import onlineVideo from '../lib/site/onlineVideo'
import { star, history } from '../lib/dexie'
const { clipboard } = require('electron')
export default {
name: 'detail',
data () {
return {
loading: true,
m3u8List: [],
info: {},
playOnline: false,
selectedOnlineSite: '哔嘀',
onlineSites: ['哔嘀', '素白白', '简影', '1080影视']
}
},
filters: {
ftName (e) {
const name = e.split('$')[0]
return name
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_VIDEO', 'SET_DETAIL', 'SET_SHARE']),
close () {
this.detail.show = false
},
m3u8Parse (e) {
const dd = e.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
if (i._flag.indexOf('m3u8') >= 0) {
this.m3u8List = i._t.split('#')
}
}
} else {
this.m3u8List = dd._t.split('#')
}
},
playEvent (n) {
if (!this.playOnline) {
history.find({ site: this.detail.key, ids: this.detail.info.id }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: n, site: this.detail.site } }
} else {
this.video = { key: this.detail.key, info: { id: this.detail.info.id, name: this.detail.info.name, index: n, site: this.detail.site } }
}
})
this.view = 'Play'
this.detail.show = false
} else {
history.find({ site: this.detail.key, ids: this.detail.info.id }).then(res => {
if (res) {
res.index = n
history.update(res.id, res)
} else {
const doc = {
site: this.detail.key,
ids: this.detail.info.id,
name: this.detail.info.name,
type: this.detail.info.type,
year: this.detail.info.year,
index: n,
time: ''
}
history.add(doc)
}
})
this.playVideoOnline(this.detail.info.name, n)
}
},
starEvent () {
star.find({ key: this.detail.key, ids: this.info.id }).then(res => {
if (res) {
this.$message.info('该影片已被收藏')
} else {
const docs = {
key: this.detail.key,
ids: this.info.id,
name: this.info.name,
type: this.info.type,
year: this.info.year,
last: this.info.last,
note: this.info.note
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
}).catch(() => {
this.$message.warning('收藏失败')
})
},
togglePlayOnlineEvent () {
this.playOnline = !this.playOnline
},
playVideoOnline (videoName, videoIndex) {
switch (this.selectedOnlineSite) {
case '哔嘀':
onlineVideo.playVideoOnBde4(videoName, videoIndex)
break
case '1080影视':
onlineVideo.playVideoOnK1080(videoName, videoIndex)
break
case '素白白':
onlineVideo.playVideoOnSubaibai(videoName, videoIndex)
break
case '哆咪动漫':
onlineVideo.playVideoOndmdm2020(videoName, videoIndex)
break
case '樱花动漫':
onlineVideo.playVideoOnYhdm(videoName, videoIndex)
break
case '简影':
onlineVideo.playVideoOnSyrme(videoName, videoIndex)
break
default:
this.$message.console.error(`不支持该网站:${this.selectedOnlineSite}`)
}
},
downloadEvent () {
zy.download(this.detail.key, this.info.id).then(res => {
if (res && res.dl && res.dl.dd) {
const text = res.dl.dd._t
if (text) {
const list = text.split('#')
let downloadUrl = res.name + '\n'
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
const list = [...this.m3u8List]
let downloadUrl = this.detail.info.name + '\n'
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
}
})
},
shareEvent () {
this.share = {
show: true,
key: this.detail.key,
info: this.detail.info
}
},
doubanLinkEvent () {
const open = require('open')
const axios = require('axios')
const cheerio = require('cheerio')
const name = this.detail.info.name.trim()
// 豆瓣搜索链接
var doubanSearchLink = 'https://www.douban.com/search?q=' + name
var link = doubanSearchLink
axios.get(doubanSearchLink).then(res => {
const $ = cheerio.load(res.data)
// 比较第一和第二豆瓣搜索结果, 如果名字相符, 就打开该链接,否则打开搜索页面
var nameInDouban = $($('div.result')[0]).find('div>div>h3>a').first()
if (name.replace(/\s/g, '') === nameInDouban.text().replace(/\s/g, '')) {
link = nameInDouban.attr('href')
} else {
nameInDouban = $($('div.result')[1]).find('div>div>h3>a').first()
if (name.replace(/\s/g, '') === nameInDouban.text().replace(/\s/g, '')) {
link = nameInDouban.attr('href')
}
}
open(link)
})
},
getDoubanRate () {
const axios = require('axios')
const cheerio = require('cheerio')
const name = this.detail.info.name.trim()
// 豆瓣搜索链接
var doubanSearchLink = 'https://www.douban.com/search?q=' + name
axios.get(doubanSearchLink).then(res => {
const $ = cheerio.load(res.data)
// 比较第一和第二给豆瓣搜索结果, 看名字是否相符
var link = ''
var nameInDouban = $($('div.result')[0]).find('div>div>h3>a').first()
if (name.replace(/\s/g, '') === nameInDouban.text().replace(/\s/g, '')) {
link = nameInDouban.attr('href')
} else {
nameInDouban = $($('div.result')[1]).find('div>div>h3>a').first()
if (name.replace(/\s/g, '') === nameInDouban.text().replace(/\s/g, '')) {
link = nameInDouban.attr('href')
}
}
// 如果找到链接,就打开该链接获取评分
if (link) {
axios.get(link).then(response => {
const parsedHtml = cheerio.load(response.data)
var rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first()
if (rating.text()) {
this.info.rate = rating.text()
} else {
this.info.rate = '暂无评分'
}
})
} else {
this.info.rate = '暂无评分'
}
})
},
getDetailInfo () {
const id = this.detail.info.ids || this.detail.info.id
zy.detail(this.detail.key, id).then(res => {
if (res) {
this.info = res
this.$set(this.info, 'rate', '')
this.m3u8Parse(res)
this.getDoubanRate()
this.loading = false
}
})
}
},
created () {
this.getDetailInfo()
}
}
</script>
<style lang="scss" scoped>
.detail{
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content{
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header{
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title{
font-size: 16px;
}
.detail-close{
cursor: pointer;
}
}
}
.detail-body{
height: calc(100% - 50px);
overflow-y: auto;
.info{
width: 100%;
padding: 10px;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
border: 1px solid;
border-radius: 2px;
margin-bottom: 10px;
height: auto;
.info-left{
width: 200px;
height: 100%;
img{
width: 100%;
height: auto;
}
}
.info-right{
flex: 1;
margin-left: 20px;
.name{
font-size: 20px;
margin-bottom: 10px;
font-weight: bold;
}
.director, .actor, .type, .area, .lang, .year, .last, .note{
font-size: 14px;
line-height: 26px;
}
.rate{
font-size: 16px;
line-height: 26px;
font-weight: bolder;
}
}
}
.operate{
border: 1px solid;
padding: 10px;
width: 100%;
margin-bottom: 10px;
border-radius: 2px;
span{
margin-right: 20px;
font-size: 14px;
cursor: pointer;
user-select: none;
}
}
.desc{
border: 1px solid;
padding: 10px;
width: 100%;
margin-bottom: 10px;
border-radius: 2px;
font-size: 14px;
line-height: 20px;
}
.m3u8{
border: 1px solid;
padding: 10px 0 10px 10px;
width: 100%;
margin-bottom: 10px;
border-radius: 2px;
.box{
width: 100%;
span{
display: inline-block;
font-size: 12px;
border: 1px solid;
border-radius: 2px;
cursor: pointer;
margin: 6px 10px 0px 0px;
padding: 8px 22px;
}
}
}
}
.detail-mask{
position: absolute;
top: 50px;
left: 0;
width: 100%;
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center;
.loader {
font-size: 8px;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
text-indent: -9999em;
animation: load4 1.3s infinite linear;
transform: translateZ(0);
}
@keyframes load4 {
0%,
100% {
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
}
}
</style>

View File

@@ -1,268 +0,0 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="openAddSite">添加新源</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="exportSites">导出</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="importSites">导入</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="resetSites">重置</div>
</div>
<span class="detail-close zy-svg" @click="close">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="detail-body zy-scroll">
<div class="zy-table">
<div class="tBody zy-scroll">
<div class="addSites-box zy-scroll" v-show="showAddSite">
<ul>
<li >
<span class="name">源名称</span>
<span class="name">API接口</span>
<span class="name">DOWNLOAD接口</span>
<span class="operate">
<span class="btn"></span>
<span class="btn"></span>
</span>
</li>
<li>
<span class="name" style="display:inline-block;vertical-align:middle">
<input style="height: 30px" v-model="newSite.name">
</span>
<span class="name" style="display:inline-block;vertical-align:middle">
<input style="height: 30px" v-model="newSite.api">
</span>
<span class="name" style="display:inline-block;vertical-align:middle">
<input style="height: 30px" v-model="newSite.download" placeholder="可以为空">
</span>
<span class="operate">
<span class="btn" @click="addNewSite">添加</span>
<span class="btn" @click="closeAddSite">关闭</span>
</span>
</li>
<li ></li>
</ul>
</div>
<ul>
<draggable v-model="sites" @change="listUpdatedEvent">
<transition-group>
<li v-for="(i, j) in sites" :key="j">
<span class="name">{{i.name}}</span>
<span class="operate">
<span class="btn" @click.stop="removeEvent(i)">删除</span>
</span>
</li>
</transition-group>
</draggable>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { sites, setting } from '../lib/dexie'
import draggable from 'vuedraggable'
import { remote } from 'electron'
import { sites as defaultSites } from '../lib/dexie/initData'
import fs from 'fs'
export default {
name: 'editSites',
data () {
return {
show: false,
sites: [],
showAddSite: false,
newSite: {
name: '',
api: '',
download: ''
}
}
},
components: {
draggable
},
computed: {
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
},
editSites: {
get () {
return this.$store.getters.getEditSites
},
set (val) {
this.SET_EDITSITES(val)
}
}
},
methods: {
...mapMutations(['SET_SETTING', 'SET_EDITSITES']),
close () {
this.editSites.show = false
},
getSites () {
sites.all().then(res => {
this.sites = res
})
},
removeEvent (e) {
sites.remove(e.id).then(res => {
this.getSites()
}).catch(err => {
this.$message.warning('删除源失败, 错误信息: ' + err)
})
},
listUpdatedEvent () {
sites.clear().then(res1 => {
// 重新排序
var id = 1
this.sites.forEach(element => {
element.id = id
sites.add(element)
id += 1
})
})
},
openAddSite () {
this.showAddSite = true
},
closeAddSite () {
this.showAddSite = false
},
addNewSite () {
if (!this.newSite.name || !this.newSite.api) {
this.$message.error('名称和API接口不能为空。')
return
}
var randomstring = require('randomstring')
var doc = {
key: randomstring.generate(6),
id: this.sites[this.sites.length - 1].id + 1,
name: this.newSite.name,
api: this.newSite.api,
download: this.newSite.download
}
sites.add(doc).then(res => {
this.newSite = {
name: '',
api: '',
download: ''
}
this.$message.success('添加新源成功!')
this.getSites()
})
},
exportSites () {
this.getSites()
const arr = [...this.sites]
const str = JSON.stringify(arr, null, 4)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
]
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
}).catch(err => {
this.$message.error(err)
})
},
importSites () {
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
],
properties: ['openFile']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
sites.clear()
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
sites.bulkAdd(json).then(e => {
this.getSites()
this.d.site = json[0].key
setting.update(this.d).then(res => {
this.setting = this.d
})
})
this.$message.success('导入成功')
}).catch(err => {
this.$message.error(err)
})
}
})
},
resetSites () {
sites.clear()
sites.bulkAdd(defaultSites).then(e => {
this.getSites()
this.$message.success('重置源成功')
})
}
},
created () {
this.getSites()
}
}
</script>
<style lang="scss" scoped>
.detail{
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content{
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header{
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title{
font-size: 16px;
}
.detail-close{
cursor: pointer;
}
}
}
.detail-body{
height: calc(100% - 50px);
overflow-y: auto;
}
}
</style>

View File

@@ -1,586 +0,0 @@
<template>
<div class="film">
<div class="header">
<div class="zy-select" @mouseleave="show.site = false">
<div class="vs-placeholder" @click="show.site = true">{{site.name}}</div>
<div class="vs-options" v-show="show.site">
<ul class="zy-scroll" style="max-height: 600px;">
<li :class="site.key === i.key ? 'active' : ''" v-for="i in sites" :key="i.key" @click="siteClick(i)">{{ i.name }}</li>
</ul>
</div>
</div>
<div class="zy-select" @mouseleave="show.classList = false" v-show="show.class">
<div class="vs-placeholder" @click="show.classList = true">{{type.name}}</div>
<div class="vs-options" v-show="show.classList">
<ul class="zy-scroll" style="max-height: 600px;">
<li :class="type.tid === i.tid ? 'active' : ''" v-for="i in classList" :key="i.tid" @click="classClick(i)">{{ i.name }}</li>
</ul>
</div>
</div>
<div class="zy-select" @mouseleave="show.search = false">
<div class="vs-input" @click="show.search = true"><input v-model.trim="searchTxt" type="text" placeholder="搜索" @keyup.enter="searchEvent(searchTxt)"></div>
<div class="vs-options" v-show="show.search">
<ul class="zy-scroll" style="max-height: 600px">
<li v-for="(i, j) in searchList" :key="j" @click="searchEvent(i.keywords)">{{i.keywords}}</li>
<li v-show="searchList.length >= 1" @click="clearSearch">清空历史记录</li>
</ul>
</div>
</div>
</div>
<div class="body zy-scroll" infinite-wrapper>
<div class="body-box" v-show="!show.find">
<div class="show-img" v-if="setting.view === 'picture'">
<Waterfall ref="waterfall" :list="list" :gutter="20" :width="240"
:breakpoints="{ 1200: { rowPerView: 4 } }"
animationEffect="fadeInUp"
backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props">
<div class="card" v-show="!setting.excludeR18Films || !containsR18Keywords(props.data.type)">
<div class="img">
<img style="width: 100%" :src="props.data.pic" alt="" @load="$refs.waterfall.refresh()" @click="detailEvent(site, props.data)">
<div class="operate">
<div class="operate-wrap">
<span class="o-play" @click="playEvent(site, props.data)">播放</span>
<span class="o-star" @click="starEvent(site, props.data)">收藏</span>
<span class="o-share" @click="shareEvent(site, props.data)">分享</span>
</div>
</div>
</div>
<div class="name" @click="detailEvent(site, props.data)">{{props.data.name}}</div>
<div class="info">
<span>{{props.data.year}}</span>
<span>{{props.data.note}}</span>
<span>{{props.data.type}}</span>
</div>
</div>
</template>
</Waterfall>
<infinite-loading force-use-infinite-wrapper :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div>
<div class="show-table" v-if="setting.view === 'table'">
<div class="zy-table">
<div class="tBody">
<ul>
<li v-for="(i, j) in list" :key="j" @click="detailEvent(site, i)" v-show="!setting.excludeR18Films || !containsR18Keywords(i.type)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.year}}</span>
<span class="time">{{i.note}}</span>
<span class="last">{{i.last}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(site, i)">播放</span>
<span class="btn" @click.stop="starEvent(site, i)">收藏</span>
<span class="btn" @click.stop="shareEvent(site, i)">分享</span>
<span class="btn" @click.stop="downloadEvent(site, i)">下载</span>
</span>
</li>
</ul>
<infinite-loading force-use-infinite-wrapper="tBody" :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div>
</div>
</div>
</div>
<div class="body-box" v-show="show.find">
<div class="show-table">
<div class="zy-table">
<div class="tBody zy-scroll">
<ul>
<li v-for="(i, j) in searchContents" :key="j" @click="detailEvent(i.site, i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="last">{{i.last}}</span>
<span class="site">{{i.site.name}}</span>
<span class="note">{{i.note}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i.site, i)">播放</span>
<span class="btn" @click.stop="starEvent(i.site, i)">收藏</span>
<span class="btn" @click.stop="shareEvent(i.site, i)">分享</span>
<span class="btn" @click.stop="downloadEvent(i.site, i)">下载</span>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { star, history, search, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import Waterfall from 'vue-waterfall-plugin'
import InfiniteLoading from 'vue-infinite-loading'
const { clipboard } = require('electron')
export default {
name: 'film',
data () {
return {
show: {
body: false,
site: false,
class: false,
classList: false,
search: false,
img: true,
table: false,
find: false
},
sites: [],
site: {},
classList: [],
type: {},
pagecount: 0,
list: [],
infiniteId: +new Date(),
searchList: [],
searchTxt: '',
searchContents: [],
// 福利片关键词
r18KeyWords: ['伦理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番']
}
},
components: {
Waterfall,
InfiniteLoading
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
},
setting () {
return this.$store.getters.getSetting
}
},
watch: {
view () {
this.changeView()
},
searchTxt () {
this.searchChangeEvent()
},
'$store.state.editSites.sites': function () {
this.getAllsites()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
siteClick (e) {
this.list = []
this.site = e
this.show.site = false
this.show.class = false
if (this.searchTxt.length > 0) {
this.searchSingleSiteEvent(this.site, this.searchTxt)
} else {
this.classList = []
this.type = {}
this.getClass().then(res => {
if (res) {
this.show.class = true
this.infiniteId += 1
}
})
}
},
classClick (e) {
this.show.classList = false
this.list = []
this.type = e
this.getPage().then(res => {
if (res) {
this.infiniteId += 1
}
})
},
getClass () {
return new Promise((resolve, reject) => {
const key = this.site.key
// 屏蔽主分类
const classToHide = ['电影', '电影片', '电视剧', '连续剧', '综艺', '动漫']
zy.class(key).then(res => {
var allClass = [{ name: '最新', tid: 0 }]
res.class.forEach(element => {
if (!this.setting.excludeRootClasses || !classToHide.includes(element.name)) {
if (this.setting.excludeR18Films) {
const containKeyWord = this.containsR18Keywords(element.name)
if (!containKeyWord) {
allClass.push(element)
}
} else {
allClass.push(element)
}
}
})
this.classList = allClass
this.show.class = true
this.pagecount = res.pagecount
this.type = this.classList[0]
resolve(true)
}).catch(err => {
reject(err)
})
})
},
containsR18Keywords (name) {
var containKeyWord = false
if (!name) {
return containKeyWord
}
return this.r18KeyWords.some(v => name.includes(v))
},
getPage () {
return new Promise((resolve, reject) => {
const key = this.site.key
const type = this.type.tid
zy.page(key, type).then(res => {
this.pagecount = res.pagecount
this.show.body = true
resolve(true)
}).catch(err => {
reject(err)
})
})
},
infiniteHandler ($state) {
const key = this.site.key
const type = this.type.tid
const page = this.pagecount
if (page < 1) {
$state.complete()
return false
}
zy.list(key, page, type).then(res => {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Undefined]') {
$state.complete()
}
if (type === '[object Array]') {
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧
this.list.push(...res.reverse())
}
if (type === '[object Object]') {
this.list.push(res)
}
$state.loaded()
} else {
$state.complete()
}
})
},
detailEvent (site, e) {
this.detail = {
show: true,
key: site.key,
site: site,
info: e
}
},
playEvent (site, e) {
history.find({ site: site.key, ids: e.id }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: res.index, site: site } }
} else {
this.video = { key: site.key, info: { id: e.id, name: e.name, index: 0, site: site } }
}
})
this.view = 'Play'
},
starEvent (site, e) {
star.find({ key: site.key, ids: e.id }).then(res => {
if (res) {
this.$message.info('已存在')
} else {
const docs = {
key: site.key,
ids: e.id,
name: e.name,
type: e.type,
year: e.year,
last: e.last,
note: e.note
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
}).catch(() => {
this.$message.warning('收藏失败')
})
},
shareEvent (site, e) {
this.share = {
show: true,
key: site.key,
info: e
}
},
downloadEvent (site, e) {
zy.download(site.key, e.id).then(res => {
if (res && res.length > 0 && res.dl && res.dl.dd) {
const text = res.dl.dd._t
if (text) {
const list = text.split('#')
let downloadUrl = res.name + '\n'
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
let m3u8List = []
const dd = res.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
if (i._flag.indexOf('m3u8') >= 0) {
m3u8List = i._t.split('#')
}
}
} else {
m3u8List = dd._t.split('#')
}
let downloadUrl = e.name + '\n'
for (const i of m3u8List) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
}
})
},
changeView () {
if (this.view === 'Film') {
if (this.show.img) {
this.$refs.waterfall.refresh()
}
this.getPage().then(() => {
this.infiniteId += 1
if (this.show.img || this.show.table) {
}
})
}
},
getAllSearch () {
search.all().then(res => {
this.searchList = res.reverse()
})
},
searchAllSitesEvent (sites, wd) {
this.searchTxt = wd
this.searchContents = []
this.pagecount = 0
this.show.search = false
this.show.find = true
if (wd) {
search.find({ keywords: wd }).then(res => {
if (!res) {
search.add({ keywords: wd })
}
this.getAllSearch()
})
sites.forEach(site => {
zy.search(site.key, wd).then(res => {
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
res.forEach(element => {
element.site = site
this.searchContents.push(element)
})
}
if (type === '[object Object]') {
res.site = site
this.searchContents.push(res)
}
})
})
} else {
this.show.find = false
this.getClass().then(res => {
if (res) {
this.infiniteId += 1
}
})
}
},
searchEvent (wd) {
if (this.setting.searchAllSites) {
this.searchAllSitesEvent(this.sites, wd)
} else {
this.searchSingleSiteEvent(this.site, wd)
}
},
searchSingleSiteEvent (site, wd) {
var sites = []
sites.push(this.site)
this.searchAllSitesEvent(sites, wd)
},
clearSearch () {
search.clear().then(res => {
this.getAllSearch()
})
},
searchChangeEvent () {
if (this.searchTxt.length >= 1) {
this.show.class = false
} else {
this.show.class = true
this.searchContents = []
this.show.find = false
if (this.show.img) {
this.$refs.waterfall.refresh()
}
}
},
getAllsites () {
sites.all().then(res => {
this.sites = res
this.site = this.sites[0]
this.siteClick(this.site)
})
}
},
created () {
this.getAllsites()
this.getAllSearch()
}
}
</script>
<style lang="scss" scoped>
.film{
height: calc(100% - 40px);
width: 100%;
display: flex;
flex-direction: column;
.header{
height: 30px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 10;
}
.body{
margin-top: 20px;
flex: 1;
width: 100%;
border-radius: 0 0 5px 5px;
overflow-y: scroll;
&::-webkit-scrollbar{
width: 5px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
position: absolute;
}
&::-webkit-scrollbar-track {
border-radius: 10px;
position: absolute;
}
.body-box{
height: 100%;
width: 100%;
}
.show-img{
height: 100%;
width: 100%;
padding: 10px;
.card{
border-radius: 6px;
overflow: hidden;
.img{
position: relative;
min-height: 40px;
img{
width: 100%;
height: auto;
cursor: pointer;
}
.operate{
display: none;
position: absolute;
left: 0;
bottom: 0;
background-color: #111111aa;
width: 100%;
font-size: 13px;
.operate-wrap{
display: flex;
justify-content: space-between;
.o-play, .o-star, .o-share{
cursor: pointer;
display: inline-block;
width: 80px;
height: 36px;
text-align: center;
line-height: 36px;
color: #cdcdcd;
&:hover{
background-color: #111;
}
}
}
}
}
.name{
font-size: 16px;
padding: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.info{
display: flex;
justify-content: space-between;
font-size: 12px;
padding: 10px;
}
&:hover{
.operate{
display: block;
}
}
}
}
}
}
</style>

View File

@@ -1,68 +0,0 @@
<template>
<div class="frame">
<span class="top" @click="frameClickEvent('top')" title="置顶">
<svg t="1595919317571" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1188" style="width:10px;height:14px"><path d="M43.072 974.72l380.864-301.952 151.936 161.6c0 0 63.424 17.28 67.328-30.72l-3.904-163.584 225.088-259.648 98.048-5.696c0 0 76.928-15.488 21.184-82.752l-275.072-276.928c0 0-74.944-9.6-69.248 59.584l0 75.008L383.552 367.104 225.856 376.64c0 0-57.728 19.2-36.608 69.248l148.16 146.176L43.072 974.72 43.072 974.72z" p-id="1189" :fill="isAlwaysOnTop ? '#555555' : '#ffffff'"></path></svg>
</span>
<span class="min" @click="frameClickEvent('min')" title="最小化">
<svg t="1595917239849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1155" style="width:8px;height:14px"><path d="M0 479.936C0 444.64 28.448 416 64.064 416L959.936 416C995.328 416 1024 444.736 1024 479.936L1024 544.064C1024 579.392 995.552 608 959.936 608L64.064 608C28.672 608 0 579.264 0 544.064L0 479.936Z" p-id="1156" fill="#ffffff"></path></svg>
</span>
<span class="max" @click="frameClickEvent('max')" title="最大化">
<svg t="1595917343956" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1540" style="width:8px;height:14px"><path d="M416 416 64.064 416C28.448 416 0 444.64 0 479.936L0 544.064C0 579.264 28.672 608 64.064 608L416 608 416 959.936C416 995.552 444.64 1024 479.936 1024L544.064 1024C579.264 1024 608 995.328 608 959.936L608 608 959.936 608C995.552 608 1024 579.36 1024 544.064L1024 479.936C1024 444.736 995.328 416 959.936 416L608 416 608 64.064C608 28.448 579.36 0 544.064 0L479.936 0C444.736 0 416 28.672 416 64.064L416 416Z" p-id="1541" fill="#ffffff"></path></svg>
</span>
<span class="close" @click="frameClickEvent('close')" title="关闭">
<svg t="1595917372551" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1685" style="width:8px;height:14px"><path d="M511.968 376.224 796.096 92.096C833.536 54.624 894.4 54.624 931.84 92.096 969.312 129.568 969.312 190.4 931.84 227.872L647.744 512 931.84 796.096C969.312 833.568 969.312 894.4 931.84 931.872 894.4 969.344 833.536 969.344 796.096 931.872L511.968 647.744 227.84 931.872C190.4 969.344 129.536 969.344 92.096 931.872 54.624 894.4 54.624 833.568 92.096 796.096L376.224 512 92.096 227.872C54.624 190.4 54.624 129.568 92.096 92.096 129.536 54.624 190.4 54.624 227.84 92.096L511.968 376.224Z" p-id="1686" fill="#ffffff"></path></svg>
</span>
</div>
</template>
<script>
const { remote } = require('electron')
export default {
name: 'frame',
data () {
const win = remote.getCurrentWindow()
return {
isAlwaysOnTop: win.isAlwaysOnTop()
}
},
methods: {
frameClickEvent (e) {
const win = remote.getCurrentWindow()
if (e === 'min') {
win.minimize()
}
if (e === 'max') {
win.isMaximized() ? win.unmaximize() : win.maximize()
}
if (e === 'close') {
win.destroy()
}
if (e === 'top') {
this.isAlwaysOnTop = !this.isAlwaysOnTop
win.setAlwaysOnTop(this.isAlwaysOnTop)
}
}
}
}
</script>
<style lang="scss" scoped>
.frame{
width: 100%;
height: 40px;
display: flex;
user-select: none;
align-items: center;
justify-content: flex-end;
-webkit-app-region: drag;
span{
width: 14px;
height: 14px;
cursor: pointer;
margin-left: 10px;
border-radius: 50%;
text-align: center;
line-height: 14px;
display: inline-block;
-webkit-app-region: no-drag;
}
}
</style>

View File

@@ -1,225 +0,0 @@
<template>
<div class="history">
<div class="body zy-scroll">
<div class="zy-table">
<div class="tHeader">
<span class="btn"></span>
<span class="btn" @click="clearAllHistory">清空</span>
</div>
<div class="tBody zy-scroll">
<ul>
<li v-show="this.history.length === 0">无数据</li>
<li v-show="this.history.length > 0">
<span class="name">名字</span>
<span class="site">片源</span>
<span class="note">观看至</span>
<span class="operate">
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
</span>
</li>
<li v-for="(i, j) in history" :key="j" @click="historyItemEvent(i)">
<span class="name" @click.stop="detailEvent(i)">{{i.name}}</span>
<span class="site">{{getSiteName(i.site)}}</span>
<span class="note">{{i.index+1}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i)">播放</span>
<span class="btn" @click.stop="shareEvent(i)">分享</span>
<span class="btn" @click.stop="downloadEvent(i)">下载</span>
<span class="btn" @click.stop="removeHistoryItem(i)">删除</span>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { history, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
const { clipboard } = require('electron')
export default {
name: 'history',
data () {
return {
history: history,
sites: []
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
watch: {
view () {
this.getAllhistory()
this.getAllsites()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
detailEvent (e) {
this.detail = {
show: true,
key: e.site,
info: {
id: e.ids,
name: e.name
}
}
},
playEvent (e) {
history.find({ site: e.site, ids: e.ids }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: res.index } }
} else {
this.video = { key: e.site, info: { id: e.ids, name: e.name, index: 0 } }
}
})
this.view = 'Play'
},
shareEvent (e) {
this.share = {
show: true,
key: e.site,
info: e
}
},
downloadEvent (e) {
zy.download(e.site, e.ids).then(res => {
if (res && res.dl && res.dl.dd) {
const text = res.dl.dd._t
if (text) {
const list = text.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
var m3u8List = {}
zy.detail(e.site, e.ids).then(res => {
const dd = res.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
if (i._flag.indexOf('m3u8') >= 0) {
m3u8List = i._t.split('#')
}
}
} else {
m3u8List = dd._t.split('#')
}
const list = [...m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
})
},
clearAllHistory () {
history.clear().then(res => {
this.history = []
})
},
getAllhistory () {
history.all().then(res => {
this.history = res.reverse()
})
},
getAllsites () {
sites.all().then(res => {
this.sites = res
})
},
getSiteName (key) {
var site = this.sites.find(e => e.key === key)
if (site) {
return site.name
}
},
historyItemEvent (e) {
this.video = {
key: e.site,
info: {
id: e.ids,
name: e.name,
type: e.type,
year: e.year,
index: e.index,
time: e.time
}
}
},
removeHistoryItem (e) {
history.remove(e.id).then(res => {
this.getAllhistory()
}).catch(err => {
this.$message.warning('删除历史记录失败, 错误信息: ' + err)
})
}
},
created () {
this.getAllhistory()
}
}
</script>
<style lang="scss" scoped>
.history{
position: relative;
height: calc(100% - 40px);
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
.body{
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -1,276 +0,0 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="exportSites">导出</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="importSites">导入</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="removeAllSites">清空</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="resetSites">重置</div>
</div>
<div style="width: 200px; height: 30px;">
</div>
</div>
<div class="detail-header">
<div>
<div class="vs-placeholder vs-noAfter" @click="exportSites">总频道数:{{iptvList.length}}</div>
</div>
<div class="zy-select" @mouseleave="show.search = false">
<div class="vs-input" @click="show.search = true"><input v-model.trim="searchTxt" type="text" placeholder="搜索" @keyup.enter="searchEvent(searchTxt)"></div>
<div class="vs-options" v-show="show.search">
<ul class="zy-scroll" style="max-height: 600px">
<li v-for="(i, j) in searchList" :key="j" @click="searchEvent(i.keywords)">{{i.keywords}}</li>
<li v-show="searchList.length >= 1" @click="clearSearch">清空历史记录</li>
</ul>
</div>
</div>
</div>
<div class="detail-body zy-scroll">
<div class="zy-table">
<div class="tBody zy-scroll">
<ul>
<draggable v-model="iptvList" @change="listUpdatedEvent">
<transition-group>
<li v-for="(i, j) in iptvList" :key="j" @click.stop="playEvent(i)" v-show="containsearchTxt(i)">
<span class="name">{{i.name}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i)">播放</span>
<span class="btn" @click.stop="removeEvent(i)">删除</span>
</span>
</li>
</transition-group>
</draggable>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { iptv, iptvSearch } from '../lib/dexie'
import draggable from 'vuedraggable'
import { iptv as defaultSites } from '../lib/dexie/initData'
import { remote } from 'electron'
import fs from 'fs'
export default {
name: 'iptv',
data () {
return {
iptvList: [],
searchTxt: '',
searchList: [],
show: {
search: false
}
}
},
components: {
draggable
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
setting () {
return this.$store.getters.getSetting
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
}
},
watch: {
view () {
this.getAllSites()
},
searchTxt () {
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
playEvent (e) {
this.video = { iptv: { name: e.name, url: e.url } }
this.view = 'Play'
},
containsearchTxt (i) {
if (this.searchTxt) {
return i.name.toLowerCase().includes(this.searchTxt.toLowerCase())
} else {
return true
}
},
removeEvent (e) {
iptv.remove(e.id).then(res => {
this.getAllSites()
}).catch(err => {
this.$message.warning('删除频道失败, 错误信息: ' + err)
})
},
listUpdatedEvent () {
iptv.clear().then(res1 => {
// 重新排序
var id = 1
this.iptvList.forEach(element => {
element.id = id
iptv.add(element)
id += 1
})
})
},
exportSites () {
const options = {
filters: [
{ name: 'm3u file', extensions: ['m3u'] },
{ name: 'JSON file', extensions: ['json'] }
]
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
if (result.filePath.endsWith('m3u')) {
var writer = require('m3u').extendedWriter()
this.iptvList.forEach(e => {
writer.file(e.url, -1, e.name)
})
fs.writeFileSync(result.filePath, writer.toString())
this.$message.success('已保存成功')
} else {
const arr = [...this.iptvList]
const str = JSON.stringify(arr, null, 4)
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
}
}).catch(err => {
this.$message.error(err)
})
},
importSites () {
const options = {
filters: [
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] }
],
properties: ['openFile', 'multiSelections']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var docs = this.iptvList
result.filePaths.forEach(file => {
const parser = require('iptv-playlist-parser')
const playlist = fs.readFileSync(file, { encoding: 'utf-8' })
const result = parser.parse(playlist)
result.items.forEach(ele => {
if (ele.name && ele.url && ele.url.includes('.m3u8')) {
var doc = {
name: ele.name,
url: ele.url
}
docs.push(doc)
}
})
})
// 获取url不重复的列表
const uniqueList = [...new Map(docs.map(item => [item.url, item])).values()]
iptv.clear().then(res => {
iptv.bulkAdd(uniqueList).then(e => {
this.getAllSites()
this.$message.success('导入成功')
})
})
}
})
},
resetSites () {
iptv.clear()
iptv.bulkAdd(defaultSites).then(e => {
this.getAllSites()
})
},
removeAllSites () {
iptv.clear().then(res => {
this.getAllSites()
})
},
getAllSites () {
iptv.all().then(res => {
this.iptvList = res
})
},
getAllSearch () {
iptvSearch.all().then(res => {
this.searchList = res.reverse()
})
},
clearSearch () {
iptvSearch.clear().then(res => {
this.getAllSearch()
})
},
searchEvent (wd) {
this.searchTxt = wd
this.show.search = false
if (wd) {
iptvSearch.find({ keywords: wd }).then(res => {
if (!res) {
iptvSearch.add({ keywords: wd })
}
this.getAllSearch()
})
}
}
},
created () {
this.getAllSites()
this.getAllSearch()
}
}
</script>
<style lang="scss" scoped>
.detail{
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content{
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header{
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title{
font-size: 16px;
}
.detail-close{
cursor: pointer;
}
}
}
.detail-body{
height: calc(100% - 100px);
overflow-y: auto;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,493 +0,0 @@
<template>
<div class="setting">
<div class="setting-box zy-scroll">
<div class="logo"><img src="@/assets/image/logo.png" alt=""></div>
<div class="info">
<a @click="linkOpen('http://zyplayer.fun/')">官网</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player')">Github</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">当前版本v{{pkg.version}} 反馈</a>
<a style="color:#38dd77" @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/releases/tag/v' + latestVersion)" v-show="latestVersion !== pkg.version" >最新版本v{{latestVersion}}</a>
</div>
<div class="view">
<div class="title">视图</div>
<div class="view-box">
<div class="zy-select" @mouseleave="show.view = false">
<div class="vs-placeholder" @click="show.view = true">默认视图</div>
<div class="vs-options" v-show="show.view">
<ul class="zy-scroll">
<li :class="d.view === 'picture' ? 'active' : ''" @click="changeView('picture')">海报</li>
<li :class="d.view === 'table' ? 'active' : ''" @click="changeView('table')">列表</li>
</ul>
</div>
</div>
</div>
</div>
<div class="shortcut">
<div class="title">快捷键</div>
<div class="shortcut-box">
<div class="zy-select" @mouseleave="show.shortcut = false">
<div class="vs-placeholder" @click="show.shortcut = true">快捷键</div>
<div class="vs-options" v-show="show.shortcut">
<ul class="zy-scroll">
<li :class="d.shortcut === true ? 'active' : ''" @click="changeShortcut(true)">开启</li>
<li :class="d.shortcut === false ? 'active' : ''" @click="changeShortcut(false)">关闭</li>
</ul>
</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="expShortcut">导出</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="impShortcut">导入</div>
</div>
</div>
</div>
<div class="site">
<div class="title">定位时间设置</div>
<div class="zy-input">
/右方向键:<input style="width:50px" type="number" v-model = "d.forwardTimeInSec" @change="updateSettingEvent">
</div>
</div>
<div class='search'>
<div class="title">搜索</div>
<div class="zy-input" @click="toggleSearchAllSites">
<input type="checkbox" v-model = "d.searchAllSites" @change="updateSettingEvent"> 搜索所有资源
</div>
</div>
<div class='site'>
<div class="title">第三方播放</div>
<div class="site-box">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="selectLocalPlayer">选择本地播放器</div>
</div>
<div class="zy-select" @click = "editPlayerPath = true">
<div class="vs-placeholder vs-noAfter" v-show = "editPlayerPath == false">
<label>编辑</label>
</div>
<input class="zy-input" v-show = "editPlayerPath == true" v-model = "d.externalPlayer"
@blur= "updateSettingEvent"
@keyup.enter = "updateSettingEvent">
</div>
</div>
</div>
<div class="site">
<div class="title">源管理</div>
<div class="site-box">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="editSitesEvent">编辑源</div>
</div>
<div class="zy-input" @click="toggleExcludeRootClasses">
<input type="checkbox" v-model = "d.excludeRootClasses" @change="updateSettingEvent"> 屏蔽主分类
</div>
<div class="zy-input" @click="toggleExcludeR18Films">
<input type="checkbox" v-model = "d.excludeR18Films" @change="updateSettingEvent"> 屏蔽福利片
</div>
</div>
</div>
<div class="theme">
<div class="title">主题</div>
<div class="theme-box">
<div @click="changeTheme('light')" class="theme-item light">
<div class="theme-image">
<img src="../assets/image/light.png" alt="">
</div>
<div class="theme-name">Light</div>
</div>
<div @click="changeTheme('dark')" class="theme-item dark">
<div class="theme-image">
<img src="../assets/image/dark.png" alt="">
</div>
<div class="theme-name">Dark</div>
</div>
<div @click="changeTheme('green')" class="theme-item green">
<div class="theme-image">
<img src="../assets/image/green.png" alt="">
</div>
<div class="theme-name">Green</div>
</div>
<div @click="changeTheme('pink')" class="theme-item pink">
<div class="theme-image">
<img src="../assets/image/pink.png" alt="">
</div>
<div class="theme-name">Pink</div>
</div>
</div>
</div>
<div class="qrcode">
<div class="title">请作者吃辣条</div>
<div class="qrcode-box">
<img class="qrcode-item" src="../assets/image/wepay-hunlongyu.png">
<img class="qrcode-item" src="../assets/image/wepay_cuiocean.jpg">
</div>
</div>
<div class="clearDB">
<span @click="clearDBEvent" class="clearBtn">软件重置</span>
<span class="clearTips">如果新安装用户, 无法显示资源, 请点击软件重置. 如非必要, 切勿点击. 会清空用户数据, 恢复默认设置. 点击即软件重置, 并关闭软件.</span>
</div>
<div class="Tips">
<span>所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.</span>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import pkg from '../../package.json'
import { setting, sites, shortcut, star } from '../lib/dexie'
import { shell, clipboard, remote } from 'electron'
import db from '../lib/dexie/dexie'
export default {
name: 'setting',
data () {
return {
pkg: pkg,
sitesList: [],
shortcutList: [],
favoritesList: [],
show: {
site: false,
shortcut: false,
view: false
},
externalPlayer: '',
editPlayerPath: false,
excludeR18Films: true,
latestVersion: pkg.version,
forwardTimeInSec: 5,
d: {
id: 0,
site: '',
theme: '',
shortcut: true,
searchAllSites: true,
view: 'picture',
externalPlayer: '',
editPlayerPath: false,
excludeRootClasses: true,
excludeR18Films: true,
forwardTimeInSec: 5
}
}
},
computed: {
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
},
editSites: {
get () {
return this.$store.getters.getEditSites
},
set (val) {
this.SET_EDITSITES(val)
}
}
},
methods: {
...mapMutations(['SET_SETTING', 'SET_EDITSITES']),
linkOpen (e) {
shell.openExternal(e)
},
getSetting () {
setting.find().then(res => {
this.d = {
id: res.id,
theme: res.theme,
shortcut: res.shortcut,
view: res.view,
externalPlayer: res.externalPlayer,
searchAllSites: res.searchAllSites,
excludeRootClasses: res.excludeRootClasses,
excludeR18Films: res.excludeR18Films,
forwardTimeInSec: res.forwardTimeInSec
}
this.setting = this.d
})
},
getSites () {
sites.all().then(res => {
this.sitesList = res
})
},
getShortcut () {
shortcut.all().then(res => {
this.shortcutList = res
})
},
getFavorites () {
star.all().then(res => {
this.favoritesList = res
})
},
changeView (e) {
this.d.view = e
this.updateSettingEvent()
this.show.view = false
},
updateSettingEvent () {
this.editPlayerPath = false
this.setting = this.d
setting.update(this.d)
},
toggleSearchAllSites () {
this.d.searchAllSites = !this.d.searchAllSites
this.updateSettingEvent()
},
toggleExcludeR18Films () {
this.d.excludeR18Films = !this.d.excludeR18Films
this.updateSettingEvent()
},
toggleExcludeRootClasses () {
this.d.excludeRootClasses = !this.d.excludeRootClasses
this.updateSettingEvent()
},
selectLocalPlayer () {
const options = {
filters: [
{ name: 'Executable file', extensions: ['exe'] },
{ name: 'All types', extensions: ['*'] }
],
properties: ['openFile']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var playerPath = result.filePaths[0].replace(/\\/g, '/')
this.$message.success('设定第三方播放器路径为:' + result.filePaths[0])
this.d.externalPlayer = playerPath
this.updateSettingEvent()
}
}).catch(err => {
this.$message.error(err)
})
},
resetLocalPlayer () {
this.d.externalPlayer = ''
setting.update(this.d).then(res => {
this.updateSettingEvent()
this.$message.success('重置第三方播放器成功')
})
},
updatePlayerPath () {
this.$message.success('设定第三方播放器路径为:' + this.d.externalPlayer)
this.editPlayerPath = false
this.updateSettingEvent()
},
editSitesEvent () {
this.editSites = {
show: true,
sites: this.sitesList
}
},
changeTheme (e) {
this.d.theme = e
this.updateSettingEvent()
},
changeShortcut (e) {
this.d.shortcut = e
this.updateSettingEvent()
this.show.shortcut = false
},
expShortcut () {
const arr = [...this.shortcutList]
const str = JSON.stringify(arr, null, 4)
clipboard.writeText(str)
this.$message.success('已复制到剪贴板')
},
impShortcut () {
const str = clipboard.readText()
const json = JSON.parse(str)
shortcut.clear().then(res => {
this.$message.info('已清空原数据')
shortcut.add(json).then(e => {
this.$message.success('已添加成功')
this.getSites()
})
})
},
clearDBEvent () {
db.delete().then(res => {
this.$message.success('重置成功')
const win = remote.getCurrentWindow()
win.destroy()
})
},
openDoc (e) {
if (e === 'sites') {
this.linkOpen('http://zyplayer.fun/doc/sites/')
return false
}
if (e === 'shortcut') {
this.linkOpen('http://zyplayer.fun/doc/shortcut/')
return false
}
},
getLatestVersion () {
const cheerio = require('cheerio')
const axios = require('axios')
var url = 'https://github.com/Hunlongyu/ZY-Player/releases'
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.release-header')[0]
var firstResult = $(e).find('div>div>a')
this.latestVersion = firstResult.text()
})
}
},
created () {
this.getSites()
this.getSetting()
this.getShortcut()
this.getFavorites()
this.getLatestVersion()
}
}
</script>
<style lang="scss" scoped>
.setting{
height: calc(100% - 40px);
width: 100%;
border-radius: 5px;
padding: 20px 0;
.setting-box{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.logo{
margin-top: 10px;
width: 100%;
text-align: center;
img{
width: 120px;
height: auto;
}
}
.info{
width: 100%;
margin-top: 20px;
text-align: center;
a{
text-decoration: none;
margin: 0 10px;
font-size: 14px;
cursor: pointer;
}
}
.view{
width: 100%;
padding: 20px;
margin-top: 20px;
.view-box{
margin-top: 10px;
.zy-select{
margin-right: 20px;
}
}
}
.search{
width: 100%;
padding: 20px;
margin-top: 20px;
}
.site{
width: 100%;
padding: 20px;
margin-top: 20px;
.site-box{
margin-top: 10px;
.zy-select{
margin-right: 20px;
}
}
}
.shortcut{
width: 100%;
padding: 20px;
margin-top: 20px;
.shortcut-box{
margin-top: 10px;
.zy-select{
margin-right: 20px;
}
}
}
.theme{
width: 100%;
padding-left: 20px;
margin-top: 20px;
.theme-box{
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
margin-top: 10px;
.theme-item{
width: 200px;
height: 180px;
margin-right: 20px;
cursor: pointer;
border-radius: 2px;
.theme-image{
width: 180px;
margin: 10px auto;
img{
width: 100%;
}
}
.theme-name{
width: 100%;
text-align: center;
}
}
}
}
.qrcode{
width: 100%;
padding-left: 20px;
margin-top: 20px;
margin-bottom: 20px;
.qrcode-box{
display: flex;
justify-content: flex-start;
margin-top: 10px;
.qrcode-item{
width: auto;
height: 300px;
margin-right: 20px;
border-radius: 2px;
}
}
}
.clearDB{
margin-top: 20px;
margin-bottom: 20px;
.clearBtn{
margin-left: 20px;
color: red;
cursor: pointer;
border: 1px solid #ff000088;
display: inline-block;
width: 160px;
height: 32px;
font-size: 14px;
text-align: center;
line-height: 32px;
}
.clearTips{
font-size: 12px;
color: #ff000088;
margin-left: 10px;
}
}
.Tips{
margin: 20px;
font-size: 12px;
color: #ff000066;
}
}
</style>

View File

@@ -1,205 +0,0 @@
<template>
<div class="share" id="share" @click="shareClickEvent">
<div class="left">
<img :src="pic" alt="" @load="picLoadEvent">
</div>
<div class="right" id="right">
<div class="title">{{ share.info.name }}</div>
<qrcode-vue id="qr" :value="link" :size="160" level="L" />
<div class="tips">
<p>长按二维码识别播放</p>
<p><img src="@/assets/image/logo.png"></p>
<p class="zy">ZY Player技术支持严禁传播违法资源</p>
</div>
</div>
<div class="share-mask" v-show="loading">
<div class="loader"></div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import QrcodeVue from 'qrcode.vue'
import html2canvas from 'html2canvas'
import zy from '../lib/site/tools'
const { clipboard, nativeImage } = require('electron')
export default {
name: 'share',
data () {
return {
pic: '',
png: '',
link: '',
loading: false
}
},
components: {
QrcodeVue
},
computed: {
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
watch: {
share: {
handler () {
this.getDetail(
this.loading = true
)
},
deep: true
}
},
methods: {
...mapMutations(['SET_SHARE']),
shareClickEvent () {
this.share = {
show: false,
info: {}
}
},
getDetail () {
this.loading = true
const id = this.share.info.ids || this.share.info.id
zy.detail(this.share.key, id).then(res => {
if (res) {
this.pic = res.pic
var m3u8List = {}
const dd = res.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
if (i._flag.indexOf('m3u8') >= 0) {
m3u8List = i._t.split('#')
}
}
} else {
m3u8List = dd._t.split('#')
}
const url = m3u8List[1]
this.link = 'http://zyplayer.fun/player/player.html?url=' + url + '&title=' + this.share.info.name
}
this.loading = false
})
},
picLoadEvent () {
const dom = document.getElementById('right')
html2canvas(dom, { useCORS: true, allowTaint: true }).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$message.success('已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!')
})
}
},
mounted () {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.share{
position: absolute;
bottom: 20px;
right: 20px;
width: 540px;
height: 360px;
border-radius: 2px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0px;
z-index: 999;
.left, .right{
width: 50%;
height: 100%;
}
.left{
display: flex;
justify-content: center;
align-items: center;
img{
height: 320px;
width: auto;
max-width: 240px;
}
}
.right{
padding: 10px;
.title{
font-size: 18px;
margin-bottom: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#qr{
text-align: center;
}
.tips{
font-size: 14px;
text-align: center;
img{
width: 50px;
}
.zy{
font-size: 12px;
}
}
}
.share-mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.loader {
color: #823aa055;
font-size: 8px;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
text-indent: -9999em;
animation: load4 1.3s infinite linear;
transform: translateZ(0);
}
@keyframes load4 {
0%,
100% {
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
}
}
</style>

View File

@@ -1,412 +0,0 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="exportFavoritesEvent">
导出
</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="importFavoritesEvent">
导入
</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="clearFavoritesEvent">
清空
</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="updateAllEvent">
同步所有收藏
</div>
</div>
</div>
<div class="detail-body zy-scroll">
<div class="zy-table">
<div class="tBody zy-scroll">
<ul>
<li v-show="this.list.length > 0">
<span class="name">名字</span>
<span class="type">类型</span>
<span class="time">上映</span>
<span class="site">片源</span>
<span class="note">备注</span>
<span class="note">观看至</span>
<span class="operate">
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
</span>
</li>
<draggable v-model="list" @change="listUpdatedEvent">
<transition-group>
<li
v-for="(i, j) in list"
:key="j"
@click="detailEvent(i)"
:class="[i.hasUpdate ? 'zy-highlighted' : '']"
>
<span class="name">{{ i.name }}</span>
<span class="type">{{ i.type }}</span>
<span class="time">{{ i.year }}</span>
<span class="site">{{ getSiteName(i.key) }}</span>
<span class="note">{{ i.note }}</span>
<span class="note">{{ getHistoryNote(i.index) }}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i)">播放</span>
<span class="btn" @click.stop="shareEvent(i)">分享</span>
<span class="btn" @click.stop="updateEvent(i)">同步</span>
<span class="btn" @click.stop="downloadEvent(i)"
>下载</span
>
<span class="btn" @click.stop="deleteEvent(i)">删除</span>
</span>
</li>
</transition-group>
</draggable>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { star, history, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import draggable from 'vuedraggable'
import { remote } from 'electron'
import fs from 'fs'
const { clipboard } = require('electron')
export default {
name: 'star',
data () {
return {
list: [],
sites: []
}
},
components: {
draggable
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
watch: {
view () {
this.getFavorites()
this.getAllsites()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
detailEvent (e) {
this.detail = {
show: true,
key: e.key,
info: {
id: e.ids,
name: e.name
}
}
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
}
},
playEvent (e) {
history.find({ site: e.key, ids: e.ids }).then(res => {
if (res) {
this.video = { key: e.key, info: { id: res.ids, name: res.name, index: res.index } }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 } }
}
})
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
}
this.view = 'Play'
},
deleteEvent (e) {
star.remove(e.id).then(res => {
if (res) {
this.$message.warning('删除失败')
} else {
this.$message.success('删除成功')
}
this.getFavorites()
})
},
shareEvent (e) {
this.share = {
show: true,
key: e.key,
info: e
}
},
clearHasUpdateFlag (e) {
star.find({ id: e.id }).then(res => {
res.hasUpdate = false
star.update(e.id, res)
this.getFavorites()
})
},
listUpdatedEvent () {
star.clear().then(res1 => {
// 重新排序
var id = this.list.length
this.list.forEach(element => {
element.id = id
star.add(element)
id -= 1
})
})
},
updateEvent (e) {
zy.detail(e.key, e.ids).then(res => {
var doc = {
key: e.key,
id: e.id,
ids: res.id,
last: res.last,
name: res.name,
type: res.type,
year: res.year,
note: res.note
}
star.get(e.id).then(resStar => {
doc.hasUpdate = resStar.hasUpdate
var msg = ''
if (e.last === res.last) {
msg = `同步"${e.name}"成功, 未查询到更新。`
this.$message.info(msg)
} else {
doc.hasUpdate = true
msg = `同步"${e.name}"成功, 检查到更新。`
this.$message.success(msg)
}
star.update(e.id, doc)
this.getFavorites()
})
}).catch(err => {
var msg = `同步"${e.name}"失败, 请重试。`
this.$message.warning(msg, err)
})
},
updateAllEvent () {
this.list.forEach(e => {
this.updateEvent(e)
})
},
downloadEvent (e) {
zy.download(e.key, e.ids).then(res => {
if (res && res.dl && res.dl.dd) {
const text = res.dl.dd._t
if (text) {
const list = text.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
var m3u8List = {}
zy.detail(e.key, e.ids).then(res => {
const dd = res.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
if (i._flag.indexOf('m3u8') >= 0) {
m3u8List = i._t.split('#')
}
}
} else {
m3u8List = dd._t.split('#')
}
const list = [...m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
})
},
getSiteName (key) {
var site = this.sites.find(e => e.key === key)
if (site) {
return site.name
}
},
getHistoryNote (index) {
if (index !== null && index !== undefined) {
return `${index + 1}`
} else {
return ''
}
},
getFavorites () {
star.all().then(res => {
this.list = res.reverse()
})
},
getAllsites () {
sites.all().then(res => {
this.sites = res
})
},
exportFavoritesEvent () {
const arr = [...this.list]
const str = JSON.stringify(arr, null, 4)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
]
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
}).catch(err => {
this.$message.error(err)
})
},
importFavoritesEvent () {
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
],
properties: ['openFile', 'multiSelections']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
star.bulkAdd(json).then(e => {
this.getFavorites()
})
this.upgradeFavorites()
})
this.$message.success('导入收藏成功')
}
}).catch(err => {
this.$message.error(err)
})
},
upgradeFavorites () {
star.all().then(res => {
res.forEach(element => {
const docs = {
key: element.key,
ids: element.ids,
name: element.name,
type: element.type,
year: element.year,
last: element.last,
note: element.note
}
star.find({ key: element.key, ids: element.ids }).then(res => {
if (!res) {
star.add(docs)
}
})
})
this.getFavorites()
})
},
clearFavoritesEvent () {
star.clear().then(e => {
this.getFavorites()
this.$message.success('清空所有收藏成功')
})
}
},
created () {
this.getFavorites()
window.Sortable = require('sortablejs').Sortable
}
}
</script>
<style lang="scss" scoped>
.detail{
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content{
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header{
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title{
font-size: 16px;
}
.detail-close{
cursor: pointer;
}
}
}
.detail-body{
height: calc(100% - 50px);
overflow-y: auto;
}
}
</style>

View File

@@ -1,28 +0,0 @@
import Vue from 'vue'
import Aside from './Aside'
import Frame from './Frame'
import Film from './Film'
import Play from './Play'
import Star from './Star'
import Setting from './Setting'
import Detail from './Detail'
import Share from './Share'
import History from './History'
import EditSites from './EditSites'
import IPTV from './IPTV'
export default {
registerComponents () {
Vue.component('Aside', Aside)
Vue.component('Frame', Frame)
Vue.component('Film', Film)
Vue.component('Play', Play)
Vue.component('Star', Star)
Vue.component('Setting', Setting)
Vue.component('Detail', Detail)
Vue.component('Share', Share)
Vue.component('History', History)
Vue.component('EditSites', EditSites)
Vue.component('IPTV', IPTV)
}
}

View File

@@ -1,27 +0,0 @@
import Dexie from 'dexie'
import { setting, sites, localKey, iptv } from './initData'
const db = new Dexie('zy')
db.version(3).stores({
search: '++id, keywords',
iptvSearch: '++id, keywords',
setting: 'id, theme, site, shortcut, view, externalPlayer, searchAllSites, excludeRootClasses, excludeR18Films, forwardTimeInSec',
shortcut: 'name, key, desc',
star: '++id, site, ids, name, type, year, index',
sites: '++id, key, name, json, xml, down, level',
history: '++id, site, ids, name, type, year, index, time',
mini: 'id, site, ids, name, index, time',
iptv: '++id, name, url'
})
db.on('populate', () => {
db.setting.bulkAdd(setting)
db.sites.bulkAdd(sites)
db.shortcut.bulkAdd(localKey)
db.iptv.bulkAdd(iptv)
})
db.open()
export default db

View File

@@ -1,22 +0,0 @@
import db from './dexie'
const { history } = db
export default {
async add (doc) {
return await history.add(doc)
},
async find (doc) {
return await history.get(doc)
},
async update (id, docs) {
return await history.update(id, docs)
},
async all () {
return await history.toArray()
},
async remove (id) {
return await history.delete(id)
},
async clear () {
return await history.clear()
}
}

View File

@@ -1,21 +0,0 @@
import history from './history'
import mini from './mini'
import setting from './setting'
import shortcut from './shortcut'
import star from './star'
import sites from './sites'
import search from './search'
import iptvSearch from './iptvSearch'
import iptv from './iptv'
export {
history,
mini,
setting,
shortcut,
star,
sites,
iptv,
search,
iptvSearch
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
import db from './dexie'
const { iptv } = db
export default {
async all () {
return await iptv.toArray()
},
async clear () {
return await iptv.clear()
},
async add (doc) {
return await iptv.add(doc)
},
async bulkAdd (doc) {
return await iptv.bulkAdd(doc)
},
async find (doc) {
return await iptv.get(doc)
},
async update (id, docs) {
return await iptv.update(id, docs)
},
async remove (id) {
return await iptv.delete(id)
}
}

View File

@@ -1,22 +0,0 @@
import db from './dexie'
const { iptvSearch } = db
export default {
async add (doc) {
return await iptvSearch.add(doc)
},
async find (doc) {
return await iptvSearch.get(doc)
},
async update (id, docs) {
return await iptvSearch.update(id, docs)
},
async all () {
return await iptvSearch.toArray()
},
async remove (id) {
return await iptvSearch.delete(id)
},
async clear () {
return await iptvSearch.clear()
}
}

View File

@@ -1,13 +0,0 @@
import db from './dexie'
const { mini } = db
export default {
async add (doc) {
return await mini.add(doc)
},
async find () {
return await mini.get({ id: 0 })
},
async update (docs) {
return await mini.update(0, docs)
}
}

View File

@@ -1,22 +0,0 @@
import db from './dexie'
const { search } = db
export default {
async add (doc) {
return await search.add(doc)
},
async find (doc) {
return await search.get(doc)
},
async update (id, docs) {
return await search.update(id, docs)
},
async all () {
return await search.toArray()
},
async remove (id) {
return await search.delete(id)
},
async clear () {
return await search.clear()
}
}

View File

@@ -1,11 +0,0 @@
import db from './dexie'
const { setting } = db
export default {
async find () {
return await setting.get({ id: 0 })
},
async update (docs) {
return await setting.update(0, docs)
}
}

View File

@@ -1,14 +0,0 @@
import db from './dexie'
const { shortcut } = db
export default {
async all () {
return await shortcut.toArray()
},
async clear () {
return await shortcut.clear()
},
async add (doc) {
return await shortcut.bulkAdd(doc)
}
}

View File

@@ -1,22 +0,0 @@
import db from './dexie'
const { sites } = db
export default {
async all () {
return await sites.toArray()
},
async clear () {
return await sites.clear()
},
async bulkAdd (doc) {
return await sites.bulkAdd(doc)
},
async find (doc) {
return await sites.get(doc)
},
async add (doc) {
return await sites.add(doc)
},
async remove (id) {
return await sites.delete(id)
}
}

View File

@@ -1,28 +0,0 @@
import db from './dexie'
const { star } = db
export default {
async add (doc) {
return await star.add(doc)
},
async bulkAdd (doc) {
return await star.bulkAdd(doc)
},
async find (doc) {
return await star.get(doc)
},
async update (id, docs) {
return await star.update(id, docs)
},
async all () {
return await star.toArray()
},
async remove (id) {
return await star.delete(id)
},
async get (id) {
return await star.get(id)
},
async clear () {
return await star.clear()
}
}

View File

@@ -1,3 +0,0 @@
import Vue from 'vue'
import { Message } from 'element-ui'
Vue.prototype.$message = Message

View File

@@ -1,219 +0,0 @@
import open from 'open'
import axios from 'axios'
import cheerio from 'cheerio'
const onlineVideo = {
playVideoOnBde4 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://bde4.com/search/${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.search-list')
var searchResult = $(e).find('div>div>div>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
var detailPageFullLink = 'https://bde4.com/' + detailPageLink
// 解析详情页面
axios.get(detailPageFullLink).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.info1')
var videoList = $(e).find('a').toArray()
var videoFullLink = detailPageFullLink
// 获取index视频链接
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'https://bde4.com' + indexVideoLink
}
}
open(videoFullLink)
})
}
})
},
playVideoOnK1080 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://k1080.net/vodsearch123/-------------.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('li>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'https://k1080.net' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
var videoFullLink = detailPageFullLink
// 获取index视频链接
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'https://k1080.net' + indexVideoLink
}
}
open(videoFullLink)
})
}
})
},
playVideoOnSubaibai (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://www.subaibai.com/xssearch?q=${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.search_list')
var searchResult = $(e).find('div>ul>li>h3>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).text()
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('div.paly_list_btn')
// 获取所有视频链接
var videoList = $(e).find('a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = indexVideoLink
}
}
open(videoFullLink)
})
}
})
},
playVideoOnYhdm (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `http://www.yhdm.tv/search/${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.lpic')
var searchResult = $(e).find('div>ul>li>h2>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'http://www.yhdm.tv/' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('div.movurl')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'http://www.yhdm.tv/' + indexVideoLink
}
}
open(videoFullLink)
})
}
})
},
playVideoOndmdm2020 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `http://www.dmdm2020.com/dongmansearch.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('ul>li>div>h4>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).text()
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'http://www.dmdm2020.com' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'http://www.dmdm2020.com' + indexVideoLink
}
}
open(videoFullLink)
})
}
})
},
playVideoOnSyrme (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://syrme.top/searchs?q=${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('ul.MovieList')
var searchResult = $(e).find('ul>li>article>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).find('a>h2').text()
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'https://syrme.top' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#categories-2')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
videoFullLink = 'https://syrme.top' + indexVideoLink
}
open(videoFullLink)
})
}
})
}
}
export default onlineVideo

View File

@@ -1,18 +0,0 @@
import express from 'express'
import cors from 'cors'
const Axios = require('axios')
const app = express()
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.post('/api', async (req, res) => {
const result = await Axios.get(req.body.url)
res.json({
code: 1,
info: result.data
})
})
app.listen(44444)

View File

@@ -1,199 +0,0 @@
import { sites } from '../dexie'
import axios from 'axios'
import parser from 'fast-xml-parser'
const zy = {
xmlConfig: { // XML 转 JSON 配置
trimValues: true,
textNodeName: '_t',
ignoreAttributes: false,
attributeNamePrefix: '_',
parseAttributeValue: true
},
getSite (key) {
return new Promise((resolve, reject) => {
sites.all().then(res => {
for (const i of res) {
if (key === i.key) {
resolve(i)
}
}
}).catch(err => {
reject(err)
})
})
},
/**
* 获取资源分类 和 所有资源的总数, 分页等信息
* @param {*} key 资源网 key
* @returns
*/
class (key) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = res.api
axios.post(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const arr = []
if (json.rss.class) {
for (const i of json.rss.class.ty) {
const j = {
tid: i._id,
name: i._t
}
arr.push(j)
}
}
const doc = {
class: arr,
page: json.rss.list._page,
pagecount: json.rss.list._pagecount,
pagesize: json.rss.list._pagesize,
recordcount: json.rss.list._recordcount
}
resolve(doc)
}).catch(err => {
reject(err)
})
})
})
},
/**
* 获取资源列表
* @param {*} key 资源网 key
* @param {number} [pg=1] 翻页 page
* @param {*} t 分类 type
* @returns
*/
list (key, pg = 1, t) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
let url = null
if (t) {
url = `${site.api}?ac=videolist&t=${t}&pg=${pg}`
} else {
url = `${site.api}?ac=videolist&pg=${pg}`
}
axios.post(url).then(async res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
})
})
},
/**
* 获取总资源数, 以及页数
* @param {*} key 资源网
* @param {*} t 分类 type
* @returns page object
*/
page (key, t) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
let url = ''
if (t) {
url = `${site.api}?ac=videolist&t=${t}`
} else {
url = `${site.api}?ac=videolist`
}
axios.post(url).then(async res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const pg = {
page: json.rss.list._page,
pagecount: json.rss.list._pagecount,
pagesize: json.rss.list._pagesize,
recordcount: json.rss.list._recordcount
}
resolve(pg)
}).catch(err => {
reject(err)
})
})
})
},
/**
* 搜索资源
* @param {*} key 资源网 key
* @param {*} wd 搜索关键字
* @returns
*/
search (key, wd) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
wd = encodeURI(wd)
var url = `${site.api}?wd=${wd}`
axios.post(url, { timeout: 3000 }).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
if (json && json.rss && json.rss.list) {
const videoList = json.rss.list.video
resolve(videoList)
}
}).catch(err => {
reject(err)
})
}).catch(err => {
reject(err)
})
})
},
/**
* 获取资源详情
* @param {*} key 资源网 key
* @param {*} id 资源唯一标识符 id
* @returns
*/
detail (key, id) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = `${res.api}?ac=videolist&ids=${id}`
axios.post(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
}).catch(err => {
reject(err)
})
})
},
/**
* 下载资源
* @param {*} key 资源网 key
* @param {*} id 资源唯一标识符 id
* @returns
*/
download (key, id) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
const url = `${site.download}?ac=videolist&ids=${id}&ct=1`
if (url) {
axios.post(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
} else {
resolve([])
}
})
})
}
}
export default zy

View File

@@ -1,12 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import 'modern-normalize'
import Register from './components/register'
import './lib/element/index'
Register.registerComponents()
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')

View File

@@ -1,524 +0,0 @@
<template>
<div class="mini">
<div class="top">
<div class="left">
<span class="title">
<span v-if="m3u8Arr.length > 1"> {{(video.index + 1)}} </span>{{name}}
</span>
<span class="zy-svg" @click="prevEvent" v-show="video.index > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="backIconTitle">
<title id="backIconTitle">上一集</title>
<path d="M14 14.74L21 19V5l-7 4.26V5L2 12l12 7v-4.26z"></path>
</svg>
</span>
<span class="zy-svg" @click="nextEvent" v-show="video.index < (m3u8Arr.length - 1)">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="forwardIconTitle">
<title id="forwardIconTitle">下一集</title>
<path d="M10 14.74L3 19V5l7 4.26V5l12 7-12 7v-4.26z"></path>
</svg>
</span>
<span class="opacity" v-show="opacity !== 100">透明度: {{opacity}}</span>
<span class="rate" v-show="rate !== 1">播放速率: {{rate}}</span>
<span class="progress" v-show="progress > 0">播放进度: {{progress}}%</span>
</div>
<div class="right">
<span class="top" @click="frameClickEvent('top')" title="置顶">
<svg t="1595919317571" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1188" style="width:10px;height:14px"><path d="M43.072 974.72l380.864-301.952 151.936 161.6c0 0 63.424 17.28 67.328-30.72l-3.904-163.584 225.088-259.648 98.048-5.696c0 0 76.928-15.488 21.184-82.752l-275.072-276.928c0 0-74.944-9.6-69.248 59.584l0 75.008L383.552 367.104 225.856 376.64c0 0-57.728 19.2-36.608 69.248l148.16 146.176L43.072 974.72 43.072 974.72z" p-id="1189" :fill="isAlwaysOnTop ? '#555555' : '#ffffff'"></path></svg>
</span>
<span class="min" @click="frameClickEvent('min')" title="最小化">
<svg t="1595917239849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1155" style="width:8px;height:14px"><path d="M0 479.936C0 444.64 28.448 416 64.064 416L959.936 416C995.328 416 1024 444.736 1024 479.936L1024 544.064C1024 579.392 995.552 608 959.936 608L64.064 608C28.672 608 0 579.264 0 544.064L0 479.936Z" p-id="1156" fill="#ffffff"></path></svg>
</span>
<span class="max" @click="frameClickEvent('max')" title="最大化">
<svg t="1595917343956" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1540" style="width:8px;height:14px"><path d="M416 416 64.064 416C28.448 416 0 444.64 0 479.936L0 544.064C0 579.264 28.672 608 64.064 608L416 608 416 959.936C416 995.552 444.64 1024 479.936 1024L544.064 1024C579.264 1024 608 995.328 608 959.936L608 608 959.936 608C995.552 608 1024 579.36 1024 544.064L1024 479.936C1024 444.736 995.328 416 959.936 416L608 416 608 64.064C608 28.448 579.36 0 544.064 0L479.936 0C444.736 0 416 28.672 416 64.064L416 416Z" p-id="1541" fill="#ffffff"></path></svg>
</span>
<span class="close" @click="frameClickEvent('close')" title="关闭">
<svg t="1595917372551" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1685" style="width:8px;height:14px"><path d="M511.968 376.224 796.096 92.096C833.536 54.624 894.4 54.624 931.84 92.096 969.312 129.568 969.312 190.4 931.84 227.872L647.744 512 931.84 796.096C969.312 833.568 969.312 894.4 931.84 931.872 894.4 969.344 833.536 969.344 796.096 931.872L511.968 647.744 227.84 931.872C190.4 969.344 129.536 969.344 92.096 931.872 54.624 894.4 54.624 833.568 92.096 796.096L376.224 512 92.096 227.872C54.624 190.4 54.624 129.568 92.096 92.096 129.536 54.624 190.4 54.624 227.84 92.096L511.968 376.224Z" p-id="1686" fill="#ffffff"></path></svg>
</span>
</div>
</div>
<div class="bottom">
<div id="xg"></div>
</div>
</div>
</template>
<script>
import zy from '../lib/site/tools'
import { history, setting, shortcut, mini } from '../lib/dexie'
import mt from 'mousetrap'
import 'xgplayer'
import Hls from 'xgplayer-hls.js'
const { remote, ipcRenderer } = require('electron')
const VIDEO_DETAIL_CACHE = {}
export default {
name: 'mini',
data () {
const win = remote.getCurrentWindow()
return {
xg: null,
config: {
id: 'xg',
url: '',
lang: 'zh-cn',
width: '100%',
height: '100%',
autoplay: false,
videoInit: true,
screenShot: true,
keyShortcut: 'off',
crossOrigin: true,
cssFullscreen: true,
defaultPlaybackRate: 1,
playbackRate: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5],
controls: false
},
opacity: 100,
name: '',
video: {},
detail: {},
m3u8Arr: [],
rate: 1,
progress: 0,
isAlwaysOnTop: win.isAlwaysOnTop()
}
},
methods: {
frameClickEvent (e) {
const win = remote.getCurrentWindow()
if (e === 'min') {
win.minimize()
}
if (e === 'max') {
win.isMaximized() ? win.unmaximize() : win.maximize()
}
if (e === 'close') {
ipcRenderer.send('win')
return false
}
if (e === 'top') {
this.isAlwaysOnTop = !this.isAlwaysOnTop
win.setAlwaysOnTop(this.isAlwaysOnTop)
}
},
opacityChange (val) {
const win = remote.getCurrentWindow()
const num = val / 100
win.setOpacity(num)
return false
},
getUrls () {
mini.find().then(res => {
this.video = res
this.fetchM3u8List(res).then(m3u8Arr => {
this.m3u8Arr = m3u8Arr
this.xg.src = m3u8Arr[res.index]
if (res.time !== 0 || res.time !== '') {
this.xg.play()
this.xg.once('playing', () => {
this.xg.currentTime = res.time
})
} else {
this.xg.play()
}
this.videoPlaying()
this.xg.once('ended', () => {
if (m3u8Arr.length > 1 && (m3u8Arr.length - 1 > res.index)) {
this.video.time = 0
this.video.index++
this.xg.src = m3u8Arr[this.video.index]
this.xg.play()
}
})
})
})
},
fetchM3u8List (info) {
return new Promise((resolve) => {
const cacheKey = info.site + '@' + info.ids
if (VIDEO_DETAIL_CACHE[cacheKey]) {
this.name = VIDEO_DETAIL_CACHE[cacheKey].name
resolve(VIDEO_DETAIL_CACHE[cacheKey].list)
return
}
zy.detail(info.site, info.ids).then(res => {
this.name = res.name
const dd = res.dl.dd
const type = Object.prototype.toString.call(dd)
let m3u8Txt = []
if (type === '[object Array]') {
for (const i of dd) {
if (i._t.indexOf('m3u8') >= 0) {
m3u8Txt = i._t.split('#')
}
}
} else {
m3u8Txt = dd._t.split('#')
}
const m3u8Arr = []
for (const i of m3u8Txt) {
const j = i.split('$')
if (j.length > 1) {
for (let m = 0; m < j.length; m++) {
if (j[m].indexOf('m3u8') >= 0) {
m3u8Arr.push(j[m])
}
}
} else {
m3u8Arr.push(j[0])
}
}
VIDEO_DETAIL_CACHE[cacheKey] = {
list: m3u8Arr,
name: res.name
}
resolve(m3u8Arr)
})
})
},
videoPlaying () {
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
if (res) {
res.index = this.video.index
history.update(res.id, res)
} else {
const doc = {
site: this.video.site,
ids: this.video.ids,
name: this.video.name,
index: this.video.index,
time: 0
}
history.add(doc)
}
})
this.timerEvent()
},
timerEvent () {
this.timer = setInterval(() => {
const endTime = this.xg.duration
const currentTime = this.xg.currentTime
const progress = (currentTime / endTime) * 100
this.progress = progress.toFixed(2)
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
if (res) {
const v = res
v.time = this.xg.currentTime
v.index = this.video.index
const id = v.id
delete v.id
history.update(id, v)
}
})
}, 10000)
},
prevEvent () {
if (this.video.index === 0) {
this.$message.info('已是第一集.')
return false
}
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
const v = res
const id = v.id
v.index--
delete v.id
history.update(id, v).then(e => {
this.xg.src = this.m3u8Arr[v.index]
this.video.index--
})
})
},
nextEvent () {
if (this.video.index >= this.m3u8Arr.length - 1) {
this.$message.info('已是最后一集.')
return false
}
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
const v = res
v.index++
const id = v.id
delete v.id
history.update(id, v).then(e => {
this.xg.src = this.m3u8Arr[v.index]
this.video.index++
})
})
},
playbackRateEvent (e) {
let rate = this.xg.playbackRate
if (rate > 0.25) {
rate = rate + e
this.xg.playbackRate = rate
}
},
mtEvent () {
setting.find().then(res => {
if (res.shortcut) {
shortcut.all().then(res => {
for (const i of res) {
mt.bind(i.key, () => {
this.shortcutEvent(i.name)
})
}
})
}
})
},
shortcutEvent (e) {
if (e === 'playAndPause') {
if (this.xg) {
if (this.xg.paused) {
this.xg.play()
} else {
this.xg.pause()
}
}
return false
}
if (e === 'forward') {
if (this.xg && !this.xg.paused) {
this.xg.currentTime += 5
}
return false
}
if (e === 'back') {
if (this.xg && !this.xg.paused) {
this.xg.currentTime -= 5
}
return false
}
if (e === 'volumeUp') {
if (this.xg && this.xg.volume < 0.9) {
this.xg.volume += 0.1
}
return false
}
if (e === 'volumeDown') {
if (this.xg && this.xg.volume > 0.2) {
this.xg.volume -= 0.1
}
return false
}
if (e === 'mute') {
if (this.xg) {
this.xg.volume = 0
}
return false
}
if (e === 'top') {
const win = remote.getCurrentWindow()
if (win.isAlwaysOnTop()) {
win.setAlwaysOnTop(false)
} else {
win.setAlwaysOnTop(true)
}
return false
}
if (e === 'fullscreen') {
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
} else {
this.xg.getFullscreen(this.xg.root)
}
return false
}
if (e === 'escape') {
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
this.xg.exitCssFullscreen()
}
return false
}
if (e === 'next') {
this.nextEvent()
return false
}
if (e === 'prev') {
this.prevEvent()
return false
}
if (e === 'home') {
if (this.xg && !this.xg.paused) {
this.xg.currentTime = 0
}
return false
}
if (e === 'end') {
if (this.xg && !this.xg.paused) {
const endTime = this.xg.duration
this.xg.currentTime = endTime
}
return false
}
if (e === 'opacityUp') {
const win = remote.getCurrentWindow()
if (this.opacity >= 10) {
this.opacity -= 5
const num = this.opacity / 100
win.setOpacity(num)
}
return false
}
if (e === 'opacityDown') {
const win = remote.getCurrentWindow()
if (this.opacity <= 95) {
this.opacity += 5
const num = this.opacity / 100
win.setOpacity(num)
}
return false
}
if (e === 'playbackRateUp') {
if (this.xg && !this.xg.paused) {
const rate = this.xg.playbackRate
this.xg.playbackRate = rate + 0.25
this.rate = this.xg.playbackRate
}
return false
}
if (e === 'playbackRateDown') {
if (this.xg && !this.xg.paused) {
const rate = this.xg.playbackRate
if (rate > 0.25) {
this.xg.playbackRate = rate - 0.25
this.rate = this.xg.playbackRate
}
}
return false
}
if (e === 'mini') {
ipcRenderer.send('win')
return false
}
}
},
mounted () {
this.xg = new Hls(this.config)
this.mtEvent()
this.getUrls()
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
<style lang="scss">
html,body{
padding: 1px;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
background-color: #000;
}
.mini{
-webkit-app-region: drag;
box-sizing: border-box;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
.top{
width: 100%;
height: 30px;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
.zy-svg{
-webkit-app-region: no-drag;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
svg{
width: 24px;
height: 24px;
stroke: #888;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
.left{
display: flex;
justify-content: flex-start;
align-items: center;
height: 100%;
flex: 1;
.title, .opacity, .rate, .progress{
color: #888;
font-size: 12px;
margin: 0 10px;
}
}
.right{
width: 80px;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
span{
-webkit-app-region: no-drag;
display: inline-block;
width: 14px;
height: 14px;
text-align: center;
line-height: 14px;
border-radius: 50%;
margin-right: 10px;
cursor: pointer;
opacity: 0.4;
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
&.top{
background-color: #f3bab7;
}
&:hover{
animation: heartbeat 3s ease-in-out infinite both;
}
@keyframes heartbeat {
from {
transform: scale(1);
transform-origin: center center;
animation-timing-function: ease-out;
}
10% {
opacity: 1;
transform: scale(0.91);
animation-timing-function: ease-in;
}
17% {
transform: scale(0.98);
animation-timing-function: ease-out;
}
33% {
transform: scale(0.87);
animation-timing-function: ease-in;
}
45% {
transform: scale(1);
animation-timing-function: ease-out;
}
}
}
}
}
.bottom{
width: 100%;
flex: 1;
.xgplayer-start{
-webkit-app-region: no-drag;
}
}
}
</style>

View File

@@ -1,10 +0,0 @@
import Vue from 'vue'
import Mini from './Mini'
import 'modern-normalize'
import '../lib/element/index'
Vue.config.productionTip = false
new Vue({
render: h => h(Mini)
}).$mount('#app')

View File

@@ -1,74 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
view: 'Film',
setting: {
theme: 'light',
site: 'zuidazy',
view: 'picture',
shortcut: true
},
detail: {
show: false,
key: '',
info: {}
},
share: {
show: false,
key: '',
info: {}
},
video: {
key: '',
info: {}
},
editSites: {
show: false,
sites: []
}
},
getters: {
getView: state => {
return state.view
},
getSetting: state => {
return state.setting
},
getDetail: state => {
return state.detail
},
getShare: state => {
return state.share
},
getVideo: state => {
return state.video
},
getEditSites: state => {
return state.editSites
}
},
mutations: {
SET_VIEW: (state, payload) => {
state.view = payload
},
SET_SETTING: (state, payload) => {
state.setting = payload
},
SET_DETAIL: (state, payload) => {
state.detail = payload
},
SET_SHARE: (state, payload) => {
state.share = payload
},
SET_VIDEO: (state, payload) => {
state.video = payload
},
SET_EDITSITES: (state, payload) => {
state.editSites = payload
}
}
})