功能:新增单部电影重新刮削功能,优化刮削日志展示样式
This commit is contained in:
parent
69494434e9
commit
83e23b7365
@ -414,6 +414,7 @@
|
|||||||
@batch-delete="handleBatchDelete"
|
@batch-delete="handleBatchDelete"
|
||||||
@toggle-multi-select-exit="toggleMultiSelectMode"
|
@toggle-multi-select-exit="toggleMultiSelectMode"
|
||||||
@scrape-status="handleScrapeStatus"
|
@scrape-status="handleScrapeStatus"
|
||||||
|
@rescrape="handleScrapeMovie"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -558,17 +559,33 @@
|
|||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="scraper-log-list">
|
<div class="scraper-log-list">
|
||||||
<div v-for="log in scraperLogs" :key="log.id" class="log-entry">
|
<div v-for="log in scraperLogs" :key="log.id" class="log-entry log-entry--expandable">
|
||||||
<div class="log-entry__header">
|
<!-- 日志头部(可点击展开) -->
|
||||||
<div>
|
<div class="log-entry__header" @click="toggleLogDetail(log.id)">
|
||||||
<div class="log-time">{{ log.createTime || log.create_time }}</div>
|
<div class="log-entry__summary">
|
||||||
|
<span class="log-entry__toggle">{{ log.showDetail ? '▾' : '▸' }}</span>
|
||||||
|
<span class="log-entry__time">{{ log.scanTime || log.createTime || '-' }}</span>
|
||||||
|
<span class="log-entry__desc">{{ log.description || '刮削任务执行记录' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="log-stats">
|
<div class="log-entry__stats">
|
||||||
<span class="stat-total">总共: {{ log.totalProcessed || log.total_processed || 0 }}</span>
|
<span class="stat-total">总共: {{ log.totalMovies || log.total || 0 }}</span>
|
||||||
<span class="stat-success">成功: {{ log.totalSuccess || log.total_success || 0 }}</span>
|
<span class="stat-success">成功: {{ log.successCount || log.success || 0 }}</span>
|
||||||
<span class="stat-fail">失败: {{ log.totalFailed || log.total_failed || 0 }}</span>
|
<span class="stat-fail">失败: {{ log.failCount || log.failed || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 详细日志(可展开) -->
|
||||||
|
<Transition name="slide-down">
|
||||||
|
<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">
|
||||||
|
<span class="detail-item__name">{{ item.movieName || item.title || '未知电影' }}</span>
|
||||||
|
<span class="detail-item__status" :class="`detail-item__status--${item.status}`">
|
||||||
|
{{ item.status === 'success' ? '✓ 成功' : item.status === 'failed' ? '✗ 失败' : '⋯ 处理中' }}
|
||||||
|
</span>
|
||||||
|
<span v-if="item.message" class="detail-item__message">{{ item.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -797,6 +814,7 @@ import {
|
|||||||
requestState,
|
requestState,
|
||||||
resetData,
|
resetData,
|
||||||
saveTmdbConfig as apiSaveTmdbConfig,
|
saveTmdbConfig as apiSaveTmdbConfig,
|
||||||
|
scrapeMovie,
|
||||||
updateDrive,
|
updateDrive,
|
||||||
updateFolderInDrive,
|
updateFolderInDrive,
|
||||||
updateMovieInDrive,
|
updateMovieInDrive,
|
||||||
@ -1695,6 +1713,14 @@ function handleLoadMoreScraperLogs() {
|
|||||||
loadMoreScraperLogs()
|
loadMoreScraperLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换日志详情展开/收起
|
||||||
|
function toggleLogDetail(logId) {
|
||||||
|
const log = scraperLogs.value.find(l => l.id === logId)
|
||||||
|
if (log) {
|
||||||
|
log.showDetail = !log.showDetail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TMDB 配置
|
// TMDB 配置
|
||||||
function handleTmdbConfig() {
|
function handleTmdbConfig() {
|
||||||
showUserMenu.value = false
|
showUserMenu.value = false
|
||||||
@ -2578,6 +2604,36 @@ async function doBatchScrapeAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新刮削单部电影
|
||||||
|
*/
|
||||||
|
async function handleScrapeMovie(node) {
|
||||||
|
if (!node || node.type !== 'file') {
|
||||||
|
setErrorMessage('只有电影文件才能重新刮削')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driveLoading.value = true
|
||||||
|
try {
|
||||||
|
const result = await scrapeMovie(node.id)
|
||||||
|
|
||||||
|
if (result?.code === 200) {
|
||||||
|
setSuccessMessage(`电影“${node.title}”刮削请求已提交`)
|
||||||
|
|
||||||
|
// 刮削完成后重新加载数据
|
||||||
|
await loadDriveFolders(activeSheetId.value)
|
||||||
|
await loadDuplicateItems()
|
||||||
|
} else {
|
||||||
|
setErrorMessage(result?.message || '刮削失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('刮削失败', err)
|
||||||
|
setErrorMessage(err?.payload?.message || err?.message || '刮削失败')
|
||||||
|
} finally {
|
||||||
|
driveLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量刮削选中的电影文件
|
* 批量刮削选中的电影文件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -747,6 +747,19 @@ export function batchScrapeAll() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 单部影片刮削 API ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新刮削单部电影
|
||||||
|
* @param {number} movieId - 电影ID
|
||||||
|
* @returns {Promise} 返回格式:{ code: 200, message: "刮削完成", data: { movieId, title, rating, posterUrl } }
|
||||||
|
*/
|
||||||
|
export function scrapeMovie(movieId) {
|
||||||
|
return request(`${API_BASE_URL}/tmdb-scraper/scrape?movieId=${movieId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 刮削状态 API ====================
|
// ==================== 刮削状态 API ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
@edit="handleEdit"
|
@edit="handleEdit"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@scrape-status="handleScrapeStatus"
|
@scrape-status="handleScrapeStatus"
|
||||||
|
@rescrape="handleRescrape"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +54,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['add-folder', 'add-file', 'expand', 'edit', 'delete', 'toggle-select', 'batch-delete', 'toggle-multi-select-exit', 'scrape-status'])
|
const emit = defineEmits(['add-folder', 'add-file', 'expand', 'edit', 'delete', 'toggle-select', 'batch-delete', 'toggle-multi-select-exit', 'scrape-status', 'rescrape'])
|
||||||
|
|
||||||
function handleAddFolder(parentId) {
|
function handleAddFolder(parentId) {
|
||||||
emit('add-folder', parentId)
|
emit('add-folder', parentId)
|
||||||
@ -79,6 +80,10 @@ function handleScrapeStatus(node) {
|
|||||||
emit('scrape-status', node)
|
emit('scrape-status', node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRescrape(node) {
|
||||||
|
emit('rescrape', node)
|
||||||
|
}
|
||||||
|
|
||||||
function handleToggleSelect(nodeId) {
|
function handleToggleSelect(nodeId) {
|
||||||
emit('toggle-select', nodeId)
|
emit('toggle-select', nodeId)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,6 +74,15 @@
|
|||||||
>
|
>
|
||||||
+📄
|
+📄
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="node.type === 'file'"
|
||||||
|
type="button"
|
||||||
|
class="action-btn action-btn--rescrape"
|
||||||
|
title="重新刮削"
|
||||||
|
@click="handleRescrape"
|
||||||
|
>
|
||||||
|
🔄
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="node.type === 'file'"
|
v-if="node.type === 'file'"
|
||||||
type="button"
|
type="button"
|
||||||
@ -144,6 +153,7 @@
|
|||||||
@edit="$emit('edit', $event)"
|
@edit="$emit('edit', $event)"
|
||||||
@delete="$emit('delete', $event)"
|
@delete="$emit('delete', $event)"
|
||||||
@scrape-status="$emit('scrape-status', $event)"
|
@scrape-status="$emit('scrape-status', $event)"
|
||||||
|
@rescrape="$emit('rescrape', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@ -176,7 +186,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['add-folder', 'add-file', 'expand', 'edit', 'delete', 'toggle-select', 'scrape-status'])
|
const emit = defineEmits(['add-folder', 'add-file', 'expand', 'edit', 'delete', 'toggle-select', 'scrape-status', 'rescrape'])
|
||||||
|
|
||||||
const isSelected = computed(() => props.selectedIds.has(props.node.id))
|
const isSelected = computed(() => props.selectedIds.has(props.node.id))
|
||||||
|
|
||||||
@ -270,6 +280,11 @@ function handleScrapeStatus() {
|
|||||||
emit('scrape-status', props.node)
|
emit('scrape-status', props.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRescrape() {
|
||||||
|
console.log('[TreeNode] handleRescrape called, node:', props.node)
|
||||||
|
emit('rescrape', props.node)
|
||||||
|
}
|
||||||
|
|
||||||
function getScrapeStatusText(status) {
|
function getScrapeStatusText(status) {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
scraping: '刮削中',
|
scraping: '刮削中',
|
||||||
|
|||||||
@ -2171,6 +2171,115 @@ canvas {
|
|||||||
color: #b91c1c;
|
color: #b91c1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 可展开刮削日志样式 ===== */
|
||||||
|
.log-entry--expandable {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry--expandable:active {
|
||||||
|
transform: scale(0.995);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry__toggle {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #6366f1;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry__summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry__time {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry__desc {
|
||||||
|
color: #475569;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 展开后的详细日志面板 */
|
||||||
|
.log-entry__detail {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(99, 102, 241, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 详细日志项 */
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(249, 250, 251, 0.8);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item:hover {
|
||||||
|
background: rgba(243, 244, 246, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item__name {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1e293b;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item__status {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item__status--success {
|
||||||
|
background: rgba(34, 197, 94, 0.12);
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item__status--failed {
|
||||||
|
background: rgba(239, 68, 68, 0.12);
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item__status--pending {
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item__message {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
.log-time {
|
.log-time {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
@ -3272,19 +3381,16 @@ canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ====== 网盘操作加载遮罩层 ====== */
|
/* ====== 网盘操作加载遮罩层 ====== */
|
||||||
/* 覆盖整个管理区域,显示旋转动画和提示文字 */
|
/* 覆盖整个视口,显示旋转动画和提示文字,始终固定在屏幕中央 */
|
||||||
.drive-loading-overlay {
|
.drive-loading-overlay {
|
||||||
position: absolute; /* 相对于 manage-layout 定位 */
|
position: fixed; /* 固定定位,覆盖整个视口 */
|
||||||
top: 0;
|
inset: 0; /* 等价于 top: 0; left: 0; right: 0; bottom: 0 */
|
||||||
left: 0;
|
background: rgba(255, 255, 255, 0.92); /* 半透明白色背景 */
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.88); /* 半透明白色背景 */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 9999; /* 确保在所有内容之上 */
|
z-index: 10000; /* 确保在所有内容之上,包括全局加载遮罩 */
|
||||||
border-radius: 8px;
|
backdrop-filter: blur(4px); /* 毛玻璃效果,增强视觉层次 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 加载动画容器:垂直居中显示spinner和文字 */
|
/* 加载动画容器:垂直居中显示spinner和文字 */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user