移植桌面端 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 测试,全部通过)。
560 lines
20 KiB
JavaScript
560 lines
20 KiB
JavaScript
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){
|
|
const version = ++taskRefreshVersion
|
|
return axios.get(GalleryManageUrl, {
|
|
params:{
|
|
AuthCode: context.state.AuthCode,
|
|
type: 'all'
|
|
}
|
|
}).then((res) => {
|
|
if(res.data.result === "success" && version === taskRefreshVersion)
|
|
context.commit("_updateGalleryTasks", JSON.parse(res.data.data))
|
|
})
|
|
},
|
|
postGalleryTask(context, data){
|
|
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})
|
|
}
|
|
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){
|
|
axios.get(GalleryManageUrl, {
|
|
params:{
|
|
param: link,
|
|
type: 'link',
|
|
AuthCode: context.state.AuthCode
|
|
}
|
|
}).then((res) => {
|
|
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("查询失败")
|
|
})
|
|
},
|
|
validate(context, AuthCode){
|
|
axios.post(BaseUrl + "validate?AuthCode=" + AuthCode).then((res)=>{
|
|
if(res.data.result === 'success'){
|
|
let data = JSON.parse(res.data.data);
|
|
if(!data.isAvailable)
|
|
ElMessage({duration:0, message:"节点挂了,不能下也不能看,找狮子处理", type: "error"})
|
|
context.commit("_authed", {AuthCode, ...data})
|
|
//初始化
|
|
context.dispatch("loadWeekUsedAmount").then()
|
|
context.dispatch("updateGalleryTasks").then(() => confirmCurrentTask(context.state))
|
|
context.dispatch("initWebsocket").then()
|
|
}
|
|
else {
|
|
context.dispatch("disconnectWebsocket")
|
|
context.commit("_unAuthed")
|
|
}
|
|
})
|
|
},
|
|
initWebsocket(context){
|
|
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)
|
|
else if(message.type === "fullUpdate")
|
|
context.dispatch("updateGalleryTasks").catch(() => {})
|
|
}
|
|
})
|
|
taskSocket.start()
|
|
},
|
|
disconnectWebsocket(context){
|
|
taskSocket?.stop()
|
|
taskSocket = undefined
|
|
context.commit("_setConnectionStatus", "disconnected")
|
|
},
|
|
loadWeekUsedAmount(context){
|
|
return axios.get(GalleryManageUrl + "/weekUsedAmount", {
|
|
params: {
|
|
AuthCode: context.state.AuthCode
|
|
}
|
|
}).then((res) => {
|
|
if(res.data.result === "success"){
|
|
context.state.weekUsed = JSON.parse(res.data.data)
|
|
}else
|
|
ElMessage("查询用量失败")
|
|
})
|
|
},
|
|
collectGallery(context, gid){
|
|
axios.post(GalleryManageUrl + "/collect?" +qs.stringify( {
|
|
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:context.state.AuthCode
|
|
})).then((res) => {
|
|
ElMessage(res.data.data)
|
|
if(res.data.result === 'success')
|
|
context.commit("_disCollectGallery", gid)
|
|
})
|
|
},
|
|
deleteGallery(context, gid){
|
|
return axios.delete(GalleryManageUrl, {
|
|
params:{
|
|
AuthCode:context.state.AuthCode, gid
|
|
}}).then((res) => {
|
|
if(res.data.result === "success"){
|
|
ElMessage("删除成功")
|
|
context.commit("_deleteGallery", gid)
|
|
}
|
|
else
|
|
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
|
|
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(() => {
|
|
context.commit("_setReadingGallery", gallery)
|
|
}, 100)
|
|
} else
|
|
ElMessage.error(res.data.data)
|
|
})
|
|
},
|
|
alterAuthCode(context, 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") === context.state.AuthCode)
|
|
localStorage.setItem("auth", AuthCode)
|
|
context.state.AuthCode = AuthCode
|
|
}
|
|
else
|
|
ElMessage(res.data.data)
|
|
})
|
|
},
|
|
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++){
|
|
if(!tasks[i].isCollect && tasks[i].gid === gid){
|
|
tasks[i].isCollect = true
|
|
state.collectGallery.push(tasks[i])
|
|
}
|
|
}
|
|
},
|
|
_disCollectGallery(state, gid){
|
|
let index
|
|
for(let i=0; i < state.collectGallery.length; i++)
|
|
if(state.collectGallery[i].gid === gid){
|
|
index = i
|
|
break
|
|
}
|
|
state.collectGallery[index].isCollect = false
|
|
state.collectGallery.splice(index, 1)
|
|
},
|
|
_updateGalleryTasks(state, tasks){
|
|
state.totalGalleryTask.splice(0)
|
|
state.collectGallery.splice(0)
|
|
state.downloadGallery.splice(0)
|
|
|
|
tasks.forEach((task) => {
|
|
//处理名字
|
|
task.shortName = getShortname(task.name)
|
|
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;
|
|
case "下载中":
|
|
task.progress = (Math.round((task.proceeding / task.pages) * 100)).toString() + "%"
|
|
break;
|
|
case "下载完成":
|
|
task.progress = task.status
|
|
task.download = buildGalleryDownloadUrl(task.gid, state.AuthCode)
|
|
break;
|
|
}
|
|
|
|
//处理时间戳
|
|
task.createTimeDisplay = new Date(task.createTime * 1000).toLocaleString("zh")
|
|
|
|
//处理是否收藏
|
|
if('isCollect' in task)
|
|
state.collectGallery.push(task)
|
|
else
|
|
task.isCollect = false
|
|
|
|
//处理是否下载
|
|
if(task.downloader === state.userId)
|
|
state.downloadGallery.push(task)
|
|
|
|
state.totalGalleryTask.push(task)
|
|
})
|
|
|
|
sortTasks(state)
|
|
if(state.isAuth && !state.loadComplete){
|
|
state.loadComplete = true
|
|
ElMessage("加载完成")
|
|
}
|
|
},
|
|
_updateGalleryTaskProceeding(state, tasks){
|
|
let galleries = Array.from(state.totalGalleryTask)
|
|
state.totalGalleryTask.splice(0)
|
|
galleries.forEach((gallery) => {
|
|
if(gallery.status !== '下载完成')
|
|
tasks.forEach((task) => {
|
|
if(task.gid === gallery.gid){
|
|
gallery.status = status[task.status]
|
|
gallery.proceeding = task.proceeding
|
|
if(gallery.status === '下载中')
|
|
gallery.progress = (Math.round((gallery.proceeding / gallery.pages) * 100)).toString() + "%"
|
|
else
|
|
gallery.progress = gallery.status
|
|
}
|
|
})
|
|
state.totalGalleryTask.push(gallery)
|
|
})
|
|
},
|
|
_changePage(state, targetPage){
|
|
state.page = targetPage
|
|
},
|
|
_authed(state, data){
|
|
state.AuthCode = data.AuthCode
|
|
state.userId = data.userId
|
|
state.username = data.username
|
|
state.isAuth = true
|
|
ElMessage("验证成功,加载中")
|
|
},
|
|
_unAuthed(state){
|
|
state.isAuth = false
|
|
state.AuthCode = ""
|
|
state.userId = -1
|
|
state.username = ""
|
|
ElMessage("授权码错误")
|
|
localStorage.removeItem("auth")
|
|
},
|
|
_searchLocalByLink(state, link){
|
|
let tasks = state.currentTasks
|
|
let gid = link.split("/")[4]
|
|
let name = null
|
|
|
|
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 (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
|
|
}
|
|
|
|
if(!name)
|
|
ElMessage("未找到此任务")
|
|
else
|
|
ElMessage("已跳转到该任务所在页数,任务名:" + name)
|
|
},
|
|
_searchLocalByKeyword(state, keyword){
|
|
state.searchTask.splice(0)
|
|
if(keyword.trim() !== '') {
|
|
state.page = 1
|
|
let tasks = state.currentTasks
|
|
tasks.forEach((task) => {
|
|
if (task.name.includes(keyword))
|
|
state.searchTask.push(task)
|
|
})
|
|
if (state.searchTask.length === 0) {
|
|
ElMessage("未找到该关键字的任务")
|
|
state.isSearch = false
|
|
}
|
|
else {
|
|
state.isSearch = true
|
|
}
|
|
}else {
|
|
state.isSearch = false
|
|
confirmCurrentTask(state)
|
|
}
|
|
},
|
|
_deleteGallery(state, gid){
|
|
let tasks = [state.totalGalleryTask, state.downloadGallery, state.collectGallery]
|
|
deleteTask(tasks, 'gid', gid)
|
|
},
|
|
_setChosenGallery(state,data){
|
|
state.chosenGallery = data.gallery
|
|
},
|
|
_setCategory(state, category){
|
|
state.category = category
|
|
confirmCurrentTask(state)
|
|
sortTasks(state)
|
|
},
|
|
_setSortType(state, sortType){
|
|
state.sortType = sortType
|
|
sortTasks(state)
|
|
},
|
|
_setGalleryNameType(state, galleryNameType){
|
|
state.galleryNameType = galleryNameType
|
|
},
|
|
_setShowNameType(state, type){
|
|
if(type === "shortName")
|
|
state.length = state.shortLength
|
|
else
|
|
state.length = state.defaultLength
|
|
},
|
|
_setReadingGallery(state, gallery){
|
|
if(gallery.images === undefined) {
|
|
gallery.images = []
|
|
for(let i=1; i<=gallery.pages; i++)
|
|
gallery.images.push(buildGalleryManageUrl("/onlineImage/" + i, {
|
|
gid: gallery.gid,
|
|
AuthCode: state.AuthCode
|
|
}));
|
|
}
|
|
state.readingGallery = gallery
|
|
state.isReading = true;
|
|
},
|
|
_closeReader(state){
|
|
state.isReading = false;
|
|
},
|
|
_openHistoryPanel(state){
|
|
state.isShowHistory = true
|
|
},
|
|
_closeHistoryPanel(state){
|
|
state.isShowHistory = false
|
|
}
|
|
}
|
|
|
|
const state = {
|
|
connectionStatus: "disconnected", // live task updates
|
|
websocket: null, //websocket
|
|
|
|
totalGalleryTask: [], //存放数据的数组
|
|
chosenGallery: false, //准备下载
|
|
collectGallery: [], //收藏
|
|
downloadGallery: [], //下载
|
|
isSearch: false, //用于决定是否显示搜索结果
|
|
|
|
readingGallery: {'name': '', 'images': []}, //在线看
|
|
isReading: false, //是否正在看
|
|
|
|
currentGid: "", //当前GID
|
|
lengthPerPage: 0, //在线预览每页图片数量
|
|
|
|
page: 1, //当前页数
|
|
length: 5, //每页能有多少个链接
|
|
defaultLength: 4, //默认个数
|
|
shortLength: 5, //简洁个数
|
|
|
|
userId: -1, //用户id
|
|
username: "", //用户名
|
|
isAuth: false, //是否授权
|
|
AuthCode: '', //授权码
|
|
loadComplete: false, //是否加载完成
|
|
galleryRefreshTimer: 0, //更新计时器id
|
|
|
|
isInclude: false, //是否搜索到任务
|
|
searchTask: [], //搜索到的任务
|
|
isShowHistory: false, //是否打开面板
|
|
galleryNameType: 'shortName', //名字类型 shortName name
|
|
category: 'myDownload', //分类 myDownload myCollect total
|
|
sortType:'shortName', //排序类型 shortName name createTime
|
|
currentTasks: [], //当前任务
|
|
weekUsed: {}, //每周用量
|
|
}
|
|
|
|
const getters = {
|
|
currentTasks(state){
|
|
if(state.isSearch)
|
|
return state.searchTask.slice((state.page - 1) * state.length, state.page * state.length)
|
|
else
|
|
return state.currentTasks.slice((state.page - 1) * state.length, state.page * state.length)
|
|
},
|
|
min(){
|
|
return 1
|
|
},
|
|
max(state){
|
|
let max = 0
|
|
let tasks
|
|
if(state.isSearch)
|
|
tasks = state.searchTask
|
|
else
|
|
tasks = state.currentTasks
|
|
|
|
if(!tasks)
|
|
return 1
|
|
|
|
max = Math.floor(tasks.length/state.length)
|
|
if(tasks.length % state.length !== 0 || max === 0)
|
|
max += 1
|
|
|
|
return max
|
|
}
|
|
}
|
|
|
|
export default new vuex.Store({
|
|
actions,
|
|
mutations,
|
|
state,
|
|
getters
|
|
})
|
|
|
|
function getShortname(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){
|
|
switch (state.category){
|
|
case 'myDownload':
|
|
state.currentTasks = state.downloadGallery
|
|
break
|
|
case 'myCollect':
|
|
state.currentTasks = state.collectGallery
|
|
break
|
|
case 'total':
|
|
state.currentTasks = state.totalGalleryTask
|
|
break
|
|
}
|
|
}
|
|
|
|
function sortTasks(state){
|
|
switch (state.sortType) {
|
|
case "name":
|
|
state.currentTasks = state.currentTasks.sort((before, after) => {
|
|
return before.name > after.name ? 1: -1
|
|
})
|
|
break
|
|
case "shortName":
|
|
state.currentTasks = state.currentTasks.sort((before, after) => {
|
|
return before.shortName > after.shortName ? 1: -1
|
|
})
|
|
break
|
|
case "createTime":
|
|
state.currentTasks = state.currentTasks.sort((before, after) => {
|
|
return before.createTime - after.createTime
|
|
})
|
|
}
|
|
}
|
|
|
|
function deleteTask(tasks, key, value){
|
|
for(let j=0; j < tasks.length; j++)
|
|
for(let i=0; i<tasks[j].length; i++)
|
|
if(tasks[j][i][key] === value){
|
|
tasks[j].splice(i, 1)
|
|
i--
|
|
break
|
|
}
|
|
}
|
|
|
|
let status = ['已提交', '下载中', '等待压缩', '压缩中', '下载完成']
|