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 }, _setActiveTab(state, tab){ state.activeTab = tab }, _setDetailGallery(state, gallery){ state.detailGallery = gallery }, // The mobile list grows in place instead of paging through a footer control. _showMoreTasks(state){ state.visiblePages += 1 }, _authed(state, data){ state.AuthCode = data.AuthCode state.userId = data.userId state.username = data.username state.isAdmin = Boolean(data.isAdmin) state.downloaderFilter = null 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 matched = null 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 matched = tasks[i] 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 matched = tasks[i] name = state.sortType === "shortName" ? tasks[i].shortName: tasks[i].name break } if(!name) ElMessage("未找到此任务") else { state.searchTask.splice(0) if(matched) state.searchTask.push(matched) state.isSearch = true state.visiblePages = 1 ElMessage("已跳转到该任务所在页数,任务名:" + name) } }, _searchLocalByKeyword(state, keyword){ state.searchTask.splice(0) if(keyword.trim() !== '') { state.page = 1 state.visiblePages = 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 state.visiblePages = 1 // 换分类后旧的下载人筛选可能在新分类里根本不存在,先清掉。 state.downloaderFilter = null confirmCurrentTask(state) sortTasks(state) }, _setSortType(state, sortType){ state.sortType = sortType state.visiblePages = 1 sortTasks(state) }, _setGalleryNameType(state, galleryNameType){ state.galleryNameType = galleryNameType }, _setDownloaderFilter(state, downloader){ state.downloaderFilter = downloader === null || downloader === undefined ? null : Number(downloader) state.visiblePages = 1 }, _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, //是否打开面板 activeTab: 'tasks', //移动端底部标签 tasks search settings detailGallery: null, //详情抽屉里正在看的任务 visiblePages: 1, //列表已展开的页数,用于“继续加载” galleryNameType: 'shortName', //名字类型 shortName name category: 'myDownload', //分类 myDownload myCollect total sortType:'shortName', //排序类型 shortName name createTime currentTasks: [], //当前任务 weekUsed: {}, //每周用量 isAdmin: false, //管理员(后端 validate 下发) downloaderFilter: null, //管理员按下载人筛选,null 表示全部 } const getters = { currentTasks(state){ const tasks = activeTaskList(state) return tasks.slice((state.page - 1) * state.length, state.page * state.length) }, min(){ return 1 }, visibleTasks(state){ return activeTaskList(state).slice(0, state.visiblePages * state.length) }, taskTotal(state){ return activeTaskList(state).length }, taskRemaining(state, getters){ return Math.max(0, getters.taskTotal - state.visiblePages * state.length) }, searchResults(state){ return (state.searchTask || []).slice(0, state.visiblePages * state.length) }, max(state){ let max = 0 const tasks = activeTaskList(state) 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 activeTaskList(state){ if(state.isSearch) return state.searchTask if(state.isAdmin && state.downloaderFilter !== null) return state.currentTasks.filter(task => task.downloader === state.downloaderFilter) return state.currentTasks } function getShortname(name){ if(name === null) return null if (!name.includes("[")) return name // 截取最后一个 [ 之前的部分,然后移除所有 [...] 和 (...) 标签 const trimmed = name.substring(0, name.lastIndexOf("[")) .replace(/\s*\[[^\]]*\]\s*/g, '') .replace(/\s*\([^\)]*\)\s*/g, '') .trim() // 名字以 [...] 开头时截取结果为空,退回原名避免整列空白 return trimmed === '' ? name.trim() : trimmed } 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