同步桌面端更新:重连/重试、断线自动重连、图片容错与鉴权参数
移植桌面端 2025-03-22 之后的更新到移动端,保留移动端自身布局: - 补齐管理子路径鉴权参数:缩略图、在线图片、/cache 请求带上 AuthCode; 原线上靠临时热补丁脚本 online-reader-auth-fix.js 绕过,现由源码原生支持。 - 断线自动重连:新增 src/utils/taskSocket.js,指数退避重连并在重连后重新同步 任务列表与用量;App.vue 接入页面可见性/网络恢复事件,Side.vue 显示连接状态。 - 未完成任务重试:新增 retryGallery action 与重试按钮;补 "等待压缩" 状态。 - 图片容错:新增 src/utils/progressiveImages.js,单图加载失败不再阻塞后续页, 支持单图重试。 - store 修复:收藏列表残留(slice→splice)、action 内改 context.state、 gid 判空(null→undefined)、deleteTask 跳过下一元素、resetUndone 用 state 授权码、 axios 全局错误拦截器、getShortname 简化。 - 下载链接统一由 buildGalleryDownloadUrl 生成(按 gid 而非名字)。 - 构建配置:base 改为 /asserts/mobile/、固定产物名 index.js/index.css、新增 .gitignore。 - 升级 Vite 8.3.0 + @vitejs/plugin-vue 6.0.9,npm audit 0 漏洞。 - 新增 tests/(11 项 Node 测试,全部通过)。
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
run.out
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" href="/asserts/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
|
||||||
<title>Vite + Vue</title>
|
<title>LionWebsite</title>
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Generated
+1136
-825
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test": "node --test tests/*.test.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.0.0",
|
"axios": "^1.0.0",
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
"vuex": "^4.0.2"
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.0",
|
"@vitejs/plugin-vue": "6.0.9",
|
||||||
"vite": "^5.0.10"
|
"vite": "8.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -1,7 +1,26 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import {onMounted, onBeforeUnmount} from "vue";
|
||||||
|
import store from "./store/index.js";
|
||||||
import Side from "./components/Side.vue";
|
import Side from "./components/Side.vue";
|
||||||
import DashBoard from "./components/DashBoard.vue";
|
import DashBoard from "./components/DashBoard.vue";
|
||||||
|
|
||||||
|
const resumeProgress = () => {
|
||||||
|
if (store.state.isAuth && document.visibilityState === 'visible') store.dispatch("initWebsocket");
|
||||||
|
};
|
||||||
|
const pauseProgress = () => store.dispatch("disconnectWebsocket");
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('online', resumeProgress);
|
||||||
|
window.addEventListener('pageshow', resumeProgress);
|
||||||
|
window.addEventListener('pagehide', pauseProgress);
|
||||||
|
document.addEventListener('visibilitychange', resumeProgress);
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('online', resumeProgress);
|
||||||
|
window.removeEventListener('pageshow', resumeProgress);
|
||||||
|
window.removeEventListener('pagehide', pauseProgress);
|
||||||
|
document.removeEventListener('visibilitychange', resumeProgress);
|
||||||
|
pauseProgress();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
<hr>
|
<hr>
|
||||||
<el-button @click="isQuerying = true">在线搜索</el-button>
|
<el-button @click="isQuerying = true">在线搜索</el-button>
|
||||||
|
<el-button @click="reconnect">重连节点</el-button>
|
||||||
<el-button @click="isAlterAuthCode = true">修改授权码</el-button>
|
<el-button @click="isAlterAuthCode = true">修改授权码</el-button>
|
||||||
<el-button @click="deleteAuthCode">删除本地授权码</el-button>
|
<el-button @click="deleteAuthCode">删除本地授权码</el-button>
|
||||||
<el-button @click="isConfig = true">配置</el-button><br>
|
<el-button @click="isConfig = true">配置</el-button><br>
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
|
|
||||||
<el-dialog title="查询" v-model="chosenGallery" width="100%">
|
<el-dialog title="查询" v-model="chosenGallery" width="100%">
|
||||||
<el-image v-show='chosenGallery.thumb_link !== undefined' style="float: right; width: 250px; height: 250px" fit="contain"
|
<el-image v-show='chosenGallery.thumb_link !== undefined' style="float: right; width: 250px; height: 250px" fit="contain"
|
||||||
:src="chosenGallery.thumb_link !== undefined ? 'https://downloader.lionwebsite.xyz/GalleryManage/ehThumbnail?path=' + chosenGallery.thumb_link: ''"/>
|
:src="chosenGallery.thumb_link !== undefined ? thumbnailUrl(chosenGallery.thumb_link) : ''"/>
|
||||||
<table>
|
<table>
|
||||||
<tr>名字:{{chosenGallery.name}}</tr>
|
<tr>名字:{{chosenGallery.name}}</tr>
|
||||||
<tr>页数:{{chosenGallery.pages}}</tr>
|
<tr>页数:{{chosenGallery.pages}}</tr>
|
||||||
@@ -42,11 +43,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="onlineGalleryReader(chosenGallery)">在线预览</el-button>
|
|
||||||
<el-button @click="postTask" v-if="chosenGallery.availableResolution">下载</el-button>
|
<el-button @click="postTask" v-if="chosenGallery.availableResolution">下载</el-button>
|
||||||
<tr v-if="chosenGallery.status === '下载完成'">
|
<el-button @click="downloadChosenGallery" v-else-if="chosenGallery.status === '下载完成'">下载文件</el-button>
|
||||||
<el-button @click="deleteGallery">删除</el-button>
|
<el-button @click="onlineGalleryReader(chosenGallery)">在线预览</el-button>
|
||||||
</tr>
|
<el-button v-if="chosenGallery.status === '下载完成'" @click="deleteGallery">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -123,9 +123,10 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
import {computed, ref, onMounted} from "vue";
|
import {computed, ref, onMounted, watch} from "vue";
|
||||||
import {ElMessage} from "element-plus"
|
import {ElMessage} from "element-plus"
|
||||||
import HentaiSearch from "./HentaiSearch.vue";
|
import HentaiSearch from "./HentaiSearch.vue";
|
||||||
|
import {validateLink} from "../utils/validate.js";
|
||||||
|
|
||||||
//授权码相关
|
//授权码相关
|
||||||
let AuthCode = ref("")
|
let AuthCode = ref("")
|
||||||
@@ -155,10 +156,14 @@ let realAuthCode = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let chosenGallery = computed(() => {
|
let chosenGallery = computed(() => {
|
||||||
param.value = ''
|
|
||||||
return store.state.chosenGallery
|
return store.state.chosenGallery
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(chosenGallery, () => {
|
||||||
|
param.value = ''
|
||||||
|
targetResolution.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
let loadComplete = computed(() => {
|
let loadComplete = computed(() => {
|
||||||
return store.state.loadComplete
|
return store.state.loadComplete
|
||||||
})
|
})
|
||||||
@@ -175,6 +180,11 @@ let isLion = computed(() => {
|
|||||||
return store.state.userId === 3
|
return store.state.userId === 3
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//重连节点
|
||||||
|
function reconnect(){
|
||||||
|
store.dispatch("reconnect")
|
||||||
|
}
|
||||||
|
|
||||||
//修改授权码
|
//修改授权码
|
||||||
function alterAuthCode(){
|
function alterAuthCode(){
|
||||||
if(newAuthCode.value.trim() === "" || tempAuthCode.value.trim() === "" || newAuthCode.value !== tempAuthCode.value)
|
if(newAuthCode.value.trim() === "" || tempAuthCode.value.trim() === "" || newAuthCode.value !== tempAuthCode.value)
|
||||||
@@ -223,6 +233,13 @@ function deleteGallery(){
|
|||||||
store.dispatch("deleteGallery", chosenGallery.value.gid)
|
store.dispatch("deleteGallery", chosenGallery.value.gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadChosenGallery(){
|
||||||
|
if(chosenGallery.value.download)
|
||||||
|
window.open(chosenGallery.value.download)
|
||||||
|
else
|
||||||
|
ElMessage({message: "下载地址不存在,请刷新任务后重试", type: "error"})
|
||||||
|
}
|
||||||
|
|
||||||
//验证授权码
|
//验证授权码
|
||||||
function validate(){
|
function validate(){
|
||||||
if(AuthCode.value.trim() === ""){
|
if(AuthCode.value.trim() === ""){
|
||||||
@@ -235,21 +252,18 @@ function validate(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//验证链接
|
|
||||||
function validateLink(rawLink){
|
|
||||||
if(rawLink.trim() === "")
|
|
||||||
return false
|
|
||||||
if(rawLink.includes("hentai"))
|
|
||||||
return rawLink.includes("/g/")
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
//打开面板以及在线阅读
|
//打开面板以及在线阅读
|
||||||
function onlineGalleryReader(gallery){
|
function onlineGalleryReader(gallery){
|
||||||
store.dispatch("readOnlineGallery", gallery)
|
store.dispatch("readOnlineGallery", gallery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function thumbnailUrl(path) {
|
||||||
|
return "https://downloader.lionwebsite.xyz/GalleryManage/ehThumbnail?" + new URLSearchParams({
|
||||||
|
path,
|
||||||
|
AuthCode: store.state.AuthCode
|
||||||
|
}).toString()
|
||||||
|
}
|
||||||
|
|
||||||
//重新给节点发送未完成任务
|
//重新给节点发送未完成任务
|
||||||
function resetUndone(){
|
function resetUndone(){
|
||||||
store.dispatch("resetUndone").then()
|
store.dispatch("resetUndone").then()
|
||||||
@@ -367,4 +381,4 @@ function saveConfig(){
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {ref, watch} from "vue";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import store from "../store/index.js";
|
import store from "../store/index.js";
|
||||||
|
import {validateLink} from "../utils/validate.js";
|
||||||
|
|
||||||
let props = defineProps(['isQuerying'])
|
let props = defineProps(['isQuerying'])
|
||||||
let emit = defineEmits(['close'])
|
let emit = defineEmits(['close'])
|
||||||
@@ -14,6 +15,7 @@ let queryPage = ref({})
|
|||||||
let galleries = ref([])
|
let galleries = ref([])
|
||||||
let param = ref()
|
let param = ref()
|
||||||
let isShowUp = ref()
|
let isShowUp = ref()
|
||||||
|
let isLoading = ref(false)
|
||||||
watch(props, () => {
|
watch(props, () => {
|
||||||
isShowUp.value = props.isQuerying
|
isShowUp.value = props.isQuerying
|
||||||
})
|
})
|
||||||
@@ -27,11 +29,10 @@ function queryGalleries(link){
|
|||||||
tempParam = keyword.value
|
tempParam = keyword.value
|
||||||
}
|
}
|
||||||
tempParam = tempParam.replace(" ", "+")
|
tempParam = tempParam.replace(" ", "+")
|
||||||
document.getElementById("loading").style.display = "inline-block";
|
isLoading.value = true
|
||||||
|
|
||||||
axios.get("https://downloader.lionwebsite.xyz/query?keyword=" + tempParam)
|
axios.get("https://downloader.lionwebsite.xyz/query?keyword=" + tempParam)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
document.getElementById("loading").style.display = "none";
|
|
||||||
if (res.data.result === "success") {
|
if (res.data.result === "success") {
|
||||||
let tempGalleries = JSON.parse(res.data.data)
|
let tempGalleries = JSON.parse(res.data.data)
|
||||||
queryPage.value.first = 'first' in res.data ? res.data.first : undefined
|
queryPage.value.first = 'first' in res.data ? res.data.first : undefined
|
||||||
@@ -48,6 +49,8 @@ function queryGalleries(link){
|
|||||||
}else {
|
}else {
|
||||||
ElMessage({message: res.data.data, type: "error"})
|
ElMessage({message: res.data.data, type: "error"})
|
||||||
}
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
isLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,19 +62,17 @@ function queryRemoteTask(){
|
|||||||
store.dispatch("queryGalleryTask", param.value)
|
store.dispatch("queryGalleryTask", param.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateLink(rawLink){
|
|
||||||
if(rawLink.trim() === "")
|
|
||||||
return false
|
|
||||||
if(rawLink.includes("hentai"))
|
|
||||||
return rawLink.includes("/g/")
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function close(){
|
function close(){
|
||||||
emit("close")
|
emit("close")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function thumbnailUrl(path) {
|
||||||
|
return "https://downloader.lionwebsite.xyz/GalleryManage/ehThumbnail?" + new URLSearchParams({
|
||||||
|
path,
|
||||||
|
AuthCode: store.state.AuthCode
|
||||||
|
}).toString()
|
||||||
|
}
|
||||||
|
|
||||||
function adjustGalleryName(name, length) {
|
function adjustGalleryName(name, length) {
|
||||||
let truncated = '';
|
let truncated = '';
|
||||||
let bytesCount = 0;
|
let bytesCount = 0;
|
||||||
@@ -95,12 +96,12 @@ function adjustGalleryName(name, length) {
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog title="在线搜索" v-model="isShowUp" top="0" style="margin-bottom: 0" fullscreen class="el-dialogClass" @close="close">
|
<el-dialog title="在线搜索" v-model="isShowUp" top="0" style="margin-bottom: 0" fullscreen class="el-dialogClass" @close="close">
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<el-input v-model="keyword" style="width: 50vw"></el-input> <el-button @click="queryGalleries(null)">查询</el-button> <div id="loading"/>
|
<el-input v-model="keyword" style="width: 50vw"></el-input> <el-button @click="queryGalleries(null)">查询</el-button> <div class="loading" v-show="isLoading"/>
|
||||||
</div>
|
</div>
|
||||||
<el-scrollbar height="75vh" ref="scrollBar">
|
<el-scrollbar height="75vh" ref="scrollBar">
|
||||||
<div style="height: 20vh; width: 100%; border-radius: 5px; padding-bottom: 2vh" v-for="gallery in galleries">
|
<div style="height: 20vh; width: 100%; border-radius: 5px; padding-bottom: 2vh" v-for="gallery in galleries">
|
||||||
<el-image alt="picture" :preview-src-list="['https://downloader.lionwebsite.xyz/GalleryManage/ehThumbnail?path=' + gallery.thumbnailUrl,]"
|
<el-image alt="picture" :preview-src-list="[thumbnailUrl(gallery.thumbnailUrl)]"
|
||||||
:src="'https://downloader.lionwebsite.xyz/GalleryManage/ehThumbnail?path=' + gallery.thumbnailUrl"
|
:src="thumbnailUrl(gallery.thumbnailUrl)"
|
||||||
style="height:20vh;width:35vw;float: left;"
|
style="height:20vh;width:35vw;float: left;"
|
||||||
fit="contain"
|
fit="contain"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -132,14 +133,13 @@ function adjustGalleryName(name, length) {
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading {
|
.loading {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
border: 2px solid #ccc;
|
border: 2px solid #ccc;
|
||||||
border-top-color: #3498db;
|
border-top-color: #3498db;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
@@ -150,4 +150,4 @@ function adjustGalleryName(name, length) {
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch, onBeforeUnmount} from "vue";
|
||||||
|
import {startImages, settleImage, retryImage} from "../utils/progressiveImages.js";
|
||||||
import store from "../store/index.js";
|
import store from "../store/index.js";
|
||||||
let onlineReadingScrollbar = ref()
|
let onlineReadingScrollbar = ref()
|
||||||
let links = ref()
|
let links = ref()
|
||||||
@@ -7,19 +8,18 @@ let index = ref(0) //下标
|
|||||||
let page = ref(0) //页数 下标+1=页数 用于跳转
|
let page = ref(0) //页数 下标+1=页数 用于跳转
|
||||||
|
|
||||||
let imagesForLoading = ref([])
|
let imagesForLoading = ref([])
|
||||||
let loadIndex = ref(0)
|
|
||||||
let isReading = ref(false)
|
let isReading = ref(false)
|
||||||
|
|
||||||
let max = ref(0)
|
let max = ref(0)
|
||||||
let current_page = 0
|
let current_page = 0
|
||||||
let lengthPerPage = computed(() => {
|
let lengthPerPage = computed(() => {
|
||||||
return store.state.lengthPerPage
|
return Math.max(1, Number(store.state.lengthPerPage) || 10)
|
||||||
})
|
})
|
||||||
let readingGallery = computed(() => {
|
let readingGallery = computed(() => {
|
||||||
return store.state.readingGallery
|
return store.state.readingGallery
|
||||||
})
|
})
|
||||||
watch((store.state), (value) => {
|
watch(() => store.state.isReading, (isReadingVal) => {
|
||||||
if(value.isReading && !isReading.value) {
|
if(isReadingVal && !isReading.value) {
|
||||||
alterPage()
|
alterPage()
|
||||||
isReading.value = true
|
isReading.value = true
|
||||||
}
|
}
|
||||||
@@ -36,28 +36,34 @@ function alterPage(){
|
|||||||
}
|
}
|
||||||
index.value = 0
|
index.value = 0
|
||||||
page.value = 1
|
page.value = 1
|
||||||
loadIndex.value = 0
|
imagesForLoading.value = startImages(links.value)
|
||||||
imagesForLoading.value.splice(0)
|
|
||||||
loadImage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//跳转到对应页数
|
//跳转到对应页数
|
||||||
function jump(targetIndex){
|
function jump(targetIndex){
|
||||||
|
if(targetIndex < 0 || targetIndex >= Math.ceil(readingGallery.value.images.length / lengthPerPage.value)) return
|
||||||
|
clearPageSwitchTimer()
|
||||||
links.value = readingGallery.value.images.slice(targetIndex * lengthPerPage.value, (targetIndex + 1) * lengthPerPage.value)
|
links.value = readingGallery.value.images.slice(targetIndex * lengthPerPage.value, (targetIndex + 1) * lengthPerPage.value)
|
||||||
index.value = targetIndex
|
index.value = targetIndex
|
||||||
page.value = targetIndex + 1
|
page.value = targetIndex + 1
|
||||||
imagesForLoading.value.splice(0)
|
imagesForLoading.value = startImages(links.value)
|
||||||
loadIndex.value = 0
|
|
||||||
loadImage()
|
|
||||||
onlineReadingScrollbar.value.setScrollTop(0)
|
onlineReadingScrollbar.value.setScrollTop(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadImage(){
|
function imageResult(item, failed = false){
|
||||||
if(loadIndex.value < links.value.length)
|
settleImage(imagesForLoading.value, links.value, item, failed)
|
||||||
imagesForLoading.value.push(links.value[loadIndex.value++])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pageSwitchTimer
|
||||||
|
function clearPageSwitchTimer(){
|
||||||
|
clearInterval(pageSwitchTimer)
|
||||||
|
pageSwitchTimer = undefined
|
||||||
|
}
|
||||||
|
onBeforeUnmount(clearPageSwitchTimer)
|
||||||
|
|
||||||
function closeDialog(){
|
function closeDialog(){
|
||||||
|
clearPageSwitchTimer()
|
||||||
|
imagesForLoading.value = []
|
||||||
onlineReadingScrollbar.value.setScrollTop(0)
|
onlineReadingScrollbar.value.setScrollTop(0)
|
||||||
isReading.value = false
|
isReading.value = false
|
||||||
store.commit("_closeReader")
|
store.commit("_closeReader")
|
||||||
@@ -70,9 +76,9 @@ function switch_page(target_page){
|
|||||||
jump(index.value - 1)
|
jump(index.value - 1)
|
||||||
onlineReadingScrollbar.value.setScrollTop(onlineReadingScrollbar.value.wrapRef.scrollHeight)
|
onlineReadingScrollbar.value.setScrollTop(onlineReadingScrollbar.value.wrapRef.scrollHeight)
|
||||||
let top = 0
|
let top = 0
|
||||||
let timer = setInterval(() => {
|
pageSwitchTimer = setInterval(() => {
|
||||||
if(onlineReadingScrollbar.value.scrollTop === top){
|
if(onlineReadingScrollbar.value.scrollTop === top){
|
||||||
clearInterval(timer)
|
clearPageSwitchTimer()
|
||||||
document.querySelector("div.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default > div > div:last-child > img").click()
|
document.querySelector("div.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default > div > div:last-child > img").click()
|
||||||
}
|
}
|
||||||
top = onlineReadingScrollbar.value.scrollTop
|
top = onlineReadingScrollbar.value.scrollTop
|
||||||
@@ -81,7 +87,7 @@ function switch_page(target_page){
|
|||||||
|
|
||||||
|
|
||||||
//下一页
|
//下一页
|
||||||
}else if(target_page === 0 && current_page === lengthPerPage.value - 1 && index.value < max.value){
|
}else if(target_page === 0 && current_page === lengthPerPage.value - 1 && index.value < max.value - 1){
|
||||||
jump(index.value + 1)
|
jump(index.value + 1)
|
||||||
current_page = 0
|
current_page = 0
|
||||||
document.querySelector("div.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default > div > div:nth-child(1) > img").click()
|
document.querySelector("div.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default > div > div:nth-child(1) > img").click()
|
||||||
@@ -107,9 +113,10 @@ function set_current_page(page){
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-scrollbar height="75vh" ref="onlineReadingScrollbar">
|
<el-scrollbar height="75vh" ref="onlineReadingScrollbar">
|
||||||
<div v-for="(link, i) in imagesForLoading" style="display: inline-block; text-align: center">
|
<div v-for="(item, i) in imagesForLoading" :key="item.url + '-' + item.attempt" style="display: inline-block; text-align: center">
|
||||||
<el-image :src="link" :style="{'width': 'auto', 'text-align': 'center', 'background-color': 'ghostwhite'}"
|
<el-image :src="item.src" :style="{'width': 'auto', 'text-align': 'center', 'background-color': 'ghostwhite'}"
|
||||||
:preview-src-list="links" :initial-index="i" @switch="switch_page" @show="set_current_page(i)" loading="lazy" @load="loadImage"/> <br>
|
:preview-src-list="links" :initial-index="i" @switch="switch_page" @show="set_current_page(i)" loading="lazy" @load="imageResult(item)" @error="imageResult(item, true)"/>
|
||||||
|
<div v-if="item.failed" role="alert">图片加载失败 <el-button size="small" @click="retryImage(item)">重试</el-button></div> <br>
|
||||||
{{lengthPerPage * index + i + 1}}
|
{{lengthPerPage * index + i + 1}}
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
@@ -137,4 +144,4 @@ function set_current_page(page){
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+23
-2
@@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="side">
|
<div class="side">
|
||||||
<div v-show="loadComplete" class="load_complete">
|
<div v-show="loadComplete" class="load_complete">
|
||||||
|
<el-tag :type="store.state.connectionStatus === 'connected' ? 'success' : 'warning'" role="status">
|
||||||
|
{{ {connected: '进度已连接', connecting: '正在连接进度', reconnecting: '连接中断,正在重连', disconnected: '进度连接已断开'}[store.state.connectionStatus] }}
|
||||||
|
</el-tag>
|
||||||
<el-scrollbar max-height="80vh">
|
<el-scrollbar max-height="80vh">
|
||||||
<div v-if="currentTasks.length === 0">
|
<div v-if="currentTasks.length === 0">
|
||||||
{{emptyText}}
|
{{emptyText}}
|
||||||
@@ -108,6 +111,12 @@
|
|||||||
<el-button @click="changeGalleryCollect(currentGallery.gid, currentGallery.isCollect)" :disabled="currentGallery.status !== '下载完成'"
|
<el-button @click="changeGalleryCollect(currentGallery.gid, currentGallery.isCollect)" :disabled="currentGallery.status !== '下载完成'"
|
||||||
size="large">{{currentGallery.isCollect ? '取消收藏' : '收藏'}}</el-button>
|
size="large">{{currentGallery.isCollect ? '取消收藏' : '收藏'}}</el-button>
|
||||||
<el-button @click="readOnlineGallery(currentGallery)" size="large">在线看</el-button>
|
<el-button @click="readOnlineGallery(currentGallery)" size="large">在线看</el-button>
|
||||||
|
<el-button v-if="currentGallery.status !== '下载完成'"
|
||||||
|
@click="retryGallery(currentGallery.gid)"
|
||||||
|
:loading="retryingGids.has(currentGallery.gid)"
|
||||||
|
size="large">
|
||||||
|
重试
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -125,6 +134,7 @@ let inputNode = ref(null)
|
|||||||
let isEditingPage = ref(false)
|
let isEditingPage = ref(false)
|
||||||
//是否查看详情
|
//是否查看详情
|
||||||
let isViewing = ref(false)
|
let isViewing = ref(false)
|
||||||
|
let retryingGids = ref(new Set())
|
||||||
|
|
||||||
let category = computed(() => {
|
let category = computed(() => {
|
||||||
return store.state.category
|
return store.state.category
|
||||||
@@ -203,8 +213,9 @@ function changePage(){
|
|||||||
}
|
}
|
||||||
function reverseEditMode(){
|
function reverseEditMode(){
|
||||||
isEditingPage.value = !isEditingPage.value
|
isEditingPage.value = !isEditingPage.value
|
||||||
if(isEditingPage)
|
if(isEditingPage.value){
|
||||||
inputNode.value.focus()
|
inputNode.value.focus()
|
||||||
|
}
|
||||||
targetPage.value = page.value
|
targetPage.value = page.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +257,16 @@ function deleteGallery(gid){
|
|||||||
isViewing.value = false
|
isViewing.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function retryGallery(gid){
|
||||||
|
if(retryingGids.value.has(gid))
|
||||||
|
return
|
||||||
|
retryingGids.value.add(gid)
|
||||||
|
try {
|
||||||
|
await store.dispatch("retryGallery", gid)
|
||||||
|
} finally {
|
||||||
|
retryingGids.value.delete(gid)
|
||||||
|
}
|
||||||
|
}
|
||||||
function readOnlineGallery(gallery){
|
function readOnlineGallery(gallery){
|
||||||
store.dispatch("readOnlineGallery", gallery)
|
store.dispatch("readOnlineGallery", gallery)
|
||||||
}
|
}
|
||||||
@@ -292,4 +313,4 @@ function isDark(){
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+138
-101
@@ -2,36 +2,63 @@ import vuex from "vuex"
|
|||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import {ElMessage} from "element-plus"
|
import {ElMessage} from "element-plus"
|
||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
|
import {createTaskSocket} from "../utils/taskSocket.js"
|
||||||
|
let taskSocket
|
||||||
|
let taskRefreshVersion = 0
|
||||||
const BaseUrl = "https://downloader.lionwebsite.xyz/"
|
const BaseUrl = "https://downloader.lionwebsite.xyz/"
|
||||||
const GalleryManageUrl = BaseUrl + "GalleryManage"
|
const GalleryManageUrl = BaseUrl + "GalleryManage"
|
||||||
|
|
||||||
|
// Axios 全局错误处理
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
response => response,
|
||||||
|
error => {
|
||||||
|
ElMessage({message: "网络请求失败: " + (error.message || ""), type: "error"})
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
reconnect(context) {
|
||||||
|
axios.post(GalleryManageUrl + "/reconnect", null, {
|
||||||
|
params: {
|
||||||
|
AuthCode: context.state.AuthCode
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
if (res.data.result === "success") {
|
||||||
|
ElMessage("重连成功")
|
||||||
|
} else {
|
||||||
|
ElMessage("重连失败")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
updateGalleryTasks(context){
|
updateGalleryTasks(context){
|
||||||
axios.get(GalleryManageUrl, {
|
const version = ++taskRefreshVersion
|
||||||
|
return axios.get(GalleryManageUrl, {
|
||||||
params:{
|
params:{
|
||||||
AuthCode: state.AuthCode,
|
AuthCode: context.state.AuthCode,
|
||||||
type: 'all'
|
type: 'all'
|
||||||
}
|
}
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if(res.data.result === "success")
|
if(res.data.result === "success" && version === taskRefreshVersion)
|
||||||
context.commit("_updateGalleryTasks", JSON.parse(res.data.data))
|
context.commit("_updateGalleryTasks", JSON.parse(res.data.data))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
postGalleryTask(context, data){
|
postGalleryTask(context, data){
|
||||||
axios.post(GalleryManageUrl, qs.stringify({
|
return axios.post(GalleryManageUrl + '?' + qs.stringify({
|
||||||
AuthCode: state.AuthCode,
|
AuthCode: context.state.AuthCode,
|
||||||
...data
|
...data
|
||||||
}, {indices:false})).then((res) => {
|
}, {indices:false})).then((res) => {
|
||||||
if(res.data.result === "success") {
|
if(res.data.result === "success") {
|
||||||
ElMessage("提交成功")
|
ElMessage("提交成功")
|
||||||
context.commit("_setChosenGallery", {gallery: false,
|
context.commit("_setChosenGallery", {gallery: false})
|
||||||
resolution: data.targetResolution})
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if(res.data.data)
|
if(res.data.data)
|
||||||
ElMessage(res.data.data)
|
ElMessage(res.data.data)
|
||||||
else
|
else
|
||||||
ElMessage("提交失败")
|
ElMessage("提交失败")
|
||||||
|
// A node timeout can still leave a persisted task; always reload its actual state.
|
||||||
|
return Promise.all([context.dispatch("updateGalleryTasks"), context.dispatch("loadWeekUsedAmount")])
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
queryGalleryTask(context, link){
|
queryGalleryTask(context, link){
|
||||||
@@ -39,11 +66,15 @@ const actions = {
|
|||||||
params:{
|
params:{
|
||||||
param: link,
|
param: link,
|
||||||
type: 'link',
|
type: 'link',
|
||||||
AuthCode: state.AuthCode
|
AuthCode: context.state.AuthCode
|
||||||
}
|
}
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if(res.data.result === 'success')
|
if(res.data.result === 'success'){
|
||||||
context.commit("_setChosenGallery", {gallery: JSON.parse(res.data.data)})
|
let gallery = JSON.parse(res.data.data)
|
||||||
|
if(gallery.status === "下载完成")
|
||||||
|
gallery.download = buildGalleryDownloadUrl(gallery.gid, context.state.AuthCode)
|
||||||
|
context.commit("_setChosenGallery", {gallery})
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ElMessage("查询失败")
|
ElMessage("查询失败")
|
||||||
})
|
})
|
||||||
@@ -60,54 +91,62 @@ const actions = {
|
|||||||
context.dispatch("updateGalleryTasks").then(() => confirmCurrentTask(context.state))
|
context.dispatch("updateGalleryTasks").then(() => confirmCurrentTask(context.state))
|
||||||
context.dispatch("initWebsocket").then()
|
context.dispatch("initWebsocket").then()
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
|
context.dispatch("disconnectWebsocket")
|
||||||
context.commit("_unAuthed")
|
context.commit("_unAuthed")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initWebsocket(context){
|
initWebsocket(context){
|
||||||
state.websocket = new WebSocket("wss://downloader.lionwebsite.xyz/ws/")
|
taskSocket?.stop()
|
||||||
state.websocket.onopen = () => {
|
taskSocket = createTaskSocket({
|
||||||
state.websocket.send("DownloaderWebsocket")
|
url: "wss://downloader.lionwebsite.xyz/ws/",
|
||||||
}
|
onState: status => context.commit("_setConnectionStatus", status),
|
||||||
|
onOpen: () => {
|
||||||
state.websocket.onmessage = (event) => {
|
// Catch up on every connection, including events missed during an outage.
|
||||||
let message = JSON.parse(event.data)
|
Promise.all([context.dispatch("updateGalleryTasks"), context.dispatch("loadWeekUsedAmount")]).catch(() => {})
|
||||||
|
},
|
||||||
switch (message.type){
|
onMessage: event => {
|
||||||
case "updateTasks":
|
let message
|
||||||
|
try { message = JSON.parse(event.data) }
|
||||||
|
catch { return }
|
||||||
|
if(message.type === "updateTasks" && Array.isArray(message.data))
|
||||||
context.commit("_updateGalleryTaskProceeding", message.data)
|
context.commit("_updateGalleryTaskProceeding", message.data)
|
||||||
break
|
else if(message.type === "fullUpdate")
|
||||||
case "fullUpdate":
|
context.dispatch("updateGalleryTasks").catch(() => {})
|
||||||
context.dispatch("updateGalleryTasks").then()
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
taskSocket.start()
|
||||||
|
},
|
||||||
|
disconnectWebsocket(context){
|
||||||
|
taskSocket?.stop()
|
||||||
|
taskSocket = undefined
|
||||||
|
context.commit("_setConnectionStatus", "disconnected")
|
||||||
},
|
},
|
||||||
loadWeekUsedAmount(context){
|
loadWeekUsedAmount(context){
|
||||||
axios.get(GalleryManageUrl + "/weekUsedAmount", {
|
return axios.get(GalleryManageUrl + "/weekUsedAmount", {
|
||||||
params: {
|
params: {
|
||||||
AuthCode: state.AuthCode
|
AuthCode: context.state.AuthCode
|
||||||
}
|
}
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if(res.data.result === "success"){
|
if(res.data.result === "success"){
|
||||||
context.state.weekUsed = JSON.parse(res.data.data)
|
context.state.weekUsed = JSON.parse(res.data.data)
|
||||||
ElMessage("查询用量成功")
|
|
||||||
}else
|
}else
|
||||||
ElMessage("查询用量失败")
|
ElMessage("查询用量失败")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
collectGallery(context, gid){
|
collectGallery(context, gid){
|
||||||
axios.post(GalleryManageUrl + "/collect?" +qs.stringify( {
|
axios.post(GalleryManageUrl + "/collect?" +qs.stringify( {
|
||||||
gid, AuthCode:state.AuthCode
|
gid, AuthCode:context.state.AuthCode
|
||||||
})).then((res) => {
|
})).then((res) => {
|
||||||
ElMessage(res.data.data)
|
ElMessage(res.data.data)
|
||||||
if(res.data.result === 'success')
|
if(res.data.result === 'success')
|
||||||
context.commit("_collectGallery", gid)
|
context.commit("_collectGallery", gid)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
disCollectGallery(context, gid){
|
disCollectGallery(context, gid){
|
||||||
axios.post(GalleryManageUrl + "/disCollect?" + qs.stringify(
|
axios.post(GalleryManageUrl + "/disCollect?" + qs.stringify({
|
||||||
{
|
gid, AuthCode:context.state.AuthCode
|
||||||
gid, AuthCode:state.AuthCode
|
|
||||||
})).then((res) => {
|
})).then((res) => {
|
||||||
ElMessage(res.data.data)
|
ElMessage(res.data.data)
|
||||||
if(res.data.result === 'success')
|
if(res.data.result === 'success')
|
||||||
@@ -115,9 +154,9 @@ const actions = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteGallery(context, gid){
|
deleteGallery(context, gid){
|
||||||
axios.delete(GalleryManageUrl, {
|
return axios.delete(GalleryManageUrl, {
|
||||||
params:{
|
params:{
|
||||||
AuthCode:state.AuthCode, gid
|
AuthCode:context.state.AuthCode, gid
|
||||||
}}).then((res) => {
|
}}).then((res) => {
|
||||||
if(res.data.result === "success"){
|
if(res.data.result === "success"){
|
||||||
ElMessage("删除成功")
|
ElMessage("删除成功")
|
||||||
@@ -127,11 +166,24 @@ const actions = {
|
|||||||
ElMessage(res.data.data)
|
ElMessage(res.data.data)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
retryGallery(context, gid){
|
||||||
|
return axios.post(GalleryManageUrl + "/retry?" + qs.stringify({
|
||||||
|
gid, AuthCode: context.state.AuthCode
|
||||||
|
})).then((res) => {
|
||||||
|
if(res.data.result === "success"){
|
||||||
|
ElMessage({message: "重试结果:" + res.data.data, type: "success"})
|
||||||
|
return context.dispatch("updateGalleryTasks")
|
||||||
|
}
|
||||||
|
ElMessage({message: res.data.data || "重试失败", type: "error"})
|
||||||
|
})
|
||||||
|
},
|
||||||
readOnlineGallery(context, gallery){
|
readOnlineGallery(context, gallery){
|
||||||
if(gallery.images !== undefined && gallery.images.length !== 0)
|
if(gallery.images !== undefined && gallery.images.length !== 0)
|
||||||
context.commit("_setReadingGallery", gallery)
|
context.commit("_setReadingGallery", gallery)
|
||||||
else
|
else
|
||||||
axios.post(GalleryManageUrl + "/cache?url=" + gallery.link).then((res) => {
|
return axios.post(GalleryManageUrl + "/cache", null, {
|
||||||
|
params: {url: gallery.link, AuthCode: context.state.AuthCode}
|
||||||
|
}).then((res) => {
|
||||||
if (res.data.result === 'success') {
|
if (res.data.result === 'success') {
|
||||||
gallery.pages = res.data.data.pages
|
gallery.pages = res.data.data.pages
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -142,26 +194,29 @@ const actions = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
alterAuthCode(context, AuthCode){
|
alterAuthCode(context, AuthCode){
|
||||||
axios.put(BaseUrl + "AuthCode?" + qs.stringify({'AuthCode': state.AuthCode, 'newAuthCode': AuthCode}))
|
axios.put(BaseUrl + "AuthCode?" + qs.stringify({'AuthCode': context.state.AuthCode, 'newAuthCode': AuthCode}))
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if(res.data.result === 'success') {
|
if(res.data.result === 'success') {
|
||||||
ElMessage("修改成功")
|
ElMessage("修改成功")
|
||||||
if(localStorage.getItem("auth") === state.AuthCode)
|
if(localStorage.getItem("auth") === context.state.AuthCode)
|
||||||
localStorage.setItem("auth", AuthCode)
|
localStorage.setItem("auth", AuthCode)
|
||||||
state.AuthCode = AuthCode
|
context.state.AuthCode = AuthCode
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ElMessage(res.data.data)
|
ElMessage(res.data.data)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resetUndone() {
|
resetUndone(context){
|
||||||
axios.post(GalleryManageUrl + "/reset?AuthCode=big+lion").then((res) => {
|
axios.post(GalleryManageUrl + "/reset?AuthCode=" + context.state.AuthCode).then((res) => {
|
||||||
ElMessage(res.data.data)
|
ElMessage(res.data.data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
_setConnectionStatus(state, status){
|
||||||
|
state.connectionStatus = status
|
||||||
|
},
|
||||||
_collectGallery(state, gid){
|
_collectGallery(state, gid){
|
||||||
let tasks = state.totalGalleryTask
|
let tasks = state.totalGalleryTask
|
||||||
for(let i=0; i< tasks.length; i++){
|
for(let i=0; i< tasks.length; i++){
|
||||||
@@ -183,17 +238,24 @@ const mutations = {
|
|||||||
},
|
},
|
||||||
_updateGalleryTasks(state, tasks){
|
_updateGalleryTasks(state, tasks){
|
||||||
state.totalGalleryTask.splice(0)
|
state.totalGalleryTask.splice(0)
|
||||||
state.collectGallery.slice(0)
|
state.collectGallery.splice(0)
|
||||||
state.downloadGallery.splice(0)
|
state.downloadGallery.splice(0)
|
||||||
|
|
||||||
tasks.forEach((task) => {
|
tasks.forEach((task) => {
|
||||||
//处理名字
|
//处理名字
|
||||||
task.shortName = getShortname(task.name)
|
task.shortName = getShortname(task.name)
|
||||||
task.thumb_link = GalleryManageUrl + "/ehThumbnail?path=" + task.thumb_link
|
if(task.thumb_link.trim() !== '')
|
||||||
|
task.thumb_link = buildGalleryManageUrl("/ehThumbnail", {
|
||||||
|
path: task.thumb_link,
|
||||||
|
AuthCode: state.AuthCode
|
||||||
|
})
|
||||||
|
else
|
||||||
|
delete task.thumb_link
|
||||||
|
|
||||||
//处理进度相关
|
//处理进度相关
|
||||||
switch (task.status) {
|
switch (task.status) {
|
||||||
case "已提交":
|
case "已提交":
|
||||||
|
case "等待压缩":
|
||||||
case "压缩中":
|
case "压缩中":
|
||||||
task.progress = task.status
|
task.progress = task.status
|
||||||
break;
|
break;
|
||||||
@@ -202,7 +264,7 @@ const mutations = {
|
|||||||
break;
|
break;
|
||||||
case "下载完成":
|
case "下载完成":
|
||||||
task.progress = task.status
|
task.progress = task.status
|
||||||
task.download = GalleryManageUrl + "/file/" + encodeURI(task.name) + ".zip?AuthCode=" + state.AuthCode + "&gid=" + task.gid
|
task.download = buildGalleryDownloadUrl(task.gid, state.AuthCode)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,23 +328,19 @@ const mutations = {
|
|||||||
},
|
},
|
||||||
_searchLocalByLink(state, link){
|
_searchLocalByLink(state, link){
|
||||||
let tasks = state.currentTasks
|
let tasks = state.currentTasks
|
||||||
let i = 0
|
let gid = link.split("/")[4]
|
||||||
let gid = null
|
|
||||||
let name = null
|
let name = null
|
||||||
|
|
||||||
if(state.showType === "gallery")
|
if(gid === undefined)
|
||||||
gid = link.split("/")[4]
|
for (let i = 0; i < tasks.length; i++) {
|
||||||
|
|
||||||
if(gid === null)
|
|
||||||
for (i = 0; i < tasks.length; i++) {
|
|
||||||
if (tasks[i].link === link) {
|
if (tasks[i].link === link) {
|
||||||
state.page = Math.floor(i / state.length) + 1
|
state.page = Math.floor(i / state.length) + 1
|
||||||
name = tasks[i].name
|
name = tasks[i].name
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else for (i = 0; i < tasks.length; i++)
|
else for (let i = 0; i < tasks.length; i++)
|
||||||
if (tasks[i].gid === gid) {
|
if (String(tasks[i].gid) === gid) {
|
||||||
state.page = Math.floor(i / state.length) + 1
|
state.page = Math.floor(i / state.length) + 1
|
||||||
name = state.sortType === "shortName" ? tasks[i].shortName: tasks[i].name
|
name = state.sortType === "shortName" ? tasks[i].shortName: tasks[i].name
|
||||||
break
|
break
|
||||||
@@ -319,17 +377,6 @@ const mutations = {
|
|||||||
deleteTask(tasks, 'gid', gid)
|
deleteTask(tasks, 'gid', gid)
|
||||||
},
|
},
|
||||||
_setChosenGallery(state,data){
|
_setChosenGallery(state,data){
|
||||||
if(data.gallery === false) {
|
|
||||||
state.chosenGallery.shortName = getShortname(state.chosenGallery.name)
|
|
||||||
state.chosenGallery.resolution = data.resolution
|
|
||||||
state.chosenGallery.fileSize = "等待下载完成后再查看"
|
|
||||||
state.chosenGallery.createTimeDisplay = "等待下载完成后再查看"
|
|
||||||
state.chosenGallery.progress = "已提交"
|
|
||||||
state.chosenGallery.downloader = state.userId
|
|
||||||
state.chosenGallery.thumb_link = GalleryManageUrl + "/ehThumbnail?path=" + state.chosenGallery.thumb_link
|
|
||||||
state.totalGalleryTask.push(state.chosenGallery)
|
|
||||||
state.downloadGallery.push(state.chosenGallery)
|
|
||||||
}
|
|
||||||
state.chosenGallery = data.gallery
|
state.chosenGallery = data.gallery
|
||||||
},
|
},
|
||||||
_setCategory(state, category){
|
_setCategory(state, category){
|
||||||
@@ -354,7 +401,10 @@ const mutations = {
|
|||||||
if(gallery.images === undefined) {
|
if(gallery.images === undefined) {
|
||||||
gallery.images = []
|
gallery.images = []
|
||||||
for(let i=1; i<=gallery.pages; i++)
|
for(let i=1; i<=gallery.pages; i++)
|
||||||
gallery.images.push(GalleryManageUrl + "/onlineImage/" + i + "?gid=" + gallery.gid);
|
gallery.images.push(buildGalleryManageUrl("/onlineImage/" + i, {
|
||||||
|
gid: gallery.gid,
|
||||||
|
AuthCode: state.AuthCode
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
state.readingGallery = gallery
|
state.readingGallery = gallery
|
||||||
state.isReading = true;
|
state.isReading = true;
|
||||||
@@ -371,7 +421,8 @@ const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
websocket: {}, //websocket
|
connectionStatus: "disconnected", // live task updates
|
||||||
|
websocket: null, //websocket
|
||||||
|
|
||||||
totalGalleryTask: [], //存放数据的数组
|
totalGalleryTask: [], //存放数据的数组
|
||||||
chosenGallery: false, //准备下载
|
chosenGallery: false, //准备下载
|
||||||
@@ -391,7 +442,7 @@ const state = {
|
|||||||
shortLength: 5, //简洁个数
|
shortLength: 5, //简洁个数
|
||||||
|
|
||||||
userId: -1, //用户id
|
userId: -1, //用户id
|
||||||
username: ",", //用户名
|
username: "", //用户名
|
||||||
isAuth: false, //是否授权
|
isAuth: false, //是否授权
|
||||||
AuthCode: '', //授权码
|
AuthCode: '', //授权码
|
||||||
loadComplete: false, //是否加载完成
|
loadComplete: false, //是否加载完成
|
||||||
@@ -401,8 +452,8 @@ const state = {
|
|||||||
searchTask: [], //搜索到的任务
|
searchTask: [], //搜索到的任务
|
||||||
isShowHistory: false, //是否打开面板
|
isShowHistory: false, //是否打开面板
|
||||||
galleryNameType: 'shortName', //名字类型 shortName name
|
galleryNameType: 'shortName', //名字类型 shortName name
|
||||||
category: 'myDownload', //分类
|
category: 'myDownload', //分类 myDownload myCollect total
|
||||||
sortType:'shortName', //排序类型
|
sortType:'shortName', //排序类型 shortName name createTime
|
||||||
currentTasks: [], //当前任务
|
currentTasks: [], //当前任务
|
||||||
weekUsed: {}, //每周用量
|
weekUsed: {}, //每周用量
|
||||||
}
|
}
|
||||||
@@ -444,37 +495,22 @@ export default new vuex.Store({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function getShortname(name){
|
function getShortname(name){
|
||||||
if (name.includes("[")) {
|
if(name === null)
|
||||||
let lastIndex = name.lastIndexOf("[")
|
return null
|
||||||
name = name.substring(0, lastIndex)
|
if (!name.includes("[")) return name
|
||||||
while (name.includes("[") && name.includes("]") && name.indexOf("[") < name.indexOf("]")) {
|
|
||||||
let start = name.indexOf("[")
|
// 截取最后一个 [ 之前的部分,然后移除所有 [...] 和 (...) 标签
|
||||||
let end = name.indexOf("]") + 1
|
name = name.substring(0, name.lastIndexOf("["))
|
||||||
let temp = name.substring(start, end)
|
return name.replace(/\s*\[[^\]]*\]\s*/g, '').replace(/\s*\([^\)]*\)\s*/g, '').trim()
|
||||||
temp = name.replace(temp, "")
|
}
|
||||||
if(temp.trim() === ""){
|
|
||||||
name = name.replace("[", "").replace("]", "")
|
function buildGalleryDownloadUrl(gid, AuthCode){
|
||||||
break
|
let params = new URLSearchParams({AuthCode, gid: gid.toString()})
|
||||||
}
|
return GalleryManageUrl + "/file/" + gid + ".zip?" + params.toString()
|
||||||
else
|
}
|
||||||
name = temp
|
|
||||||
}
|
function buildGalleryManageUrl(path, params){
|
||||||
while (name.includes("(") && name.includes(")") && name.indexOf("(") < name.indexOf(")")) {
|
return GalleryManageUrl + path + "?" + new URLSearchParams(params).toString()
|
||||||
let start = name.indexOf("(")
|
|
||||||
let end = name.indexOf(")") + 1
|
|
||||||
let temp = name.substring(start, end)
|
|
||||||
temp = name.replace(temp, "")
|
|
||||||
if(temp.trim() === ""){
|
|
||||||
name = name.replace("(", "").replace(")", "")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
else
|
|
||||||
name = temp
|
|
||||||
}
|
|
||||||
return name.trim()
|
|
||||||
} else {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmCurrentTask(state){
|
function confirmCurrentTask(state){
|
||||||
@@ -515,8 +551,9 @@ function deleteTask(tasks, key, value){
|
|||||||
for(let i=0; i<tasks[j].length; i++)
|
for(let i=0; i<tasks[j].length; i++)
|
||||||
if(tasks[j][i][key] === value){
|
if(tasks[j][i][key] === value){
|
||||||
tasks[j].splice(i, 1)
|
tasks[j].splice(i, 1)
|
||||||
|
i--
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = ['已提交', '下载中', '等待压缩', '压缩中', '下载完成']
|
let status = ['已提交', '下载中', '等待压缩', '压缩中', '下载完成']
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
function imageItem(url, index) {
|
||||||
|
return {url, src: url, index, attempt: 0, failed: false, settled: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startImages(urls) {
|
||||||
|
return urls.length ? [imageItem(urls[0], 0)] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function settleImage(items, urls, item, failed = false) {
|
||||||
|
// Ignore events from images removed by a page change or dialog close.
|
||||||
|
if (!items.includes(item)) return
|
||||||
|
item.failed = failed
|
||||||
|
if (item.settled) return
|
||||||
|
item.settled = true
|
||||||
|
if (items.length < urls.length)
|
||||||
|
items.push(imageItem(urls[items.length], items.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function retryImage(item) {
|
||||||
|
item.failed = false
|
||||||
|
item.attempt++
|
||||||
|
item.src = item.url + (item.url.includes('?') ? '&' : '?') + 'retry=' + item.attempt
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
export function createTaskSocket({url, onOpen, onMessage, onState,
|
||||||
|
createSocket = address => new WebSocket(address),
|
||||||
|
setTimer = setTimeout, clearTimer = clearTimeout}) {
|
||||||
|
let socket = null
|
||||||
|
let retryTimer
|
||||||
|
let connectTimer
|
||||||
|
let attempts = 0
|
||||||
|
let stopped = true
|
||||||
|
|
||||||
|
function detach() {
|
||||||
|
clearTimer(connectTimer)
|
||||||
|
connectTimer = undefined
|
||||||
|
if (socket) {
|
||||||
|
const previous = socket
|
||||||
|
socket = null
|
||||||
|
previous.onopen = previous.onclose = previous.onerror = previous.onmessage = null
|
||||||
|
try { previous.close() } catch { /* already closed */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedule() {
|
||||||
|
if (stopped) return
|
||||||
|
onState('reconnecting')
|
||||||
|
const delay = Math.min(1000 * 2 ** attempts, 30000)
|
||||||
|
attempts = Math.min(attempts + 1, 5)
|
||||||
|
clearTimer(retryTimer)
|
||||||
|
retryTimer = setTimer(connect, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
if (stopped) return
|
||||||
|
clearTimer(retryTimer)
|
||||||
|
retryTimer = undefined
|
||||||
|
onState(attempts ? 'reconnecting' : 'connecting')
|
||||||
|
let current
|
||||||
|
try { current = socket = createSocket(url) }
|
||||||
|
catch { schedule(); return }
|
||||||
|
const retry = () => {
|
||||||
|
if (stopped || socket !== current) return
|
||||||
|
detach()
|
||||||
|
schedule()
|
||||||
|
}
|
||||||
|
current.onopen = () => {
|
||||||
|
if (stopped || socket !== current) return
|
||||||
|
clearTimer(connectTimer)
|
||||||
|
connectTimer = undefined
|
||||||
|
attempts = 0
|
||||||
|
try { current.send('DownloaderWebsocket') }
|
||||||
|
catch { retry(); return }
|
||||||
|
onState('connected')
|
||||||
|
onOpen()
|
||||||
|
}
|
||||||
|
current.onmessage = event => {
|
||||||
|
if (!stopped && socket === current) onMessage(event)
|
||||||
|
}
|
||||||
|
current.onclose = retry
|
||||||
|
current.onerror = retry
|
||||||
|
connectTimer = setTimer(retry, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
start() {
|
||||||
|
if (!stopped) return
|
||||||
|
stopped = false
|
||||||
|
attempts = 0
|
||||||
|
connect()
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
stopped = true
|
||||||
|
clearTimer(retryTimer)
|
||||||
|
retryTimer = undefined
|
||||||
|
detach()
|
||||||
|
onState('disconnected')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export function validateLink(rawLink){
|
||||||
|
if(rawLink.trim() === "")
|
||||||
|
return false
|
||||||
|
if(rawLink.includes("hentai"))
|
||||||
|
return rawLink.includes("/g/")
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import {startImages, settleImage, retryImage} from '../src/utils/progressiveImages.js'
|
||||||
|
|
||||||
|
test('a failed image does not prevent later pages from loading', () => {
|
||||||
|
const urls = ['first', 'second', 'third']
|
||||||
|
const items = startImages(urls)
|
||||||
|
settleImage(items, urls, items[0], true)
|
||||||
|
assert.equal(items[0].failed, true)
|
||||||
|
assert.equal(items[1].url, 'second')
|
||||||
|
settleImage(items, urls, items[1])
|
||||||
|
assert.equal(items[2].url, 'third')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('retry does not append a duplicate page and bypasses a failed URL cache', () => {
|
||||||
|
const urls = ['first?gid=1', 'second', 'third']
|
||||||
|
const items = startImages(urls)
|
||||||
|
settleImage(items, urls, items[0], true)
|
||||||
|
retryImage(items[0])
|
||||||
|
assert.equal(items[0].src, 'first?gid=1&retry=1')
|
||||||
|
settleImage(items, urls, items[0])
|
||||||
|
assert.equal(items.length, 2)
|
||||||
|
assert.equal(items[0].failed, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('late image events cannot append content after changing pages', () => {
|
||||||
|
const old = startImages(['old'])[0]
|
||||||
|
const urls = ['new', 'next']
|
||||||
|
const items = startImages(urls)
|
||||||
|
settleImage(items, urls, old)
|
||||||
|
assert.equal(items.length, 1)
|
||||||
|
assert.deepEqual(startImages([]), [])
|
||||||
|
})
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import vm from 'node:vm'
|
||||||
|
|
||||||
|
function fixture() {
|
||||||
|
let code = fs.readFileSync(new URL('../src/store/index.js', import.meta.url), 'utf8')
|
||||||
|
.replace(/^import .*$/gm, '')
|
||||||
|
.replace('export default new vuex.Store(', 'globalThis.store = new vuex.Store(')
|
||||||
|
const messages = []
|
||||||
|
const showMessage = message => messages.push(message)
|
||||||
|
showMessage.error = message => messages.push(message)
|
||||||
|
const axios = {interceptors: {response: {use() {}}}}
|
||||||
|
const context = vm.createContext({axios, vuex: {Store: function (config) {Object.assign(this, config)}},
|
||||||
|
ElMessage: showMessage, URLSearchParams, console,
|
||||||
|
qs: {stringify: () => ''}})
|
||||||
|
vm.runInContext(code, context)
|
||||||
|
const store = context.store
|
||||||
|
const api = {state: store.state,
|
||||||
|
commit: (name, data) => store.mutations[name](store.state, data),
|
||||||
|
dispatch: (name, data) => Promise.resolve(store.actions[name](api, data))}
|
||||||
|
return {store, messages, axios, api}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('link search matches a numeric gid and jumps to the right page', () => {
|
||||||
|
const {store, messages} = fixture()
|
||||||
|
store.state.currentTasks = [{gid: 1}, {gid: 2}, {gid: 12345, name: 'sample', shortName: 'sample'}]
|
||||||
|
store.state.length = 2
|
||||||
|
store.mutations._searchLocalByLink(store.state, 'https://example.org/g/12345/key/')
|
||||||
|
assert.equal(store.state.page, 2)
|
||||||
|
assert.match(messages[0], /已跳转/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('missing gid still reports no match', () => {
|
||||||
|
const {store, messages} = fixture()
|
||||||
|
store.state.currentTasks = [{gid: 12345}]
|
||||||
|
store.mutations._searchLocalByLink(store.state, 'https://example.org/g/999/key/')
|
||||||
|
assert.equal(messages[0], '未找到此任务')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('submission reloads persisted status without duplicating a completed task', async () => {
|
||||||
|
const {store, axios, api} = fixture()
|
||||||
|
const gallery = {gid: 123, name: 'sample', status: '下载完成', thumb_link: '', createTime: 1, downloader: 7}
|
||||||
|
store.state.chosenGallery = {...gallery, status: '已提交'}
|
||||||
|
store.state.totalGalleryTask.push(gallery)
|
||||||
|
axios.post = async () => ({data: {result: 'success'}})
|
||||||
|
axios.get = async url => ({data: {result: 'success', data: JSON.stringify(url.endsWith('weekUsedAmount') ? {} : [gallery])}})
|
||||||
|
await store.actions.postGalleryTask(api, {targetResolution: 'original'})
|
||||||
|
assert.equal(store.state.totalGalleryTask.length, 1)
|
||||||
|
assert.equal(store.state.totalGalleryTask[0].status, '下载完成')
|
||||||
|
assert.equal(store.state.chosenGallery, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('gallery management subrequests carry the active auth code', async () => {
|
||||||
|
const {store, axios, api} = fixture()
|
||||||
|
store.state.AuthCode = 'test-auth'
|
||||||
|
let call
|
||||||
|
axios.post = async (...args) => {
|
||||||
|
call = args
|
||||||
|
return {data: {result: args[0].endsWith('/cache') ? 'failure' : 'success', data: 'expected test response'}}
|
||||||
|
}
|
||||||
|
await store.actions.reconnect(api)
|
||||||
|
assert.equal(call[0], 'https://downloader.lionwebsite.xyz/GalleryManage/reconnect')
|
||||||
|
assert.equal(call[1], null)
|
||||||
|
assert.equal(call[2].params.AuthCode, 'test-auth')
|
||||||
|
|
||||||
|
await store.actions.readOnlineGallery(api, {link: 'https://example.org/g/1/key/', images: []})
|
||||||
|
assert.equal(call[0], 'https://downloader.lionwebsite.xyz/GalleryManage/cache')
|
||||||
|
assert.equal(call[1], null)
|
||||||
|
assert.deepEqual({...call[2].params}, {
|
||||||
|
url: 'https://example.org/g/1/key/',
|
||||||
|
AuthCode: 'test-auth'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('thumbnail and online image URLs carry the active auth code', () => {
|
||||||
|
const {store} = fixture()
|
||||||
|
store.state.AuthCode = 'code with spaces'
|
||||||
|
store.mutations._updateGalleryTasks(store.state, [{
|
||||||
|
gid: 123,
|
||||||
|
name: 'sample',
|
||||||
|
status: '已提交',
|
||||||
|
thumb_link: 'https://example.org/thumb?a=1&b=2',
|
||||||
|
createTime: 1,
|
||||||
|
downloader: 7
|
||||||
|
}])
|
||||||
|
const thumbnail = new URL(store.state.totalGalleryTask[0].thumb_link)
|
||||||
|
assert.equal(thumbnail.searchParams.get('AuthCode'), 'code with spaces')
|
||||||
|
assert.equal(thumbnail.searchParams.get('path'), 'https://example.org/thumb?a=1&b=2')
|
||||||
|
|
||||||
|
store.mutations._setReadingGallery(store.state, {gid: 123, pages: 1})
|
||||||
|
const image = new URL(store.state.readingGallery.images[0])
|
||||||
|
assert.equal(image.searchParams.get('gid'), '123')
|
||||||
|
assert.equal(image.searchParams.get('AuthCode'), 'code with spaces')
|
||||||
|
})
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import {createTaskSocket} from '../src/utils/taskSocket.js'
|
||||||
|
|
||||||
|
function fixture() {
|
||||||
|
const sockets = [], states = [], messages = [], timers = new Map()
|
||||||
|
let id = 0, opens = 0
|
||||||
|
const manager = createTaskSocket({url: 'wss://example.invalid',
|
||||||
|
onOpen: () => opens++, onState: value => states.push(value), onMessage: event => messages.push(event.data),
|
||||||
|
setTimer: (fn, delay) => { timers.set(++id, {fn, delay}); return id },
|
||||||
|
clearTimer: key => timers.delete(key),
|
||||||
|
createSocket: () => {
|
||||||
|
const socket = {sent: [], send(value) { this.sent.push(value) }, close() { this.closed = true }}
|
||||||
|
sockets.push(socket)
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
})
|
||||||
|
function tick(delay) {
|
||||||
|
const entry = [...timers].find(([, timer]) => timer.delay === delay)
|
||||||
|
assert.ok(entry, `expected timer ${delay}`)
|
||||||
|
timers.delete(entry[0]); entry[1].fn()
|
||||||
|
}
|
||||||
|
return {manager, sockets, states, messages, timers, tick, opens: () => opens}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('reconnects and refreshes after each successful connection', () => {
|
||||||
|
const f = fixture(); f.manager.start()
|
||||||
|
f.sockets[0].onopen()
|
||||||
|
assert.deepEqual(f.sockets[0].sent, ['DownloaderWebsocket'])
|
||||||
|
f.sockets[0].onclose()
|
||||||
|
f.tick(1000)
|
||||||
|
f.sockets[1].onopen()
|
||||||
|
assert.equal(f.opens(), 2)
|
||||||
|
assert.equal(f.states.at(-1), 'connected')
|
||||||
|
assert.equal(f.timers.size, 0)
|
||||||
|
f.manager.stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('backs off and times out connection attempts', () => {
|
||||||
|
const f = fixture(); f.manager.start()
|
||||||
|
f.tick(10000)
|
||||||
|
assert.equal(f.sockets[0].closed, true)
|
||||||
|
f.tick(1000); f.sockets[1].onerror(); f.tick(2000)
|
||||||
|
f.sockets[2].onerror(); f.tick(4000)
|
||||||
|
assert.equal(f.sockets.length, 4)
|
||||||
|
f.manager.stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('stop cancels timers and ignores callbacks from replaced sockets', () => {
|
||||||
|
const f = fixture(); f.manager.start()
|
||||||
|
const oldOpen = f.sockets[0].onopen, oldMessage = f.sockets[0].onmessage
|
||||||
|
f.sockets[0].onerror()
|
||||||
|
f.manager.stop()
|
||||||
|
assert.equal(f.timers.size, 0)
|
||||||
|
f.manager.start()
|
||||||
|
oldOpen(); oldMessage({data: 'stale'})
|
||||||
|
assert.equal(f.opens(), 0)
|
||||||
|
assert.deepEqual(f.messages, [])
|
||||||
|
f.sockets[1].onopen()
|
||||||
|
f.sockets[1].onmessage({data: 'current'})
|
||||||
|
assert.deepEqual(f.messages, ['current'])
|
||||||
|
f.manager.stop()
|
||||||
|
})
|
||||||
@@ -3,8 +3,21 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
// Nginx 从 /asserts/mobile/ 提供移动端静态文件;部署文件名保持固定,
|
||||||
|
// 以便现有静态路由无需随每次构建改名。
|
||||||
|
base: '/asserts/mobile/',
|
||||||
|
assetsDir: '',
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
server:{
|
server:{
|
||||||
host: '0.0.0.0'
|
host: '0.0.0.0'
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'index.js',
|
||||||
|
chunkFileNames: '[name].js',
|
||||||
|
assetFileNames: '[name][extname]'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user