优化黑夜

This commit is contained in:
liRQ 2026-06-26 18:14:35 +08:00
parent 83e23b7365
commit e4a0ac71cc
2 changed files with 1377 additions and 18 deletions

View File

@ -44,14 +44,39 @@
<input <input
v-model="searchKeyword" v-model="searchKeyword"
type="search" type="search"
placeholder="请输入标题、标签、资源库或维护人" placeholder="🔍 输入关键词搜索..."
@focus="isSearchFocused = true" @focus="isSearchFocused = true"
@blur="isSearchFocused = false" @blur="isSearchFocused = false"
@keydown.enter="handleHeroSearch" @keydown.enter="handleHeroSearch"
/> />
<button type="button" @click="handleHeroSearch">搜索</button>
</div> </div>
<!-- 黑夜/白天模式切换按钮 -->
<button
type="button"
class="theme-toggle-btn"
:class="{ 'theme-toggle-btn--dark': isDarkMode }"
@click="toggleDarkMode"
:title="isDarkMode ? '切换到白天模式' : '切换到黑夜模式'"
>
<svg class="theme-toggle-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<g v-if="!isDarkMode" class="theme-icon--sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</g>
<g v-if="isDarkMode" class="theme-icon--moon">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</g>
</svg>
</button>
<!-- 快捷入口按钮和悬浮面板 --> <!-- 快捷入口按钮和悬浮面板 -->
<div class="quick-access-wrapper" ref="quickAccessWrapperRef" @mouseenter="handleQuickAccessEnter" @mouseleave="handleQuickAccessWrapperLeave"> <div class="quick-access-wrapper" ref="quickAccessWrapperRef" @mouseenter="handleQuickAccessEnter" @mouseleave="handleQuickAccessWrapperLeave">
<button <button
@ -366,6 +391,14 @@
<button type="button" class="folder-tree-actions__btn" @click="handleOpenScraperLogDialog"> <button type="button" class="folder-tree-actions__btn" @click="handleOpenScraperLogDialog">
📋 刮削日志 📋 刮削日志
</button> </button>
<button
type="button"
class="folder-tree-actions__btn folder-tree-actions__btn--refresh"
@click="handleRefreshFolders"
title="刷新当前网盘文件夹列表"
>
🗂 刷新文件夹
</button>
<button <button
type="button" type="button"
class="folder-tree-actions__btn" class="folder-tree-actions__btn"
@ -458,9 +491,13 @@
{{ getStatusText(scrapeStatusData.status) }} {{ getStatusText(scrapeStatusData.status) }}
</span> </span>
</div> </div>
<div class="scrape-status-message"> <div v-if="scrapeStatusData.message" class="scrape-status-message">
{{ scrapeStatusData.message }} {{ scrapeStatusData.message }}
</div> </div>
<!-- 失败时显示详细错误信息 -->
<div v-if="scrapeStatusData.status === 'failed' && scrapeStatusData.error" class="scrape-status-error">
<strong>失败原因</strong>{{ scrapeStatusData.error }}
</div>
<div v-if="scrapeStatusData.posterUrl" class="scrape-status-poster"> <div v-if="scrapeStatusData.posterUrl" class="scrape-status-poster">
<img :src="scrapeStatusData.posterUrl" alt="海报" /> <img :src="scrapeStatusData.posterUrl" alt="海报" />
</div> </div>
@ -564,13 +601,13 @@
<div class="log-entry__header" @click="toggleLogDetail(log.id)"> <div class="log-entry__header" @click="toggleLogDetail(log.id)">
<div class="log-entry__summary"> <div class="log-entry__summary">
<span class="log-entry__toggle">{{ log.showDetail ? '▾' : '▸' }}</span> <span class="log-entry__toggle">{{ log.showDetail ? '▾' : '▸' }}</span>
<span class="log-entry__time">{{ log.scanTime || log.createTime || '-' }}</span> <span class="log-entry__time">{{ log.scanTime || '-' }}</span>
<span class="log-entry__desc">{{ log.description || '刮削任务执行记录' }}</span> <span class="log-entry__desc">{{ getStatusText(log.status) || '刮削任务执行记录' }}</span>
</div> </div>
<div class="log-entry__stats"> <div class="log-entry__stats">
<span class="stat-total">总共: {{ log.totalMovies || log.total || 0 }}</span> <span class="stat-total">总共: {{ log.totalMovies || 0 }}</span>
<span class="stat-success">成功: {{ log.successCount || log.success || 0 }}</span> <span class="stat-success">成功: {{ log.successCount || 0 }}</span>
<span class="stat-fail">失败: {{ log.failCount || log.failed || 0 }}</span> <span class="stat-fail">失败: {{ log.failCount || 0 }}</span>
</div> </div>
</div> </div>
@ -578,11 +615,10 @@
<Transition name="slide-down"> <Transition name="slide-down">
<div v-if="log.showDetail && log.detailList && log.detailList.length > 0" class="log-entry__detail"> <div v-if="log.showDetail && log.detailList && log.detailList.length > 0" class="log-entry__detail">
<div v-for="(item, idx) in log.detailList" :key="idx" class="detail-item"> <div v-for="(item, idx) in log.detailList" :key="idx" class="detail-item">
<span class="detail-item__name">{{ item.movieName || item.title || '未知电影' }}</span> <span class="detail-item__name">👤 {{ item.username || '用户 ' + (item.userId || idx) }}</span>
<span class="detail-item__status" :class="`detail-item__status--${item.status}`"> <span class="detail-item__status detail-item__status--info">
{{ item.status === 'success' ? '✓ 成功' : item.status === 'failed' ? '✗ 失败' : '⋯ 处理中' }} 处理: {{ item.processed || 0 }} | 成功: {{ item.success || 0 }} | 失败: {{ item.failed || 0 }} | 跳过: {{ item.skipped || 0 }}
</span> </span>
<span v-if="item.message" class="detail-item__message">{{ item.message }}</span>
</div> </div>
</div> </div>
</Transition> </Transition>
@ -845,6 +881,7 @@ const searchKeyword = ref('')
const isSearchFocused = ref(false) const isSearchFocused = ref(false)
const hasSearched = ref(false) const hasSearched = ref(false)
const currentView = ref('poster') const currentView = ref('poster')
const isDarkMode = ref(false) //
const activeSheetId = ref('') const activeSheetId = ref('')
const showRecordDialog = ref(false) const showRecordDialog = ref(false)
const showFolderDialog = ref(false) const showFolderDialog = ref(false)
@ -1678,11 +1715,52 @@ async function loadMoreScraperLogs() {
const logsRes = await fetchScraperLogs(params) const logsRes = await fetchScraperLogs(params)
console.log('[刮削日志] 后端返回原始数据:', logsRes)
if (logsRes?.code === 200 && Array.isArray(logsRes?.data?.list)) { if (logsRes?.code === 200 && Array.isArray(logsRes?.data?.list)) {
const newLogs = logsRes.data.list const newLogs = logsRes.data.list
console.log('[刮削日志] 日志列表数据:', newLogs)
console.log('[刮削日志] 第一条日志的字段:', Object.keys(newLogs[0] || {}))
if (newLogs[0]) {
console.log('[刮削日志] 第一条日志完整数据:', JSON.stringify(newLogs[0], null, 2))
}
//
const normalizedLogs = newLogs.map(log => {
// detail_json
let detailList = []
if (log.detail_json) {
try {
detailList = typeof log.detail_json === 'string'
? JSON.parse(log.detail_json)
: log.detail_json
} catch (e) {
console.warn('解析 detail_json 失败:', e)
}
}
return {
...log,
// showDetail
showDetail: false,
// 使
totalMovies: log.total_processed ?? log.totalMovies ?? log.total ?? 0,
successCount: log.total_success ?? log.successCount ?? log.success ?? 0,
failCount: log.total_failed ?? log.failCount ?? log.failed ?? 0,
totalSkipped: log.total_skipped ?? 0,
scanTime: log.start_time ?? log.create_time ?? log.createTime ?? '-',
endTime: log.end_time ?? '',
durationMs: log.duration_ms ?? 0,
status: log.status ?? 'unknown',
triggerType: log.trigger_type ?? 'manual',
//
detailList: detailList,
}
})
// //
scraperLogs.value.push(...newLogs) scraperLogs.value.push(...normalizedLogs)
// //
scraperLogPagination.total = logsRes.data.total || 0 scraperLogPagination.total = logsRes.data.total || 0
@ -2491,6 +2569,28 @@ function preloadImage(url) {
img.src = url img.src = url
} }
/**
* 刷新当前网盘文件夹列表
*/
async function handleRefreshFolders() {
const driveId = activeSheetId.value
if (!driveId) {
setErrorMessage('请先选择一个网盘')
return
}
driveLoading.value = true
try {
await loadDriveFolders(driveId)
setSuccessMessage('文件夹列表已刷新')
} catch (err) {
console.error('刷新文件夹列表失败', err)
setErrorMessage(err?.message || '刷新文件夹列表失败')
} finally {
driveLoading.value = false
}
}
// //
function handleScrapeStatus(node) { function handleScrapeStatus(node) {
// //
@ -2521,7 +2621,7 @@ function handleScrapeStatus(node) {
rating: data.rating, rating: data.rating,
category: data.category, category: data.category,
tags: data.tags, tags: data.tags,
error: data.error, error: data.error || data.message,
} }
} else { } else {
setErrorMessage(result?.message || '查询刮削状态失败') setErrorMessage(result?.message || '查询刮削状态失败')
@ -2805,6 +2905,27 @@ onMounted(() => {
resumeFeaturedAutoplay() resumeFeaturedAutoplay()
}) })
// /
function toggleDarkMode() {
isDarkMode.value = !isDarkMode.value
// body class
document.body.classList.toggle('dark-mode', isDarkMode.value)
// localStorage
localStorage.setItem('darkMode', isDarkMode.value ? '1' : '0')
}
// localStorage
function initDarkMode() {
const savedMode = localStorage.getItem('darkMode')
if (savedMode === '1') {
isDarkMode.value = true
document.body.classList.add('dark-mode')
}
}
// onMounted
initDarkMode()
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('wangpan:unauthorized', handleUnauthorized) window.removeEventListener('wangpan:unauthorized', handleUnauthorized)
window.removeEventListener('wangpan:forbidden', handleForbidden) window.removeEventListener('wangpan:forbidden', handleForbidden)

File diff suppressed because it is too large Load Diff