桌面下载端改用共享核心包,并修两处已知缺陷
- store 改由 createGalleryStore 生成:totalGalleryTask 成为唯一任务源, 收藏/下载/分类/搜索结果全部由 getter 派生,不再维护三份互相引用的数组 - 排序不再原地修改分类数组;本地搜索记住关键词,任务刷新后结果仍在 - 本地搜索结果补齐翻页入口:此前匹配 25 项却只显示 10 项,其余无法看到 - 取消收藏不再因本地找不到 gid 抛 TypeError - 删除重复的 taskSocket/progressiveImages/validate/thumbnail,改从共享包导入 - 远端搜索改用共享 fetchGalleries,关键词交给 axios 序列化,翻页按游标转发 - main.js 按需注册 Element Plus,入口 JS 由约 1.14MB 降到约 494KB - 依赖新增 file:../lionwebsite-frontend-shared 测试:14 项通过;构建成功,入口仍为 /asserts/index/index.js|css。
This commit is contained in:
@@ -3,7 +3,7 @@ import {computed, ref, watch} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import store from "../store/index.js";
|
||||
import AppIcon from "./AppIcon.vue";
|
||||
import {ehThumbnailUrl, pickDefaultResolution} from "../utils/thumbnail.js";
|
||||
import {ehThumbnailUrl, pickDefaultResolution} from "lionwebsite-frontend-shared";
|
||||
|
||||
const targetResolution = ref("");
|
||||
const isRetrying = ref(false);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import {computed, nextTick, onBeforeUnmount, ref, watch} from "vue";
|
||||
import {startImages, settleImage, retryImage} from "../utils/progressiveImages.js";
|
||||
import {startImages, settleImage, retryImage} from "lionwebsite-frontend-shared";
|
||||
import store from "../store/index.js";
|
||||
import AppIcon from "./AppIcon.vue";
|
||||
import {ehThumbnailUrl} from "../utils/thumbnail.js";
|
||||
import {ehThumbnailUrl} from "lionwebsite-frontend-shared";
|
||||
|
||||
const viewer = ref(null);
|
||||
const readingGallery = computed(() => store.state.readingGallery);
|
||||
|
||||
@@ -5,8 +5,7 @@ import {ElMessage} from "element-plus";
|
||||
import store from "../store/index.js";
|
||||
import TaskRow from "./TaskRow.vue";
|
||||
import AppIcon from "./AppIcon.vue";
|
||||
import {validateLink} from "../utils/validate.js";
|
||||
import {ehThumbnailUrl} from "../utils/thumbnail.js";
|
||||
import {ehThumbnailUrl, fetchGalleries, validateLink} from "lionwebsite-frontend-shared";
|
||||
|
||||
const mode = ref("ehentai");
|
||||
const localMode = ref("keyword");
|
||||
@@ -20,8 +19,16 @@ const remoteLoading = ref(false);
|
||||
const retryingGids = ref(new Set());
|
||||
|
||||
const localResults = computed(() => store.getters.currentTasks || []);
|
||||
const localTotal = computed(() => store.state.searchTask.length);
|
||||
const localTotal = computed(() => (store.getters.filteredTasks || []).length);
|
||||
const isSearching = computed(() => store.state.isSearch);
|
||||
const localPage = computed(() => store.state.page);
|
||||
const localMin = computed(() => store.getters.min);
|
||||
const localMax = computed(() => store.getters.max);
|
||||
|
||||
function gotoLocalPage(value) {
|
||||
if (value < localMin.value || value > localMax.value) return;
|
||||
store.commit("_changePage", value);
|
||||
}
|
||||
|
||||
function runLocalSearch() {
|
||||
if (localKeyword.value.trim() === "") {
|
||||
@@ -37,32 +44,20 @@ function clearLocalSearch() {
|
||||
localKeyword.value = "";
|
||||
}
|
||||
|
||||
function queryGalleries(link) {
|
||||
let keyword;
|
||||
if (link != null) {
|
||||
keyword = new URL(link).search.replace("?f_search=", "");
|
||||
} else {
|
||||
keyword = remoteKeyword.value;
|
||||
if (keyword.trim() === "") {
|
||||
ElMessage("请输入搜索关键词");
|
||||
return;
|
||||
}
|
||||
// direction 为 "next"/"prev" 时使用后端返回的翻页游标;href 为空表示新关键词。
|
||||
function queryGalleries(href, direction = null) {
|
||||
if (href == null && remoteKeyword.value.trim() === "") {
|
||||
ElMessage("请输入搜索关键词");
|
||||
return;
|
||||
}
|
||||
remoteLoading.value = true;
|
||||
axios.get("https://downloader.lionwebsite.xyz/query?keyword=" + keyword.replace(" ", "+"))
|
||||
.then(res => {
|
||||
if (res.data.result === "success") {
|
||||
remotePage.value = {
|
||||
first: res.data.first,
|
||||
previous: res.data.previous,
|
||||
next: res.data.next,
|
||||
last: res.data.last,
|
||||
};
|
||||
remoteGalleries.value.splice(0);
|
||||
JSON.parse(res.data.data).forEach(g => remoteGalleries.value.push(g));
|
||||
} else {
|
||||
ElMessage({message: res.data.data, type: "error"});
|
||||
}
|
||||
fetchGalleries(axios, {keyword: remoteKeyword.value, direction, href: href || ""})
|
||||
.then(({galleries, pagination}) => {
|
||||
remotePage.value = pagination;
|
||||
remoteGalleries.value.splice(0, remoteGalleries.value.length, ...galleries);
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage({message: error.payload || error.message || "搜索失败", type: "error"});
|
||||
})
|
||||
.finally(() => {
|
||||
remoteLoading.value = false;
|
||||
@@ -159,8 +154,8 @@ async function retryGallery(gallery) {
|
||||
|
||||
<div class="pager-row" v-if="remoteGalleries.length">
|
||||
<el-button size="small" :disabled="remotePage.first === undefined" @click="queryGalleries(remotePage.first)">首页</el-button>
|
||||
<el-button size="small" :disabled="remotePage.previous === undefined" @click="queryGalleries(remotePage.previous)">上一页</el-button>
|
||||
<el-button size="small" :disabled="remotePage.next === undefined" @click="queryGalleries(remotePage.next)">下一页</el-button>
|
||||
<el-button size="small" :disabled="remotePage.previous === undefined" @click="queryGalleries(remotePage.previous, 'prev')">上一页</el-button>
|
||||
<el-button size="small" :disabled="remotePage.next === undefined" @click="queryGalleries(remotePage.next, 'next')">下一页</el-button>
|
||||
<el-button size="small" :disabled="remotePage.last === undefined" @click="queryGalleries(remotePage.last)">尾页</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -190,6 +185,12 @@ async function retryGallery(gallery) {
|
||||
@open="store.commit('_selectGallery', $event)"
|
||||
@download="downloadTask"
|
||||
@retry="retryGallery" />
|
||||
<!-- 命中项可能多于每页条数,必须给出翻页入口,否则后面的结果看不到。 -->
|
||||
<div class="pager-row" v-if="localMax > 1">
|
||||
<el-button size="small" :disabled="localPage <= localMin" @click="gotoLocalPage(localPage - 1)">上一页</el-button>
|
||||
<span class="hint">第 {{ localPage }} / {{ localMax }} 页</span>
|
||||
<el-button size="small" :disabled="localPage >= localMax" @click="gotoLocalPage(localPage + 1)">下一页</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="empty-state">输入关键字或链接在已有任务里查找</p>
|
||||
</template>
|
||||
|
||||
@@ -42,7 +42,7 @@ const downloaderFilter = computed({
|
||||
// downloader that would filter the visible list down to nothing.
|
||||
const downloaderOptions = computed(() => {
|
||||
const seen = new Map();
|
||||
;(store.state.currentTasks || []).forEach(task => {
|
||||
;(store.getters.categoryGallery || []).forEach(task => {
|
||||
if (task.downloaderName && !seen.has(task.downloader))
|
||||
seen.set(task.downloader, task.downloaderName);
|
||||
});
|
||||
|
||||
+11
-2
@@ -1,9 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import {
|
||||
ElButton, ElCheckbox, ElDialog, ElForm, ElFormItem, ElImage, ElInput,
|
||||
ElInputNumber, ElOption, ElProgress, ElScrollbar, ElSelect, ElSwitch,
|
||||
} from "element-plus"
|
||||
import 'element-plus/dist/index.css'
|
||||
import element from "element-plus"
|
||||
import "./reset.css"
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import "./styles/theme.css"
|
||||
|
||||
createApp(App).use(element).mount('#app')
|
||||
// 只注册本仓库用到的组件,避免把整个 Element Plus 打进入口包。
|
||||
// ElMessage 是函数式 API,按需在组件里 import 即可。
|
||||
const app = createApp(App)
|
||||
for (const component of [ElButton, ElCheckbox, ElDialog, ElForm, ElFormItem, ElImage,
|
||||
ElInput, ElInputNumber, ElOption, ElProgress, ElScrollbar, ElSelect, ElSwitch])
|
||||
app.component(component.name, component)
|
||||
app.mount('#app')
|
||||
|
||||
+8
-576
@@ -2,11 +2,7 @@ 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"
|
||||
import {createGalleryStore} from "lionwebsite-frontend-shared"
|
||||
|
||||
// Axios 全局错误处理
|
||||
axios.interceptors.response.use(
|
||||
@@ -17,574 +13,10 @@ axios.interceptors.response.use(
|
||||
}
|
||||
)
|
||||
|
||||
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){
|
||||
// Returned so callers can clear a selection once the task is really gone.
|
||||
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.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 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
|
||||
// 换分类后旧的下载人筛选可能在新分类里根本不存在,先清掉。
|
||||
state.downloaderFilter = null
|
||||
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;
|
||||
},
|
||||
_setActiveView(state, view){
|
||||
state.activeView = view
|
||||
},
|
||||
_selectGallery(state, gallery){
|
||||
state.selectedGallery = gallery || null
|
||||
},
|
||||
_setDownloaderFilter(state, downloader){
|
||||
state.downloaderFilter = downloader === null || downloader === undefined
|
||||
? null
|
||||
: Number(downloader)
|
||||
// 筛完可能不足一页,回到第一页避免停在空页上。
|
||||
state.page = 1
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
connectionStatus: "disconnected", // live task updates
|
||||
|
||||
totalGalleryTask: [], //存放数据的数组
|
||||
chosenGallery: false, //准备下载
|
||||
collectGallery: [], //收藏
|
||||
downloadGallery: [], //下载
|
||||
isSearch: false, //用于决定是否显示搜索结果
|
||||
|
||||
readingGallery: {'name': '', 'images': []}, //在线看
|
||||
isReading: false, //是否正在看
|
||||
|
||||
currentGid: "", //当前GID
|
||||
lengthPerPage: 0, //在线预览每页图片数量
|
||||
|
||||
page: 1, //当前页数
|
||||
length: 10, //每页能有多少个链接
|
||||
defaultLength: 7, //默认个数
|
||||
shortLength: 10, //简洁个数
|
||||
|
||||
userId: -1, //用户id
|
||||
username: "", //用户名
|
||||
isAuth: false, //是否授权
|
||||
AuthCode: '', //授权码
|
||||
loadComplete: false, //是否加载完成
|
||||
galleryRefreshTimer: 0, //更新计时器id
|
||||
|
||||
isInclude: false, //是否搜索到任务
|
||||
searchTask: [], //搜索到的任务
|
||||
category: 'myDownload', //分类 myDownload myCollect total
|
||||
sortType:'shortName', //排序类型 shortName name createTime
|
||||
galleryNameType: 'shortName', //名字类型 shortName name
|
||||
currentTasks: [], //当前任务
|
||||
weekUsed: {}, //每周用量
|
||||
|
||||
activeView: 'tasks', //当前视图 tasks search settings
|
||||
selectedGallery: null, //右侧详情面板选中的任务
|
||||
isAdmin: false, //管理员(后端 validate 下发)
|
||||
downloaderFilter: null, //管理员按下载人筛选,null 表示全部
|
||||
}
|
||||
|
||||
const getters = {
|
||||
/** 当前筛选条件命中的完整任务列表(未分页),用于计数。 */
|
||||
filteredTasks(state){
|
||||
return activeTaskList(state)
|
||||
},
|
||||
currentTasks(state){
|
||||
const tasks = activeTaskList(state)
|
||||
return tasks.slice((state.page - 1) * state.length, state.page * state.length)
|
||||
},
|
||||
min(){
|
||||
return 1
|
||||
},
|
||||
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){
|
||||
console.log(name)
|
||||
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<tasks[j].length; i++)
|
||||
if(tasks[j][i][key] === value){
|
||||
tasks[j].splice(i, 1)
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let status = ['已提交', '下载中', '等待压缩', '压缩中', '下载完成']
|
||||
// 桌面端与移动端共用同一份任务业务核心,只在分页交互上不同。
|
||||
export default new vuex.Store(createGalleryStore({
|
||||
variant: "desktop",
|
||||
axios,
|
||||
qs,
|
||||
Message: ElMessage,
|
||||
}))
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
const BaseUrl = "https://downloader.lionwebsite.xyz/";
|
||||
|
||||
// Remote galleries carry a raw thumbnail path; it only resolves through the
|
||||
// backend proxy once the active auth code is attached.
|
||||
export function ehThumbnailUrl(path, AuthCode) {
|
||||
if (!path) return "";
|
||||
return BaseUrl + "GalleryManage/ehThumbnail?" + new URLSearchParams({path, AuthCode}).toString();
|
||||
}
|
||||
|
||||
const UNIT_SCALE = {b: 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4};
|
||||
|
||||
function sizeInBytes(text) {
|
||||
const match = String(text).match(/([\d.]+)\s*([kmgt]?)(?:i?b)/i);
|
||||
if (!match) return 0;
|
||||
const scale = UNIT_SCALE[match[2].toLowerCase()] ?? 1;
|
||||
return Number(match[1]) * scale;
|
||||
}
|
||||
|
||||
// Prefer the original file; otherwise take whichever entry reports the most bytes.
|
||||
export function pickDefaultResolution(availableResolution) {
|
||||
const entries = Object.entries(availableResolution || {});
|
||||
if (entries.length === 0) return "";
|
||||
const original = entries.find(([resolution]) => /original/i.test(resolution));
|
||||
if (original) return original[0];
|
||||
return entries.reduce((best, current) =>
|
||||
sizeInBytes(current[1]) > sizeInBytes(best[1]) ? current : best)[0];
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export function validateLink(rawLink){
|
||||
if(rawLink.trim() === "")
|
||||
return false
|
||||
if(rawLink.includes("hentai"))
|
||||
return rawLink.includes("/g/")
|
||||
else
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user