Compare commits

...

154 Commits

Author SHA1 Message Date
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
haiyangcui
cd2cb684a5 v2.5.3 2020-10-21 12:35:07 +02:00
Hunlongyu
500dbfa1ee 🖼 测试代码 2020-10-21 18:23:45 +08:00
Hunlongyu
e970074266 Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player into master 2020-10-21 15:33:58 +08:00
Hunlongyu
9dc7ab4d1e 🎪 升级最新依赖 2020-10-21 09:13:53 +08:00
haiyangcui
ffe821d107 IPTV函数名更新 2020-10-20 21:57:52 +02:00
Hunlongyu
0f015b26e2 🎢 优化 IPTV 的搜索框样式 2020-10-20 09:27:13 +08:00
haiyangcui
f031f9e7fd 修复确定分组的逻辑 2020-10-19 23:40:50 +02:00
haiyangcui
b38de7f393 IPTV分组 2020-10-19 22:57:53 +02:00
haiyangcui
8b5e8fd072 IPTV添加group 2020-10-19 22:15:50 +02:00
haiyangcui
58e556554e IPTV搜索功能 2020-10-19 21:21:59 +02:00
haiyangcui
cceed30d35 IPTV拖曳功能 2020-10-19 13:46:59 +02:00
haiyangcui
980b0a6e50 el-table实现IPTV页面 2020-10-19 13:46:59 +02:00
Hunlongyu
c98236222d 🎡 修复 Film table 模式下, 不加载的 BUG 2020-10-19 18:37:48 +08:00
Hunlongyu
6a7c2afb2d 🎠 代码优化 2020-10-19 18:31:46 +08:00
Hunlongyu
af482a450a 🎟 新增右键菜单 2020-10-19 18:14:42 +08:00
Hunlongyu
42b81b98cf 🎞 修复 Film 视图报错 BUG 2020-10-19 17:42:00 +08:00
Hunlongyu
da7dfed5ba 🎁 修复视频停止播放后, 下次点击按钮失效的问题 2020-10-19 17:28:17 +08:00
Hunlongyu
7d6c5482af 🎀 新增窗口最小化, 暂停视频. 从最小化恢复窗口, 视频播放 2020-10-19 16:57:32 +08:00
Hunlongyu
11e7ffc554 🎑 新增视频停止播放按钮 2020-10-19 16:31:02 +08:00
Hunlongyu
4974d57fbf 🎑 mini 模式修复 2020-10-19 13:41:50 +08:00
Hunlongyu
037bb1a2ff 🎐 升级electron依赖到最新版 2020-10-19 13:40:12 +08:00
Hunlongyu
b19870d228 🎏 优化 table 滚动条 2020-10-19 11:52:28 +08:00
Hunlongyu
de0161c560 🎎 升级依赖, 修复element 2020-10-19 11:28:52 +08:00
Hunlongyu
623a10bd13 🎎 按需引入 element button 2020-10-19 11:09:14 +08:00
Hunlongyu
c69eadbe3e 🎍 新增清理视频缓存功能 2020-10-19 11:00:11 +08:00
Hunlongyu
5f48f46cbc 🎋 table style 2020-10-19 09:35:50 +08:00
haiyangcui
694986d6c5 用el-button实现header部分的按钮 2020-10-18 22:54:21 +02:00
haiyangcui
d4ad68e030 监听EditSites页面sites数据变化 2020-10-18 21:57:39 +02:00
haiyangcui
72089bf53f Code cleanup 2020-10-18 18:05:48 +02:00
haiyangcui
0401ce6d7e Revert "记录窗口大小及位置"
This reverts commit 381b0a8735.
2020-10-18 10:11:26 +02:00
haiyangcui
09e27d4f40 添加overflow-y: auto;到el-table格式定义中 2020-10-18 09:38:06 +02:00
haiyangcui
45de6650bf 统一EditSites页面格式 2020-10-17 23:59:58 +02:00
haiyangcui
4e0fa4d980 支持sites页面拖曳排序 2020-10-17 23:35:01 +02:00
haiyangcui
7a0f8f9644 支持star页面的拖曳排序 2020-10-17 21:19:05 +02:00
haiyangcui
e43ef98ce7 支持历史页面的拖曳排序 2020-10-17 21:11:50 +02:00
haiyangcui
2abbf41ed1 header上的按钮,鼠标划过更改指针模式 2020-10-17 18:37:49 +02:00
haiyangcui
02139d3d24 header部分加入padding 2020-10-17 18:34:27 +02:00
haiyangcui
d86a10d753 el-table实现EditSites页面 2020-10-17 18:09:17 +02:00
haiyangcui
dc8bdb29dc 解决有更新行高亮的问题 2020-10-17 14:12:24 +02:00
haiyangcui
98b019be5f 更新listpage样式 2020-10-17 13:55:04 +02:00
haiyangcui
e6d1698d62 删除star页面无用格式定义 2020-10-17 13:43:37 +02:00
haiyangcui
6c1e6c511f 删除无用的样式 2020-10-17 12:13:52 +02:00
haiyangcui
1a1393615c 套用listpage样式 2020-10-17 12:08:30 +02:00
cuiocean
5930690144 Merge pull request #292 from buvta/patch-3
el重写Star.vue,用排序替代拖拽
2020-10-17 12:07:09 +02:00
haiyangcui
091493fa77 定义listpage样式 2020-10-17 11:23:53 +02:00
buvta
cd1c4eaffe el重写Star.vue,用排序替代拖拽 2020-10-17 13:03:34 +08:00
cuiocean
1bb217d84f Merge pull request #289 from buvta/patch-4
去掉表格底部横线
2020-10-16 18:02:26 +02:00
buvta
92eac6d2dd 去掉表格底部横线 2020-10-16 23:49:12 +08:00
haiyangcui
b067cfa143 解决History页面自适应问题 2020-10-16 16:16:21 +02:00
haiyangcui
99d21fd7fe 设置不显示table的border,无需响应header-click打开关闭border 2020-10-16 14:46:24 +02:00
Hunlongyu
b167f711d0 🎄 修改历史界面样式 2020-10-16 18:03:20 +08:00
Hunlongyu
7d4ffeed87 🎃 移除 element 全局导入, 修改 star 样式 2020-10-16 18:00:13 +08:00
haiyangcui
7e94ef8025 更新el-table背景色 2020-10-15 22:30:54 +02:00
haiyangcui
1a36bc85b9 更新el-table背景色 2020-10-15 22:21:54 +02:00
haiyangcui
f7ed0e1c29 更新el-button的样式 2020-10-15 21:25:14 +02:00
cuiocean
970a359aba Merge pull request #280 from buvta/forPR1
改写History.vue
2020-10-15 21:15:31 +02:00
haiyangcui
be0441d042 设置不同主题下el-table的css样式 2020-10-15 21:12:42 +02:00
buvta
3fc350e6d6 el改写History.vue 2020-10-16 01:07:08 +08:00
buvta
b26a4d4f27 全局引入elementUI 2020-10-16 01:00:28 +08:00
haiyangcui
454ea48891 格式化Detail.vue 2020-10-14 23:02:02 +02:00
haiyangcui
d564d0928b 播放直播源时VideoTitle显示直播频道的名字 2020-10-13 22:08:40 +02:00
haiyangcui
2562119bad 支持导入源时multiSelections 2020-10-13 21:47:22 +02:00
haiyangcui
1a7aaa8dff 导入源时不删除原有源,但会检查源是否有重复,不添加重复的源 2020-10-13 21:44:24 +02:00
haiyangcui
583a768068 改进函数名 2020-10-12 18:26:27 +02:00
haiyangcui
6216ad96d6 源编辑页面添加清空功能 2020-10-12 18:25:24 +02:00
haiyangcui
766b1d458f IPTV添加置顶功能 2020-10-11 14:46:52 +02:00
haiyangcui
6a0699ec20 添加源置顶功能 2020-10-11 14:46:13 +02:00
haiyangcui
a6fd748e09 统一按钮名称和对话框名称 2020-10-11 13:47:18 +02:00
cuiocean
064ff38650 Merge pull request #271 from buvta/master
使用对话框新增或修改源
2020-10-11 13:39:25 +02:00
buvta
1c74174a3a 使用对话框新增或编辑源
bug已解决
2020-10-11 14:11:30 +08:00
haiyangcui
0625c4945f 支持'极品''喜欢看' 2020-10-11 00:05:49 +02:00
Hunlongyu
e1d0643c68 🍔 电视直播位置移动考上 2020-10-10 17:01:41 +08:00
Hunlongyu
bce10b906c 🍛 移除二维码 2020-10-10 16:54:18 +08:00
haiyangcui
d485a5733e 支持'喜欢看' 2020-10-10 10:15:09 +02:00
haiyangcui
5ab5cee6dc 支持'极品' 2020-10-09 21:06:11 +02:00
haiyangcui
235e894f66 修复IPTV页面高度问题 2020-10-09 17:34:04 +02:00
haiyangcui
ee62ae0c8f Revert "格式化vue文件"
This reverts commit 8b78aafda4.

# Conflicts:
#	src/components/IPTV.vue
2020-10-09 17:29:53 +02:00
haiyangcui
f8b95fe430 v2.5.2-1 2020-10-09 17:23:36 +02:00
haiyangcui
a3fc5dba79 v2.5.2.1 2020-10-09 16:56:09 +02:00
haiyangcui
e1cee12222 加入占位按钮,以免不小心点到重置 2020-10-09 16:43:53 +02:00
haiyangcui
6d2b1720b7 恢复m3u8限制 2020-10-09 16:27:14 +02:00
haiyangcui
1c7024a68a 删除多余空白按钮 2020-10-09 16:21:39 +02:00
haiyangcui
8729184ba9 修复列表主题的问题 2020-10-09 16:16:58 +02:00
haiyangcui
618fdf03d9 移除m3u8的限制 2020-10-09 16:10:20 +02:00
35 changed files with 3556 additions and 3345 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "zy",
"version": "2.5.2",
"version": "2.5.4",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -17,30 +17,32 @@
},
"main": "background.js",
"dependencies": {
"axios": "^0.19.2",
"axios": "^0.20.0",
"cheerio": "^1.0.0-rc.3",
"child_process": "^1.0.2",
"core-js": "^3.6.5",
"cors": "^2.8.5",
"dexie": "^3.0.1",
"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",
"html2canvas": "^1.0.0-rc.5",
"html2canvas": "^1.0.0-rc.7",
"iptv-playlist-parser": "^0.5.0",
"m3u": "0.0.2",
"modern-normalize": "^0.6.0",
"modern-normalize": "^1.0.0",
"mousetrap": "^1.6.5",
"qrcode.vue": "^1.7.0",
"randomstring": "^1.1.5",
"vue": "^2.6.11",
"v-fit-columns": "^0.2.0",
"vue": "^2.6.12",
"vue-infinite-loading": "^2.4.5",
"vue-waterfall-plugin": "^1.0.7",
"vuedraggable": "^2.24.1",
"vuex": "^3.4.0",
"xgplayer": "^2.9.10",
"xgplayer-hls.js": "^2.2.3"
"vue-waterfall-plugin": "^1.1.0",
"vuedraggable": "^2.24.2",
"vuex": "^3.5.1",
"xgplayer": "^2.13.0",
"xgplayer-hls.js": "^2.2.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.4.0",
@@ -50,7 +52,7 @@
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"electron": "^9.3.1",
"electron": "^10.1.4",
"electron-devtools-installer": "^3.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
@@ -61,6 +63,6 @@
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-cli-plugin-electron-builder": "2.0.0-rc.4",
"vue-template-compiler": "^2.6.11"
"vue-template-compiler": "^2.6.12"
}
}

View File

@@ -6,6 +6,15 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<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

@@ -9,6 +9,7 @@
<Star v-show="view === 'Star'" />
<History v-show="view === 'History'" />
<Setting v-show="view === 'Setting'" />
<EditSites v-if="view === 'EditSites'"/>
</div>
<transition name="slide">
<Detail v-if="detail.show"/>
@@ -16,68 +17,15 @@
<transition name="slide">
<Share v-if="share.show"/>
</transition>
<transition name="slide">
<EditSites v-if="editSites.show"/>
</transition>
</div>
</template>
<script>
import { setting } from './lib/dexie'
const { remote } = require('electron')
export default {
name: 'App',
data () {
return {
appTheme: 'theme-light',
winSizePosition: {
x: 0,
y: 0,
width: 0,
height: 0
}
}
},
created () {
// 窗口创建口,检查是否有窗口大小位置的记录,如果有的话,更新窗口位置及大小
setting.find().then(res => {
if (res.windowSizePosition) {
var win = remote.getCurrentWindow()
win.setBounds({
x: res.windowSizePosition.x,
y: res.windowSizePosition.y,
width: res.windowSizePosition.width,
height: res.windowSizePosition.height
})
}
})
var win = remote.getCurrentWindow()
this.winSizePosition = {
x: win.getPosition()[0],
y: win.getPosition()[1],
width: win.getSize()[0],
height: win.getSize()[1]
}
},
updated () {
// 本来想hook up到beforedestroy 但不工作
// 每当窗口更新时检查窗口大小及位置记录到setting数据库中
const win = remote.getCurrentWindow()
var newWinSizePosition = {
x: win.getPosition()[0],
y: win.getPosition()[1],
width: win.getSize()[0],
height: win.getSize()[1]
}
if (newWinSizePosition.x !== this.winSizePosition.x ||
newWinSizePosition.y !== this.winSizePosition.y ||
newWinSizePosition.width !== this.winSizePosition.width ||
newWinSizePosition.height !== this.winSizePosition.height) {
this.winSizePosition = newWinSizePosition
setting.find().then(res => {
res.windowSizePosition = newWinSizePosition
setting.update(res)
})
appTheme: 'theme-light'
}
},
computed: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

View File

@@ -124,6 +124,12 @@
flex: 1;
border-bottom: 1px solid;
overflow: auto;
.el-table__row td{
border: none;
}
.el-table::before{
height: 0px;
}
ul{
list-style: none;
padding: 0;
@@ -177,7 +183,7 @@
// scroll
.zy-scroll{
&::-webkit-scrollbar{
width: 5px;
width: 10px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
@@ -189,6 +195,84 @@
position: absolute;
}
}
// Page of list using el-table
.listpage{
position: absolute;
left: 80px;
right: 20px;
top: 40px;
bottom: 20px;
width: calc(100% - 100px);
height: calc(100% - 60px);
border-radius: 5px;
.listpage-content{
height: 100%;
position: relative;
font-size: 1rem;
.listpage-header{
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 50px;
padding-right: 50px;
.el-button{
font-size: 1rem;
border: none;
&:hover{
cursor: pointer;
}
}
.el-input{
width: 200px;
}
}
.listpage-body{
height: calc(100% - 40px);
overflow-y: auto;
.el-table::before{
height: 0px;
}
.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{
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;
}
}
}
}
// loading
.zy-loading{
width: 100%;

View File

@@ -65,7 +65,7 @@
--p-c-9: #f4f7f799;
--p-fc-1: #ffffff;
--p-fc-2: #FFFFF3;
--p-fc-3: #f15c5c;
--p-fc-3: #177ea7;
--p-bgc-1: #ff8499;
--p-bgc-2: #fea1b2;
--p-bsc: 0 1px 3px #ef528533, 0 1px 2px #ef528544;

View File

@@ -62,7 +62,9 @@
border-bottom-color: var(--d-c-2);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
box-shadow: var(--d-bsc);
background-color: var(--d-bgc-2);
color: #fff;
}
span{
&.btn:hover{
@@ -353,4 +355,72 @@
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
}
// Page of list using el-table
.listpage{
color: var(--d-fc-2);
.listpage-content{
.listpage-header{
border-bottom-color: var(--d-c-3);
.btn{
&:hover{
color: var(--d-fc-3)
}
}
.el-button{
background-color: var(--d-bgc-2);
color: var(--d-fc-2);
&:hover{
color: var(--d-fc-3)
}
}
.el-input{
input{
background-color: var(--d-bgc-1);
border: 1px solid var(--d-bgc-1);
color: var(--d-fc-2);
}
}
}
.listpage-body{
/* 设置el-table的样式*/
.el-table{
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);
}
.el-button{
color: var(--d-fc-1);
&:hover{
color: var(--d-fc-3)
}
}
}
}
}
}

View File

@@ -250,7 +250,7 @@
color: var(--g-fc-3);
}
&:hover{
background-color: var(--d-c-3);
background-color: var(--g-c-3);
}
}
}
@@ -351,4 +351,72 @@
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
}
// Page of list using el-table
.listpage{
color: var(--g-fc-2);
.listpage-content{
.listpage-header{
border-bottom-color: var(--g-c-3);
.btn{
&:hover{
color: var(--g-fc-3)
}
}
.el-button{
background-color: var(--g-bgc-2);
color: var(--g-fc-2);
&:hover{
color: var(--g-fc-3)
}
}
.el-input{
input{
background-color: var(--g-bgc-1);
border: 1px solid var(--g-bgc-1);
color: var(--g-fc-2);
}
}
}
.listpage-body{
/* 设置el-table的样式*/
.el-table{
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);
}
.el-button{
color: var(--g-fc-1);
&:hover{
color: var(--g-fc-3)
}
}
}
}
}
}

View File

@@ -250,7 +250,7 @@
color: var(--l-fc-3);
}
&:hover{
background-color: var(--d-c-3);
background-color: var(--l-c-3);
}
}
}
@@ -351,4 +351,72 @@
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
}
// Page of list using el-table
.listpage{
color: var(--l-fc-2);
.listpage-content{
.listpage-header{
border-bottom-color: var(--l-c-3);
.btn{
&:hover{
color: var(--l-fc-3)
}
}
.el-button{
background-color: var(--l-bgc-2);
color: var(--l-fc-2);
&:hover{
color: var(--l-fc-3)
}
}
.el-input{
input{
background-color: var(--l-bgc-1);
border: 1px solid var(--l-bgc-1);
color: var(--l-fc-2);
}
}
}
.listpage-body{
/* 设置el-table的样式*/
.el-table{
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);
}
.el-button{
color: var(--l-fc-1);
&:hover{
color: var(--l-fc-3)
}
}
}
}
}
}

View File

@@ -249,7 +249,7 @@
color: var(--p-fc-3);
}
&:hover{
background-color: var(--d-c-3);
background-color: var(--p-c-3);
}
}
}
@@ -350,4 +350,72 @@
background-color: var(--p-bgc-1);
box-shadow: var(--p-bsc);
}
// Page of list using el-table
.listpage{
color: var(--p-fc-2);
.listpage-content{
.listpage-header{
border-bottom-color: var(--p-c-3);
.btn{
&:hover{
color: var(--p-fc-3)
}
}
.el-button{
background-color: var(--p-bgc-2);
color: var(--p-fc-2);
&:hover{
color: var(--p-fc-3)
}
}
.el-input{
input{
background-color: var(--p-bgc-1);
border: 1px solid var(--p-bgc-1);
color: var(--p-fc-2);
}
}
}
.listpage-body{
/* 设置el-table的样式*/
.el-table{
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);
}
.el-button{
color: var(--p-fc-1);
&:hover{
color: var(--p-fc-3)
}
}
}
}
}
}

View File

@@ -4,6 +4,7 @@ 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'
// 允许跨域
@@ -22,6 +23,7 @@ function createWindow () {
resizable: true,
webPreferences: {
webSecurity: false,
enableRemoteModule: true,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
}
})
@@ -34,6 +36,8 @@ function createWindow () {
win.loadURL('app://./index.html')
}
initUpdater(win)
win.on('closed', () => {
win = null
})
@@ -49,6 +53,7 @@ function createMini () {
resizable: true,
webPreferences: {
webSecurity: false,
enableRemoteModule: true,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
}
})

View File

@@ -1,17 +1,7 @@
<template>
<div class="aside">
<span
:class="[view === 'Film' ? 'active ': ''] + 'zy-svg'"
@click="changeView('Film')"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="apertureIconTitle"
>
<span :class="[view === 'Film' ? 'active ': ''] + 'zy-svg'" @click="changeView('Film')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="apertureIconTitle">
<title id="apertureIconTitle">电影</title>
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"></path>
<g stroke-linecap="round">
@@ -24,98 +14,37 @@
</g>
</svg>
</span>
<span
:class="[view === 'Play' ? 'active ': ''] + 'zy-svg'"
@click="changeView('Play')"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="playIconTitle"
>
<span :class="[view === 'IPTV' ? 'active ': ''] + 'zy-svg'" @click="changeView('IPTV')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="tvIconTitle">
<title id="tvIconTitle">电视直播</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
<polyline stroke-linejoin="round" points="8 4 12 7.917 16 4"></polyline>
</svg>
</span>
<span :class="[view === 'Play' ? 'active ': ''] + 'zy-svg'" @click="changeView('Play')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="playIconTitle">
<title id="playIconTitle">播放</title>
<path d="M20 12L5 21V3z"></path>
</svg>
</span>
<span
:class="[view === 'Star' ? 'active ': ''] + 'zy-svg'"
@click="changeView('Star')"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="favouriteIconTitle"
>
<span :class="[view === 'Star' ? 'active ': ''] + 'zy-svg'" @click="changeView('Star')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="favouriteIconTitle">
<title id="favouriteIconTitle">收藏</title>
<path d="M12,21 L10.55,19.7051771 C5.4,15.1242507 2,12.1029973 2,8.39509537 C2,5.37384196 4.42,3 7.5,3 C9.24,3 10.91,3.79455041 12,5.05013624 C13.09,3.79455041 14.76,3 16.5,3 C19.58,3 22,5.37384196 22,8.39509537 C22,12.1029973 18.6,15.1242507 13.45,19.7149864 L12,21 Z"></path>
</svg>
</span>
<span
:class="[view === 'IPTV' ? 'active ': ''] + 'zy-svg'"
@click="changeView('IPTV')"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="tvIconTitle"
>
<title id="tvIconTitle">电视直播</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
<polyline
stroke-linejoin="round"
points="8 4 12 7.917 16 4"
></polyline>
</svg>
<span :class="[view === 'History' ? 'active ': ''] + 'zy-svg'" @click="changeView('History')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="timeIconTitle">
<title id="timeIconTitle">历史记录</title>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 5 12 12 16 16"></polyline>
</svg>
</span>
<span
:class="[view === 'History' ? 'active ': ''] + 'zy-svg'"
@click="changeView('History')"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="timeIconTitle"
>
<title id="timeIconTitle">历史记录</title>
<circle
cx="12"
cy="12"
r="10"
></circle>
<polyline points="12 5 12 12 16 16"></polyline>
</svg>
</span>
<span
:class="[view === 'Setting' ? 'active ': ''] + 'zy-svg'"
@click="changeView('Setting')"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="settingsIconTitle"
>
<span :class="[view === 'Setting' ? 'active ': ''] + 'zy-svg'" @click="changeView('Setting')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="settingsIconTitle">
<title id="settingsIconTitle">设置</title>
<path d="M5.03506429,12.7050339 C5.01187484,12.4731696 5,12.2379716 5,12 C5,11.7620284 5.01187484,11.5268304 5.03506429,11.2949661 L3.20577137,9.23205081 L5.20577137,5.76794919 L7.9069713,6.32070904 C8.28729123,6.0461342 8.69629298,5.80882212 9.12862533,5.61412402 L10,3 L14,3 L14.8713747,5.61412402 C15.303707,5.80882212 15.7127088,6.0461342 16.0930287,6.32070904 L18.7942286,5.76794919 L20.7942286,9.23205081 L18.9649357,11.2949661 C18.9881252,11.5268304 19,11.7620284 19,12 C19,12.2379716 18.9881252,12.4731696 18.9649357,12.7050339 L20.7942286,14.7679492 L18.7942286,18.2320508 L16.0930287,17.679291 C15.7127088,17.9538658 15.303707,18.1911779 14.8713747,18.385876 L14,21 L10,21 L9.12862533,18.385876 C8.69629298,18.1911779 8.28729123,17.9538658 7.9069713,17.679291 L5.20577137,18.2320508 L3.20577137,14.7679492 L5.03506429,12.7050339 Z"></path>
<circle
cx="12"
cy="12"
r="1"
></circle>
<circle cx="12" cy="12" r="1"></circle>
</svg>
</span>
</div>
@@ -140,35 +69,23 @@ 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和EditSites页面
this.detail = {
show: false
}
if (this.editSites.show === true) {
this.editSites = {
show: false
}
}
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'page', 'view', e])
}
}
}
</script>
<style lang="scss" scoped>
.aside {
.aside{
width: 60px;
height: 100%;
display: flex;
@@ -177,7 +94,7 @@ export default {
flex-direction: column;
justify-content: center;
-webkit-app-region: drag;
span {
span{
width: 60px;
height: 60px;
cursor: pointer;

View File

@@ -3,10 +3,7 @@
<div class="detail-content">
<div class="detail-header">
<span class="detail-title">详情</span>
<span
class="detail-close zy-svg"
@click="close"
>
<span class="detail-close zy-svg" @click="close">
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
@@ -20,55 +17,22 @@
</svg>
</span>
</div>
<div
class="detail-body zy-scroll"
v-show="!loading"
>
<div class="detail-body zy-scroll" v-show="!loading">
<div class="info">
<div class="info-left">
<img
:src="info.pic"
alt=""
>
<img :src="info.pic" alt="">
</div>
<div class="info-right">
<div class="name">{{info.name}}</div>
<div
class="director"
v-show="info.director"
>导演: {{info.director}}</div>
<div
class="actor"
v-show="info.actor"
>主演: {{info.actor}}</div>
<div
class="type"
v-show="info.type"
>类型: {{info.type}}</div>
<div
class="area"
v-show="info.area"
>地区: {{info.area}}</div>
<div
class="lang"
v-show="info.lang"
>语言: {{info.lang}}</div>
<div
class="year"
v-show="info.year"
>上映: {{info.year}}</div>
<div
class="last"
v-show="info.last"
>更新: {{info.last}}</div>
<div
class="note"
v-show="info.note"
>备注: {{info.note}}</div>
<div
class="rate"
v-show="info.rate"
>豆瓣评分: {{info.rate}}</div>
<div class="director" v-show="info.director">导演: {{info.director}}</div>
<div class="actor" v-show="info.actor">主演: {{info.actor}}</div>
<div class="type" v-show="info.type">类型: {{info.type}}</div>
<div class="area" v-show="info.area">地区: {{info.area}}</div>
<div class="lang" v-show="info.lang">语言: {{info.lang}}</div>
<div class="year" v-show="info.year">上映: {{info.year}}</div>
<div class="last" v-show="info.last">更新: {{info.last}}</div>
<div class="note" v-show="info.note">备注: {{info.note}}</div>
<div class="rate" v-show="info.rate">豆瓣评分: {{info.rate}}</div>
</div>
</div>
<div class="operate">
@@ -78,45 +42,24 @@
<span @click="shareEvent">分享</span>
<span @click="doubanLinkEvent">豆瓣</span>
<span @click="togglePlayOnlineEvent">
<input
type="checkbox"
v-model="playOnline"
> 播放在线高清视频
<input type="checkbox" v-model="playOnline"> 播放在线高清视频
</span>
<span>
<select
v-model="selectedOnlineSite"
class="vs-options"
>
<option
disabled
value=""
>Please select one</option>
<option
v-for="(i, j) in onlineSites"
:key="j"
>{{i}}</option>
<select v-model="selectedOnlineSite" class="vs-options">
<option disabled value="">Please select one</option>
<option v-for="(i, j) in onlineSites" :key="j">{{i}}</option>
</select>
</span>
</div>
<div
class="desc"
v-show="info.des"
>{{info.des}}</div>
class="desc" v-show="info.des">{{info.des}}</div>
<div class="m3u8">
<div class="box">
<span
v-for="(i, j) in m3u8List"
:key="j"
@click="playEvent(j)"
>{{i | ftName}}</span>
<span v-for="(i, j) in m3u8List" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
</div>
</div>
</div>
<div
class="detail-mask zy-loading"
v-show="loading"
>
<div class="detail-mask zy-loading" v-show="loading">
<div class="loader"></div>
</div>
</div>
@@ -137,7 +80,7 @@ export default {
info: {},
playOnline: false,
selectedOnlineSite: '哔嘀',
onlineSites: ['哔嘀', '素白白', '简影', '1080影视']
onlineSites: ['哔嘀', '素白白', '简影', '极品', '喜欢看', '1080影视']
}
},
filters: {
@@ -198,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: ''
}
})
this.playVideoOnline(this.detail.info.name, n)
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
@@ -275,6 +215,9 @@ export default {
case '简影':
onlineVideo.playVideoOnSyrme(videoName, videoIndex)
break
case '极品':
onlineVideo.playVideoOnJpysvip(videoName, videoIndex)
break
default:
this.$message.console.error(`不支持该网站:${this.selectedOnlineSite}`)
}
@@ -383,6 +326,9 @@ export default {
this.loading = false
}
})
const _hmt = window._hmt
const name = this.detail.info.name
_hmt.push(['_trackEvent', 'detail', 'view', name])
}
},
created () {

View File

@@ -1,159 +1,140 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="openAddSite"
>添加新源</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="exportSites"
>导出</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="importSites"
>导入</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="resetSites"
>重置</div>
</div>
<span
class="detail-close zy-svg"
@click="close"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-labelledby="closeIconTitle"
>
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
<div class="listpage" id="editSites">
<div class="listpage-content">
<div class="listpage-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组">></el-switch>
<el-button @click.stop="addSite" icon="el-icon-document-add">新增</el-button>
<el-button @click.stop="exportSites" icon="el-icon-upload2" >导出</el-button>
<el-button @click.stop="importSites" icon="el-icon-download">导入</el-button>
<el-button @click.stop="removeAllSites" icon="el-icon-delete-solid">清空</el-button>
<el-button @click.stop="resetSitesEvent" icon="el-icon-refresh-left">重置</el-button>
</div>
<div class="detail-body zy-scroll">
<div class="zy-table">
<div class="tBody zy-scroll">
<div
class="addSites-box zy-scroll"
v-show="showAddSite"
>
<ul>
<li>
<span class="name">源名称</span>
<span class="name">API接口</span>
<span class="name">DOWNLOAD接口</span>
<span class="operate">
<span class="btn"></span>
<span class="btn"></span>
</span>
</li>
<li>
<span
class="name"
style="display:inline-block;vertical-align:middle"
>
<input
style="height: 30px"
v-model="newSite.name"
>
</span>
<span
class="name"
style="display:inline-block;vertical-align:middle"
>
<input
style="height: 30px"
v-model="newSite.api"
>
</span>
<span
class="name"
style="display:inline-block;vertical-align:middle"
>
<input
style="height: 30px"
v-model="newSite.download"
placeholder="可以为空"
>
</span>
<span class="operate">
<span
class="btn"
@click="addNewSite"
>添加</span>
<span
class="btn"
@click="closeAddSite"
>关闭</span>
</span>
</li>
<li></li>
</ul>
</div>
<ul>
<draggable
v-model="sites"
@change="listUpdatedEvent"
>
<transition-group>
<li
v-for="(i, j) in sites"
:key="j"
>
<span class="name">{{i.name}}</span>
<span class="operate">
<span
class="btn"
@click.stop="removeEvent(i)"
>删除</span>
</span>
</li>
</transition-group>
</draggable>
</ul>
</div>
</div>
<div 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
ref="editSitesTable"
size="mini" fit height="100%" row-key="id"
:data="sites"
@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="操作"
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-item label="分组" prop='group'>
<el-input v-model="siteInfo.group" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入分组"/>
</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, setting } from '../lib/dexie'
import draggable from 'vuedraggable'
import { sites } from '../lib/dexie'
import { remote } from 'electron'
import { sites as defaultSites } from '../lib/dexie/initData'
import fs from 'fs'
import Sortable from 'sortablejs'
export default {
name: 'editSites',
data () {
return {
show: false,
sites: [],
showAddSite: false,
newSite: {
dialogType: 'new',
dialogVisible: false,
siteInfo: {
key: '',
name: '',
api: '',
download: ''
}
download: '',
group: '',
isActive: 1
},
rules: {
name: [
{ required: true, message: '源站名不能为空', trigger: 'blur' }
],
api: [
{ required: true, message: 'API地址不能为空', trigger: 'blur' }
],
download: [
{ required: false, trigger: 'blur' }
]
},
enableBatchEdit: false,
batchGroupName: '',
batchIsActive: 1,
multipleSelection: []
}
},
components: {
draggable
},
computed: {
setting: {
get () {
@@ -170,18 +151,69 @@ 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']),
close () {
this.editSites.show = false
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
}
})
},
addSite () {
this.dialogType = 'new'
this.dialogVisible = true
this.siteInfo = {
key: '',
name: '',
api: '',
download: '',
group: '',
isActive: 1
}
},
editSite (siteInfo) {
this.dialogType = 'edit'
this.dialogVisible = true
this.siteInfo = siteInfo
},
closeDialog () {
this.dialogVisible = false
this.getSites()
},
removeEvent (e) {
sites.remove(e.id).then(res => {
this.getSites()
@@ -200,39 +232,41 @@ export default {
})
})
},
openAddSite () {
this.showAddSite = true
},
closeAddSite () {
this.showAddSite = false
},
addNewSite () {
if (!this.newSite.name || !this.newSite.api) {
addOrEditSite () {
if (!this.siteInfo.name || !this.siteInfo.api) {
this.$message.error('名称和API接口不能为空。')
return
}
var randomstring = require('randomstring')
var doc = {
key: randomstring.generate(6),
id: this.sites[this.sites.length - 1].id + 1,
name: this.newSite.name,
api: this.newSite.api,
download: this.newSite.download
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,
group: this.siteInfo.group,
isActive: this.siteInfo.isActive
}
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'site', 'add', `${this.siteInfo.name}: ${this.siteInfo.api}`])
if (this.dialogType === 'edit') sites.remove(this.siteInfo.id)
sites.add(doc).then(res => {
this.newSite = {
this.siteInfo = {
key: '',
name: '',
api: '',
download: ''
download: '',
group: ''
}
this.$message.success('添加新源成功!')
this.dialogType === 'edit' ? this.$message.success('修改成功!') : this.$message.success('新增源成功!')
this.dialogVisible = false
this.getSites()
})
},
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'] },
@@ -256,72 +290,88 @@ export default {
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
],
properties: ['openFile']
properties: ['openFile', 'multiSelections']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
sites.clear()
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
sites.bulkAdd(json).then(e => {
this.getSites()
this.d.site = json[0].key
setting.update(this.d).then(res => {
this.setting = this.d
})
json.forEach(ele => {
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('导入成功')
}).catch(err => {
this.$message.error(err)
this.getSites()
})
}
})
},
resetSites () {
sites.clear()
sites.bulkAdd(defaultSites).then(e => {
this.getSites()
this.$message.success('重置源成功')
resetSitesEvent () {
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites()))
this.$message.success('重置源成功')
},
moveToTopEvent (i) {
this.sites.sort(function (x, y) { return x.key === i.key ? -1 : y.key === i.key ? 1 : 0 })
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
inArray.forEach(ele => {
ele.id = id
id += 1
})
},
updateDatabase () {
// 因为el-table的数据是单向绑定,我们先同步el-table里的数据和其绑定的数据
this.syncTableData()
sites.clear().then(res => {
var id = 1
this.sites.forEach(ele => {
ele.id = id
id += 1
})
sites.bulkAdd(this.sites).then(this.getSites())
})
},
removeAllSites () {
sites.clear().then(this.getSites())
},
rowDrop () {
const tbody = document.getElementById('sites-table').querySelector('.el-table__body-wrapper tbody')
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()
}
})
}
},
mounted () {
this.rowDrop()
},
created () {
this.getSites()
}
}
</script>
<style lang="scss" scoped>
.detail {
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content {
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title {
font-size: 16px;
}
.detail-close {
cursor: pointer;
}
}
}
.detail-body {
height: calc(100% - 50px);
overflow-y: auto;
}
}
</style>

View File

@@ -1,149 +1,52 @@
<template>
<div class="film">
<div class="header">
<div
class="zy-select"
@mouseleave="show.site = false"
>
<div
class="vs-placeholder"
@click="show.site = true"
>{{site.name}}</div>
<div
class="vs-options"
v-show="show.site"
>
<ul
class="zy-scroll"
style="max-height: 600px;"
>
<li
:class="site.key === i.key ? 'active' : ''"
v-for="i in sites"
:key="i.key"
@click="siteClick(i)"
>{{ i.name }}</li>
<div class="zy-select" @mouseleave="show.site = false">
<div class="vs-placeholder" @click="show.site = true">{{site.name}}</div>
<div class="vs-options" v-show="show.site">
<ul class="zy-scroll" style="max-height: 600px;">
<li :class="site.key === i.key ? 'active' : ''" v-for="i in sites" :key="i.key" @click="siteClick(i)">{{ i.name }}</li>
</ul>
</div>
</div>
<div
class="zy-select"
@mouseleave="show.classList = false"
v-show="show.class"
>
<div
class="vs-placeholder"
@click="show.classList = true"
>{{type.name}}</div>
<div
class="vs-options"
v-show="show.classList"
>
<ul
class="zy-scroll"
style="max-height: 600px;"
>
<li
:class="type.tid === i.tid ? 'active' : ''"
v-for="i in classList"
:key="i.tid"
@click="classClick(i)"
>{{ i.name }}</li>
<div class="zy-select" @mouseleave="show.classList = false" v-show="show.class">
<div class="vs-placeholder" @click="show.classList = true">{{type.name}}</div>
<div class="vs-options" v-show="show.classList">
<ul class="zy-scroll" style="max-height: 600px;">
<li :class="type.tid === i.tid ? 'active' : ''" v-for="i in classList" :key="i.tid" @click="classClick(i)">{{ i.name }}</li>
</ul>
</div>
</div>
<div
class="zy-select"
@mouseleave="show.search = false"
>
<div
class="vs-input"
@click="show.search = true"
><input
v-model.trim="searchTxt"
type="text"
placeholder="搜索"
@keyup.enter="searchEvent(searchTxt)"
></div>
<div
class="vs-options"
v-show="show.search"
>
<ul
class="zy-scroll"
style="max-height: 600px"
>
<li
v-for="(i, j) in searchList"
:key="j"
@click="searchEvent(i.keywords)"
>{{i.keywords}}</li>
<li
v-show="searchList.length >= 1"
@click="clearSearch"
>清空历史记录</li>
<div class="zy-select" @mouseleave="show.search = false">
<div class="vs-input" @click="show.search = true"><input v-model.trim="searchTxt" type="text" placeholder="搜索" @keyup.enter="searchEvent(searchTxt)"></div>
<div class="vs-options" v-show="show.search">
<ul class="zy-scroll" style="max-height: 600px">
<li v-for="(i, j) in searchList" :key="j" @click="searchEvent(i.keywords)">{{i.keywords}}</li>
<li v-show="searchList.length >= 1" @click="clearSearch">清空历史记录</li>
</ul>
</div>
</div>
</div>
<div
class="body zy-scroll"
infinite-wrapper
>
<div
class="body-box"
v-show="!show.find"
>
<div
class="show-img"
v-if="setting.view === 'picture'"
>
<Waterfall
ref="waterfall"
:list="list"
:gutter="20"
:width="240"
:breakpoints="{ 1200: { rowPerView: 4 } }"
animationEffect="fadeInUp"
backgroundColor="rgba(0, 0, 0, 0)"
>
<template
slot="item"
slot-scope="props"
>
<div
class="card"
v-show="!setting.excludeR18Films || !containsR18Keywords(props.data.type)"
>
<div class="body zy-scroll" infinite-wrapper>
<div class="body-box" v-show="!show.find">
<div class="show-img" v-if="setting.view === 'picture'">
<Waterfall ref="waterfall" :list="list" :gutter="20" :width="240"
:breakpoints="{ 1200: { rowPerView: 4 } }"
animationEffect="fadeInUp"
backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props">
<div class="card" v-show="!setting.excludeR18Films || !containsR18Keywords(props.data.type)">
<div class="img">
<img
style="width: 100%"
:src="props.data.pic"
alt=""
@load="$refs.waterfall.refresh()"
@click="detailEvent(site, props.data)"
>
<img style="width: 100%" :src="props.data.pic" alt="" @load="$refs.waterfall.refresh()" @click="detailEvent(site, props.data)">
<div class="operate">
<div class="operate-wrap">
<span
class="o-play"
@click="playEvent(site, props.data)"
>播放</span>
<span
class="o-star"
@click="starEvent(site, props.data)"
>收藏</span>
<span
class="o-share"
@click="shareEvent(site, props.data)"
>分享</span>
<span class="o-play" @click="playEvent(site, props.data)">播放</span>
<span class="o-star" @click="starEvent(site, props.data)">收藏</span>
<span class="o-share" @click="shareEvent(site, props.data)">分享</span>
</div>
</div>
</div>
<div
class="name"
@click="detailEvent(site, props.data)"
>{{props.data.name}}</div>
<div class="name" @click="detailEvent(site, props.data)">{{props.data.name}}</div>
<div class="info">
<span>{{props.data.year}}</span>
<span>{{props.data.note}}</span>
@@ -152,94 +55,48 @@
</div>
</template>
</Waterfall>
<infinite-loading
force-use-infinite-wrapper
:identifier="infiniteId"
@infinite="infiniteHandler"
></infinite-loading>
<infinite-loading force-use-infinite-wrapper :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div>
<div
class="show-table"
v-if="setting.view === 'table'"
>
<div class="show-table" v-if="setting.view === 'table'">
<div class="zy-table">
<div class="tBody">
<ul>
<li
v-for="(i, j) in list"
:key="j"
@click="detailEvent(site, i)"
v-show="!setting.excludeR18Films || !containsR18Keywords(i.type)"
>
<li v-for="(i, j) in list" :key="j" @click="detailEvent(site, i)" v-show="!setting.excludeR18Films || !containsR18Keywords(i.type)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.year}}</span>
<span class="time">{{i.note}}</span>
<span class="note">{{i.note}}</span>
<span class="last">{{i.last}}</span>
<span class="operate">
<span
class="btn"
@click.stop="playEvent(site, i)"
>播放</span>
<span
class="btn"
@click.stop="starEvent(site, i)"
>收藏</span>
<span
class="btn"
@click.stop="shareEvent(site, i)"
>分享</span>
<span
class="btn"
@click.stop="downloadEvent(site, i)"
>下载</span>
<span class="btn" @click.stop="playEvent(site, i)">播放</span>
<span class="btn" @click.stop="starEvent(site, i)">收藏</span>
<span class="btn" @click.stop="shareEvent(site, i)">分享</span>
<span class="btn" @click.stop="downloadEvent(site, i)">下载</span>
</span>
</li>
</ul>
<infinite-loading
force-use-infinite-wrapper="tBody"
:identifier="infiniteId"
@infinite="infiniteHandler"
></infinite-loading>
<infinite-loading force-use-infinite-wrapper :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div>
</div>
</div>
</div>
<div
class="body-box"
v-show="show.find"
>
<div class="body-box" v-show="show.find">
<div class="show-table">
<div class="zy-table">
<div class="tBody zy-scroll">
<ul>
<li
v-for="(i, j) in searchContents"
:key="j"
@click="detailEvent(i.site, i)"
>
<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="type">{{i.type}}</span>
<span class="time">{{i.year}}</span>
<span class="note">{{i.note}}</span>
<span class="last">{{i.last}}</span>
<span class="operate">
<span
class="btn"
@click.stop="playEvent(i.site, i)"
>播放</span>
<span
class="btn"
@click.stop="starEvent(i.site, i)"
>收藏</span>
<span
class="btn"
@click.stop="shareEvent(i.site, i)"
>分享</span>
<span
class="btn"
@click.stop="downloadEvent(i.site, i)"
>下载</span>
<span class="btn" @click.stop="playEvent(i.site, i)">播放</span>
<span class="btn" @click.stop="starEvent(i.site, i)">收藏</span>
<span class="btn" @click.stop="shareEvent(i.site, i)">分享</span>
<span class="btn" @click.stop="downloadEvent(i.site, i)">下载</span>
</span>
</li>
</ul>
@@ -267,8 +124,6 @@ export default {
class: false,
classList: false,
search: false,
img: true,
table: false,
find: false
},
sites: [],
@@ -282,7 +137,7 @@ export default {
searchTxt: '',
searchContents: [],
// 福利片关键词
r18KeyWords: ['伦理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番']
r18KeyWords: ['伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番']
}
},
components: {
@@ -324,6 +179,9 @@ export default {
},
setting () {
return this.$store.getters.getSetting
},
sitesList () {
return this.$store.getters.getEditSites.sites // 需要监听的数据
}
},
watch: {
@@ -333,7 +191,7 @@ export default {
searchTxt () {
this.searchChangeEvent()
},
'$store.state.editSites.sites': function () {
sitesList () {
this.getAllsites()
}
},
@@ -356,6 +214,8 @@ export default {
}
})
}
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'site', 'change', e.name])
},
classClick (e) {
this.show.classList = false
@@ -366,6 +226,8 @@ export default {
this.infiniteId += 1
}
})
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'class', 'change', e.name])
},
getClass () {
return new Promise((resolve, reject) => {
@@ -452,37 +314,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 = {
@@ -532,13 +391,11 @@ export default {
},
changeView () {
if (this.view === 'Film') {
if (this.show.img) {
if (this.setting.view === 'picture') {
this.$refs.waterfall.refresh()
}
this.getPage().then(() => {
this.infiniteId += 1
if (this.show.img || this.show.table) {
}
})
}
},
@@ -547,6 +404,15 @@ export default {
this.searchList = res.reverse()
})
},
searchEvent (wd) {
if (this.setting.searchAllSites) {
this.searchAllSitesEvent(this.sites, wd)
} else {
this.searchSingleSiteEvent(this.site, wd)
}
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'film', 'search', wd])
},
searchAllSitesEvent (sites, wd) {
this.searchTxt = wd
this.searchContents = []
@@ -584,13 +450,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)
@@ -608,32 +467,39 @@ export default {
this.show.class = true
this.searchContents = []
this.show.find = false
if (this.show.img) {
if (this.setting.view === 'picture') {
this.$refs.waterfall.refresh()
}
}
},
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()
}
}
</script>
<style lang="scss" scoped>
.film {
.film{
height: calc(100% - 40px);
width: 100%;
display: flex;
flex-direction: column;
.header {
.header{
height: 30px;
width: 100%;
display: flex;
@@ -641,13 +507,13 @@ export default {
justify-content: space-between;
z-index: 10;
}
.body {
.body{
margin-top: 20px;
flex: 1;
width: 100%;
border-radius: 0 0 5px 5px;
overflow-y: scroll;
&::-webkit-scrollbar {
&::-webkit-scrollbar{
width: 5px;
height: 1px;
}
@@ -659,26 +525,26 @@ export default {
border-radius: 10px;
position: absolute;
}
.body-box {
.body-box{
height: 100%;
width: 100%;
}
.show-img {
.show-img{
height: 100%;
width: 100%;
padding: 10px;
.card {
.card{
border-radius: 6px;
overflow: hidden;
.img {
.img{
position: relative;
min-height: 40px;
img {
img{
width: 100%;
height: auto;
cursor: pointer;
}
.operate {
.operate{
display: none;
position: absolute;
left: 0;
@@ -686,12 +552,10 @@ export default {
background-color: #111111aa;
width: 100%;
font-size: 13px;
.operate-wrap {
.operate-wrap{
display: flex;
justify-content: space-between;
.o-play,
.o-star,
.o-share {
.o-play, .o-star, .o-share{
cursor: pointer;
display: inline-block;
width: 80px;
@@ -699,14 +563,14 @@ export default {
text-align: center;
line-height: 36px;
color: #cdcdcd;
&:hover {
&:hover{
background-color: #111;
}
}
}
}
}
.name {
.name{
font-size: 16px;
padding: 10px;
overflow: hidden;
@@ -714,14 +578,14 @@ export default {
white-space: nowrap;
cursor: pointer;
}
.info {
.info{
display: flex;
justify-content: space-between;
font-size: 12px;
padding: 10px;
}
&:hover {
.operate {
&:hover{
.operate{
display: block;
}
}

View File

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

View File

@@ -1,60 +1,45 @@
<template>
<div class="history">
<div class="body zy-scroll">
<div class="zy-table">
<div class="tHeader">
<span class="btn"></span>
<span
class="btn"
@click="clearAllHistory"
>清空</span>
</div>
<div class="tBody zy-scroll">
<ul>
<li v-show="this.history.length === 0">无数据</li>
<li v-show="this.history.length > 0">
<span class="name">名字</span>
<span class="site">片源</span>
<span class="note">观看至</span>
<span class="operate">
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
</span>
</li>
<li
v-for="(i, j) in history"
:key="j"
@click="historyItemEvent(i)"
>
<span
class="name"
@click.stop="detailEvent(i)"
>{{i.name}}</span>
<span class="site">{{getSiteName(i.site)}}</span>
<span class="note">{{i.index+1}}</span>
<span class="operate">
<span
class="btn"
@click.stop="playEvent(i)"
>播放</span>
<span
class="btn"
@click.stop="shareEvent(i)"
>分享</span>
<span
class="btn"
@click.stop="downloadEvent(i)"
>下载</span>
<span
class="btn"
@click.stop="removeHistoryItem(i)"
>删除</span>
</span>
</li>
</ul>
</div>
<div class="listpage" id="history">
<div class="listpage-content">
<div class="listpage-header">
<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 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>
@@ -63,12 +48,16 @@
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 {
name: 'history',
data () {
return {
history: history,
history: [],
sites: []
}
},
@@ -124,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) {
@@ -183,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 = []
@@ -204,44 +230,40 @@ export default {
return site.name
}
},
historyItemEvent (e) {
this.video = {
key: e.site,
info: {
id: e.ids,
name: e.name,
type: e.type,
year: e.year,
index: e.index,
time: e.time
}
}
},
removeHistoryItem (e) {
history.remove(e.id).then(res => {
this.getAllhistory()
}).catch(err => {
this.$message.warning('删除历史记录失败, 错误信息: ' + err)
})
},
updateDatabase (data) {
history.clear().then(res => {
var id = length
data.forEach(ele => {
ele.id = id
id -= 1
history.add(ele)
})
})
},
rowDrop () {
const tbody = document.getElementById('history-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.history.splice(oldIndex, 1)[0]
_this.history.splice(newIndex, 0, currRow)
_this.updateDatabase(_this.history)
}
})
}
},
mounted () {
this.rowDrop()
},
created () {
this.getAllhistory()
}
}
</script>
<style lang="scss" scoped>
.history {
position: relative;
height: calc(100% - 40px);
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
.body {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -1,107 +1,69 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="exportSites"
>导出</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="importSites"
>导入</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="removeAllSites"
>清空</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="resetSites"
>重置</div>
</div>
<div class="zy-select">
</div>
<div class="listpage" id="IPTV">
<div class="listpage-content">
<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="detail-header">
<div>
<div
class="vs-placeholder vs-noAfter"
@click="exportSites"
>总频道数:{{iptvList.length}}</div>
</div>
<div
class="zy-select"
@mouseleave="show.search = false"
>
<div
class="vs-input"
@click="show.search = true"
><input
v-model.trim="searchTxt"
type="text"
<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
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="搜索"
@keyup.enter="searchEvent(searchTxt)"
></div>
<div
class="vs-options"
v-show="show.search"
>
<ul
class="zy-scroll"
style="max-height: 600px"
>
<li
v-for="(i, j) in searchList"
:key="j"
@click="searchEvent(i.keywords)"
>{{i.keywords}}</li>
<li
v-show="searchList.length >= 1"
@click="clearSearch"
>清空历史记录</li>
</ul>
</div>
</div>
</div>
<div class="detail-body zy-scroll">
<div class="zy-table">
<div class="tBody zy-scroll">
<ul>
<draggable
v-model="iptvList"
@change="listUpdatedEvent"
>
<transition-group>
<li
v-for="(i, j) in iptvList"
:key="j"
@click.stop="playEvent(i)"
v-show="containsearchTxt(i)"
>
<span class="name">{{i.name}}</span>
<span class="operate">
<span
class="btn"
@click.stop="playEvent(i)"
>播放</span>
<span
class="btn"
@click.stop="removeEvent(i)"
>删除</span>
</span>
</li>
</transition-group>
</draggable>
</ul>
</div>
</div>
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>
</div>
@@ -109,25 +71,25 @@
<script>
import { mapMutations } from 'vuex'
import { iptv, iptvSearch } from '../lib/dexie'
import draggable from 'vuedraggable'
import { iptv as defaultSites } from '../lib/dexie/initData'
import { remote } from 'electron'
import fs from 'fs'
import Sortable from 'sortablejs'
export default {
name: 'iptv',
data () {
return {
iptvList: [],
searchTxt: '',
searchList: [],
searchRecordList: [],
enableBatchEdit: false,
batchGroupName: '',
multipleSelection: [],
show: {
search: false
}
}
},
components: {
draggable
},
computed: {
view: {
get () {
@@ -147,21 +109,60 @@ export default {
set (val) {
this.SET_VIDEO(val)
}
},
filteredTableData () {
if (this.searchTxt) {
return this.iptvList.filter(x => x.name.toLowerCase().includes(this.searchTxt.toLowerCase()))
} else {
return this.iptvList
}
},
getFilters () {
const groups = [...new Set(this.iptvList.map(iptv => iptv.group))]
var filters = []
groups.forEach(g => {
var doc = {
text: g,
value: g
}
filters.push(doc)
})
return filters
}
},
watch: {
view () {
this.getAllSites()
this.getChannels()
},
searchTxt () {
}
},
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'
},
filterHandle (value, row) {
return row.group === value
},
containsearchTxt (i) {
if (this.searchTxt) {
return i.name.toLowerCase().includes(this.searchTxt.toLowerCase())
@@ -171,7 +172,7 @@ export default {
},
removeEvent (e) {
iptv.remove(e.id).then(res => {
this.getAllSites()
this.getChannels()
}).catch(err => {
this.$message.warning('删除频道失败, 错误信息: ' + err)
})
@@ -187,7 +188,7 @@ export default {
})
})
},
exportSites () {
exportChannels () {
const options = {
filters: [
{ name: 'm3u file', extensions: ['m3u'] },
@@ -205,7 +206,7 @@ export default {
this.$message.success('已保存成功')
} else {
const arr = [...this.iptvList]
const str = JSON.stringify(arr, null, 4)
const str = JSON.stringify(arr, null, 2)
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
@@ -214,65 +215,104 @@ export default {
this.$message.error(err)
})
},
importSites () {
importChannels () {
const options = {
filters: [
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] }
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] },
{ name: 'JSON file', extensions: ['json'] }
],
properties: ['openFile', 'multiSelections']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
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.endsWith('m3u8')) {
var doc = {
name: ele.name,
url: ele.url
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)
}
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.getAllSites()
this.getChannels()
this.$message.success('导入成功')
})
})
}
})
},
resetSites () {
iptv.clear()
iptv.bulkAdd(defaultSites).then(e => {
this.getAllSites()
})
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 '其他'
}
},
removeAllSites () {
resetChannelsEvent () {
this.resetChannels(defaultSites)
},
resetChannels (newSites) {
this.resetId(newSites)
iptv.clear().then(iptv.bulkAdd(newSites).then(this.getChannels()))
},
removeAllChannels () {
iptv.clear().then(res => {
this.getAllSites()
this.getChannels()
})
},
getAllSites () {
getChannels () {
iptv.all().then(res => {
this.iptvList = res
})
},
getAllSearch () {
getSearchRecordList () {
iptvSearch.all().then(res => {
this.searchList = res.reverse()
this.searchRecordList = res.reverse()
})
},
clearSearch () {
iptvSearch.clear().then(res => {
this.getAllSearch()
this.getSearchRecordList()
})
},
searchEvent (wd) {
@@ -283,47 +323,51 @@ export default {
if (!res) {
iptvSearch.add({ keywords: wd })
}
this.getAllSearch()
this.getSearchRecordList()
})
}
},
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()
},
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(this.iptvList)
iptv.bulkAdd(this.iptvList)
})
},
resetId (inArray) {
var id = 1
inArray.forEach(ele => {
ele.id = id
id += 1
})
},
rowDrop () {
const tbody = document.getElementById('iptv-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.iptvList.splice(oldIndex, 1)[0]
_this.iptvList.splice(newIndex, 0, currRow)
_this.updateDatabase()
}
})
}
},
mounted () {
this.rowDrop()
},
created () {
this.getAllSites()
this.getAllSearch()
this.getChannels()
this.getSearchRecordList()
}
}
</script>
<style lang="scss" scoped>
.detail {
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content {
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title {
font-size: 16px;
}
.detail-close {
cursor: pointer;
}
}
}
.detail-body {
height: calc(100% - 50px);
overflow-y: auto;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +1,22 @@
<template>
<div class="setting">
<div class="setting-box zy-scroll">
<div class="logo"><img
src="@/assets/image/logo.png"
alt=""
></div>
<div class="logo"><img src="@/assets/image/logo.png" alt=""></div>
<div class="info">
<a @click="linkOpen('http://zyplayer.fun/')">官网</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player')">Github</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">当前版本v{{pkg.version}} 反馈</a>
<a
style="color:#38dd77"
@click="linkOpen('https://github.com/Hunlongyu/ZY-Player/releases/tag/v' + latestVersion)"
v-show="latestVersion !== pkg.version"
>最新版本v{{latestVersion}}</a>
<a style="color:#38dd77" @click="quitAndInstall()" v-show="latestVersion !== pkg.version" >最新版本v{{latestVersion}}</a>
</div>
<div class="view">
<div class="title">视图</div>
<div class="view-box">
<div
class="zy-select"
@mouseleave="show.view = false"
>
<div
class="vs-placeholder"
@click="show.view = true"
>默认视图</div>
<div
class="vs-options"
v-show="show.view"
>
<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>
<li :class="d.view === 'picture' ? 'active' : ''" @click="changeView('picture')">海报</li>
<li :class="d.view === 'table' ? 'active' : ''" @click="changeView('table')">列表</li>
</ul>
</div>
</div>
@@ -47,176 +25,97 @@
<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"
>
<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>
<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 class="vs-placeholder vs-noAfter" @click="expShortcut">导出</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="impShortcut"
>导入</div>
<div class="vs-placeholder vs-noAfter" @click="impShortcut">导入</div>
</div>
</div>
</div>
<div class="shortcut">
<div class="title">缓存</div>
<div class="shortcut-box">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="clearCache">清理视频缓存</div>
</div>
</div>
</div>
<div class="site">
<div class="title">定位时间设置</div>
<div class="zy-input">
/右方向键:<input
style="width:50px"
type="number"
v-model="d.forwardTimeInSec"
@change="updateSettingEvent"
>
/右方向键:<input style="width:50px" type="number" v-model = "d.forwardTimeInSec" @change="updateSettingEvent">
</div>
</div>
<div class='search'>
<div class="title">搜索</div>
<div
class="zy-input"
@click="toggleSearchAllSites"
>
<input
type="checkbox"
v-model="d.searchAllSites"
@change="updateSettingEvent"
> 搜索所有资源
</div>
<div class="title">搜索</div>
<div class="zy-input" @click="toggleSearchAllSites">
<input type="checkbox" v-model = "d.searchAllSites" @change="updateSettingEvent"> 搜索所有资源
</div>
</div>
<div class='site'>
<div class="title">第三方播放</div>
<div class="site-box">
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="selectLocalPlayer"
>选择本地播放器</div>
</div>
<div
class="zy-select"
@click="editPlayerPath = true"
>
<div
class="vs-placeholder vs-noAfter"
v-show="editPlayerPath == false"
>
<label>编辑</label>
<div class="title">第三方播放</div>
<div class="site-box">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="selectLocalPlayer">选择本地播放器</div>
</div>
<div class="zy-select" @click = "editPlayerPath = true">
<div class="vs-placeholder vs-noAfter" v-show = "editPlayerPath == false">
<label>编辑</label>
</div>
<input class="zy-input" v-show = "editPlayerPath == true" v-model = "d.externalPlayer"
@blur= "updateSettingEvent"
@keyup.enter = "updateSettingEvent">
</div>
<input
class="zy-input"
v-show="editPlayerPath == true"
v-model="d.externalPlayer"
@blur="updateSettingEvent"
@keyup.enter="updateSettingEvent"
>
</div>
</div>
</div>
<div class="site">
<div class="title">源管理</div>
<div class="site-box">
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="editSitesEvent"
>编辑源</div>
<div class="vs-placeholder vs-noAfter" @click="editSitesEvent">编辑源</div>
</div>
<div
class="zy-input"
@click="toggleExcludeRootClasses"
>
<input
type="checkbox"
v-model="d.excludeRootClasses"
@change="updateSettingEvent"
> 屏蔽主分类
</div>
<div
class="zy-input"
@click="toggleExcludeR18Films"
>
<input
type="checkbox"
v-model="d.excludeR18Films"
@change="updateSettingEvent"
> 屏蔽福利片
<div class="zy-input" @click="toggleExcludeRootClasses">
<input type="checkbox" v-model = "d.excludeRootClasses" @change="updateSettingEvent"> 屏蔽主分类
</div>
<div class="zy-input" @click="toggleExcludeR18Films">
<input type="checkbox" v-model = "d.excludeR18Films" @change="updateSettingEvent"> 屏蔽福利片
</div>
</div>
</div>
<div class="theme">
<div class="title">主题</div>
<div class="theme-box">
<div
@click="changeTheme('light')"
class="theme-item light"
>
<div @click="changeTheme('light')" class="theme-item light">
<div class="theme-image">
<img
src="../assets/image/light.png"
alt=""
>
<img src="../assets/image/light.png" alt="">
</div>
<div class="theme-name">Light</div>
</div>
<div
@click="changeTheme('dark')"
class="theme-item dark"
>
<div @click="changeTheme('dark')" class="theme-item dark">
<div class="theme-image">
<img
src="../assets/image/dark.png"
alt=""
>
<img src="../assets/image/dark.png" alt="">
</div>
<div class="theme-name">Dark</div>
</div>
<div
@click="changeTheme('green')"
class="theme-item green"
>
<div @click="changeTheme('green')" class="theme-item green">
<div class="theme-image">
<img
src="../assets/image/green.png"
alt=""
>
<img src="../assets/image/green.png" alt="">
</div>
<div class="theme-name">Green</div>
</div>
<div
@click="changeTheme('pink')"
class="theme-item pink"
>
<div @click="changeTheme('pink')" class="theme-item pink">
<div class="theme-image">
<img
src="../assets/image/pink.png"
alt=""
>
<img src="../assets/image/pink.png" alt="">
</div>
<div class="theme-name">Pink</div>
</div>
@@ -225,25 +124,12 @@
<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"
>
<img
class="qrcode-item"
src="../assets/image/wepay_cuiocean.jpg"
>
<img class="qrcode-item" src="../assets/image/wepay-hunlongyu.png">
<img class="qrcode-item" src="../assets/image/wepay_cuiocean.jpg">
</div>
</div>
<div class="clearDB">
<span
@click="clearDBEvent"
class="clearBtn"
>软件重置</span>
<span @click="clearDBEvent" class="clearBtn">软件重置</span>
<span class="clearTips">如果新安装用户, 无法显示资源, 请点击软件重置. 如非必要, 切勿点击. 会清空用户数据, 恢复默认设置. 点击即软件重置, 并关闭软件.</span>
</div>
<div class="Tips">
@@ -255,9 +141,11 @@
<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'
const _hmt = window._hmt
export default {
name: 'setting',
data () {
@@ -265,7 +153,6 @@ export default {
pkg: pkg,
sitesList: [],
shortcutList: [],
favoritesList: [],
show: {
site: false,
shortcut: false,
@@ -292,6 +179,14 @@ export default {
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
setting: {
get () {
return this.$store.getters.getSetting
@@ -310,7 +205,7 @@ export default {
}
},
methods: {
...mapMutations(['SET_SETTING', 'SET_EDITSITES']),
...mapMutations(['SET_SETTING', 'SET_VIEW', 'SET_EDITSITES']),
linkOpen (e) {
shell.openExternal(e)
},
@@ -327,11 +222,20 @@ export default {
excludeR18Films: res.excludeR18Films,
forwardTimeInSec: res.forwardTimeInSec
}
this.setting = this.d
})
},
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 () {
@@ -339,17 +243,18 @@ export default {
this.shortcutList = res
})
},
getFavorites () {
star.all().then(res => {
this.favoritesList = res
})
},
changeView (e) {
this.d.view = e
setting.update(this.d).then(res => {
this.$message.success('修改成功')
this.show.view = false
})
this.updateSettingEvent()
this.show.view = false
},
async clearCache () {
const win = remote.getCurrentWindow()
const ses = win.webContents.session
const size = await ses.getCacheSize() / 1024 / 1024
const mb = size.toFixed(2)
await ses.clearCache()
this.$message.success(`清除缓存成功, 共清理 ${mb} MB`)
},
updateSettingEvent () {
this.editPlayerPath = false
@@ -400,14 +305,12 @@ export default {
this.updateSettingEvent()
},
editSitesEvent () {
this.editSites = {
show: true,
sites: this.sitesList
}
this.view = 'EditSites'
},
changeTheme (e) {
this.d.theme = e
this.updateSettingEvent()
_hmt.push(['_trackEvent', 'setting', 'theme', e])
},
changeShortcut (e) {
this.d.shortcut = e
@@ -416,7 +319,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('已复制到剪贴板')
},
@@ -449,14 +352,29 @@ 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 () {
ipcRenderer.send('quitAndInstall')
},
createContextMenu () {
const { Menu, MenuItem } = remote
const menu = new Menu()
menu.append(new MenuItem({ label: '快速复制', role: 'copy' }))
menu.append(new MenuItem({ label: '快速粘贴', role: 'paste' }))
menu.append(new MenuItem({ label: '编辑', role: 'editMenu' }))
window.addEventListener('contextmenu', e => {
e.preventDefault()
menu.popup(remote.getCurrentWindow())
})
}
},
@@ -464,121 +382,121 @@ export default {
this.getSites()
this.getSetting()
this.getShortcut()
this.getFavorites()
this.getLatestVersion()
this.createContextMenu()
}
}
</script>
<style lang="scss" scoped>
.setting {
.setting{
height: calc(100% - 40px);
width: 100%;
border-radius: 5px;
padding: 20px 0;
.setting-box {
.setting-box{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.logo {
.logo{
margin-top: 10px;
width: 100%;
text-align: center;
img {
img{
width: 120px;
height: auto;
}
}
.info {
.info{
width: 100%;
margin-top: 20px;
text-align: center;
a {
a{
text-decoration: none;
margin: 0 10px;
font-size: 14px;
cursor: pointer;
}
}
.view {
.view{
width: 100%;
padding: 20px;
margin-top: 20px;
.view-box {
.view-box{
margin-top: 10px;
.zy-select {
.zy-select{
margin-right: 20px;
}
}
}
.search {
.search{
width: 100%;
padding: 20px;
margin-top: 20px;
}
.site {
.site{
width: 100%;
padding: 20px;
margin-top: 20px;
.site-box {
.site-box{
margin-top: 10px;
.zy-select {
.zy-select{
margin-right: 20px;
}
}
}
.shortcut {
.shortcut{
width: 100%;
padding: 20px;
margin-top: 20px;
.shortcut-box {
.shortcut-box{
margin-top: 10px;
.zy-select {
.zy-select{
margin-right: 20px;
}
}
}
.theme {
.theme{
width: 100%;
padding-left: 20px;
margin-top: 20px;
.theme-box {
.theme-box{
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
margin-top: 10px;
.theme-item {
.theme-item{
width: 200px;
height: 180px;
margin-right: 20px;
cursor: pointer;
border-radius: 2px;
.theme-image {
.theme-image{
width: 180px;
margin: 10px auto;
img {
img{
width: 100%;
}
}
.theme-name {
.theme-name{
width: 100%;
text-align: center;
}
}
}
}
.qrcode {
.qrcode{
width: 100%;
padding-left: 20px;
margin-top: 20px;
margin-bottom: 20px;
.qrcode-box {
.qrcode-box{
display: flex;
justify-content: flex-start;
margin-top: 10px;
.qrcode-item {
.qrcode-item{
width: auto;
height: 300px;
margin-right: 20px;
@@ -586,10 +504,10 @@ export default {
}
}
}
.clearDB {
.clearDB{
margin-top: 20px;
margin-bottom: 20px;
.clearBtn {
.clearBtn{
margin-left: 20px;
color: red;
cursor: pointer;
@@ -601,13 +519,13 @@ export default {
text-align: center;
line-height: 32px;
}
.clearTips {
.clearTips{
font-size: 12px;
color: #ff000088;
margin-left: 10px;
}
}
.Tips {
.Tips{
margin: 20px;
font-size: 12px;
color: #ff000066;

View File

@@ -1,37 +1,18 @@
<template>
<div
class="share"
id="share"
@click="shareClickEvent"
>
<div class="share" id="share" @click="shareClickEvent">
<div class="left">
<img
:src="pic"
alt=""
@load="picLoadEvent"
>
<img :src="pic" alt="" @load="picLoadEvent">
</div>
<div
class="right"
id="right"
>
<div class="right" id="right">
<div class="title">{{ share.info.name }}</div>
<qrcode-vue
id="qr"
:value="link"
:size="160"
level="L"
/>
<qrcode-vue id="qr" :value="link" :size="160" level="L" />
<div class="tips">
<p>长按二维码识别播放</p>
<p><img src="@/assets/image/logo.png"></p>
<p class="zy">ZY Player技术支持严禁传播违法资源</p>
</div>
</div>
<div
class="share-mask"
v-show="loading"
>
<div class="share-mask" v-show="loading">
<div class="loader"></div>
</div>
</div>
@@ -88,6 +69,8 @@ export default {
const id = this.share.info.ids || this.share.info.id
zy.detail(this.share.key, id).then(res => {
if (res) {
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'film', 'share', res.name])
this.pic = res.pic
var m3u8List = {}
const dd = res.dl.dd
@@ -108,7 +91,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)
@@ -123,7 +106,7 @@ export default {
}
</script>
<style lang="scss" scoped>
.share {
.share{
position: absolute;
bottom: 20px;
right: 20px;
@@ -135,45 +118,44 @@ export default {
align-items: center;
padding: 0px;
z-index: 999;
.left,
.right {
.left, .right{
width: 50%;
height: 100%;
}
.left {
.left{
display: flex;
justify-content: center;
align-items: center;
img {
img{
height: 320px;
width: auto;
max-width: 240px;
}
}
.right {
.right{
padding: 10px;
.title {
.title{
font-size: 18px;
margin-bottom: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#qr {
#qr{
text-align: center;
}
.tips {
.tips{
font-size: 14px;
text-align: center;
img {
img{
width: 50px;
}
.zy {
.zy{
font-size: 12px;
}
}
}
.share-mask {
.share-mask{
position: absolute;
top: 0;
left: 0;
@@ -196,37 +178,28 @@ export default {
@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;
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;
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;
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;
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;
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;
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;
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;
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
}

View File

@@ -1,104 +1,77 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="exportFavoritesEvent"
>
导出
</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="importFavoritesEvent"
>
导入
</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="clearFavoritesEvent"
>
清空
</div>
</div>
<div class="zy-select">
<div
class="vs-placeholder vs-noAfter"
@click="updateAllEvent"
>
同步所有收藏
</div>
</div>
<div class="listpage" id="star">
<div class="listpage-content">
<div class="listpage-header">
<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="detail-body zy-scroll">
<div class="zy-table">
<div class="tBody zy-scroll">
<ul>
<li v-show="this.list.length > 0">
<span class="name">名字</span>
<span class="type">类型</span>
<span class="time">上映</span>
<span class="site">片源</span>
<span class="note">备注</span>
<span class="note">观看至</span>
<span class="operate">
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
<span class="btn"></span>
</span>
</li>
<draggable
v-model="list"
@change="listUpdatedEvent"
>
<transition-group>
<li
v-for="(i, j) in list"
:key="j"
@click="detailEvent(i)"
:class="[i.hasUpdate ? 'zy-highlighted' : '']"
>
<span class="name">{{ i.name }}</span>
<span class="type">{{ i.type }}</span>
<span class="time">{{ i.year }}</span>
<span class="site">{{ getSiteName(i.key) }}</span>
<span class="note">{{ i.note }}</span>
<span class="note">{{ getHistoryNote(i.index) }}</span>
<span class="operate">
<span
class="btn"
@click.stop="playEvent(i)"
>播放</span>
<span
class="btn"
@click.stop="shareEvent(i)"
>分享</span>
<span
class="btn"
@click.stop="updateEvent(i)"
>同步</span>
<span
class="btn"
@click.stop="downloadEvent(i)"
>下载</span>
<span
class="btn"
@click.stop="deleteEvent(i)"
>删除</span>
</span>
</li>
</transition-group>
</draggable>
</ul>
</div>
</div>
<div class="listpage-body" id="star-table">
<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>
@@ -107,10 +80,9 @@
import { mapMutations } from 'vuex'
import { star, history, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import draggable from 'vuedraggable'
import { remote } from 'electron'
import fs from 'fs'
import Sortable from 'sortablejs'
const { clipboard } = require('electron')
export default {
name: 'star',
@@ -120,9 +92,6 @@ export default {
sites: []
}
},
components: {
draggable
},
computed: {
view: {
get () {
@@ -159,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,
@@ -178,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)
}
@@ -208,35 +193,33 @@ export default {
info: e
}
},
clearHasUpdateFlag (e) {
star.find({ id: e.id }).then(res => {
res.hasUpdate = false
star.update(e.id, res)
this.getFavorites()
})
checkUpdate ({ row, rowIndex }) {
if (this.list[rowIndex].hasUpdate) {
return 'highlight'
}
},
listUpdatedEvent () {
star.clear().then(res1 => {
// 重新排序
var id = this.list.length
this.list.forEach(element => {
element.id = id
star.add(element)
id -= 1
})
})
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
@@ -329,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'] },
@@ -357,84 +340,76 @@ 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()
this.$message.success('清空所有收藏成功')
})
},
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 = this.list.length
this.list.forEach(ele => {
ele.id = id
id -= 1
})
star.bulkAdd(this.list)
})
},
rowDrop () {
const tbody = document.getElementById('star-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.list.splice(oldIndex, 1)[0]
_this.list.splice(newIndex, 0, currRow)
_this.updateDatabase()
}
})
}
},
mounted () {
this.rowDrop()
},
created () {
this.getFavorites()
window.Sortable = require('sortablejs').Sortable
}
}
</script>
<style lang="scss" scoped>
.detail {
position: absolute;
left: 80px;
right: 20px;
bottom: 0;
width: calc(100% - 100px);
height: calc(100% - 40px);
z-index: 888;
.detail-content {
height: calc(100% - 10px);
padding: 0 60px;
position: relative;
.detail-header {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
.detail-title {
font-size: 16px;
}
.detail-close {
cursor: pointer;
}
}
}
.detail-body {
height: calc(100% - 50px);
overflow-y: auto;
}
}
</style>

View File

@@ -3,16 +3,16 @@ 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'
iptv: '++id, name, url, group'
})
db.on('populate', () => {

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

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,3 +1,14 @@
import Vue from 'vue'
import { Message } from 'element-ui'
import { Message, Button, Table, TableColumn, Tag, Input, Dialog, Form, FormItem, Switch } 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.prototype.$message = Message

View File

@@ -3,6 +3,36 @@ import axios from 'axios'
import cheerio from 'cheerio'
const onlineVideo = {
playVideoOnline (selectedOnlineSite, videoName, videoIndex) {
switch (selectedOnlineSite) {
case '哔嘀':
onlineVideo.playVideoOnBde4(videoName, videoIndex)
break
case '1080影视':
onlineVideo.playVideoOnK1080(videoName, videoIndex)
break
case '素白白':
onlineVideo.playVideoOnSubaibai(videoName, videoIndex)
break
case '哆咪动漫':
onlineVideo.playVideoOndmdm2020(videoName, videoIndex)
break
case '樱花动漫':
onlineVideo.playVideoOnYhdm(videoName, videoIndex)
break
case '简影':
onlineVideo.playVideoOnSyrme(videoName, videoIndex)
break
case '极品':
onlineVideo.playVideoOnJpysvip(videoName, videoIndex)
break
case '喜欢看':
onlineVideo.playVideoOnXhkan(videoName, videoIndex)
break
default:
this.$message.console.error(`不支持该网站:${this.selectedOnlineSite}`)
}
},
playVideoOnBde4 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://bde4.com/search/${videoName}`
@@ -214,6 +244,74 @@ const onlineVideo = {
})
}
})
},
playVideoOnJpysvip (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://www.jpysvip.net/vodsearch/-------------.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('ul>li>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'https://www.jpysvip.net' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
videoFullLink = 'https://www.jpysvip.net/' + indexVideoLink
}
open(videoFullLink)
})
}
})
},
playVideoOnXhkan (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://www.xhkan.com/vodsearch.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('ul>li>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
videoFullLink = indexVideoLink
}
open(videoFullLink)
})
}
})
}
}
export default onlineVideo

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

@@ -4,6 +4,7 @@ import store from './store'
import 'modern-normalize'
import Register from './components/register'
import './lib/element/index'
Register.registerComponents()
Vue.config.productionTip = false
new Vue({

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
@@ -395,6 +395,8 @@ export default {
this.xg = new Hls(this.config)
this.mtEvent()
this.getUrls()
const _hmt = window._hmt
_hmt.push(['_trackEvent', 'page', 'view', 'mini'])
},
beforeDestroy () {
clearInterval(this.timer)

View File

@@ -27,7 +27,6 @@ export default new Vuex.Store({
info: {}
},
editSites: {
show: false,
sites: []
}
},

179
yarn.lock
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"
@@ -1809,12 +1814,12 @@ aws4@^1.8.0:
resolved "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
axios@^0.19.2:
version "0.19.2"
resolved "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
axios@^0.20.0:
version "0.20.0"
resolved "https://registry.npm.taobao.org/axios/download/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
integrity sha1-BXujDwSIRpSZOozQf6OUz/EcUL0=
dependencies:
follow-redirects "1.5.10"
follow-redirects "^1.10.0"
babel-eslint@^10.1.0:
version "10.1.0"
@@ -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"
@@ -3175,10 +3188,10 @@ d@1, d@^1.0.1:
es5-ext "^0.10.50"
type "^1.0.1"
danmu.js@^0.2.17:
version "0.2.18"
resolved "https://registry.npmjs.org/danmu.js/-/danmu.js-0.2.18.tgz#fd688e7df50367a8a68630d1a189a0132c08dd9c"
integrity sha512-ALfB4o0eQoK5pceEQCwyHmmg2wcxOOGqOflxodVtuc9Prq9Fyb2MAiyrfA+q/4WV1E4CnjoCep/8YHKy1X9IQg==
danmu.js@0.2.23:
version "0.2.23"
resolved "https://registry.npm.taobao.org/danmu.js/download/danmu.js-0.2.23.tgz#339b512152ed5a8253829be007c5be5bd3821856"
integrity sha1-M5tRIVLtWoJTgpvgB8W+W9OCGFY=
dependencies:
event-emitter "^0.3.5"
@@ -3201,13 +3214,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.1, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -3382,10 +3388,10 @@ detect-node@^2.0.4:
resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
dexie@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/dexie/-/dexie-3.0.1.tgz#faafeb94be0d5e18b25d700546a2c05725511cfc"
integrity sha512-/s4KzlaerQnCad/uY1ZNdFckTrbdMVhLlziYQzz62Ff9Ick1lHGomvTXNfwh4ApEZATyXRyVk5F6/y8UU84B0w==
dexie@^3.0.2:
version "3.0.2"
resolved "https://registry.npm.taobao.org/dexie/download/dexie-3.0.2.tgz#4b979904d739e0530b68352005f175a82633a075"
integrity sha1-S5eZBNc54FMLaDUgBfF1qCYzoHU=
diffie-hellman@^5.0.0:
version "5.0.3"
@@ -3664,10 +3670,23 @@ 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@^9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-9.3.1.tgz#e301932c5c0537d8c9a8850d216d3ba454dbf55c"
integrity sha512-DScrhqBT4a54KfdF0EoipALpHmdQTn3m7SSCtbpTcEcG+UDUiXad2cOfW6DHeVH7N+CVDKDG12q2PhVJjXkFAA==
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"
integrity sha1-VGLF+sW0coaRBC0PYhM+osEz5v0=
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"
@@ -4121,6 +4140,11 @@ eventemitter3@^4.0.0:
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==
eventemitter3@^4.0.7:
version "4.0.7"
resolved "https://registry.npm.taobao.org/eventemitter3/download/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha1-Lem2j2Uo1WRO9cWVJqG0oHMGFp8=
events@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
@@ -4528,18 +4552,16 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
follow-redirects@^1.0.0:
version "1.12.1"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6"
integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==
follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha1-tC6Nk6Kn7qXtiGM2dtZZe8jjhNs=
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -5081,10 +5103,10 @@ html-webpack-plugin@^3.2.0:
toposort "^1.0.0"
util.promisify "1.0.0"
html2canvas@^1.0.0-rc.5:
version "1.0.0-rc.5"
resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-rc.5.tgz#4ee3cac9f6e20a0fa0c2f35a6f99c960ae7ec4c1"
integrity sha512-DtNqPxJNXPoTajs+lVQzGS1SULRI4GQaROeU5R41xH8acffHukxRh/NBVcTBsfCkJSkLq91rih5TpbEwUP9yWA==
html2canvas@^1.0.0-rc.7:
version "1.0.0-rc.7"
resolved "https://registry.npm.taobao.org/html2canvas/download/html2canvas-1.0.0-rc.7.tgz#70c159ce0e63954a91169531894d08ad5627ac98"
integrity sha1-cMFZzg5jlUqRFpUxiU0IrVYnrJg=
dependencies:
css-line-break "1.1.1"
@@ -6070,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"
@@ -6159,6 +6186,13 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
m3u@0.0.2:
version "0.0.2"
resolved "https://mirrors.huaweicloud.com/repository/npm/m3u/-/m3u-0.0.2.tgz#cb971743b434efd8c77b3cc3a47fc0411a903df7"
integrity sha1-y5cXQ7Q079jHezzDpH/AQRqQPfc=
dependencies:
oop "0.0.3"
make-dir@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -6433,10 +6467,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
modern-normalize@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/modern-normalize/-/modern-normalize-0.6.0.tgz#21a469988edfc7c9020348e7a3b5b3583a1c9406"
integrity sha512-gzvL1uFLV4EErHhaKoqTcrx52/sea6AIcMFWz/GBCuFrDBp485wSgyX5M+MZsj+grfcuqYLQGfHjDRrCvvwZLw==
modern-normalize@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/modern-normalize/download/modern-normalize-1.0.0.tgz#539d84a1e141338b01b346f3e27396d0ed17601e"
integrity sha1-U52EoeFBM4sBs0bz4nOW0O0XYB4=
mousetrap@^1.6.5:
version "1.6.5"
@@ -6812,6 +6846,11 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
oop@0.0.3:
version "0.0.3"
resolved "https://mirrors.huaweicloud.com/repository/npm/oop/-/oop-0.0.3.tgz#70fa405a5650891a194fdc82ca68dad6dabf4401"
integrity sha1-cPpAWlZQiRoZT9yCymja1tq/RAE=
open@^6.3.0:
version "6.4.0"
resolved "https://registry.npmjs.org/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9"
@@ -9572,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"
@@ -9675,10 +9719,10 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.6.11:
version "2.6.11"
resolved "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080"
integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==
vue-template-compiler@^2.6.12:
version "2.6.12"
resolved "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.12.tgz?cache=0&sync_timestamp=1602239010008&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-template-compiler%2Fdownload%2Fvue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
integrity sha1-lH7XGWdEyKUoXr4SM/6WBDf8xX4=
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -9688,27 +9732,27 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue-waterfall-plugin@^1.0.7:
version "1.0.7"
resolved "https://registry.npmjs.org/vue-waterfall-plugin/-/vue-waterfall-plugin-1.0.7.tgz#bd351e8d3015b9ce71320e00a88443929d63465f"
integrity sha512-60JlBvrQbIOapGwc9AW9cXV064uzIEV1bjzc1ApNGt+MI4PI0Xs9aDvFgRMsGdynV9UlsLNtplX0bRwJ1syT9A==
vue-waterfall-plugin@^1.1.0:
version "1.1.0"
resolved "https://registry.npm.taobao.org/vue-waterfall-plugin/download/vue-waterfall-plugin-1.1.0.tgz#a4d87c74b72f1e36de5bbbadf1d645eb549ed592"
integrity sha1-pNh8dLcvHjbeW7ut8dZF61Se1ZI=
vue@^2.6.11:
version "2.6.11"
resolved "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
vue@^2.6.12:
version "2.6.12"
resolved "https://registry.npm.taobao.org/vue/download/vue-2.6.12.tgz?cache=0&sync_timestamp=1602778254831&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
integrity sha1-9evU+mvShpQD4pqJau1JBEVskSM=
vuedraggable@^2.24.1:
version "2.24.1"
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.1.tgz#304abd7644dde05c1f199a227bf9e9107f56197a"
integrity sha512-G1fxO1oshx+WLdieSGl6jSJdlHOQFga1FpjuUpgXldbpKNzxpjsGn4xYNnRHVrOAqm8aG5FfpdQlh5LHesxCeA==
vuedraggable@^2.24.2:
version "2.24.2"
resolved "https://registry.npm.taobao.org/vuedraggable/download/vuedraggable-2.24.2.tgz#cd98faec99905238469e9b4d235fba5a59fb7da2"
integrity sha1-zZj67JmQUjhGnptNI1+6Wln7faI=
dependencies:
sortablejs "^1.10.1"
vuex@^3.4.0:
vuex@^3.5.1:
version "3.5.1"
resolved "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d"
integrity sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw==
resolved "https://registry.npm.taobao.org/vuex/download/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d"
integrity sha1-8bjc6mSbwlJUz09DWAgdv12hiz0=
watchpack-chokidar2@^2.0.0:
version "2.0.0"
@@ -9982,22 +10026,23 @@ xdg-basedir@^4.0.0:
resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xgplayer-hls.js@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/xgplayer-hls.js/-/xgplayer-hls.js-2.2.3.tgz#bf538911145346c447528d609fc95dd267a24d81"
integrity sha512-CFsZanBHHRbqTmqYCFFONk76oWwJwbn+8LK/XkwBBrUrqGPQPmrXfjelGjKDDGfZ+w3xReC1nGom3IpN+lLoaQ==
xgplayer-hls.js@^2.2.5:
version "2.2.5"
resolved "https://registry.npm.taobao.org/xgplayer-hls.js/download/xgplayer-hls.js-2.2.5.tgz?cache=0&sync_timestamp=1600935842683&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxgplayer-hls.js%2Fdownload%2Fxgplayer-hls.js-2.2.5.tgz#8f90980cf2b6b4610b5449a3a55a4e7d87335cc8"
integrity sha1-j5CYDPK2tGELVEmjpVpOfYczXMg=
dependencies:
deepmerge "2.0.1"
event-emitter "^0.3.5"
eventemitter3 "^4.0.7"
xgplayer@^2.9.10:
version "2.9.10"
resolved "https://registry.yarnpkg.com/xgplayer/-/xgplayer-2.9.10.tgz#4e79b889621f829b773c118ab93da55f829d4490"
integrity sha512-5a7Xm0AY8qPRXZFxrPRErjzCERsENaskQ8Fq5Sy4ht13TMzYp0VIRREU7XF8efpJGcPsOOuFlCiMrqSpYEIAUA==
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"
danmu.js "^0.2.17"
danmu.js "0.2.23"
deepmerge "^1.5.0"
downloadjs "1.4.7"
draggabilly "^2.2.0"