Compare commits

...

88 Commits

Author SHA1 Message Date
hunlongyu
022b1f4090 紧急修复视频源检测bug, 以及增加手动更新提示。 2020-10-28 22:44:53 +08:00
hunlongyu
9c08a64524 💎 手动更新提示 2020-10-28 22:11:41 +08:00
hunlongyu
90ec848c11 v2.5.6 2020-10-28 21:45:48 +08:00
hunlongyu
82270a702f v2.5.5 2020-10-28 21:39:29 +08:00
Hunlongyu
61dcb1ec49 💍 优化视频源分组功能 2020-10-28 18:22:18 +08:00
Hunlongyu
40b853361e 💄 新增或编辑源的时候, 校验key值的唯一性 2020-10-28 18:00:59 +08:00
Hunlongyu
57ff3325f0 💋 优化图标显示逻辑 2020-10-28 17:38:02 +08:00
Hunlongyu
c4283e2da0 🎓 优化播放页图标, 优化视频停止播放后不显示历史记录提示. 修复报错 2020-10-28 17:30:46 +08:00
Hunlongyu
7052bd7e05 🎩 新增播放视频时换源功能. 2020-10-28 17:04:44 +08:00
Hunlongyu
64a12a06c4 👒 移除百度统计, 主要是记录缺失, 并且不准确 2020-10-28 15:38:02 +08:00
Hunlongyu
3df3b385ce ⛑ 新增分类过滤器, 主要针对豆瓣资源网分类出错问题 2020-10-28 15:34:33 +08:00
Hunlongyu
e77db4711e 🧢 优化视频源检测功能 2020-10-28 15:33:30 +08:00
Hunlongyu
fb3fd26870 Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player into master 2020-10-28 12:00:14 +08:00
Hunlongyu
72790f5f3e 👑 添加了视频源检测功能. 2020-10-28 11:59:55 +08:00
haiyangcui
ea2815969d 把IPTV的初始数据放入单独的Json文件中 2020-10-27 23:12:30 +01:00
Hunlongyu
e330cff7d4 🧦 electron 优化, 忽略证书错误, 关闭沙盒. 2020-10-27 11:43:11 +08:00
haiyangcui
da5b91535e 搜索后获取详细信息并展示 2020-10-26 23:53:12 +01:00
haiyangcui
00c8f0c1e2 v2.5.4 2020-10-26 17:26:49 +01:00
haiyangcui
924fd439d9 更新导入收藏,支持一次导入多个文件 2020-10-26 16:37:44 +01:00
haiyangcui
ed039894c8 导入时无需调用upgradeFavorites 2020-10-26 16:20:08 +01:00
haiyangcui
005bdf7ea6 监听并更新收藏及源列表 2020-10-26 16:19:48 +01:00
haiyangcui
19071faf70 更新福利片关键词 2020-10-26 15:13:18 +01:00
hunlongyu
176d9f6b5a Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player 2020-10-26 20:57:31 +08:00
hunlongyu
31a8dd0f32 🧤 升级 xgplayer 依赖 2020-10-26 20:57:25 +08:00
haiyangcui
336ea08c69 Film页面只获取active的源站 2020-10-26 13:47:47 +01:00
haiyangcui
8e4a0870a1 播放电视频道时,更新播放页面的左上角标题 2020-10-26 13:42:44 +01:00
hunlongyu
86130e6c53 🧣 修复第一次加载资源失败的bug 2020-10-26 20:24:35 +08:00
Hunlongyu
e984d0a349 🩳 修复修改数据库,导致无法播放的bug 2020-10-26 18:18:07 +08:00
Hunlongyu
506991f3b1 👖 优化 history 和 star 数据库的搜索.后台不再提示warning.需要重置数据库. 2020-10-26 18:06:01 +08:00
Hunlongyu
04c35ba7a9 👔 开发所有系统的更新功能 2020-10-26 16:46:41 +08:00
Hunlongyu
275b6757bf 👕 实现手动更新功能 2020-10-26 16:33:00 +08:00
Hunlongyu
612930cc8b 🥼 修复停止播放后,再次播放有2个声音的问题 2020-10-26 11:19:15 +08:00
haiyangcui
80e36fa99c 源站排序总是有问题, 先移除掉 2020-10-25 15:40:18 +01:00
haiyangcui
f069044f28 简单重构 2020-10-25 16:23:31 +01:00
haiyangcui
c77ac7ea7f 重构一下EditSites页面的代码 2020-10-25 15:33:39 +01:00
haiyangcui
803181b4f7 修复源页面编辑后Film页面不更新的bug 2020-10-25 13:49:58 +01:00
haiyangcui
ac2474903d 恢复滚动条 2020-10-24 23:38:02 +02:00
haiyangcui
04b7e139da 更新内置电视频道 2020-10-24 23:00:58 +02:00
haiyangcui
b49e1b82bf 支持自选源列排序 2020-10-24 23:00:40 +02:00
haiyangcui
dc00c690e7 重新排序后更新数据库 2020-10-24 22:58:15 +02:00
haiyangcui
44ba552435 改进IPTV页面逻辑 2020-10-24 16:20:49 +02:00
haiyangcui
8d84d1bf22 统一字体大小 2020-10-24 14:29:30 +02:00
haiyangcui
c78368dac9 新增源时,支持输入源标识 2020-10-24 14:24:18 +02:00
haiyangcui
11af5a7ecd 导入收藏时,更新收藏项的源站信息 2020-10-24 14:14:44 +02:00
haiyangcui
ecfeecf45f 为表头按钮添加图标 2020-10-24 14:06:25 +02:00
haiyangcui
455f5dae84 统一star数据格式的定义 2020-10-24 13:19:47 +02:00
Hunlongyu
ffbffde74f Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player into master 2020-10-24 13:52:29 +08:00
Hunlongyu
0be0433355 🥼 移除master 分支下的更新功能 2020-10-24 13:52:20 +08:00
haiyangcui
cc2dc19e39 Revert "统一star数据格式的定义"
This reverts commit aa4583898a.
2020-10-23 23:31:44 +02:00
haiyangcui
aa4583898a 统一star数据格式的定义 2020-10-23 23:31:01 +02:00
haiyangcui
2e96812a1f 最后一列的表头向右对齐 2020-10-23 23:23:25 +02:00
haiyangcui
6f52d73d52 鼠标划过的行添加少许放大效果 2020-10-23 22:45:04 +02:00
haiyangcui
a3ebba641f 源站编辑,支持编辑分组 2020-10-23 22:29:52 +02:00
haiyangcui
8ceffab2fe 搜索结果里添加“源站”列 2020-10-23 16:43:33 +02:00
haiyangcui
454d192141 更好支持中文排序 2020-10-23 15:53:21 +02:00
haiyangcui
a1423993c8 固定表头 2020-10-23 15:14:12 +02:00
haiyangcui
1c9a84dbc6 开关批处理,无需返回页面顶部 2020-10-23 13:37:41 +02:00
haiyangcui
304b0d10b4 Fix typo 2020-10-23 13:32:46 +02:00
cuiocean
aff97f6d46 Merge pull request #307 from buvta/patch-1
IPTV小调整
2020-10-23 12:27:23 +02:00
haiyangcui
650c882f58 移除自选源列的排序 2020-10-23 12:20:17 +02:00
Hunlongyu
32fa465cf2 🦺 关闭自动更新, 手动更新软件. (未完成) 2020-10-23 14:40:40 +08:00
Hunlongyu
1fdfada14f Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player into master 2020-10-23 11:21:52 +08:00
Hunlongyu
5ed8b6e49a 👓 补上忘记写的自动更新功能 😂 2020-10-23 11:20:36 +08:00
haiyangcui
c2e2f1c490 支持自选源列排序 2020-10-22 18:34:00 +02:00
haiyangcui
7ad40ba375 历史记录的导入导出 2020-10-22 18:22:41 +02:00
buvta
c708292051 IPTV变更分租状态开关时表格回到顶部 2020-10-23 00:02:18 +08:00
buvta
79932a74bc 固定IPTV和源编辑表头 2020-10-22 23:48:19 +08:00
buvta
3f79f31550 IPTV转移总频道数到表头
这PR也太好混了吧
2020-10-22 23:40:28 +08:00
haiyangcui
1d33db0143 IPTV支持从JSON文件导入 2020-10-22 17:07:05 +02:00
haiyangcui
1dc7b38160 批处理开启关闭源是否为自选源 2020-10-22 16:53:57 +02:00
haiyangcui
db7cdab787 批处理视频源的分组 2020-10-22 16:37:54 +02:00
haiyangcui
d1e6ac1ae6 支持源分组 2020-10-22 16:18:24 +02:00
haiyangcui
d63710afe6 统一字体大小 2020-10-22 14:07:14 +02:00
haiyangcui
8abe4b7ef8 IPTV批处理分组 2020-10-22 14:04:45 +02:00
haiyangcui
98b4f5bc1d 改进star页面的排序 2020-10-22 12:27:45 +02:00
haiyangcui
248c0994c9 统一页面字体大小,更新操作栏使其自适应宽度 2020-10-22 12:22:19 +02:00
Hunlongyu
2eba0523bc 🛒 优化分享图片 2020-10-22 14:23:38 +08:00
Hunlongyu
49589102d9 🧶 添加百度统计 2020-10-22 14:22:59 +08:00
Hunlongyu
79b02df628 🧵 优化初次加载请求多次的问题 2020-10-22 13:41:47 +08:00
Hunlongyu
0f456fe7a8 🧵 优化样式 2020-10-22 11:33:30 +08:00
Hunlongyu
850c8d5423 🎨 视频源异常状态处理, 自动重置源 2020-10-22 10:59:29 +08:00
haiyangcui
63cf367f52 自适应列宽 2020-10-21 18:42:50 +02:00
haiyangcui
bd43f06e7d 可以打开或关闭源是否为自选源 2020-10-21 17:34:11 +02:00
haiyangcui
2ed47e64c3 解决Film页面的warning 2020-10-21 17:11:54 +02:00
haiyangcui
8817d39d49 IPTV搜索移到表头位置 2020-10-21 16:25:15 +02:00
haiyangcui
b68525213a 引入需要的element-ui的组件 2020-10-21 16:07:37 +02:00
haiyangcui
0efbc38137 Merge branch 'release_2.5.3' 2020-10-21 13:51:16 +02:00
Hunlongyu
500dbfa1ee 🖼 测试代码 2020-10-21 18:23:45 +08:00
29 changed files with 2962 additions and 2344 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "zy",
"version": "2.5.3",
"version": "2.5.7",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -24,6 +24,7 @@
"cors": "^2.8.5",
"dexie": "^3.0.2",
"electron-localshortcut": "^3.2.1",
"electron-updater": "^4.3.5",
"element-ui": "^2.13.2",
"express": "^4.17.1",
"fast-xml-parser": "^3.17.4",
@@ -34,12 +35,13 @@
"mousetrap": "^1.6.5",
"qrcode.vue": "^1.7.0",
"randomstring": "^1.1.5",
"v-fit-columns": "^0.2.0",
"vue": "^2.6.12",
"vue-infinite-loading": "^2.4.5",
"vue-waterfall-plugin": "^1.1.0",
"vuedraggable": "^2.24.2",
"vuex": "^3.5.1",
"xgplayer": "^2.12.2",
"xgplayer": "^2.13.0",
"xgplayer-hls.js": "^2.2.5"
},
"devDependencies": {

View File

@@ -5,7 +5,7 @@
<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 %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>

View File

@@ -9,7 +9,7 @@
<Star v-show="view === 'Star'" />
<History v-show="view === 'History'" />
<Setting v-show="view === 'Setting'" />
<EditSites v-show="view === 'EditSites'"/>
<EditSites v-if="view === 'EditSites'"/>
</div>
<transition name="slide">
<Detail v-if="detail.show"/>

View File

@@ -170,6 +170,9 @@
&.note{
width: 10%;
}
&.info{
width: 10%;
}
&.operate{
.btn{
width: 40px;
@@ -183,7 +186,7 @@
// scroll
.zy-scroll{
&::-webkit-scrollbar{
width: 5px;
width: 10px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
@@ -209,6 +212,7 @@
.listpage-content{
height: 100%;
position: relative;
font-size: 1rem;
.listpage-header{
width: 100%;
height: 40px;
@@ -217,17 +221,16 @@
justify-content: space-between;
padding-left: 50px;
padding-right: 50px;
.btn{
&:hover{
cursor: pointer;
}
}
.el-button{
font-size: 1rem;
border: none;
&:hover{
cursor: pointer;
}
}
.is-loading:before {
background-color: none !important;
}
.el-input{
width: 200px;
}
@@ -240,15 +243,39 @@
}
.el-table{
height: 100%;
width: 100%;
overflow-y: auto;
font-size: 1rem;
}
.el-table__body-wrapper{
&::-webkit-scrollbar{
width: 10px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
position: absolute;
}
&::-webkit-scrollbar-track {
border-radius: 10px;
position: absolute;
}
}
.el-input{
width: 200px;
}
.el-table__body td,.el-table__body th{
height: 50px;
border-bottom: 1px solid;
}
.el-table--enable-row-hover .el-table__body tr:hover>td{
transform: scale(1.02);
}
.el-table .highlight{
color: var(--highlight-color) !important;
}
.el-button{
font-size: 1rem;
}
}
}
}
@@ -296,19 +323,4 @@
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;
}
}
}
.el-table, .el-table__body-wrapper{
&::-webkit-scrollbar{
width: 5px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
position: absolute;
}
&::-webkit-scrollbar-track {
border-radius: 10px;
position: absolute;
}
}

View File

@@ -387,11 +387,30 @@
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
}
.el-input{
input{
background-color: var(--d-bgc-2);
border: 1px solid var(--d-bgc-2);
color: var(--d-fc-1);
}
}
.el-table__header th, .el-table__header tr, .el-table__body td,.el-table__body th{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
border-bottom-color: var(--d-c-2);
}
.el-table__body-wrapper{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--d-bsc-scroll);
background: var(--d-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--d-bsc-scroll);
background: var(--d-bgc-1);
}
}
}
.el-table--enable-row-hover .el-table__body tr:hover>td{
background-color: var(--d-bgc-2);
}

View File

@@ -383,11 +383,30 @@
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
}
.el-input{
input{
background-color: var(--g-bgc-2);
border: 1px solid var(--g-bgc-2);
color: var(--g-fc-1);
}
}
.el-table__header th, .el-table__header tr, .el-table__body td,.el-table__body th{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
border-bottom-color: var(--g-c-2);
}
.el-table__body-wrapper{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--g-bsc-scroll);
background: var(--g-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--g-bsc-scroll);
background: var(--g-bgc-1);
}
}
}
.el-table--enable-row-hover .el-table__body tr:hover>td{
background-color: var(--g-bgc-2);
}

View File

@@ -383,11 +383,30 @@
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
}
.el-input{
input{
background-color: var(--l-bgc-2);
border: 1px solid var(--l-bgc-2);
color: var(--l-fc-1);
}
}
.el-table__header th, .el-table__header tr, .el-table__body td,.el-table__body th{
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
border-bottom-color: var(--l-c-2);
}
.el-table__body-wrapper{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--l-bsc-scroll);
background: var(--l-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--l-bsc-scroll);
background: var(--l-bgc-1);
}
}
}
.el-table--enable-row-hover .el-table__body tr:hover>td{
background-color: var(--l-bgc-2);
}

View File

@@ -382,11 +382,30 @@
color: var(--p-fc-1);
background-color: var(--p-bgc-1);
}
.el-input{
input{
background-color: var(--p-bgc-2);
border: 1px solid var(--p-bgc-2);
color: var(--p-fc-1);
}
}
.el-table__header th, .el-table__header tr, .el-table__body td,.el-table__body th{
color: var(--p-fc-1);
background-color: var(--p-bgc-1);
border-bottom-color: var(--p-c-2);
}
.el-table__body-wrapper{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--p-bsc-scroll);
background: var(--p-c-5);
}
&::-webkit-scrollbar-track {
box-shadow: var(--p-bsc-scroll);
background: var(--p-bgc-1);
}
}
}
.el-table--enable-row-hover .el-table__body tr:hover>td{
background-color: var(--p-bgc-2);
}

View File

@@ -4,10 +4,11 @@ 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'
import { initUpdater } from './lib/update/update'
const isDevelopment = process.env.NODE_ENV !== 'production'
// 允许跨域
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') // 允许跨域
app.commandLine.appendSwitch('--ignore-certificate-errors', 'true') // 忽略证书相关错误
let win
let mini
@@ -35,6 +36,8 @@ function createWindow () {
win.loadURL('app://./index.html')
}
initUpdater(win)
win.on('closed', () => {
win = null
})
@@ -49,6 +52,7 @@ function createMini () {
frame: false,
resizable: true,
webPreferences: {
sandbox: false,
webSecurity: false,
enableRemoteModule: true,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
@@ -73,6 +77,7 @@ if (process.platform === 'darwin') {
}
if (process.platform === 'Linux') {
app.disableHardwareAcceleration()
app.commandLine.appendSwitch('--no-sandbox') // linux 关闭沙盒模式
}
app.allowRendererProcessReuse = true

View File

@@ -69,21 +69,12 @@ export default {
set (val) {
this.SET_DETAIL(val)
}
},
editSites: {
get () {
return this.$store.getters.getEditSites
},
set (val) {
this.SET_EDITSITES(val)
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_EDITSITES']),
...mapMutations(['SET_VIEW', 'SET_DETAIL']),
changeView (e) {
this.view = e
// ChangeView 的时候关闭Detail页面
this.detail = {
show: false
}

View File

@@ -141,59 +141,56 @@ export default {
this.m3u8List = dd._t.split('#')
}
},
playEvent (n) {
async playEvent (n) {
if (!this.playOnline) {
history.find({ site: this.detail.key, ids: this.detail.info.id }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: n, site: this.detail.site } }
} else {
this.video = { key: this.detail.key, info: { id: this.detail.info.id, name: this.detail.info.name, index: n, site: this.detail.site } }
}
})
const db = await history.find({ site: this.detail.key, ids: this.detail.info.id })
if (db) {
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: n, site: this.detail.site } }
} else {
this.video = { key: this.detail.key, info: { id: this.detail.info.id, name: this.detail.info.name, index: n, site: this.detail.site } }
}
this.view = 'Play'
this.detail.show = false
} else {
history.find({ site: this.detail.key, ids: this.detail.info.id }).then(res => {
if (res) {
res.index = n
history.update(res.id, res)
} else {
const doc = {
site: this.detail.key,
ids: this.detail.info.id,
name: this.detail.info.name,
type: this.detail.info.type,
year: this.detail.info.year,
index: n,
time: ''
}
history.add(doc)
const db = await history.find({ site: this.detail.key, ids: this.detail.info.id })
if (db) {
db.index = n
history.update(db.id, db)
} else {
const doc = {
site: this.detail.key,
ids: this.detail.info.id,
name: this.detail.info.name,
type: this.detail.info.type,
year: this.detail.info.year,
index: n,
time: ''
}
})
history.add(doc)
}
onlineVideo.playVideoOnline(this.selectedOnlineSite, this.detail.info.name, n)
}
},
starEvent () {
star.find({ key: this.detail.key, ids: this.info.id }).then(res => {
if (res) {
this.$message.info('该影片已被收藏')
} else {
const docs = {
key: this.detail.key,
ids: this.info.id,
name: this.info.name,
type: this.info.type,
year: this.info.year,
last: this.info.last,
note: this.info.note
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
async starEvent () {
const db = await star.find({ key: this.detail.key, ids: this.info.id })
if (db) {
this.$message.info('该影片已被收藏')
} else {
const docs = {
key: this.detail.key,
ids: this.info.id,
site: this.detail.site,
name: this.info.name,
type: this.info.type,
year: this.info.year,
note: this.info.note,
last: this.info.last
}
}).catch(() => {
this.$message.warning('收藏失败')
})
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
},
togglePlayOnlineEvent () {
this.playOnline = !this.playOnline

View File

@@ -1,63 +1,115 @@
<template>
<div class="listpage" id="editSites">
<div class="listpage-content">
<div class="listpage-header">
<el-button @click.stop="addSite" type="text">添加</el-button>
<el-button @click.stop="exportSites" type="text">导出</el-button>
<el-button @click.stop="importSites" type="text"></el-button>
<el-button @click.stop="removeAllSites" type="text">清空</el-button>
<el-button @click.stop="resetSitesEvent" type="text">重置</el-button>
<div class="listpage-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组">></el-switch>
<el-button @click="addSite" icon="el-icon-document-add">新增</el-button>
<el-button @click="exportSites" icon="el-icon-upload2" ></el-button>
<el-button @click="importSites" icon="el-icon-download">导入</el-button>
<el-button @click="checkAllSite" icon="el-icon-refresh" :loading="checkAllSiteLoading">检测</el-button>
<el-button @click="removeAllSites" icon="el-icon-delete-solid">清空</el-button>
<el-button @click="resetSitesEvent" icon="el-icon-refresh-left">重置</el-button>
</div>
<div class="listpage-header" v-show="enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组"></el-switch>
<el-input placeholder="新组名" v-model="batchGroupName"></el-input>
<el-switch v-model="batchIsActive" :active-value="1" :inactive-value="0" active-text="自选源"></el-switch>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit">保存</el-button>
</div>
<div class="listpage-body" id="sites-table">
<el-table
:data="sites"
row-key="id"
style="width: 100%">
<el-table-column
prop="name"
label="资源名"
min-width="200">
</el-table-column>
<el-table-column
label="操作"
header-align="center"
align="right"
width="140">
<template slot-scope="scope">
<el-button @click.stop="moveToTopEvent(scope.row)" type="text">置顶</el-button>
<el-button @click.stop="editSite(scope.row)" type="text">编辑</el-button>
<el-button @click.stop="removeEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
ref="editSitesTable"
size="mini" fit height="100%" row-key="id"
:data="sites"
:key="tableKey"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange">
<el-table-column
type="selection"
v-if="enableBatchEdit">
</el-table-column>
<el-table-column
prop="name"
label="资源名">
</el-table-column>
<el-table-column
prop="isActive"
label="自选源">
<template slot-scope="scope">
<el-switch
v-model="scope.row.isActive"
:active-value="1"
:inactive-value="0"
@change='isActiveChangeEvent'>
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="group"
label="分组"
:filters="getFilters"
:filter-method="filterHandle"
filter-placement="bottom-end">
<template slot-scope="scope">
<el-button type="text">{{scope.row.group}}</el-button>
</template>
</el-table-column>
<el-table-column label="状态" width="120">
<template slot-scope="scope">
<span v-show="scope.row.status === ''">
<i class="el-icon-loading"></i>
检测中...
</span>
<span v-show="scope.row.status !== ''">{{scope.row.status}}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="right"
align="right">
<template slot-scope="scope">
<el-button size="mini" @click.stop="moveToTopEvent(scope.row)" type="text">置顶</el-button>
<el-button size="mini" @click.stop="editSite(scope.row)" type="text">编辑</el-button>
<el-button size="mini" @click.stop="removeEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 编辑页面 -->
<div>
<el-dialog :visible.sync="dialogVisible" v-if='dialogVisible' :title="dialogType==='edit'?'编辑源':'添加源'" :append-to-body="true" @close="closeDialog">
<el-form :model="siteInfo" ref='siteInfo' label-width="75px" label-position="left" :rules="rules">
<el-form-item label="源站名" prop='name'>
<el-input v-model="siteInfo.name" placeholder="请输入源站名" />
</el-form-item>
<el-form-item label="API接口" prop='api'>
<el-input v-model="siteInfo.api" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入API接口地址"/>
</el-form-item>
<el-form-item label="下载接口" prop='download'>
<el-input v-model="siteInfo.download" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入Download接口地址可以空着"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="addOrEditSite">保存</el-button>
</span>
</el-dialog>
<el-dialog :visible.sync="dialogVisible" v-if='dialogVisible' :title="dialogType==='edit'?'编辑源':'新增源'" :append-to-body="true" @close="closeDialog">
<el-form :model="siteInfo" ref='siteInfo' label-width="75px" label-position="left" :rules="rules">
<el-form-item label="源站名" prop='name'>
<el-input v-model="siteInfo.name" placeholder="请输入源站名" />
</el-form-item>
<el-form-item label="API接口" prop='api'>
<el-input v-model="siteInfo.api" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入API接口地址"/>
</el-form-item>
<el-form-item label="下载接口" prop='download'>
<el-input v-model="siteInfo.download" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入Download接口地址可以空着"/>
</el-form-item>
<el-form-item label="分组" prop='group'>
<el-select v-model="siteInfo.group" allow-create filterable default-first-option placeholder="请输入分组">
<el-option v-for="item in siteGroup" :key="item" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="源站标识" prop='key'>
<el-input v-model="siteInfo.key" placeholder="请输入源站标识,如果为空,系统则自动生成" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="addOrEditSite">保存</el-button>
</span>
</el-dialog>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import { remote } from 'electron'
import { sites as defaultSites } from '../lib/dexie/initData'
import fs from 'fs'
@@ -72,10 +124,14 @@ export default {
dialogType: 'new',
dialogVisible: false,
siteInfo: {
key: '',
name: '',
api: '',
download: ''
download: '',
group: '',
isActive: 1
},
siteGroup: [],
rules: {
name: [
{ required: true, message: '源站名不能为空', trigger: 'blur' }
@@ -86,7 +142,14 @@ export default {
download: [
{ required: false, trigger: 'blur' }
]
}
},
enableBatchEdit: false,
batchGroupName: '',
batchIsActive: 1,
multipleSelection: [],
tableKey: 1,
checkAllSiteLoading: false,
editeOldkey: ''
}
},
computed: {
@@ -105,37 +168,93 @@ export default {
set (val) {
this.SET_EDITSITES(val)
}
},
getFilters () {
const groups = [...new Set(this.sites.map(site => site.group))]
var filters = []
groups.forEach(g => {
var doc = {
text: g,
value: g
}
filters.push(doc)
})
return filters
}
},
methods: {
...mapMutations(['SET_SETTING', 'SET_EDITSITES']),
filterHandle (value, row) {
return row.group === value
},
handleSelectionChange (rows) {
this.multipleSelection = rows
},
handleSortChange (column, prop, order) {
this.updateDatabase(this.sites)
},
saveBatchEdit () {
this.multipleSelection.forEach(ele => {
if (this.batchGroupName) {
ele.group = this.batchGroupName
}
ele.isActive = this.batchIsActive
})
this.updateDatabase()
},
getSites () {
sites.all().then(res => {
this.sites = res
this.editSites = {
sites: res
}
})
this.editSites = {
sites: this.sites
for (const i of this.sites) {
delete i.status
}
},
getSitesGroup () {
const arr = []
for (const i of this.sites) {
if (arr.indexOf(i.group) < 0) {
arr.push(i.group)
}
}
this.siteGroup = arr
},
addSite () {
this.getSitesGroup()
this.dialogType = 'new'
this.dialogVisible = true
this.siteInfo = {
key: '',
name: '',
api: '',
download: ''
download: '',
group: '',
isActive: 1
}
},
editSite (siteInfo) {
this.getSitesGroup()
if (this.checkAllSiteLoading) {
this.$message.info('正在检测, 请勿操作.')
return false
}
this.dialogType = 'edit'
this.dialogVisible = true
this.siteInfo = siteInfo
this.editeOldkey = siteInfo.key
},
closeDialog () {
this.dialogVisible = false
this.getSites()
},
removeEvent (e) {
if (this.checkAllSiteLoading) {
this.$message.info('正在检测, 请勿操作.')
return false
}
sites.remove(e.id).then(res => {
this.getSites()
}).catch(err => {
@@ -153,35 +272,56 @@ export default {
})
})
},
checkSiteKey (e) {
if (this.dialogType === 'edit' && this.editeOldkey === this.siteInfo.key) {
return true
} else {
for (const i of this.sites) {
if (i.key === this.siteInfo.key) {
this.$message.warning(`源站标识: ${i.key} 已存在, 请勿重复填写.`)
return false
}
}
return true
}
},
addOrEditSite () {
if (!this.siteInfo.name || !this.siteInfo.api) {
this.$message.error('名称和API接口不能为空。')
return
return false
}
if (!this.checkSiteKey()) {
return false
}
var randomstring = require('randomstring')
var doc = {
key: this.dialogType === 'edit' ? this.siteInfo.key : randomstring.generate(6),
key: this.dialogType === 'edit' ? this.siteInfo.key : this.siteInfo.key ? this.siteInfo.key : randomstring.generate(6),
id: this.dialogType === 'edit' ? this.siteInfo.id : this.sites[this.sites.length - 1].id + 1,
name: this.siteInfo.name,
api: this.siteInfo.api,
download: this.siteInfo.download
download: this.siteInfo.download,
group: this.siteInfo.group,
isActive: this.siteInfo.isActive
}
if (this.dialogType === 'edit') sites.remove(this.siteInfo.id)
sites.add(doc).then(res => {
this.siteInfo = {
key: '',
name: '',
api: '',
download: ''
download: '',
group: ''
}
this.dialogType === 'edit' ? this.$message.success('修改成功!') : this.$message.success('添加新源成功!')
this.dialogType === 'edit' ? this.$message.success('修改成功!') : this.$message.success('新源成功!')
this.dialogVisible = false
this.getSites()
})
this.editeOldkey = ''
},
exportSites () {
this.getSites()
const arr = [...this.sites]
const str = JSON.stringify(arr, null, 4)
const str = JSON.stringify(arr, null, 2)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
@@ -213,14 +353,21 @@ export default {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(ele => {
if (this.sites.filter(x => x.key === ele.key).length === 0 && this.sites.filter(x => x.name === ele.name && x.url === ele.url).length === 0) {
if (ele.api && this.sites.filter(x => x.key === ele.key).length === 0 && this.sites.filter(x => x.name === ele.name && x.api === ele.api).length === 0) {
// 不含该key 同时也不含名字和url一样的
if (ele.isActive === undefined) {
ele.isActive = 1
}
if (ele.group === undefined) {
ele.group = '导入'
}
this.sites.push(ele)
}
})
this.resetId(this.sites)
sites.clear().then(sites.bulkAdd(this.sites))
this.$message.success('导入成功')
this.getSites()
})
}
})
@@ -230,8 +377,20 @@ export default {
this.$message.success('重置源成功')
},
moveToTopEvent (i) {
if (this.checkAllSiteLoading) {
this.$message.info('正在检测, 请勿操作.')
return false
}
this.sites.sort(function (x, y) { return x.key === i.key ? -1 : y.key === i.key ? 1 : 0 })
this.updateDatabase(this.sites)
this.updateDatabase()
},
syncTableData () {
if (this.$refs.editSitesTable.tableData && this.$refs.editSitesTable.tableData.length === this.sites.length) {
this.sites = this.$refs.editSitesTable.tableData
}
},
isActiveChangeEvent (row) {
this.updateDatabase()
},
resetId (inArray) {
var id = 1
@@ -240,14 +399,16 @@ export default {
id += 1
})
},
updateDatabase (data) {
updateDatabase () {
// 因为el-table的数据是单向绑定,我们先同步el-table里的数据和其绑定的数据
this.syncTableData()
sites.clear().then(res => {
var id = 1
data.forEach(ele => {
this.sites.forEach(ele => {
ele.id = id
id += 1
})
sites.bulkAdd(data).then(this.getSites())
sites.bulkAdd(this.sites).then(this.getSites())
})
},
removeAllSites () {
@@ -255,18 +416,36 @@ export default {
},
rowDrop () {
const tbody = document.getElementById('sites-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
var _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.sites.splice(oldIndex, 1)[0]
_this.sites.splice(newIndex, 0, currRow)
_this.updateDatabase(_this.sites)
_this.updateDatabase()
}
})
},
async checkAllSite () {
this.checkAllSiteLoading = true
for (const i of this.sites) {
i.status = ''
this.tableKey = Math.random()
const flag = await zy.check(i.key)
if (flag) {
i.status = '可用'
} else {
i.status = '失效'
i.isActive = 0
}
this.tableKey = Math.random()
}
this.checkAllSiteLoading = false
this.updateDatabase()
}
},
mounted () {
this.rowDrop()
this.checkAllSiteLoading = false
},
created () {
this.getSites()

View File

@@ -13,7 +13,7 @@
<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>
<li :class="type.tid === i.tid ? 'active' : ''" v-for="i in classList" :key="i.tid" @click="classClick(i)">{{ i.name | classNameFilter }}</li>
</ul>
</div>
</div>
@@ -65,7 +65,7 @@
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.year}}</span>
<span class="time">{{i.note}}</span>
<span class="note">{{i.note}}</span>
<span class="last">{{i.last}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(site, i)">播放</span>
@@ -87,10 +87,13 @@
<ul>
<li v-for="(i, j) in searchContents" :key="j" @click="detailEvent(i.site, i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="last">{{i.last}}</span>
<span class="site">{{i.site.name}}</span>
<span class="note">{{i.note}}</span>
<span class="info">{{i.site.name}}</span>
<span class="info">{{i.director}}</span>
<span class="info">{{i.type}}</span>
<span class="info">{{i.area}}</span>
<span class="info">{{i.lang}}</span>
<span class="info">{{i.year}}</span>
<span class="info">{{i.note}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i.site, i)">播放</span>
<span class="btn" @click.stop="starEvent(i.site, i)">收藏</span>
@@ -136,7 +139,7 @@ export default {
searchTxt: '',
searchContents: [],
// 福利片关键词
r18KeyWords: ['伦理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番']
r18KeyWords: ['伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番']
}
},
components: {
@@ -183,6 +186,11 @@ export default {
return this.$store.getters.getEditSites.sites // 需要监听的数据
}
},
filters: {
classNameFilter: (name) => {
return name.replace(/[^\u4e00-\u9fa5]/gi, '')
}
},
watch: {
view () {
this.changeView()
@@ -309,37 +317,34 @@ export default {
info: e
}
},
playEvent (site, e) {
history.find({ site: site.key, ids: e.id }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: res.index, site: site } }
} else {
this.video = { key: site.key, info: { id: e.id, name: e.name, index: 0, site: site } }
}
})
async playEvent (site, e) {
const db = await history.find({ site: site.key, ids: e.id })
if (db) {
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: db.index, site: site } }
} else {
this.video = { key: site.key, info: { id: e.id, name: e.name, index: 0, site: site } }
}
this.view = 'Play'
},
starEvent (site, e) {
star.find({ key: site.key, ids: e.id }).then(res => {
if (res) {
this.$message.info('已存在')
} else {
const docs = {
key: site.key,
ids: e.id,
name: e.name,
type: e.type,
year: e.year,
last: e.last,
note: e.note
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
async starEvent (site, e) {
const db = await star.find({ key: site.key, ids: e.id })
if (db) {
this.$message.info('已存在')
} else {
const docs = {
key: site.key,
ids: e.id,
site: site,
name: e.name,
type: e.type,
year: e.year,
last: e.last,
note: e.note
}
}).catch(() => {
this.$message.warning('收藏失败')
})
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
},
shareEvent (site, e) {
this.share = {
@@ -402,6 +407,13 @@ export default {
this.searchList = res.reverse()
})
},
searchEvent (wd) {
if (this.setting.searchAllSites) {
this.searchAllSitesEvent(this.sites, wd)
} else {
this.searchSingleSiteEvent(this.site, wd)
}
},
searchAllSitesEvent (sites, wd) {
this.searchTxt = wd
this.searchContents = []
@@ -420,13 +432,17 @@ export default {
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
res.forEach(element => {
element.site = site
this.searchContents.push(element)
zy.detail(site.key, element.id).then(detailRes => {
detailRes.site = site
this.searchContents.push(detailRes)
})
})
}
if (type === '[object Object]') {
res.site = site
this.searchContents.push(res)
zy.detail(site.key, res.id).then(detailRes => {
detailRes.site = site
this.searchContents.push(detailRes)
})
}
})
})
@@ -439,13 +455,6 @@ export default {
})
}
},
searchEvent (wd) {
if (this.setting.searchAllSites) {
this.searchAllSitesEvent(this.sites, wd)
} else {
this.searchSingleSiteEvent(this.site, wd)
}
},
searchSingleSiteEvent (site, wd) {
var sites = []
sites.push(this.site)
@@ -470,14 +479,21 @@ export default {
},
getAllsites () {
sites.all().then(res => {
this.sites = res
this.site = this.sites[0]
this.siteClick(this.site)
if (res.length <= 0) {
this.site = {}
this.type = {}
this.list = []
} else {
this.sites = res.filter((item, index, self) => {
return self.indexOf(item) >= 0 && item.isActive
})
this.site = this.sites[0]
this.siteClick(this.site)
}
})
}
},
created () {
this.getAllsites()
this.getAllSearch()
}
}

View File

@@ -2,50 +2,46 @@
<div class="listpage" id="history">
<div class="listpage-content">
<div class="listpage-header">
<span class="btn"></span>
<el-button @click.stop="clearAllHistory" type="text">清空</el-button>
<el-button @click.stop="exportHistory" icon="el-icon-upload2">导出</el-button>
<el-button @click.stop="importHistory" icon="el-icon-download">导入</el-button>
<el-button @click.stop="clearAllHistory" icon="el-icon-delete-solid">清空</el-button>
</div>
<div class="listpage-body" id="history-table">
<el-table
:data="history"
row-key="id"
@row-click="detailEvent"
style="width: 100%">
<el-table-column
prop="name"
label="片名"
min-width="200">
</el-table-column>
<el-table-column
prop="site"
label="片源"
width="120">
<template slot-scope="scope">
<span>{{ getSiteName(scope.row.site) }}</span>
</template>
</el-table-column>
<el-table-column
prop="index"
label="观看至">
<template slot-scope="scope">
<span>{{ scope.row.index + 1 }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="center"
align="right"
width="180">
<template slot-scope="scope">
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="removeHistoryItem(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
<el-table size="mini" fit height="100%" :data="history" row-key="id" @row-click="detailEvent">
<el-table-column
prop="name"
label="片名">
</el-table-column>
<el-table-column
prop="site"
width="120"
label="片源">
<template slot-scope="scope">
<span>{{ getSiteName(scope.row.site) }}</span>
</template>
</el-table-column>
<el-table-column
prop="index"
width="120"
label="观看至">
<template slot-scope="scope">
<span>{{ scope.row.index + 1 }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="right"
align="right">
<template slot-scope="scope">
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="removeHistoryItem(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</template>
<script>
@@ -53,6 +49,8 @@ import { mapMutations } from 'vuex'
import { history, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import Sortable from 'sortablejs'
import { remote } from 'electron'
import fs from 'fs'
const { clipboard } = require('electron')
export default {
@@ -115,14 +113,13 @@ export default {
}
}
},
playEvent (e) {
history.find({ site: e.site, ids: e.ids }).then(res => {
if (res) {
this.video = { key: res.site, info: { id: res.ids, name: res.name, index: res.index } }
} else {
this.video = { key: e.site, info: { id: e.ids, name: e.name, index: 0 } }
}
})
async playEvent (e) {
const db = await history.find({ site: e.site, ids: e.ids })
if (db) {
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: db.index } }
} else {
this.video = { key: e.site, info: { id: e.ids, name: e.name, index: 0 } }
}
this.view = 'Play'
},
shareEvent (e) {
@@ -174,6 +171,44 @@ export default {
}
})
},
exportHistory () {
this.getAllhistory()
const arr = [...this.history]
const str = JSON.stringify(arr, null, 2)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] }
]
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
}).catch(err => {
this.$message.error(err)
})
},
importHistory () {
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] }
],
properties: ['openFile', 'multiSelections']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
history.bulkAdd(json).then(res => {
this.$message.success('导入成功')
this.getAllhistory()
})
})
}
})
},
clearAllHistory () {
history.clear().then(res => {
this.history = []

View File

@@ -1,51 +1,68 @@
<template>
<div class="listpage" id="IPTV">
<div class="listpage-content">
<div class="listpage-header">
<el-button type="text">总频道数:{{iptvList.length}}</el-button>
<el-button @click.stop="exportChannels" type="text">导出</el-button>
<el-button @click.stop="importChannels" type="text">导入</el-button>
<el-button @click.stop="removeAllChannels" type="text">清空</el-button>
<el-button @click.stop="resetChannelsEvent" type="text">重置</el-button>
<el-input
placeholder="搜索"
size="mini"
v-model.trim="searchTxt">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<div class="listpage-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组"></el-switch>
<el-button @click.stop="exportChannels" icon="el-icon-upload2" >导出</el-button>
<el-button @click.stop="importChannels" icon="el-icon-download">导入</el-button>
<el-button @click.stop="removeAllChannels" icon="el-icon-delete-solid">清空</el-button>
<el-button @click.stop="resetChannelsEvent" icon="el-icon-refresh-left">重置</el-button>
</div>
<div class="listpage-header" v-show="enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组"></el-switch>
<el-input placeholder="新组名" v-model="batchGroupName"></el-input>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit">保存</el-button>
</div>
<div class="listpage-body" id="iptv-table">
<el-table
:data="filteredTableData"
row-key="id"
@row-click="playEvent"
style="width: 100%">
<el-table-column
prop="name"
label="频道名"
min-width="200">
</el-table-column>
<el-table-column
prop="group"
label="分组"
width="100"
:filters="getFilters"
:filter-method="filterHandle"
filter-placement="bottom-end">
<template slot-scope="scope">
<el-button type="text">{{scope.row.group}}</el-button>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="center"
align="right"
width="140">
<template slot-scope="scope">
<el-button @click.stop="moveToTopEvent(scope.row)" type="text">置顶</el-button>
<el-button @click.stop="removeEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
ref="iptvTable"
size="mini" fit height="100%" row-key="id"
:data="filteredTableData"
@row-click="playEvent"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange">>
<el-table-column
type="selection"
v-if="enableBatchEdit">
</el-table-column>
<el-table-column
default-sort="ascending"
prop="name"
label="频道名">
<template #header>
<el-input
placeholder="搜索"
size="mini"
v-model.trim="searchTxt">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
</template>
</el-table-column>
<el-table-column
sort-by="['group', 'name']"
sortable
:sort-method="sortByGroup"
prop="group"
label="分组"
:filters="getFilters"
:filter-method="filterHandle"
filter-placement="bottom-end">
<template slot-scope="scope">
<el-button type="text">{{scope.row.group}}</el-button>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="right"
align="right">
<template #header>
<span>总频道数:{{ iptvList.length }}</span>
</template>
<template slot-scope="scope">
<el-button @click.stop="moveToTopEvent(scope.row)" type="text">置顶</el-button>
<el-button @click.stop="removeEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
@@ -65,6 +82,9 @@ export default {
iptvList: [],
searchTxt: '',
searchRecordList: [],
enableBatchEdit: false,
batchGroupName: '',
multipleSelection: [],
show: {
search: false
}
@@ -119,6 +139,23 @@ export default {
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
sortByGroup (a, b) {
return a.group.localeCompare(b.group, 'zh')
},
handleSelectionChange (rows) {
this.multipleSelection = rows
},
handleSortChange (column, prop, order) {
this.updateDatabase()
},
saveBatchEdit () {
if (this.multipleSelection && this.batchGroupName) {
this.multipleSelection.forEach(ele => {
ele.group = this.batchGroupName
})
}
this.updateDatabase()
},
playEvent (e) {
this.video = { iptv: { name: e.name, url: e.url } }
this.view = 'Play'
@@ -181,7 +218,8 @@ export default {
importChannels () {
const options = {
filters: [
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] }
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] },
{ name: 'JSON file', extensions: ['json'] }
],
properties: ['openFile', 'multiSelections']
}
@@ -190,24 +228,42 @@ export default {
var docs = this.iptvList
var id = docs.length
result.filePaths.forEach(file => {
const parser = require('iptv-playlist-parser')
const playlist = fs.readFileSync(file, { encoding: 'utf-8' })
const result = parser.parse(playlist)
result.items.forEach(ele => {
if (ele.name && ele.url && ele.url.includes('.m3u8')) {
var doc = {
id: id,
name: ele.name,
url: ele.url,
group: this.determineGroup(ele.group, ele.name)
if (file.endsWith('m3u') || file.endsWith('m3u8')) {
const parser = require('iptv-playlist-parser')
const playlist = fs.readFileSync(file, { encoding: 'utf-8' })
const result = parser.parse(playlist)
result.items.forEach(ele => {
if (ele.name && ele.url && ele.url.endsWith('.m3u8')) {
var doc = {
id: id,
name: ele.name,
url: ele.url,
group: this.determineGroup(ele.name)
}
id += 1
docs.push(doc)
}
id += 1
docs.push(doc)
}
})
})
} else {
// Import Json file
var str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(ele => {
if (ele.name && ele.url && ele.url.endsWith('.m3u8')) {
var doc = {
id: id,
name: ele.name,
url: ele.url,
group: this.determineGroup(ele.name)
}
id += 1
docs.push(doc)
}
})
}
})
// 获取url不重复的列表
const uniqueList = [...new Map(docs.map(item => [item.url, item])).values()]
// 获取name不重复的列表
const uniqueList = [...new Map(docs.map(item => [item.name, item])).values()]
iptv.clear().then(res => {
iptv.bulkAdd(uniqueList).then(e => {
this.getChannels()
@@ -217,13 +273,17 @@ export default {
}
})
},
determineGroup (group, name) {
if (!group) {
return group
determineGroup (name) {
if (name.toLowerCase().includes('cctv') && (name.includes('蓝光') || name.includes('高清'))) {
return '央视高清'
} else if (name.toLowerCase().includes('cctv')) {
return '央视'
} else if (name.includes('卫视')) {
return '卫视'
} else if (name.includes('香港') || name.includes('澳门') || name.includes('台湾') || name.includes('凤凰')) {
return '港澳台'
} else if (name.includes('高清') || name.includes('蓝光') || name.includes('1080P')) {
return '高清'
} else {
return '其他'
}
@@ -269,12 +329,18 @@ export default {
},
moveToTopEvent (i) {
this.iptvList.sort(function (x, y) { return (x.name === i.name && x.url === i.url) ? -1 : (y.name === i.name && y.url === i.url) ? 1 : 0 })
this.updateDatabase(this.iptvList)
this.updateDatabase()
},
updateDatabase (data) {
syncTableData () {
if (this.$refs.iptvTable.tableData && this.$refs.iptvTable.tableData.length === this.iptvList.length) {
this.iptvList = this.$refs.iptvTable.tableData
}
},
updateDatabase () {
this.syncTableData()
iptv.clear().then(res => {
this.resetId(data)
iptv.bulkAdd(data).then(this.getChannels())
this.resetId(this.iptvList)
iptv.bulkAdd(this.iptvList)
})
},
resetId (inArray) {
@@ -291,7 +357,7 @@ export default {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.iptvList.splice(oldIndex, 1)[0]
_this.iptvList.splice(newIndex, 0, currRow)
_this.updateDatabase(_this.iptvList)
_this.updateDatabase()
}
})
}

View File

@@ -3,23 +3,20 @@
<div class="box">
<div class="title">
<span v-if="this.right.list.length > 1"> {{(video.info.index + 1)}} </span>{{name}}
<span v-if="video.key" class="right" @click="playWithExternalPalyerEvent" title="使用第三方播放器">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<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 v-if="video.key" class="right" @click="issueEvent" title="复制调试信息">
<svg t="1596338860607" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3127" width="24" height="24">
<path d="M503.803829 63.578014c-247.050676 0-447.328072 200.277396-447.328072 447.327048 0 247.054769 200.277396 447.333188 447.328072 447.333188 247.054769 0 447.332165-200.278419 447.332165-447.333188C951.13497 263.85541 750.858598 63.578014 503.803829 63.578014L503.803829 63.578014zM503.803829 894.313336c-211.749682 0-383.408273-171.659615-383.408273-383.408273 0-211.749682 171.659615-383.40725 383.408273-383.40725 211.753775 0 383.412366 171.658591 383.412366 383.40725C887.216195 722.653721 715.557604 894.313336 503.803829 894.313336L503.803829 894.313336zM447.745069 255.897158l127.914298 0L575.659367 383.576095 447.745069 383.576095 447.745069 255.897158 447.745069 255.897158zM447.745069 425.470251l127.914298 0 0 342.058516L447.745069 767.528767 447.745069 425.470251 447.745069 425.470251zM447.745069 425.470251" p-id="3128"></path>
</svg>
</span>
</div>
<div class="player">
<div id="xgplayer"></div>
</div>
<div class="more">
<span class="zy-svg" @click="nextEvent" v-show="showNext">
<span class="zy-svg" @click="otherEvent" v-show="name !== ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="coloursIconTitle">
<title id="coloursIconTitle">换源</title>
<circle cx="12" cy="9" r="5"></circle>
<circle cx="9" cy="14" r="5"></circle>
<circle cx="15" cy="14" r="5"></circle>
</svg>
</span>
<span class="zy-svg" @click="nextEvent" v-show="right.list.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>
@@ -58,8 +55,14 @@
</svg>
</span>
<span class="zy-svg" @click="miniEvent" 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="diamondIconTitle">
<title id="diamondIconTitle">精简模式</title>
<path d="M12 20L3 11M12 20L21 11M12 20L8 11M12 20L16 11M3 11L7 5M3 11H8M7 5L8 11M7 5H12M17 5L21 11M17 5L16 11M17 5H12M21 11H16M8 11H16M8 11L12 5M16 11L12 5"></path>
</svg>
</span>
<span class="zy-svg" @click="playWithExternalPalyerEvent" 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">精简模式</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>
@@ -81,13 +84,21 @@
<rect x="17" y="6" width="1" height="1"></rect>
</svg>
</span>
<span class="zy-svg" @click="issueEvent" 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="infoIconTitle">
<title id="infoIconTitle">复制调试信息</title>
<path d="M12,12 L12,15"></path>
<line x1="12" y1="9" x2="12" y2="9"></line>
<circle cx="12" cy="12" r="10"></circle>
</svg>
</span>
<span class="last-tip" v-if="!video.key && right.history.length > 0" @click="historyItemEvent(right.history[0])">上次播放到{{right.history[0].site}}{{right.history[0].name}} {{right.history[0].index+1}}</span>
</div>
</div>
<transition name="slideX">
<div v-if="right.show" class="list">
<div class="list-top">
<span class="list-top-title">{{ right.type === 'list' ? '播放列表' : '历史记录' }}</span>
<span class="list-top-title">{{ right.type === 'list' ? '播放列表' : right.type === 'history' ? '历史记录' : '其他相同资源' }}</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">关闭</title>
@@ -106,6 +117,10 @@
<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" :title="'【' + m.site + '】' + m.name + ' 第' + (m.index+1) + '集'">【{{m.site}}】{{m.name}} 第{{m.index+1}}集</span><span @click.stop="removeHistoryItem(m)" class="detail-delete">删除</span></li>
</ul>
<ul v-show="right.type === 'other'" class="list-other">
<li v-show="right.other.length === 0">无数据</li>
<li @click="otherItemEvent(m)" v-for="(m, n) in right.other" :key="n"><span class="title">{{m.name}} - [{{m.site}}]</span></li>
</ul>
</div>
</div>
</transition>
@@ -113,7 +128,7 @@
</template>
<script>
import { mapMutations } from 'vuex'
import { star, history, setting, shortcut, mini, iptv } from '../lib/dexie'
import { star, history, setting, shortcut, mini, iptv, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import Player from 'xgplayer'
import Hls from 'xgplayer-hls.js'
@@ -170,6 +185,7 @@ export default {
right: {
show: false,
type: '',
other: [],
list: [],
history: []
},
@@ -202,7 +218,6 @@ export default {
length: 0,
timer: null,
scroll: false,
showNext: false,
isStar: false,
isTop: false,
mini: {},
@@ -287,7 +302,10 @@ export default {
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
getUrls () {
async getUrls () {
if (this.video.key === '') {
return false
}
this.name = ''
if (this.timer !== null) {
clearInterval(this.timer)
@@ -305,14 +323,13 @@ export default {
} else {
const index = this.video.info.index | 0
let time = 0
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
if (res.index === index) {
time = res.time
}
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
if (db.index === index) {
time = db.time
}
this.playVideo(index, time)
})
}
this.playVideo(index, time)
}
},
playUrl (url) {
@@ -322,7 +339,6 @@ export default {
playVideo (index = 0, time = 0) {
this.fetchM3u8List().then(m3u8Arr => {
this.xg.src = m3u8Arr[index]
this.showNext = m3u8Arr.length > 1
if (time !== 0) {
this.xg.play()
@@ -349,7 +365,6 @@ export default {
if (VIDEO_DETAIL_CACHE[cacheKey]) {
this.name = VIDEO_DETAIL_CACHE[cacheKey].name
resolve(VIDEO_DETAIL_CACHE[cacheKey].list)
return
}
zy.detail(this.video.key, this.video.info.id).then(res => {
this.name = res.name
@@ -389,34 +404,33 @@ export default {
})
})
},
videoPlaying () {
async videoPlaying () {
this.changeVideo()
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
const doc = {
site: res.site,
ids: res.ids,
name: res.name,
type: res.type,
year: res.year,
index: this.video.info.index,
time: res.time
}
history.remove(res.id)
history.add(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)
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
const doc = {
site: db.site,
ids: db.ids,
name: db.name,
type: db.type,
year: db.year,
index: this.video.info.index,
time: db.time
}
})
history.remove(db.id)
history.add(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.updateStar()
this.timerEvent()
},
@@ -425,15 +439,14 @@ export default {
this.checkTop()
},
timerEvent () {
this.timer = setInterval(() => {
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
const doc = { ...res }
doc.time = this.xg.currentTime
delete doc.id
history.update(res.id, doc)
}
})
this.timer = setInterval(async () => {
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
const doc = { ...db }
doc.time = this.xg.currentTime
delete doc.id
history.update(db.id, doc)
}
}, 10000)
},
prevEvent () {
@@ -498,21 +511,28 @@ export default {
this.right.history = res.reverse()
})
},
updateStar () {
async updateStar () {
const info = this.video.info
star.find({ key: this.video.key, ids: info.id }).then(res => {
if (res) {
res.index = info.index
star.update(res.id, res)
}
}).catch(() => {
this.$message.warning('检查收藏失败')
})
const db = await star.find({ key: this.video.key, ids: info.id })
if (db) {
db.index = info.index
star.update(db.id, db)
}
},
starEvent () {
async starEvent () {
const info = this.video.info
star.find({ key: this.video.key, ids: info.id }).then(res => {
const doc = {
const db = await star.find({ key: this.video.key, ids: info.id })
if (db) {
star.remove(db.id).then(res => {
if (res) {
this.$message.warning('取消收藏失败')
} else {
this.$message.success('取消收藏成功')
this.isStar = false
}
})
} else {
const docs = {
key: this.video.key,
ids: info.id,
name: info.name,
@@ -522,17 +542,11 @@ export default {
note: info.note,
index: info.index
}
if (res) {
star.update(res.id, doc)
} else {
star.add(doc).then(starRes => {
this.$message.success('收藏成功')
this.isStar = true
})
}
}).catch(() => {
this.$message.warning('检查收藏失败')
})
star.add(docs).then(res => {
this.$message.success('收藏成功')
this.isStar = true
})
}
},
detailEvent () {
this.detail = {
@@ -624,14 +638,13 @@ export default {
fs.writeFileSync(filePath, str)
return filePath
},
checkStar () {
star.find({ key: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
this.isStar = true
} else {
this.isStar = false
}
})
async checkStar () {
const db = await star.find({ key: this.video.key, ids: this.video.info.id })
if (db) {
this.isStar = true
} else {
this.isStar = false
}
},
checkTop () {
const win = remote.getCurrentWindow()
@@ -686,6 +699,7 @@ export default {
if (this.video.iptv) {
var channel = this.iptvList[n]
this.video.iptv = channel
this.name = this.video.iptv.name
// 是直播源,直接播放
this.playUrl(channel.url)
} else {
@@ -718,6 +732,70 @@ export default {
this.$message.warning('删除历史记录失败, 错误信息: ' + err)
})
},
getAllsitestest () {
this.name = '喜宝'
sites.all().then(res => {
const sites = res
const arr = []
for (const i of sites) {
zy.search(i.key, this.name).then(res => {
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
res.forEach(element => {
zy.detail(i.key, element.id).then(detailRes => {
arr.push(detailRes)
})
})
}
if (type === '[object Object]') {
zy.detail(i.key, res.id).then(detailRes => {
arr.push(detailRes)
})
}
})
}
console.log(arr, 'arr')
})
},
async getAllsites () {
const all = await sites.all()
this.right.other = []
for (const i of all) {
if (i.isActive) {
const searchRes = await zy.search(i.key, this.name)
const type = Object.prototype.toString.call(searchRes)
if (type === '[object Array]') {
searchRes.forEach(async element => {
const detailRes = await zy.detail(i.key, element.id)
detailRes.key = i.key
detailRes.site = i.name
this.right.other.push(detailRes)
})
}
if (type === '[object Object]') {
const detailRes = await zy.detail(i.key, searchRes.id)
detailRes.key = i.key
detailRes.site = i.name
this.right.other.push(detailRes)
}
}
}
},
otherEvent (m) {
this.right.type = 'other'
this.getAllsites()
this.right.show = true
},
async otherItemEvent (e) {
const db = await history.find({ site: e.key, ids: e.id })
if (db) {
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: db.index, site: e.key } }
} else {
this.video = { key: e.key, info: { id: e.id, name: e.name, index: 0, site: e.key } }
}
this.right.show = false
this.right.type = ''
},
mtEvent () {
setting.find().then(res => {
if (res.shortcut) {
@@ -998,18 +1076,17 @@ export default {
let timerID
ev.forEach(item => {
this.xg.root.addEventListener(item, () => {
if (!this.xg.fullscreen) {
return
if (this.xg && this.xg.fullscreen) {
const videoTitle = document.querySelector('.xg-view-videoTitle')
videoTitle.style.display = 'block'
clearTimeout(timerID)
timerID = setTimeout(() => {
// 播放中自动消失
if (this.xg && !this.xg.paused) {
videoTitle.style.display = 'none'
}
}, 3000)
}
const videoTitle = document.querySelector('.xg-view-videoTitle')
videoTitle.style.display = 'block'
clearTimeout(timerID)
timerID = setTimeout(() => {
// 播放中自动消失
if (this.xg && !this.xg.paused) {
videoTitle.style.display = 'none'
}
}, 3000)
})
})
@@ -1021,16 +1098,20 @@ export default {
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
}
this.xg.destroy()
clearInterval(this.timer)
this.video.key = ''
this.xg.src = ''
this.config.src = ''
this.xg.destroy(false)
this.xg = null
this.name = ''
this.right.list = []
this.showNext = false
this.getAllhistory()
setTimeout(() => {
this.playerInstall()
this.xg = new Hls(this.config)
this.playerInstall()
this.bindEvent()
}, 500)
}, 1000)
},
minMaxEvent () {
const win = remote.getCurrentWindow()
@@ -1080,16 +1161,15 @@ export default {
mounted () {
this.playerInstall()
this.xg = new Hls(this.config)
ipcRenderer.on('miniClosed', () => {
history.find({ site: this.video.key, ids: this.video.info.id }).then(res => {
if (res) {
if (this.video.info.index !== res.index) {
this.video.info.index = res.index
} else {
this.getUrls()
}
ipcRenderer.on('miniClosed', async () => {
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
if (this.video.info.index !== db.index) {
this.video.info.index = db.index
} else {
this.getUrls()
}
})
}
})
this.bindEvent()
this.minMaxEvent()

View File

@@ -6,7 +6,7 @@
<a @click="linkOpen('http://zyplayer.fun/')">官网</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player')">Github</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">当前版本v{{pkg.version}} 反馈</a>
<a style="color:#38dd77" @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/releases/tag/v' + latestVersion)" v-show="latestVersion !== pkg.version" >最新版本v{{latestVersion}}</a>
<a style="color:#38dd77" @click="quitAndInstall()" v-show="latestVersion !== pkg.version" >最新版本v{{latestVersion}}</a>
</div>
<div class="view">
<div class="title">视图</div>
@@ -141,8 +141,9 @@
<script>
import { mapMutations } from 'vuex'
import pkg from '../../package.json'
import { setting, sites, shortcut, star } from '../lib/dexie'
import { shell, clipboard, remote } from 'electron'
import { setting, sites, shortcut } from '../lib/dexie'
import { sites as defaultSites } from '../lib/dexie/initData'
import { shell, clipboard, remote, ipcRenderer } from 'electron'
import db from '../lib/dexie/dexie'
export default {
name: 'setting',
@@ -151,7 +152,6 @@ export default {
pkg: pkg,
sitesList: [],
shortcutList: [],
favoritesList: [],
show: {
site: false,
shortcut: false,
@@ -193,10 +193,18 @@ export default {
set (val) {
this.SET_SETTING(val)
}
},
editSites: {
get () {
return this.$store.getters.getEditSites
},
set (val) {
this.SET_EDITSITES(val)
}
}
},
methods: {
...mapMutations(['SET_SETTING', 'SET_VIEW']),
...mapMutations(['SET_SETTING', 'SET_VIEW', 'SET_EDITSITES']),
linkOpen (e) {
shell.openExternal(e)
},
@@ -218,7 +226,15 @@ export default {
},
getSites () {
sites.all().then(res => {
this.sitesList = res
if (res.length <= 0) {
this.$message.warning('检测到视频源未能正常加载, 即将重置源.')
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites()))
} else {
this.sitesList = res
this.editSites = {
sites: res
}
}
})
},
getShortcut () {
@@ -226,11 +242,6 @@ export default {
this.shortcutList = res
})
},
getFavorites () {
star.all().then(res => {
this.favoritesList = res
})
},
changeView (e) {
this.d.view = e
this.updateSettingEvent()
@@ -306,7 +317,7 @@ export default {
},
expShortcut () {
const arr = [...this.shortcutList]
const str = JSON.stringify(arr, null, 4)
const str = JSON.stringify(arr, null, 2)
clipboard.writeText(str)
this.$message.success('已复制到剪贴板')
},
@@ -339,15 +350,20 @@ export default {
}
},
getLatestVersion () {
const cheerio = require('cheerio')
const axios = require('axios')
var url = 'https://github.com/Hunlongyu/ZY-Player/releases'
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.release-header')[0]
var firstResult = $(e).find('div>div>a')
this.latestVersion = firstResult.text()
ipcRenderer.send('checkForUpdate')
ipcRenderer.on('update-available', (e, info) => {
this.latestVersion = info.version
})
ipcRenderer.on('update-error', () => {
this.$message.warning = '更新出错.'
})
ipcRenderer.on('update-downloaded', () => {
this.$message.info = '下载完毕, 退出安装'
})
},
quitAndInstall () {
this.$message.success('已开始下载更新,下载完毕后,将自动退出安装。')
ipcRenderer.send('quitAndInstall')
},
createContextMenu () {
const { Menu, MenuItem } = remote
@@ -365,7 +381,6 @@ export default {
this.getSites()
this.getSetting()
this.getShortcut()
this.getFavorites()
this.getLatestVersion()
this.createContextMenu()
}

View File

@@ -89,7 +89,7 @@ export default {
})
},
picLoadEvent () {
const dom = document.getElementById('right')
const dom = document.getElementById('share')
html2canvas(dom, { useCORS: true, allowTaint: true }).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)

View File

@@ -2,78 +2,78 @@
<div class="listpage" id="star">
<div class="listpage-content">
<div class="listpage-header">
<el-button @click.stop="exportFavoritesEvent" type="text">导出</el-button>
<el-button @click.stop="importFavoritesEvent" type="text">导入</el-button>
<el-button @click.stop="clearFavoritesEvent" type="text">清空</el-button>
<el-button @click.stop="updateAllEvent" type="text">同步所有收藏</el-button>
<el-button @click.stop="exportFavoritesEvent" icon="el-icon-upload2">导出</el-button>
<el-button @click.stop="importFavoritesEvent" icon="el-icon-download">导入</el-button>
<el-button @click.stop="clearFavoritesEvent" icon="el-icon-delete-solid">清空</el-button>
<el-button @click.stop="updateAllEvent" icon="el-icon-refresh">同步所有收藏</el-button>
</div>
<div class="listpage-body" id="star-table">
<el-table
:data="list"
height="100%"
row-key="id"
:cell-class-name="checkUpdate"
@row-click="detailEvent"
style="width: 100%">
<el-table-column
sortable
prop="name"
label="片名"
min-width="200">
</el-table-column>
<el-table-column
sortable
prop="type"
label="类型"
width="100">
</el-table-column>
<el-table-column
sortable
prop="year"
label="上映"
align="center"
width="100">
</el-table-column>
<el-table-column
sortable
prop="site"
label="片源"
width="100">
<template slot-scope="scope">
<span>{{ getSiteName(scope.row.key) }}</span>
</template>
</el-table-column>
<el-table-column v-if="list.some(e => e.note)"
sortable
prop="note"
label="备注"
min-width="100">
</el-table-column>
<el-table-column v-if="list.some(e => e.index >= 0)"
sortable
prop="index"
label="观看至"
width="100">
<template slot-scope="scope">
<span>{{ getHistoryNote(scope.row.index) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="center"
align="right"
width="220">
<template slot-scope="scope">
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="updateEvent(scope.row)" type="text">同步</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="deleteEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
<el-table size="mini" fit height="100%" row-key="id"
ref="starTable"
:data="list"
:cell-class-name="checkUpdate"
@row-click="detailEvent"
@sort-change="handleSortChange">
<el-table-column
sortable
:sort-method="sortByName"
prop="name"
label="片名">
</el-table-column>
<el-table-column
:sort-by="['type', 'name']"
sortable
:sort-method="sortByType"
prop="type"
label="类型"
width="100">
</el-table-column>
<el-table-column
sortable
:sort-by="['year', 'name']"
prop="year"
label="上映"
width="100"
align="center">
</el-table-column>
<el-table-column
:sort-by="['site', 'name']"
sortable
:sort-method="sortBySite"
prop="site"
width="120"
label="片源">
<template slot-scope="scope">
<span>{{ getSiteName(scope.row.key) }}</span>
</template>
</el-table-column>
<el-table-column v-if="list.some(e => e.note)"
prop="note"
width="120"
label="备注">
</el-table-column>
<el-table-column v-if="list.some(e => e.index >= 0)"
prop="index"
width="120"
label="观看至">
<template slot-scope="scope">
<span>{{ getHistoryNote(scope.row.index) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
header-align="right"
align="right">
<template slot-scope="scope">
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="deleteEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</template>
<script>
@@ -128,12 +128,29 @@ export default {
},
watch: {
view () {
this.getFavorites()
this.getAllsites()
this.getFavorites()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
handleSortChange (column, prop, order) {
this.updateDatabase()
},
sortByName (a, b) {
return a.name.localeCompare(b.name, 'zh')
},
sortByType (a, b) {
return a.type.localeCompare(b.type)
},
sortBySite (a, b) {
const siteA = this.getSiteName(a.key)
if (!siteA) {
return -1
} else {
return siteA.localeCompare(this.getSiteName(b.key))
}
},
detailEvent (e) {
this.detail = {
show: true,
@@ -147,14 +164,13 @@ export default {
this.clearHasUpdateFlag(e)
}
},
playEvent (e) {
history.find({ site: e.key, ids: e.ids }).then(res => {
if (res) {
this.video = { key: e.key, info: { id: res.ids, name: res.name, index: res.index } }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 } }
}
})
async playEvent (e) {
const db = await history.find({ site: e.key, ids: e.ids })
if (db) {
this.video = { key: e.key, info: { id: db.ids, name: db.name, index: db.index } }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 } }
}
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
}
@@ -182,24 +198,28 @@ export default {
return 'highlight'
}
},
clearHasUpdateFlag (e) {
star.find({ id: e.id }).then(res => {
res.hasUpdate = false
star.update(e.id, res)
async clearHasUpdateFlag (e) {
const db = await star.find({ id: e.id })
if (db) {
db.hasUpdate = false
star.update(e.id, db)
this.getFavorites()
})
}
},
updateEvent (e) {
zy.detail(e.key, e.ids).then(res => {
var doc = {
key: e.key,
id: e.id,
key: e.key,
ids: res.id,
last: res.last,
site: res.site,
name: res.name,
type: res.type,
year: res.year,
note: res.note
note: res.note,
index: res.index,
last: res.last,
hasUpdate: res.hasUpdate
}
star.get(e.id).then(resStar => {
doc.hasUpdate = resStar.hasUpdate
@@ -292,7 +312,7 @@ export default {
},
exportFavoritesEvent () {
const arr = [...this.list]
const str = JSON.stringify(arr, null, 4)
const str = JSON.stringify(arr, null, 2)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
@@ -320,54 +340,57 @@ export default {
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var starList = this.list
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
star.bulkAdd(json).then(e => {
this.getFavorites()
json.forEach(ele => {
const starExists = starList.includes(x => x.key === ele.key && x.ids === ele.ids)
if (!starExists) {
var doc = {
key: ele.key,
ids: ele.ids,
site: ele.site === undefined ? ele.site = this.sites.find(x => x.key === ele.key) : ele.site,
name: ele.name,
type: ele.type,
year: ele.year,
note: ele.note,
index: ele.index,
last: ele.last,
hasUpdate: ele.hasUpdate
}
starList.push(doc)
}
})
this.upgradeFavorites()
})
this.$message.success('导入收藏成功')
star.clear().then(star.bulkAdd(starList).then(res => {
this.getFavorites()
this.$message.success('导入收藏成功')
}))
}
}).catch(err => {
this.$message.error(err)
})
},
upgradeFavorites () {
star.all().then(res => {
res.forEach(element => {
const docs = {
key: element.key,
ids: element.ids,
name: element.name,
type: element.type,
year: element.year,
last: element.last,
note: element.note
}
star.find({ key: element.key, ids: element.ids }).then(res => {
if (!res) {
star.add(docs)
}
})
})
this.getFavorites()
})
},
clearFavoritesEvent () {
star.clear().then(e => {
this.getFavorites()
})
},
updateDatabase (data) {
syncTableData () {
if (this.$refs.starTable.tableData && this.$refs.starTable.tableData.length === this.list.length) {
this.list = this.$refs.starTable.tableData
}
},
updateDatabase () {
this.syncTableData()
star.clear().then(res => {
var id = length
data.forEach(ele => {
var id = this.list.length
this.list.forEach(ele => {
ele.id = id
id -= 1
})
star.bulkAdd(data)
star.bulkAdd(this.list)
})
},
rowDrop () {
@@ -377,7 +400,7 @@ export default {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.list.splice(oldIndex, 1)[0]
_this.list.splice(newIndex, 0, currRow)
_this.updateDatabase(_this.list)
_this.updateDatabase()
}
})
}

View File

@@ -3,14 +3,14 @@ import { setting, sites, localKey, iptv } from './initData'
const db = new Dexie('zy')
db.version(3).stores({
db.version(4).stores({
search: '++id, keywords',
iptvSearch: '++id, keywords',
setting: 'id, theme, site, shortcut, view, externalPlayer, searchAllSites, excludeRootClasses, excludeR18Films, forwardTimeInSec',
shortcut: 'name, key, desc',
star: '++id, site, ids, name, type, year, index',
sites: '++id, key, name, json, xml, down, level',
history: '++id, site, ids, name, type, year, index, time',
star: '++id, [key+ids], site, name, type, year, note, index, last, hasUpdate',
sites: '++id, key, name, api, download, isActive, group',
history: '++id, [site+ids], name, type, year, index, time',
mini: 'id, site, ids, name, index, time',
iptv: '++id, name, url, group'
})

View File

@@ -4,8 +4,11 @@ export default {
async add (doc) {
return await history.add(doc)
},
async bulkAdd (doc) {
return await history.bulkAdd(doc)
},
async find (doc) {
return await history.get(doc)
return await history.where(doc).first()
},
async update (id, docs) {
return await history.update(id, docs)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ export default {
return await star.bulkAdd(doc)
},
async find (doc) {
return await star.get(doc)
return await star.where(doc).first()
},
async update (id, docs) {
return await star.update(id, docs)

View File

@@ -1,8 +1,16 @@
import Vue from 'vue'
import { Message, Button, Table, TableColumn, Tag, Input } from 'element-ui'
import { Message, Button, Table, TableColumn, Tag, Input, Dialog, Form, FormItem, Switch, Select, Option } from 'element-ui'
import Plugin from 'v-fit-columns'
Vue.use(Button)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Tag)
Vue.use(Input)
Vue.use(Dialog)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Switch)
Vue.use(Plugin)
Vue.use(Select)
Vue.use(Option)
Vue.prototype.$message = Message

View File

@@ -193,6 +193,23 @@ const zy = {
}
})
})
},
/**
* 检查资源
* @param {*} key 资源网 key
* @returns boolean
*/
async check (key, id) {
try {
const cls = await this.class(key)
if (cls) {
return true
} else {
return false
}
} catch (e) {
return false
}
}
}

48
src/lib/update/update.js Normal file
View File

@@ -0,0 +1,48 @@
import { BrowserWindow, ipcMain } from 'electron'
import { autoUpdater } from 'electron-updater'
export function initUpdater (win = BrowserWindow) {
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = false
// 主进程监听检查更新事件
ipcMain.on('checkForUpdate', () => {
autoUpdater.checkForUpdates()
})
// 主进程监听退出并安装事件
ipcMain.on('quitAndInstall', () => {
autoUpdater.downloadUpdate()
})
// 开始检测是否有更新
autoUpdater.on('checking-for-update', () => {
win.webContents.send('checking-for-update')
})
// 检测到有可用的更新
autoUpdater.on('update-available', (info) => {
win.webContents.send('update-available', info)
})
// 没有检测到有可用的更新
autoUpdater.on('update-not-available', () => {
win.webContents.send('update-not-available')
})
// 更新出错
autoUpdater.on('update-error', err => {
win.webContents.send('update-error', err)
})
// 下载更新进度
autoUpdater.on('download-progress', (progressObj) => {
win.webContents.send('download-progress', progressObj)
})
// 下载完成并退出安装
autoUpdater.on('update-downloaded', () => {
win.webContents.send('update-downloaded')
autoUpdater.quitAndInstall()
})
}

View File

@@ -175,49 +175,48 @@ export default {
})
})
},
videoPlaying () {
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
if (res) {
res.index = this.video.index
history.update(res.id, res)
} else {
const doc = {
site: this.video.site,
ids: this.video.ids,
name: this.video.name,
index: this.video.index,
time: 0
}
history.add(doc)
async videoPlaying () {
const db = await history.find({ site: this.video.site, ids: this.video.ids })
if (db) {
db.index = this.video.index
history.update(db.id, db)
} else {
const doc = {
site: this.video.site,
ids: this.video.ids,
name: this.video.name,
index: this.video.index,
time: 0
}
})
history.add(doc)
}
this.timerEvent()
},
timerEvent () {
this.timer = setInterval(() => {
this.timer = setInterval(async () => {
const endTime = this.xg.duration
const currentTime = this.xg.currentTime
const progress = (currentTime / endTime) * 100
this.progress = progress.toFixed(2)
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
if (res) {
const v = res
v.time = this.xg.currentTime
v.index = this.video.index
const id = v.id
delete v.id
history.update(id, v)
}
})
const db = await history.find({ site: this.video.site, ids: this.video.ids })
if (db) {
const v = db
v.time = this.xg.currentTime
v.index = this.video.index
const id = v.id
delete v.id
history.update(id, v)
}
}, 10000)
},
prevEvent () {
async prevEvent () {
if (this.video.index === 0) {
this.$message.info('已是第一集.')
return false
}
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
const v = res
const db = await history.find({ site: this.video.site, ids: this.video.ids })
if (db) {
const v = db
const id = v.id
v.index--
delete v.id
@@ -225,15 +224,16 @@ export default {
this.xg.src = this.m3u8Arr[v.index]
this.video.index--
})
})
}
},
nextEvent () {
async nextEvent () {
if (this.video.index >= this.m3u8Arr.length - 1) {
this.$message.info('已是最后一集.')
return false
}
history.find({ site: this.video.site, ids: this.video.ids }).then(res => {
const v = res
const db = await history.find({ site: this.video.site, ids: this.video.ids })
if (db) {
const v = db
v.index++
const id = v.id
delete v.id
@@ -241,7 +241,7 @@ export default {
this.xg.src = this.m3u8Arr[v.index]
this.video.index++
})
})
}
},
playbackRateEvent (e) {
let rate = this.xg.playbackRate

View File

@@ -1023,6 +1023,11 @@
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/semver@^7.3.1":
version "7.3.4"
resolved "https://registry.npm.taobao.org/@types/semver/download/@types/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
integrity sha1-Q9cWj+xvoJiLsaUTppeykpZyGvs=
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -2166,6 +2171,14 @@ builder-util-runtime@8.7.1:
debug "^4.2.0"
sax "^1.2.4"
builder-util-runtime@8.7.2:
version "8.7.2"
resolved "https://registry.npm.taobao.org/builder-util-runtime/download/builder-util-runtime-8.7.2.tgz#d93afc71428a12789b437e13850e1fa7da956d72"
integrity sha1-2Tr8cUKKEnibQ34ThQ4fp9qVbXI=
dependencies:
debug "^4.1.1"
sax "^1.2.4"
builder-util@22.7.0:
version "22.7.0"
resolved "https://registry.npmjs.org/builder-util/-/builder-util-22.7.0.tgz#0776a66e6d6e408a78bed7f17a7ad22516d9e7f0"
@@ -3657,6 +3670,19 @@ electron-to-chromium@^1.3.488:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz#3f43d32930481d82ad3663d79658e7c59a58af0b"
integrity sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ==
electron-updater@^4.3.5:
version "4.3.5"
resolved "https://registry.npm.taobao.org/electron-updater/download/electron-updater-4.3.5.tgz?cache=0&sync_timestamp=1600328924432&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-updater%2Fdownload%2Felectron-updater-4.3.5.tgz#4fb36f593a031c87ea07ee141c9f064d5deffb15"
integrity sha1-T7NvWToDHIfqB+4UHJ8GTV3v+xU=
dependencies:
"@types/semver" "^7.3.1"
builder-util-runtime "8.7.2"
fs-extra "^9.0.1"
js-yaml "^3.14.0"
lazy-val "^1.0.4"
lodash.isequal "^4.5.0"
semver "^7.3.2"
electron@^10.1.4:
version "10.1.4"
resolved "https://registry.npm.taobao.org/electron/download/electron-10.1.4.tgz?cache=0&sync_timestamp=1603157068707&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron%2Fdownload%2Felectron-10.1.4.tgz#5462c5fac5b4728691042d0f62133ea2c133e6fd"
@@ -6066,6 +6092,11 @@ lodash.defaultsdeep@^4.6.1:
resolved "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.npm.taobao.org/lodash.isequal/download/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.kebabcase@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@@ -9580,6 +9611,11 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
v-fit-columns@^0.2.0:
version "0.2.0"
resolved "https://registry.npm.taobao.org/v-fit-columns/download/v-fit-columns-0.2.0.tgz#4d75b0eb66fcd701025044f356cc00e8d6fec7a2"
integrity sha1-TXWw62b81wECUETzVswA6Nb+x6I=
v8-compile-cache@^2.0.3:
version "2.1.1"
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
@@ -9999,10 +10035,10 @@ xgplayer-hls.js@^2.2.5:
event-emitter "^0.3.5"
eventemitter3 "^4.0.7"
xgplayer@^2.12.2:
version "2.12.2"
resolved "https://registry.npm.taobao.org/xgplayer/download/xgplayer-2.12.2.tgz#72111cfbb21f97cbe454b0f5d17e85f720892b2a"
integrity sha1-chEc+7Ifl8vkVLD10X6F9yCJKyo=
xgplayer@^2.13.0:
version "2.13.0"
resolved "https://registry.npmjs.org/xgplayer/-/xgplayer-2.13.0.tgz#24df20fd527a87cccc5cee3db70937408eab86ed"
integrity sha512-QMjwnpO6o8bd2ZywHtANaOqvey476UAmPS+hXC0im9wiESMKqxPsCmeqhIxFFVao/pUtIyopHADcor9ipai5Rg==
dependencies:
chalk "^2.3.2"
commander "^2.15.1"