同步桌面端更新:重连/重试、断线自动重连、图片容错与鉴权参数
移植桌面端 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:
+138
-101
@@ -2,36 +2,63 @@ import vuex from "vuex"
|
||||
import axios from "axios"
|
||||
import {ElMessage} from "element-plus"
|
||||
import qs from "qs"
|
||||
import {createTaskSocket} from "../utils/taskSocket.js"
|
||||
let taskSocket
|
||||
let taskRefreshVersion = 0
|
||||
const BaseUrl = "https://downloader.lionwebsite.xyz/"
|
||||
const GalleryManageUrl = BaseUrl + "GalleryManage"
|
||||
|
||||
// Axios 全局错误处理
|
||||
axios.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
ElMessage({message: "网络请求失败: " + (error.message || ""), type: "error"})
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
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){
|
||||
axios.get(GalleryManageUrl, {
|
||||
const version = ++taskRefreshVersion
|
||||
return axios.get(GalleryManageUrl, {
|
||||
params:{
|
||||
AuthCode: state.AuthCode,
|
||||
AuthCode: context.state.AuthCode,
|
||||
type: 'all'
|
||||
}
|
||||
}).then((res) => {
|
||||
if(res.data.result === "success")
|
||||
if(res.data.result === "success" && version === taskRefreshVersion)
|
||||
context.commit("_updateGalleryTasks", JSON.parse(res.data.data))
|
||||
})
|
||||
},
|
||||
postGalleryTask(context, data){
|
||||
axios.post(GalleryManageUrl, qs.stringify({
|
||||
AuthCode: state.AuthCode,
|
||||
return axios.post(GalleryManageUrl + '?' + qs.stringify({
|
||||
AuthCode: context.state.AuthCode,
|
||||
...data
|
||||
}, {indices:false})).then((res) => {
|
||||
if(res.data.result === "success") {
|
||||
ElMessage("提交成功")
|
||||
context.commit("_setChosenGallery", {gallery: false,
|
||||
resolution: data.targetResolution})
|
||||
context.commit("_setChosenGallery", {gallery: false})
|
||||
}
|
||||
else
|
||||
if(res.data.data)
|
||||
ElMessage(res.data.data)
|
||||
else
|
||||
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){
|
||||
@@ -39,11 +66,15 @@ const actions = {
|
||||
params:{
|
||||
param: link,
|
||||
type: 'link',
|
||||
AuthCode: state.AuthCode
|
||||
AuthCode: context.state.AuthCode
|
||||
}
|
||||
}).then((res) => {
|
||||
if(res.data.result === 'success')
|
||||
context.commit("_setChosenGallery", {gallery: JSON.parse(res.data.data)})
|
||||
if(res.data.result === 'success'){
|
||||
let gallery = JSON.parse(res.data.data)
|
||||
if(gallery.status === "下载完成")
|
||||
gallery.download = buildGalleryDownloadUrl(gallery.gid, context.state.AuthCode)
|
||||
context.commit("_setChosenGallery", {gallery})
|
||||
}
|
||||
else
|
||||
ElMessage("查询失败")
|
||||
})
|
||||
@@ -60,54 +91,62 @@ const actions = {
|
||||
context.dispatch("updateGalleryTasks").then(() => confirmCurrentTask(context.state))
|
||||
context.dispatch("initWebsocket").then()
|
||||
}
|
||||
else
|
||||
else {
|
||||
context.dispatch("disconnectWebsocket")
|
||||
context.commit("_unAuthed")
|
||||
}
|
||||
})
|
||||
},
|
||||
initWebsocket(context){
|
||||
state.websocket = new WebSocket("wss://downloader.lionwebsite.xyz/ws/")
|
||||
state.websocket.onopen = () => {
|
||||
state.websocket.send("DownloaderWebsocket")
|
||||
}
|
||||
|
||||
state.websocket.onmessage = (event) => {
|
||||
let message = JSON.parse(event.data)
|
||||
|
||||
switch (message.type){
|
||||
case "updateTasks":
|
||||
taskSocket?.stop()
|
||||
taskSocket = createTaskSocket({
|
||||
url: "wss://downloader.lionwebsite.xyz/ws/",
|
||||
onState: status => context.commit("_setConnectionStatus", status),
|
||||
onOpen: () => {
|
||||
// Catch up on every connection, including events missed during an outage.
|
||||
Promise.all([context.dispatch("updateGalleryTasks"), context.dispatch("loadWeekUsedAmount")]).catch(() => {})
|
||||
},
|
||||
onMessage: event => {
|
||||
let message
|
||||
try { message = JSON.parse(event.data) }
|
||||
catch { return }
|
||||
if(message.type === "updateTasks" && Array.isArray(message.data))
|
||||
context.commit("_updateGalleryTaskProceeding", message.data)
|
||||
break
|
||||
case "fullUpdate":
|
||||
context.dispatch("updateGalleryTasks").then()
|
||||
else if(message.type === "fullUpdate")
|
||||
context.dispatch("updateGalleryTasks").catch(() => {})
|
||||
}
|
||||
}
|
||||
})
|
||||
taskSocket.start()
|
||||
},
|
||||
disconnectWebsocket(context){
|
||||
taskSocket?.stop()
|
||||
taskSocket = undefined
|
||||
context.commit("_setConnectionStatus", "disconnected")
|
||||
},
|
||||
loadWeekUsedAmount(context){
|
||||
axios.get(GalleryManageUrl + "/weekUsedAmount", {
|
||||
return axios.get(GalleryManageUrl + "/weekUsedAmount", {
|
||||
params: {
|
||||
AuthCode: state.AuthCode
|
||||
AuthCode: context.state.AuthCode
|
||||
}
|
||||
}).then((res) => {
|
||||
if(res.data.result === "success"){
|
||||
context.state.weekUsed = JSON.parse(res.data.data)
|
||||
ElMessage("查询用量成功")
|
||||
}else
|
||||
ElMessage("查询用量失败")
|
||||
})
|
||||
},
|
||||
collectGallery(context, gid){
|
||||
axios.post(GalleryManageUrl + "/collect?" +qs.stringify( {
|
||||
gid, AuthCode:state.AuthCode
|
||||
})).then((res) => {
|
||||
gid, AuthCode:context.state.AuthCode
|
||||
})).then((res) => {
|
||||
ElMessage(res.data.data)
|
||||
if(res.data.result === 'success')
|
||||
context.commit("_collectGallery", gid)
|
||||
})
|
||||
},
|
||||
disCollectGallery(context, gid){
|
||||
axios.post(GalleryManageUrl + "/disCollect?" + qs.stringify(
|
||||
{
|
||||
gid, AuthCode:state.AuthCode
|
||||
axios.post(GalleryManageUrl + "/disCollect?" + qs.stringify({
|
||||
gid, AuthCode:context.state.AuthCode
|
||||
})).then((res) => {
|
||||
ElMessage(res.data.data)
|
||||
if(res.data.result === 'success')
|
||||
@@ -115,9 +154,9 @@ const actions = {
|
||||
})
|
||||
},
|
||||
deleteGallery(context, gid){
|
||||
axios.delete(GalleryManageUrl, {
|
||||
return axios.delete(GalleryManageUrl, {
|
||||
params:{
|
||||
AuthCode:state.AuthCode, gid
|
||||
AuthCode:context.state.AuthCode, gid
|
||||
}}).then((res) => {
|
||||
if(res.data.result === "success"){
|
||||
ElMessage("删除成功")
|
||||
@@ -127,11 +166,24 @@ const actions = {
|
||||
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){
|
||||
if(gallery.images !== undefined && gallery.images.length !== 0)
|
||||
context.commit("_setReadingGallery", gallery)
|
||||
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') {
|
||||
gallery.pages = res.data.data.pages
|
||||
setTimeout(() => {
|
||||
@@ -142,26 +194,29 @@ const actions = {
|
||||
})
|
||||
},
|
||||
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) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage("修改成功")
|
||||
if(localStorage.getItem("auth") === state.AuthCode)
|
||||
if(localStorage.getItem("auth") === context.state.AuthCode)
|
||||
localStorage.setItem("auth", AuthCode)
|
||||
state.AuthCode = AuthCode
|
||||
context.state.AuthCode = AuthCode
|
||||
}
|
||||
else
|
||||
ElMessage(res.data.data)
|
||||
})
|
||||
},
|
||||
resetUndone() {
|
||||
axios.post(GalleryManageUrl + "/reset?AuthCode=big+lion").then((res) => {
|
||||
resetUndone(context){
|
||||
axios.post(GalleryManageUrl + "/reset?AuthCode=" + context.state.AuthCode).then((res) => {
|
||||
ElMessage(res.data.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
_setConnectionStatus(state, status){
|
||||
state.connectionStatus = status
|
||||
},
|
||||
_collectGallery(state, gid){
|
||||
let tasks = state.totalGalleryTask
|
||||
for(let i=0; i< tasks.length; i++){
|
||||
@@ -183,17 +238,24 @@ const mutations = {
|
||||
},
|
||||
_updateGalleryTasks(state, tasks){
|
||||
state.totalGalleryTask.splice(0)
|
||||
state.collectGallery.slice(0)
|
||||
state.collectGallery.splice(0)
|
||||
state.downloadGallery.splice(0)
|
||||
|
||||
tasks.forEach((task) => {
|
||||
//处理名字
|
||||
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) {
|
||||
case "已提交":
|
||||
case "等待压缩":
|
||||
case "压缩中":
|
||||
task.progress = task.status
|
||||
break;
|
||||
@@ -202,7 +264,7 @@ const mutations = {
|
||||
break;
|
||||
case "下载完成":
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -266,23 +328,19 @@ const mutations = {
|
||||
},
|
||||
_searchLocalByLink(state, link){
|
||||
let tasks = state.currentTasks
|
||||
let i = 0
|
||||
let gid = null
|
||||
let gid = link.split("/")[4]
|
||||
let name = null
|
||||
|
||||
if(state.showType === "gallery")
|
||||
gid = link.split("/")[4]
|
||||
|
||||
if(gid === null)
|
||||
for (i = 0; i < tasks.length; i++) {
|
||||
if(gid === undefined)
|
||||
for (let i = 0; i < tasks.length; i++) {
|
||||
if (tasks[i].link === link) {
|
||||
state.page = Math.floor(i / state.length) + 1
|
||||
name = tasks[i].name
|
||||
break
|
||||
}
|
||||
}
|
||||
else for (i = 0; i < tasks.length; i++)
|
||||
if (tasks[i].gid === gid) {
|
||||
else for (let i = 0; i < tasks.length; i++)
|
||||
if (String(tasks[i].gid) === gid) {
|
||||
state.page = Math.floor(i / state.length) + 1
|
||||
name = state.sortType === "shortName" ? tasks[i].shortName: tasks[i].name
|
||||
break
|
||||
@@ -319,17 +377,6 @@ const mutations = {
|
||||
deleteTask(tasks, 'gid', gid)
|
||||
},
|
||||
_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
|
||||
},
|
||||
_setCategory(state, category){
|
||||
@@ -354,7 +401,10 @@ const mutations = {
|
||||
if(gallery.images === undefined) {
|
||||
gallery.images = []
|
||||
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.isReading = true;
|
||||
@@ -371,7 +421,8 @@ const mutations = {
|
||||
}
|
||||
|
||||
const state = {
|
||||
websocket: {}, //websocket
|
||||
connectionStatus: "disconnected", // live task updates
|
||||
websocket: null, //websocket
|
||||
|
||||
totalGalleryTask: [], //存放数据的数组
|
||||
chosenGallery: false, //准备下载
|
||||
@@ -391,7 +442,7 @@ const state = {
|
||||
shortLength: 5, //简洁个数
|
||||
|
||||
userId: -1, //用户id
|
||||
username: ",", //用户名
|
||||
username: "", //用户名
|
||||
isAuth: false, //是否授权
|
||||
AuthCode: '', //授权码
|
||||
loadComplete: false, //是否加载完成
|
||||
@@ -401,8 +452,8 @@ const state = {
|
||||
searchTask: [], //搜索到的任务
|
||||
isShowHistory: false, //是否打开面板
|
||||
galleryNameType: 'shortName', //名字类型 shortName name
|
||||
category: 'myDownload', //分类
|
||||
sortType:'shortName', //排序类型
|
||||
category: 'myDownload', //分类 myDownload myCollect total
|
||||
sortType:'shortName', //排序类型 shortName name createTime
|
||||
currentTasks: [], //当前任务
|
||||
weekUsed: {}, //每周用量
|
||||
}
|
||||
@@ -444,37 +495,22 @@ export default new vuex.Store({
|
||||
})
|
||||
|
||||
function getShortname(name){
|
||||
if (name.includes("[")) {
|
||||
let lastIndex = name.lastIndexOf("[")
|
||||
name = name.substring(0, lastIndex)
|
||||
while (name.includes("[") && name.includes("]") && name.indexOf("[") < name.indexOf("]")) {
|
||||
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
|
||||
}
|
||||
while (name.includes("(") && name.includes(")") && name.indexOf("(") < name.indexOf(")")) {
|
||||
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
|
||||
}
|
||||
if(name === null)
|
||||
return null
|
||||
if (!name.includes("[")) return name
|
||||
|
||||
// 截取最后一个 [ 之前的部分,然后移除所有 [...] 和 (...) 标签
|
||||
name = name.substring(0, name.lastIndexOf("["))
|
||||
return name.replace(/\s*\[[^\]]*\]\s*/g, '').replace(/\s*\([^\)]*\)\s*/g, '').trim()
|
||||
}
|
||||
|
||||
function buildGalleryDownloadUrl(gid, AuthCode){
|
||||
let params = new URLSearchParams({AuthCode, gid: gid.toString()})
|
||||
return GalleryManageUrl + "/file/" + gid + ".zip?" + params.toString()
|
||||
}
|
||||
|
||||
function buildGalleryManageUrl(path, params){
|
||||
return GalleryManageUrl + path + "?" + new URLSearchParams(params).toString()
|
||||
}
|
||||
|
||||
function confirmCurrentTask(state){
|
||||
@@ -515,8 +551,9 @@ function deleteTask(tasks, key, value){
|
||||
for(let i=0; i<tasks[j].length; i++)
|
||||
if(tasks[j][i][key] === value){
|
||||
tasks[j].splice(i, 1)
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let status = ['已提交', '下载中', '等待压缩', '压缩中', '下载完成']
|
||||
let status = ['已提交', '下载中', '等待压缩', '压缩中', '下载完成']
|
||||
|
||||
Reference in New Issue
Block a user