Compare commits

..

99 Commits

Author SHA1 Message Date
hunlongyu
7d94cbd114 🚁 版本更新 2020-05-08 22:31:19 +08:00
hunlongyu
c7d1d208b8 🚢 样式优化 2020-05-08 22:30:20 +08:00
hunlongyu
122d4bd10c 🎀 升级合并代码 2020-05-08 10:09:52 +08:00
hunlongyu
3e26f9cb51 🚞 升级插件 2020-05-08 10:08:53 +08:00
hunlongyu
9012be09ad 🚚 修复最大资源网翻页 Bug 2020-05-07 23:37:10 +08:00
hunlongyu
bd85987e72 🛴 只能启动一个实例 2020-05-07 23:17:37 +08:00
hunlongyu
5c28cf19ed 🧶 替换旧的 Logo 2020-05-07 23:14:21 +08:00
hunlongyu
4e06e30a5c 🎫 替换新的 Logo 2020-05-07 23:08:52 +08:00
hunlongyu
15fa6f3bfb 👔 增加切换视频的遮罩 2020-05-07 21:17:50 +08:00
hunlongyu
1e2bc39bde 🙃 更进版本号 2020-05-07 15:47:05 +08:00
hunlongyu
551d11c802 🎃 解决数据库冲突,导致软件启动失败 2020-05-07 15:46:44 +08:00
hunlongyu
c4d8662b51 🚐 测试自动更新 2020-05-07 15:38:13 +08:00
hunlongyu
dea6306acb ☺ 测试自动更新 2020-05-07 15:27:30 +08:00
hunlongyu
420bf12c79 👼 设置里添加版本号 2020-05-07 15:21:40 +08:00
hunlongyu
9b1995e596 😡 解决冲突 2020-05-07 15:13:58 +08:00
hunlongyu
b89b56cce4 😱 添加自动更新代码 2020-05-07 15:12:17 +08:00
hunlongyu
84e2f456f8 🎠 新增绿色护眼主题,修复若干BUG 2020-05-07 00:32:10 +08:00
hunlongyu
3e345bf57b 🥋 修复Bug, 新增视频源 2020-04-30 16:11:48 +08:00
hunlongyu
d70da3dc3e 😂 修复浏览第一次加载时默认不显示全部视频的bug 2020-04-28 17:21:52 +08:00
hunlongyu
8f6a8fef07 🐷 修复圆角透明bug 2020-04-28 17:04:48 +08:00
hunlongyu
bda6e32b04 😂 新旧版本并不会覆盖安装 2020-04-28 16:52:35 +08:00
hunlongyu
51fd2c47da ✈ 修复一些问题 2020-04-28 16:38:55 +08:00
hunlongyu
5b3a2efc42 🥠 新增一些花里胡哨的徽章 2020-04-28 16:06:43 +08:00
hunlongyu
791febea06 🚍 Readme 修改截图 2020-04-28 14:08:37 +08:00
hunlongyu
402aa62589 🎭 修改主题里显示的图片 2020-04-28 13:59:08 +08:00
hunlongyu
70b49c8424 🥚 版本号 2020-04-28 11:45:10 +08:00
hunlongyu
d22d52a317 🍞 修复问题 2020-04-28 11:38:24 +08:00
hunlongyu
c936e8479a 🍗 发布内测公告 2020-04-28 11:35:10 +08:00
hunlongyu
ed8ceffd9c 🥗 发布内测版本 2020-04-28 11:20:52 +08:00
hunlongyu
22c471332d 🥗 发布内测版本 2020-04-28 11:19:43 +08:00
hunlongyu
99480f82e7 Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player 2020-04-28 10:49:05 +08:00
hunlongyu
7695b6376d 👻 修改分享链接 2020-04-28 10:48:59 +08:00
Hunlongyu
3e7eab6d5b Update CNAME 2020-04-28 10:38:27 +08:00
Hunlongyu
123579cb1a Update CNAME 2020-04-28 10:35:31 +08:00
Hunlongyu
b1b55042b5 Create CNAME 2020-04-28 10:33:42 +08:00
hunlongyu
5549b59730 🍥 补充分享播放页面 2020-04-28 10:27:30 +08:00
hunlongyu
474717c2df 🎏 自动构建跨平台软件 2020-04-28 00:13:32 +08:00
hunlongyu
81c17e5104 🥼 增加自动更新 2020-04-26 20:51:29 +08:00
hunlongyu
c8e6e8703c 🎀 Icon 2020-04-23 18:05:56 +08:00
hunlongyu
74a7d6b35b 🗃 进一步完善小窗口模式 2020-04-23 17:26:16 +08:00
hunlongyu
055813eabd 🎍 播放界面添加遮罩 2020-04-22 23:40:01 +08:00
hunlongyu
6311cba1d1 😭 尝试一键更新 2020-04-22 18:07:36 +08:00
hunlongyu
8ea52857a5 🚲 修复一些Bug, 新增自动播放下一集 2020-04-22 13:44:32 +08:00
hunlongyu
0776147a89 🎏 增加暗黑皮肤,优化翻译 2020-04-22 01:00:54 +08:00
hunlongyu
75cc6dd3c1 🍗 优化分类,以及详情页播放逻辑 2020-04-21 18:03:23 +08:00
hunlongyu
ba8312b78c 🍤 小窗口模式基本完成, 顺便修复了一个bug 2020-04-20 18:02:14 +08:00
hunlongyu
5b51f18675 😝 新增百度统计 2020-04-18 17:53:12 +08:00
hunlongyu
307391da4d 📚 Mini 模式实现,待完善 2020-04-18 11:13:47 +08:00
hunlongyu
8d1d751a3c 🧶 Demo功能迁移完成 2020-04-18 00:27:36 +08:00
hunlongyu
d64a834c7f 😥 设置页面 2020-04-17 18:16:24 +08:00
hunlongyu
b5778be234 🧶 完成大部分翻译 2020-04-17 00:27:52 +08:00
hunlongyu
5bf514ffcc 🎋 大部分样式完成 2020-04-17 00:00:23 +08:00
hunlongyu
8e380b6463 😛 部分组件样式 2020-04-16 13:41:03 +08:00
hunlongyu
152b654d41 🏍 页面切换,侧边栏,控制栏 2020-04-15 22:53:39 +08:00
hunlongyu
8f10dcfb20 😂 安装依赖, 移植 demo 代码 2020-04-15 18:12:58 +08:00
hunlongyu
d0024458ea 🎡 目录结构完成 2020-04-15 16:53:30 +08:00
hunlongyu
f6d479de21 😋 修改 Readme 文件 2020-04-15 09:08:03 +08:00
hunlongyu
40cb662e7e 🚀 快速开发 2020-04-14 22:41:06 +08:00
hunlongyu
f271d92149 初始化项目 2020-04-14 22:13:02 +08:00
Hunlongyu
4d391dbe14 🎉 开始 v1.x 版本开发
全新版本,更多功能。
2020-04-14 22:05:02 +08:00
hunlongyu
5b52dad32b 🎃 remove all file 2020-04-14 21:54:25 +08:00
hunlongyu
d74485eaaf Development plan 2020-03-19 10:55:44 +08:00
Hunlongyu
10baba4241 Update README.md 2020-03-14 09:46:03 +08:00
Hunlongyu
90a2c55059 Update README.md 2020-03-14 09:45:01 +08:00
Hunlongyu
aea794bf29 💘 升级到 0.9.1 版本 2020-02-26 22:06:13 +08:00
Hunlongyu
636ef602b1 💝 电视剧下,记录按钮点击后的集数。 2020-02-26 22:04:42 +08:00
Hunlongyu
b75de05f09 💖 修复网页端播放失败bug 2020-02-26 21:57:59 +08:00
Hunlongyu
9a14219418 v0.9.1 优化 2020-02-23 20:32:50 +08:00
Hunlongyu
2a0afa6ce0 移除 诚通网盘 2020-02-22 21:39:57 +08:00
Hunlongyu
f003b1b148 v0.9.0 2020-02-22 21:27:34 +08:00
Hunlongyu
55ac770461 v0.8.19 2020-02-22 14:03:00 +08:00
Hunlongyu
4505186307 v0.8.18 优化代码 2020-02-22 12:21:06 +08:00
Hunlongyu
9ce1a8ddf6 ❤️v0.8.17 add two themes 2020-02-17 00:21:56 +08:00
Hunlongyu
ee1fb8fbe5 v0.8.16 2020-02-13 20:42:06 +08:00
Hunlongyu
8bee55f706 v0.8.10 2020-02-13 19:39:14 +08:00
Hunlongyu
7c5dd0be8d remove mac 2020-01-31 07:09:08 +08:00
Hunlongyu
ffee7321a0 add mac 2020-01-31 07:08:05 +08:00
Hunlongyu
1ab025ccfe v0.8.9 移除版本相关提示 2020-01-31 06:57:35 +08:00
Hunlongyu
0e359be8a3 v0.8.7 test update 2020-01-31 06:14:40 +08:00
Hunlongyu
d9122fd24e v0.8.6 2020-01-31 05:21:46 +08:00
Hunlongyu
a746de8294 v0.8.5 size 2020-01-31 04:46:26 +08:00
Hunlongyu
f56b81ab9b 0.8.4 2020-01-31 04:33:48 +08:00
Hunlongyu
be784c9d60 v0.8.4 移动端页面扫码播放 2020-01-31 04:30:25 +08:00
Hunlongyu
fd715b7cfe v0.8.3 视频连播, 浏览加载超时提示,生成二维码 2020-01-31 04:00:43 +08:00
Hunlongyu
6979d14f35 v0.8.2 窗口置顶,窗口透明,启动唯一应用 2020-01-31 01:35:55 +08:00
Hunlongyu
be6b4b0d78 electron 7.1.9 2020-01-23 15:12:01 +08:00
hunlongyu
90b466e958 add cssFullscreen 2020-01-22 11:12:31 +08:00
hunlongyu
4b6485a922 v0.8.1 更改官网地址 2020-01-21 12:23:46 +08:00
hunlongyu
7e4f1f9127 update readme 2020-01-21 11:38:58 +08:00
hunlongyu
f8c351aa1e v0.8.0 正式版 2020-01-21 11:12:54 +08:00
hunlongyu
65fc90819a http 2020-01-21 11:11:19 +08:00
hunlongyu
cf634b55d7 Create CNAME 2020-01-21 11:10:02 +08:00
hunlongyu
b6acaf83e0 add docs 2020-01-21 10:52:22 +08:00
hunlongyu
f21d0a6963 v0.7.23tste 2020-01-20 18:25:47 +08:00
hunlongyu
367bd0ebd7 测试 2020-01-20 18:08:44 +08:00
hunlongyu
8f89c3db4d v0.7.22 优化更新 2020-01-20 17:40:19 +08:00
hunlongyu
c8881bec3b v0.7.21 优化loading 暗黑样式 ,新增版本检测 2020-01-20 17:00:43 +08:00
hunlongyu
888e2e838a add logo 2020-01-20 16:17:44 +08:00
hunlongyu
7540cee532 测试 更新 2020-01-20 16:12:10 +08:00
85 changed files with 16049 additions and 13246 deletions

View File

@@ -1,2 +1,3 @@
> 1%
last 2 versions
not dead

1
.env
View File

@@ -1 +0,0 @@
GH_TOKEN=f2ee53736fd2262eb87a53f5f1f425ee44cea7ed

View File

@@ -3,16 +3,15 @@ module.exports = {
env: {
node: true
},
'extends': [
extends: [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript'
'@vue/standard'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: '@typescript-eslint/parser'
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

27
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: release-build
on:
push:
tags:
- v*.*.*
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: |
yarn
yarn release
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,41 +1,42 @@
<p align="center">
<img src="https://i.loli.net/2020/05/07/9kLvPnWVCp7538c.png" >
</p>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
<img src="https://forthebadge.com/images/badges/made-with-vue.svg">
<p>
<p align="center">
<img alt="GitHub" src="https://img.shields.io/github/license/Hunlongyu/ZY-Player?style=for-the-badge">
<img src="https://github.com/aleen42/badges/raw/master/src/visual_studio_code_flat_square.svg?sanitize=true">
<img src="https://github.com/aleen42/badges/raw/master/src/vue_flat_square.svg?sanitize=true">
<img src="https://github.com/aleen42/badges/raw/master/src/javascript_flat_square.svg?sanitize=true">
<img src="https://github.com/aleen42/badges/raw/master/src/eslint_flat_square.svg?sanitize=true">
</p>
<p align="center">
<img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/Hunlongyu/ZY-Player/total?style=for-the-badge">
<img alt="GitHub release (latest by date including pre-releases)" src="https://img.shields.io/github/v/release/Hunlongyu/ZY-Player?include_prereleases&style=for-the-badge">
<img alt="GitHub" src="https://img.shields.io/github/license/Hunlongyu/ZY-Player?style=for-the-badge">
<img src="https://img.shields.io/github/workflow/status/Hunlongyu/ZY-Player/release-build?style=for-the-badge">
<p>
# ZY Player
资源播放器, 提供影视资源的浏览,搜索,播放,收藏,查看详情等功能.
## ZY Player
### 截图:
主界面 ⬇
![film.png](https://i.loli.net/2020/01/19/U1EPzoJHhTDnuxA.png)
暗黑主题 ⬇
![dark.png](https://i.loli.net/2020/01/20/eU6J3EFcPTXnjlK.png)
搜索 ⬇
![search.png](https://i.loli.net/2020/01/19/BPvJKxlnNfquRI4.png)
搜索结果 ⬇
![search-keyword.png](https://i.loli.net/2020/01/19/6wfY3rPBokM15hl.png)
详情 ⬇
![detail.png](https://i.loli.net/2020/01/19/CN8E1ikyMbhzo9t.png)
播放 ⬇
![play.png](https://i.loli.net/2020/01/19/4XlJRqmx2y8zAec.png)
收藏 ⬇
![star.png](https://i.loli.net/2020/01/19/Q2fkWUvaXKZJcS4.png)
### 下载:
### 新版内测
[下载地址](https://github.com/Hunlongyu/ZY-Player/releases)
新版本: v0.9.x 已开发完, 现进行内测, [点击查看内测公告](https://github.com/Hunlongyu/ZY-Player/issues/24)
### 未完成:
1. 更新: 手动检测更新
2. 图标: 求一个 zy 的logo, 256x256 像素的
[点击这里可以查看开发计划](https://github.com/Hunlongyu/ZY-Player/projects/3).
大家有什么新的需求建议欢迎踊跃提出[点击这里提意见](https://github.com/Hunlongyu/ZY-Player/issues/14)
1. [Github -- 官方下载](https://github.com/Hunlongyu/ZY-Player/releases)
2. [蓝奏云 -- 快速下载](https://www.lanzous.com/b04s6a3re) 密码:95px
#### 截图:
![001.png](https://i.loli.net/2020/04/28/T3YZvStwNQJ5aHy.png)
![003.png](https://i.loli.net/2020/04/28/2xYw9nIyWSZ5Rsr.png)
![002.png](https://i.loli.net/2020/04/28/sxnY28hgFcpUrb9.png)
### 重要:
所有资源来自网上, 该软件不参与任何制作, 上传, 储存, 下载等内容. 该软件仅供学习参考, 请于安装后24小时内删除.

View File

@@ -1,13 +1,13 @@
module.exports = {
'presets': [
presets: [
'@vue/cli-plugin-babel/preset'
],
'plugins': [
plugins: [
[
'component',
{
'libraryName': 'element-ui',
'styleLibraryName': 'theme-chalk'
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 353 KiB

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
zyplayer.fun

11
docs/index.html Normal file
View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>官网</title>
</head>
<body>
待重建
</body>
</html>

38
docs/player/player.html Normal file
View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui">
<meta name="referrer" content="no-referrer">
<title>ZY Player</title>
<style type="text/css">
html, body {width:100%;height:100%;margin:0;padding:0;overflow:hidden;}
</style>
</head>
<body>
<div id="mse"></div>
<script src="//cdn.jsdelivr.net/npm/xgplayer@1.1.4/browser/index.js" charset="utf-8"></script>
<script src="//cdn.jsdelivr.net/npm/xgplayer-hls.js/browser/index.js" charset="utf-8"></script><script>
function get (name) {
var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);//search,查询?后面的参数,并匹配正则
if (r!=null)return unescape(r[2]); return null;
}
let link = window.location.href
let url = get('url')
let player = new HlsJsPlayer({
"id": "mse",
"url": url,
"playsinline": true,
"autoplay": true,
"fluid": true,
"height": window.innerHeight,
"width": window.innerWidth,
"playbackRate": [ 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3 ],
"x5-video-player-type": "h5",
"x5-video-player-fullscreen": "true",
"x5-video-orientation": "landscape"
})
</script>
</body>
</html>

View File

@@ -1,53 +1,59 @@
{
"name": "zy",
"version": "0.7.19",
"author": "Hunlongyu",
"description": "ZY Player 资源播放器",
"version": "0.9.30",
"private": true,
"author": {
"name": "Hunlongyu",
"email": "hunlongyu@gmail.com"
},
"description": "ZY Player 资源播放器",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"dev": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"electron:generate-icons": "electron-icon-builder --input=./public/icon.png --output=build --flatten",
"bp": "vue-cli-service electron:build --win -p always"
"release": "vue-cli-service electron:build -p always"
},
"main": "background.js",
"dependencies": {
"core-js": "^3.4.4",
"axios": "^0.19.2",
"core-js": "^3.6.5",
"dexie": "^2.0.4",
"electron-updater": "^4.2.0",
"element-ui": "^2.4.5",
"flyio": "^0.6.14",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-property-decorator": "^8.3.0",
"vuex": "^3.1.2",
"xgplayer": "^2.4.7",
"xgplayer-hls.js": "^2.1.6"
"electron-updater": "^4.2.5",
"element-ui": "^2.13.1",
"html2canvas": "^1.0.0-rc.5",
"leancloud-storage": "^4.5.3",
"macaddress": "^0.2.9",
"modern-normalize": "^0.6.0",
"qrcode.vue": "^1.7.0",
"vue": "^2.6.11",
"vue-i18n": "^8.17.0",
"vuex": "^3.1.3",
"xgplayer": "^2.6.19",
"xgplayer-hls.js": "^2.2.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-typescript": "^4.1.0",
"@vue/cli-plugin-vuex": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/eslint-config-typescript": "^4.0.0",
"@vue/cli-plugin-babel": "~4.3.0",
"@vue/cli-plugin-eslint": "~4.3.0",
"@vue/cli-plugin-vuex": "~4.3.0",
"@vue/cli-service": "~4.3.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"electron": "^7.1.9",
"electron-icon-builder": "^1.0.2",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"sass": "^1.23.7",
"sass-loader": "^8.0.0",
"typescript": "~3.5.3",
"vue-cli-plugin-electron-builder": "^1.4.4",
"vue-cli-plugin-element": "^1.0.1",
"vue-template-compiler": "^2.6.10"
"electron": "^8.2.5",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"sass": "^1.26.3",
"sass-loader": "^8.0.2",
"vue-cli-plugin-electron-builder": "2.0.0-beta.6",
"vue-template-compiler": "^2.6.11"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -5,11 +5,20 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>icon.png">
<title>ZY Player</title>
<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>
<strong>We're sorry but zy doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->

View File

@@ -1,65 +1,38 @@
<template>
<el-container id="app" :class="appTheme">
<el-header class="Header">
<i class="el-icon-minus" @click="clickFrameEvent('min')"></i>
<i class="el-icon-plus" @click="clickFrameEvent('max')"></i>
<i class="el-icon-close" @click="clickFrameEvent('close')"></i>
<el-row class="Header-box">
</el-row>
</el-header>
<el-container>
<el-aside class="Aside" width="70px">
<el-row class="top">
<i :class="Main === 'Film' ? 'el-icon-film active' : 'el-icon-film'" @click="asideMenuClick('Film')"></i>
<i :class="Main === 'Search' ? 'el-icon-search active' : 'el-icon-search'" @click="asideMenuClick('Search')"></i>
<i :class="Main === 'Player' ? 'el-icon-video-play active' : 'el-icon-video-play'" @click="asideMenuClick('Player')"></i>
<i :class="Main === 'Star' ? 'el-icon-star-off active' : 'el-icon-star-off'" @click="asideMenuClick('Star')"></i>
</el-row>
<el-row class="bottom">
<i :class="Main === 'Setting' ? 'el-icon-setting active' : 'el-icon-setting'" @click="asideMenuClick('Setting')"></i>
</el-row>
</el-aside>
<el-main class="Main">
<Film v-show="Main === 'Film'" />
<Search v-show="Main === 'Search'" />
<Player v-show="Main === 'Player'" />
<Star v-show="Main === 'Star'" />
<Setting v-show="Main === 'Setting'" />
</el-main>
</el-container>
<el-drawer class="drawer" :visible.sync="detail.show" :show-close="true" size="90%" :with-header="true" direction="btt" title="详情">
<Detail />
</el-drawer>
</el-container>
<div id="app" :class="appTheme">
<Aside />
<div class="zy-body">
<Frame />
<Film v-show="view === 'Film'" />
<Play v-show="view === 'Play'" />
<Star v-show="view === 'Star'" />
<Setting v-show="view === 'Setting'" />
</div>
<transition name="slide">
<Detail v-if="detail.show"/>
</transition>
<transition name="slide">
<Share v-if="share.show"/>
</transition>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Detail from '@/components/detail.vue'
import { mapMutations } from 'vuex'
const { ipcRenderer: ipc } = require('electron')
export default Vue.extend({
<script>
export default {
name: 'App',
data () {
return {
appTheme: 'theme-light'
}
},
computed: {
Main: {
get () {
return this.$store.getters.getMain
},
set (val) {
this.SET_MAIN(val)
}
view () {
return this.$store.getters.getView
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
detail () {
return this.$store.getters.getDetail
},
share () {
return this.$store.getters.getShare
},
theme () {
return this.$store.getters.getTheme
@@ -70,71 +43,38 @@ export default Vue.extend({
this.changeTheme()
}
},
components: {
Detail
},
methods: {
...mapMutations(['SET_DETAIL', 'SET_MAIN']),
clickFrameEvent (e:string) {
ipc.send(e)
},
asideMenuClick (e:string) {
this.Main = e
},
changeTheme () {
this.appTheme = `theme-${this.theme}`
}
}
})
}
</script>
<style lang="scss">
@import './assets/theme/global.scss';
@import './assets/theme/dark.scss';
@import './assets/theme/light.scss';
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,body{
@import './assets/scss/theme.scss';
html, body, #app{
height: 100%;
border-radius: 6px;
}
#app{
#app {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent;
width: 100%;
height: 100%;
.Header{
display: flex;
justify-content: flex-end;
align-items: center;
-webkit-app-region: drag;
-webkit-user-select: none;
padding: 0;
i{
width: 60px;
height: 60px;
font-size: 30px;
cursor: pointer;
-webkit-app-region: no-drag;
display: flex;
justify-content: center;
align-items: center;
}
}
.Aside{
-webkit-app-region: drag;
-webkit-user-select: none;
text-align: center;
i{
-webkit-app-region: no-drag;
font-size: 32px;
width: 70px;
height: 70px;
line-height: 70px;
cursor: pointer;
}
}
.Main{
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: flex-start;
.zy-body{
flex: 1;
height: 100%;
overflow: hidden;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
padding: 0 20px 20px;
}
}
</style>

BIN
src/assets/image/alipay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 80 KiB

BIN
src/assets/image/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 75 KiB

BIN
src/assets/image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/image/wepay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

217
src/assets/scss/style.scss Normal file
View File

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

View File

@@ -0,0 +1,61 @@
:root{
// light
--l-c-0: #823aa0;
--l-c-1: #823aa011;
--l-c-2: #823aa022;
--l-c-3: #823aa033;
--l-c-5: #823aa055;
--l-c-8: #823aa088;
--l-c-9: #823aa099;
--l-fc-1: #808695;
--l-fc-2: #332f5c;
--l-fc-3: #823aa0;
--l-bgc-1: #ffffff;
--l-bgc-2: #f2f6f9;
--l-bsc: 0 3px 1px -2px #8e8da233, 0 2px 2px 0 #8e8da224, 0 1px 5px 0 #8e8da21f;
--l-bsc-2: 0 -4px 23px 0 #8e8da233;
--l-bsc-hover: 0 14px 26px -12px #8e8da26b, 0 4px 23px 0 #8e8da21f, 0 8px 10px -5px #8e8da233;
--l-bsc-scroll: inset 0 0 5px #823aa005;
// dark
--d-c-0: #38dd77;
--d-c-1: #38dd7711;
--d-c-2: #38dd7722;
--d-c-3: #38dd7733;
--d-c-5: #38dd7755;
--d-c-8: #38dd7788;
--d-c-9: #38dd7799;
--d-fc-1: #808695;
--d-fc-2: #acacac;
--d-fc-3: #38dd77;
--d-bgc-1: #222222;
--d-bgc-2: #2f2f2f;
--d-bsc: 0 3px 1px -2px #38dd7733, 0 2px 2px 0 #38dd7722, 0 1px 5px 0 #38dd7711;
--d-bsc-2: 0 -4px 23px 0 #38dd7733;
--d-bsc-hover: 0 14px 26px -12px #38dd7733, 0 4px 23px 0 #38dd7722, 0 8px 10px -5px #38dd7711;
--d-bsc-scroll: inset 0 0 5px #38dd7705;
// green
--g-c-0: #EAEF9D;
--g-c-1: #EAEF9D11;
--g-c-2: #EAEF9D22;
--g-c-3: #EAEF9D33;
--g-c-5: #EAEF9D55;
--g-c-8: #EAEF9D88;
--g-c-9: #EAEF9D99;
--g-fc-1: #80B155;
--g-fc-2: #C1D95CAA;
--g-fc-3: #C1D95C;
--g-bgc-1: #336A29;
--g-bgc-2: #498428;
--g-bsc: 0 3px 1px -2px #C1D95C33, 0 2px 2px 0 #C1D95C22, 0 1px 5px 0 #C1D95C11;
--g-bsc-2: 0 -4px 23px 0 #C1D95C33;
--g-bsc-hover: 0 14px 26px -12px #C1D95C33, 0 4px 23px 0 #C1D95C22, 0 8px 10px -5px #C1D95C11;
--g-bsc-scroll: inset 0 0 5px #C1D95C05;
}
@import './theme/light.scss';
@import './theme/dark.scss';
@import './theme/green.scss';
@import './style.scss';

View File

@@ -0,0 +1,346 @@
.theme-dark{
background-color: var(--d-bgc-1);
.el-pagination{
background-color: var(--d-bgc-1);
color: var(--d-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
}
.el-input__inner{
border-color: var(--d-c-3);
}
.el-pager{
.number{
background-color: var(--d-bgc-1);
}
.number:hover{
color: var(--d-c-8);
}
.active{
color: var(--d-c-9);
}
}
.more, .btn-next, .btn-prev{
background-color: var(--d-bgc-1);
&:hover{
color: var(--d-c-8);
}
}
}
.zy-select{
color: var(--d-fc-1);
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
&:hover{
box-shadow: var(--d-bsc-hover);
}
.vs-options{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
ul{
li{
&:hover{
background-color: var(--d-c-1);
}
&.active{
background-color: var(--d-c-3);
}
}
}
}
}
.zy-table{
color: var(--d-fc-2);
.tHead{
background-color: var(--d-bgc-1);
border-bottom-color: var(--d-c-3);
}
.tBody{
border-bottom-color: var(--d-c-3);
ul{
li{
border-bottom-color: var(--d-c-2);
&:hover{
animation: d-tableHoverAni 0.2s ease both;
@keyframes d-tableHoverAni {
to{
box-shadow: var(--d-bsc-hover);
}
}
}
span{
&.btn:hover{
color: var(--d-fc-3)
}
}
}
}
}
.tFooter{
.tFooter-span{
color: var(--d-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--d-bsc-scroll);
background: var(--d-c-3);
}
&::-webkit-scrollbar-track {
box-shadow: var(--d-bsc-scroll);
background: var(--bgc);
}
}
}
.zy-loading{
.loader{
color: var(--d-c-3);
}
}
.zy-body{
background-color: var(--d-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--d-c-0);
}
&:hover{
background-color: var(--d-c-2);
}
&.active{
svg{
stroke: var(--d-c-0);
stroke-width: 2;
fill: var(--d-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
}
}
.detail{
color: var(--d-fc-1);
background-color:var(--d-bgc-1);
box-shadow: var(--d-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--d-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--d-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
border-color: var(--d-c-2);
}
.m3u8_urls, .mp4_urls{
.box{
span{
border-color: var(--d-c-5);
&:hover{
color: var(--d-fc-2);
background-color: var(--d-c-1);
border-color: var(--d-c-8);
}
}
}
}
}
}
}
.film{
.top{
.search{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
&:hover{
box-shadow: var(--d-bsc-hover);
}
svg{
stroke: var(--d-c-0);
stroke-width: 1;
fill: none;
}
.search-box{
background-color: var(--d-bgc-1);
}
&.active{
box-shadow: var(--d-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
input{
color: var(--d-fc-1);
}
}
}
.middle{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
}
}
.play{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.title{
color: var(--d-fc-1);
}
.box{
.more{
span{
svg{
stroke: var(--d-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--d-c-8);
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
&.active{
svg{
stroke: var(--d-c-9);
stroke-width: 2;
fill: var(--d-c-3);
}
}
}
}
}
.list{
border: 1px solid var(--d-c-3);
background-color: var(--d-bgc-2);
.list-top{
color: var(--d-fc-2);
.list-top-close{
svg{
stroke: var(--d-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--d-c-8);
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--d-fc-1);
&.active{
background-color: var(--d-c-2);
color: var(--d-fc-3);
}
}
}
.list-history{
li{
.title{
color: var(--d-fc-1);
}
&.active{
background-color: var(--d-c-2);
.title{
color: var(--d-fc-3);
}
}
&:hover{
background-color: var(--d-c-3);
.detail-delete{
display: inline-block;
color: var(--d-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--d-c-2);
}
}
}
}
}
}
.play-mask{
background-color: var(--d-bgc-1);
color: var(--d-fc-1);
}
}
.star{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
}
.setting{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.info{
a{
color: var(--d-fc-1);
&:hover{
color: var(--d-fc-2);
}
}
}
.theme{
.title{
color: var(--d-fc-1);
}
.theme-item{
box-shadow: var(--d-bsc);
&:hover{
box-shadow: var(--d-bsc-hover);
.theme-name{
color: var(--d-fc-2)
}
}
.theme-name{
color: var(--d-fc-1);
}
}
}
.qrcode{
.title{
color: var(--d-fc-1);
}
.qrcode-item{
box-shadow: var(--d-bsc);
}
}
}
.share{
background-color: var(--d-bgc-1);
color: var(--d-fc-1);
border: 1px solid var(--d-c-8);
.right{
color: var(--d-fc-1);
.tops{
color: var(--d-fc-2);
}
}
.share-mask{
background-color: var(--d-bgc-1);
}
}
}

View File

@@ -0,0 +1,346 @@
.theme-green{
background-color: var(--g-bgc-1);
.el-pagination{
background-color: var(--g-bgc-1);
color: var(--g-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
}
.el-input__inner{
border-color: var(--g-c-3);
}
.el-pager{
.number{
background-color: var(--g-bgc-1);
}
.number:hover{
color: var(--g-c-8);
}
.active{
color: var(--g-c-9);
}
}
.more, .btn-next, .btn-prev{
background-color: var(--g-bgc-1);
&:hover{
color: var(--g-c-8);
}
}
}
.zy-select{
color: var(--g-fc-1);
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
&:hover{
box-shadow: var(--g-bsc-hover);
}
.vs-options{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
ul{
li{
&:hover{
background-color: var(--g-c-1);
}
&.active{
background-color: var(--g-c-3);
}
}
}
}
}
.zy-table{
color: var(--g-fc-2);
.tHead{
background-color: var(--g-bgc-1);
border-bottom-color: var(--g-c-3);
}
.tBody{
border-bottom-color: var(--g-c-3);
ul{
li{
border-bottom-color: var(--g-c-2);
&:hover{
animation: d-tableHoverAni 0.2s ease both;
@keyframes d-tableHoverAni {
to{
box-shadow: var(--g-bsc-hover);
}
}
}
span{
&.btn:hover{
color: var(--g-fc-3)
}
}
}
}
}
.tFooter{
.tFooter-span{
color: var(--g-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--g-bsc-scroll);
background: var(--g-c-3);
}
&::-webkit-scrollbar-track {
box-shadow: var(--g-bsc-scroll);
background: var(--bgc);
}
}
}
.zy-loading{
.loader{
color: var(--g-c-3);
}
}
.zy-body{
background-color: var(--g-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--g-c-0);
}
&:hover{
background-color: var(--g-c-2);
}
&.active{
svg{
stroke: var(--g-c-0);
stroke-width: 2;
fill: var(--g-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
}
}
.detail{
color: var(--g-fc-1);
background-color:var(--g-bgc-1);
box-shadow: var(--g-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--g-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--g-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
border-color: var(--g-c-2);
}
.m3u8_urls, .mp4_urls{
.box{
span{
border-color: var(--g-c-5);
&:hover{
color: var(--g-fc-2);
background-color: var(--g-c-1);
border-color: var(--g-c-8);
}
}
}
}
}
}
}
.film{
.top{
.search{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
&:hover{
box-shadow: var(--g-bsc-hover);
}
svg{
stroke: var(--g-c-0);
stroke-width: 1;
fill: none;
}
.search-box{
background-color: var(--g-bgc-1);
}
&.active{
box-shadow: var(--g-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
input{
color: var(--g-fc-1);
}
}
}
.middle{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
}
}
.play{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
.title{
color: var(--g-fc-1);
}
.box{
.more{
span{
svg{
stroke: var(--g-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--g-c-8);
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
&.active{
svg{
stroke: var(--g-c-9);
stroke-width: 2;
fill: var(--g-c-3);
}
}
}
}
}
.list{
border: 1px solid var(--g-c-3);
background-color: var(--g-bgc-2);
.list-top{
color: var(--g-fc-2);
.list-top-close{
svg{
stroke: var(--g-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--g-c-8);
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--g-fc-1);
&.active{
background-color: var(--g-c-2);
color: var(--g-fc-3);
}
}
}
.list-history{
li{
.title{
color: var(--g-fc-1);
}
&.active{
background-color: var(--g-c-2);
.title{
color: var(--g-fc-3);
}
}
&:hover{
background-color: var(--g-c-3);
.detail-delete{
display: inline-block;
color: var(--g-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--g-c-2);
}
}
}
}
}
}
.play-mask{
background-color: var(--g-bgc-1);
color: var(--g-fc-1);
}
}
.star{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
}
.setting{
background-color: var(--g-bgc-1);
box-shadow: var(--g-bsc);
.info{
a{
color: var(--g-fc-1);
&:hover{
color: var(--g-fc-2);
}
}
}
.theme{
.title{
color: var(--g-fc-1);
}
.theme-item{
box-shadow: var(--g-bsc);
&:hover{
box-shadow: var(--g-bsc-hover);
.theme-name{
color: var(--g-fc-2)
}
}
.theme-name{
color: var(--g-fc-1);
}
}
}
.qrcode{
.title{
color: var(--g-fc-1);
}
.qrcode-item{
box-shadow: var(--g-bsc);
}
}
}
.share{
background-color: var(--g-bgc-1);
color: var(--g-fc-1);
border: 1px solid var(--g-c-8);
.right{
color: var(--g-fc-1);
.tops{
color: var(--g-fc-2);
}
}
.share-mask{
background-color: var(--g-bgc-1);
}
}
}

View File

@@ -0,0 +1,340 @@
.theme-light{
background-color: var(--l-bgc-1);
.el-pagination{
color: var(--l-fc-1);
.el-pagination__total, .el-pagination__jump, .el-input__inner{
color: var(--l-fc-1);
}
.el-pager{
.number:hover{
color: var(--l-c-8);
}
.active{
color: var(--l-c-9);
}
}
.more, .btn-next, .btn-prev{
&:hover{
color: var(--l-c-8);
}
}
}
.zy-select{
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
&:hover{
box-shadow: var(--l-bsc-hover);
}
.vs-options{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
ul{
li{
&:hover{
background-color: var(--l-c-1);
}
&.active{
background-color: var(--l-c-3);
}
}
}
}
}
.zy-table{
color: var(--l-fc-2);
.tHead{
background-color: var(--l-bgc-1);
border-bottom-color: var(--l-c-3);
}
.tBody{
border-bottom-color: var(--l-c-3);
ul{
li{
border-bottom-color: var(--l-c-2);
&:hover{
animation: l-tableHoverAni 0.2s ease both;
@keyframes l-tableHoverAni {
to{
box-shadow: var(--l-bsc-hover);
}
}
}
span{
&.btn:hover{
color: var(--l-fc-3)
}
}
}
}
}
.tFooter{
.tFooter-span{
color: var(--l-fc-1);
}
}
}
.zy-scroll{
&:hover{
&::-webkit-scrollbar-thumb {
box-shadow: var(--l-bsc-scroll);
background: var(--l-c-3);
}
&::-webkit-scrollbar-track {
box-shadow: var(--l-bsc-scroll);
background: var(--bgc);
}
}
}
.zy-loading{
.loader{
color: var(--l-c-3);
}
}
.zy-body{
background-color: var(--l-bgc-2);
}
.aside{
.zy-svg{
svg{
stroke: var(--l-c-0);
}
&:hover{
background-color: var(--l-c-2);
}
&.active{
svg{
stroke: var(--l-c-0);
stroke-width: 2;
fill: var(--l-c-2);
}
}
}
}
.frame{
span{
&.min{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
}
}
.detail{
color: var(--l-fc-1);
background-color:var(--l-bgc-1);
box-shadow: var(--l-bsc-2);
.detail-content{
.detail-close{
svg{
stroke-width: 1;
stroke: var(--l-c-8);
&:hover{
stroke-width: 2px;
stroke: var(--l-c-9);
}
}
}
.detail-body{
.info, .desc, .m3u8_urls, .mp4_urls{
border-color: var(--l-c-2);
}
.m3u8_urls, .mp4_urls{
.box{
span{
border-color: var(--l-c-5);
&:hover{
color: var(--l-fc-2);
background-color: var(--l-c-1);
border-color: var(--l-c-8);
}
}
}
}
}
}
}
.film{
.top{
.search{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
&:hover{
box-shadow: var(--l-bsc-hover);
}
svg{
stroke: #823aa099;
stroke-width: 1;
fill: none;
}
.search-box{
background-color: none;
}
&.active{
box-shadow: var(--l-bsc-hover);
svg{
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
input{
color: var(--l-fc-1);
}
}
}
.middle{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
}
}
.play{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
.title{
color: var(--d-fc-1);
}
.box{
.more{
span{
svg{
stroke: var(--l-c-5);
stroke-width: 1;
fill: none;
}
&:hover{
svg{
stroke: var(--l-c-8);
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
&.active{
svg{
stroke: var(--l-c-9);
stroke-width: 2;
fill: var(--l-c-3);
}
}
}
}
.mask{
background-color: var(--l-bgc-1);
}
}
.list{
border: 1px solid var(--l-c-3);
background-color: var(--l-bgc-2);
.list-top{
color: var(--l-fc-2);
.list-top-close{
svg{
stroke: var(--l-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--l-c-8);
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
}
}
.list-body{
.list-item{
li{
color: var(--l-fc-1);
&.active{
background-color: var(--l-c-2);
color: var(--l-fc-3);
}
}
}
.list-history{
li{
.title{
color: var(--l-fc-1);
}
&.active{
background-color: var(--l-c-2);
.title{
color: var(--l-fc-3);
}
}
&:hover{
background-color: var(--l-c-3);
.detail-delete{
display: inline-block;
color: var(--l-fc-2);
}
}
.detail-delete{
&:hover{
background-color: var(--l-c-2);
}
}
}
}
}
}
.play-mask{
background-color: var(--l-bgc-1);
color: var(--l-fc-1);
}
}
.star{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
}
.setting{
background-color: var(--l-bgc-1);
box-shadow: var(--l-bsc);
.info{
a{
color: var(--l-fc-1);
&:hover{
color: var(--l-fc-2);
}
}
}
.theme{
.title{
color: var(--l-fc-1);
}
.theme-item{
box-shadow: var(--l-bsc);
&:hover{
box-shadow: var(--l-bsc-hover);
.theme-name{
color: var(--l-fc-2)
}
}
.theme-name{
color: var(--l-fc-1);
}
}
}
.qrcode{
.title{
color: var(--l-fc-1);
}
.qrcode-item{
box-shadow: var(--l-bsc);
}
}
}
.share{
background-color: var(--l-bgc-1);
color: var(--l-fc-1);
border: 1px solid var(--l-c-8);
.right{
color: var(--l-fc-1);
.tops{
color: var(--l-fc-2);
}
}
.share-mask{
background-color: var(--l-bgc-1);
}
}
}

View File

@@ -1,104 +0,0 @@
.theme-dark{
color: var(--d-c);
background-color: var(--d-bgc);
.el-button, input, select, .el-select{
color: var(--d-c);
background-color: var(--d-bgc);
border-color: var(--d-bdc);
}
.el-button--text{
border: none;
}
.el-tabs__item, .el-tabs__nav-wrap::after{
color: var(--d-c);
background-color: var(--d-bgc);
border-color: var(--d-dbc);
}
.el-table, .el-table th, .el-table tr,
.el-table--striped .el-table__body tr.el-table__row--striped td,
.el-table--enable-row-hover .el-table__body tr:hover>td,
.el-table td, .el-table th.is-leaf{
color: var(--d-c);
background-color: var(--d-bgc);
border-color: var(--d-dbc);
}
.el-pagination, .el-pagination .el-pagination__total, .el-pagination .el-pagination__jump,
.el-pagination .btn-next, .el-pagination .btn-prev, .el-pager li{
color: var(--d-c);
background-color: var(--d-bgc);
}
.el-input__inner,
.el-input-group__append, .el-input-group__prepend{
background-color: var(--d-bgc);
border-color: var(--d-dbc);
}
.el-select-dropdown{
color: var(--d-c);
}
.el-drawer{
background-color: var(--d-bgc) !important;
overflow: auto;
&::-webkit-scrollbar{
width: 0px;
}
}
.Header, .Aside{
i{
color: var(--d-icon);
&:hover{
color: var(--d-icon-h);
background-color: var(--d-bgc-h);
}
}
}
.Aside{
i{
&.active{
color: var(--d-icon-h);
background-color: var(--d-bgc-h);
border-left: 4px solid var(--d-icon-h);
}
}
}
.Main{
.film, .search, .star, .player{
.table-box{
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px var(--d-bdc);
}
&::-webkit-scrollbar-thumb {
background-color: var(--d-icon);
outline: 1px solid var(--d-icon);
}
}
}
.setting{
.el-link, .card{
color: var(--d-c);
background-color: var(--d-bgc);
border-color: var(--d-bdc);
}
}
}
.detail{
color: var(--d-c);
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px var(--d-bdc);
}
&::-webkit-scrollbar-thumb {
background-color: var(--d-icon);
outline: 1px solid var(--d-icon);
}
.box, .info, .urls{
border: 1px solid var(--d-bdc);
}
.vodInfo{
li, span, a{
color: var(--d-c);
}
label{
color: #f90;
}
}
}
}

View File

@@ -1,19 +0,0 @@
:root{
// light
--l-c: #808695;
--l-c-h: #515a6e;
--l-icon: #C0C4CC;
--l-icon-h: #515a6e;
--l-bgc: #ffffff;
--l-bgc-h: #efefef;
--l-bdc: #dcdee2;
// dark
--d-c: #919191;
--d-c-h: #454545;
--d-icon: #919191;
--d-icon-h: #919191;
--d-bgc: #242424;
--d-bgc-h: #454545;
--d-bdc: #7a7a7a;
}

View File

@@ -1,74 +0,0 @@
.theme-light{
color: var(--l-c);
background-color: var(--l-bgc);
.el-table, .el-tabs__item, .btn-next, input{
color: var(--l-c);
}
.el-pagination, .el-pagination .el-pagination__total, .el-pagination .el-pagination__jump,
.el-pagination .btn-next, .el-pagination .btn-prev{
color: var(--l-c);
}
.el-drawer{
overflow: auto;
&::-webkit-scrollbar{
width: 0px;
}
}
.Header, .Aside{
i{
color: var(--l-icon);
&:hover{
color: var(--l-icon-h);
background-color: var(--l-bgc-h);
}
}
}
.Aside{
i{
&.active{
color: var(--l-icon-h);
background-color: var(--l-bgc-h);
border-left: 4px solid var(--l-icon-h);
}
}
}
.Main{
.film, .search, .star, .player{
.table-box{
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px var(--l-bdc);
}
&::-webkit-scrollbar-thumb {
background-color: var(--l-icon);
outline: 1px solid var(--l-icon);
}
}
}
.setting{
.el-link, .card{
color: var(--l-c);
}
}
}
.detail{
color: var(--l-c);
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px var(--l-bdc);
}
&::-webkit-scrollbar-thumb {
background-color: var(--l-icon);
outline: 1px solid var(--l-icon);
}
.box, .info, .urls{
border: 1px solid var(--l-bdc);
}
.vodInfo{
li, span, a{
color: var(--l-c);
}
label{
color: #f90;
}
}
}
}

151
src/background.js Normal file
View File

@@ -0,0 +1,151 @@
'use strict'
import { app, ipcMain, protocol, BrowserWindow } from 'electron'
import {
createProtocol
// installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
import path from 'path'
import { autoUpdater } from 'electron-updater'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
let mini
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
function createWindow () {
win = new BrowserWindow({
width: 1080,
height: 720,
frame: false,
resizable: false,
transparent: true,
webPreferences: {
webSecurity: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
},
// eslint-disable-next-line
icon: path.join(__static, 'icon.png')
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
win.loadURL('app://./index.html')
autoUpdater.checkForUpdatesAndNotify()
}
win.on('closed', () => {
win = null
})
}
function createMini () {
mini = new BrowserWindow({
width: 550,
minWidth: 260,
height: 340,
minHeight: 180,
frame: false,
resizable: true,
transparent: true,
alwaysOnTop: true,
webPreferences: {
webSecurity: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
},
// eslint-disable-next-line
icon: path.join(__static, 'icon.png')
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
mini.loadURL(process.env.WEBPACK_DEV_SERVER_URL + 'mini')
if (!process.env.IS_TEST) mini.webContents.openDevTools()
} else {
mini.loadURL('app://./mini.html')
}
mini.on('closed', () => {
mini = null
})
}
app.allowRendererProcessReuse = true
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (win === null) {
createWindow()
}
if (mini === null) {
createMini()
}
})
ipcMain.on('min', () => {
win.minimize()
})
ipcMain.on('close', () => {
win.close()
})
ipcMain.on('mini', () => {
createMini()
win.close()
})
ipcMain.on('miniMin', () => {
mini.minimize()
})
ipcMain.on('miniClose', () => {
mini.close()
createWindow()
})
ipcMain.on('miniOpacity', (e, arg) => {
mini.setOpacity(arg)
})
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 当运行第二个实例时,将会聚焦到win这个窗口
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
}
})
// 创建 win, 加载应用的其余部分, etc...
app.on('ready', () => {
if (!process.env.WEBPACK_DEV_SERVER_URL) {
createProtocol('app')
}
createWindow()
})
}
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}

View File

@@ -1,119 +0,0 @@
'use strict'
import { app, protocol, ipcMain, BrowserWindow } from 'electron'
import {
createProtocol,
installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
import path from 'path'
import { autoUpdater } from 'electron-updater'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win: BrowserWindow | null
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
function createWindow () {
// Create the browser window.
win = new BrowserWindow({
width: 1080,
height: 720,
frame: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true
},
// @ts-ignore
icon: path.join(__static, 'icon.png')
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
autoUpdater.checkForUpdatesAndNotify()
}
win.on('closed', () => {
win = null
})
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})
ipcMain.on('min', () => {
if (win) {
win.minimize()
}
})
ipcMain.on('max', e => {
if (win) {
if (win.isMaximized()) {
win.unmaximize()
} else {
win.maximize()
}
}
})
ipcMain.on('close', e => {
if (win) {
win.close()
}
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
// Devtools extensions are broken in Electron 6.0.0 and greater
// See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info
// Electron will not launch with Devtools extensions installed on Windows 10 with dark mode
// If you are not using Windows 10 dark mode, you may uncomment these lines
// In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines
// try {
// await installVueDevtools()
// } catch (e) {
// console.error('Vue Devtools failed to install:', e.toString())
// }
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}

77
src/components/Aside.vue Normal file
View File

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

345
src/components/Detail.vue Normal file
View File

@@ -0,0 +1,345 @@
<template>
<div class="detail">
<div class="detail-content">
<div class="detail-header">
<span class="detail-title">{{$t('detail')}}</span>
<span class="detail-close zy-svg" @click="closeDetail">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">{{$t('close')}}</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="detail-body zy-scroll" v-show="!loading" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<div class="info" v-html="vDetail.info"></div>
<div class="desc" v-html="vDetail.desc" v-if="show.desc"></div>
<div class="m3u8_urls">
<div class="title">{{$t('play')}}:</div>
<div class="box">
<span v-for="(i, j) in vDetail.m3u8_urls" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
</div>
</div>
<div class="mp4_urls" v-if="show.download">
<div class="title">{{$t('download')}}:</div>
<div class="box">
<span v-for="(i, j) in vDetail.mp4_urls" :key="j" @click="download(i)">{{i | ftName}}</span>
<span @click="allDownload" v-show="vDetail.mp4_urls.length > 1">{{$t('all_download')}}</span>
</div>
</div>
</div>
<div class="detail-mask zy-loading" v-show="loading">
<div class="loader"></div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
const { clipboard } = require('electron')
export default {
name: 'detail',
data () {
return {
scroll: false,
loading: true,
vDetail: {},
show: {
desc: false,
download: false
}
}
},
filters: {
ftName (e) {
const name = e.split('$')[0]
return name
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_VIDEO', 'SET_DETAIL']),
closeDetail () {
this.detail.show = false
},
getDetail () {
tools.detail_get(this.detail.v.site, this.detail.v.detail).then(res => {
this.vDetail = res
if (res.desc.length > 0) {
this.show.desc = true
}
if (res.mp4_urls.length > 0) {
this.show.download = true
}
this.$nextTick(() => {
this.loading = false
})
})
},
playEvent (n) {
const v = { ...this.detail.v }
v.index = n
this.video = v
this.detail.show = false
this.view = 'Play'
},
download (e) {
const name = e.split('$')[0]
const txt = encodeURI(e.split('$')[1])
clipboard.writeText(txt)
this.$m.success(name + this.$t('copy_success'))
},
allDownload () {
const urls = [...this.vDetail.mp4_urls]
let txt = ''
for (const i of urls) {
const url = encodeURI(i.split('$')[1])
txt += (url + '\n')
}
clipboard.writeText(txt)
this.$m.success(this.$t('copy_success'))
}
},
created () {
this.getDetail()
}
}
</script>
<style lang="scss">
.detail{
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 680px;
z-index: 999;
.detail-content{
height: 680px;
padding: 0 60px;
position: relative;
.detail-header{
width: 100%;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 -40px;
.detail-title{
font-size: 16px;
}
.detail-close{
cursor: pointer;
}
}
.detail-body{
height: 630px;
overflow-y: auto;
.info{
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
width: 955px;
padding: 10px;
border: 1px solid;
border-radius: 2px;
margin-bottom: 10px;
.vodImg{
width: 200px;
img{
width: 100%;
height: auto;
}
}
.vodAd{
display: none;
}
.vodInfo{
flex: 1;
margin-left: 20px;
overflow: hidden;
.vodh{
margin-bottom: 6px;
h2{
display: inline-block;
margin: 0;
}
span{
font-size: 12px;
margin-left: 10px;
}
label{
font-size: 20px;
font-weight: bold;
margin-left: 20px;
}
}
.cont, .tags{
display: none;
}
ul{
padding: 0;
margin: 0;
}
a{
display: none;
pointer-events: none;
}
li{
list-style: none;
font-size: 14px;
line-height: 18px;
height: 18px;
overflow: hidden;
span{
word-wrap: nowrap;
}
}
}
.whitetitle{
width: 100%;
font-size: 22px;
font-weight: bold;
margin: 4px 0;
}
.people{
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
.left{
width: 200px;
img{
width: 100%;
height: auto;
}
}
.right{
flex: 1;
margin-left: 20px;
overflow: hidden;
p{
font-size: 14px;
}
a{
pointer-events: none;
text-decoration: none;
}
}
}
}
.desc{
border: 1px solid;
padding: 10px;
width: 955px;
margin-bottom: 10px;
border-radius: 2px;
font-size: 14px;
line-height: 20px;
}
.m3u8_urls, .mp4_urls{
border: 1px solid;
padding: 10px;
width: 955px;
margin-bottom: 10px;
border-radius: 2px;
.title{
font-size: 16px;
}
.box{
width: 100%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
span{
font-size: 12px;
border: 1px solid;
border-radius: 2px;
cursor: pointer;
margin: 6px 6px 0px 0px;
padding: 8px 22px;
}
&::after {
content: '';
flex: 1;
}
}
}
.mp4_urls{
margin-bottom: 10px;
}
}
.detail-mask{
width: 980px;
height: 600px;
position: absolute;
top: 50px;
display: flex;
justify-content: center;
align-items: center;
.loader {
font-size: 8px;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
text-indent: -9999em;
animation: load4 1.3s infinite linear;
transform: translateZ(0);
}
@keyframes load4 {
0%,
100% {
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
}
}
}
</style>

333
src/components/Film.vue Normal file
View File

@@ -0,0 +1,333 @@
<template>
<div class="film">
<div class="top" v-if="top">
<!-- site -->
<div class="zy-select" @mouseleave="show.site = false">
<div class="vs-placeholder" @click="show.site = true">{{site.name}}</div>
<div class="vs-options" v-show="show.site">
<ul>
<li :class="site === j ? 'active' : ''" v-for="(i, j) in sites" :key="j" @click="siteClick(i)">{{ i.name }}</li>
</ul>
</div>
</div>
<!-- tags -->
<div class="zy-select" @mouseleave="show.tags = false" v-if="site.tags.length > 0 && keywords.length <= 0">
<div class="vs-placeholder" @click="show.tags = true">{{site.tags[tag].title}}</div>
<div class="vs-options" v-show="show.tags">
<ul>
<li :class="tag === j ? 'active' : ''" v-for="(i, j) in site.tags" :key="j" @click="tagClick(i, j)">{{ i.title }}</li>
</ul>
</div>
</div>
<!-- type -->
<div class="zy-select" @mouseleave="show.type = false" v-if="site.tags[tag].children.length > 0 && keywords.length <= 0">
<div class="vs-placeholder" @click="show.type = true">{{typeName}}</div>
<div class="vs-options" v-show="show.type">
<ul>
<li :class="type === j ? 'active' : ''" v-for="(i, j) in site.tags[tag].children" :key="j" @click="typeClick(i, j)">{{ i.title }}</li>
</ul>
</div>
</div>
<div :class="[inputFocus ? 'active ': ''] + 'search'" @mouseover="inputFocus = true" @mouseleave="inputFocus = false">
<div class="search-icon">
<span class="zy-svg">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="searchIconTitle">
<title id="searchIconTitle">Search</title>
<path d="M14.4121122,14.4121122 L20,20"></path>
<circle cx="10" cy="10" r="6"></circle>
</svg>
</span>
</div>
<input type="text" class="search-box" v-model="keywords" @keypress.enter="searchEvent">
</div>
</div>
<div class="middle">
<div class="zy-table">
<div class="tHead">
<span class="name">{{$t('videoName')}}</span>
<span class="type">{{$t('type')}}</span>
<span class="time">{{$t('time')}}</span>
<span class="operate">{{$t('operate')}}</span>
</div>
<div class="tBody zy-scroll">
<ul v-show="!tb.loading">
<li v-for="(i, j) in tb.list" :key="j" @click="detailEvent(i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.time}}</span>
<span class="operate">
<span class="btn" @click.stop="playEvent(i)">{{$t('play')}}</span>
<span class="btn" @click.stop="starEvent(i)">{{$t('star')}}</span>
<span class="btn" @click.stop="shareEvent(i)">{{$t('share')}}</span>
</span>
</li>
</ul>
<div class="tBody-mask zy-loading" v-show="tb.loading">
<div class="loader"></div>
</div>
</div>
<div class="tFooter">
<span class="tFooter-span">今日更新: {{ tb.update }} </span>
<el-pagination small :page-size="tb.size" :total="tb.total" :current-page="tb.page" @current-change="tbPageChange" layout="total, prev, pager, next, jumper"></el-pagination>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { sites, getSite } from '../lib/site/sites'
import tools from '../lib/site/tools'
import video from '../lib/dexie/video'
import setting from '../lib/dexie/setting'
export default {
name: 'film',
data () {
return {
sites: sites,
site: {},
top: false,
tag: 0,
type: 0,
typeName: '',
keywords: '',
id: '',
show: {
site: false,
tags: false,
type: false
},
inputFocus: false,
tb: {
list: [],
page: 1,
size: 50,
total: 0,
update: 0,
loading: true
}
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
gSite: {
get () {
return this.$store.getters.getSite
},
set (val) {
this.SET_SITE(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
watch: {
gSite (n, o) {
const s = getSite(n)
this.siteClick(s)
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_SITE', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
init () {
setting.find().then(res => {
this.site = getSite(res.site)
this.top = true
tools.film_get(res.site).then(tRes => {
this.tb.list = tRes.list
this.tb.total = tRes.total
this.tb.update = tRes.update
this.tb.loading = false
})
})
},
siteClick (e) {
this.site = e
this.tag = 0
this.id = e.tags[0].id
this.show.site = false
if (this.keywords.length > 0) {
this.searchEvent()
} else {
this.tb.update = 0
this.tb.total = 0
this.tb.loading = true
tools.film_get(e.key, this.id).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.update = res.update
this.tb.loading = false
})
}
},
tagClick (e, n) {
this.tb.update = 0
this.tb.total = 0
this.tag = n
this.id = e.id
this.typeName = 'All'
this.tb.loading = true
this.show.tags = false
tools.film_get(this.site.key, this.id).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.update = res.update
this.tb.loading = false
})
},
typeClick (e, n) {
this.tb.update = 0
this.tb.total = 0
this.type = n
this.typeName = e.title
this.id = e.id
this.tb.loading = true
this.show.type = false
tools.film_get(this.site.key, this.id).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.update = res.update
this.tb.loading = false
})
},
searchEvent () {
const flag = this.site.search
if (flag === '') {
this.$m.warning(this.$t('not_support_search'))
return false
}
this.tb.loading = true
this.tb.update = 0
this.tb.total = 0
tools.search_get(this.site.key, this.keywords).then(res => {
this.tb.list = res.list
this.tb.total = res.total
this.tb.loading = false
})
},
detailEvent (e) {
this.detail = {
show: true,
v: e
}
},
playEvent (e) {
this.video = e
this.view = 'Play'
},
starEvent (e) {
video.find({ detail: e.detail }).then(res => {
if (res) {
this.$m.warning(this.$t('exists'))
} else {
video.add(e).then(res => {
this.$m.success(this.$t('star_success'))
})
}
})
},
shareEvent (e) {
this.share = {
show: true,
v: e
}
},
tbPageChange (e) {
this.tb.loading = true
this.tb.page = e
tools.film_get(this.site.key, this.id, this.tb.page).then(res => {
this.tb.list = res.list
this.tb.loading = false
})
}
},
created () {
this.init()
}
}
</script>
<style lang="scss" scoped>
.film{
height: 670px;
width: 100%;
display: flex;
flex-direction: column;
animation: viewFadeIn 1s ease-in both;
.top{
width: 100%;
height: 30px;
display: flex;
justify-content: space-between;
align-items: center;
.search{
width: 200px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 15px;
svg{
width: 20px;
height: 20px;
stroke-linecap: round;
stroke-linejoin: round;
}
.search-icon{
width: 40px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.search-box{
width: 160px;
height: 30px;
border-radius: 20px;
border: none;
text-indent: 2px;
font-size: 14px;
&:focus{
outline: none;
border: none;
}
}
}
}
.middle{
height: 620px;
width: 100%;
margin-top: 10px;
padding-bottom: 0px;
border-radius: 5px;
}
}
</style>

65
src/components/Frame.vue Normal file
View File

@@ -0,0 +1,65 @@
<template>
<div class="frame">
<span class="min" @click="frameClickEvent('min')"></span>
<span class="close" @click="frameClickEvent('close')"></span>
</div>
</template>
<script>
const ipc = require('electron').ipcRenderer
export default {
name: 'frame',
methods: {
frameClickEvent (e) {
ipc.send(e)
}
}
}
</script>
<style lang="scss" scoped>
.frame{
width: 100%;
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
user-select: none;
-webkit-app-region: drag;
span{
-webkit-app-region: no-drag;
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
margin-left: 10px;
cursor: pointer;
opacity: 0.5;
&:hover{
animation: heartbeat 3s ease-in-out infinite both;
}
@keyframes heartbeat {
from {
transform: scale(1);
transform-origin: center center;
animation-timing-function: ease-out;
}
10% {
opacity: 1;
transform: scale(0.91);
animation-timing-function: ease-in;
}
17% {
transform: scale(0.98);
animation-timing-function: ease-out;
}
33% {
transform: scale(0.87);
animation-timing-function: ease-in;
}
45% {
transform: scale(1);
animation-timing-function: ease-out;
}
}
}
}
</style>

530
src/components/Play.vue Normal file
View File

@@ -0,0 +1,530 @@
<template>
<div class="play">
<div class="box">
<div class="title">{{name}}</div>
<div id="xg"></div>
<div class="mask zy-loading" v-show="mask">
<div class="loader"></div>
</div>
<div class="more" v-show="more">
<span class="zy-svg" @click="nextEvent" v-show="showNext">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="forwardIconTitle">
<title id="forwardIconTitle">{{$t('next')}}</title>
<path d="M10 14.74L3 19V5l7 4.26V5l12 7-12 7v-4.26z"></path>
</svg>
</span>
<span class="zy-svg" @click="listEvent" :class="right.type === 'list' ? 'active' : ''" v-show="right.listData.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="dashboardIconTitle">
<title id="dashboardIconTitle">{{$t('play_list')}}</title>
<rect width="20" height="20" x="2" y="2"></rect>
<path d="M11 7L17 7M11 12L17 12M11 17L17 17"></path>
<line x1="7" y1="7" x2="7" y2="7"></line>
<line x1="7" y1="12" x2="7" y2="12"></line>
<line x1="7" y1="17" x2="7" y2="17"></line>
</svg>
</span>
<span class="zy-svg" @click="historyEvent" :class="right.type === 'history' ? 'active' : ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="timeIconTitle">
<title id="timeIconTitle">{{$t('history')}}</title>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 5 12 12 16 16"></polyline>
</svg>
</span>
<span class="zy-svg" @click="starEvent" :class="isStar ? 'active' : ''" v-show="right.listData.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="favouriteIconTitle">
<title id="favouriteIconTitle">{{$t('star')}}</title>
<path d="M12,21 L10.55,19.7051771 C5.4,15.1242507 2,12.1029973 2,8.39509537 C2,5.37384196 4.42,3 7.5,3 C9.24,3 10.91,3.79455041 12,5.05013624 C13.09,3.79455041 14.76,3 16.5,3 C19.58,3 22,5.37384196 22,8.39509537 C22,12.1029973 18.6,15.1242507 13.45,19.7149864 L12,21 Z"></path>
</svg>
</span>
<span class="zy-svg" @click="detailEvent" v-show="right.listData.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="feedIconTitle">
<title id="feedIconTitle">{{$t('detail')}}</title>
<circle cx="7.5" cy="7.5" r="2.5"></circle>
<path d="M22 13H2"></path>
<path d="M18 6h-5m5 3h-5"></path>
<path d="M5 2h14a3 3 0 0 1 3 3v17H2V5a3 3 0 0 1 3-3z"></path>
</svg>
</span>
<span class="zy-svg" @click="smallEvent" v-show="right.listData.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="tvIconTitle">
<title id="tvIconTitle">{{$t('mini')}}</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
<polyline stroke-linejoin="round" points="8 4 12 7.917 16 4"></polyline>
</svg>
</span>
<span class="zy-svg" @click="shareEvent" v-show="right.listData.length > 0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="qrIconTitle">
<title id="qrIconTitle">{{$t('share')}}</title>
<rect x="10" y="3" width="7" height="7" transform="rotate(90 10 3)"></rect>
<rect width="1" height="1" transform="matrix(-1 0 0 1 7 6)"></rect>
<rect x="10" y="14" width="7" height="7" transform="rotate(90 10 14)"></rect>
<rect x="6" y="17" width="1" height="1"></rect>
<rect x="14" y="20" width="1" height="1"></rect>
<rect x="17" y="17" width="1" height="1"></rect>
<rect x="14" y="14" width="1" height="1"></rect>
<rect x="20" y="17" width="1" height="1"></rect>
<rect x="20" y="14" width="1" height="1"></rect>
<rect x="20" y="20" width="1" height="1"></rect>
<rect x="21" y="3" width="7" height="7" transform="rotate(90 21 3)"></rect>
<rect x="17" y="6" width="1" height="1"></rect>
</svg>
</span>
</div>
</div>
<transition name="slideX">
<div v-if="right.show" class="list">
<div class="list-top">
<span class="list-top-title">{{ right.type === 'list' ? $t('play_list') : $t('history') }}</span>
<span class="list-top-close zy-svg" @click="closeEvent">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">{{$t('close')}}</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="list-body zy-scroll" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<ul v-show="right.type === 'list'" class="list-item">
<li v-show="right.listData.length === 0">{{$t('no_data')}}</li>
<li @click="listItemEvent(j)" :class="video.index === j ? 'active' : ''" v-for="(i, j) in right.listData" :key="j">{{i | ftName}}</li>
</ul>
<ul v-show="right.type === 'history'" class="list-history">
<li v-show="right.historyData.length > 1" @click="clearAll">{{$t('clear_data')}}</li>
<li v-show="right.historyData.length === 0">{{$t('no_data')}}</li>
<li @click="historyItemEvent(m)" :class="video.detail === m.detail ? 'active' : ''" v-for="(m, n) in right.historyData" :key="n"><span class="title">{{m.name}}</span><span @click.stop="removeItem(m)" class="detail-delete">{{$t('delete')}}</span></li>
</ul>
</div>
</div>
</transition>
<div class="play-mask" v-if="right.listData.length === 0 && right.historyData.length === 0">{{$t('no_history')}}</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
import history from '../lib/dexie/history'
import video from '../lib/dexie/video'
import mini from '../lib/dexie/mini'
import 'xgplayer'
import Hls from 'xgplayer-hls.js'
const { ipcRenderer: ipc } = require('electron')
export default {
name: 'play',
data () {
return {
xg: null,
right: {
show: false,
type: '',
listData: [],
historyData: []
},
config: {
id: 'xg',
lang: 'zh-cn',
url: '',
fluid: true,
autoplay: false,
videoInit: true,
screenShot: true,
keyShortcut: 'on',
crossOrigin: true,
cssFullscreen: true,
defaultPlaybackRate: 1,
playbackRate: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5]
},
name: '',
timer: null,
scroll: false,
more: true,
showNext: false,
isStar: false,
isTop: false,
mask: false
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
filters: {
ftName (e) {
return e.split('$')[0]
}
},
watch: {
view () {
this.right.show = false
this.right.type = ''
},
video: {
handler () {
this.getUrls()
},
deep: true
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
getUrls () {
this.mask = true
if (this.timer !== null) {
clearInterval(this.timer)
this.timer = null
}
if (this.xg) {
this.xg.pause()
}
this.changeVideo()
tools.detail_get(this.video.site, this.video.detail).then(res => {
this.name = this.video.name
this.right.listData = res.m3u8_urls
if (res.m3u8_urls.length > 1) {
const m3 = res.m3u8_urls
const arr = []
for (const i of m3) {
arr.push(i.split('$')[1])
}
this.xg.src = arr[this.video.index]
this.showNext = true
} else {
const link = res.m3u8_urls[this.video.index]
const src = link.split('$')[1]
this.xg.src = src
this.showNext = false
}
const currentTime = this.video.currentTime
if (currentTime !== '') {
this.xg.play()
this.xg.once('playing', () => {
this.xg.currentTime = currentTime
})
} else {
this.xg.play()
}
this.xg.once('play', () => {
this.mask = false
})
this.onPlayVideo()
this.xg.once('ended', () => {
if (res.m3u8_urls.length > 1 && (res.m3u8_urls.length - 1 > this.video.index)) {
this.video.currentTime = 0
this.video.index++
}
this.xg.off('ended')
})
})
},
changeVideo () {
this.checkStar()
this.checkTop()
this.name = ''
},
checkStar () {
video.find({ detail: this.video.detail }).then(res => {
if (res) {
this.isStar = true
} else {
this.isStar = false
}
})
},
checkTop () {
ipc.send('checkTop')
ipc.on('isTop', (e, flag) => {
this.isTop = flag
})
},
onPlayVideo () {
this.more = true
const h = { ...this.video }
history.find({ detail: h.detail }).then(res => {
if (res) {
h.id = res.id
history.update(res.id, h)
} else {
h.currentTime = ''
delete h.id
history.add(h)
}
})
this.timerEvent(h.detail)
},
timerEvent (d) {
this.timer = setInterval(() => {
history.find({ detail: d }).then(res => {
if (res) {
const h = { ...this.video }
h.currentTime = this.xg.currentTime
delete h.id
history.update(res.id, h)
}
})
}, 10000)
},
closeEvent () {
this.right.show = false
this.right.type = ''
},
nextEvent () {
const v = { ...this.video }
const i = v.index + 1
if (i < this.right.listData.length) {
this.video.currentTime = 0
this.video.index++
} else {
this.$m.warning(this.$t('last_video'))
}
},
listEvent () {
if (this.right.type === 'list') {
this.right.show = false
this.right.type = ''
} else {
this.right.show = true
this.right.type = 'list'
}
},
historyEvent () {
if (this.right.type === 'history') {
this.right.show = false
this.right.type = ''
} else {
this.right.show = true
this.right.type = 'history'
}
history.all().then(res => {
this.right.historyData = res.reverse()
})
},
starEvent () {
video.find({ detail: this.video.detail }).then(res => {
if (res !== undefined) {
video.remove(res.id).then(r => {
this.$m.info(this.$t('delete_success'))
this.isStar = false
})
} else {
const v = { ...this.video }
if (v.id) {
delete v.id
}
video.add(v).then(r => {
this.$m.success(this.$t('star_success'))
this.isStar = true
})
}
})
},
detailEvent () {
this.detail = {
show: true,
v: this.video
}
},
smallEvent () {
this.xg.pause()
mini.find().then(res => {
const d = { ...this.video }
d.currentTime = this.xg.currentTime
d.id = 0
if (res) {
mini.update(d)
} else {
mini.add(d)
}
ipc.send('min')
ipc.send('mini')
})
},
shareEvent () {
this.share = {
show: true,
v: this.video
}
},
clearAll () {
history.clear().then(res => {
this.right.historyData = []
})
},
listItemEvent (n) {
this.video.index = n
this.right.show = false
this.right.type = ''
},
historyItemEvent (e) {
this.video = e
this.right.show = false
this.right.type = ''
},
removeItem (e) {
history.remove(e.id).then(res => {
history.all().then(e => {
this.right.historyData = e.reverse()
})
})
}
},
mounted () {
this.xg = new Hls(this.config)
history.all().then(res => {
this.right.historyData = res
})
}
}
</script>
<style lang="scss" scoped>
.play{
position: relative;
height: 660px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
.box{
width: 92%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.title{
width: 100%;
height: 40px;
display: flex;
justify-content: flex-start;
align-items: center;
}
.more{
width: 100%;
height: 60px;
display: flex;
justify-content: flex-start;
align-items: center;
span{
display: flex;
margin-right: 10px;
cursor: pointer;
}
}
.mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 600;
opacity: 0.98;
}
}
.list{
position: absolute;
top: 0;
right: 0;
width: 300px;
height: 100%;
z-index: 555;
border-radius: 3px;
padding: 6px;
display: flex;
flex-direction: column;
.list-top{
display: flex;
justify-content: space-between;
align-items: center;
height: 30px;
.list-top-title{
font-size: 16px;
}
.list-top-close{
display: inline-block;
cursor: pointer;
}
}
.list-body{
flex: 1;
overflow-y: auto;
ul{
margin: 0;
padding: 0;
list-style: none;
li{
position: relative;
height: 28px;
width: 100%;
line-height: 28px;
padding-left: 10px;
font-size: 14px;
cursor: pointer;
.title{
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 231px;
}
.detail-delete{
display: none;
position: absolute;
right: 0;
height: 28px;
width: 50px;
text-align: center;
}
}
}
}
}
.slideX-enter-active, .slideX-leave-active{
transition: all .5s ease-in-out;
}
.slideX-enter, .slideX-leave-to{
transform: translateX(100%);
opacity: 0;
}
.play-mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 900;
display: flex;
font-size: 14px;
border-radius: 5px;
justify-content: center;
align-items: center;
}
}
</style>

246
src/components/Setting.vue Normal file
View File

@@ -0,0 +1,246 @@
<template>
<div class="setting">
<div class="setting-box zy-scroll" v-if="show.setting">
<div class="logo"><img src="@/assets/image/logo.png"></div>
<div class="info">
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player')">{{$t('website')}}</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">v{{pkg.version}} {{$t('issues')}}</a>
</div>
<div class="change">
<div class="zy-select" @mouseleave="show.language = false">
<div class="vs-placeholder" @click="show.language = true">{{$t('language')}}</div>
<div class="vs-options" v-show="show.language">
<ul>
<li :class="s.language === i.key ? 'active' : ''" v-for="(i, j) in languages" :key="j" @click="languageClick(i.key)">{{ i.name }}</li>
</ul>
</div>
</div>
<div class="zy-select" @mouseleave="show.site = false">
<div class="vs-placeholder" @click="show.site = true">{{$t('default_site')}}</div>
<div class="vs-options" v-show="show.site">
<ul>
<li :class="s.site === i.key ? 'active' : ''" v-for="(i, j) in sites" :key="j" @click="siteClick(i.key)">{{ i.name }}</li>
</ul>
</div>
</div>
</div>
<div class="theme">
<div class="title">{{$t('theme')}}</div>
<div class="theme-box">
<div @click="changeTheme('light')" class="theme-item light">
<div class="theme-image">
<img src="../assets/image/light.png" alt="">
</div>
<div class="theme-name">Light</div>
</div>
<div @click="changeTheme('dark')" class="theme-item dark">
<div class="theme-image">
<img src="../assets/image/dark.png" alt="">
</div>
<div class="theme-name">Dark</div>
</div>
<div @click="changeTheme('green')" class="theme-item green">
<div class="theme-image">
<img src="../assets/image/green.png" alt="">
</div>
<div class="theme-name">Green</div>
</div>
</div>
</div>
<div class="qrcode">
<div class="title">{{$t('donate')}}</div>
<div class="qrcode-box">
<img class="qrcode-item" src="../assets/image/alipay.png">
<img class="qrcode-item" src="../assets/image/wepay.jpg">
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import setting from '../lib/dexie/setting'
import { sites } from '../lib/site/sites'
import '../lib/cloud/index.js'
import { shell } from 'electron'
import pkg from '../../package.json'
export default {
name: 'setting',
data () {
return {
pkg: pkg,
s: {},
languages: [
{
key: 'zhCn',
name: '中文'
},
{
key: 'en',
name: 'English'
}
],
sites: sites,
show: {
setting: false,
language: false,
site: false
}
}
},
computed: {
theme: {
get () {
return this.$store.getters.getTheme
},
set (val) {
this.SET_THEME(val)
}
},
language: {
get () {
return this.$store.getters.getLanguage
},
set (val) {
this.SET_LANGUAGE(val)
}
},
site: {
get () {
return this.$store.getters.getSite
},
set (val) {
this.SET_SITE(val)
}
}
},
methods: {
...mapMutations(['SET_THEME', 'SET_LANGUAGE', 'SET_SITE']),
linkOpen (e) {
shell.openExternal(e)
},
languageClick (e) {
this.language = e
this.show.language = false
this.$i18n.locale = e
this.s.language = e
setting.update(this.s).then(res => {
this.$m.success(this.$t('set_success'))
})
},
siteClick (e) {
this.site = e
this.show.site = false
this.s.site = e
setting.update(this.s).then(res => {
this.$m.success(this.$t('set_success'))
})
},
changeTheme (e) {
this.theme = e
this.s.theme = e
setting.update(this.s).then(res => {
this.$m.success(this.$t('set_success'))
})
}
},
created () {
setting.find().then(res => {
this.s = res
this.theme = res.theme
this.$i18n.locale = this.s.language
this.show.setting = true
})
}
}
</script>
<style lang="scss" scoped>
.setting{
height: 660px;
width: 100%;
padding: 20px 0;
.setting-box{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
border-radius: 5px;
overflow-y: auto;
}
.logo{
margin-top: 10px;
width: 100%;
text-align: center;
img{
width: 120px;
height: auto;
}
}
.info{
width: 100%;
margin-top: 20px;
text-align: center;
a{
text-decoration: none;
margin: 0 10px;
font-size: 14px;
cursor: pointer;
}
}
.change{
width: 100%;
display: flex;
justify-content: flex-start;
padding-left: 20px;
margin-top: 40px;
.zy-select{
margin-right: 20px;
}
}
.theme{
width: 100%;
padding-left: 20px;
margin-top: 20px;
.theme-box{
display: flex;
justify-content: flex-start;
margin-top: 10px;
.theme-item{
width: 200px;
height: 180px;
margin-right: 20px;
cursor: pointer;
border-radius: 2px;
.theme-image{
width: 180px;
margin: 10px auto;
img{
width: 100%;
}
}
.theme-name{
width: 100%;
text-align: center;
}
}
}
}
.qrcode{
width: 100%;
padding-left: 20px;
margin-top: 20px;
margin-bottom: 20px;
.qrcode-box{
display: flex;
justify-content: flex-start;
margin-top: 10px;
.qrcode-item{
width: auto;
height: 300px;
margin-right: 20px;
border-radius: 2px;
}
}
}
}
</style>

197
src/components/Share.vue Normal file
View File

@@ -0,0 +1,197 @@
<template>
<div class="share" id="share" @click="shareClickEvent">
<div class="left">
<img :src="this.card.img" alt="">
</div>
<div class="right">
<div class="title">{{ card.name }}</div>
<qrcode-vue id="qr" :value="value" :size="160" level="L" />
<div class="tips">
<p>{{$t('qr_tips')}}</p>
<p><img src="@/assets/image/logo.png"></p>
<p class="zy">{{$t('zy_tips')}}</p>
</div>
</div>
<div class="share-mask" v-show="loading">
<div class="loader"></div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
import QrcodeVue from 'qrcode.vue'
import html2canvas from 'html2canvas'
const { clipboard, nativeImage } = require('electron')
export default {
name: 'share',
data () {
return {
card: {
img: '',
name: '',
png: ''
},
value: 'https://www.baidu.com',
loading: true
}
},
components: {
QrcodeVue
},
computed: {
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
watch: {
share: {
handler () {
this.getDetail()
},
deep: true
}
},
methods: {
...mapMutations(['SET_SHARE']),
getDetail () {
this.loading = true
tools.detail_get(this.share.v.site, this.share.v.detail).then(res => {
const info = res.info
const parser = new DOMParser()
const html = parser.parseFromString(info, 'text/html')
const img = html.querySelector('img').src
this.card.img = img
this.card.name = this.share.v.name
const urls = res.m3u8_urls
const url = urls[this.share.v.index].split('$')[1]
this.value = 'http://zyplayer.fun/player/player.html?url=' + url + '&title=' + this.share.v.name
this.loading = false
this.$nextTick(() => {
const dom = document.getElementById('share')
html2canvas(dom, { allowTaint: true, useCORS: true }).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$m.success(this.$t('share_tips'))
this.share.show = true
})
})
})
},
shareClickEvent () {
this.share = {
show: false,
v: {}
}
}
},
created () {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.share{
position: absolute;
bottom: 20px;
right: 20px;
width: 540px;
height: 360px;
border-radius: 2px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
z-index: 888;
.left, .right{
width: 50%;
height: 100%;
}
.left{
display: flex;
justify-content: center;
align-items: center;
img{
height: 320px;
width: auto;
max-width: 240px;
}
}
.right{
.title{
font-size: 18px;
margin-bottom: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#qr{
text-align: center;
}
.tips{
font-size: 14px;
text-align: center;
img{
width: 50px;
}
.zy{
font-size: 12px;
}
}
}
.share-mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.loader {
color: #823aa055;
font-size: 8px;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
text-indent: -9999em;
animation: load4 1.3s infinite linear;
transform: translateZ(0);
}
@keyframes load4 {
0%,
100% {
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
}
}
</style>

159
src/components/Star.vue Normal file
View File

@@ -0,0 +1,159 @@
<template>
<div class="star">
<div class="zy-table">
<div class="tHead">
<span class="name">{{$t('videoName')}}</span>
<span class="type">{{$t('type')}}</span>
<span class="time">{{$t('time')}}</span>
<span class="from">{{$t('from')}}</span>
<span class="operate" style="width: 170px">{{$t('operate')}}</span>
</div>
<div class="tBody zy-scroll">
<ul v-show="!loading">
<li v-for="(i, j) in data" :key="j" @click="detailEvent(i)">
<span class="name">{{i.name}}</span>
<span class="type">{{i.type}}</span>
<span class="time">{{i.time}}</span>
<span class="from">{{i.site | ftSite}}</span>
<span class="operate" style="width: 170px">
<span class="btn" @click.stop="playEvent(i)">{{$t('play')}}</span>
<span class="btn" @click.stop="deleteEvent(i)">{{$t('delete')}}</span>
<span class="btn" @click.stop="shareEvent(i)">{{$t('share')}}</span>
<span class="btn" @click.stop="updateEvent(i)">{{$t('sync')}}</span>
</span>
</li>
</ul>
<div class="tBody-mask" v-show="loading">
<div class="loader"></div>
</div>
</div>
<div class="tFooter">
<span class="tFooter-span">{{data.length}} {{$t('total')}}</span>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import tools from '../lib/site/tools'
import video from '../lib/dexie/video'
import { sites, getSite } from '../lib/site/sites'
export default {
name: 'star',
data () {
return {
sites: sites,
data: [],
loading: true,
checkFlag: false
}
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
}
},
filters: {
ftSite (e) {
const name = getSite(e).name
return name
}
},
watch: {
view () {
this.getAllStar()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
detailEvent (e) {
this.detail = {
show: true,
v: e
}
},
playEvent (e) {
this.video = e
this.view = 'Play'
},
deleteEvent (e) {
video.remove(e.id).then(res => {
if (res) {
this.$m.warning(this.$t('delete_failed'))
} else {
this.$m.success(this.$t('delete_success'))
}
this.getAllStar()
})
},
shareEvent (e) {
this.share = {
show: true,
v: e
}
},
updateEvent (e) {
tools.detail_get(e.site, e.detail).then(res => {
const nameOne = e.name.replace(/\s*/g, '')
const nameTwo = res.name.replace(/\s*/g, '')
if (nameOne === nameTwo) {
this.$m.info(this.$t('async_failed'))
} else {
const h = e
h.name = res.name
video.update(h.id, h).then(res => {
this.$m.success(this.$t('async_success'))
})
}
})
},
getAllStar () {
video.all().then(res => {
this.data = res.reverse()
this.loading = false
})
}
},
created () {
this.getAllStar()
}
}
</script>
<style lang="scss" scoped>
.star{
height: 660px;
width: 100%;
display: flex;
flex-direction: column;
border-radius: 5px;
}
</style>

View File

@@ -1,159 +0,0 @@
<template>
<el-row class="detail">
<el-row class="detail-box" v-loading="loading">
<el-row v-html="detail.box" class="box"></el-row>
<el-row v-html="detail.info" class="info"></el-row>
<el-row class="urls">
<el-button size="mini" v-for="(i, j) in detail.urls" :key="j" @click="playBtn(i, j)">{{i | ftLink}}</el-button>
</el-row>
</el-row>
</el-row>
</template>
<script lang="ts">
import Vue from 'vue'
import zy from '@/lib/util.zy'
import { mapMutations } from 'vuex'
export default Vue.extend({
name: 'detail',
data () {
return {
detail: {},
loading: true
}
},
computed: {
d () {
return this.$store.getters.getDetail
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
Main: {
get () {
return this.$store.getters.getMain
},
set (val) {
this.SET_MAIN(val)
}
}
},
watch: {
d () {
this.getDetailEvent()
}
},
filters: {
ftLink (e: string) {
let name = e.split('$')[0]
return name
}
},
methods: {
...mapMutations(['SET_MAIN', 'SET_VIDEO']),
getDetailEvent () {
let url = this.d.video.detail
this.detail = {}
this.loading = true
zy.detail(url).then((res: any) => {
this.detail = res
this.loading = false
})
},
playBtn (i: string, j: number) {
if (this.Main !== 'Player') {
this.d.video.index = j
this.video = this.d.video
this.Main = 'Player'
} else {
this.d.video.index = j
this.video = this.d.video
}
this.d.show = false
}
},
created () {
this.getDetailEvent()
},
mounted () {}
})
</script>
<style lang="scss">
.detail{
box-sizing: border-box;
padding: 0 60px;
.detail-box{
width: 100%;
.box{
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
width: 100%;
padding: 10px;
.vodImg{
width: 200px;
img{
width: 100%;
height: auto;
}
}
.vodAd{
display: none;
}
.vodInfo{
flex: 1;
margin-left: 20px;
.vodh{
h2{
display: inline-block;
}
span{
font-size: 12px;
margin-left: 10px;
}
label{
font-size: 20px;
font-weight: bold;
margin-left: 20px;
}
}
.cont, .tags{
display: none;
}
li{
list-style: none;
font-size: 14px;
height: 20px;
overflow: hidden;
a{
display: none;
pointer-events: none;
}
span{
word-wrap:break-word
}
}
}
}
.info, .urls{
padding: 10px;
margin-top: 10px;
}
.info{
font-size: 14px;
}
.urls{
margin-bottom: 20px;
padding-bottom: 0;
button{
margin: 0 10px 10px 0;
}
}
}
}
</style>

View File

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

32
src/lib/cloud/index.js Normal file
View File

@@ -0,0 +1,32 @@
import setting from '../dexie/setting'
const os = require('os')
const macadress = require('macaddress')
const AV = require('leancloud-storage')
setting.find().then(res => {
const cloud = res.cloud
if (!cloud) {
macadress.one((err, mac) => {
if (err) {
return false
}
const system = os.hostname() + ' ' + os.type() + ' ' + os.arch()
AV.init({
appId: 'X6TRIcMjgOG7EJ0t1l5r9In1-gzGzoHsz',
appKey: 'JmkGF9UqkWGQNYDcJ2g1QV1b',
serverURL: 'https://x6tricmj.lc-cn-n1-shared.com'
})
const ZYPlayer = AV.Object.extend('ZYPlayer')
const zyPlayer = new ZYPlayer()
zyPlayer.set('os', system)
zyPlayer.set('mac', mac)
zyPlayer.save().then(e => {
const id = e.id
res.cloud = true
res.cloudKey = id
setting.update(res)
})
})
}
})

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

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

33
src/lib/dexie/index.js Normal file
View File

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

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

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

10
src/lib/dexie/setting.js Normal file
View File

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

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

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

6
src/lib/element/index.js Normal file
View File

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

1232
src/lib/site/sites.js Normal file

File diff suppressed because it is too large Load Diff

364
src/lib/site/tools.js Normal file
View File

@@ -0,0 +1,364 @@
import axios from 'axios'
import { getSite } from './sites'
const zy = {
key: 'zuidazy', // sites[n] 视频源
id: 0, // 视频类型
page: 1, // 第几页
keywords: '', // 搜索关键字
// 获取浏览列表
film_get (key, id = 0, page = 1) {
return new Promise((resolve, reject) => {
const site = getSite(key)
let url = ''
if (id === 0) {
url = site.new.replace(/{page}/, page)
} else {
url = site.view.replace(/{id}/, id).replace(/{page}/, page)
}
const type = site.type
axios.get(url).then(async res => {
const data = res.data
if (type === 0) {
const zeroData = await this.film_get_type_zero(data, key)
resolve(zeroData)
}
if (type === 1) {
const oneData = await this.film_get_type_one(data, key)
resolve(oneData)
}
if (type === 2) {
const twoData = await this.film_get_type_two(data, key)
resolve(twoData)
}
}).catch(err => {
reject(err)
})
})
},
film_get_type_zero (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.xing_vb li')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 1; i < list.length - 1; i++) {
const info = {
site: key,
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[3].innerText,
time: list[i].childNodes[5].innerText,
detail: url + list[i].childNodes[1].querySelector('a').getAttribute('href'),
index: 0
}
d.list.push(info)
}
d.update = parseInt(html.querySelectorAll('.xing_top_right li strong')[0].innerText)
let t = html.querySelector('.pages').innerText
t = t.split('条')[0]
t = t.split('共')[1]
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
})
},
film_get_type_one (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.videoContent li')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 0; i < list.length; i++) {
const info = {
site: key,
name: list[i].querySelector('.videoName').innerText,
type: list[i].querySelector('.category').innerText,
time: list[i].querySelector('.time').innerText,
detail: url + list[i].querySelector('.address').getAttribute('href'),
index: 0
}
d.list.push(info)
}
d.update = parseInt(html.querySelectorAll('.header_list li span')[0].innerText)
let t = html.querySelectorAll('.pagination li')
t = t[t.length - 2].innerText
d.total = parseInt(t) * 50
resolve(d)
} catch (err) {
reject(err)
}
})
},
film_get_type_two (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.nr')
const d = { list: [], total: 0, update: 0 }
const url = getSite(key).url
for (let i = 0; i < list.length; i++) {
const info = {
site: key,
name: '',
type: list[i].querySelector('.btn_span').innerText,
time: list[i].querySelector('.hours').innerText,
detail: url + list[i].querySelector('.name').getAttribute('href'),
index: 0
}
let name = list[i].querySelector('.name').innerText
name = name.replace(/^\s*|\s*$/g, '')
info.name = name
d.list.push(info)
}
d.update = parseInt(html.querySelector('.kfs em').innerText)
d.total = parseInt(html.querySelector('.date span').innerText)
let t = html.querySelector('.pag2').innerText
t = t.split('条')[0]
t = t.split('共')[1]
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
})
},
// 获取详情
detail_get (key, url) {
return new Promise((resolve, reject) => {
const type = getSite(key).type
axios.get(url).then(async res => {
if (type === 0) {
const zeroData = await this.detail_get_type_zero(res.data, key)
resolve(zeroData)
}
if (type === 1) {
const oneData = await this.detail_get_type_one(res.data, key)
resolve(oneData)
}
if (type === 2) {
const twoData = await this.detail_get_type_two(res.data, key)
resolve(twoData)
}
}).catch(err => {
reject(err)
})
})
},
detail_get_type_zero (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
}
const vodBox = html.querySelector('.vodBox')
data.info = vodBox.innerHTML
const title = html.querySelector('.vodh h2').innerText
const index = html.querySelector('.vodh span').innerText
data.name = title + index
const vodInfo = html.querySelectorAll('.playBox')
for (let i = 0; i < vodInfo.length; i++) {
const k = vodInfo[i].innerText
if (k.indexOf('剧情介绍') >= 0) {
data.desc = vodInfo[i].querySelector('.vodplayinfo').innerHTML
}
}
const vodLi = html.querySelectorAll('.ibox .vodplayinfo li')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].innerText
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.push(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.push(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
detail_get_type_one (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
}
let name = html.querySelector('.whitetitle').innerText
name = name.split('')[1].replace(/^\s*|\s*$/g, '')
data.name = name
const vodBox = html.querySelector('.white').innerHTML
data.info = vodBox
const vodInfo = html.querySelectorAll('.white')
for (let i = 0; i < vodInfo.length; i++) {
const k = vodInfo[i].innerText
if (k.indexOf('剧情介绍') >= 0) {
data.desc = vodInfo[i].querySelector('div').innerText
}
}
const vodLi = html.querySelectorAll('.playlist li #m3u8')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].value
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.push(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.push(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
detail_get_type_two (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const data = {
site: key,
name: '',
info: '',
desc: '',
m3u8_urls: [],
mp4_urls: []
}
const title = html.querySelector('.vodh h2').innerText
const index = html.querySelector('.vodh span').innerText
data.name = title + index
const vodBox = html.querySelector('.vodBox').innerHTML
data.info = vodBox
data.desc = html.querySelector('.vodplayinfo').innerText
const vodLi = html.querySelectorAll('.vodplayinfo li')
const m3u8UrlArr = []
const mp4UrlArr = []
for (let i = 0; i < vodLi.length; i++) {
const j = vodLi[i].innerText
if (j.indexOf('.m3u8') >= 0) {
m3u8UrlArr.push(j)
}
if (j.indexOf('.mp4') >= 0) {
mp4UrlArr.push(j)
}
}
data.m3u8_urls = m3u8UrlArr
data.mp4_urls = mp4UrlArr
resolve(data)
} catch (err) {
reject(err)
}
})
},
// 搜索列表
search_get (key, keywords = '', page = 1) {
return new Promise((resolve, reject) => {
const site = getSite(key)
const type = site.type
let url = null
if (type === 0) {
url = site.search.replace(/{page}/, page).replace(/{keywords}/, keywords)
}
if (type === 1) {
url = site.search.replace(/{keywords}/, keywords)
}
axios.get(url).then(async res => {
const data = res.data
if (type === 0) {
const zeroData = await this.search_get_type_zero(data, key)
resolve(zeroData)
}
if (type === 1) {
const oneData = await this.search_get_type_one(data, key)
resolve(oneData)
}
}).catch(err => {
reject(err)
})
})
},
search_get_type_zero (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.xing_vb li')
const d = { list: [], total: 0 }
const url = getSite(key).url
for (let i = 1; i < list.length - 1; i++) {
const info = {
site: key,
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[3].innerText,
time: list[i].childNodes[5].innerText,
detail: url + list[i].childNodes[1].querySelector('a').getAttribute('href'),
index: 0
}
d.list.push(info)
}
const t = html.querySelector('.nvc dd').innerText.replace(/[^\d]/g, '')
d.total = parseInt(t)
resolve(d)
} catch (err) {
reject(err)
}
})
},
search_get_type_one (txt, key) {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list = html.querySelectorAll('.videoContent li')
const d = { list: [], total: 0 }
const url = getSite(key).url
for (let i = 0; i < list.length; i++) {
const info = {
site: key,
name: list[i].querySelector('.videoName').innerText,
type: list[i].querySelector('.category').innerText,
time: list[i].querySelector('.time').innerText,
detail: url + list[i].querySelector('.address').getAttribute('href'),
index: 0
}
d.list.push(info)
}
d.total = list.length
resolve(d)
} catch (err) {
reject(err)
}
})
}
}
export default zy

View File

@@ -1,41 +0,0 @@
interface Site {
id: string
name: string
url: string
type: Array<any>
}
const sites:Array<Site> = [
{
id: 'okzy',
name: 'OK资源网',
url: 'https://www.okzy.co',
type: [[ '最新', '0' ], [ '电影', '1' ], [ '电视剧', '2' ], [ '综艺', '3' ], [ '动漫', '4' ], [ '伦理', '21' ], [ '福利', '22' ], [ '解说', '33' ]]
},
{
id: 'zuidazy',
name: '最大资源网',
url: 'http://www.zuidazy1.com',
type: [[ '最新', '0' ], [ '电影', '1' ], [ '电视剧', '2' ], [ '综艺', '3' ], [ '动漫', '4' ], [ '福利', '16' ], [ '伦理', '17' ], [ '音乐', '18' ]]
},
{
id: 'subo',
name: '速播资源站',
url: 'https://www.subo988.com',
type: [[ '最新', '0' ], [ '电影', '1' ], [ '电视剧', '2' ], [ '综艺', '3' ], [ '动漫', '4' ], [ '伦理', '16' ], [ '音乐', '20' ]]
},
{
id: 'zuixinzy',
name: '最新资源网',
url: 'http://www.zuixinzy.cc',
type: [[ '最新', '0' ], [ '电影', '1' ], [ '电视剧', '2' ], [ '综艺', '3' ], [ '动漫', '4' ], ['伦理', '21'], ['情色', '23'], [ '福利', '30' ], [ '解说', '34' ]]
},
{
id: '123ku',
name: '123资源网',
url: 'https://www.123ku.com',
type: [[ '最新', '0' ], [ '电影', '1' ], [ '电视剧', '2' ], [ '综艺', '3' ], [ '动漫', '4' ], [ '伦理', '16' ]]
}
]
export default sites

View File

@@ -1,171 +0,0 @@
import fly from 'flyio'
import sites from './sites'
import { TimeSelect } from 'element-ui'
interface ZY {
num: number
page: number
key: string
site: any
list: Array<string>
getInfoRequire: any
getInfoHtml: any
info: any
detail: any
films: any
}
interface info {
name?: string
type?: string
time?: string
detail?: string
urls?: Array<string>
index?: number
}
interface detail {
box?: any
info?: any
urls?: Array<string>
}
const zy: ZY = {
num: 0,
page: 1,
key: '',
site: {},
list: [],
getInfoRequire () {
return new Promise((resolve, reject) => {
let key = encodeURI(this.key)
const params = `${this.site.url}/index.php?m=vod-search-pg-${this.page}-wd-${key}.html`
fly.get(params).then(res => {
this.getInfoHtml(res.data).then((data: any) => {
resolve(data)
})
}).catch(err => {
reject(err)
})
})
},
getInfoHtml (txt: string): any {
return new Promise((resolve, reject) => {
try {
const parser = new DOMParser()
const html = parser.parseFromString(txt, 'text/html')
const list: any = html.querySelectorAll('.xing_vb li')
let d: any = { list: [], total: 0 }
for (let i = 1; i < list.length - 1; i++) {
let info: info = {
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[3].innerText,
time: list[i].childNodes[5].innerText,
detail: this.site.url + list[i].childNodes[1].childNodes[0].getAttribute('href'),
index: 0,
urls: []
}
d.list.push(info)
}
let num: any = (<HTMLImageElement>html.querySelectorAll('.nvc dd span')[1]).innerText
num = parseInt(num)
d.total = num
resolve(d)
} catch (err) {
reject(err)
}
})
},
info (n: number = 0, p: number = 1, k: string = '') {
return new Promise((resolve, reject) => {
this.num = n
this.page = p
this.key = k
this.site = sites[n]
this.getInfoRequire().then((res: any) => {
resolve(res)
}).catch((err: any) => {
reject(err)
})
})
},
detail (url: string) {
return new Promise((resolve, reject) => {
if (!url) return
fly.get(url).then(res => {
const parser = new DOMParser()
let html = parser.parseFromString(res.data, 'text/html')
let data: detail = {
box: '',
info: '',
urls: []
}
let vodBox = html.querySelector('.vodBox')
if (vodBox) {
data.box = vodBox.innerHTML
}
let vodInfo = {}
if (url.indexOf('123ku') !== -1 || url.indexOf('subo988') !== -1) {
vodInfo = html.querySelectorAll('.vodplayinfo')[1]
} else {
vodInfo = <HTMLImageElement>html.querySelector('.vodplayinfo')
}
data.info = (<HTMLImageElement>vodInfo).innerText
let urls = html.querySelectorAll('.vodplayinfo li')
let arr = []
for (let i in urls) {
let j = (<HTMLImageElement>urls[i]).innerText
if (j !== undefined && j.indexOf('.m3u8') !== -1) {
arr.push((<HTMLImageElement>urls[i]).innerText)
}
}
data.urls = arr
resolve(data)
}).catch(err => {
reject(err)
})
})
},
films (n: number = 0, p: number = 1, type: string = '0') {
return new Promise((resolve, reject) => {
this.site = sites[n]
let url: string = sites[n].url
let params: string = ''
if (type === '0') {
params = `${url}/?m=vod-index-pg-${p}.html`
} else {
params = `${url}/?m=vod-type-id-${type}-pg-${p}.html`
}
fly.get(params).then(res => {
const parser = new DOMParser()
const html = parser.parseFromString(res.data, 'text/html')
const list: any = html.querySelectorAll('.xing_vb li')
let d: any = { list: [], total: 0 }
for (let i = 1; i < list.length - 1; i++) {
let info: info = {
name: list[i].childNodes[1].innerText,
type: list[i].childNodes[3].innerText,
time: list[i].childNodes[5].innerText,
detail: this.site.url + list[i].querySelector('a').getAttribute('href'),
index: 0,
urls: []
}
d.list.push(info)
}
let num: any = html.querySelectorAll('.pages')
if (num) {
let n = num[0].innerText
n = n.split('条')[0]
n = n.split('共')[1]
n = parseInt(n)
d.total = n
}
resolve(d)
}).catch(err => {
reject(err)
})
})
}
}
export default zy

48
src/locales/en.json Normal file
View File

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

9
src/locales/index.js Normal file
View File

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

48
src/locales/zh-cn.json Normal file
View File

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

26
src/main.js Normal file
View File

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

View File

@@ -1,14 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import './plugins/element.ts'
import Register from './page/register'
Vue.config.productionTip = false
Register.registerComponents()
new Vue({
store,
render: h => h(App)
}).$mount('#app')

279
src/mini/Mini.vue Normal file
View File

@@ -0,0 +1,279 @@
<template>
<div class="mini">
<div class="top">
<div class="left">
<span class="number" v-show="show.number">{{index + 1}} / {{length}}</span>
<span class="zy-svg" @click="prevEvent" v-show="show.prev">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="backIconTitle">
<title id="backIconTitle">上一集</title>
<path d="M14 14.74L21 19V5l-7 4.26V5L2 12l12 7v-4.26z"></path>
</svg>
</span>
<span class="zy-svg" @click="nextEvent" v-show="show.next">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="forwardIconTitle">
<title id="forwardIconTitle">下一集</title>
<path d="M10 14.74L3 19V5l7 4.26V5l12 7-12 7v-4.26z"></path>
</svg>
</span>
<span class="opacity">
<input type="number" min="5" max="100" v-model="opacity" @change="opacityChange"/>
</span>
</div>
<div class="right">
<span class="min" @click="frameClickEvent('miniMin')"></span>
<span class="close" @click="frameClickEvent('miniClose')"></span>
</div>
</div>
<div class="bottom">
<div id="xg"></div>
</div>
</div>
</template>
<script>
import tools from '../lib/site/tools'
import mini from '../lib/dexie/mini'
import history from '../lib/dexie/history'
import 'xgplayer'
import Hls from 'xgplayer-hls.js'
const ipc = require('electron').ipcRenderer
export default {
name: 'mini',
data () {
return {
xg: null,
config: {
id: 'xg',
lang: 'zh-cn',
url: '',
fluid: true,
autoplay: false,
videoInit: true,
screenShot: true,
keyShortcut: 'on',
crossOrigin: true,
defaultPlaybackRate: 1,
playbackRate: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5]
},
opacity: 100,
video: {},
d: {},
show: {
prev: false,
next: false,
number: false
},
index: 0,
length: 0
}
},
methods: {
frameClickEvent (e) {
ipc.send(e)
},
opacityChange (e) {
ipc.send('mini-opacity', this.opacity / 100)
},
getUrls () {
mini.find().then(res => {
const v = res
if (v.index > 0) {
this.show.next = true
this.show.number = true
}
this.video = res
tools.detail_get(v.site, v.detail).then(res => {
this.d = res
if (v.index >= this.d.m3u8_urls.length) {
this.show.next = false
}
this.index = v.index
this.length = this.d.m3u8_urls.length
const link = res.m3u8_urls[v.index]
const src = link.split('$')[1]
this.xg.src = src
const currentTime = v.currentTime
if (currentTime !== '') {
this.xg.play()
this.xg.once('playing', () => {
this.xg.currentTime = currentTime
})
} else {
this.xg.play()
}
this.onPlayVideo()
})
})
},
onPlayVideo () {
const h = { ...this.video }
history.find({ detail: h.detail }).then(res => {
if (res) {
h.id = res.id
history.update(res.id, h)
} else {
h.currentTime = ''
delete h.id
history.add(h)
}
})
this.timerEvent(h.detail)
},
timerEvent (d) {
this.timer = setInterval(() => {
history.find({ detail: d }).then(res => {
if (res) {
const h = { ...this.video }
h.currentTime = this.xg.currentTime
h.id = res.id
history.update(res.id, h)
}
})
}, 10000)
},
prevEvent () {
if (this.index > 0) {
this.index--
let src = this.d.m3u8_urls[this.index]
src = src.split('$')[1]
this.xg.src = src
this.show.next = true
}
if (this.index === 0) {
this.show.prev = false
}
},
nextEvent () {
if (this.index < this.d.m3u8_urls.length - 1) {
this.index++
let src = this.d.m3u8_urls[this.index]
src = src.split('$')[1]
this.xg.src = src
this.show.prev = true
}
if (this.index === this.d.m3u8_urls.length - 1) {
this.show.next = false
}
}
},
created () {
this.getUrls()
},
mounted () {
this.xg = new Hls(this.config)
}
}
</script>
<style lang="scss">
html,body{
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
background-color: #000;
}
.mini{
box-sizing: border-box;
.top{
-webkit-app-region: drag;
width: 100%;
height: 30px;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
.zy-svg{
-webkit-app-region: no-drag;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
svg{
width: 24px;
height: 24px;
stroke: #fff;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
}
.left{
display: flex;
justify-content: flex-start;
align-items: center;
height: 100%;
flex: 1;
.number{
color: #fff;
margin: 0 10px;
font-size: 12px;
}
.opacity{
-webkit-app-region: no-drag;
margin-left: 10px;
input{
text-indent: 4px;
background-color: #000;
color: #fff;
border: 1px solid #aaa;
}
}
}
.right{
width: 80px;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
span{
-webkit-app-region: no-drag;
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 10px;
cursor: pointer;
opacity: 0.5;
&.min{
background-color: #ffbe2a;
}
&.close{
background-color: #ff5f56;
}
&:hover{
animation: heartbeat 3s ease-in-out infinite both;
}
@keyframes heartbeat {
from {
transform: scale(1);
transform-origin: center center;
animation-timing-function: ease-out;
}
10% {
opacity: 1;
transform: scale(0.91);
animation-timing-function: ease-in;
}
17% {
transform: scale(0.98);
animation-timing-function: ease-out;
}
33% {
transform: scale(0.87);
animation-timing-function: ease-in;
}
45% {
transform: scale(1);
animation-timing-function: ease-out;
}
}
}
}
}
.bottom{
width: 100%;
height: 305px;
}
}
</style>

7
src/mini/main.js Normal file
View File

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

View File

@@ -1,172 +0,0 @@
<template>
<el-row class="film">
<el-row class="film-tabs">
<el-tabs v-model="tabs" @tab-click="tabClick">
<el-tab-pane class="film-tabpane" v-for="(i, j) in sites[site].type" :key="j" :label="i[0]" :name="i[1]"></el-tab-pane>
</el-tabs>
</el-row>
<el-row class="film-table-box table-box">
<el-table :data="filmData" stripe class="film-table" v-loading="loading" size="mini">
<el-table-column prop="name" label="影片名称"></el-table-column>
<el-table-column prop="type" label="影片类别" width="120"></el-table-column>
<el-table-column prop="time" label="更新时间" width="180"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="small" type="text" @click="tableBtnClick('detail', scope.row)">详情</el-button>
<el-button size="small" type="text" @click="tableBtnClick('star', scope.row)">收藏</el-button>
<el-button size="small" type="text" @click="tableBtnClick('play', scope.row)">播放</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row class="film-bottom" type="flex" justify="space-between">
<el-select v-model="site" placeholder="请选择" size="small" @change="selectSite">
<el-option
v-for="(i, j) in sites"
:key="i.id"
:label="i.name"
:value="j">
</el-option>
</el-select>
<el-pagination
small
layout="total, prev, pager, next, jumper"
:current-page="filmPage"
@current-change="pageChange"
:page-size="50"
:total="filmTotal">
</el-pagination>
</el-row>
</el-row>
</template>
<script lang="ts">
import Vue from 'vue'
import zy from '@/lib/util.zy'
import sites from '@/lib/sites'
import { mapMutations } from 'vuex'
import video from '@/plugins/dexie/video'
export default Vue.extend({
data () {
return {
sites: sites,
tabs: '0',
filmPage: 1,
filmTotal: 0,
filmData: [],
loading: false
}
},
computed: {
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
site: {
get () {
return this.$store.getters.getSite
},
set (val) {
this.SET_SITE(val)
}
},
Main: {
get () {
return this.$store.getters.getMain
},
set (val) {
this.SET_MAIN(val)
}
}
},
methods: {
...mapMutations(['SET_SITE', 'SET_DETAIL', 'SET_MAIN', 'SET_VIDEO']),
tabClick (tab:any) {
this.filmPage = 1
this.getFilmList(this.site, this.filmPage, tab.name)
},
selectSite (e:any) {
this.site = e
this.tabs = '0'
this.filmPage = 1
this.getFilmList(e, 1, '0')
},
pageChange (e:number) {
this.filmPage = e
this.getFilmList(this.site, this.filmPage, this.tabs)
},
getFilmList (n: number = 0, p: number = 1, type: string = '0') {
this.loading = true
this.filmData = []
this.tabs = type
zy.films(n, p, type).then((res: any) => {
this.filmTotal = res.total
this.filmData = res.list
this.filmPage = p
this.loading = false
})
},
tableBtnClick (type: string, e: any) {
if (type === 'detail') {
let d = {
show: true,
video: e
}
this.SET_DETAIL(d)
}
if (type === 'star') {
video.find({ detail: e.detail }).then(res => {
if (res) {
this.$message.warning('已存在')
} else {
video.add(e).then(res => {
this.$message.success('收藏成功')
})
}
})
}
if (type === 'play') {
this.Main = 'Player'
this.video = e
}
}
},
created () {
this.getFilmList()
}
})
</script>
<style lang="scss" scoped>
.film{
height: 100%;
position: relative;
.film-tabs{
position: absolute;
top: 0px;
left: 0;
width: 100%;
height: 60px;
}
.film-table-box{
position: absolute;
top: 54px;
width: 100%;
height: calc(100% - 100px);
overflow: auto;
&::-webkit-scrollbar{
width: 6px;
}
}
.film-bottom{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40px;
align-items: center;
}
}
</style>

View File

@@ -1,237 +0,0 @@
<template>
<el-row class="player">
<el-row class="player-show" v-if="Object.keys(video).length > 0">
<el-row class="player-title">
<el-row class="player-title-box" type="flex" justify="space-between">
<span>
<span>{{video.name ? video.name + ' -- ' : '' }}</span>
<span>{{ num }}</span>
</span>
<span>
<el-button size="mini" @click="openDetail" icon="el-icon-document" circle></el-button>
<el-button size="mini" v-show="!star" @click="starEvent" icon="el-icon-star-off" circle></el-button>
<el-button size="mini" v-show="star" @click="starEvent" icon="el-icon-star-on" circle></el-button>
</span>
</el-row>
</el-row>
<el-row class="player-box">
<div id="xg"></div>
</el-row>
<el-row class="player-films table-box">
<el-row class="player-films-box">
<el-button :type="j === video.index ? 'primary' : ''" size="mini" v-for="(i, j) in urls" :key="j" @click="playBtnClick(i, j)" plain>{{i | ftLink}}</el-button>
</el-row>
</el-row>
</el-row>
<el-row class="player-none" v-if="Object.keys(video).length <= 0">
<el-row class="tips">
浏览或者搜索资源, 点击播放视频, 即可观看~
</el-row>
<el-row class="btns">
<el-button size="small" @click="goView('Film')" icon="el-icon-film">浏览</el-button>
<el-button size="small" @click="goView('Search')" icon="el-icon-search">搜索</el-button>
</el-row>
</el-row>
</el-row>
</template>
<script lang="ts">
import Vue from 'vue'
import { mapMutations } from 'vuex'
import zy from '@/lib/util.zy'
import 'xgplayer'
// @ts-ignore
import Hls from 'xgplayer-hls.js'
import video from '@/plugins/dexie/video'
export default Vue.extend({
data () {
return {
xg: null,
config: {
id: 'xg',
url: '',
fluid: false,
autoplay: true,
videoInit: true,
keyShortcut: 'on',
defaultPlaybackRate: 1,
playbackRate: [0.5, 0.75, 1, 1.5, 2]
},
urls: [],
num: '',
star: false
}
},
computed: {
d () {
return this.$store.getters.getDetail
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
Main: {
get () {
return this.$store.getters.getMain
},
set (val) {
this.SET_MAIN(val)
}
}
},
watch: {
video: {
handler () {
this.getUrls()
},
deep: true
}
},
filters: {
ftLink (e: string) {
let name = e.split('$')[0]
return name
}
},
methods: {
...mapMutations(['SET_DETAIL', 'SET_VIDEO', 'SET_MAIN']),
goView (e: string) {
this.Main = e
},
openDetail () {
let d = {
show: true,
video: this.video
}
this.SET_DETAIL(d)
},
getUrls () {
if (this.xg) {
// @ts-ignore
this.xg.destroy(true)
this.xg = null
}
this.checkStar()
zy.detail(this.video.detail).then((res: any) => {
this.urls = res.urls
if (this.xg === null) {
let info: any = this.urls[this.video.index]
let url = info.split('$')[1]
this.num = info.split('$')[0]
this.config.url = url
this.$nextTick(() => {
this.xg = new Hls(this.config)
// @ts-ignore
this.xg.on('error', () => {
this.$message.error('播放失败请重试~')
})
})
}
})
},
checkStar () {
video.find({ detail: this.video.detail }).then(res => {
if (res) {
this.star = true
} else {
this.star = false
}
})
},
starEvent () {
video.find({ detail: this.video.detail }).then(res => {
if (res) {
video.remove(res.id).then(res => {
if (!res) {
this.$message.success('删除成功')
this.star = false
} else {
this.$message.warning('删除失败, 请重试~')
}
})
} else {
video.add(this.video).then(res => {
this.star = true
this.$message.success('收藏成功')
})
}
})
},
playBtnClick (i: string, j: number) {
if (this.video.index !== j) {
let url = i.split('$')[1]
this.num = i.split('$')[0]
this.video.index = j
// @ts-ignore
this.xg.src = url
}
}
}
})
</script>
<style lang="scss" scoped>
.player{
height: 100%;
width: 100%;
position: relative;
.player-show{
height: 100%;
width: 100%;
position: relative;
}
.player-none{
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.tips{
font-size: 14px;
margin-bottom: 10px;
}
}
.player-title{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50px;
line-height: 50px;
.player-title-box{
width: 600px;
margin: 0 auto;
}
}
.player-box{
position: absolute;
top: 50px;
left: 0;
width: 100%;
height: 350px;
#xg{
margin: 0 auto;
}
}
.player-films{
position: absolute;
top: 400px;
left: 0;
width: 100%;
height: calc(100% - 400px);
overflow: auto;
button{
margin: 0 10px 10px 0;
}
&::-webkit-scrollbar{
width: 6px;
}
.player-films-box{
width: 600px;
margin: 0 auto;
}
}
}
</style>

View File

@@ -1,16 +0,0 @@
import Vue from 'vue'
import Film from './film.vue'
import Search from './search.vue'
import Player from './player.vue'
import Star from './star.vue'
import Setting from './setting.vue'
export default {
registerComponents () {
Vue.component('Film', Film)
Vue.component('Search', Search)
Vue.component('Player', Player)
Vue.component('Star', Star)
Vue.component('Setting', Setting)
}
}

View File

@@ -1,193 +0,0 @@
<template>
<el-row class="search">
<el-row class="search-box" :class="table === true ? 'search-box hasTable' : 'search-box'">
<el-input class="search-input" size="medium" clearable placeholder="请输入内容" v-model.trim="keywords" @change="searchEvent">
<el-select v-model="site" slot="prepend" placeholder="请选择" @change="selectSite" style="width: 130px;">
<el-option v-for="(i, j) in sites" :key="i.id" :label="i.name" :value="j"></el-option>
</el-select>
<el-button slot="append" icon="el-icon-search" @click="searchEvent"></el-button>
</el-input>
</el-row>
<el-row v-show="table" class="search-table-box table-box">
<el-table :data="filmData" stripe class="search-table" size="mini" v-loading="loading">
<el-table-column prop="name" label="影片名称"></el-table-column>
<el-table-column prop="type" label="影片类别" width="120"></el-table-column>
<el-table-column prop="time" label="更新时间" width="180"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="small" type="text" @click="tableBtnClick('detail', scope.row)">详情</el-button>
<el-button size="small" type="text" @click="tableBtnClick('star', scope.row)">收藏</el-button>
<el-button size="small" type="text" @click="tableBtnClick('play', scope.row)">播放</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row v-show="table" class="search-bottom" type="flex" justify="end">
<el-pagination
small
layout="total, prev, pager, next, jumper"
:current-page="filmPage"
:page-size="50"
@current-change="pageChange"
:total="filmTotal">
</el-pagination>
</el-row>
</el-row>
</template>
<script lang="ts">
import Vue from 'vue'
import sites from '@/lib/sites'
import zy from '@/lib/util.zy'
import { mapMutations } from 'vuex'
import video from '@/plugins/dexie/video'
export default Vue.extend({
data () {
return {
table: false,
sites: sites,
keywords: '',
filmData: [],
filmPage: 1,
filmTotal: 0,
loading: false
}
},
computed: {
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
site: {
get () {
return this.$store.getters.getSite
},
set (val) {
this.SET_SITE(val)
}
},
Main: {
get () {
return this.$store.getters.getMain
},
set (val) {
this.SET_MAIN(val)
}
}
},
methods: {
...mapMutations(['SET_SITE', 'SET_DETAIL', 'SET_MAIN', 'SET_VIDEO']),
selectSite (e:number) {
this.site = e
this.filmData = []
this.filmPage = 1
this.filmTotal = 0
this.searchEvent()
},
searchEvent () {
if (this.keywords !== '') {
this.loading = true
this.table = true
zy.info(this.site, this.filmPage, this.keywords).then((res: any) => {
this.filmData = res.list
this.filmTotal = res.total
this.loading = false
})
}
},
pageChange (e:number) {
this.filmPage = e
this.searchEvent()
},
tableBtnClick (type: string, e: any) {
if (type === 'detail') {
let d = {
show: true,
video: e
}
this.SET_DETAIL(d)
}
if (type === 'star') {
video.find({ detail: e.detail }).then(res => {
if (res) {
this.$message.warning('已存在')
} else {
video.add(e).then(res => {
this.$message.success('收藏成功')
})
}
})
}
if (type === 'play') {
this.Main = 'Player'
this.video = e
}
}
},
created () {}
})
</script>
<style lang="scss" scoped>
.search{
height: 100%;
position: relative;
.search-box{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
animation: slideDown 0.6s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
@keyframes slideDown {
from {
height: 40px;
}
to{
height: 90%;
}
}
&.hasTable{
animation: slideUp 0.2s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
}
@keyframes slideUp {
from {
height: 90%;
opacity: 0;
}
to{
height: 40px;
opacity: 1;
}
}
}
.search-table-box{
position: absolute;
top: 40px;
width: 100%;
height: calc(100% - 100px);
overflow: auto;
&::-webkit-scrollbar{
width: 6px;
}
.search-table{
width: 100%;
}
}
.search-bottom{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
}
}
</style>

View File

@@ -1,173 +0,0 @@
<template>
<el-row class="setting">
<el-row class="item site">
<el-row class="title"><i class="el-icon-set-up"></i><span>默认资源</span></el-row>
<el-row class="info">
<el-select v-model="dbSite" placeholder="请选择" size="small" @change="selectSite">
<el-option
v-for="(i, j) in sites"
:key="i.id"
:label="i.name"
:value="j">
</el-option>
</el-select>
</el-row>
</el-row>
<el-row class="item theme">
<el-row class="title"><i class="el-icon-picture-outline-round"></i><span>主题</span></el-row>
<el-row class="card-box">
<el-card shadow="hover" class="card">
<img src="@/assets/image/light.png" class="image" @click="selectTheme('light')">
<span size="mini">Light</span>
</el-card>
<el-card shadow="hover" class="card">
<img src="@/assets/image/dark.png" class="image" @click="selectTheme('dark')">
<span size="mini">Dark</span>
</el-card>
</el-row>
</el-row>
<el-row class="item update">
<el-row class="title"><i class="el-icon-refresh"></i><span>更新</span></el-row>
<el-row class="btns">
<el-button size="small" @click="checkUpdate">检查更新</el-button>
</el-row>
</el-row>
<el-row class="item about">
<el-row class="title"><i class="el-icon-view"></i><span>关于</span></el-row>
<el-row class="info">
<ul>
<li><el-link :underline="false" @click="linkOpen('https://zy_player.hunlongyu.fun')">官网: ZY Player</el-link></li>
<li><el-link :underline="false" @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">反馈: Issues</el-link></li>
</ul>
</el-row>
</el-row>
</el-row>
</template>
<script lang="ts">
import Vue from 'vue'
import sites from '@/lib/sites'
import { mapMutations } from 'vuex'
import { shell } from 'electron'
import site from '@/plugins/dexie/site'
import theme from '@/plugins/dexie/theme'
import fly from 'flyio'
export default Vue.extend({
name: 'setting',
data () {
return {
sites: sites,
dbSite: 0
}
},
computed: {
site: {
get () {
return this.$store.getters.getSite
},
set (val) {
this.SET_SITE(val)
}
},
theme: {
get () {
return this.$store.getters.getTheme
},
set (val) {
this.SET_THEME(val)
}
}
},
methods: {
...mapMutations(['SET_SITE', 'SET_THEME']),
linkOpen (e:string) {
if (e) {
shell.openExternal(e)
}
},
initSetting () {
site.find().then(res => {
if (!res) {
site.add({ site: 0 })
} else {
this.dbSite = res.site
this.site = res.site
}
})
theme.find().then(res => {
if (!res) {
theme.add({ theme: 'light' })
} else {
this.theme = res.theme
}
})
},
selectSite () {
site.update({ site: this.dbSite }).then(res => {
this.site = this.dbSite
this.$message.success('设置默认资源成功~')
}).catch(() => {
this.$message.warning('设置默认资源失败~')
})
},
selectTheme (e: string) {
theme.update({ theme: e }).then(res => {
this.theme = e
this.$message.success('切换主题成功~')
}).catch(() => {
this.$message.warning('切换主题失败~')
})
},
checkUpdate () {
// fly.get('https://api.github.com/repos/Hunlongyu/ZY-Player/releases/latest').then(res => {}).catch(err => {})
}
},
created () {
this.initSetting()
}
})
</script>
<style lang="scss" scoped>
.setting{
.item{
margin-bottom: 30px;
.title{
height: 24px;
line-height: 24px;
margin-bottom: 10px;
display: flex;
align-items: center;
i{
font-size: 24px;
margin-right: 6px;
}
}
}
.about{
ul{
list-style: none;
li{
height: 30px;
}
}
}
.theme{
.card-box{
display: flex;
justify-content: flex-start;
.card{
width: 160px;
margin-right: 20px;
text-align: center;
img{
cursor: pointer;
width: 100%;
}
span{
font-size: 14px;
margin-top: 10px;
}
}
}
}
}
</style>

View File

@@ -1,128 +0,0 @@
<template>
<el-row class="star">
<el-row class="star-table-box table-box">
<el-table :data="filmData" stripe class="film-table" size="mini" empty-text="收藏夹里空空的~快去填满我吧~">
<el-table-column prop="name" label="影片名称"></el-table-column>
<el-table-column prop="type" label="影片类别" width="120"></el-table-column>
<el-table-column prop="time" label="更新时间" width="180"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="small" type="text" @click="tableBtnClick('detail', scope.row)">详情</el-button>
<el-button size="small" type="text" @click="tableBtnClick('delete', scope.row)">删除</el-button>
<el-button size="small" type="text" @click="tableBtnClick('play', scope.row)">播放</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row class="star-bottom">
<el-pagination
small
layout="total, prev, pager, next, jumper"
:current-page="filmPage"
:page-size="50"
:total="filmTotal">
</el-pagination>
</el-row>
</el-row>
</template>
<script lang="ts">
import Vue from 'vue'
import video from '@/plugins/dexie/video'
import { mapMutations } from 'vuex'
export default Vue.extend({
data () {
return {
filmData: [],
filmPage: 1,
filmTotal: 0
}
},
computed: {
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
Main: {
get () {
return this.$store.getters.getMain
},
set (val) {
this.SET_MAIN(val)
}
}
},
watch: {
Main: {
handler () {
this.getAllStar()
},
deep: true
}
},
methods: {
...mapMutations(['SET_DETAIL', 'SET_VIDEO', 'SET_MAIN']),
getAllStar () {
video.all().then(res => {
this.filmData = res
this.filmTotal = res.length
})
},
tableBtnClick (type: string, e: any) {
if (type === 'detail') {
let d = {
show: true,
video: e
}
this.SET_DETAIL(d)
}
if (type === 'delete') {
video.remove(e.id).then(res => {
if (!res) {
this.$message.success('删除成功')
} else {
this.$message.warning('删除失败, 请重试~')
}
this.getAllStar()
})
}
if (type === 'play') {
this.Main = 'Player'
this.video = e
}
}
},
created () {
this.getAllStar()
}
})
</script>
<style lang="scss" scoped>
.star{
height: 100%;
position: relative;
.star-table-box{
position: absolute;
top: 0px;
width: 100%;
height: calc(100% - 40px);
overflow: auto;
&::-webkit-scrollbar{
width: 6px;
}
}
.star-bottom{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
}
}
</style>

View File

@@ -1,42 +0,0 @@
import Dexie from 'dexie'
class ZYDB extends Dexie {
theme: Dexie.Table<theme, number>
site: Dexie.Table<site, number>
video: Dexie.Table<video, number>
constructor () {
super('ZYDB')
this.version(1).stores({
theme: '++id, theme',
site: '++id, site',
video: '++id, name, type, time, detail, urls, index'
})
this.theme = this.table('theme')
this.site = this.table('site')
this.video = this.table('video')
}
}
export interface theme {
id: number
theme?: string
}
export interface site {
id: number
site?: number
}
export interface video {
id?: number
name?: string
type?: string
time?: string
detail?: string
urls?: Array<string>
index?: number
}
export default new ZYDB()

View File

@@ -1,33 +0,0 @@
import db from './index'
export default {
add (data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.site.add(data).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
find (): Promise<any> {
return new Promise((resolve, reject) => {
db.site.get(1).then((res: any) => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
update (data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.site.update(1, data).then(updated => {
if (updated) {
resolve(updated)
} else {
reject(updated)
}
})
})
}
}

View File

@@ -1,33 +0,0 @@
import db from './index'
export default {
add (data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.theme.add(data).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
find (): Promise<any> {
return new Promise((resolve, reject) => {
db.theme.get(1).then((res: any) => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
update (data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.theme.update(1, data).then(updated => {
if (updated) {
resolve(updated)
} else {
reject(updated)
}
})
})
}
}

View File

@@ -1,69 +0,0 @@
import db from './index'
export default {
add (data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.video.add(data).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
find (data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.video.get(data).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
all (): Promise<any> {
return new Promise((resolve, reject) => {
db.video.toArray().then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
update (id: number, data: any): Promise<any> {
return new Promise((resolve, reject) => {
db.video.update(id, data).then(updated => {
if (updated) {
resolve(updated)
} else {
reject(updated)
}
})
})
},
count (): Promise<any> {
return new Promise((resolve, reject) => {
db.video.count().then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
remove (id: number) {
return new Promise((resolve, reject) => {
db.video.delete(id).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
},
clear () {
return new Promise((resolve, reject) => {
db.video.clear().then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
}

View File

@@ -1,30 +0,0 @@
import Vue from 'vue'
import {
Container, Row, Col, Header, Aside, Main, Drawer,
Tabs, TabPane, Button, Select, Option, Pagination,
Table, TableColumn, Input, Card, Link, Loading,
Notification, Message
} from 'element-ui'
Vue.use(Container)
Vue.use(Row)
Vue.use(Col)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Drawer)
Vue.use(Tabs)
Vue.use(TabPane)
Vue.use(Button)
Vue.use(Select)
Vue.use(Option)
Vue.use(Pagination)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Input)
Vue.use(Card)
Vue.use(Link)
Vue.use(Loading)
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message

13
src/shims-tsx.d.ts vendored
View File

@@ -1,13 +0,0 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}

4
src/shims-vue.d.ts vendored
View File

@@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

View File

@@ -5,50 +5,64 @@ Vue.use(Vuex)
export default new Vuex.Store({
state: {
Main: 'Search',
site: 0,
view: 'Film',
theme: 'light',
site: 'zuidazy',
language: 'zhCn',
detail: {
show: false,
video: ''
v: {}
},
share: {
show: false,
v: {}
},
dUrl: '',
video: {}
},
getters: {
getMain: state => {
return state.Main
getView: state => {
return state.view
},
getTheme: state => {
return state.theme
},
getSite: state => {
return state.site
},
getTheme: state => {
return state.theme
getLanguage: state => {
return state.language
},
getDetail: state => {
return state.detail
},
getVideo: state => {
return state.video
},
getShare: state => {
return state.share
}
},
mutations: {
SET_MAIN: (state, payload) => {
state.Main = payload
SET_VIEW: (state, payload) => {
state.view = payload
},
SET_THEME: (state, payload) => {
state.theme = payload
},
SET_SITE: (state, payload) => {
state.site = payload
},
SET_THEME: (state, payload) => {
state.theme = payload
SET_LANGUAGE: (state, payload) => {
state.language = payload
},
SET_DETAIL: (state, payload) => {
state.detail = payload
},
SET_VIDEO: (state, payload) => {
state.video = payload
},
SET_SHARE: (state, payload) => {
state.share = payload
}
},
actions: {},
modules: {}
}
})

View File

@@ -1,39 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@@ -1,16 +1,43 @@
module.exports = {
pages: {
index: 'src/main.js',
mini: 'src/mini/main.js'
},
pluginOptions: {
electronBuilder: {
nodeIntegration: true,
builderOptions: {
win: {
icon: './build/icons/icon.ico'
nsis: {
oneClick: false,
allowToChangeInstallationDirectory: true
},
productName: 'ZY Player',
publish: ['github']
},
chainWebpackRendererProcess: config => {
if (process.env.NODE_ENV === 'development') {
config.plugins.delete('prefetch')
appId: 'com.hunlongyu.zy',
copyright: 'Copyright @ 2020 Hunlongyu',
productName: 'ZY Player 内测版',
publish: [
{
provider: 'github',
owner: 'Hunlongyu',
repo: 'ZY-Player'
}
],
mac: {
icon: 'build/icon/icon.icns',
category: 'public.app-category.developer-tools',
target: 'default',
extendInfo: {
LSUIElement: 1
}
},
win: {
icon: 'build/icons/icon.ico',
target: 'nsis'
},
linux: {
icon: 'build/icons/'
},
snap: {
publish: ['github']
}
}
}

21350
yarn.lock

File diff suppressed because it is too large Load Diff