Files
lionwebsite-frontend-desktop/src/components/DashBoard.vue
T
root e836becda6 桌面端下载器调优:任务列表信息层级与操作区重排
- 统一 theme.css 视觉变量与卡片、状态标签样式
- DashBoard 与 Side 的任务行重排:状态标签与进度对齐,排序/显示条件更明确
- 新增 AppIcon 图标组件,store 同步空名短标题修复
2026-09-20 18:41:09 +08:00

424 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- Auth Screen -->
<div v-show="!loadComplete" class="auth-container">
<div class="auth-card">
<div class="auth-brand">
<span class="auth-mark" aria-hidden="true">L</span>
<span class="auth-name">LionWebsite</span>
</div>
<h1>下载管理器</h1>
<p class="auth-subtitle">输入授权码以继续</p>
<div class="auth-form">
<el-input v-model="AuthCode"
placeholder="请输入授权码"
size="large"
show-password
@keydown.enter="validate" />
<el-checkbox v-model="isRemember">记住授权码</el-checkbox>
<el-button @click="validate" type="primary" size="large" class="auth-btn">验证</el-button>
</div>
</div>
</div>
<!-- Dashboard Main -->
<div v-show="loadComplete" class="dashboard">
<div class="control-card">
<div class="card-head">
<h3 class="section-title">本周用量</h3>
<el-button @click="queryWeekUsedAmount" size="small" text>刷新</el-button>
</div>
<div class="usage-line">
<span class="usage-value">{{ weekUsed.weekUsedAmount ?? '—' }}</span>
<span class="usage-note">上次重置 {{ weekUsed.lastResetAmountTime ?? '—' }}</span>
</div>
</div>
<div class="dashboard-grid">
<div class="control-card">
<div class="card-head">
<h3 class="section-title">查询任务</h3>
</div>
<div class="card-row">
<el-select v-model="type" @change="resetLocalQuery" size="small" style="width: 92px">
<el-option value="link" label="链接"/>
<el-option value="keyword" label="关键字"/>
</el-select>
<el-input v-model="param"
size="small"
placeholder="输入链接或关键字"
style="flex: 1; min-width: 150px"
@keydown.enter="type === 'link' ? queryRemoteTask() : queryLocalTask()" />
<el-button @click="queryRemoteTask" v-show="type === 'link'" size="small" type="primary">远程查询</el-button>
<el-button @click="queryLocalTask" size="small">当前页查询</el-button>
</div>
</div>
<div class="control-card">
<div class="card-head">
<h3 class="section-title">管理</h3>
</div>
<div class="card-row">
<el-button @click="isAlterAuthCode = true" size="small">修改授权码</el-button>
<el-button @click="deleteAuthCode" size="small">删除本地授权码</el-button>
<el-button @click="isConfig = true" size="small">配置</el-button>
</div>
</div>
<div class="control-card">
<div class="card-head">
<h3 class="section-title">系统</h3>
</div>
<div class="card-row">
<el-button @click="isQuerying = true" size="small" type="primary">在线搜索</el-button>
<el-button @click="reconnect" size="small">重连节点</el-button>
<el-button v-if="isLion" @click="resetUndone" size="small" type="danger" plain>重置任务</el-button>
</div>
</div>
<div class="control-card">
<div class="card-head">
<h3 class="section-title">外观</h3>
</div>
<div class="setting-row">
<span>夜间模式</span>
<el-switch v-model="isDark" @change="toggleStyle" />
</div>
</div>
</div>
<div v-show="thumbnailGallery.thumb_link !== undefined" class="control-card">
<div class="card-head">
<h3 class="section-title">缩略图</h3>
</div>
<div class="thumbnail-body">
<p class="gallery-name">{{ thumbnailGallery.shortName }}</p>
<div class="thumbnail-frame">
<el-image :src="thumbnailGallery.thumb_link"
:preview-src-list="[thumbnailGallery.thumb_link]"
style="width: 100%; height: 420px"
fit="contain" />
</div>
</div>
</div>
</div>
<el-dialog title="任务详情" v-model="chosenGallery" width="560px">
<div class="detail-layout">
<el-image v-show="chosenGallery.thumb_link !== undefined"
class="detail-thumb"
fit="contain"
:src="chosenGallery.thumb_link !== undefined ? thumbnailUrl(chosenGallery.thumb_link) : ''" />
<dl class="detail-list">
<dt>名字</dt>
<dd>{{ chosenGallery.name }}</dd>
<dt>页数</dt>
<dd>{{ chosenGallery.pages }}</dd>
<dt>语言</dt>
<dd>{{ chosenGallery.language }}</dd>
<dt>大小</dt>
<dd>{{ chosenGallery.fileSize }}</dd>
<dt>状态</dt>
<dd>{{ chosenGallery.status }}</dd>
<template v-if="chosenGallery.availableResolution">
<dt>目标分辨率</dt>
<dd>
<el-select v-model="targetResolution" style="width: 220px">
<el-option v-for="(fileSize, resolution) in chosenGallery.availableResolution"
:key="resolution"
:value="resolution"
:label="resolution + ' ' + fileSize" />
</el-select>
</dd>
</template>
</dl>
</div>
<template #footer>
<el-button @click="postTask" v-if="chosenGallery.availableResolution" type="primary">提交下载</el-button>
<el-button @click="downloadChosenGallery" v-else-if="chosenGallery.status === '下载完成'" type="primary">下载文件</el-button>
<el-button @click="readOnlineGallery(chosenGallery)">在线预览</el-button>
<el-button v-if="chosenGallery.status === '下载完成'" type="danger" plain @click="deleteGallery">删除</el-button>
</template>
</el-dialog>
<el-dialog title="修改授权码" v-model="isAlterAuthCode">
<el-form label-position="top">
<el-form-item label="当前授权码">
<span class="mono-value">{{ realAuthCode }}</span>
</el-form-item>
<el-form-item label="新的授权码">
<el-input v-model="newAuthCode" show-password></el-input>
</el-form-item>
<el-form-item label="再次输入授权码">
<el-input v-model="tempAuthCode" show-password></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="isAlterAuthCode = false">取消</el-button>
<el-button type="primary" @click="alterAuthCode">提交</el-button>
</template>
</el-dialog>
<HentaiSearch v-model:is-querying="isQuerying" @close="isQuerying = false"></HentaiSearch>
<el-dialog title="配置" v-model="isConfig">
<el-form label-position="top" class="config-form">
<h4 class="section-title">外观</h4>
<div class="setting-row">
<span>夜间模式跟随系统</span>
<el-switch v-model="darkConfig.followSystem"></el-switch>
</div>
<h4 class="section-title">在线预览</h4>
<el-form-item label="每页图片数量">
<el-input-number v-model="lengthPerPage" :min="1" :max="30" :step="1" style="width: 160px" />
<span class="field-hint">1 – 30</span>
</el-form-item>
<h4 class="section-title">默认设置</h4>
<el-form-item label="分类">
<el-select v-model="category" default-first-option style="width: 220px">
<el-option label="全部" value="total"/>
<el-option label="我的下载" value="myDownload"/>
<el-option label="我的收藏" value="myCollect"/>
</el-select>
</el-form-item>
<el-form-item label="排序方式">
<el-select v-model="sortType" default-first-option style="width: 220px">
<el-option label="名字" value="name"/>
<el-option label="简洁名字" value="shortName"/>
<el-option label="任务创建时间" value="createTime"/>
</el-select>
</el-form-item>
<el-form-item label="显示类型">
<el-select v-model="galleryNameType" default-first-option style="width: 220px">
<el-option label="名字" value="name"/>
<el-option label="简洁名字" value="shortName"/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="saveConfig">保存</el-button>
</template>
</el-dialog>
</template>
<script setup>
import store from "../store";
import {computed, ref, onMounted, watch} from "vue";
import {ElMessage} from "element-plus"
import HentaiSearch from "./HentaiSearch.vue";
import {validateLink} from "../utils/validate.js";
let AuthCode = ref("")
let isRemember = ref(false)
let isAlterAuthCode = ref(false)
let newAuthCode = ref("")
let tempAuthCode = ref("")
let isQuerying = ref(false)
let isConfig = ref(false)
let isDark = ref(false)
let keyword = ref("")
let darkConfig = ref({})
let lengthPerPage = ref(0)
let category = ref("")
let sortType = ref("")
let galleryNameType = ref("")
let type = ref("link")
let param = ref("")
let targetResolution = ref("")
let realAuthCode = computed(() => {
return store.state.AuthCode
})
let chosenGallery = computed(() => {
return store.state.chosenGallery
})
let loadComplete = computed(() => {
return store.state.loadComplete
})
let weekUsed = computed(() => {
return store.state.weekUsed
})
let thumbnailGallery = computed(() => {
return store.state.thumbnailGallery
})
let isLion = computed(() => {
return store.state.userId === 3
})
watch(chosenGallery, () => {
param.value = ''
targetResolution.value = ''
})
function reconnect(){
store.dispatch("reconnect")
}
function alterAuthCode(){
if(newAuthCode.value.trim() === "" || tempAuthCode.value.trim() === "" || newAuthCode.value !== tempAuthCode.value)
ElMessage("请检查授权码输入是否错误")
else {
store.dispatch("alterAuthCode", newAuthCode.value)
isAlterAuthCode.value = false
newAuthCode.value = ""
tempAuthCode.value = ""
}
}
function queryWeekUsedAmount(){
store.dispatch("loadWeekUsedAmount")
}
function postTask(){
if(!validateLink(chosenGallery.value.link)){
ElMessage("链接错误")
return
}
if(targetResolution.value === ''){
ElMessage("请选择分辨率再提交")
return
}
store.dispatch("postGalleryTask",
{link: chosenGallery.value.link,
targetResolution: targetResolution.value})
}
function queryRemoteTask(){
if(!validateLink(param.value)){
ElMessage("链接错误")
return
}
if(param.value.includes("e-hentai"))
param.value = param.value.replace("e-hentai", "exhentai")
store.dispatch("queryGalleryTask", param.value)
}
function queryLocalTask(){
switch (type.value){
case "link":
store.commit("_searchLocalByLink", param.value)
break
case "keyword":
store.commit("_searchLocalByKeyword", param.value)
break
}
}
function resetLocalQuery(){
store.commit("_searchLocalByKeyword", "")
param.value = ""
}
function deleteGallery(){
store.dispatch("deleteGallery", chosenGallery.value.gid)
}
function downloadChosenGallery(){
if(chosenGallery.value.download)
window.open(chosenGallery.value.download)
else
ElMessage({message: "下载地址不存在,请刷新任务后重试", type: "error"})
}
function validate(){
if(AuthCode.value.trim() === ""){
ElMessage("请输入授权码后再验证")
}
else{
store.dispatch("validate", AuthCode.value)
if(isRemember.value)
localStorage.setItem("auth", AuthCode.value)
}
}
function 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(){
store.dispatch("resetUndone").then()
}
function deleteAuthCode(){
localStorage.removeItem('auth')
ElMessage("删除授权码完成")
}
function toggleStyle(){
if(isDark.value)
document.documentElement.classList.add('dark')
else
document.documentElement.classList.remove('dark')
}
onMounted(() => {
const auth = localStorage.getItem("auth")
adjustForStyle()
store.state.lengthPerPage = localStorage.getItem("lengthPerPage")
store.state.lengthPerPage = store.state.lengthPerPage === null ? 30: Number(store.state.lengthPerPage)
category.value = store.state.category = localStorage.getItem("category") === null ? "myDownload" : localStorage.getItem("category")
sortType.value = store.state.sortType = localStorage.getItem("sortType") === null ? "createTime" : localStorage.getItem("sortType")
galleryNameType.value = store.state.galleryNameType = localStorage.getItem("galleryNameType") === null ? "shortName" : localStorage.getItem("galleryNameType")
lengthPerPage.value = store.state.lengthPerPage
if(auth !== null){
store.dispatch("validate", auth)
AuthCode.value = localStorage.getItem("auth")
}
})
function adjustForStyle(){
let darkConfigStr = localStorage.getItem("darkConfig")
if(darkConfigStr !== null) {
darkConfig.value = JSON.parse(darkConfigStr)
if (darkConfig.value.followSystem && isSystemDark())
document.documentElement.classList.add('dark')
else
document.documentElement.classList.remove('dark')
}else {
document.documentElement.classList.remove('dark')
darkConfig.value = {'followSystem': false}
}
isDark.value = document.documentElement.classList.contains('dark')
}
function isSystemDark(){
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
function saveConfig(){
if(lengthPerPage.value < 0 || lengthPerPage.value > 30) {
ElMessage("分页页数设置错误,范围1~30")
lengthPerPage.value = 30
}
else {
store.state.lengthPerPage = Number(lengthPerPage.value)
localStorage.setItem("lengthPerPage", lengthPerPage.value)
}
localStorage.setItem("darkConfig", JSON.stringify(darkConfig.value))
localStorage.setItem("category", category.value)
localStorage.setItem("sortType", sortType.value)
localStorage.setItem("galleryNameType", galleryNameType.value)
store.commit("_setCategory", category.value)
store.commit("_setSortType", sortType.value)
store.commit("_setGalleryNameType", galleryNameType.value)
isConfig.value = false
adjustForStyle()
}
</script>
<style scoped>
</style>