Compare commits

..

78 Commits

Author SHA1 Message Date
buvta
2a03388d6b v2.7.0 2020-12-19 13:29:31 +08:00
buvta
ef704a9d45 禁用allowRunningInsecureContent 2020-12-19 13:27:07 +08:00
buvta
946978fa86 修复深色主题下el-switch标签文本不可见 2020-12-19 13:10:30 +08:00
haiyangcui
70b03e67fb 在右侧显示分隔栏内容,1.在中间的时候永远无法和中间的下拉菜单对齐, 2. 不常用的按钮放在边侧,以降低对用户注意力的影响 2020-12-18 22:25:55 +01:00
haiyangcui
1ee7c6f032 设置不同主题下的工具栏样式 2020-12-18 22:11:18 +01:00
buvta
f2f58fb888 调整版本号到v2.6.10 2020-12-18 18:03:35 +08:00
buvta
5286568801 允许证书错误v2.7.0rc 2020-12-18 17:16:23 +08:00
buvta
2a9f8ed0dc 避免改动数据库时重置软件 2020-12-18 16:25:04 +08:00
buvta
eb3d064cb5 改动shortcut数据库,避免快捷键指南太过杂乱 2020-12-17 20:33:56 +08:00
buvta
a0ec282cc2 fix:改动快捷键设置时未及时生效 2020-12-17 20:15:16 +08:00
buvta
ea61dca27b film下el-select的visible-change事件改为change 2020-12-17 16:54:21 +08:00
buvta
fd0be9e96b film搜索调整隐藏列 2020-12-17 16:49:49 +08:00
buvta
775247e28b 设置快捷键添加重置功能 2020-12-17 16:47:57 +08:00
buvta
ded5c39790 Play频道列表添加clickaway事件 2020-12-17 16:36:40 +08:00
buvta
16e44d71bd 海报模式下不提醒支持shift 2020-12-17 16:22:20 +08:00
buvta
ae0858319f 完善导入导出的title提示 2020-12-15 20:35:50 +08:00
buvta
3c6733648b 再次调整resize事件 2020-12-15 20:22:01 +08:00
buvta
06bd915964 调大el-select下拉框高度 2020-12-15 17:15:25 +08:00
buvta
dfcd786f53 fix:收藏批量删除 2020-12-15 16:24:12 +08:00
buvta
d0f6282b81 批量检测完成时提示 2020-12-15 15:42:13 +08:00
buvta
ab3a6e1fd2 各页面完善按钮提示 2020-12-15 15:20:55 +08:00
buvta
0cb540d3e5 历史收藏添加支持shift提示 2020-12-15 14:34:14 +08:00
buvta
1d0123d69f fix:历史收藏列表模式拖拽调序 2020-12-15 14:23:31 +08:00
buvta
6dbcb8e621 历史与收藏添加批量删除 2020-12-15 14:17:13 +08:00
buvta
b1a6d58974 清除iptv的watch view 2020-12-15 14:12:02 +08:00
haiyangcui
bb45006ff7 Revert "删除跳过片头片尾里的无用代码"
This reverts commit e9f81faf70.
2020-12-14 17:11:47 +01:00
haiyangcui
e9f81faf70 删除跳过片头片尾里的无用代码 2020-12-14 16:26:47 +01:00
cuiocean
b864c6ca15 Merge pull request #383 from Hunlongyu/dependabot/npm_and_yarn/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8
2020-12-14 16:12:49 +01:00
buvta
9f98231eed 添加提示次数限制 2020-12-14 22:27:21 +08:00
buvta
3bcf5b1618 添加多选支持shift提示 2020-12-14 21:21:29 +08:00
buvta
d7a6245f8a 导出为json格式时自动添加扩展名 2020-12-14 21:19:32 +08:00
buvta
2abf2aeda7 设置页添加更新日志与常见问题链接 2020-12-14 17:18:19 +08:00
buvta
c3129c7cee 调整Play页面某些变量的命名 2020-12-14 17:08:38 +08:00
buvta
3b0ec94d2b 直播停止时亦可弹出频道列表 2020-12-14 17:03:24 +08:00
buvta
9902d98342 调整海报模式延时间隔 2020-12-13 23:17:28 +08:00
buvta
b5f5baeb8f 点击播放历史记录按钮时隐藏频道列表 2020-12-13 22:59:17 +08:00
buvta
f396d5ea05 停止时隐藏跳过片头片尾 2020-12-13 22:51:46 +08:00
buvta
e616aa5e2a 播放器初始状态点播放,历史记录为空时直接停止重置 2020-12-13 22:25:58 +08:00
buvta
f041093fe9 清除播放窗口还原时hasStart变量 2020-12-13 22:25:58 +08:00
buvta
e66c056283 稍微调整跳过片头片尾,并添加注释 2020-12-13 22:25:52 +08:00
haiyangcui
d33bbcff06 设置片头片尾按钮的文字提示改为tooltips提示 2020-12-13 14:49:11 +01:00
buvta
992c2f152f 推荐页切换页面时没必要重新读取数据库,并修正换行符 2020-12-13 18:36:31 +08:00
buvta
118bc5058c 调整历史播放页面watch监测view的事件 2020-12-13 18:21:38 +08:00
buvta
19c11f2694 fix:海报模式film页面加载图片时来回切换页面偶尔有卡片被部分遮掩,其它页面是重新加载数据故而不用 2020-12-13 18:12:47 +08:00
buvta
0a812ff6c4 el-select多选时显示全部标签 2020-12-13 16:30:12 +08:00
buvta
1e6a98df88 海报模式重新加上resize事件 2020-12-13 16:08:57 +08:00
buvta
b1b4e61244 fix:改造时调整rowDrop触发时机 2020-12-13 15:38:36 +08:00
buvta
2a05b2abdb 推荐、历史、收藏页面进行类film改造 2020-12-13 15:37:10 +08:00
buvta
74beb47ad5 fix: film搜索切换回海报模式时部分卡片被遮掩显示不全的bug 2020-12-13 15:08:32 +08:00
buvta
325a7f3b0d 调小播放器重置延时 2020-12-13 13:10:52 +08:00
buvta
9b5b5a1334 修复播放器初始状态时点击播放时的bug,并给直播加上对应功能 2020-12-13 13:09:36 +08:00
buvta
74dee7a892 调整film窗口resize事件触发机制 2020-12-13 11:53:35 +08:00
dependabot[bot]
d07436d876 Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-13 01:04:59 +00:00
buvta
e283152a89 去除多余代码,Waterfall在数据及窗口变动时会自动刷新 2020-12-12 23:02:20 +08:00
buvta
bdeeccedb9 film列表模式窗口宽度足够时显示“最近更新” 2020-12-12 23:01:31 +08:00
haiyangcui
5931266770 播放页面,显示历史时间信息 2020-12-12 13:51:58 +01:00
haiyangcui
bbeb75631d 打开ZY,直接播放的话,或者是视频停止后再点击播放的话,播放最近的历史记录 2020-12-12 13:39:44 +01:00
haiyangcui
c0f4940289 设置quitMiniMode按钮默认不显示,这样每次播放视频的时候就无需设置'xg-btn-quitMiniMode'的样式 2020-12-12 13:01:27 +01:00
buvta
db1b81243a 修复“站内”时切换站点可搜索 2020-12-12 12:56:55 +08:00
buvta
97b6a8259f 继续调整 2020-12-12 12:52:27 +08:00
buvta
4c92ff9e70 fix:优化film停止搜索时的自动切换searchRunning问题 2020-12-12 12:25:04 +08:00
buvta
b8dc7f4526 film工具栏分割线添加"回到顶部" 2020-12-11 23:42:28 +08:00
buvta
0d6e0d6e9f film停止搜索优化 2020-12-11 23:07:13 +08:00
buvta
1a5bac68ad 调整film搜索表格过滤器设置 2020-12-11 16:11:12 +08:00
buvta
83e302aeb7 film搜索使用工具栏实现过滤(功能实现) 2020-12-11 15:45:36 +08:00
buvta
2196eaa68c 视图模式切换转移到工具栏分割线,搜索亦可用工具栏(功能待实现) 2020-12-11 15:41:28 +08:00
haiyangcui
40aae02af6 修复收藏页的共享功能 2020-12-10 17:17:28 +01:00
haiyangcui
f078df6f8e 修复推荐页的分享功能 2020-12-10 17:14:49 +01:00
haiyangcui
ee44bfe0a8 支持停止搜索,虽然实际上是停止把搜索结果加入searchContents,但对于用户来说应该是足够了. 2020-12-10 16:59:07 +01:00
hunlongyu
9d9a808226 修复分享二维码地址为空的bug 2020-12-10 23:17:55 +08:00
buvta
d78784c0d9 fix:精简模式切换内容时quitMini图标消失 2020-12-10 16:10:51 +08:00
buvta
1ead8a5594 开启过滤切换分类数据过少时加大请求间隔 2020-12-10 16:10:51 +08:00
Hunlongyu
b63c8f4daf 优化自动更新功能, 增加进度条 2020-12-10 14:59:49 +08:00
Hunlongyu
011400f714 删除 server.js 文件 2020-12-10 10:00:20 +08:00
Hunlongyu
1d725579de Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player into master 2020-12-10 09:59:07 +08:00
Hunlongyu
93749c3841 移除 node 代理 2020-12-10 09:59:01 +08:00
haiyangcui
4b0c67b8fa 添加"跳略设置"按钮,避免一直显示片头片尾设置 2020-12-08 18:08:03 +01:00
buvta
165040872c fix:工具栏收起时重置selectedArea 2020-12-08 22:06:09 +08:00
21 changed files with 1135 additions and 788 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "zy",
"version": "2.6.9",
"version": "2.7.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -17,16 +17,14 @@
},
"main": "background.js",
"dependencies": {
"@imjs/electron-differential-updater": "^5.1.3",
"axios": "^0.21.0",
"cheerio": "^1.0.0-rc.3",
"core-js": "^3.8.0",
"cors": "^2.8.5",
"dexie": "^3.0.3",
"electron-localshortcut": "^3.2.1",
"electron-proxy-agent": "^1.2.0",
"electron-updater": "^4.3.5",
"element-ui": "^2.14.1",
"express": "^4.17.1",
"fast-xml-parser": "^3.17.4",
"html2canvas": "^1.0.0-rc.7",
"iptv-playlist-parser": "^0.5.0",

View File

@@ -178,6 +178,10 @@
.el-select-dropdown__item.selected.hover{ //是上游的bug吗临时性修补
background-color: transparent;
}
.el-select-dropdown__wrap{
max-height: 574px
}
}
> span{
.el-input-number{

View File

@@ -139,6 +139,12 @@
.play{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.el-switch__label {
color: var(--d-fc-2)
}
.el-switch__label.is-active {
color: #409EFF
}
.el-input{
input{
background-color: var(--d-bgc-1);
@@ -319,6 +325,19 @@
// Page of list using table and picture
.listpage{
color: var(--d-fc-2);
.listpage-header-divider{
background-color: var(--d-bgc-1);
.el-divider__text {
background-color: var(--d-bgc-2);
}
.el-button{
background-color: var(--d-bgc-2);
color: var(--d-fc-2);
&:hover{
color: var(--d-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--d-c-3);
.btn{
@@ -326,6 +345,12 @@
color: var(--d-fc-3)
}
}
.el-switch__label {
color: var(--d-fc-2)
}
.el-switch__label.is-active {
color: #409EFF
}
.el-button{
background-color: var(--d-bgc-2);
color: var(--d-fc-2);

View File

@@ -319,6 +319,19 @@
// Page of list using table and picture
.listpage{
color: var(--g-fc-2);
.listpage-header-divider{
background-color: var(--g-bgc-1);
.el-divider__text {
background-color: var(--g-bgc-2);
}
.el-button{
background-color: var(--g-bgc-2);
color: var(--g-fc-2);
&:hover{
color: var(--g-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--g-c-3);
.btn{

View File

@@ -319,6 +319,19 @@
// Page of list using table and picture
.listpage{
color: var(--l-fc-2);
.listpage-header-divider{
background-color: var(--l-bgc-1);
.el-divider__text {
background-color: var(--l-bgc-2);
}
.el-button{
background-color: var(--l-bgc-2);
color: var(--l-fc-2);
&:hover{
color: var(--l-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--l-c-3);
.btn{

View File

@@ -319,6 +319,19 @@
// Page of list using table and picture
.listpage{
color: var(--p-fc-2);
.listpage-header-divider{
background-color: var(--p-bgc-1);
.el-divider__text {
background-color: var(--p-bgc-2);
}
.el-button{
background-color: var(--p-bgc-2);
color: var(--p-fc-2);
&:hover{
color: var(--p-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--p-c-3);
.btn{

View File

@@ -1,6 +1,5 @@
'use strict'
import './lib/site/server'
import { app, protocol, BrowserWindow, globalShortcut } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
@@ -8,7 +7,7 @@ import { initUpdater } from './lib/update/update'
const isDevelopment = process.env.NODE_ENV !== 'production'
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') // 允许跨域
// app.commandLine.appendSwitch('--ignore-certificate-errors', 'true') // 忽略证书相关错误
app.commandLine.appendSwitch('--ignore-certificate-errors', 'true') // 忽略证书相关错误
let win
@@ -23,7 +22,8 @@ function createWindow () {
webPreferences: {
webSecurity: false,
enableRemoteModule: true,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
allowRunningInsecureContent: false
}
})

View File

@@ -4,16 +4,16 @@
<el-switch v-model="enableBatchEdit" active-text="批处理分组">></el-switch>
<el-checkbox v-model="setting.excludeR18Films" @change="excludeR18FilmsChangeEvent">屏蔽福利片</el-checkbox>
<el-button @click="addSite" icon="el-icon-document-add">新增</el-button>
<el-button @click="exportSites" icon="el-icon-upload2" >导出</el-button>
<el-button @click="importSites" icon="el-icon-download">导入</el-button>
<el-button @click="checkAllSite" icon="el-icon-refresh" :loading="checkAllSitesLoading">检测{{ this.checkAllSitesLoading ? this.checkProgress + '/' + this.sites.length : '' }}</el-button>
<el-button @click="exportSites" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click="importSites" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
<el-button @click="checkAllSite" icon="el-icon-refresh" :loading="checkAllSitesLoading" title="可在后台运行">检测{{ this.checkAllSitesLoading ? this.checkProgress + '/' + this.sites.length : '' }}</el-button>
<el-button @click="resetSitesEvent" icon="el-icon-refresh-left">重置</el-button>
</div>
<div class="listpage-header" v-show="enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组"></el-switch>
<el-input placeholder="新组名" v-model="batchGroupName"></el-input>
<el-switch v-model="batchIsActive" active-text="启用"></el-switch>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit">保存</el-button>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit" title="输入框组名为空时仅保存开关状态">保存分组与开关状态</el-button>
<el-button @click="removeSelectedSites" icon="el-icon-delete-solid">删除</el-button>
</div>
<div class="listpage-body" id="sites-body">
@@ -186,6 +186,17 @@ export default {
this.$message.info('正在检测, 请勿操作.')
this.enableBatchEdit = false
}
if (this.enableBatchEdit) {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
this.$message.info('多选时支持shift快捷键')
this.setting.shiftTooltipLimitTimes--
setting.find().then(res => {
res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
setting.update(res)
})
}
}
}
},
methods: {
@@ -342,13 +353,12 @@ export default {
const str = JSON.stringify(arr, null, 2)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
{ name: 'JSON file', extensions: ['json'] }
]
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
@@ -363,9 +373,7 @@ export default {
}
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
{ name: 'JSON file', extensions: ['json'] }
],
properties: ['openFile', 'multiSelections']
}
@@ -462,6 +470,7 @@ export default {
})
},
async checkAllSite () {
if (this.checkAllSitesLoading) return
this.checkAllSitesLoading = true
this.stopFlag = false
this.checkProgress = 0
@@ -471,6 +480,7 @@ export default {
await Promise.all(other.map(site => this.checkSingleSite(site))).then(res => {
this.checkAllSitesLoading = false
this.getSites()
if (!this.stopFlag) this.$message.success('视频点播源站批量检测已完成!')
})
},
async checkSingleSite (row) {

View File

@@ -9,10 +9,7 @@
:value="item.name">
</el-option>
</el-select>
<el-switch v-model="searchViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateSearchViewMode"
v-if="show.find">
</el-switch>
<el-select v-model="selectedClassName" size="small" placeholder="类型" :popper-append-to-body="false" popper-class="popper" @change="classClick" v-show="show.class">
<el-select v-model="selectedClassName" size="small" placeholder="类型" :popper-append-to-body="false" popper-class="popper" @change="classClick" v-if="classList && classList.length" v-show="!showFind">
<el-option
v-for="item in classList"
:key="item.tid"
@@ -20,6 +17,14 @@
:value="item.name">
</el-option>
</el-select>
<el-select v-model="selectedSearchClassNames" size="small" multiple placeholder="类型" :popper-append-to-body="false" popper-class="popper" v-if="searchClassList && searchClassList.length" v-show="showFind && showToolbar" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="(item, index) in searchClassList"
:key='index'
:label="item"
:value="item">
</el-option>
</el-select>
<el-autocomplete
clearable
size="small"
@@ -45,11 +50,12 @@
</el-option>
</el-select>
<!--方便触屏-->
<el-button icon="el-icon-search" @click.stop="searchEvent" slot="append" />
<el-button icon="el-icon-search" @click.stop="searchEvent" slot="append" v-if="!searchRunning"/>
<el-button icon="el-icon-loading" @click.stop="stopSearchEvent" slot="append" v-if="searchRunning" title='点击可停止搜索'/>
</el-autocomplete>
</div>
<div class="toolbar" v-if="!show.find && showToolbar">
<el-select v-model="selectedAreas" size="small" multiple collapse-tags placeholder="地区" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @visible-change="refreshFilteredList($event)">
<div class="toolbar" v-show="showToolbar">
<el-select v-model="selectedAreas" size="small" multiple placeholder="地区" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in areas"
:key="item"
@@ -72,11 +78,13 @@
<el-input-number size="small" v-model="selectedYears.end" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
</span>
</div>
<el-divider content-position="center" v-if="!show.find">
<el-button type="text" size="mini" @click='() => { showToolbar = !showToolbar; if (!showToolbar) this.refreshFilteredList() }'>{{ showToolbar ? '隐藏工具栏' : '显示工具栏' }}</el-button>
<el-divider class="listpage-header-divider" content-position="right">
<el-button type="text" size="mini" @click="toggleViewMode">视图切换</el-button>
<el-button type="text" size="mini" @click='() => { showToolbar = !showToolbar; if (!showToolbar) this.refreshFilteredList() }' title="收起工具栏会重置筛选排序">{{ showToolbar ? '隐藏工具栏' : '显示工具栏' }}</el-button>
<el-button type="text" size="mini" @click="backTop">回到顶部</el-button>
</el-divider>
<div class="listpage-body" id="film-body" infinite-wrapper>
<div class="show-picture" v-if="setting.view === 'picture' && !show.find">
<div class="show-picture" v-if="setting.view === 'picture' && !showFind">
<Waterfall ref="filmWaterfall" :list="filteredList" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
@@ -115,10 +123,11 @@
</Waterfall>
<infinite-loading force-use-infinite-wrapper :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div>
<div class="show-table" v-if="setting.view === 'table' && !show.find">
<div class="show-table" v-if="setting.view === 'table' && !showFind">
<el-table
size="mini"
:data="filteredList"
ref="filmTable"
height="100%"
:empty-text="statusText"
@row-click="(row) => detailEvent(site, row)"
@@ -127,7 +136,7 @@
prop="name"
label="片名">
</el-table-column>
<el-table-column
<el-table-column v-if="type.name === '最新'"
prop="type"
label="类型"
width="100">
@@ -148,16 +157,16 @@
label="语言"
width="100">
</el-table-column>
<el-table-column
prop="note"
label="备注"
width="120">
</el-table-column>
<el-table-column
<el-table-column v-if="showTableLastColumn"
prop="last"
label="最近更新"
:formatter="dateFormat"
align="left">
align="left"
width="120">
</el-table-column>
<el-table-column
prop="note"
label="备注">
</el-table-column>
<el-table-column
label="操作"
@@ -180,10 +189,10 @@
</infinite-loading>
</el-table>
</div>
<div class="show-table" v-if="searchViewMode=== 'table' && show.find">
<div class="show-table" v-if="setting.searchViewMode === 'table' && showFind">
<el-table size="mini"
ref="searchResultTable"
:data="searchContents.filter(res => !setting.excludeR18Films || (res.type !== undefined && !containsR18Keywords(res.type)))"
:data="filteredSearchContents"
height="100%"
:empty-text="statusText"
@filter-change="filterChange"
@@ -209,36 +218,38 @@
</el-table-column>
<el-table-column
prop="type"
:filters="getFilters('type')"
:filter-method="(value, row, column) => { this.currentColumn = column; return value === row.type }"
label="类型"
width="90">
width="100">
</el-table-column>
<el-table-column
sortable
prop="year"
label="上映"
width="90">
width="100">
</el-table-column>
<el-table-column
prop="area"
:filters="getFilters('area')"
:filter-method="(value, row, column) => { this.currentColumn = column; return value === row.area }"
label="地区"
width="90">
width="100">
</el-table-column>
<el-table-column
:filters="getFilters('lang')"
:filter-method="(value, row, column) => { this.currentColumn = column; return value === row.lang }"
prop="lang"
label="语言"
width="70">
width="100">
</el-table-column>
<el-table-column v-if="showTableLastColumn"
prop="last"
label="最近更新"
:formatter="dateFormat"
align="left"
width="120">
</el-table-column>
<el-table-column
sortable
prop="note"
label="备注"
width="120">
label="备注">
</el-table-column>
<el-table-column
label="操作"
@@ -254,8 +265,8 @@
</el-table-column>
</el-table>
</div>
<div class="show-picture" v-if="searchViewMode === 'picture' && show.find">
<Waterfall ref="filmSearchWaterfall" :list="searchContents.filter(res => !setting.excludeR18Films || (res.type !== undefined && !containsR18Keywords(res.type)))" :gutter="20" :width="240"
<div class="show-picture" v-if="setting.searchViewMode === 'picture' && showFind">
<Waterfall ref="filmSearchWaterfall" :list="filteredSearchContents" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
@@ -310,19 +321,17 @@ export default {
name: 'film',
data () {
return {
show: {
body: false,
site: false,
class: false,
classList: false,
find: false
},
showFind: false,
showToolbar: false,
showTableLastColumn: false,
sites: [],
site: {},
classList: [],
searchClassList: [],
type: {},
selectedSiteName: '',
selectedClassName: '',
selectedSearchClassNames: [],
pagecount: 0,
recordcount: 0,
list: [],
@@ -332,19 +341,21 @@ export default {
searchList: [],
searchTxt: '',
searchContents: [],
filteredSearchContents: [],
currentColumn: '',
searchGroup: '',
searchGroups: [],
// 福利片关键词
r18KeyWords: ['伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番'],
searchViewMode: 'picture',
filteredList: [],
areas: [],
selectedAreas: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间'],
selectedYears: { start: 0, end: new Date().getFullYear() },
showToolbar: false
searchRunning: false,
siteSearchCount: 0,
infiniteHandlerCount: 0
}
},
components: {
@@ -394,6 +405,11 @@ export default {
},
filterSettings () {
return this.$store.getters.getSetting.excludeR18Films // 需要监听的数据
},
searchSites () {
if (this.searchGroup === '站内') return [this.site]
if (this.searchGroup === '全站') return this.sites
return this.sites.filter(site => site.group === this.searchGroup)
}
},
filters: {
@@ -404,7 +420,11 @@ export default {
},
watch: {
view () {
this.changeView()
if (this.view === 'Film') {
this.getAllSites()
if (this.$refs.filmWaterfall) this.$refs.filmWaterfall.resize() // 瀑布插件resize和refresh功能相同只是延时不同
if (this.$refs.filmSearchWaterfall) this.$refs.filmSearchWaterfall.resize()
}
},
searchTxt () {
if (this.searchTxt === '清除历史记录...') {
@@ -422,22 +442,57 @@ export default {
this.refreshFilteredList()
},
deep: true
},
siteSearchCount () {
if (this.siteSearchCount === this.searchSites.length) this.searchRunning = false
},
searchContents: {
handler (list) {
this.areas = [...new Set(list.map(ele => ele.area))].filter(x => x)
this.searchClassList = [...new Set(list.map(ele => ele.type))].filter(x => x)
this.refreshFilteredList()
},
deep: true
},
selectedAreas: {
handler () {
this.infiniteHandlerCount = 0
},
deep: true
},
selectedYears: {
handler () {
this.infiniteHandlerCount = 0
},
deep: true
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
refreshFilteredList (popperVisible) {
if (popperVisible === true) return
backTop () {
const viewMode = this.showFind ? this.setting.searchViewMode : this.setting.view
if (viewMode === 'picture') {
document.getElementById('film-body').scrollTop = 0
} else {
const table = this.showFind ? this.$refs.searchResultTable : this.$refs.filmTable
table.bodyWrapper.scrollTop = 0
}
},
refreshFilteredList () {
if (!this.showToolbar) {
this.sortKeyword = ''
this.selectedAreas = []
this.selectedSearchClassNames = []
this.selectedYears.start = 0
this.selectedYears.end = new Date().getFullYear()
}
let filteredData = this.list.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.area))
let filteredData = this.showFind ? this.searchContents : this.list
if (this.showFind) filteredData = filteredData.filter(x => (this.selectedSearchClassNames.length === 0) || this.selectedSearchClassNames.includes(x.type))
filteredData = filteredData.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.area))
filteredData = filteredData.filter(res => !this.setting.excludeR18Films || !this.containsR18Keywords(res.type))
filteredData = filteredData.filter(res => res.year >= this.selectedYears.start)
filteredData = filteredData.filter(res => res.year <= this.selectedYears.end)
this.selectedClassName = this.type.name + ' ' + filteredData.length + '/' + this.recordcount
if (!this.showFind) this.selectedClassName = this.type.name + ' ' + filteredData.length + '/' + this.recordcount
switch (this.sortKeyword) {
case '按上映年份':
filteredData.sort(function (a, b) {
@@ -457,11 +512,22 @@ export default {
default:
break
}
this.filteredList = filteredData
if (this.showFind) {
this.filteredSearchContents = filteredData
} else {
this.filteredList = filteredData
}
},
updateSearchViewMode () {
toggleViewMode () {
if (this.showFind) {
this.setting.searchViewMode = this.setting.searchViewMode === 'picture' ? 'table' : 'picture'
setTimeout(() => { if (this.$refs.filmSearchWaterfall) this.$refs.filmSearchWaterfall.refresh() }, 700)
} else {
this.setting.view = this.setting.view === 'picture' ? 'table' : 'picture'
}
setting.find().then(res => {
res.searchViewMode = this.searchViewMode
res.searchViewMode = this.setting.searchViewMode
res.view = this.setting.view
setting.update(res)
})
},
@@ -476,9 +542,8 @@ export default {
return date.split(/\s/)[0]
},
getFilters (column) {
const searchContents = this.searchContents.filter(res => !this.setting.excludeR18Films || (res.type !== undefined && !this.containsR18Keywords(res.type)))
if (column === 'siteName') return [...new Set(searchContents.map(row => row.site.name))].map(e => { return { text: e, value: e } }) // 有方法合并这两行吗?
return [...new Set(searchContents.map(row => row[column]))].map(e => { return { text: e, value: e } })
if (column === 'siteName') return [...new Set(this.filteredSearchContents.map(row => row.site.name))].map(e => { return { text: e, value: e } }) // 有方法合并这两行吗?
return [...new Set(this.filteredSearchContents.map(row => row[column]))].map(e => { return { text: e, value: e } })
},
filterChange (filters) {
// 一次只能一列
@@ -486,7 +551,7 @@ export default {
const otherColumns = this.$refs.searchResultTable.columns.filter(col => col.id !== this.currentColumn.id)
otherColumns.forEach(col => { col.filterable = false })
} else {
const filterLabels = ['源站', '类型', '地区', '语言']
const filterLabels = ['源站', '语言']
const columns = this.$refs.searchResultTable.columns.filter(col => filterLabels.includes(col.label))
columns.forEach(col => { col.filterable = true })
}
@@ -494,17 +559,20 @@ export default {
siteClick (siteName) {
this.list = []
this.site = this.sites.find(x => x.name === siteName)
this.searchTxt = ''
this.show.find = false
if (this.searchGroup === '站内' && this.searchTxt) {
this.searchEvent()
return
} else {
this.searchTxt = ''
}
this.showFind = false
this.classList = []
if (FILM_DATA_CACHE[this.site.key]) {
this.classList = FILM_DATA_CACHE[this.site.key].classList
this.show.class = true
this.classClick(this.type.name)
} else {
this.getClass().then(res => {
this.classList = res
this.show.class = true
// cache classList data
FILM_DATA_CACHE[this.site.key] = {
classList: this.classList
@@ -514,12 +582,13 @@ export default {
}
},
classClick (className) {
this.show.classList = false
this.list = []
this.type = this.classList.find(x => x.name === className)
this.infiniteHandlerCount = 0
if (!this.type) {
this.type = this.classList[0]
}
if (this.type.name.endsWith('剧')) this.selectedAreas = []
const cacheKey = this.site.key + '@' + this.type.tid
if (FILM_DATA_CACHE[cacheKey]) {
this.pagecount = FILM_DATA_CACHE[cacheKey].pagecount
@@ -576,34 +645,36 @@ export default {
this.statusText = '暂无数据'
return false
}
zy.list(key, page, typeTid).then(res => {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Undefined]') {
$state.complete()
if (this.showToolbar && this.filteredList.length && this.filteredList.length < 10) {
this.infiniteHandlerCount++
}
const interval = this.setting.view === 'picture' ? 1200 : 300
setTimeout(() => {
zy.list(key, page, typeTid).then(res => {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Undefined]') {
$state.complete()
}
if (type === '[object Array]') {
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧
this.list.push(...res.reverse())
}
if (type === '[object Object]') {
this.list.push(res)
}
$state.loaded()
// 更新缓存数据
const cacheKey = this.site.key + '@' + typeTid
FILM_DATA_CACHE[cacheKey] = {
pagecount: this.pagecount,
recordcount: this.recordcount,
list: this.list
}
}
if (type === '[object Array]') {
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧
this.list.push(...res.reverse())
}
if (type === '[object Object]') {
this.list.push(res)
}
$state.loaded()
// 数据更新后,刷新页面
if (this.setting.view === 'picture' && this.$refs.filmWaterfall) {
this.$refs.filmWaterfall.refresh()
}
// 更新缓存数据
const cacheKey = this.site.key + '@' + typeTid
FILM_DATA_CACHE[cacheKey] = {
pagecount: this.pagecount,
recordcount: this.recordcount,
list: this.list
}
}
})
})
}, (this.infiniteHandlerCount <= 1 ? 0 : this.infiniteHandlerCount - 1) * interval)
},
detailEvent (site, e) {
this.detail = {
@@ -678,16 +749,6 @@ export default {
}
})
},
changeView () {
if (this.view === 'Film') {
this.getAllSites()
if (this.setting.view === 'picture') {
if (this.$refs.filmWaterfall) {
this.$refs.filmWaterfall.refresh()
}
}
}
},
querySearch (queryString, cb) {
var searchList = this.searchList.slice(0, -1)
var results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList
@@ -721,6 +782,9 @@ export default {
this.searchList.push({ id: this.searchList.length + 1, keywords: '清除历史记录...' })
})
},
stopSearchEvent () {
this.searchRunning = false
},
searchEvent () {
const wd = this.searchTxt
if (this.setting.searchGroup !== this.searchGroup) {
@@ -729,64 +793,53 @@ export default {
}
if (!wd) return
this.searchID += 1
var searchSites = []
if (this.searchGroup === '站内') searchSites.push(this.site)
if (this.searchGroup === '全站') searchSites = this.sites
if (!searchSites.length) {
searchSites = this.sites.filter(site => site.group === this.searchGroup)
}
this.searchContents = []
this.show.find = true
this.show.class = false
this.showFind = true
this.statusText = ' '
if (wd) {
searchSites.forEach(site => {
const id = this.searchID
zy.search(site.key, wd).then(res => {
if (id !== this.searchID) return
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
res.forEach(element => {
zy.detail(site.key, element.id).then(detailRes => {
detailRes.site = site
if (id !== this.searchID) return
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
this.statusText = '暂无数据'
})
})
}
if (type === '[object Object]') {
zy.detail(site.key, res.id).then(detailRes => {
this.searchRunning = true
this.siteSearchCount = 0
this.searchSites.forEach(site => {
const id = this.searchID
zy.search(site.key, wd).then(res => {
if (id !== this.searchID || !this.searchRunning) return
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
let count = 0
res.forEach(element => {
zy.detail(site.key, element.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
if (id !== this.searchID) return
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
this.statusText = '暂无数据'
}).finally(() => { count++; if (count === res.length) { this.siteSearchCount++; this.statusText = '暂无数据' } })
})
} else if (type === '[object Object]') {
zy.detail(site.key, res.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
}
})
})
}
}).finally(() => { this.siteSearchCount++; this.statusText = '暂无数据' })
} else if (res === undefined) {
this.siteSearchCount++
this.statusText = '暂无数据'
if (this.searchGroup === '站内') this.$message.info('没有查询到数据!')
}
}).catch(() => { this.siteSearchCount++; if (this.searchGroup === '站内') this.$message.error('本次查询状态异常,未获取到数据!') })
})
},
searchAndRecord () {
this.addSearchRecord()
this.searchEvent()
},
searchChangeEvent () {
if (this.searchTxt.length >= 1) {
this.show.class = false
} else {
this.show.class = true
if (!this.searchTxt.length) {
this.searchContents = []
this.show.find = false
if (this.setting.view === 'picture' && this.$refs.filmWaterfall) {
this.$refs.filmWaterfall.refresh()
}
this.showFind = false
}
},
getAllSites () {
@@ -809,28 +862,20 @@ export default {
this.searchGroup = this.setting.searchGroup
if (this.searchGroup === undefined) setting.find().then(res => { this.searchGroup = res.searchGroup })
})
},
getSearchViewMode () {
setting.find().then(res => {
this.searchViewMode = res.searchViewMode === undefined ? 'picture' : res.searchViewMode
})
}
},
created () {
this.getAllSites()
this.getSearchHistory()
this.getSearchViewMode()
},
mounted () {
window.addEventListener('resize', () => {
if (this.$refs.filmWaterfall && this.view === 'Film') {
this.$refs.filmWaterfall.resize()
this.$refs.filmWaterfall.refresh()
setTimeout(() => {
this.$refs.filmWaterfall.refresh()
}, 500)
}
}, false)
addEventListener('resize', () => {
setTimeout(() => {
this.showTableLastColumn = window.outerWidth >= 1200
if (this.$refs.filmWaterfall) this.$refs.filmWaterfall.resize()
if (this.$refs.filmSearchWaterfall) this.$refs.filmSearchWaterfall.resize()
}, 500)
})
}
}
</script>

View File

@@ -1,14 +1,23 @@
<template>
<div class="listpage" id="history">
<div class="listpage-header" id="history-header">
<el-switch v-model="viewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button @click.stop="exportHistory" icon="el-icon-upload2">导出</el-button>
<el-button @click.stop="importHistory" icon="el-icon-download">导入</el-button>
<el-button @click.stop="clearAllHistory" icon="el-icon-delete-solid">清空</el-button>
<el-switch v-model="setting.historyViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button @click.stop="exportHistory" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click.stop="importHistory" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
<el-button @click.stop="removeSelectedItems" icon="el-icon-delete-solid">{{ multipleSelection.length === 0 ? "清空" : "删除所选" }}</el-button>
</div>
<div class="listpage-body" id="history-body">
<div class="show-table" id="history-table" v-show="viewMode === 'table'">
<el-table size="mini" fit height="100%" :data="history" row-key="id" @row-click="detailEvent">
<div class="show-table" id="history-table" v-if="setting.historyViewMode === 'table'">
<el-table size="mini" fit height="100%"
:data="history"
row-key="id"
ref="historyTable"
@select="selectionCellClick"
@selection-change="handleSelectionChange"
@row-click="detailEvent">
<el-table-column
type="selection">
</el-table-column>
<el-table-column
prop="name"
label="片名">
@@ -52,7 +61,7 @@
</el-table-column>
</el-table>
</div>
<div class="show-picture" id="star-picture" v-show="viewMode === 'picture'">
<div class="show-picture" id="star-picture" v-if="setting.historyViewMode === 'picture'">
<Waterfall ref="historyWaterfall" :list="history" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
@@ -112,7 +121,10 @@ export default {
return {
history: [],
sites: [],
viewMode: setting.historyViewMode
shiftDown: false,
selectionBegin: '',
selectionEnd: '',
multipleSelection: []
}
},
components: {
@@ -150,22 +162,57 @@ export default {
set (val) {
this.SET_SHARE(val)
}
},
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
}
},
watch: {
view () {
this.getAllhistory()
this.getAllsites()
if (this.$refs.historyWaterfall) {
this.$refs.historyWaterfall.refresh()
if (this.view === 'History') {
this.getAllhistory()
this.getAllsites()
if (this.setting.historyViewMode === 'table') this.showShiftPrompt()
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
fmtMSS (s) {
return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
},
selectionCellClick (selection, row) { // 历史id与顺序刚好相反大的反而在前面
if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) {
this.selectionEnd = row.id
const start = this.history.findIndex(e => e.id === Math.max(this.selectionBegin, this.selectionEnd))
const end = this.history.findIndex(e => e.id === Math.min(this.selectionBegin, this.selectionEnd))
const selections = this.history.slice(start, end + 1)
this.$nextTick(() => {
selections.forEach(e => this.$refs.historyTable.toggleRowSelection(e, true))
})
this.selectionBegin = this.selectionEnd = ''
return
}
if (selection.includes(row)) {
this.selectionBegin = row.id
} else {
this.selectionBegin = ''
}
},
handleSelectionChange (rows) {
this.multipleSelection = rows
},
removeSelectedItems () {
if (!this.multipleSelection.length) this.multipleSelection = this.history
this.multipleSelection.forEach(e => history.remove(e.id))
this.getAllhistory()
this.updateDatabase()
},
detailEvent (e) {
this.detail = {
show: true,
@@ -233,6 +280,7 @@ export default {
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功')
}
@@ -260,11 +308,6 @@ export default {
}
})
},
clearAllHistory () {
history.clear().then(res => {
this.history = []
})
},
getAllhistory () {
history.all().then(res => {
this.history = res.reverse()
@@ -288,10 +331,10 @@ export default {
this.$message.warning('删除历史记录失败, 错误信息: ' + err)
})
},
updateDatabase (data) {
updateDatabase () {
history.clear().then(res => {
var id = length
data.forEach(ele => {
this.history.forEach(ele => {
ele.id = id
id -= 1
history.add(ele)
@@ -305,37 +348,44 @@ export default {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.history.splice(oldIndex, 1)[0]
_this.history.splice(newIndex, 0, currRow)
_this.updateDatabase(_this.history)
_this.updateDatabase()
}
})
},
getViewMode () {
setting.find().then(res => {
this.viewMode = res.historyViewMode
})
},
updateViewMode () {
if (this.setting.historyViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.historyWaterfall) this.$refs.historyWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.historyViewMode = this.viewMode
res.historyViewMode = this.setting.historyViewMode
setting.update(res)
})
},
showShiftPrompt () {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
this.$message.info('多选时支持shift快捷键')
this.setting.shiftTooltipLimitTimes--
setting.find().then(res => {
res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
setting.update(res)
})
}
}
},
mounted () {
this.rowDrop()
window.addEventListener('resize', () => {
if (this.$refs.historyWaterfall && this.view === 'History') {
this.$refs.historyWaterfall.resize()
this.$refs.historyWaterfall.refresh()
setTimeout(() => {
this.$refs.historyWaterfall.refresh()
}, 500)
}
}, false)
if (this.setting.historyViewMode === 'table') setTimeout(() => { this.rowDrop() }, 100)
addEventListener('keydown', code => { if (code.keyCode === 16) this.shiftDown = true })
addEventListener('keyup', code => { if (code.keyCode === 16) this.shiftDown = false })
addEventListener('resize', () => {
setTimeout(() => { if (this.$refs.historyWaterfall) this.$refs.historyWaterfall.resize() }, 500)
})
},
created () {
this.getAllhistory()
this.getViewMode()
}
}
</script>

View File

@@ -2,17 +2,17 @@
<div class="listpage" id="iptv">
<div class="listpage-header" id="iptv-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理及频道调整"></el-switch>
<el-button @click.stop="exportChannels" icon="el-icon-upload2" >导出</el-button>
<el-button @click.stop="importChannels" icon="el-icon-download">导入</el-button>
<el-button @click="checkAllChannels" icon="el-icon-refresh" :loading="checkAllChannelsLoading">检测{{ this.checkAllChannelsLoading ? this.checkProgress + '/' + this.iptvList.length : '' }}</el-button>
<el-button @click.stop="exportChannels" icon="el-icon-upload2" title="导出m3u时必须手动添加扩展名要保存频道配置信息请选择json格式">导出</el-button>
<el-button @click.stop="importChannels" icon="el-icon-download" title='支持同时导入多个文件,导入m3u时网址可带参数、含有"#"号时自动分割'>导入</el-button>
<el-button @click="checkAllChannels" icon="el-icon-refresh" :loading="checkAllChannelsLoading" title="可在后台运行">检测{{ this.checkAllChannelsLoading ? this.checkProgress + '/' + this.iptvList.length : '' }}</el-button>
<el-button @click.stop="resetChannelsEvent" icon="el-icon-refresh-left">重置</el-button>
</div>
<div class="listpage-header" id="iptv-header" v-show="enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理及频道调整"></el-switch>
<el-input placeholder="新组名/新频道名" v-model="inputContent"></el-input>
<el-switch v-model="batchIsActive" active-text="启用"></el-switch>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit">保存分组与开关状态</el-button>
<el-button type="primary" icon="el-icon-film" @click.stop="mergeChannel">{{ this.multipleSelection.length === 1 ? '频道重命名' : '频道合并' }}</el-button>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit" title="输入框组名为空时仅保存开关状态">保存分组与开关状态</el-button>
<el-button type="primary" icon="el-icon-film" @click.stop="mergeChannel" title="勾选单个时可重命名频道">{{ this.multipleSelection.length === 1 ? '频道重命名' : '频道合并' }}</el-button>
<el-button @click.stop="removeSelectedChannels" icon="el-icon-delete-solid">删除</el-button>
</div>
<div class="listpage-body" id="iptv-table">
@@ -107,7 +107,7 @@
</template>
<script>
import { mapMutations } from 'vuex'
import { iptv, channelList } from '../lib/dexie'
import { iptv, channelList, setting } from '../lib/dexie'
import { iptv as defaultChannels } from '../lib/dexie/initData'
import zy from '../lib/site/tools'
import { remote } from 'electron'
@@ -143,8 +143,13 @@ export default {
this.SET_VIEW(val)
}
},
setting () {
return this.$store.getters.getSetting
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
},
video: {
get () {
@@ -175,11 +180,6 @@ export default {
}
},
watch: {
view () {
if (this.view === 'IPTV' && !this.checkAllChannelsLoading) {
this.getChannelList()
}
},
enableBatchEdit () {
if (this.checkAllChannelsLoading) {
this.$message.info('正在检测, 请勿操作.')
@@ -187,6 +187,15 @@ export default {
return
}
if (this.enableBatchEdit) {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
this.$message.info('多选时支持shift快捷键')
this.setting.shiftTooltipLimitTimes--
setting.find().then(res => {
res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
setting.update(res)
})
}
this.$nextTick(() => {
this.expandedRows.forEach(e => this.$refs.iptvTable.toggleRowExpansion(e, false))
})
@@ -197,7 +206,7 @@ export default {
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
sortByLocaleCompare (a, b) {
return a.localeCompare(b, 'zh')
},
@@ -318,6 +327,7 @@ export default {
fs.writeFileSync(result.filePath, writer.toString())
this.$message.success('已保存成功')
} else {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
const arr = [...this.channelList] // 要保存channelList必须选json
const str = JSON.stringify(arr, null, 2)
fs.writeFileSync(result.filePath, str)
@@ -550,6 +560,7 @@ export default {
}
},
async checkAllChannels () {
if (this.checkAllChannelsLoading) return
this.checkAllChannelsLoading = true
this.stopFlag = false
this.checkProgress = 0
@@ -560,6 +571,7 @@ export default {
await this.checkChannelsBySite(other).then(res => {
this.checkAllChannelsLoading = false
this.getChannelList()
if (!this.stopFlag) this.$message.success('直播频道批量检测已完成!')
})
},
async checkChannelsBySite (channels) {

View File

@@ -98,25 +98,29 @@
<circle cx="12" cy="12" r="10"></circle>
</svg>
</span>
<span class="timespan" v-if="right.list.length > 1">
<label>片头长度</label>
<span class="timespanSwitch" v-if="right.list.length > 1" title="跳过片头片尾,建议优先通过快捷键设置,更便捷更精准">
<el-switch v-model="state.showTimespanSetting" active-text="手动跳略时长"></el-switch>
</span>
<span class="timespan" v-if="state.showTimespanSetting">
<label>片头</label>
<input type="number" v-model="startPosition.min" style="width:45px" min="00" max="59" placeholder="00" required>
<label>:</label>
<input type="number" v-model="startPosition.sec" style="width:45px" min="00" max="59" placeholder="00" required>
<span></span>
<label>片尾长度</label>
<label>片尾</label>
<input type="number" v-model="endPosition.min" style="width:45px" min="00" max="59" placeholder="00" required>
<label>:</label>
<input type="number" v-model="endPosition.sec" style="width:45px" min="00" max="59" placeholder="00" required>
<span></span>
<input type="button" value="确定" @click="setProgressDotByInput" title="还可以通过快捷键设置">
<input type="button" value="确定" @click="setProgressDotByInput">
<span></span>
<input type="button" value="重置" @click="() => { startPosition.min = startPosition.sec = endPosition.min = endPosition.sec = '00'; this.clearPosition() }">
</span>
<span class="last-tip" v-if="!video.key && right.history.length > 0" @click="historyItemEvent(right.history[0])">上次播放到{{right.history[0].site}}{{right.history[0].name}} {{right.history[0].index+1}}</span>
<span class="last-tip" v-if="!video.key && right.history.length > 0" @click="historyItemEvent(right.history[0])">
上次播放到:{{right.history[0].site}}{{right.history[0].name}} {{right.history[0].index+1}} {{fmtMSS(right.history[0].time.toFixed(0))}}/{{fmtMSS(right.history[0].duration.toFixed(0))}}</span>
</div>
<div class="more" v-if="video.iptv" :key="Boolean(video.iptv)">
<span class="zy-svg" @click="channelListShow = !channelListShow">
<span class="zy-svg" @click="state.showChannelList = !state.showChannelList">
<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">频道列表</title>
<rect width="20" height="20" x="2" y="2"></rect>
@@ -199,10 +203,10 @@
</div>
</transition>
<transition name="slideX">
<div v-if="channelListShow" class="list">
<div v-if="state.showChannelList" class="list" v-on-clickaway="closeListEvent">
<div class="list-top">
<span class="list-top-title">频道列表</span>
<span class="list-top-close zy-svg" @click="channelListShow = false">
<span class="list-top-close zy-svg" @click="state.showChannelList = false">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
@@ -212,13 +216,13 @@
<div class="list-body zy-scroll" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<el-input
clearable
size="small"
size="small" title="支持按拼音首字母搜索"
v-model.trim="searchTxt"
placeholder="搜索">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<el-tree ref="channelTree"
:data="channelListForShow"
:data="channelTree"
:props="defaultProps"
accordion
:filter-node-method="filterNode"
@@ -328,7 +332,9 @@ export default {
},
state: {
showList: false,
showHistory: false
showHistory: false,
showChannelList: false,
showTimespanSetting: false
},
name: '',
length: 0,
@@ -340,14 +346,16 @@ export default {
mainWindowBounds: {},
searchTxt: '',
channelList: [],
channelListForShow: [],
channelListShow: false,
channelTree: [],
isLive: false,
defaultProps: {
label: 'label',
children: 'children'
},
startPosition: { min: '00', sec: '00' },
endPosition: { min: '00', sec: '00' }
startPosition: { min: '00', sec: '00' }, // 对应调略输入框
endPosition: { min: '00', sec: '00' },
skipendStatus: false, // 是否跳过了片尾
currentShortcutList: []
}
},
filters: {
@@ -404,18 +412,23 @@ export default {
this.SET_APPSTATE(val)
}
},
setting () {
return this.$store.getters.getSetting
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
}
},
watch: {
view () {
this.right.show = false
this.right.type = ''
if (this.view === 'Play') {
this.right.show = false
this.right.type = ''
this.getChannelList()
if (this.video.key === '' && !this.video.iptv) {
this.channelListShow = true
if (this.video.key === '' && !this.isLive) {
this.state.showChannelList = true
}
}
},
@@ -460,13 +473,15 @@ export default {
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_APPSTATE']),
leadingZero (time) {
fmtMSS (s) {
return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
},
leadingZero (time) { // 格式化单个调略输入框
Object.keys(time).forEach(key => {
if (time[key] > 59 || time[key] < 0) {
time[key] = '00'
} else if (time[key].length > 2) {
time[key] = '' + parseInt(time[key])
// time[key] = time[key].replace(/^0+/, '')
} else if (time[key] < 10 && time[key].length === 1) {
time[key] = '0' + time[key]
}
@@ -483,7 +498,7 @@ export default {
},
async getUrls () {
if (this.video.key === '') {
if (!this.video.iptv) this.channelListShow = true
if (!this.video.iptv) this.state.showChannelList = true
return false
}
this.name = ''
@@ -499,7 +514,7 @@ export default {
// 是直播源,直接播放
this.playChannel(this.video.iptv)
} else {
this.channelListShow = false
this.state.showChannelList = false
const index = this.video.info.index || 0
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
const key = this.video.key + '@' + this.video.info.id
@@ -511,7 +526,7 @@ export default {
time = db.time
}
if (!VIDEO_DETAIL_CACHE[key]) VIDEO_DETAIL_CACHE[key] = {}
if (db.startPosition) {
if (db.startPosition) { // 数据库保存的时长通过快捷键设置时可能为小数, this.startPosition为object对应输入框分秒转化到数据库后肯定为整数
VIDEO_DETAIL_CACHE[key].startPosition = db.startPosition
this.startPosition = { min: '' + parseInt(db.startPosition / 60), sec: '' + parseInt(db.startPosition % 60) }
}
@@ -534,6 +549,7 @@ export default {
channelList.add(ele)
},
playChannel (channel) {
this.isLive = true
if (channel.channels) {
this.right.sources = channel.channels.filter(e => e.isActive)
channel = channel.prefer ? channel.channels.find(e => e.id === channel.prefer) : channel.channels.filter(e => e.isActive)[0]
@@ -548,12 +564,11 @@ export default {
this.name = channel.name
this.xg.src = channel.url
this.xg.play()
document.querySelector('xg-btn-quitMiniMode').style.display = 'none'
document.querySelector('xg-btn-showhistory').style.display = 'none'
document.querySelector('.xgplayer-playbackrate').style.display = 'none'
},
playVideo (index = 0, time = 0) {
document.querySelector('xg-btn-quitMiniMode').style.display = 'none'
this.isLive = false
document.querySelector('xg-btn-showhistory').style.display = 'block'
document.querySelector('.xgplayer-playbackrate').style.display = 'inline-block'
this.fetchM3u8List().then(m3u8Arr => {
@@ -573,13 +588,13 @@ export default {
if (VIDEO_DETAIL_CACHE[key].endPosition) this.xg.addProgressDot(this.xg.duration - VIDEO_DETAIL_CACHE[key].endPosition, '片尾')
})
this.videoPlaying()
VIDEO_DETAIL_CACHE[key].skipend = false
this.skipendStatus = false
this.xg.once('ended', () => {
if (m3u8Arr.length > 1 && (m3u8Arr.length - 1 > index)) {
this.video.info.time = 0
this.video.info.index++
}
this.xg.off('ended')
this.xg.off('ended') // 明明是once为何会触发多次得注销掉以真正只执行一次
})
}
})
@@ -654,14 +669,14 @@ export default {
this.checkStar()
this.checkTop()
},
async setProgressDotEvent (position, timespan, text) {
async setProgressDotEvent (position, timespan, text) { // 根据跳略时长在进度条上添加标记, position为位置, timespan为时长text为标记文本(title)
const key = this.video.key + '@' + this.video.info.id
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db && this.xg && VIDEO_DETAIL_CACHE[key].list.length > 1) {
this[position] = { min: '' + parseInt(timespan / 60), sec: '' + parseInt(timespan % 60) }
const positionTime = position === 'endPosition' ? this.xg.duration - timespan : timespan
if (db[position]) this.xg.removeProgressDot(position === 'endPosition' ? this.xg.duration - db[position] : db[position])
this.xg.addProgressDot(positionTime, text)
if (parseInt(this[position].min) || parseInt(this[position].sec)) this.xg.addProgressDot(positionTime, text) // 均为0时不添加标记
const doc = { ...db }
doc[position] = timespan
delete doc.id
@@ -669,7 +684,7 @@ export default {
VIDEO_DETAIL_CACHE[key][position] = timespan
}
},
async setProgressDotByInput () {
async setProgressDotByInput () { // 对应调略输入框后的“确定”
this.xg.removeAllProgressDot()
const startTime = parseInt(this.startPosition.min) * 60 + parseInt(this.startPosition.sec)
const endTime = parseInt(this.endPosition.min) * 60 + parseInt(this.endPosition.sec)
@@ -747,6 +762,7 @@ export default {
}
},
historyEvent () {
this.state.showChannelList = false
if (this.right.type === 'history') {
this.right.show = false
this.right.type = ''
@@ -915,6 +931,7 @@ export default {
closeListEvent () {
this.right.show = false
this.right.type = ''
this.state.showChannelList = false
},
exportM3u8 () {
const m3u8Arr = []
@@ -1023,7 +1040,7 @@ export default {
this.getOtherSites()
this.right.currentTime = this.xg.currentTime
} else {
this.channelListShow = false
this.state.showChannelList = false
this.right.type = 'sources'
}
this.right.show = true
@@ -1033,6 +1050,7 @@ export default {
this.video = { key: e.key, info: { id: e.id, name: e.name, site: e.site, index: this.video.info.index, time: this.right.currentTime } }
},
mtEvent () {
if (this.setting.shortcutModified) this.currentShortcutList.forEach(e => mt.unbind(e.key))
setting.find().then(res => {
if (res.shortcut) {
shortcut.all().then(res => {
@@ -1043,14 +1061,22 @@ export default {
}
})
}
this.currentShortcutList = res
})
} else {
shortcut.all().then(res => {
for (const i of res) {
mt.unbind(i.key)
}
this.currentShortcutList = []
})
}
}).finally(() => {
this.setting.shortcutModified = false
setting.find().then(res => {
res.shortcutModified = this.setting.shortcutModified
setting.update(res)
})
})
},
async shortcutEvent (e) {
@@ -1312,14 +1338,14 @@ export default {
async getChannelList () {
await channelList.all().then(res => {
this.channelList = res.filter(e => e.isActive)
this.channelListForShow = []
this.channelTree = []
const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
groups.forEach(g => {
var doc = {
label: g,
children: this.channelList.filter(x => x.group === g).map(i => { return { label: i.name, channel: i } })
}
this.channelListForShow.push(doc)
this.channelTree.push(doc)
})
})
},
@@ -1337,9 +1363,9 @@ export default {
const key = this.video.key + '@' + this.video.info.id
if (VIDEO_DETAIL_CACHE[key] && VIDEO_DETAIL_CACHE[key].endPosition) {
const time = this.xg.duration - VIDEO_DETAIL_CACHE[key].endPosition - this.xg.currentTime
if (time > 0 && time < 0.3) {
if (!VIDEO_DETAIL_CACHE[key].skipend) {
VIDEO_DETAIL_CACHE[key].skipend = true
if (time > 0 && time < 0.3) { // timeupdate每0.25秒触发一次,只有自然播放到该点时才会跳过片尾
if (!this.skipendStatus) {
this.skipendStatus = true
this.xg.emit('ended')
}
}
@@ -1392,6 +1418,25 @@ export default {
this.xg.on('exitFullscreen', () => {
document.querySelector('.xg-view-videoTitle').style.display = 'none'
})
this.xg.on('play', () => {
if (!this.video.key) {
if (!this.video.iptv && !this.video.info.ids) {
// 如果当前播放页面的播放信息没有被赋值,播放历史记录
if (this.right.history.length === 0) {
this.$message.error('历史记录为空,无法播放!')
this.videoStop()
return
}
var historyItem = this.right.history[0]
this.video = { key: historyItem.site, info: { id: historyItem.ids, name: historyItem.name, index: historyItem.index } }
this.getUrls()
} else if (this.video.iptv && !this.isLive) {
this.playChannel(this.video.iptv)
this.state.showChannelList = false
}
}
})
},
videoStop () {
win.setProgressBar(-1)
@@ -1405,13 +1450,16 @@ export default {
this.xg.destroy(false)
this.xg = null
this.name = ''
this.isLive = false
this.state.showChannelList = true
this.state.showTimespanSetting = false
this.right.list = []
this.getAllhistory()
setTimeout(() => {
this.xg = new HlsJsPlayer(this.config)
this.playerInstall()
this.bindEvent()
}, 1000)
}, 100)
},
minMaxEvent () {
win.on('minimize', () => {
@@ -1420,9 +1468,7 @@ export default {
}
})
win.on('restore', () => {
// 不知为何在if clause里直接使用this.xg.hasStart居然就不工作不得其解。
var hasStart = this.xg.hasStart
if (this.xg && hasStart) {
if (this.xg && this.xg.hasStart) {
this.xg.play()
}
})
@@ -1508,6 +1554,9 @@ export default {
cursor: pointer;
margin-left: 3px;
}
.xgplayer-skin-default .xg-btn-quitMiniMode {
display: none;
}
.xgplayer-skin-default .xg-btn-playPrev:hover,
.xgplayer-skin-default .xg-btn-playNextOne:hover,
.xgplayer-skin-default .xg-btn-showList:hover,

View File

@@ -1,345 +1,338 @@
<template>
<div class="listpage" id="recommendataions">
<div class="listpage-header" id="recommendataions-header">
<el-switch v-model="viewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button type="text">视频数{{ recommendations.length }}</el-button>
<el-select v-model="selectedAreas" size="small" multiple collapse-tags placeholder="地区" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in areas"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="selectedTypes" size="small" multiple collapse-tags placeholder="类型" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in types"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="sortKeyword" size="small" placeholder="排序" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in sortKeywords"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-button :loading="loading" @click.stop="updateEvent" icon="el-icon-refresh">更新推荐</el-button>
</div>
<div class="listpage-body" id="recommendataions-body" >
<div class="show-table" id="star-table" v-show="viewMode === 'table'">
<el-table size="mini" fit height="100%" row-key="id"
ref="recommendataionsTable"
:data="filteredRecommendations"
@row-click="detailEvent">
<el-table-column
prop="name"
label="片名">
</el-table-column>
<el-table-column
prop="detail.area"
label="地区"
width="100">
</el-table-column>
<el-table-column
prop="detail.type"
label="类型"
width="100">
</el-table-column>
<el-table-column
prop="detail.year"
label="上映"
width="100"
align="center">
</el-table-column>
<el-table-column v-if="filteredRecommendations.some(e => e.rate)"
prop="rate"
align="center"
width="100"
label="豆瓣评分">
</el-table-column>
<el-table-column v-if="filteredRecommendations.some(e => e.detail.note)"
prop="detail.note"
label="备注">
</el-table-column>
<el-table-column
label="操作"
header-align="center"
align="right"
width="200">
<template slot-scope="scope">
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="deleteEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="show-picture" id="star-picture" v-show="viewMode === 'picture'">
<Waterfall ref="recommendataionsWaterfall" :list="filteredRecommendations" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
},
800: { //当屏幕宽度小于等于800
rowPerView: 3,
},
500: { //当屏幕宽度小于等于500
rowPerView: 2,
}
}"
animationDuration="0.5s"
backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props">
<div class="card">
<div class="img">
<div class="rate" v-if="props.data.rate && props.data.rate !== '暂无评分'">
<span>{{props.data.rate}}</span>
</div>
<img style="width: 100%" :src="props.data.detail.pic" alt="" @load="$refs.recommendataionsWaterfall.refresh()" @click="detailEvent(props.data)">
<div class="operate">
<div class="operate-wrap">
<span class="o-play" @click="playEvent(props.data)">播放</span>
<span class="o-share" @click="shareEvent(props.data)">分享</span>
<span class="o-star" @click="downloadEvent(props.data)">下载</span>
<span class="o-star" @click="deleteEvent(props.data)">删除</span>
</div>
</div>
</div>
<div class="name" @click="detailEvent(props.data)">{{props.data.name}}</div>
<div class="info">
<span>{{props.data.detail.area}}</span>
<span>{{props.data.detail.year}}</span>
<span>{{props.data.detail.note}}</span>
<span>{{props.data.detail.type}}</span>
</div>
</div>
</template>
</Waterfall>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { history, recommendation, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import Waterfall from 'vue-waterfall-plugin'
const { clipboard } = require('electron')
export default {
name: 'recommendations',
data () {
return {
recommendations: [],
sites: [],
viewMode: 'picture',
loading: false,
types: [],
selectedTypes: [],
areas: [],
selectedAreas: [],
sortKeyword: '',
sortKeywords: ['上映', '评分', '默认']
}
},
components: {
Waterfall
},
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)
}
},
filteredRecommendations () {
var filteredData = this.recommendations.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
return filteredData
}
},
watch: {
view () {
if (this.view === 'Recommendation') {
this.getRecommendations()
if (this.$refs.recommendataionsWaterfall) {
this.$refs.recommendataionsWaterfall.refresh()
}
}
},
sortKeyword () {
switch (this.sortKeyword) {
case '上映':
this.recommendations = this.recommendations.sort(function (a, b) {
return b.detail.year - a.detail.year
})
break
case '评分':
this.recommendations.sort(function (a, b) {
return b.rate - a.rate
})
break
case '默认':
this.recommendations.sort(function (a, b) {
return b.id - a.id
})
break
default:
break
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
detailEvent (e) {
this.detail = {
show: true,
key: e.key,
info: {
id: e.ids,
name: e.name
}
}
},
updateEvent () {
const url = 'https://raw.githubusercontent.com/cuiocean/ZY-Player-Resources/main/Recommendations/Recommendations.json'
this.loading = true
const axios = require('axios')
axios.get(url).then(res => {
if (res.status === 200) {
if (res.data.length > 0) {
this.recommendations = res.data
recommendation.clear().then(recommendation.bulkAdd(this.recommendations))
this.getFilterData()
this.$message.success('更新推荐成功. 仅根据作者cuiocean个人喜好推荐,不喜请无视.')
}
}
this.loading = false
}).catch(error => {
this.loading = false
this.$message.error('更新推荐失败. ' + error)
this.$message.warning('最新的推荐数据保存在Github上,请考虑使用代理或者等待下一版本内置数据更新.')
})
},
async playEvent (e) {
const db = await history.find({ site: e.key, ids: e.ids })
if (db) {
this.video = { key: e.key, info: { id: db.ids, name: db.name, index: db.index }, detail: db.detail }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 }, detail: e.detail }
}
this.view = 'Play'
},
deleteEvent (e) {
recommendation.remove(e.id).then(res => {
if (res) {
this.$message.warning('删除失败')
}
this.getRecommendations()
})
},
shareEvent (e) {
this.share = {
show: true,
key: e.key,
info: e
}
},
downloadEvent (e) {
const key = e.key
const id = e.ids
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
})
},
getRecommendations () {
recommendation.all().then(res => {
this.recommendations = res.sort(function (a, b) {
return b.id - a.id
})
this.getFilterData()
})
},
getFilterData () {
this.types = [...new Set(this.recommendations.map(ele => ele.detail.type))].filter(x => x)
this.areas = [...new Set(this.recommendations.map(ele => ele.detail.area))].filter(x => x)
},
getViewMode () {
setting.find().then(res => {
this.viewMode = res.recommendationViewMode
})
},
updateViewMode () {
setting.find().then(res => {
res.recommendationViewMode = this.viewMode
setting.update(res)
})
}
},
created () {
this.getRecommendations()
this.getViewMode()
},
mounted () {
window.addEventListener('resize', () => {
if (this.$refs.recommendataionsWaterfall && this.view === 'Recommendation') {
this.$refs.recommendataionsWaterfall.resize()
this.$refs.recommendataionsWaterfall.refresh()
setTimeout(() => {
this.$refs.recommendataionsWaterfall.refresh()
}, 500)
}
}, false)
}
}
</script>
<template>
<div class="listpage" id="recommendataions">
<div class="listpage-header" id="recommendataions-header">
<el-switch v-model="setting.recommendationViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button type="text">视频数{{ recommendations.length }}</el-button>
<el-select v-model="selectedAreas" size="small" multiple placeholder="地区" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in areas"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="selectedTypes" size="small" multiple placeholder="类型" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in types"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="sortKeyword" size="small" placeholder="排序" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in sortKeywords"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-button :loading="loading" @click.stop="updateEvent" icon="el-icon-refresh">更新推荐</el-button>
</div>
<div class="listpage-body" id="recommendataions-body" >
<div class="show-table" id="star-table" v-if="setting.recommendationViewMode === 'table'">
<el-table size="mini" fit height="100%" row-key="id"
ref="recommendataionsTable"
:data="filteredRecommendations"
@row-click="detailEvent">
<el-table-column
prop="name"
label="片名">
</el-table-column>
<el-table-column
prop="detail.area"
label="地区"
width="100">
</el-table-column>
<el-table-column
prop="detail.type"
label="类型"
width="100">
</el-table-column>
<el-table-column
prop="detail.year"
label="上映"
width="100"
align="center">
</el-table-column>
<el-table-column v-if="filteredRecommendations.some(e => e.rate)"
prop="rate"
align="center"
width="100"
label="豆瓣评分">
</el-table-column>
<el-table-column v-if="filteredRecommendations.some(e => e.detail.note)"
prop="detail.note"
label="备注">
</el-table-column>
<el-table-column
label="操作"
header-align="center"
align="right"
width="200">
<template slot-scope="scope">
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="deleteEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="show-picture" id="star-picture" v-if="setting.recommendationViewMode === 'picture'">
<Waterfall ref="recommendataionsWaterfall" :list="filteredRecommendations" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
},
800: { //当屏幕宽度小于等于800
rowPerView: 3,
},
500: { //当屏幕宽度小于等于500
rowPerView: 2,
}
}"
animationDuration="0.5s"
backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props">
<div class="card">
<div class="img">
<div class="rate" v-if="props.data.rate && props.data.rate !== '暂无评分'">
<span>{{props.data.rate}}</span>
</div>
<img style="width: 100%" :src="props.data.detail.pic" alt="" @load="$refs.recommendataionsWaterfall.refresh()" @click="detailEvent(props.data)">
<div class="operate">
<div class="operate-wrap">
<span class="o-play" @click="playEvent(props.data)">播放</span>
<span class="o-share" @click="shareEvent(props.data)">分享</span>
<span class="o-star" @click="downloadEvent(props.data)">下载</span>
<span class="o-star" @click="deleteEvent(props.data)">删除</span>
</div>
</div>
</div>
<div class="name" @click="detailEvent(props.data)">{{props.data.name}}</div>
<div class="info">
<span>{{props.data.detail.area}}</span>
<span>{{props.data.detail.year}}</span>
<span>{{props.data.detail.note}}</span>
<span>{{props.data.detail.type}}</span>
</div>
</div>
</template>
</Waterfall>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { history, recommendation, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import Waterfall from 'vue-waterfall-plugin'
const { clipboard } = require('electron')
export default {
name: 'recommendations',
data () {
return {
recommendations: [],
sites: [],
loading: false,
types: [],
selectedTypes: [],
areas: [],
selectedAreas: [],
sortKeyword: '',
sortKeywords: ['上映', '评分', '默认']
}
},
components: {
Waterfall
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
},
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
},
filteredRecommendations () {
var filteredData = this.recommendations.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
return filteredData
}
},
watch: {
view () {
if (this.view === 'Recommendation') {
if (this.$refs.recommendataionsWaterfall) this.$refs.recommendataionsWaterfall.resize()
}
},
sortKeyword () {
switch (this.sortKeyword) {
case '上映':
this.recommendations = this.recommendations.sort(function (a, b) {
return b.detail.year - a.detail.year
})
break
case '评分':
this.recommendations.sort(function (a, b) {
return b.rate - a.rate
})
break
case '默认':
this.recommendations.sort(function (a, b) {
return b.id - a.id
})
break
default:
break
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
detailEvent (e) {
this.detail = {
show: true,
key: e.key,
info: {
id: e.ids,
name: e.name
}
}
},
updateEvent () {
const url = 'https://raw.githubusercontent.com/cuiocean/ZY-Player-Resources/main/Recommendations/Recommendations.json'
this.loading = true
const axios = require('axios')
axios.get(url).then(res => {
if (res.status === 200) {
if (res.data.length > 0) {
this.recommendations = res.data
recommendation.clear().then(recommendation.bulkAdd(this.recommendations))
this.getFilterData()
this.$message.success('更新推荐成功. 仅根据作者cuiocean个人喜好推荐,不喜请无视.')
}
}
this.loading = false
}).catch(error => {
this.loading = false
this.$message.error('更新推荐失败. ' + error)
this.$message.warning('最新的推荐数据保存在Github上,请考虑使用代理或者等待下一版本内置数据更新.')
})
},
async playEvent (e) {
const db = await history.find({ site: e.key, ids: e.ids })
if (db) {
this.video = { key: e.key, info: { id: db.ids, name: db.name, index: db.index }, detail: db.detail }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 }, detail: e.detail }
}
this.view = 'Play'
},
deleteEvent (e) {
recommendation.remove(e.id).then(res => {
if (res) {
this.$message.warning('删除失败')
}
this.getRecommendations()
})
},
shareEvent (e) {
this.share = {
show: true,
key: e.key,
info: e.detail
}
},
downloadEvent (e) {
const key = e.key
const id = e.ids
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
})
},
getRecommendations () {
recommendation.all().then(res => {
this.recommendations = res.sort(function (a, b) {
return b.id - a.id
})
this.getFilterData()
})
},
getFilterData () {
this.types = [...new Set(this.recommendations.map(ele => ele.detail.type))].filter(x => x)
this.areas = [...new Set(this.recommendations.map(ele => ele.detail.area))].filter(x => x)
},
updateViewMode () {
setTimeout(() => { if (this.$refs.recommendataionsWaterfall) this.$refs.recommendataionsWaterfall.refresh() }, 700)
setting.find().then(res => {
res.recommendationViewMode = this.setting.recommendationViewMode
setting.update(res)
})
}
},
created () {
this.getRecommendations()
},
mounted () {
addEventListener('resize', () => {
setTimeout(() => { if (this.$refs.recommendataionsWaterfall) this.$refs.recommendataionsWaterfall.resize() }, 500)
})
}
}
</script>

View File

@@ -5,22 +5,10 @@
<div class="info">
<a @click="linkOpen('http://zyplayer.fun/')">官网</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player')">Github</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">当前版本v{{pkg.version}} 反馈</a>
<a style="color:#38dd77" @click="quitAndInstall()" v-show="latestVersion !== pkg.version" >最新版本v{{latestVersion}}</a>
</div>
<div class="view">
<div class="title">视图</div>
<div class="view-box">
<div class="zy-select" @mouseleave="show.view = false">
<div class="vs-placeholder" @click="show.view = true">默认视图</div>
<div class="vs-options" v-show="show.view">
<ul class="zy-scroll">
<li :class="d.view === 'picture' ? 'active' : ''" @click="changeView('picture')">海报</li>
<li :class="d.view === 'table' ? 'active' : ''" @click="changeView('table')">列表</li>
</ul>
</div>
</div>
</div>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/releases/tag/v' + pkg.version)">v{{pkg.version}}更新日志</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues/80')">常见问题</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">反馈建议</a>
<a style="color:#38dd77" @click="openUpdate()" v-show="update.find" >最新版本v{{update.version}}</a>
</div>
<div class="shortcut">
<div class="title">快捷键</div>
@@ -40,6 +28,9 @@
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="impShortcut">导入</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="resetShortcut">重置</div>
</div>
</div>
</div>
<div class="shortcut">
@@ -210,13 +201,29 @@
</span>
</el-dialog>
</div>
<div class="update" v-if="update.show">
<div class="wrapper">
<div class="body">
<div class="content" v-html="update.html"></div>
<div class="progress" v-show="update.percent > 0">
<el-progress :percentage="update.percent"></el-progress>
<div class="size" style="font-size: 14px">更新包大小: {{update.size}} KB</div>
</div>
</div>
<div class="footer">
<el-button size="small" @click="cancelUpdate">取消</el-button>
<el-button size="small" v-show="!update.downloaded" @click="startUpdate">更新</el-button>
<el-button size="small" v-show="update.downloaded" @click="installUpdate">安装</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import pkg from '../../package.json'
import { setting, sites, shortcut } from '../lib/dexie'
import { sites as defaultSites } from '../lib/dexie/initData'
import { sites as defaultSites, localKey as defaultShortcuts } from '../lib/dexie/initData'
import { shell, clipboard, remote, ipcRenderer } from 'electron'
import db from '../lib/dexie/dexie'
import zy from '../lib/site/tools'
@@ -245,6 +252,15 @@ export default {
scheme: '',
url: '',
port: ''
},
update: {
find: false,
version: '',
show: false,
html: '',
percent: 0,
size: 0,
downloaded: false
}
}
},
@@ -290,11 +306,6 @@ export default {
this.shortcutList = res
})
},
changeView (e) {
this.d.view = e
this.updateSettingEvent()
this.show.view = false
},
async clearCache () {
const win = remote.getCurrentWindow()
const ses = win.webContents.session
@@ -416,10 +427,20 @@ export default {
this.$message.info('已清空原数据')
shortcut.add(json).then(e => {
this.$message.success('已添加成功')
this.getSites()
this.getShortcut()
this.d.shortcutModified = true
this.updateSettingEvent()
})
})
},
resetShortcut () {
shortcut.clear().then(shortcut.add(defaultShortcuts)).then(res => {
this.getShortcut()
this.$message.success('快捷键已重置')
this.d.shortcutModified = true
this.updateSettingEvent()
})
},
async changeProxyType (e) {
this.d.proxy.type = e
if (e === 'manual') {
@@ -466,20 +487,31 @@ export default {
return false
}
},
getLatestVersion () {
checkUpdate () {
ipcRenderer.send('checkForUpdate')
ipcRenderer.on('update-available', (e, info) => {
this.latestVersion = info.version
})
ipcRenderer.on('update-error', () => {
this.$message.warning = '更新出错.'
})
ipcRenderer.on('update-downloaded', () => {
this.$message.info = '下载完毕, 退出安装'
this.update.find = true
this.update.version = info.version
this.update.html = info.releaseNotes
})
},
quitAndInstall () {
this.$message.success('已开始下载更新,下载完毕后,将自动退出安装。')
openUpdate () {
this.update.show = true
},
cancelUpdate () {
this.update.show = false
},
startUpdate () {
ipcRenderer.send('downloadUpdate')
ipcRenderer.on('download-progress', (info, progress) => {
this.update.size = progress.total
this.update.percent = parseFloat(progress.percent).toFixed(1)
})
ipcRenderer.on('update-downloaded', () => {
this.update.downloaded = true
})
},
installUpdate () {
ipcRenderer.send('quitAndInstall')
},
createContextMenu () {
@@ -498,7 +530,7 @@ export default {
this.getSites()
this.getSetting()
this.getShortcut()
this.getLatestVersion()
this.checkUpdate()
this.createContextMenu()
}
}
@@ -536,17 +568,6 @@ export default {
cursor: pointer;
}
}
.view{
width: 100%;
padding: 20px;
margin-top: 20px;
.view-box{
margin-top: 10px;
.zy-select{
margin-right: 20px;
}
}
}
.site{
width: 100%;
padding: 20px;
@@ -641,5 +662,28 @@ export default {
font-size: 12px;
color: #ff000066;
}
.update{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(7, 17, 27, 0.7);
display: flex;
align-items: center;
justify-content: center;
.wrapper{
background-color: #fff;
padding: 20px 50px 40px;
border-radius: 4px;
max-width: 500px;
max-height: 90%;
overflow: auto;
.footer{
display: flex;
justify-content: flex-end;
}
}
}
}
</style>

View File

@@ -5,7 +5,7 @@
</div>
<div class="right" id="right">
<div class="title">{{ share.info.name }}</div>
<qrcode-vue id="qr" :value="link" :size="160" level="L" />
<qrcode-vue v-if="link !== ''" id="qr" :value="link" :size="160" level="L" />
<div class="tips">
<p>长按二维码识别播放</p>
<p><img src="@/assets/image/logo.png"></p>
@@ -68,22 +68,28 @@ export default {
info: {}
}
},
getDetail () {
this.loading = true
const id = this.share.info.ids || this.share.info.id
zy.detail(this.share.key, id).then(res => {
async getUrl (dl) {
const t = dl.dd._t
if (t) {
return t.split('#')[0].split('$')[1]
} else {
const id = this.share.info.ids || this.share.info.id
const res = await zy.detail(this.share.key, id)
if (res) {
this.pic = res.pic
var m3u8List = res.m3u8List
const url = m3u8List[1]
this.link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + url + '&name=' + this.share.info.name
return res.m3u8List[1]
}
this.loading = false
})
}
},
async getDetail () {
this.loading = true
this.pic = this.share.info.pic
const url = await this.getUrl(this.share.info.dl)
this.link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + url + '&name=' + this.share.info.name
this.loading = false
},
picLoadEvent () {
const dom = document.getElementById('share')
html2canvas(dom, { useCORS: true, allowTaint: true }).then(res => {
html2canvas(dom).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)

View File

@@ -1,20 +1,25 @@
<template>
<div class="listpage" id="star">
<div class="listpage-header" id="star-header">
<el-switch v-model="viewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button @click.stop="exportFavoritesEvent" icon="el-icon-upload2">导出</el-button>
<el-button @click.stop="importFavoritesEvent" icon="el-icon-download">导入</el-button>
<el-button @click.stop="clearFavoritesEvent" icon="el-icon-delete-solid">清空</el-button>
<el-switch v-model="setting.starViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button @click.stop="exportFavoritesEvent" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click.stop="importFavoritesEvent" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
<el-button @click.stop="removeSelectedItems" icon="el-icon-delete-solid">{{ multipleSelection.length === 0 ? "清空" : "删除所选" }}</el-button>
<el-button @click.stop="updateAllEvent" icon="el-icon-refresh">同步所有收藏</el-button>
</div>
<div class="listpage-body" id="star-body">
<div class="show-table" id="star-table" v-show="viewMode === 'table'">
<div class="show-table" id="star-table" v-if="setting.starViewMode === 'table'">
<el-table size="mini" fit height="100%" row-key="id"
ref="starTable"
:data="list"
:cell-class-name="checkUpdate"
@row-click="detailEvent"
@sort-change="handleSortChange">
ref="starTable"
:data="list"
:cell-class-name="checkUpdate"
@row-click="detailEvent"
@sort-change="handleSortChange"
@select="selectionCellClick"
@selection-change="handleSelectionChange">
<el-table-column
type="selection">
</el-table-column>
<el-table-column
sortable
:sort-method="(a , b) => sortByLocaleCompare(a.name, b.name)"
@@ -78,7 +83,7 @@
</el-table-column>
</el-table>
</div>
<div class="show-picture" id="star-picture" v-show="viewMode === 'picture'">
<div class="show-picture" id="star-picture" v-if="setting.starViewMode === 'picture'">
<Waterfall ref="starWaterfall" :list="list" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
@@ -146,8 +151,11 @@ export default {
return {
list: [],
sites: [],
viewMode: 'picture',
numNoUpdate: 0
numNoUpdate: 0,
shiftDown: false,
selectionBegin: '',
selectionEnd: '',
multipleSelection: []
}
},
components: {
@@ -185,6 +193,14 @@ export default {
set (val) {
this.SET_SHARE(val)
}
},
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
}
},
watch: {
@@ -192,9 +208,7 @@ export default {
if (this.view === 'Star') {
this.getAllsites()
this.getFavorites()
if (this.$refs.starWaterfall) {
this.$refs.starWaterfall.refresh()
}
if (this.setting.starViewMode === 'table') this.showShiftPrompt()
}
},
numNoUpdate () {
@@ -206,13 +220,40 @@ export default {
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']),
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
handleSortChange (column, prop, order) {
this.updateDatabase()
},
sortByLocaleCompare (a, b) {
return a.localeCompare(b, 'zh')
},
selectionCellClick (selection, row) { // 同history一样逆序
if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) {
this.selectionEnd = row.id
const start = this.list.findIndex(e => e.id === Math.max(this.selectionBegin, this.selectionEnd))
const end = this.list.findIndex(e => e.id === Math.min(this.selectionBegin, this.selectionEnd))
const selections = this.list.slice(start, end + 1)
this.$nextTick(() => {
selections.forEach(e => this.$refs.starTable.toggleRowSelection(e, true))
})
this.selectionBegin = this.selectionEnd = ''
return
}
if (selection.includes(row)) {
this.selectionBegin = row.id
} else {
this.selectionBegin = ''
}
},
handleSelectionChange (rows) {
this.multipleSelection = rows
},
removeSelectedItems () {
if (!this.multipleSelection.length) this.multipleSelection = this.list
this.multipleSelection.forEach(e => star.remove(e.id))
this.getFavorites()
this.updateDatabase()
},
detailEvent (e) {
this.detail = {
show: true,
@@ -249,7 +290,7 @@ export default {
this.share = {
show: true,
key: e.key,
info: e
info: e.detail
}
},
checkUpdate ({ row, rowIndex }) {
@@ -359,13 +400,12 @@ export default {
const str = JSON.stringify(arr, null, 2)
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
{ name: 'JSON file', extensions: ['json'] }
]
}
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
fs.writeFileSync(result.filePath, str)
this.$message.success('导出收藏成功')
}
@@ -376,9 +416,7 @@ export default {
importFavoritesEvent () {
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] },
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
{ name: 'JSON file', extensions: ['json'] }
],
properties: ['openFile', 'multiSelections']
}
@@ -426,11 +464,6 @@ export default {
this.$message.error(err)
})
},
clearFavoritesEvent () {
star.clear().then(e => {
this.getFavorites()
})
},
syncTableData () {
if (this.$refs.starTable.tableData && this.$refs.starTable.tableData.length === this.list.length) {
this.list = this.$refs.starTable.tableData
@@ -458,33 +491,40 @@ export default {
}
})
},
getViewMode () {
setting.find().then(res => {
this.viewMode = res.starViewMode
})
},
updateViewMode () {
if (this.setting.starViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.starWaterfall) this.$refs.starWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.starViewMode = this.viewMode
res.starViewMode = this.setting.starViewMode
setting.update(res)
})
},
showShiftPrompt () {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
this.$message.info('多选时支持shift快捷键')
this.setting.shiftTooltipLimitTimes--
setting.find().then(res => {
res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
setting.update(res)
})
}
}
},
created () {
this.getFavorites()
this.getViewMode()
},
mounted () {
this.rowDrop()
window.addEventListener('resize', () => {
if (this.$refs.starWaterfall && this.view === 'Star') {
this.$refs.starWaterfall.resize()
this.$refs.starWaterfall.refresh()
setTimeout(() => {
this.$refs.starWaterfall.refresh()
}, 500)
}
}, false)
if (this.setting.starViewMode === 'table') setTimeout(() => { this.rowDrop() }, 100)
addEventListener('keydown', code => { if (code.keyCode === 16) this.shiftDown = true })
addEventListener('keyup', code => { if (code.keyCode === 16) this.shiftDown = false })
addEventListener('resize', () => {
setTimeout(() => { if (this.$refs.starWaterfall) this.$refs.starWaterfall.resize() }, 500)
})
}
}
</script>

View File

@@ -16,6 +16,20 @@ db.version(4).stores({
channelList: '++id, name, prefer, channels, group, isActive'
})
// 参考https://github.com/dfahlander/Dexie.js/releases/tag/v3.0.0-alpha.3 upgrade可以改变主键和表名了
// https://dexie.org/docs/Version/Version.stores()
// https://dexie.org/docs/Version/Version.upgrade()
// https://ahuigo.github.io/b/ria/js-indexedDB#/ 比较旧,适当参考
db.version(5).stores({
shortcut: null
})
db.version(6).stores({
shortcut: '++id, name, key, desc'
}).upgrade(async tx => {
await tx.shortcut.bulkAdd(localKey)
})
db.on('populate', () => {
db.setting.bulkAdd(setting)
db.sites.bulkAdd(sites)

View File

@@ -1,5 +1,5 @@
import Vue from 'vue'
import { Message, Button, Table, TableColumn, Tag, Input, InputNumber, Dialog, Form, FormItem, Switch, Select, Option, Checkbox, Autocomplete, Col, Tree, Divider } from 'element-ui'
import { Message, Button, Table, TableColumn, Tag, Input, InputNumber, Dialog, Form, FormItem, Switch, Select, Option, Checkbox, Autocomplete, Col, Tree, Divider, Progress } from 'element-ui'
import Plugin from 'v-fit-columns'
Vue.use(Button)
Vue.use(Col)
@@ -19,4 +19,5 @@ Vue.use(Checkbox)
Vue.use(Autocomplete)
Vue.use(Tree)
Vue.use(Divider)
Vue.use(Progress)
Vue.prototype.$message = Message

View File

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

View File

@@ -1,5 +1,5 @@
import { BrowserWindow, ipcMain } from 'electron'
import { autoUpdater } from 'electron-updater'
const { autoUpdater } = require('@imjs/electron-differential-updater')
export function initUpdater (win = BrowserWindow) {
autoUpdater.autoDownload = false
@@ -10,9 +10,14 @@ export function initUpdater (win = BrowserWindow) {
autoUpdater.checkForUpdates()
})
// 主进程监听开始下载事件
ipcMain.on('downloadUpdate', () => {
autoUpdater.downloadUpdate()
})
// 主进程监听退出并安装事件
ipcMain.on('quitAndInstall', () => {
autoUpdater.downloadUpdate()
autoUpdater.quitAndInstall()
})
// 开始检测是否有更新
@@ -36,13 +41,12 @@ export function initUpdater (win = BrowserWindow) {
})
// 下载更新进度
autoUpdater.on('download-progress', (progressObj) => {
win.webContents.send('download-progress', progressObj)
autoUpdater.on('download-progress', (info, progress) => {
win.webContents.send('download-progress', info, progress)
})
// 下载完成并退出安装
// 下载完成
autoUpdater.on('update-downloaded', () => {
win.webContents.send('update-downloaded')
autoUpdater.quitAndInstall()
})
}

129
yarn.lock
View File

@@ -1116,6 +1116,23 @@
dependencies:
"@hapi/hoek" "^8.3.0"
"@imjs/electron-differential-updater@^5.1.3":
version "5.1.3"
resolved "https://registry.npm.taobao.org/@imjs/electron-differential-updater/download/@imjs/electron-differential-updater-5.1.3.tgz#8427c387cbc20d0fe256b636d450b80312c7cbaa"
integrity sha1-hCfDh8vCDQ/iVrY21FC4AxLHy6o=
dependencies:
"@types/node" "^14.0.11"
"@types/semver" "^7.1.0"
app-builder-bin "^3.5.9"
builder-util-runtime "^8.7.1"
fs-extra "^9.0.1"
js-yaml "^3.14.0"
lazy-val "^1.0.4"
lodash.isequal "^4.5.0"
semver "^7.3.2"
watch "^1.0.2"
zlib "^1.0.5"
"@intervolga/optimize-cssnano-plugin@^1.0.5":
version "1.0.6"
resolved "https://registry.npmjs.org/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz#be7c7846128b88f6a9b1d1261a0ad06eb5c0fdf8"
@@ -1304,6 +1321,11 @@
resolved "https://registry.npmjs.org/@types/node/-/node-12.12.49.tgz#f3dec66fce54758350d309b84f989003702f1190"
integrity sha512-bkB9k2k7Vu35WM1N06j93QgdXuALx9Dv3j7p/XHPAFTTc7tK8LLp43WltPg/LsciKDssbsr/shYsg036QJxNug==
"@types/node@^14.0.11":
version "14.14.11"
resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.11.tgz?cache=0&sync_timestamp=1607444891103&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.14.11.tgz#fc25a4248a5e8d0837019b1d170146d07334abe0"
integrity sha1-/CWkJIpejQg3AZsdFwFG0HM0q+A=
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -1324,7 +1346,7 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/semver@^7.3.1":
"@types/semver@^7.1.0":
version "7.3.4"
resolved "https://registry.npm.taobao.org/@types/semver/download/@types/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
integrity sha1-Q9cWj+xvoJiLsaUTppeykpZyGvs=
@@ -2021,6 +2043,11 @@ app-builder-bin@3.5.9:
resolved "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.9.tgz#a3ac0c25286bac68357321cb2eaf7128b0bc0a4f"
integrity sha512-NSjtqZ3x2kYiDp3Qezsgukx/AUzKPr3Xgf9by4cYt05ILWGAptepeeu0Uv+7MO+41o6ujhLixTou8979JGg2Kg==
app-builder-bin@^3.5.9:
version "3.5.10"
resolved "https://registry.npm.taobao.org/app-builder-bin/download/app-builder-bin-3.5.10.tgz#4a7f9999fccc0c435b6284ae1366bc76a17c4a7d"
integrity sha1-Sn+ZmfzMDENbYoSuE2a8dqF8Sn0=
app-builder-lib@22.7.0:
version "22.7.0"
resolved "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.7.0.tgz#ccd3e7ece2d46bc209423a77aa142f74aaf65db0"
@@ -2614,7 +2641,7 @@ builder-util-runtime@8.7.1:
debug "^4.2.0"
sax "^1.2.4"
builder-util-runtime@8.7.2:
builder-util-runtime@^8.7.1:
version "8.7.2"
resolved "https://registry.npm.taobao.org/builder-util-runtime/download/builder-util-runtime-8.7.2.tgz#d93afc71428a12789b437e13850e1fa7da956d72"
integrity sha1-2Tr8cUKKEnibQ34ThQ4fp9qVbXI=
@@ -3352,24 +3379,16 @@ core-js@^3.6.5:
resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
core-js@^3.7.0:
version "3.7.0"
resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.7.0.tgz?cache=0&sync_timestamp=1604675690423&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.7.0.tgz#b0a761a02488577afbf97179e4681bf49568520f"
integrity sha1-sKdhoCSIV3r7+XF55Ggb9JVoUg8=
core-js@^3.8.0:
version "3.8.1"
resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.8.1.tgz?cache=0&sync_timestamp=1607215907966&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.8.1.tgz#f51523668ac8a294d1285c3b9db44025fda66d47"
integrity sha1-9RUjZorIopTRKFw7nbRAJf2mbUc=
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
cosmiconfig@^5.0.0:
version "5.2.1"
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
@@ -3645,10 +3664,10 @@ d@1, d@^1.0.1:
es5-ext "^0.10.50"
type "^1.0.1"
danmu.js@0.2.23:
version "0.2.23"
resolved "https://registry.npm.taobao.org/danmu.js/download/danmu.js-0.2.23.tgz#339b512152ed5a8253829be007c5be5bd3821856"
integrity sha1-M5tRIVLtWoJTgpvgB8W+W9OCGFY=
danmu.js@^0.3.0:
version "0.3.0"
resolved "https://registry.npm.taobao.org/danmu.js/download/danmu.js-0.3.0.tgz#10ddd456dcc9ddd1835f07569773cb880d43173a"
integrity sha1-EN3UVtzJ3dGDXwdWl3PLiA1DFzo=
dependencies:
event-emitter "^0.3.5"
@@ -4160,23 +4179,10 @@ electron-to-chromium@^1.3.591:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.607.tgz#1bff13f1cf89f2fee0d244b8c64a7138f80f3a3b"
integrity sha512-h2SYNaBnlplGS0YyXl8oJWokfcNxVjJANQfMCsQefG6OSuAuNIeW+A8yGT/ci+xRoBb3k2zq1FrOvkgoKBol8g==
electron-updater@^4.3.5:
version "4.3.5"
resolved "https://registry.npm.taobao.org/electron-updater/download/electron-updater-4.3.5.tgz?cache=0&sync_timestamp=1600328924432&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-updater%2Fdownload%2Felectron-updater-4.3.5.tgz#4fb36f593a031c87ea07ee141c9f064d5deffb15"
integrity sha1-T7NvWToDHIfqB+4UHJ8GTV3v+xU=
dependencies:
"@types/semver" "^7.3.1"
builder-util-runtime "8.7.2"
fs-extra "^9.0.1"
js-yaml "^3.14.0"
lazy-val "^1.0.4"
lodash.isequal "^4.5.0"
semver "^7.3.2"
electron@^11.0.2:
version "11.0.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-11.0.3.tgz#c29eaacda38ce561890e59906ca5f507c72b3ec4"
integrity sha512-nNfbLi7Q1xfJXOEO2adck5TS6asY4Jxc332E4Te8XfQ9hcaC3GiCdeEqk9FndNCwxhJA5Lr9jfSGRTwWebFa/w==
electron@^11.0.3:
version "11.0.4"
resolved "https://registry.npm.taobao.org/electron/download/electron-11.0.4.tgz?cache=0&sync_timestamp=1607394603492&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron%2Fdownload%2Felectron-11.0.4.tgz#78b54760294eb42a36f33308a7987bf0b3dd6125"
integrity sha1-eLVHYClOtCo28zMIp5h78LPdYSU=
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"
@@ -4695,6 +4701,13 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
exec-sh@^0.2.0:
version "0.2.2"
resolved "https://registry.npm.taobao.org/exec-sh/download/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36"
integrity sha1-Kl5//L19C6J1W97LFuWkJ9+97DY=
dependencies:
merge "^1.2.0"
execa@^0.8.0:
version "0.8.0"
resolved "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
@@ -5951,9 +5964,9 @@ inherits@2.0.3:
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
inquirer@^7.1.0:
version "7.3.0"
@@ -6943,6 +6956,11 @@ merge2@^1.2.3:
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
merge@^1.2.0:
version "1.2.1"
resolved "https://registry.npm.taobao.org/merge/download/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
integrity sha1-OL6/gMMiCopIe2/Ps5QbsRcgwUU=
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -7390,7 +7408,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -10387,7 +10405,7 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vary@^1, vary@~1.1.2:
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
@@ -10533,10 +10551,18 @@ vuedraggable@^2.24.3:
dependencies:
sortablejs "1.10.2"
vuex@^3.5.1:
version "3.5.1"
resolved "https://registry.npm.taobao.org/vuex/download/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d"
integrity sha1-8bjc6mSbwlJUz09DWAgdv12hiz0=
vuex@^3.6.0:
version "3.6.0"
resolved "https://registry.npm.taobao.org/vuex/download/vuex-3.6.0.tgz?cache=0&sync_timestamp=1606318256705&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvuex%2Fdownload%2Fvuex-3.6.0.tgz#95efa56a58f7607c135b053350833a09e01aa813"
integrity sha1-le+lalj3YHwTWwUzUIM6CeAaqBM=
watch@^1.0.2:
version "1.0.2"
resolved "https://registry.npm.taobao.org/watch/download/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c"
integrity sha1-NApxe952Vyb6CqB9ch4BR6VR3ww=
dependencies:
exec-sh "^0.2.0"
minimist "^1.2.0"
watchpack-chokidar2@^2.0.0:
version "2.0.0"
@@ -10819,14 +10845,14 @@ xgplayer-hls.js@^2.2.5:
event-emitter "^0.3.5"
eventemitter3 "^4.0.7"
xgplayer@^2.13.1:
version "2.13.1"
resolved "https://registry.yarnpkg.com/xgplayer/-/xgplayer-2.13.1.tgz#a463ca0f80c635dc9d3d9b1adc58b8efab7bcba2"
integrity sha512-dwmlCgFQBzmm8ru11RMzAvhXOvmXJURzJ7zVIxuadUa+ofTb1brzEdqwbKEw7Hf35XnwsO3R/Ve+Di96tKlmRw==
xgplayer@^2.13.2:
version "2.14.4"
resolved "https://registry.npm.taobao.org/xgplayer/download/xgplayer-2.14.4.tgz?cache=0&sync_timestamp=1607313373198&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxgplayer%2Fdownload%2Fxgplayer-2.14.4.tgz#d0cfacd95a08807e1783db8b8546d373a5df52e8"
integrity sha1-0M+s2VoIgH4Xg9uLhUbTc6XfUug=
dependencies:
chalk "^2.3.2"
commander "^2.15.1"
danmu.js "0.2.23"
danmu.js "^0.3.0"
deepmerge "^1.5.0"
downloadjs "1.4.7"
draggabilly "^2.2.0"
@@ -10944,3 +10970,8 @@ yorkie@^2.0.0:
is-ci "^1.0.10"
normalize-path "^1.0.0"
strip-indent "^2.0.0"
zlib@^1.0.5:
version "1.0.5"
resolved "https://registry.npm.taobao.org/zlib/download/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0"
integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=