🎉 新版本内测 🎊🎏

This commit is contained in:
hunlongyu
2020-07-10 23:24:28 +08:00
parent e0ae32027a
commit 8902282fe2
45 changed files with 12658 additions and 19811 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea

16007
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,14 @@
{
"name": "zy",
"version": "1.0.23",
"version": "1.1.0",
"private": true,
"author": {
"name": "Hunlongyu",
"email": "hunlongyu@gmail.com"
},
"description": "ZY Player 资源播放器",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"electron:generate-icons": "electron-icon-builder --input=./public/icon.png --output=build --flatten",
@@ -23,38 +19,42 @@
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.6.5",
"dexie": "^2.0.4",
"electron-updater": "^4.3.1",
"cors": "^2.8.5",
"dexie": "^3.0.1",
"electron-localshortcut": "^3.2.1",
"element-ui": "^2.13.2",
"express": "^4.17.1",
"fast-xml-parser": "^3.17.4",
"html2canvas": "^1.0.0-rc.5",
"leancloud-storage": "^4.5.3",
"macaddress": "^0.5.1",
"modern-normalize": "^0.6.0",
"mousetrap": "^1.6.5",
"qrcode.vue": "^1.7.0",
"vue": "^2.6.11",
"vue-i18n": "^8.17.7",
"vue-infinite-loading": "^2.4.5",
"vue-waterfall-plugin": "^1.0.7",
"vuex": "^3.4.0",
"xgplayer": "^2.7.1",
"xgplayer-hls.js": "^2.2.2"
"xgplayer": "^2.9.8",
"xgplayer-hls.js": "^2.2.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.3.0",
"@vue/cli-plugin-eslint": "~4.3.0",
"@vue/cli-plugin-vuex": "~4.3.0",
"@vue/cli-service": "~4.3.0",
"@vue/cli-plugin-babel": "~4.4.0",
"@vue/cli-plugin-eslint": "~4.4.0",
"@vue/cli-plugin-vuex": "~4.4.0",
"@vue/cli-service": "~4.4.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"electron": "^9.0.0",
"electron": "^9.0.5",
"electron-devtools-installer": "^3.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"sass": "^1.26.3",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-cli-plugin-electron-builder": "2.0.0-beta.6",
"vue-cli-plugin-electron-builder": "2.0.0-rc.4",
"vue-template-compiler": "^2.6.11"
}
}

View File

@@ -4,17 +4,8 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>icon.png">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?62aeb2505bfa26a2461d2a7a3b485096";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<noscript>

View File

@@ -16,6 +16,7 @@
</transition>
</div>
</template>
<script>
export default {
name: 'App',
@@ -34,18 +35,21 @@ export default {
share () {
return this.$store.getters.getShare
},
theme () {
return this.$store.getters.getTheme
setting () {
return this.$store.getters.getSetting
}
},
watch: {
theme () {
this.changeTheme()
setting: {
handler () {
this.changeSetting()
},
deep: true
}
},
methods: {
changeTheme () {
this.appTheme = `theme-${this.theme}`
changeSetting () {
this.appTheme = `theme-${this.setting.theme}`
}
}
}

View File

@@ -42,6 +42,22 @@
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%;
@@ -69,37 +85,8 @@
flex-direction: column;
height: 100%;
font-size: 15px;
.tHead{
height: 50px;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
border-bottom: 1px solid;
padding: 0 5px 0 0;
font-weight: 600;
span{
display: flex;
width: 180px;
font-size: 16px;
&.name{
flex: 1;
padding-left: 15px;
}
&.type{
width: 120px;
}
&.from{
width: 120px;
}
&.operate{
width: 170px;
}
}
}
.tBody{
flex: 1;
overflow-y: scroll;
border-bottom: 1px solid;
ul{
list-style: none;
@@ -126,9 +113,18 @@
text-overflow: ellipsis;
white-space: nowrap;
}
&.note{
width: 180px;
}
&.type{
width: 120px;
}
&.last{
width: 160px;
}
&.time{
width: 60px;
}
&.from{
width: 120px;
}
@@ -139,22 +135,6 @@
}
}
}
.tFooter{
width: 100%;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
padding-right: 10px;
.tFooter-span{
padding-left: 10px;
font-size: 12px;
}
.btn{
cursor: pointer;
}
}
}
// scroll

View File

@@ -12,10 +12,10 @@
--l-fc-3: #823aa0;
--l-bgc-1: #ffffff;
--l-bgc-2: #f2f6f9;
--l-bsc: 0 3px 1px -2px #8e8da233, 0 2px 2px 0 #8e8da224, 0 1px 5px 0 #8e8da21f;
--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-hover: 0 14px 26px -12px #8e8da26b, 0 4px 23px 0 #8e8da21f, 0 8px 10px -5px #8e8da233;
--l-bsc-scroll: inset 0 0 5px #823aa005;
--l-bsc-scroll: inset 0 0 5px #823aa000;
// dark
--d-c-0: #38dd77;
@@ -30,9 +30,9 @@
--d-fc-3: #38dd77;
--d-bgc-1: #222222;
--d-bgc-2: #2f2f2f;
--d-bsc: 0 3px 1px -2px #38dd7733, 0 2px 2px 0 #38dd7722, 0 1px 5px 0 #38dd7711;
--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-hover: 0 14px 26px -12px #38dd7733, 0 4px 23px 0 #38dd7722, 0 8px 10px -5px #38dd7711;
--d-bsc-scroll: inset 0 0 5px #38dd7705;
// green
@@ -48,9 +48,9 @@
--g-fc-3: #C1D95C;
--g-bgc-1: #4baea0;
--g-bgc-2: #74b4ac;
--g-bsc: 0 3px 1px -2px #e1ebe033, 0 2px 2px 0 #e1ebe022, 0 1px 5px 0 #e1ebe011;
--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-hover: 0 14px 26px -12px #e1ebe033, 0 4px 23px 0 #e1ebe022, 0 8px 10px -5px #e1ebe011;
--g-bsc-scroll: inset 0 0 5px #e1ebe005;
// pink
@@ -66,9 +66,9 @@
--p-fc-3: #f15c5c;
--p-bgc-1: #ff8499;
--p-bgc-2: #fea1b2;
--p-bsc: 0 3px 1px -2px #ef528533, 0 2px 2px 0 #ef528522, 0 1px 5px 0 #ef528511;
--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-hover: 0 14px 26px -12px #ef528533, 0 4px 23px 0 #ef528522, 0 8px 10px -5px #ef528511;
--p-bsc-scroll: inset 0 0 5px #ef528505;
}

View File

@@ -1,37 +1,10 @@
.theme-dark{
background-color: var(--d-bgc-1);
.el-pagination{
background-color: var(--d-bgc-1);
color: var(--d-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
}
.el-input__inner{
border-color: var(--d-c-3);
}
.el-pager{
.number{
background-color: var(--d-bgc-1);
}
.number:hover{
color: var(--d-c-8);
}
.active{
color: var(--d-c-9);
}
}
.more, .btn-next, .btn-prev{
background-color: var(--d-bgc-1);
&:hover{
color: var(--d-c-8);
}
}
}
.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);
}
@@ -39,6 +12,7 @@
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--d-c-1);
@@ -49,25 +23,25 @@
}
}
}
.vs-input{
input{
color: var(--d-fc-1);
&::-webkit-input-placeholder{
color: var(--d-fc-1);
}
}
}
}
.zy-table{
color: var(--d-fc-2);
.tHead{
background-color: var(--d-bgc-1);
border-bottom-color: var(--d-c-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{
animation: d-tableHoverAni 0.2s ease both;
@keyframes d-tableHoverAni {
to{
box-shadow: var(--d-bsc-hover);
}
}
box-shadow: var(--d-bsc-hover);
}
span{
&.btn:hover{
@@ -77,21 +51,16 @@
}
}
}
.tFooter{
.tFooter-span{
color: var(--d-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--d-bsc-scroll);
background: var(--d-c-3);
background: var(--d-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--d-bsc-scroll);
background: var(--bgc);
background: var(--d-bgc-1);
}
}
}
@@ -112,6 +81,7 @@
background-color: var(--d-c-2);
}
&.active{
background-color: var(--d-bgc-2);
svg{
stroke: var(--d-c-0);
stroke-width: 2;
@@ -123,6 +93,9 @@
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
@@ -146,10 +119,17 @@
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
.info, .desc, .m3u8, .operate{
border-color: var(--d-c-2);
}
.m3u8_urls, .mp4_urls{
.operate{
span{
&:hover{
color: var(--d-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--d-c-5);
@@ -165,36 +145,20 @@
}
}
.film{
.top{
.search{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
&:hover{
box-shadow: var(--d-bsc-hover);
}
svg{
stroke: var(--d-c-0);
stroke-width: 1;
fill: none;
}
.search-box{
background-color: var(--d-bgc-1);
}
&.active{
box-shadow: var(--d-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
input{
color: var(--d-fc-1);
}
}
}
.middle{
.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{
@@ -283,10 +247,6 @@
}
}
}
.play-mask{
background-color: var(--d-bgc-1);
color: var(--d-fc-1);
}
}
.star{
background-color: var(--d-bgc-1);
@@ -303,12 +263,18 @@
}
}
}
.view, .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{
@@ -326,6 +292,10 @@
}
.qrcode-item{
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
}
}
}
}

View File

@@ -1,37 +1,10 @@
.theme-green{
background-color: var(--g-bgc-1);
.el-pagination{
background-color: var(--g-bgc-1);
color: var(--g-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
}
.el-input__inner{
border-color: var(--g-c-3);
}
.el-pager{
.number{
background-color: var(--g-bgc-1);
}
.number:hover{
color: var(--g-c-8);
}
.active{
color: var(--g-c-9);
}
}
.more, .btn-next, .btn-prev{
background-color: var(--g-bgc-1);
&:hover{
color: var(--g-c-8);
}
}
}
.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);
}
@@ -39,6 +12,7 @@
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--g-c-1);
@@ -49,25 +23,25 @@
}
}
}
.vs-input{
input{
color: var(--g-fc-1);
&::-webkit-input-placeholder{
color: var(--g-fc-1);
}
}
}
}
.zy-table{
color: var(--g-fc-2);
.tHead{
background-color: var(--g-bgc-1);
border-bottom-color: var(--g-c-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{
animation: d-tableHoverAni 0.2s ease both;
@keyframes d-tableHoverAni {
to{
box-shadow: var(--g-bsc-hover);
}
}
box-shadow: var(--g-bsc-hover);
}
span{
&.btn:hover{
@@ -77,21 +51,16 @@
}
}
}
.tFooter{
.tFooter-span{
color: var(--g-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--g-bsc-scroll);
background: var(--g-c-3);
background: var(--g-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--g-bsc-scroll);
background: var(--bgc);
background: var(--g-bgc-1);
}
}
}
@@ -112,6 +81,7 @@
background-color: var(--g-c-2);
}
&.active{
background-color: var(--g-bgc-2);
svg{
stroke: var(--g-c-0);
stroke-width: 2;
@@ -123,6 +93,9 @@
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
@@ -131,7 +104,7 @@
}
}
.detail{
color: var(--g-fc-1);
color: var(--g-fc-1) !important;
background-color:var(--g-bgc-1);
box-shadow: var(--g-bsc-2);
.detail-content{
@@ -146,10 +119,17 @@
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
.info, .desc, .m3u8, .operate{
border-color: var(--g-c-2);
}
.m3u8_urls, .mp4_urls{
.operate{
span{
&:hover{
color: var(--g-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--g-c-5);
@@ -165,36 +145,20 @@
}
}
.film{
.top{
.search{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
&:hover{
box-shadow: var(--g-bsc-hover);
}
svg{
stroke: var(--g-c-0);
stroke-width: 1;
fill: none;
}
.search-box{
background-color: var(--g-bgc-1);
}
&.active{
box-shadow: var(--g-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
input{
color: var(--g-fc-1);
}
}
}
.middle{
.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{
@@ -283,10 +247,6 @@
}
}
}
.play-mask{
background-color: var(--g-bgc-1);
color: var(--g-fc-1);
}
}
.star{
background-color: var(--g-bgc-1);
@@ -303,12 +263,18 @@
}
}
}
.view, .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{
@@ -326,6 +292,10 @@
}
.qrcode-item{
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
}
}
}
}

View File

@@ -1,28 +1,10 @@
.theme-light{
background-color: var(--l-bgc-1);
.el-pagination{
color: var(--l-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--l-fc-1);
}
.el-pager{
.number:hover{
color: var(--l-c-8);
}
.active{
color: var(--l-c-9);
}
}
.more, .btn-next, .btn-prev{
&:hover{
color: var(--l-c-8);
}
}
}
.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);
}
@@ -30,6 +12,7 @@
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--l-c-1);
@@ -40,25 +23,25 @@
}
}
}
.vs-input{
input{
color: var(--l-fc-1);
&::-webkit-input-placeholder{
color: var(--l-fc-1);
}
}
}
}
.zy-table{
color: var(--l-fc-2);
.tHead{
background-color: var(--l-bgc-1);
border-bottom-color: var(--l-c-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{
animation: l-tableHoverAni 0.2s ease both;
@keyframes l-tableHoverAni {
to{
box-shadow: var(--l-bsc-hover);
}
}
box-shadow: var(--l-bsc-hover);
}
span{
&.btn:hover{
@@ -68,21 +51,16 @@
}
}
}
.tFooter{
.tFooter-span{
color: var(--l-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--l-bsc-scroll);
background: var(--l-c-3);
background: var(--l-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--l-bsc-scroll);
background: var(--bgc);
background: var(--l-bgc-1);
}
}
}
@@ -103,6 +81,7 @@
background-color: var(--l-c-2);
}
&.active{
background-color: var(--l-bgc-2);
svg{
stroke: var(--l-c-0);
stroke-width: 2;
@@ -114,6 +93,9 @@
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
@@ -122,7 +104,7 @@
}
}
.detail{
color: var(--l-fc-1);
color: var(--l-fc-1) !important;
background-color:var(--l-bgc-1);
box-shadow: var(--l-bsc-2);
.detail-content{
@@ -137,10 +119,17 @@
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
.info, .desc, .m3u8, .operate{
border-color: var(--l-c-2);
}
.m3u8_urls, .mp4_urls{
.operate{
span{
&:hover{
color: var(--l-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--l-c-5);
@@ -156,43 +145,27 @@
}
}
.film{
.top{
.search{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
&:hover{
box-shadow: var(--l-bsc-hover);
}
svg{
stroke: #823aa099;
stroke-width: 1;
fill: none;
}
.search-box{
background-color: none;
}
&.active{
box-shadow: var(--l-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
input{
color: var(--l-fc-1);
}
}
}
.middle{
.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(--d-fc-1);
color: var(--l-fc-1);
}
.box{
.more{
@@ -218,9 +191,6 @@
}
}
}
.mask{
background-color: var(--l-bgc-1);
}
}
.list{
border: 1px solid var(--l-c-3);
@@ -277,10 +247,6 @@
}
}
}
.play-mask{
background-color: var(--l-bgc-1);
color: var(--l-fc-1);
}
}
.star{
background-color: var(--l-bgc-1);
@@ -297,12 +263,18 @@
}
}
}
.view, .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{
@@ -320,6 +292,10 @@
}
.qrcode-item{
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
}
}
}
}

View File

@@ -1,37 +1,10 @@
.theme-pink{
background-color: var(--p-bgc-1);
.el-pagination{
background-color: var(--p-bgc-1);
color: var(--p-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--p-fc-1);
background-color: var(--p-bgc-1);
}
.el-input__inner{
border-color: var(--p-c-3);
}
.el-pager{
.number{
background-color: var(--p-bgc-1);
}
.number:hover{
color: var(--p-c-8);
}
.active{
color: var(--p-c-9);
}
}
.more, .btn-next, .btn-prev{
background-color: var(--p-bgc-1);
&:hover{
color: var(--p-c-8);
}
}
}
.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);
}
@@ -39,6 +12,7 @@
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
ul{
overflow-y: scroll;
li{
&:hover{
background-color: var(--p-c-1);
@@ -49,25 +23,25 @@
}
}
}
.vs-input{
input{
color: var(--p-fc-1);
&::-webkit-input-placeholder{
color: var(--p-fc-1);
}
}
}
}
.zy-table{
color: var(--p-fc-2);
.tHead{
background-color: var(--p-bgc-1);
border-bottom-color: var(--p-c-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{
animation: d-tableHoverAni 0.2s ease both;
@keyframes d-tableHoverAni {
to{
box-shadow: var(--p-bsc-hover);
}
}
box-shadow: var(--p-bsc-hover);
}
span{
&.btn:hover{
@@ -77,21 +51,16 @@
}
}
}
.tFooter{
.tFooter-span{
color: var(--p-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--p-bsc-scroll);
background: var(--p-c-3);
background: var(--p-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--p-bsc-scroll);
background: var(--bgc);
background: var(--p-bgc-1);
}
}
}
@@ -112,6 +81,7 @@
background-color: var(--p-c-2);
}
&.active{
background-color: var(--p-bgc-2);
svg{
stroke: var(--p-c-0);
stroke-width: 2;
@@ -123,6 +93,9 @@
.frame{
span{
&.min{
background-color: #32dc36;
}
&.max{
background-color: #ffbe2a;
}
&.close{
@@ -131,7 +104,7 @@
}
}
.detail{
color: var(--p-fc-1);
color: var(--p-fc-1) !important;
background-color:var(--p-bgc-1);
box-shadow: var(--p-bsc-2);
.detail-content{
@@ -146,10 +119,17 @@
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
.info, .desc, .m3u8, .operate{
border-color: var(--p-c-2);
}
.m3u8_urls, .mp4_urls{
.operate{
span{
&:hover{
color: var(--p-fc-2);
}
}
}
.m3u8{
.box{
span{
border-color: var(--p-c-5);
@@ -165,36 +145,20 @@
}
}
.film{
.top{
.search{
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
&:hover{
box-shadow: var(--p-bsc-hover);
}
svg{
stroke: var(--p-c-0);
stroke-width: 1;
fill: none;
}
.search-box{
background-color: var(--p-bgc-1);
}
&.active{
box-shadow: var(--p-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--p-c-2);
}
}
input{
color: var(--p-fc-1);
}
}
}
.middle{
.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{
@@ -283,10 +247,6 @@
}
}
}
.play-mask{
background-color: var(--p-bgc-1);
color: var(--p-fc-1);
}
}
.star{
background-color: var(--p-bgc-1);
@@ -303,12 +263,18 @@
}
}
}
.view, .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{
@@ -326,6 +292,10 @@
}
.qrcode-item{
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
}
}
}
}

View File

@@ -1,42 +1,34 @@
'use strict'
import { app, ipcMain, protocol, BrowserWindow } from 'electron'
import {
createProtocol
// installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
import path from 'path'
import { autoUpdater } from 'electron-updater'
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'
const globalShortcut = require('electron').globalShortcut
let win
let mini
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
function createWindow() {
function createWindow () {
win = new BrowserWindow({
width: 1080,
width: 1680,
height: 720,
frame: false,
resizable: true,
transparent: true,
webPreferences: {
webSecurity: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
},
// eslint-disable-next-line
icon: path.join(__static, 'icon.png')
}
})
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')
autoUpdater.checkForUpdatesAndNotify()
}
win.on('closed', () => {
@@ -44,28 +36,25 @@ function createWindow() {
})
}
function createMini() {
function createMini () {
mini = new BrowserWindow({
width: 550,
minWidth: 260,
width: 1150,
miniWidth: 860,
height: 340,
minHeight: 180,
miniHeight: 180,
frame: false,
resizable: true,
transparent: true,
alwaysOnTop: true,
webPreferences: {
webSecurity: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
},
// eslint-disable-next-line
icon: path.join(__static, 'icon.png')
}
})
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')
}
@@ -77,12 +66,10 @@ function createMini() {
if (process.platform === 'darwin') {
app.dock.show()
}
app.allowRendererProcessReuse = true
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
if (process.platform === 'Linux') {
app.disableHardwareAcceleration()
}
app.allowRendererProcessReuse = true
app.on('window-all-closed', () => {
app.quit()
@@ -92,31 +79,17 @@ app.on('activate', () => {
if (win === null) {
createWindow()
}
if (mini === null) {
createMini()
}
})
ipcMain.on('min', () => {
win.minimize()
})
ipcMain.on('close', () => {
win.close()
})
ipcMain.on('mini', () => {
createMini()
win.close()
win.hide()
})
ipcMain.on('miniMin', () => {
mini.minimize()
})
ipcMain.on('miniClose', () => {
mini.close()
createWindow()
})
ipcMain.on('miniOpacity', (e, arg) => {
mini.setOpacity(arg)
ipcMain.on('win', () => {
mini.destroy()
win.show()
win.webContents.send('miniClosed')
})
const gotTheLock = app.requestSingleInstanceLock()
@@ -124,65 +97,32 @@ if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 当运行第二个实例时,将会聚焦到win这个窗口
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
}
})
// 创建 win, 加载应用的其余部分, etc...
app.on('ready', () => {
globalShortcut.register('CommandOrControl+right', function () {
if (win) {
win.webContents.send('next', 0)
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())
}
if (mini) {
mini.webContents.send('next', 0)
}
})
globalShortcut.register('CommandOrControl+left', function () {
if (win) {
win.webContents.send('prev', 0)
}
if (mini) {
mini.webContents.send('prev', 0)
}
})
globalShortcut.register('CommandOrControl+up', function () {
if (mini) {
mini.webContents.send('up', 0)
}
})
globalShortcut.register('CommandOrControl+down', function () {
if (mini) {
mini.webContents.send('down', 0)
}
})
globalShortcut.register('shift+up', function () {
if (win) {
win.webContents.send('playbackRateUp', 0)
}
if (mini) {
mini.webContents.send('playbackRateUp', 0)
}
})
globalShortcut.register('shift+down', function () {
if (win) {
win.webContents.send('playbackRateDown', 0)
}
if (mini) {
mini.webContents.send('playbackRateDown', 0)
}
})
if (!process.env.WEBPACK_DEV_SERVER_URL) {
createProtocol('app')
}
createWindow()
globalShortcut.register('Alt+Space', () => {
if (win) {
win.isFocused() ? win.blur() : win.focus()
}
if (mini) {
mini.isFocused() ? mini.blur() : mini.focus()
}
})
})
}
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {

View File

@@ -2,7 +2,7 @@
<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">{{$t('view')}}</title>
<title id="apertureIconTitle">view</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>
@@ -16,19 +16,19 @@
</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">{{$t('play')}}</title>
<title id="playIconTitle">play</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">{{$t('star')}}</title>
<title id="favouriteIconTitle">star</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 === '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">{{$t('setting')}}</title>
<title id="settingsIconTitle">setting</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>
@@ -61,17 +61,17 @@ export default {
.aside{
width: 60px;
height: 100%;
user-select: none;
-webkit-app-region: drag;
display: flex;
justify-content: center;
user-select: none;
align-items: center;
flex-direction: column;
justify-content: center;
-webkit-app-region: drag;
span{
-webkit-app-region: no-drag;
width: 60px;
height: 60px;
cursor: pointer;
-webkit-app-region: no-drag;
}
}
</style>

View File

@@ -2,28 +2,41 @@
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<span class="detail-title">{{$t('detail')}}</span>
<span class="detail-close zy-svg" @click="closeDetail">
<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">{{$t('close')}}</title>
<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" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<div class="info" v-html="vDetail.info"></div>
<div class="desc" v-html="vDetail.desc" v-if="show.desc"></div>
<div class="m3u8_urls">
<div class="title">{{$t('play')}}:</div>
<div class="box">
<span v-for="(i, j) in vDetail.m3u8_urls" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
<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>
</div>
<div class="mp4_urls" v-if="show.download">
<div class="title">{{$t('download')}}:</div>
<div class="operate">
<span @click="playEvent(0)">播放</span>
<span @click="starEvent">收藏</span>
<span @click="downloadEvent">下载</span>
<span @click="shareEvent">分享</span>
</div>
<div class="desc" v-show="info.des">{{info.des}}</div>
<div class="m3u8">
<div class="box">
<span v-for="(i, j) in vDetail.mp4_urls" :key="j" @click="download(i)">{{i | ftName}}</span>
<span @click="allDownload" v-show="vDetail.mp4_urls.length > 1">{{$t('all_download')}}</span>
<span v-for="(i, j) in m3u8List" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
</div>
</div>
</div>
@@ -35,19 +48,16 @@
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
import zy from '../lib/site/tools'
import { star, history } from '../lib/dexie'
const { clipboard } = require('electron')
export default {
name: 'detail',
data () {
return {
scroll: false,
loading: true,
vDetail: {},
show: {
desc: false,
download: false
}
m3u8List: [],
info: {}
}
},
filters: {
@@ -65,6 +75,14 @@ export default {
this.SET_VIEW(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
@@ -73,71 +91,125 @@ export default {
this.SET_VIDEO(val)
}
},
detail: {
share: {
get () {
return this.$store.getters.getDetail
return this.$store.getters.getShare
},
set (val) {
this.SET_DETAIL(val)
this.SET_SHARE(val)
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_VIDEO', 'SET_DETAIL']),
closeDetail () {
...mapMutations(['SET_VIEW', 'SET_VIDEO', 'SET_DETAIL', 'SET_SHARE']),
close () {
this.detail.show = false
},
getDetail () {
tools.detail_get(this.detail.v.site, this.detail.v.detail).then(res => {
this.vDetail = res
if (res.desc.length > 0) {
this.show.desc = true
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('#')
}
}
if (res.mp4_urls.length > 0) {
this.show.download = true
}
this.$nextTick(() => {
this.loading = false
})
})
} else {
this.m3u8List = dd._t.split('#')
}
},
playEvent (n) {
const v = { ...this.detail.v }
v.index = n
this.video = v
this.detail.show = false
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 } }
} else {
this.video = { key: this.detail.key, info: { id: this.detail.info.id, name: this.detail.info.name, index: n } }
}
})
this.view = 'Play'
this.detail.show = false
},
download (e) {
const name = e.split('$')[0]
const txt = encodeURI(e.split('$')[1])
clipboard.writeText(txt)
this.$m.success(name + this.$t('copy_success'))
starEvent () {
star.find({ site: this.detail.key, ids: this.info.id }).then(res => {
if (res) {
this.$message.info('已存在')
} else {
const docs = {
site: this.detail.key,
ids: this.info.id,
name: this.info.name,
type: this.info.type,
year: this.info.year,
last: this.info.last
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
}).catch(() => {
this.$message.warning('收藏失败')
})
},
allDownload () {
const urls = [...this.vDetail.mp4_urls]
let txt = ''
for (const i of urls) {
const url = encodeURI(i.split('$')[1])
txt += (url + '\n')
downloadEvent () {
zy.download(this.detail.key, this.info.id).then(res => {
if (res) {
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 {
const list = [...this.m3u8List]
let downloadUrl = ''
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
}
clipboard.writeText(txt)
this.$m.success(this.$t('copy_success'))
},
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.m3u8Parse(res)
this.loading = false
}
})
}
},
created () {
this.getDetail()
this.getDetailInfo()
}
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.detail{
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: calc(100% - 40px);
z-index: 999;
z-index: 888;
.detail-content{
height: calc(100% - 10px);
padding: 0 60px;
@@ -146,9 +218,8 @@ export default {
width: 100%;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 -40px;
justify-content: space-between;
.detail-title{
font-size: 16px;
}
@@ -156,191 +227,131 @@ export default {
cursor: pointer;
}
}
.detail-body{
height: calc(100% - 50px);
overflow-y: auto;
.info{
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
width: 100%;
padding: 10px;
border: 1px solid;
border-radius: 2px;
margin-bottom: 10px;
.vodImg{
width: 200px;
img{
width: 100%;
height: auto;
}
}
.vodAd{
display: none;
}
.vodInfo{
flex: 1;
margin-left: 20px;
overflow: hidden;
.vodh{
margin-bottom: 6px;
h2{
display: inline-block;
margin: 0;
}
span{
font-size: 12px;
margin-left: 10px;
}
label{
font-size: 20px;
font-weight: bold;
margin-left: 20px;
}
}
.cont, .tags{
display: none;
}
ul{
padding: 0;
margin: 0;
}
a{
display: none;
pointer-events: none;
}
li{
list-style: none;
font-size: 14px;
line-height: 18px;
height: 18px;
overflow: hidden;
span{
word-wrap: nowrap;
}
}
}
.whitetitle{
}
.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%;
font-size: 22px;
height: auto;
}
}
.info-right{
flex: 1;
margin-left: 20px;
.name{
font-size: 20px;
margin-bottom: 10px;
font-weight: bold;
margin: 4px 0;
}
.people{
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
.left{
width: 200px;
img{
width: 100%;
height: auto;
}
}
.right{
flex: 1;
margin-left: 20px;
overflow: hidden;
p{
font-size: 14px;
}
a{
pointer-events: none;
text-decoration: none;
}
}
.director, .actor, .type, .area, .lang, .year, .last, .note{
font-size: 14px;
line-height: 26px;
}
}
.desc{
border: 1px solid;
padding: 10px;
width: 100%;
margin-bottom: 10px;
border-radius: 2px;
font-size: 14px;
line-height: 20px;
}
.m3u8_urls, .mp4_urls{
border: 1px solid;
padding: 10px;
width: 100%;
margin-bottom: 10px;
border-radius: 2px;
.title{
font-size: 16px;
}
.box{
width: 100%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
span{
font-size: 12px;
border: 1px solid;
border-radius: 2px;
cursor: pointer;
margin: 6px 6px 0px 0px;
padding: 8px 22px;
}
&::after {
content: '';
flex: 1;
}
}
}
.mp4_urls{
margin-bottom: 10px;
}
}
.detail-mask{
position: absolute;
top: 50px;
left: 0;
.operate{
border: 1px solid;
padding: 10px;
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);
margin-bottom: 10px;
border-radius: 2px;
span{
margin-right: 20px;
font-size: 14px;
cursor: pointer;
user-select: none;
}
@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;
}
.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,117 +1,118 @@
<template>
<div class="film">
<div class="top" v-if="top">
<!-- site -->
<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>
<li :class="site === j ? 'active' : ''" v-for="(i, j) in sites" :key="j" @click="siteClick(i)">{{ i.name }}</li>
<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>
<!-- tags -->
<div class="zy-select" @mouseleave="show.tags = false" v-if="site.tags.length > 0 && keywords.length <= 0">
<div class="vs-placeholder" @click="show.tags = true">{{site.tags[tag].title}}</div>
<div class="vs-options" v-show="show.tags">
<ul>
<li :class="tag === j ? 'active' : ''" v-for="(i, j) in site.tags" :key="j" @click="tagClick(i, j)">{{ i.title }}</li>
<div class="zy-select" @mouseleave="show.classList = false" v-if="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>
<!-- type -->
<div class="zy-select" @mouseleave="show.type = false" v-if="site.tags[tag].children.length > 0 && keywords.length <= 0">
<div class="vs-placeholder" @click="show.type = true">{{typeName}}</div>
<div class="vs-options" v-show="show.type">
<ul>
<li :class="type === j ? 'active' : ''" v-for="(i, j) in site.tags[tag].children" :key="j" @click="typeClick(i, j)">{{ i.title }}</li>
<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"></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="searchClickEvent(i)">{{i.keywords}}</li>
<li @click="clearSearch">清空历史记录</li>
</ul>
</div>
</div>
<div :class="[inputFocus ? 'active ': ''] + 'search'" @mouseover="inputFocus = true" @mouseleave="inputFocus = false">
<div class="search-icon">
<span class="zy-svg">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="searchIconTitle">
<title id="searchIconTitle">Search</title>
<path d="M14.4121122,14.4121122 L20,20"></path>
<circle cx="10" cy="10" r="6"></circle>
</svg>
</span>
</div>
<input type="text" class="search-box" v-model="keywords" @keypress.enter="searchEvent">
</div>
</div>
<div class="middle">
<div class="zy-table">
<div class="tHead">
<span class="name">{{$t('videoName')}}</span>
<span class="type">{{$t('type')}}</span>
<span class="time">{{$t('time')}}</span>
<span class="operate">{{$t('operate')}}</span>
</div>
<div class="tBody zy-scroll">
<ul v-show="!tb.loading">
<li v-for="(i, j) in tb.list" :key="j" @click="detailEvent(i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.time}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i)">{{$t('play')}}</span>
<span class="btn" @click.stop="starEvent(i)">{{$t('star')}}</span>
<span class="btn" @click.stop="shareEvent(i)">{{$t('share')}}</span>
<span class="btn" @click.stop="downloadEvent(i)">{{$t('download')}}</span>
</span>
</li>
</ul>
<div class="tBody-mask zy-loading" v-show="tb.loading">
<div class="loader"></div>
<div class="body zy-scroll" infinite-wrapper>
<div class="show-img" v-if="show.img">
<Waterfall :list="list" :gutter="20" :width="240"
:breakpoints="{ 1200: { rowPerView: 4 } }"
animationEffect="fadeInUp"
backgroundColor="rgba(0, 0, 0, 0)"
ref="waterfall">
<template slot="item" slot-scope="props">
<div class="card">
<div class="img">
<img style="width: 100%" :src="props.data.pic" alt="" @load="$refs.waterfall.refresh()" @click="detailEvent(props.data)">
<div class="operate">
<div class="operate-wrap">
<span class="o-play" @click="playEvent(props.data)">播放</span>
<span class="o-star" @click="starEvent(props.data)">收藏</span>
<span class="o-share" @click="shareEvent(props.data)">分享</span>
</div>
</div>
</div>
<div class="name" @click="detailEvent(props.data)">{{props.data.name}}</div>
<div class="info">
<span>{{props.data.year}}</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="!show.img">
<div class="zy-table">
<div class="tBody">
<ul>
<li v-for="(i, j) in list" :key="j" @click="detailEvent(i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.year}}</span>
<span class="last">{{i.last}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i)">播放</span>
<span class="btn" @click.stop="starEvent(i)">收藏</span>
<span class="btn" @click.stop="shareEvent(i)">分享</span>
</span>
</li>
</ul>
<infinite-loading force-use-infinite-wrapper="tBody" :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div>
</div>
<div class="tFooter">
<span class="tFooter-span">今日更新: {{ tb.update }} </span>
<span class="tFooter-span btn" @click="goWebsite">加载不出来,点这里</span>
<el-pagination small :page-size="tb.size" :total="tb.total" :current-page="tb.page" @current-change="tbPageChange" layout="total, prev, pager, next, jumper"></el-pagination>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { sites, getSite } from '../lib/site/sites'
import tools from '../lib/site/tools'
import video from '../lib/dexie/video'
import setting from '../lib/dexie/setting'
import { shell } from 'electron'
const { clipboard } = require('electron')
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'
export default {
name: 'film',
data () {
return {
sites: sites,
site: {},
top: false,
tag: 0,
type: 0,
typeName: '',
keywords: '',
id: '',
show: {
body: false,
site: false,
tags: false,
type: false
class: false,
classList: false,
search: false,
img: true
},
inputFocus: false,
tb: {
list: [],
page: 1,
size: 50,
total: 0,
update: 0,
loading: true
}
sites: [],
site: {},
classList: [],
type: {},
pagecount: 0,
list: [],
infiniteId: +new Date(),
refresh: 0,
searchList: [],
searchTxt: ''
}
},
components: {
Waterfall,
InfiniteLoading
},
computed: {
view: {
get () {
@@ -121,12 +122,12 @@ export default {
this.SET_VIEW(val)
}
},
gSite: {
video: {
get () {
return this.$store.getters.getSite
return this.$store.getters.getVideo
},
set (val) {
this.SET_SITE(val)
this.SET_VIDEO(val)
}
},
detail: {
@@ -137,14 +138,6 @@ export default {
this.SET_DETAIL(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
share: {
get () {
return this.$store.getters.getShare
@@ -152,160 +145,222 @@ export default {
set (val) {
this.SET_SHARE(val)
}
},
setting () {
return this.$store.getters.getSetting
}
},
watch: {
gSite (n, o) {
const s = getSite(n)
this.siteClick(s)
setting: {
handler () {
this.changeSetting()
},
deep: true
},
view () {
this.changeView()
},
searchTxt () {
this.searchChangeEvent()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_SITE', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
init () {
setting.find().then(res => {
this.site = getSite(res.site)
this.top = true
tools.film_get(res.site).then(tRes => {
this.tb.list = tRes.list
this.tb.total = tRes.total
this.tb.update = tRes.update
this.tb.loading = false
})
})
},
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
siteClick (e) {
this.list = []
this.site = e
this.tag = 0
this.id = e.tags[0].id
this.show.site = false
if (this.keywords.length > 0) {
if (this.searchTxt.length > 0) {
this.searchEvent()
} else {
this.tb.update = 0
this.tb.total = 0
this.tb.loading = true
tools.film_get(e.key, this.id).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.update = res.update
this.tb.loading = false
this.classList = []
this.type = {}
this.getClass().then(res => {
if (res) {
this.show.class = true
this.infiniteId += 1
}
})
}
},
tagClick (e, n) {
this.tb.update = 0
this.tb.total = 0
this.tag = n
this.id = e.id
this.typeName = 'All'
this.tb.loading = true
this.show.tags = false
tools.film_get(this.site.key, this.id).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.update = res.update
this.tb.loading = false
classClick (e) {
this.show.classList = false
this.list = []
this.type = e
this.getPage().then(res => {
if (res) {
this.infiniteId += 1
}
})
},
typeClick (e, n) {
this.tb.update = 0
this.tb.total = 0
this.type = n
this.typeName = e.title
this.id = e.id
this.tb.loading = true
this.show.type = false
tools.film_get(this.site.key, this.id).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.update = res.update
this.tb.loading = false
getClass () {
return new Promise((resolve, reject) => {
const key = this.site.key
zy.class(key).then(res => {
this.classList = res.class
this.show.class = true
this.pagecount = res.pagecount
this.type = { name: '最新', tid: 0 }
resolve(true)
}).catch(err => {
reject(err)
})
})
},
searchEvent () {
const flag = this.site.search
if (flag === '') {
this.$m.warning(this.$t('not_support_search'))
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
}
this.tb.loading = true
this.tb.update = 0
this.tb.total = 0
tools.search_get(this.site.key, this.keywords).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.loading = false
zy.list(key, page, type).then(res => {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
this.list.push(...res)
} else {
this.list.push(res)
}
$state.loaded()
} else {
$state.complete()
}
})
},
detailEvent (e) {
this.detail = {
show: true,
v: e
key: this.site.key,
info: e
}
},
playEvent (e) {
this.video = e
history.find({ site: this.site.key, ids: e.id }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: res.index } }
} else {
this.video = { key: this.site.key, info: { id: e.id, name: e.name, index: 0 } }
}
})
this.view = 'Play'
},
starEvent (e) {
video.find({ detail: e.detail }).then(res => {
star.find({ site: this.site.key, ids: e.id }).then(res => {
if (res) {
this.$m.warning(this.$t('exists'))
this.$message.info('已存在')
} else {
video.add(e).then(res => {
this.$m.success(this.$t('star_success'))
const docs = {
site: this.site.key,
ids: e.id,
name: e.name,
type: e.type,
year: e.year,
last: e.last
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
}).catch(() => {
this.$message.warning('收藏失败')
})
},
shareEvent (e) {
this.share = {
show: true,
v: e
key: this.site.key,
info: e
}
},
downloadEvent (e) {
tools.detail_get(e.site, e.detail).then(res => {
if (res.mp4_urls.length > 0) {
const urls = [...res.mp4_urls]
let txt = `${e.name}\n`
for (const i of urls) {
const name = i.split('$')[0]
const url = encodeURI(i.split('$')[1])
txt += (name + ': ' + url + '\n')
}
clipboard.writeText(txt)
this.$m.success('〖MP4〗: ' + this.$t('copy_success'))
return false
}
if (res.m3u8_urls.length > 0) {
const urls = [...res.m3u8_urls]
let txt = `${e.name}\n`
for (const i of urls) {
const name = i.split('$')[0]
const url = encodeURI(i.split('$')[1])
txt += (name + ': ' + url + '\n')
}
clipboard.writeText(txt)
this.$m.success('〖M3U8〗: ' + this.$t('copy_success'))
}
changeSetting () {
this.list = []
this.setting.view === 'picture' ? this.show.img = true : this.show.img = false
this.refresh++
},
changeView () {
if (this.refresh >= 1) {
this.getPage().then(() => {
this.infiniteId += 1
this.refresh = 0
})
}
},
getAllSearch () {
search.all().then(res => {
this.searchList = res.reverse()
})
},
tbPageChange (e) {
this.tb.loading = true
this.tb.page = e
tools.film_get(this.site.key, this.id, this.tb.page).then(res => {
this.tb.list = res.list
this.tb.loading = false
searchEvent () {
const wd = this.searchTxt
this.list = []
this.pagecount = 0
this.show.search = false
if (wd) {
search.find({ keywords: wd }).then(res => {
if (!res) {
search.add({ keywords: wd })
}
this.getAllSearch()
})
zy.search(this.site.key, wd).then(res => {
this.list = res
})
} else {
this.$message.warning('请输入关键字')
}
},
searchClickEvent (e) {
this.list = []
this.pagecount = 0
this.searchTxt = e.keywords
this.show.search = false
search.remove(e.id).then(res => {
search.add({ keywords: e.keywords })
this.getAllSearch()
})
zy.search(this.site.key, e.keywords).then(res => {
this.list = res
})
},
goWebsite () {
shell.openExternal(this.site.url)
clearSearch () {
search.clear().then(res => {
this.getAllSearch()
})
},
searchChangeEvent () {
if (this.searchTxt.length >= 1) {
this.show.class = false
} else {
this.show.class = true
}
},
getAllsites () {
sites.all().then(res => {
this.sites = res
this.site = this.sites[0]
this.siteClick(this.site)
})
}
},
created () {
this.init()
this.getAllsites()
this.getAllSearch()
}
}
</script>
@@ -315,53 +370,94 @@ export default {
width: 100%;
display: flex;
flex-direction: column;
animation: viewFadeIn 1s ease-in both;
.top{
width: 100%;
.header{
height: 30px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.search{
width: 200px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 15px;
svg{
width: 20px;
height: 20px;
stroke-linecap: round;
stroke-linejoin: round;
}
.search-icon{
width: 40px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.search-box{
width: 160px;
height: 30px;
border-radius: 20px;
border: none;
text-indent: 2px;
font-size: 14px;
&:focus{
outline: none;
border: none;
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;
}
.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;
}
}
}
}
}
.middle{
height: calc(100% - 40px);
width: 100%;
margin-top: 10px;
padding-bottom: 0px;
border-radius: 5px;
}
}
</style>

View File

@@ -1,16 +1,26 @@
<template>
<div class="frame">
<span class="min" @click="frameClickEvent('min')"></span>
<span class="max" @click="frameClickEvent('max')"></span>
<span class="close" @click="frameClickEvent('close')"></span>
</div>
</template>
<script>
const ipc = require('electron').ipcRenderer
const { remote } = require('electron')
export default {
name: 'frame',
methods: {
frameClickEvent (e) {
ipc.send(e)
const win = remote.getCurrentWindow()
if (e === 'min') {
win.minimize()
}
if (e === 'max') {
win.isMaximized() ? win.unmaximize() : win.maximize()
}
if (e === 'close') {
win.destroy()
}
}
}
}
@@ -20,46 +30,18 @@ export default {
width: 100%;
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
user-select: none;
align-items: center;
justify-content: flex-end;
-webkit-app-region: drag;
span{
-webkit-app-region: no-drag;
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
margin-left: 10px;
width: 14px;
height: 14px;
cursor: pointer;
opacity: 0.5;
&: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;
}
}
margin-left: 10px;
border-radius: 50%;
display: inline-block;
-webkit-app-region: no-drag;
}
}
</style>

View File

@@ -1,24 +1,22 @@
<template>
<div class="play">
<div class="box">
<div class="title" v-if="length === 1">{{name}}</div>
<div class="title" v-if="length > 1"> {{(video.index + 1)}} {{name}}</div>
<div class="xgBox">
<div class="title">
<span v-if="this.right.list.length > 1"> {{(video.info.index + 1)}} </span>{{name}}
</div>
<div class="player">
<div id="xg"></div>
</div>
<div class="mask zy-loading" v-show="mask">
<div class="loader"></div>
</div>
<div class="more" v-show="more">
<div class="more">
<span class="zy-svg" @click="nextEvent" v-show="showNext">
<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">{{$t('next')}}</title>
<title id="forwardIconTitle">下一集</title>
<path d="M10 14.74L3 19V5l7 4.26V5l12 7-12 7v-4.26z"></path>
</svg>
</span>
<span class="zy-svg" @click="listEvent" :class="right.type === 'list' ? 'active' : ''" v-show="right.listData.length > 0">
<span class="zy-svg" @click="listEvent" :class="right.type === 'list' ? 'active' : ''" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="dashboardIconTitle">
<title id="dashboardIconTitle">{{$t('play_list')}}</title>
<title id="dashboardIconTitle">播放列表</title>
<rect width="20" height="20" x="2" y="2"></rect>
<path d="M11 7L17 7M11 12L17 12M11 17L17 17"></path>
<line x1="7" y1="7" x2="7" y2="7"></line>
@@ -28,36 +26,36 @@
</span>
<span class="zy-svg" @click="historyEvent" :class="right.type === 'history' ? 'active' : ''">
<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">{{$t('history')}}</title>
<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="zy-svg" @click="starEvent" :class="isStar ? 'active' : ''" v-show="right.listData.length > 0">
<span class="zy-svg" @click="starEvent" :class="isStar ? 'active' : ''" v-show="right.list.length > 0">
<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">{{$t('star')}}</title>
<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="zy-svg" @click="detailEvent" v-show="right.listData.length > 0">
<span class="zy-svg" @click="detailEvent" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="feedIconTitle">
<title id="feedIconTitle">{{$t('detail')}}</title>
<title id="feedIconTitle">详情</title>
<circle cx="7.5" cy="7.5" r="2.5"></circle>
<path d="M22 13H2"></path>
<path d="M18 6h-5m5 3h-5"></path>
<path d="M5 2h14a3 3 0 0 1 3 3v17H2V5a3 3 0 0 1 3-3z"></path>
</svg>
</span>
<span class="zy-svg" @click="smallEvent" v-show="right.listData.length > 0">
<span class="zy-svg" @click="miniEvent" v-show="right.list.length > 0">
<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">{{$t('mini')}}</title>
<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="zy-svg" @click="shareEvent" v-show="right.listData.length > 0">
<span class="zy-svg" @click="shareEvent" v-show="right.list.length > 0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="qrIconTitle">
<title id="qrIconTitle">{{$t('share')}}</title>
<title id="qrIconTitle">分享</title>
<rect x="10" y="3" width="7" height="7" transform="rotate(90 10 3)"></rect>
<rect width="1" height="1" transform="matrix(-1 0 0 1 7 6)"></rect>
<rect x="10" y="14" width="7" height="7" transform="rotate(90 10 14)"></rect>
@@ -77,39 +75,37 @@
<transition name="slideX">
<div v-if="right.show" class="list">
<div class="list-top">
<span class="list-top-title">{{ right.type === 'list' ? $t('play_list') : $t('history') }}</span>
<span class="list-top-close zy-svg" @click="closeEvent">
<span class="list-top-title">{{ right.type === 'list' ? '播放列表' : '历史记录' }}</span>
<span class="list-top-close zy-svg" @click="closeListEvent">
<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">{{$t('close')}}</title>
<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="list-body zy-scroll" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<ul v-show="right.type === 'list'" class="list-item">
<li v-show="right.listData.length === 0">{{$t('no_data')}}</li>
<li @click="listItemEvent(j)" :class="video.index === j ? 'active' : ''" v-for="(i, j) in right.listData" :key="j">{{i | ftName}}</li>
<li v-show="right.list.length === 0">无数据</li>
<li @click="listItemEvent(j)" :class="video.info.index === j ? 'active' : ''" v-for="(i, j) in right.list" :key="j">{{i | ftName(j)}}</li>
</ul>
<ul v-show="right.type === 'history'" class="list-history">
<li v-show="right.historyData.length > 1" @click="clearAll">{{$t('clear_data')}}</li>
<li v-show="right.historyData.length === 0">{{$t('no_data')}}</li>
<li @click="historyItemEvent(m)" :class="video.detail === m.detail ? 'active' : ''" v-for="(m, n) in right.historyData" :key="n"><span class="title">{{m.name}}</span><span @click.stop="removeItem(m)" class="detail-delete">{{$t('delete')}}</span></li>
<li v-show="right.history.length > 1" @click="clearAllHistory">清空</li>
<li v-show="right.history.length === 0">无数据</li>
<li @click="historyItemEvent(m)" :class="video.info.id === m.ids ? 'active' : ''" v-for="(m, n) in right.history" :key="n"><span class="title">{{m.name}}</span><span @click.stop="removeHistoryItem(m)" class="detail-delete">删除</span></li>
</ul>
</div>
</div>
</transition>
<div class="play-mask" v-if="right.listData.length === 0 && right.historyData.length === 0">{{$t('no_history')}}</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
import history from '../lib/dexie/history'
import video from '../lib/dexie/video'
import mini from '../lib/dexie/mini'
import { star, history, setting, shortcut, mini } from '../lib/dexie'
import zy from '../lib/site/tools'
import 'xgplayer'
import Hls from 'xgplayer-hls.js'
const { ipcRenderer: ipc } = require('electron')
import mt from 'mousetrap'
const { remote, ipcRenderer } = require('electron')
export default {
name: 'play',
data () {
@@ -118,19 +114,19 @@ export default {
right: {
show: false,
type: '',
listData: [],
historyData: []
list: [],
history: []
},
config: {
id: 'xg',
lang: 'zh-cn',
url: '',
lang: 'zh-cn',
width: '100%',
height: '100%',
autoplay: false,
videoInit: true,
screenShot: true,
keyShortcut: 'on',
keyShortcut: 'off',
crossOrigin: true,
cssFullscreen: true,
defaultPlaybackRate: 1,
@@ -140,11 +136,20 @@ export default {
length: 0,
timer: null,
scroll: false,
more: true,
showNext: false,
isStar: false,
isTop: false,
mask: false
mini: {}
}
},
filters: {
ftName (e, n) {
const num = e.split('$')
if (num.length > 1) {
return e.split('$')[0]
} else {
return `${(n + 1)}`
}
}
},
computed: {
@@ -179,11 +184,9 @@ export default {
set (val) {
this.SET_SHARE(val)
}
}
},
filters: {
ftName (e) {
return e.split('$')[0]
},
setting () {
return this.$store.getters.getSetting
}
},
watch: {
@@ -196,49 +199,73 @@ export default {
this.getUrls()
},
deep: true
},
setting: {
handler () {
this.changeSetting()
},
deep: true
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
getUrls () {
this.name = ''
this.mask = true
if (this.timer !== null) {
clearInterval(this.timer)
this.timer = null
}
if (this.xg) {
if (this.xg.hasStart) {
this.xg.pause()
}
}
const index = this.video.index
const index = this.video.info.index | 0
let time = 0
history.find({ detail: this.video.detail }).then(item => {
if (item) {
if (item.index === index) {
time = item.currentTime
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
if (res.index === index) {
time = res.time
}
}
this.playVideo(index, time)
})
},
playVideo (index, time) {
tools.detail_get(this.video.site, this.video.detail).then(res => {
playVideo (index = 0, time = 0) {
const id = this.video.info.id
zy.detail(this.video.key, id).then(res => {
this.name = res.name
this.right.listData = res.m3u8_urls
const m = res.m3u8_urls
const arr = []
for (const i of m) {
arr.push(i.split('$')[1])
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('#')
}
this.length = arr.length
this.xg.src = arr[index]
this.showNext = this.length > 1
this.right.list = m3u8Txt
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])
}
}
this.xg.src = m3u8Arr[index]
this.showNext = m3u8Arr.length > 1
if (time !== 0) {
this.xg.play()
@@ -248,92 +275,77 @@ export default {
} else {
this.xg.play()
}
this.xg.once('play', () => {
this.mask = false
})
this.onPlayVideo()
this.videoPlaying()
this.xg.once('ended', () => {
if (res.m3u8_urls.length > 1 && (res.m3u8_urls.length - 1 > this.video.index)) {
this.video.currentTime = 0
this.video.index++
if (m3u8Arr.length > 1 && (m3u8Arr.length - 1 > index)) {
this.video.info.time = 0
this.video.info.index++
}
this.xg.off('ended')
})
})
},
videoPlaying () {
this.changeVideo()
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
const doc = {
id: res.id,
site: res.site,
ids: res.ids,
name: res.name,
type: res.type,
year: res.year,
index: this.video.info.index,
time: res.time
}
history.update(res.id, doc)
} else {
const doc = {
site: this.video.key,
ids: this.video.info.id,
name: this.video.info.name,
type: this.video.info.type,
year: this.video.info.year,
index: this.video.info.index,
time: ''
}
history.add(doc)
}
})
this.timerEvent()
},
changeVideo () {
this.checkStar()
this.checkTop()
},
checkStar () {
video.find({ detail: this.video.detail }).then(res => {
if (res) {
this.isStar = true
} else {
this.isStar = false
}
})
},
checkTop () {
ipc.send('checkTop')
ipc.on('isTop', (e, flag) => {
this.isTop = flag
})
},
onPlayVideo () {
this.more = true
this.changeVideo()
const h = { ...this.video }
history.find({ detail: h.detail }).then(res => {
if (res) {
h.id = res.id
history.update(res.id, h)
} else {
h.currentTime = ''
delete h.id
history.add(h)
}
})
this.timerEvent(h.detail)
},
timerEvent (d) {
timerEvent () {
this.timer = setInterval(() => {
history.find({ detail: d }).then(res => {
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
const h = { ...this.video }
h.currentTime = this.xg.currentTime
delete h.id
history.update(res.id, h)
}
})
video.find({ detail: d }).then(res => {
if (res) {
const h = { ...this.video }
delete h.id
delete h.currentTime
video.update(res.id, h)
const doc = { ...res }
doc.time = this.xg.currentTime
delete doc.id
history.update(res.id, doc)
}
})
}, 10000)
},
closeEvent () {
this.right.show = false
this.right.type = ''
},
nextEvent () {
if (this.video.index < this.right.listData.length - 1) {
this.video.index++
this.video.currentTime = 0
prevEvent () {
if (this.video.info.index >= 1) {
this.video.info.index--
this.video.info.time = 0
} else {
this.$m.warning(this.$t('last_video'))
this.$message.warning('这已经是第一集了。')
}
},
prevEvent () {
if (this.video.index > 0) {
this.video.index--
this.video.currentTime = 0
nextEvent () {
if (this.video.info.index < (this.right.list.length - 1)) {
this.video.info.index++
this.video.info.time = 0
} else {
this.$m.warning(this.$t('first_video'))
this.$message.warning('这已经是最后一集了。')
}
},
listEvent () {
@@ -354,126 +366,292 @@ export default {
this.right.type = 'history'
}
history.all().then(res => {
this.right.historyData = res.reverse()
this.right.history = res.reverse()
})
},
getAllhistory () {
history.all().then(res => {
this.right.history = res.reverse()
})
},
starEvent () {
video.find({ detail: this.video.detail }).then(res => {
if (res !== undefined) {
video.remove(res.id).then(r => {
this.$m.info(this.$t('delete_success'))
const info = this.video.info
star.find({ site: this.video.key, ids: info.id }).then(res => {
if (res) {
star.remove(res.id).then(e => {
this.$message.info('取消收藏')
this.isStar = false
})
} else {
const v = { ...this.video }
if (v.id) {
delete v.id
const docs = {
site: this.video.key,
ids: info.id,
name: info.name,
type: info.type,
year: info.year,
last: info.last
}
video.add(v).then(r => {
this.$m.success(this.$t('star_success'))
star.add(docs).then(res => {
this.$message.success('收藏成功')
this.isStar = true
})
}
}).catch(() => {
this.$message.warning('检查收藏失败')
})
},
detailEvent () {
this.detail = {
show: true,
v: this.video
key: this.video.key,
info: this.video.info
}
},
smallEvent () {
this.xg.pause()
miniEvent () {
if (this.xg) {
this.xg.pause()
}
mini.find().then(res => {
const d = { ...this.video }
d.currentTime = this.xg.currentTime
d.id = 0
if (res) {
mini.update(d)
} else {
mini.add(d)
const doc = {
id: 0,
site: this.video.key,
ids: this.video.info.id,
name: this.video.info.name,
index: this.video.info.index,
time: this.xg.currentTime
}
ipc.send('min')
ipc.send('mini')
if (res) {
mini.update(doc)
} else {
mini.add(doc)
}
this.mini = doc
clearInterval(this.timer)
const win = remote.getCurrentWindow()
win.hide()
ipcRenderer.send('mini')
})
},
shareEvent () {
this.share = {
show: true,
v: this.video
key: this.video.key,
info: this.video.info
}
},
clearAll () {
history.clear().then(res => {
this.right.historyData = []
})
},
listItemEvent (n) {
history.find({ detail: this.video.detail }).then(item => {
if (item) {
item.currentTime = 0
item.index = n
history.update(item.id, item)
checkStar () {
star.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
this.isStar = true
} else {
this.isStar = false
}
this.video.index = n
this.right.show = false
this.right.type = ''
})
},
historyItemEvent (e) {
this.video = e
checkTop () {
const win = remote.getCurrentWindow()
this.isTop = win.isAlwaysOnTop()
},
closeListEvent () {
this.right.show = false
this.right.type = ''
},
removeItem (e) {
history.remove(e.id).then(res => {
history.all().then(e => {
this.right.historyData = e.reverse()
})
clearAllHistory () {
history.clear().then(res => {
this.right.history = []
})
},
playbackRateEvent (e) {
let rate = this.xg.playbackRate
if (rate > 0.25) {
rate = rate + e
this.xg.playbackRate = rate
this.$m.success(this.$t('rate') + rate)
listItemEvent (n) {
this.video.info.time = 0
this.video.info.index = n
this.right.show = false
this.right.type = ''
},
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
}
}
this.right.show = false
this.right.type = ''
},
removeHistoryItem (e) {
history.remove(e.id).then(res => {
this.$message.success('删除历史记录成功~')
this.getAllhistory()
}).catch(err => {
this.$message.warning('删除历史记录失败, 错误信息: ' + err)
})
},
mtEvent () {
setting.find().then(res => {
if (res.shortcut) {
shortcut.all().then(res => {
for (const i of res) {
mt.bind(i.key, () => {
if (this.view === 'Play') {
this.shortcutEvent(i.name)
}
})
}
})
} else {
shortcut.all().then(res => {
for (const i of res) {
mt.unbind(i.key)
}
})
}
})
},
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') {
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()
const num = win.getOpacity()
if (num > 0.1) {
win.setOpacity(num - 0.1)
}
return false
}
if (e === 'opacityDown') {
const win = remote.getCurrentWindow()
const num = win.getOpacity()
if (num < 1) {
win.setOpacity(num + 0.1)
}
return false
}
if (e === 'playbackRateUp') {
if (this.xg && !this.xg.paused) {
const rate = this.xg.playbackRate
this.xg.playbackRate = rate + 0.25
this.$message.info('当前播放速度为: ' + 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.$message.info('当前播放速度为: ' + this.xg.playbackRate)
}
}
return false
}
if (e === 'mini') {
this.miniEvent()
return false
}
},
changeSetting () {
this.mtEvent()
}
},
created () {
this.getAllhistory()
this.mtEvent()
},
mounted () {
this.xg = new Hls(this.config)
history.all().then(res => {
this.right.historyData = res
})
ipc.on('next', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.nextEvent()
}
}
})
ipc.on('prev', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.prevEvent()
}
}
})
ipc.on('playbackRateUp', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.playbackRateEvent(0.25)
}
}
})
ipc.on('playbackRateDown', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.playbackRateEvent(-0.25)
}
}
ipcRenderer.on('miniClosed', () => {
this.xg.destroy()
this.xg = new Hls(this.config)
this.getUrls()
})
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
@@ -487,45 +665,39 @@ export default {
align-items: center;
border-radius: 5px;
.box{
width: 92%;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
border-radius: 5px;
align-items: center;
justify-content: center;
flex-direction: column;
.title{
width: 100%;
height: 40px;
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 40px;
padding: 0 10px;
}
.xgBox{
.player{
width: 100%;
height: 500px;
flex: 1;
padding: 0 10px;
overflow: hidden;
}
.more{
width: 100%;
height: 60px;
height: 50px;
min-height: 50px;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 10px;
span{
display: flex;
margin-right: 10px;
cursor: pointer;
}
}
.mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 600;
opacity: 0.98;
}
}
.list{
position: absolute;
@@ -592,18 +764,5 @@ export default {
transform: translateX(100%);
opacity: 0;
}
.play-mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 900;
display: flex;
font-size: 14px;
border-radius: 5px;
justify-content: center;
align-items: center;
}
}
</style>

View File

@@ -1,32 +1,73 @@
<template>
<div class="setting">
<div class="setting-box zy-scroll" v-if="show.setting">
<div class="logo"><img src="@/assets/image/logo.png"></div>
<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/')">{{$t('website')}}</a>
<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}} {{$t('issues')}}</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">v{{pkg.version}} 反馈</a>
</div>
<div class="change">
<div class="zy-select" @mouseleave="show.language = false">
<div class="vs-placeholder" @click="show.language = true">{{$t('language')}}</div>
<div class="vs-options" v-show="show.language">
<ul>
<li :class="s.language === i.key ? 'active' : ''" v-for="(i, j) in languages" :key="j" @click="languageClick(i.key)">{{ i.name }}</li>
</ul>
<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 class="zy-select" @mouseleave="show.site = false">
<div class="vs-placeholder" @click="show.site = true">{{$t('default_site')}}</div>
<div class="vs-options" v-show="show.site">
<ul>
<li :class="s.site === i.key ? 'active' : ''" v-for="(i, j) in sites" :key="j" @click="siteClick(i.key)">{{ i.name }}</li>
</ul>
</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 class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="openDoc('shortcut')">说明文档</div>
</div>
</div>
</div>
<div class="site">
<div class="title">源管理</div>
<div class="site-box">
<div class="zy-select" @mouseleave="show.site = false">
<div class="vs-placeholder" @click="show.site = true">默认源</div>
<div class="vs-options" v-show="show.site">
<ul class="zy-scroll" style="height: 300px">
<li :class="d.site === i.key ? 'active' : ''" v-for="(i, j) in sitesList" :key="j" @click="siteClick(i.key)">{{ i.name }}</li>
</ul>
</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="expSites">导出</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="impSites">导入</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="openDoc('sites')">说明文档</div>
</div>
</div>
</div>
<div class="theme">
<div class="title">{{$t('theme')}}</div>
<div class="title">主题</div>
<div class="theme-box">
<div @click="changeTheme('light')" class="theme-item light">
<div class="theme-image">
@@ -54,16 +95,16 @@
</div>
</div>
</div>
<!-- <div class="qrcode">
<div class="title">{{$t('donate')}}</div>
<div class="qrcode">
<div class="title">请作者吃辣条</div>
<div class="qrcode-box">
<img class="qrcode-item" src="../assets/image/alipay.png">
<img class="qrcode-item" src="../assets/image/wepay.jpg">
</div>
</div> -->
</div>
<div class="clearDB">
<span @click="clearDBEvent" class="clearBtn">{{$t('clearDB')}}</span>
<span class="clearTips">{{$t('clearTips')}}</span>
<span @click="clearDBEvent" class="clearBtn">软件重置</span>
<span class="clearTips">如非必要, 切勿点击. 会清空用户数据, 恢复默认设置. 点击即软件重置, 并关闭软件.</span>
</div>
<div class="Tips">
<span>所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.</span>
@@ -73,106 +114,145 @@
</template>
<script>
import { mapMutations } from 'vuex'
import setting from '../lib/dexie/setting'
import { sites } from '../lib/site/sites'
import db from '../lib/dexie/index'
import '../lib/cloud/index.js'
import { shell } from 'electron'
import pkg from '../../package.json'
const ipc = require('electron').ipcRenderer
import { setting, sites, shortcut } from '../lib/dexie'
import { shell, clipboard, remote } from 'electron'
import db from '../lib/dexie/dexie'
export default {
name: 'setting',
data () {
return {
pkg: pkg,
s: {},
languages: [
{
key: 'zhCn',
name: '中文'
},
{
key: 'en',
name: 'English'
}
],
sites: sites,
sitesList: [],
shortcutList: [],
show: {
setting: false,
language: false,
site: false
site: false,
shortcut: false,
view: false
},
d: {
id: 0,
site: '',
theme: '',
shortcut: true,
view: 'picture'
}
}
},
computed: {
theme: {
setting: {
get () {
return this.$store.getters.getTheme
return this.$store.getters.getSetting
},
set (val) {
this.SET_THEME(val)
}
},
language: {
get () {
return this.$store.getters.getLanguage
},
set (val) {
this.SET_LANGUAGE(val)
}
},
site: {
get () {
return this.$store.getters.getSite
},
set (val) {
this.SET_SITE(val)
this.SET_SETTING(val)
}
}
},
methods: {
...mapMutations(['SET_THEME', 'SET_LANGUAGE', 'SET_SITE']),
...mapMutations(['SET_SETTING']),
linkOpen (e) {
shell.openExternal(e)
},
languageClick (e) {
this.language = e
this.show.language = false
this.$i18n.locale = e
this.s.language = e
setting.update(this.s).then(res => {
this.$m.success(this.$t('set_success'))
getSetting () {
setting.find().then(res => {
this.d = {
id: res.id,
site: res.site,
theme: res.theme,
shortcut: res.shortcut,
view: res.view
}
this.setting = this.d
})
},
getSites () {
sites.all().then(res => {
this.sitesList = res
})
},
getShortcut () {
shortcut.all().then(res => {
this.shortcutList = res
})
},
changeView (e) {
this.d.view = e
setting.update(this.d).then(res => {
this.$message.success('修改成功')
this.show.view = false
this.setting = this.d
})
},
siteClick (e) {
this.site = e
this.show.site = false
this.s.site = e
setting.update(this.s).then(res => {
this.$m.success(this.$t('set_success'))
this.d.site = e
setting.update(this.d).then(res => {
this.$message.success('修改默认源成功')
this.setting = this.d
this.show.site = false
})
},
expSites () {
const arr = [...this.sitesList]
const str = JSON.stringify(arr)
clipboard.writeText(str)
this.$message.success('已复制到剪贴板')
},
impSites () {
const str = clipboard.readText()
const json = JSON.parse(str)
sites.clear().then(res => {
this.$message.info('已清空原数据')
sites.add(json).then(e => {
this.$message.success('已添加成功')
this.getSites()
})
})
},
changeTheme (e) {
this.theme = e
this.s.theme = e
setting.update(this.s).then(res => {
this.$m.success(this.$t('set_success'))
this.d.theme = e
setting.update(this.d).then(res => {
this.$message.success('修改成功')
})
},
changeShortcut (e) {
this.d.shortcut = e
setting.update(this.d).then(res => {
this.$message.success('修改成功')
this.setting = this.d
this.show.shortcut = false
})
},
expShortcut () {
const arr = [...this.shortcutList]
const str = JSON.stringify(arr)
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.$m.success(this.$t('set_success'))
ipc.send('close')
this.$m.success('重置成功')
const win = remote.getCurrentWindow()
win.destroy()
})
}
},
openDoc (e) {}
},
created () {
setting.find().then(res => {
this.s = res
this.theme = res.theme
this.$i18n.locale = this.s.language
this.show.setting = true
})
this.getSetting()
this.getSites()
this.getShortcut()
}
}
</script>
@@ -180,16 +260,16 @@ export default {
.setting{
height: calc(100% - 40px);
width: 100%;
border-radius: 5px;
padding: 20px 0;
.setting-box{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
border-radius: 5px;
overflow-y: auto;
}
.logo{
.logo{
margin-top: 10px;
width: 100%;
text-align: center;
@@ -209,14 +289,37 @@ export default {
cursor: pointer;
}
}
.change{
.view{
width: 100%;
display: flex;
justify-content: flex-start;
padding-left: 20px;
margin-top: 40px;
.zy-select{
margin-right: 20px;
padding: 20px;
margin-top: 20px;
.view-box{
margin-top: 10px;
.zy-select{
margin-right: 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{

View File

@@ -1,15 +1,15 @@
<template>
<div class="share" id="share" @click="shareClickEvent">
<div class="left">
<img :src="this.card.img" alt="">
<img :src="pic" alt="">
</div>
<div class="right">
<div class="title">{{ card.name }}</div>
<qrcode-vue id="qr" :value="value" :size="160" level="L" />
<div class="title">{{ share.info.name }}</div>
<qrcode-vue id="qr" :value="link" :size="160" level="L" />
<div class="tips">
<p>{{$t('qr_tips')}}</p>
<p>长按二维码识别播放</p>
<p><img src="@/assets/image/logo.png"></p>
<p class="zy">{{$t('zy_tips')}}</p>
<p class="zy">ZY Player技术支持严禁传播违法资源</p>
</div>
</div>
<div class="share-mask" v-show="loading">
@@ -19,21 +19,18 @@
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
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 {
card: {
img: '',
name: '',
png: ''
},
value: '',
loading: true
pic: '',
png: '',
link: '',
loading: false
}
},
components: {
@@ -52,46 +49,50 @@ export default {
watch: {
share: {
handler () {
this.getDetail()
this.getDetail(
this.loading = true
)
},
deep: true
}
},
methods: {
...mapMutations(['SET_SHARE']),
getDetail () {
this.loading = true
tools.detail_get(this.share.v.site, this.share.v.detail).then(res => {
const info = res.info
const parser = new DOMParser()
const html = parser.parseFromString(info, 'text/html')
const img = html.querySelector('img').src
this.card.img = img
this.card.name = this.share.v.name
const urls = res.m3u8_urls
const url = urls[this.share.v.index].split('$')[1]
this.value = 'http://zyplayer.fun/player/player.html?url=' + url + '&title=' + this.share.v.name
this.loading = false
this.$nextTick(() => {
const dom = document.getElementById('share')
html2canvas(dom, { allowTaint: true, useCORS: true }).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$m.success(this.$t('share_tips'))
this.share.show = true
})
})
})
},
shareClickEvent () {
this.share = {
show: false,
v: {}
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
const text = res.dl.dd
for (const i of text) {
if (i._flag.indexOf('m3u8') >= 0) {
const arr = i._t.split('#')
const url = arr[0].split('$')[1]
this.link = 'http://zyplayer.fun/player/player.html?url=' + url + '&title=' + this.share.info.name
}
}
this.loading = false
this.$nextTick(() => {
const dom = document.getElementById('share')
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('已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!')
})
})
}
})
}
},
created () {
mounted () {
this.getDetail()
}
}
@@ -108,7 +109,7 @@ export default {
justify-content: space-between;
align-items: center;
padding: 20px;
z-index: 888;
z-index: 999;
.left, .right{
width: 50%;
height: 100%;

View File

@@ -1,53 +1,38 @@
<template>
<div class="star">
<div class="zy-table">
<div class="tHead">
<span class="name">{{$t('videoName')}}</span>
<span class="type">{{$t('type')}}</span>
<span class="time">{{$t('time')}}</span>
<span class="from">{{$t('from')}}</span>
<span class="operate" style="width: 220px">{{$t('operate')}}</span>
</div>
<div class="tBody zy-scroll">
<ul v-show="!loading">
<li v-for="(i, j) in data" :key="j" @click="detailEvent(i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.time}}</span>
<span class="from">{{i.site | ftSite}}</span>
<span class="operate" style="width: 220px">
<span class="btn" @click.stop="playEvent(i)">{{$t('play')}}</span>
<span class="btn" @click.stop="deleteEvent(i)">{{$t('delete')}}</span>
<span class="btn" @click.stop="shareEvent(i)">{{$t('share')}}</span>
<span class="btn" @click.stop="updateEvent(i)">{{$t('sync')}}</span>
<span class="btn" @click.stop="downloadEvent(i)">{{$t('download')}}</span>
</span>
</li>
</ul>
<div class="tBody-mask" v-show="loading">
<div class="loader"></div>
<div class="body zy-scroll">
<div class="zy-table">
<div class="tBody">
<ul>
<li v-for="(i, j) in list" :key="j" @click="detailEvent(i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.year}}</span>
<span class="from">{{i.site}}</span>
<span class="operate" style="width: 220px">
<span class="btn" @click.stop="playEvent(i)">播放</span>
<span class="btn" @click.stop="deleteEvent(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>
</li>
</ul>
</div>
</div>
<div class="tFooter">
<span class="tFooter-span">{{data.length}} {{$t('total')}}</span>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
import video from '../lib/dexie/video'
import { sites, getSite } from '../lib/site/sites'
import { star, history } from '../lib/dexie'
import zy from '../lib/site/tools'
const { clipboard } = require('electron')
export default {
name: 'star',
data () {
return {
sites: sites,
data: [],
loading: true,
checkFlag: false
list: []
}
},
computed: {
@@ -59,14 +44,6 @@ export default {
this.SET_VIEW(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
@@ -75,6 +52,14 @@ export default {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
@@ -84,15 +69,9 @@ export default {
}
}
},
filters: {
ftSite (e) {
const name = getSite(e).name
return name
}
},
watch: {
view () {
this.getAllStar()
this.getStarList()
}
},
methods: {
@@ -100,89 +79,111 @@ export default {
detailEvent (e) {
this.detail = {
show: true,
v: e
key: e.site,
info: e
}
},
playEvent (e) {
this.video = 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'
},
deleteEvent (e) {
video.remove(e.id).then(res => {
star.remove(e.id).then(res => {
if (res) {
this.$m.warning(this.$t('delete_failed'))
this.$message.warning('删除失败')
} else {
this.$m.success(this.$t('delete_success'))
this.$message.success('删除成功')
}
this.getAllStar()
this.getStarList()
})
},
shareEvent (e) {
this.share = {
show: true,
v: e
key: e.site,
info: e
}
},
updateEvent (e) {
tools.detail_get(e.site, e.detail).then(res => {
const nameOne = e.name.replace(/\s*/g, '')
const nameTwo = res.name.replace(/\s*/g, '')
if (nameOne === nameTwo) {
this.$m.info(this.$t('async_failed'))
zy.detail(e.site, e.ids).then(res => {
if (e.last === res.last) {
this.$message.info('同步成功, 未查询到更新。')
} else {
const h = e
h.name = res.name
video.update(h.id, h).then(res => {
this.$m.success(this.$t('async_success'))
const doc = {
id: e.id,
ids: res.id,
last: res.last,
name: res.name,
site: e.site,
type: res.type,
year: res.year
}
star.update(e.id, doc).then(res => {
this.$message.success('同步成功, 检查到更新.')
})
}
}).catch(err => {
this.$message.warning('同步失败, 请重试', err)
})
},
downloadEvent (e) {
tools.detail_get(e.site, e.detail).then(res => {
if (res.mp4_urls.length > 0) {
const urls = [...res.mp4_urls]
let txt = `${e.name}\n`
for (const i of urls) {
const name = i.split('$')[0]
const url = encodeURI(i.split('$')[1])
txt += (name + ': ' + url + '\n')
zy.download(e.site, e.ids).then(res => {
if (res) {
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('没有查询到下载链接.')
}
clipboard.writeText(txt)
this.$m.success('〖MP4〗: ' + this.$t('copy_success'))
return false
}
if (res.m3u8_urls.length > 0) {
const urls = [...res.m3u8_urls]
let txt = `${e.name}\n`
for (const i of urls) {
const name = i.split('$')[0]
} else {
const list = [...this.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
txt += (name + ': ' + url + '\n')
downloadUrl += (url + '\n')
}
clipboard.writeText(txt)
this.$m.success('M3U8〗: ' + this.$t('copy_success'))
clipboard.writeText(downloadUrl)
this.$message.success('M3U8』格式的链接已复制, 快去下载吧!')
}
})
},
getAllStar () {
video.all().then(res => {
this.data = res.reverse()
this.loading = false
getStarList () {
star.all().then(res => {
this.list = res.reverse()
})
}
},
created () {
this.getAllStar()
this.getStarList()
}
}
</script>
<style lang="scss" scoped>
.star{
position: relative;
height: calc(100% - 40px);
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 5px;
.body{
width: 100%;
height: 100%;
overflow: auto;
}
}
</style>

View File

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

View File

@@ -1,44 +0,0 @@
const os = require('os')
const macaddress = require('macaddress')
const AV = require('leancloud-storage')
macaddress.one((err, mac) => {
if (err) {
return false
}
AV.init({
appId: 'X6TRIcMjgOG7EJ0t1l5r9In1-gzGzoHsz',
appKey: 'JmkGF9UqkWGQNYDcJ2g1QV1b',
serverURL: 'https://x6tricmj.lc-cn-n1-shared.com'
})
const system = os.hostname() + ' ' + os.type() + ' ' + os.arch()
const query = new AV.Query('ZYPlayer')
query.equalTo('os', system)
query.equalTo('mac', mac)
query.find().then(res => {
// 存储新用户数据
if (res.length === 0) {
const ZYPlayer = AV.Object.extend('ZYPlayer')
const zyPlayer = new ZYPlayer()
zyPlayer.set('os', system)
zyPlayer.set('mac', mac)
zyPlayer.set('times', 1)
zyPlayer.save()
return false
}
// 统计启动次数
if (res.length === 1) {
const id = res[0].id
const times = AV.Object.createWithoutData('ZYPlayer', id)
times.increment('times', 1)
times.save()
return false
}
// 清除冗余数据
if (res.length > 1) {
const arr = res
arr.shift()
AV.Object.destroyAll(arr)
}
})
})

24
src/lib/dexie/dexie.js Normal file
View File

@@ -0,0 +1,24 @@
import Dexie from 'dexie'
import { setting, sites, localKey } from './initData'
const db = new Dexie('zy')
db.version(3).stores({
search: '++id, keywords',
setting: 'id, theme, site, shortcut, view',
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'
})
db.on('populate', () => {
db.setting.bulkAdd(setting)
db.sites.bulkAdd(sites)
db.shortcut.bulkAdd(localKey)
})
db.open()
export default db

View File

@@ -1,4 +1,4 @@
import db from './index'
import db from './dexie'
const { history } = db
export default {
async add (doc) {

View File

@@ -1,33 +1,17 @@
import Dexie from 'dexie'
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'
const db = new Dexie('zy')
db.version(1).stores({
theme: '++id, theme',
site: '++id, site',
video: '++id, name, type, time, detail, urls, index'
})
db.version(2).stores({
setting: 'id, theme, site, language, cloud, cloudKey',
video: '++id, site, name, type, time, detail, index',
history: '++id, site, name, type, time, detail, index, currentTime',
mini: 'id, site, name, type, time, detail, index, currentTime'
})
const initData = [{
id: 0,
theme: 'light',
site: 'zuidazy',
language: 'zhCn',
cloud: false,
cloudKey: ''
}]
db.on('populate', () => {
db.setting.bulkAdd(initData)
})
db.open()
export default db
export {
history,
mini,
setting,
shortcut,
star,
sites,
search
}

376
src/lib/dexie/initData.js Normal file
View File

@@ -0,0 +1,376 @@
const setting = [
{
id: 0,
theme: 'light',
site: 'zuidazy',
shortcut: true,
view: 'picture'
}
]
const sites = [
{
id: 1,
key: 'okzy',
name: 'OK 资源网',
api: 'http://cj.okzy.tv/inc/api.php',
download: 'http://cj.okzy.tv/inc/apidown.php',
level: 16
},
{
id: 2,
key: 'zuidazy',
name: '最大资源网',
api: 'http://www.zdziyuan.com/inc/api.php',
download: 'http://www.zdziyuan.com/inc/apidown.php',
level: 16
},
{
id: 3,
key: 'gaoqingzy',
name: '高清资源网',
api: 'http://cj.gaoqingzyw.com/inc/api.php',
download: 'http://cj.gaoqingzyw.com/inc/apidown.php',
level: 16
},
{
id: 4,
key: 'doubanzy',
name: '豆瓣电影资源',
api: 'http://v.1988cj.com/inc/api.php',
download: 'http://v.1988cj.com/inc/apidown.php',
level: 16
},
{
id: 5,
key: '135zy',
name: '135 资源网',
api: 'http://cj.zycjw1.com/inc/api.php',
download: 'http://cj.zycjw1.com/inc/apidown.php',
level: 16
},
{
id: 6,
key: 'kuyunzy',
name: '酷云资源',
api: 'http://caiji.kuyun98.com/inc/ldg_api.php',
download: 'http://caiji.kuyun98.com/inc/apidown.php',
level: 16
},
{
id: 7,
key: 'subo988',
name: '速播资源站',
api: 'https://www.subo988.com/inc/api.php',
download: '',
level: 16
},
{
id: 8,
key: '209zy',
name: '209 资源',
api: 'http://cj.1156zy.com/inc/api.php',
download: '',
level: 16
},
{
id: 9,
key: 'zuixinzy',
name: '最新资源',
api: 'http://api.zuixinapi.com/inc/api.php',
download: '',
level: 16
},
{
id: 10,
key: 'kubozy',
name: '酷播资源',
api: 'http://api.kbzyapi.com/inc/api.php',
download: '',
level: 16
},
{
id: 11,
key: 'yongjiuzy',
name: '永久资源',
api: 'http://cj.yongjiuzyw.com/inc/api.php',
download: '',
level: 16
},
{
id: 12,
key: '123ku',
name: '123 资源',
api: 'http://cj.123ku2.com:12315/inc/api.php',
download: '',
level: 16
},
{
id: 13,
key: '88zyw',
name: '88 影视资源站',
api: 'http://www.88zyw.net/inc/api.php',
download: '',
level: 16
},
{
id: 14,
key: 'wolongzy',
name: '卧龙资源',
api: 'http://cj.wlzy.tv/inc/api_mac.php',
download: '',
level: 16
},
{
id: 15,
key: 'mahuazy',
name: '麻花资源',
api: 'https://www.mhapi123.com/inc/api.php',
download: '',
level: 16
},
{
id: 16,
key: 'kkzy',
name: '快快资源',
api: 'https://api.kkzy.tv/inc/api.php',
download: '',
level: 16
},
{
id: 17,
key: '158zy',
name: '壹伍捌资源网',
api: 'http://cj.158zyz.net:158/inc/api.php',
download: '',
level: 16
},
{
id: 18,
key: 'rrzy',
name: '人人资源',
api: 'https://www.rrzyw.cc/api.php/provide/vod/from/rrm3u8/at/xml/',
download: '',
level: 16
},
{
id: 19,
key: 'mokazy',
name: '魔卡资源网',
api: 'https://cj.heiyap.com/api.php/provide/vod/at/xml/',
download: '',
level: 16
},
{
id: 20,
key: 'kyzy',
name: '快影资源站',
api: 'https://www.kyzy.tv/api.php/kyyun/vod/at/xml/',
download: '',
level: 16
},
{
id: 21,
key: 'khzy',
name: '快活资源站',
api: 'https://www.khzyapi.com/api.php/provide/vod/at/xml/',
download: '',
level: 18
},
{
id: 22,
key: 'smzy',
name: '神马资源网',
api: 'http://api.shenmacj.com/api.php/provide/vod/at/xml/',
download: '',
level: 18
},
{
id: 23,
key: 'xhgcjym',
name: '小黄瓜资源',
api: 'http://cj.xhgcjym.com/inc/api.php',
download: 'http://cj.xhgcjym.com/inc/apidown.php',
level: 18
},
{
id: 24,
key: 'jiali',
name: '佳丽 TV',
api: 'https://jialiapi.com/api.php/provide/vod/at/xml/',
download: '',
level: 18
},
{
id: 25,
key: 'agzy',
name: '环亚资源',
api: 'http://wmcj8.com/inc/sapi.php',
download: '',
level: 18
},
{
id: 26,
key: 'solezy',
name: '搜乐资源网',
api: 'https://www.caijizy.vip/api.php/provide/vod/at/xml/',
download: '',
level: 16
},
{
id: 27,
key: 'lajiaozy',
name: '辣椒资源',
api: 'http://api.11bat.com/mac10.php',
download: '',
level: 18
},
{
id: 28,
key: '9188zy',
name: '9188 资源',
api: 'http://cj.vod1769.com/zyapimacc.php',
download: '',
level: 18
},
{
id: 29,
key: 'bbkdj',
name: '步步高顶尖资源网',
api: 'http://api.bbkdj.com/api',
download: '',
level: 16
},
{
id: 30,
key: '1886zy',
name: '1886 资源',
api: 'http://cj.1886zy.co/inc/api.php',
download: '',
level: 16
},
{
id: 31,
key: 'mbo',
name: '秒播资源',
api: 'http://caiji.mb77.vip/inc/api.php',
download: '',
level: 16
},
{
id: 32,
key: 'mgtvzy',
name: '芒果 TV 资源网',
api: 'https://api.shijiapi.com/api.php/provide/vod/at/xml/',
download: '',
level: 16
}
]
const localKey = [
{
name: 'playAndPause',
desc: '播放或暂停',
key: 'space'
},
{
name: 'forward',
desc: '快进',
key: 'right'
},
{
name: 'back',
desc: '快退',
key: 'left'
},
{
name: 'volumeUp',
desc: '音量调高',
key: 'up'
},
{
name: 'volumeDown',
desc: '音量调低',
key: 'down'
},
{
name: 'mute',
desc: '静音',
key: 'm'
},
{
name: 'top',
desc: '置顶或退出置顶',
key: 't'
},
{
name: 'fullscreen',
desc: '进入或退出全屏',
key: 'f'
},
{
name: 'escape',
desc: '退出全屏',
key: 'esc'
},
{
name: 'next',
desc: '下一集',
key: 'alt+right'
},
{
name: 'prev',
desc: '上一集',
key: 'alt+left'
},
{
name: 'home',
desc: '跳到视频开始位置',
key: 'home'
},
{
name: 'end',
desc: '跳到视频结束位置',
key: 'end'
},
{
name: 'opacityUp',
desc: '透明度调高',
key: 'alt+up'
},
{
name: 'opacityDown',
desc: '透明度调低',
key: 'alt+down'
},
{
name: 'playbackRateUp',
desc: '播放倍速加快',
key: 'pageup'
},
{
name: 'playbackRateDown',
desc: '播放倍速减慢',
key: 'pagedown'
},
{
name: 'mini',
desc: '进入或退出mini模式',
key: 'alt+m'
}
]
const getSite = (key) => {
for (const i of sites) {
if (key === i.key) {
return i
}
}
}
export {
setting,
sites,
localKey,
getSite
}

View File

@@ -1,4 +1,4 @@
import db from './index'
import db from './dexie'
const { mini } = db
export default {
async add (doc) {

22
src/lib/dexie/search.js Normal file
View File

@@ -0,0 +1,22 @@
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,5 +1,6 @@
import db from './index'
import db from './dexie'
const { setting } = db
export default {
async find () {
return await setting.get({ id: 0 })

14
src/lib/dexie/shortcut.js Normal file
View File

@@ -0,0 +1,14 @@
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)
}
}

13
src/lib/dexie/sites.js Normal file
View File

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

19
src/lib/dexie/star.js Normal file
View File

@@ -0,0 +1,19 @@
import db from './dexie'
const { star } = db
export default {
async add (doc) {
return await star.add(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)
}
}

View File

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

View File

@@ -1,6 +1,3 @@
import Vue from 'vue'
import { Message, Pagination } from 'element-ui'
Vue.use(Pagination)
Vue.prototype.$m = Message
import { Message } from 'element-ui'
Vue.prototype.$message = Message

18
src/lib/site/server.js Normal file
View File

@@ -0,0 +1,18 @@
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())
app.post('/api', async (req, res) => {
const result = await Axios.get(req.body.url)
res.json({
code: 1,
info: result.data
})
})
app.listen(4848)

File diff suppressed because it is too large Load Diff

View File

@@ -1,477 +1,193 @@
import { sites } from '../dexie'
import axios from 'axios'
import { getSite } from './sites'
import parser from 'fast-xml-parser'
const zy = {
key: 'zuidazy', // sites[n] 视频源
id: 0, // 视频类型
page: 1, // 第几页
keywords: '', // 搜索关键字
// 获取浏览列表
film_get (key, id = 0, page = 1) {
ports: 4848, // 端口号
xmlConfig: { // XML 转 JSON 配置
trimValues: true,
textNodeName: '_t',
ignoreAttributes: false,
attributeNamePrefix: '_',
parseAttributeValue: true
},
getSite (key) {
return new Promise((resolve, reject) => {
const site = getSite(key)
let url = ''
if (id === 0) {
url = site.new.replace(/{page}/, page)
} else {
url = site.view.replace(/{id}/, id).replace(/{page}/, page)
}
const type = site.type
axios.get(url).then(async res => {
const data = res.data
if (type === 0) {
const zeroData = await this.film_get_type_zero(data, key)
resolve(zeroData)
}
if (type === 1) {
const oneData = await this.film_get_type_one(data, key)
resolve(oneData)
}
if (type === 2) {
const twoData = await this.film_get_type_two(data, key)
resolve(twoData)
}
if (type === 3) {
const threeData = await this.film_get_type_three(data, key)
resolve(threeData)
sites.all().then(res => {
for (const i of res) {
if (key === i.key) {
resolve(i)
}
}
}).catch(err => {
reject(err)
})
})
},
film_get_type_zero (txt, key) {
/**
* 获取资源分类 和 所有资源的总数, 分页等信息
* @param {*} key 资源网 key
* @returns
*/
class (key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.xing_vb li')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 1; i < list.length - 1; i++) {
const info = {
site: key,
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[3].innerText,
time: list[i].childNodes[5].innerText,
detail: url + list[i].childNodes[1].querySelector('a').getAttribute('href'),
index: 0
this.getSite(key).then(res => {
const site = res
axios.post(`http://localhost:${this.ports}/api`, { url: site.api }).then(res => {
const data = res.data.info
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)
}
}
d.list.push(info)
}
d.update = parseInt(html.querySelectorAll('.xing_top_right li strong')[0].innerText)
let t = html.querySelector('.pages').innerText
t = t.split('条')[0]
t = t.split('共')[1]
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
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)
})
})
})
},
film_get_type_one (txt, key) {
/**
* 获取资源列表
* @param {*} key 资源网 key
* @param {number} [pg=1] 翻页 page
* @param {*} t 分类 type
* @returns
*/
list (key, pg = 1, t) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.videoContent li')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 0; i < list.length; i++) {
const info = {
site: key,
name: list[i].querySelector('.videoName').innerText,
type: list[i].querySelector('.category').innerText,
time: list[i].querySelector('.time').innerText,
detail: url + list[i].querySelector('.address').getAttribute('href'),
index: 0
}
d.list.push(info)
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}`
}
d.update = parseInt(html.querySelectorAll('.header_list li span')[0].innerText)
let t = html.querySelectorAll('.pagination li')
t = t[t.length - 2].innerText
d.total = parseInt(t) * 50
resolve(d)
} catch (err) {
reject(err)
}
axios.post(`http://localhost:${this.ports}/api`, { url: url }).then(async res => {
const data = res.data.info
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
})
})
},
film_get_type_two (txt, key) {
/**
* 获取总资源数, 以及页数
* @param {*} key 资源网
* @param {*} t 分类 type
* @returns page object
*/
page (key, t) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.nr')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 0; i < list.length; i++) {
const info = {
site: key,
name: '',
type: list[i].querySelector('.btn_span').innerText,
time: list[i].querySelector('.hours').innerText,
detail: url + list[i].querySelector('.name').getAttribute('href'),
index: 0
}
let name = list[i].querySelector('.name').innerText
name = name.replace(/^\s*|\s*$/g, '')
info.name = name
d.list.push(info)
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`
}
d.update = parseInt(html.querySelector('.kfs em').innerText)
d.total = parseInt(html.querySelector('.date span').innerText)
let t = html.querySelector('.pag2').innerText
t = t.split('条')[0]
t = t.split('共')[1]
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
axios.post(`http://localhost:${this.ports}/api`, { url: url }).then(async res => {
const data = res.data.info
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)
})
})
})
},
film_get_type_three (txt, key) {
/**
* 搜索资源
* @param {*} key 资源网 key
* @param {*} wd 搜索关键字
* @returns
*/
search (key, wd) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.xing_vb li')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 1; i < list.length - 1; i++) {
const info = {
site: key,
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[2].innerText,
time: list[i].childNodes[3].innerText,
detail: url + list[i].childNodes[1].querySelector('a').getAttribute('href'),
index: 0
}
d.list.push(info)
}
d.update = parseInt(html.querySelectorAll('.xing_top_right li strong')[0].innerText)
let t = html.querySelector('.pages').innerText
t = t.split('条')[0]
t = t.split('共')[1]
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
this.getSite(key).then(res => {
const site = res
wd = encodeURI(wd)
axios.post(`http://localhost:${this.ports}/api`, { url: site.api + '?wd=' + wd }).then(res => {
const data = res.data.info
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
})
})
},
// 获取详情
detail_get (key, url) {
/**
* 获取资源详情
* @param {*} key 资源网 key
* @param {*} id 资源唯一标识符 id
* @returns
*/
detail (key, id) {
return new Promise((resolve, reject) => {
const type = getSite(key).type
axios.get(url).then(async res => {
if (type === 0) {
const zeroData = await this.detail_get_type_zero(res.data, key)
resolve(zeroData)
}
if (type === 1) {
const oneData = await this.detail_get_type_one(res.data, key)
resolve(oneData)
}
if (type === 2) {
const twoData = await this.detail_get_type_two(res.data, key)
resolve(twoData)
}
if (type === 3) {
const threeData = await this.detail_get_type_three(res.data, key)
resolve(threeData)
}
this.getSite(key).then(res => {
axios.post(`http://localhost:${this.ports}/api`, { url: res.api + '?ac=videolist&ids=' + id }).then(res => {
const data = res.data.info
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
}).catch(err => {
reject(err)
})
})
},
detail_get_type_zero (txt, key) {
/**
* 下载资源
* @param {*} key 资源网 key
* @param {*} id 资源唯一标识符 id
* @returns
*/
download (key, id) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
this.getSite(key).then(res => {
const site = res
const url = site.download
if (url) {
axios.post(`http://localhost:${this.ports}/api`, { url: url + '?ac=videolist&ids=' + id + '&ct=1' }).then(res => {
const data = res.data.info
const json = parser.parse(data, this.xmlConfig)
const videoList = json.rss.list.video
resolve(videoList)
}).catch(err => {
reject(err)
})
} else {
resolve(null)
}
const vodBox = html.querySelector('.vodBox')
data.info = vodBox.innerHTML
const title = html.querySelector('.vodh h2').innerText
const index = html.querySelector('.vodh span').innerText
data.name = title + index
const vodInfo = html.querySelectorAll('.playBox')
for (let i = 0; i < vodInfo.length; i++) {
const k = vodInfo[i].innerText
if (k.indexOf('剧情介绍') >= 0) {
data.desc = vodInfo[i].querySelector('.vodplayinfo').innerText
}
}
const vodLi = html.querySelectorAll('.ibox .vodplayinfo li')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].innerText
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.push(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.push(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
detail_get_type_one (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
}
let name = html.querySelector('.whitetitle').innerText
name = name.split('')[1].replace(/^\s*|\s*$/g, '')
data.name = name
const vodBox = html.querySelector('.white').innerHTML
data.info = vodBox
const vodInfo = html.querySelectorAll('.white')
for (let i = 0; i < vodInfo.length; i++) {
const k = vodInfo[i].innerText
if (k.indexOf('剧情介绍') >= 0) {
data.desc = vodInfo[i].querySelector('div').innerText
}
}
const vodLi = html.querySelectorAll('.playlist li #m3u8')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].value
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.push(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.push(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
detail_get_type_two (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
}
const title = html.querySelector('.vodh h2').innerText
const index = html.querySelector('.vodh span').innerText
data.name = title + index
const vodBox = html.querySelector('.vodBox').innerHTML
data.info = vodBox
data.desc = html.querySelector('.vodplayinfo').innerText
const vodLi = html.querySelectorAll('.vodplayinfo li')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].innerText
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.push(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.push(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
detail_get_type_three (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
}
const vodBox = html.querySelector('.vodBox')
data.info = vodBox.innerHTML
const title = html.querySelector('.vodh h2').innerText
const index = html.querySelector('.vodh span').innerText
data.name = title + index
const vodInfo = html.querySelectorAll('.playBox')
for (let i = 0; i < vodInfo.length; i++) {
const k = vodInfo[i].innerText
if (k.indexOf('剧情介绍') >= 0) {
data.desc = vodInfo[i].querySelector('.vodplayinfo').innerHTML
}
}
const vodLi = html.querySelectorAll('.ibox .vodplayinfo li')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].innerText
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.unshift(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.unshift(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
// 搜索列表
search_get (key, keywords = '', page = 1) {
return new Promise((resolve, reject) => {
const site = getSite(key)
const type = site.type
let url = null
if (type === 0) {
url = site.search.replace(/{page}/, page).replace(/{keywords}/, keywords)
}
if (type === 1) {
url = site.search.replace(/{keywords}/, keywords)
}
axios.get(url).then(async res => {
const data = res.data
if (type === 0) {
const zeroData = await this.search_get_type_zero(data, key)
resolve(zeroData)
}
if (type === 1) {
const oneData = await this.search_get_type_one(data, key)
resolve(oneData)
}
if (type === 3) {
const threeData = await this.search_get_type_three(data, key)
resolve(threeData)
}
}).catch(err => {
reject(err)
})
})
},
search_get_type_zero (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.xing_vb li')
const d = { list: [], total: 0 }
const url = getSite(key).url
for (let i = 1; i < list.length - 1; i++) {
const info = {
site: key,
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[3].innerText,
time: list[i].childNodes[5].innerText,
detail: url + list[i].childNodes[1].querySelector('a').getAttribute('href'),
index: 0
}
d.list.push(info)
}
const t = html.querySelector('.nvc dd').innerText.replace(/[^\d]/g, '')
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
})
},
search_get_type_one (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.videoContent li')
const d = { list: [], total: 0 }
const url = getSite(key).url
for (let i = 0; i < list.length; i++) {
const info = {
site: key,
name: list[i].querySelector('.videoName').innerText,
type: list[i].querySelector('.category').innerText,
time: list[i].querySelector('.time').innerText,
detail: url + list[i].querySelector('.address').getAttribute('href'),
index: 0
}
d.list.push(info)
}
d.total = list.length
resolve(d)
} catch (err) {
reject(err)
}
})
},
search_get_type_three (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.xing_vb li')
const d = { list: [], total: 0 }
const url = getSite(key).url
for (let i = 1; i < list.length - 1; i++) {
const info = {
site: key,
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[2].innerText,
time: list[i].childNodes[3].innerText,
detail: url + list[i].childNodes[1].querySelector('a').getAttribute('href'),
index: 0
}
d.list.push(info)
}
const t = html.querySelector('.nvc dd').innerText.replace(/[^\d]/g, '')
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
})
}
}

View File

@@ -1,52 +0,0 @@
{
"zh": "Chinese",
"en": "English",
"language": "Language",
"default_site": "Default Site",
"view": "View",
"play": "Play",
"star": "Star",
"setting": "Setting",
"exists": "Already exists",
"videoName": "Video Name",
"type": "Type",
"time": "Time",
"operate": "Operate",
"share": "Share",
"detail": "Detail",
"close": "Close",
"download": "Download",
"all_download": "All Download",
"next": "Next",
"play_list": "Play List",
"history": "History",
"top": "Top",
"mini": "Mini",
"no_data": "No Data",
"clear_data": "Clear Data",
"delete": "Delete",
"from": "From",
"sync": "Sync",
"total": "Items",
"website": "Official Website",
"issues": "Issues",
"theme": "Theme",
"donate": "Donate",
"set_success": "Set up successfully.",
"delete_success": "Delete successful.",
"delete_failed": "Delete failed.",
"star_success": "Collection success.",
"first_video": "This is the first episode.",
"last_video": "This is the last episode.",
"qr_tips": "Long click recognition.",
"zy_tips": "Prohibit the dissemination of illegal resources.",
"share_tips": "It has been copied to the clipboard. Please share it~",
"not_support_search": "Search is not supported on this site.",
"copy_success": "has been copied, Download it now",
"async_failed": "Synchronization successful, no updates found.",
"async_success": "Synchronization succeeded, update found.",
"no_history": "No history data.",
"clearDB": "Reset software",
"clearTips": "Click to clear the database and close the software",
"rate": "The current video speed is: "
}

View File

@@ -1,9 +0,0 @@
import en from './en.json'
import zhCn from './zh-cn'
export const defaultLocal = 'zhCn'
export const languages = {
en: en,
zhCn: zhCn
}

View File

@@ -1,52 +0,0 @@
{
"zh": "中文",
"en": "英文",
"language": "语言",
"default_site": "默认源",
"view": "浏览",
"play": "播放",
"star": "收藏",
"setting": "设置",
"exists": "已存在",
"videoName": "视频名称",
"type": "类型",
"time": "时间",
"operate": "操作",
"share": "分享",
"detail": "详情",
"close": "关闭",
"download": "下载",
"all_download": "全集下载",
"next": "下一集",
"play_list": "播放列表",
"history": "历史记录",
"top": "置顶",
"mini": "精简模式",
"no_data": "无数据",
"clear_data": "清空数据",
"delete": "删除",
"from": "来源",
"sync": "同步",
"total": "条数据",
"website": "官网",
"issues": "反馈",
"theme": "主题",
"donate": "捐赠",
"set_success": "设置成功。",
"delete_success": "删除成功。",
"delete_failed": "删除失败。",
"star_success": "收藏成功。",
"first_video": "这已经是第一集了。",
"last_video": "这已经是最后一集了。",
"qr_tips": "长按二维码,识别播放。",
"zy_tips": "『ZY Player』技术支持严禁传播违法资源。",
"share_tips": "已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!",
"not_support_search": "这个网站不支持搜索。",
"copy_success": "已复制,快去下载吧。",
"async_failed": "同步成功, 未查询到更新。",
"async_success": "同步成功, 查询到更新。",
"no_history": "无历史记录",
"clearDB": "重置软件",
"clearTips": "软件没有问题,请勿重置软件,否则数据丢失概不负责.点击即清空数据库,并关闭软件.",
"rate": "当前视频播放倍速为:"
}

View File

@@ -3,24 +3,10 @@ import App from './App.vue'
import store from './store'
import 'modern-normalize'
import Register from './components/register'
import VueI18n from 'vue-i18n'
import { languages, defaultLocal } from './locales/index'
import './lib/element/index'
Vue.config.productionTip = false
Register.registerComponents()
Vue.use(VueI18n)
const messages = Object.assign(languages)
const i18n = new VueI18n({
locale: defaultLocal,
fallbackLocale: 'zhCn',
messages
})
Vue.config.productionTip = false
new Vue({
store,
i18n,
render: h => h(App)
}).$mount('#app')

View File

@@ -2,26 +2,28 @@
<div class="mini">
<div class="top">
<div class="left">
<span class="number" v-show="length > 0">{{index + 1}} / {{length}}</span>
<span class="zy-svg" @click="prevEvent" v-show="index > 0">
<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="index < (length - 1)">
<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">
<input type="number" min="5" max="100" v-model="opacity" @change="opacityChange"/>
</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="min" @click="frameClickEvent('miniMin')"></span>
<span class="close" @click="frameClickEvent('miniClose')"></span>
<span class="min" @click="frameClickEvent('min')"></span>
<span class="close" @click="frameClickEvent('close')"></span>
</div>
</div>
<div class="bottom">
@@ -30,12 +32,12 @@
</div>
</template>
<script>
import tools from '../lib/site/tools'
import mini from '../lib/dexie/mini'
import history from '../lib/dexie/history'
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 ipc = require('electron').ipcRenderer
const { remote, ipcRenderer } = require('electron')
export default {
name: 'mini',
data () {
@@ -43,87 +45,129 @@ export default {
xg: null,
config: {
id: 'xg',
lang: 'zh-cn',
url: '',
fluid: true,
lang: 'zh-cn',
width: '100%',
height: '100%',
autoplay: false,
videoInit: true,
screenShot: true,
keyShortcut: 'on',
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]
playbackRate: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5],
controls: false
},
opacity: 100,
name: '',
video: {},
d: {},
index: 0,
length: 0
detail: {},
m3u8Arr: [],
rate: 1,
progress: 0
}
},
methods: {
frameClickEvent (e) {
ipc.send(e)
if (e === 'min') {
const win = remote.getCurrentWindow()
win.minimize()
return false
}
if (e === 'close') {
ipcRenderer.send('win')
return false
}
},
opacityChange (e) {
ipc.send('miniOpacity', this.opacity / 100)
opacityChange (val) {
const win = remote.getCurrentWindow()
const num = val / 100
win.setOpacity(num)
return false
},
getUrls () {
mini.find().then(res => {
const v = res
this.video = res
tools.detail_get(v.site, v.detail).then(res => {
this.d = res
this.index = v.index
this.length = this.d.m3u8_urls.length
const link = res.m3u8_urls[v.index]
const src = link.split('$')[1]
this.xg.src = src
const currentTime = v.currentTime
if (currentTime !== '') {
zy.detail(res.site, res.ids).then(e => {
this.name = e.name
this.detail = e
const dd = e.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])
}
}
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 = currentTime
this.xg.currentTime = res.time
})
} else {
this.xg.play()
}
this.onPlayVideo()
this.xg.on('ended', () => {
if (this.d.m3u8_urls.length > 1 && (this.d.m3u8_urls.length - 1 > this.index)) {
this.video.currentTime = 0
this.videoPlaying()
this.xg.once('ended', () => {
if (m3u8Arr.length > 1 && (m3u8Arr.length - 1 > res.index)) {
this.video.time = 0
this.video.index++
this.index++
let src = this.d.m3u8_urls[this.index]
src = src.split('$')[1]
this.xg.src = src
this.xg.src = m3u8Arr[this.video.index]
this.xg.play()
}
})
})
})
},
onPlayVideo () {
const h = { ...this.video }
history.find({ detail: h.detail }).then(res => {
videoPlaying () {
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
if (res) {
h.id = res.id
history.update(res.id, h)
res.index = this.video.index
history.update(res.id, res)
} else {
h.currentTime = ''
delete h.id
history.add(h)
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(h.detail)
this.timerEvent()
},
timerEvent (d) {
timerEvent () {
this.timer = setInterval(() => {
history.find({ detail: d }).then(res => {
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.currentTime = this.xg.currentTime
v.index = this.index
v.time = this.xg.currentTime
v.index = this.video.index
const id = v.id
delete v.id
history.update(id, v)
@@ -132,36 +176,32 @@ export default {
}, 10000)
},
prevEvent () {
if (this.index === 0) {
if (this.video.index === 0) {
return false
}
history.find({ detail: this.video.detail }).then(res => {
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
const v = res
v.index--
const id = v.id
v.index--
delete v.id
history.update(id, v).then(e => {
let src = this.d.m3u8_urls[v.index]
src = src.split('$')[1]
this.xg.src = src
this.index--
this.xg.src = this.m3u8Arr[v.index]
this.video.index--
})
})
},
nextEvent () {
if (this.index >= this.d.m3u8_urls.length - 1) {
if (this.video.index >= this.m3u8Arr.length - 1) {
return false
}
history.find({ detail: this.video.detail }).then(res => {
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 => {
let src = this.d.m3u8_urls[v.index]
src = src.split('$')[1]
this.xg.src = src
this.index++
this.xg.src = this.m3u8Arr[v.index]
this.video.index++
})
})
},
@@ -171,59 +211,161 @@ export default {
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') {
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
}
}
},
created () {
this.getUrls()
this.mtEvent()
},
mounted () {
this.xg = new Hls(this.config)
ipc.on('next', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.nextEvent()
}
}
})
ipc.on('prev', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.prevEvent()
}
}
})
ipc.on('up', () => {
if (this.opacity <= 95) {
this.opacity = this.opacity + 5
this.opacityChange(this.opacity)
}
})
ipc.on('down', () => {
if (this.opacity >= 10) {
this.opacity = this.opacity - 5
this.opacityChange(this.opacity)
}
})
ipc.on('playbackRateUp', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.playbackRateEvent(0.25)
}
}
})
ipc.on('playbackRateDown', () => {
if (this.xg) {
if (this.xg.hasStart) {
this.playbackRateEvent(-0.25)
}
}
})
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
<style lang="scss">
html,body{
padding: 0;
padding: 1px;
margin: 0;
height: 100%;
width: 100%;
@@ -231,9 +373,15 @@ html,body{
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{
-webkit-app-region: drag;
width: 100%;
height: 30px;
display: flex;
@@ -249,7 +397,7 @@ html,body{
svg{
width: 24px;
height: 24px;
stroke: #fff;
stroke: #888;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
@@ -262,20 +410,10 @@ html,body{
align-items: center;
height: 100%;
flex: 1;
.number{
color: #fff;
margin: 0 10px;
.title, .opacity, .rate, .progress{
color: #888;
font-size: 12px;
}
.opacity{
-webkit-app-region: no-drag;
margin-left: 10px;
input{
text-indent: 4px;
background-color: #000;
color: #fff;
border: 1px solid #aaa;
}
margin: 0 10px;
}
}
.right{
@@ -292,7 +430,7 @@ html,body{
border-radius: 50%;
margin-right: 10px;
cursor: pointer;
opacity: 0.5;
opacity: 0.4;
&.min{
background-color: #ffbe2a;
}
@@ -331,7 +469,10 @@ html,body{
}
.bottom{
width: 100%;
height: 305px;
flex: 1;
.xgplayer-start{
-webkit-app-region: no-drag;
}
}
}
</style>

View File

@@ -1,5 +1,8 @@
import Vue from 'vue'
import Mini from './Mini'
import 'modern-normalize'
import '../lib/element/index'
Vue.config.productionTip = false
new Vue({

View File

@@ -6,63 +6,59 @@ Vue.use(Vuex)
export default new Vuex.Store({
state: {
view: 'Film',
theme: 'light',
site: 'zuidazy',
language: 'zhCn',
setting: {
theme: 'light',
site: 'zuidazy',
view: 'picture',
shortcut: true
},
detail: {
show: false,
v: {}
key: '',
info: {}
},
share: {
show: false,
v: {}
key: '',
info: {}
},
video: {}
video: {
key: '',
info: {}
}
},
getters: {
getView: state => {
return state.view
},
getTheme: state => {
return state.theme
},
getSite: state => {
return state.site
},
getLanguage: state => {
return state.language
getSetting: state => {
return state.setting
},
getDetail: state => {
return state.detail
},
getVideo: state => {
return state.video
},
getShare: state => {
return state.share
},
getVideo: state => {
return state.video
}
},
mutations: {
SET_VIEW: (state, payload) => {
state.view = payload
},
SET_THEME: (state, payload) => {
state.theme = payload
},
SET_SITE: (state, payload) => {
state.site = payload
},
SET_LANGUAGE: (state, payload) => {
state.language = payload
SET_SETTING: (state, payload) => {
state.setting = payload
},
SET_DETAIL: (state, payload) => {
state.detail = payload
},
SET_VIDEO: (state, payload) => {
state.video = payload
},
SET_SHARE: (state, payload) => {
state.share = payload
},
SET_VIDEO: (state, payload) => {
state.video = payload
}
}
})

10046
yarn.lock Normal file

File diff suppressed because it is too large Load Diff