功能:新增电影简介、导演和主演信息展示,优化刮削日志样式
This commit is contained in:
parent
e4a0ac71cc
commit
a9066b9046
@ -513,6 +513,19 @@
|
||||
<div v-if="scrapeStatusData.tags" class="scrape-status-tags">
|
||||
<span v-for="tag in (typeof scrapeStatusData.tags === 'string' ? scrapeStatusData.tags.split(',') : scrapeStatusData.tags)" :key="tag">{{ tag }}</span>
|
||||
</div>
|
||||
<!-- 电影简介 -->
|
||||
<div v-if="scrapeStatusData.overview" class="scrape-status-overview">
|
||||
<span class="scrape-status-label">简介:</span>
|
||||
<span class="scrape-status-value">{{ scrapeStatusData.overview }}</span>
|
||||
</div>
|
||||
<!-- 导演 -->
|
||||
<div v-if="scrapeStatusData.directors" class="scrape-status-meta">
|
||||
<span>导演:{{ scrapeStatusData.directors }}</span>
|
||||
</div>
|
||||
<!-- 主演 -->
|
||||
<div v-if="scrapeStatusData.casts" class="scrape-status-meta">
|
||||
<span>主演:{{ scrapeStatusData.casts }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="scrape-status-loading">
|
||||
正在查询刮削状态...
|
||||
@ -614,12 +627,23 @@
|
||||
<!-- 详细日志(可展开) -->
|
||||
<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">
|
||||
<template v-for="(item, idx) in log.detailList" :key="idx">
|
||||
<div class="detail-item detail-item--user">
|
||||
<span class="detail-item__name">👤 {{ item.username || '用户 ' + (item.userId || idx) }}</span>
|
||||
<span class="detail-item__status detail-item__status--info">
|
||||
处理: {{ item.processed || 0 }} | 成功: {{ item.success || 0 }} | 失败: {{ item.failed || 0 }} | 跳过: {{ item.skipped || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 显示具体的消息列表 -->
|
||||
<div v-if="item.messages && item.messages.length > 0" class="detail-item__messages">
|
||||
<div v-for="(msg, msgIdx) in item.messages" :key="msgIdx" class="message-item">
|
||||
<span :class="['message-item__indicator', getMessageStatus(msg)]">
|
||||
{{ getMessageStatus(msg) === 'success' ? '✓' : getMessageStatus(msg) === 'failed' ? '✗' : '⊘' }}
|
||||
</span>
|
||||
<span class="message-item__text">{{ msg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
@ -1923,10 +1947,11 @@ function openEditRecord(item) {
|
||||
editingRecord.value = {
|
||||
id: item.id,
|
||||
title: item.title || '',
|
||||
subtitle: item.subtitle || '',
|
||||
originalName: item.originalName || '',
|
||||
description: item.description || '',
|
||||
category: item.category || '',
|
||||
directors: item.directors || '',
|
||||
casts: item.casts || '',
|
||||
updatedAt: item.updatedAt || '',
|
||||
poster: item.poster || '',
|
||||
tags: [...(item.tags || [])],
|
||||
@ -2001,12 +2026,13 @@ function handleAddFile(parentId) {
|
||||
editingRecord.value = {
|
||||
id: null,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
tags: [],
|
||||
score: 0,
|
||||
originalName: '',
|
||||
description: '',
|
||||
category: '',
|
||||
directors: '',
|
||||
casts: '',
|
||||
sheetId: activeSheetId.value,
|
||||
parentId: parentId || null,
|
||||
type: 'file',
|
||||
@ -2331,11 +2357,12 @@ async function handleSaveRecord(record) {
|
||||
const result = await updateMovieInDrive(driveId, record.id, {
|
||||
title: record.title,
|
||||
originalName: record.originalName,
|
||||
subtitle: record.subtitle,
|
||||
description: record.description,
|
||||
rating: record.score,
|
||||
tags: Array.isArray(record.tags) ? record.tags.join(',') : '',
|
||||
category: record.category,
|
||||
directors: record.directors,
|
||||
casts: record.casts,
|
||||
})
|
||||
|
||||
const updatedData = result?.data || {}
|
||||
@ -2343,9 +2370,10 @@ async function handleSaveRecord(record) {
|
||||
...record,
|
||||
title: updatedData.title || record.title,
|
||||
originalName: updatedData.originalName || record.originalName,
|
||||
subtitle: updatedData.subtitle ?? record.subtitle,
|
||||
description: updatedData.description ?? record.description,
|
||||
category: updatedData.category ?? record.category,
|
||||
directors: updatedData.directors ?? record.directors,
|
||||
casts: updatedData.casts ?? record.casts,
|
||||
score: Number(updatedData.rating ?? record.score ?? 0),
|
||||
tags: updatedData.tags ?? record.tags ?? [],
|
||||
updatedAt: updatedData.updateTime || updatedData.createTime || record.updatedAt || new Date().toLocaleString('zh-CN', { hour12: false }),
|
||||
@ -2488,7 +2516,6 @@ async function loadRecommendMovies() {
|
||||
recommendMovies.value = result.data.list.map(movie => ({
|
||||
id: movie.id,
|
||||
title: movie.title || '未知标题',
|
||||
subtitle: movie.subtitle || '',
|
||||
description: movie.description || '',
|
||||
posterUrl: movie.posterUrl || '',
|
||||
rating: movie.rating || 0,
|
||||
@ -2621,6 +2648,9 @@ function handleScrapeStatus(node) {
|
||||
rating: data.rating,
|
||||
category: data.category,
|
||||
tags: data.tags,
|
||||
overview: data.overview || '',
|
||||
casts: data.casts || '',
|
||||
directors: data.directors || '',
|
||||
error: data.error || data.message,
|
||||
}
|
||||
} else {
|
||||
@ -2640,10 +2670,37 @@ function getStatusText(status) {
|
||||
scraping: '刮削中',
|
||||
success: '刮削成功',
|
||||
failed: '刮削失败',
|
||||
partial: '部分成功',
|
||||
skipped: '已跳过',
|
||||
}
|
||||
return statusMap[status] || '未知状态'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断刮削消息的状态
|
||||
* @param {string} msg - 消息文本
|
||||
* @returns {string} 'success' | 'failed' | 'skipped'
|
||||
*/
|
||||
function getMessageStatus(msg) {
|
||||
if (!msg) return 'skipped'
|
||||
|
||||
// 成功的标志
|
||||
if (msg.includes('刮削成功') || msg.includes('成功') || msg.includes('Successfully')) {
|
||||
return 'success'
|
||||
}
|
||||
|
||||
// 失败的标志
|
||||
if (msg.includes('刮削失败') || msg.includes('失败') ||
|
||||
msg.includes('未找到') || msg.includes('错误') ||
|
||||
msg.includes('Error') || msg.includes('Failed') ||
|
||||
msg.includes('不存在') || msg.includes('跳过')) {
|
||||
return 'failed'
|
||||
}
|
||||
|
||||
// 默认视为跳过(如:已存在、无需处理等)
|
||||
return 'skipped'
|
||||
}
|
||||
|
||||
// 多选模式相关函数
|
||||
function toggleMultiSelectMode() {
|
||||
multiSelectMode.value = !multiSelectMode.value
|
||||
|
||||
@ -16,10 +16,6 @@
|
||||
<span>文件名</span>
|
||||
<input v-model.trim="localRecord.title" type="text" placeholder="请输入电影文件名,如 xxx.mp4" />
|
||||
</label>
|
||||
<label>
|
||||
<span>原始文件名</span>
|
||||
<input v-model.trim="localRecord.originalName" type="text" placeholder="用于 TMDB 刮削纠错" />
|
||||
</label>
|
||||
<label>
|
||||
<span>所属网盘</span>
|
||||
<select v-model="localRecord.sheetId">
|
||||
@ -31,12 +27,12 @@
|
||||
<input v-model.number="localRecord.score" type="number" min="0" max="100" placeholder="0-100" />
|
||||
</label>
|
||||
<label>
|
||||
<span>副标题</span>
|
||||
<input v-model.trim="localRecord.subtitle" type="text" placeholder="可选" />
|
||||
<span>导演</span>
|
||||
<input v-model.trim="localRecord.directors" type="text" placeholder="如 导演1, 导演2" />
|
||||
</label>
|
||||
<label>
|
||||
<span>分类标签</span>
|
||||
<input v-model.trim="localRecord.category" type="text" placeholder="如 科幻 / 动作" />
|
||||
<span>主演</span>
|
||||
<input v-model.trim="localRecord.casts" type="text" placeholder="如 主演1, 主演2" />
|
||||
</label>
|
||||
<label class="field-span-2">
|
||||
<span>标签</span>
|
||||
@ -98,12 +94,13 @@ function createEmptyRecord() {
|
||||
return {
|
||||
id: null,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
tags: [],
|
||||
score: 0,
|
||||
originalName: '',
|
||||
description: '',
|
||||
category: '',
|
||||
directors: '',
|
||||
casts: '',
|
||||
sheetId: props.activeSheetId || props.sheets[0]?.id || '',
|
||||
parentId: null,
|
||||
}
|
||||
@ -118,9 +115,10 @@ function handleSave() {
|
||||
...localRecord.value,
|
||||
title: localRecord.value.title?.trim() || '',
|
||||
originalName: localRecord.value.originalName?.trim() || '',
|
||||
subtitle: localRecord.value.subtitle?.trim() || '',
|
||||
description: localRecord.value.description?.trim() || '',
|
||||
category: localRecord.value.category?.trim() || '',
|
||||
directors: localRecord.value.directors?.trim() || '',
|
||||
casts: localRecord.value.casts?.trim() || '',
|
||||
})
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
698
vue/src/components/SettingsView.vue
Normal file
698
vue/src/components/SettingsView.vue
Normal file
@ -0,0 +1,698 @@
|
||||
<template>
|
||||
<div class="settings-view">
|
||||
<div class="settings-header">
|
||||
<h2>设置</h2>
|
||||
<p>配置 OpenList 后端接口,绑定您的云盘账户</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h3 class="settings-card__title">
|
||||
<span class="settings-card__icon">🔗</span>
|
||||
OpenList 绑定
|
||||
</h3>
|
||||
|
||||
<form class="settings-form" @submit.prevent="handleBind">
|
||||
<!-- OpenList 地址 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="openlist-url">
|
||||
<span class="form-label__icon">🌐</span>
|
||||
<span class="form-label__text">OpenList 地址</span>
|
||||
</label>
|
||||
<input
|
||||
id="openlist-url"
|
||||
v-model="formData.url"
|
||||
type="url"
|
||||
class="form-input"
|
||||
placeholder="例如:https://olist.example.com 或 http://localhost:5575"
|
||||
required
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="form-help">请填写 OpenList 服务的完整访问地址,包含协议头</span>
|
||||
</div>
|
||||
|
||||
<!-- 账号 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="openlist-username">
|
||||
<span class="form-label__icon">👤</span>
|
||||
<span class="form-label__text">账号</span>
|
||||
</label>
|
||||
<input
|
||||
id="openlist-username"
|
||||
v-model="formData.username"
|
||||
type="text"
|
||||
class="form-input"
|
||||
placeholder="请输入 OpenList 用户名"
|
||||
required
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 密码 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="openlist-password">
|
||||
<span class="form-label__icon">🔒</span>
|
||||
<span class="form-label__text">密码</span>
|
||||
</label>
|
||||
<div class="password-input-wrapper">
|
||||
<input
|
||||
id="openlist-password"
|
||||
v-model="formData.password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
class="form-input"
|
||||
placeholder="请输入 OpenList 密码"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="password-toggle"
|
||||
@click="showPassword = !showPassword"
|
||||
:title="showPassword ? '隐藏密码' : '显示密码'"
|
||||
>
|
||||
{{ showPassword ? '🙈' : '👁️' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 绑定按钮 -->
|
||||
<div class="form-actions">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-bind"
|
||||
:disabled="binding"
|
||||
:class="{ 'btn-bind--loading': binding }"
|
||||
>
|
||||
<span v-if="!binding">🔗 绑定 OpenList</span>
|
||||
<span v-else class="loading-spinner">⏳ 绑定中...</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-reset"
|
||||
@click="resetForm"
|
||||
:disabled="binding"
|
||||
>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 绑定状态提示 -->
|
||||
<Transition name="fade">
|
||||
<div v-if="bindStatus" class="bind-status" :class="`bind-status--${bindStatus.type}`">
|
||||
<span class="bind-status__icon">{{ bindStatus.icon }}</span>
|
||||
<span class="bind-status__text">{{ bindStatus.message }}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- 已绑定信息 -->
|
||||
<Transition name="fade">
|
||||
<div v-if="boundInfo" class="bound-info">
|
||||
<div class="bound-info__header">
|
||||
<span class="bound-info__icon">✅</span>
|
||||
<span class="bound-info__text">已绑定 OpenList</span>
|
||||
</div>
|
||||
<div class="bound-info__details">
|
||||
<div class="bound-info__item">
|
||||
<span class="bound-info__label">地址:</span>
|
||||
<span class="bound-info__value">{{ boundInfo.url }}</span>
|
||||
</div>
|
||||
<div class="bound-info__item">
|
||||
<span class="bound-info__label">账号:</span>
|
||||
<span class="bound-info__value">{{ boundInfo.username }}</span>
|
||||
</div>
|
||||
<div class="bound-info__item">
|
||||
<span class="bound-info__label">绑定时间:</span>
|
||||
<span class="bound-info__value">{{ boundInfo.boundAt }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-unbind"
|
||||
@click="handleUnbind"
|
||||
>
|
||||
🗑️ 解除绑定
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { bindOpenList, unbindOpenList, fetchOpenListStatus } from '../api'
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
url: '',
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
// 状态控制
|
||||
const binding = ref(false)
|
||||
const showPassword = ref(false)
|
||||
const bindStatus = ref(null)
|
||||
const boundInfo = ref(null)
|
||||
|
||||
/**
|
||||
* 加载已绑定的 OpenList 信息
|
||||
*/
|
||||
async function loadBoundInfo() {
|
||||
try {
|
||||
const result = await fetchOpenListStatus()
|
||||
if (result?.code === 200 && result?.data?.bound) {
|
||||
boundInfo.value = {
|
||||
url: result.data.url || '',
|
||||
username: result.data.username || '',
|
||||
boundAt: result.data.boundAt || '-',
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('加载绑定信息失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理绑定表单提交
|
||||
*/
|
||||
async function handleBind() {
|
||||
if (!formData.url || !formData.username || !formData.password) {
|
||||
setBindStatus('error', '请填写所有必填字段')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证 URL 格式
|
||||
try {
|
||||
new URL(formData.url)
|
||||
} catch {
|
||||
setBindStatus('error', '请输入有效的 URL 地址(需包含协议头,如 https://)')
|
||||
return
|
||||
}
|
||||
|
||||
binding.value = true
|
||||
setBindStatus(null, '')
|
||||
|
||||
try {
|
||||
const result = await bindOpenList({
|
||||
url: formData.url,
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
})
|
||||
|
||||
if (result?.code === 200) {
|
||||
setBindStatus('success', '绑定成功!')
|
||||
boundInfo.value = {
|
||||
url: formData.url,
|
||||
username: formData.username,
|
||||
boundAt: new Date().toLocaleString('zh-CN'),
|
||||
}
|
||||
// 清空密码字段
|
||||
formData.password = ''
|
||||
// 重新加载绑定信息
|
||||
await loadBoundInfo()
|
||||
} else {
|
||||
setBindStatus('error', result?.message || '绑定失败,请稍后重试')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('绑定 OpenList 失败:', err)
|
||||
const errorMsg = err?.message || err?.payload?.message || '网络错误,请检查后端连接'
|
||||
setBindStatus('error', errorMsg)
|
||||
} finally {
|
||||
binding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理解除绑定
|
||||
*/
|
||||
async function handleUnbind() {
|
||||
if (!confirm('确定要解除 OpenList 绑定吗?')) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await unbindOpenList()
|
||||
if (result?.code === 200) {
|
||||
setBindStatus('success', '已解除绑定')
|
||||
boundInfo.value = null
|
||||
resetForm()
|
||||
setTimeout(() => {
|
||||
setBindStatus(null, '')
|
||||
}, 3000)
|
||||
} else {
|
||||
setBindStatus('error', result?.message || '解除绑定失败')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('解除绑定失败:', err)
|
||||
setBindStatus('error', err?.message || '网络错误')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
function resetForm() {
|
||||
formData.url = ''
|
||||
formData.username = ''
|
||||
formData.password = ''
|
||||
setBindStatus(null, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置绑定状态提示
|
||||
*/
|
||||
function setBindStatus(type, message) {
|
||||
if (!type) {
|
||||
bindStatus.value = null
|
||||
return
|
||||
}
|
||||
|
||||
const icons = {
|
||||
success: '✅',
|
||||
error: '❌',
|
||||
warning: '⚠️',
|
||||
info: 'ℹ️',
|
||||
}
|
||||
|
||||
bindStatus.value = {
|
||||
type,
|
||||
message,
|
||||
icon: icons[type] || '',
|
||||
}
|
||||
|
||||
// 3秒后自动清除
|
||||
if (type !== 'success') {
|
||||
setTimeout(() => {
|
||||
bindStatus.value = null
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载已绑定信息
|
||||
onMounted(() => {
|
||||
loadBoundInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-view {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.settings-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.settings-header p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.settings-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.settings-card__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 24px 0;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.settings-card__icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-label__icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15);
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.form-help {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 密码输入框 */
|
||||
.password-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.password-input-wrapper .form-input {
|
||||
padding-right: 44px;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 按钮组 */
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 28px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.btn-bind {
|
||||
flex: 1;
|
||||
padding: 12px 24px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #409eff, #337ecc);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.btn-bind:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #66b1ff, #409eff);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-bind:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-bind:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-bind--loading {
|
||||
background: linear-gradient(135deg, #909399, #69696d);
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
padding: 12px 24px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.btn-reset:hover:not(:disabled) {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.btn-reset:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 状态提示 */
|
||||
.bind-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-top: 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.bind-status--success {
|
||||
background: #f0f9ff;
|
||||
border: 1px solid #b3e19c;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.bind-status--error {
|
||||
background: #fef0f0;
|
||||
border: 1px solid #fbc4c4;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.bind-status__icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 已绑定信息 */
|
||||
.bound-info {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
background: #f0f9ff;
|
||||
border: 1px solid #b3e19c;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bound-info__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #67c23a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.bound-info__details {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.bound-info__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.bound-info__label {
|
||||
color: #999;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.bound-info__value {
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.btn-unbind {
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
color: #f56c6c;
|
||||
background: #fff;
|
||||
border: 1px solid #fbc4c4;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-unbind:hover {
|
||||
background: #fef0f0;
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.settings-header h2 {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.settings-header p {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background: #2a2a2a;
|
||||
border-color: #3a3a3a;
|
||||
}
|
||||
|
||||
.settings-card__title {
|
||||
color: #e5e5e5;
|
||||
border-bottom-color: #3a3a3a;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
background: #1a1a1a;
|
||||
border-color: #3a3a3a;
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.form-input:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
color: #ccc;
|
||||
background: #333;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
.btn-reset:hover:not(:disabled) {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
background: #1a2a3a;
|
||||
}
|
||||
|
||||
.bound-info {
|
||||
background: #1a2a3a;
|
||||
border-color: #2a4a5a;
|
||||
}
|
||||
|
||||
.bound-info__item {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.bound-info__value {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.btn-unbind {
|
||||
color: #f56c6c;
|
||||
background: #2a1a1a;
|
||||
border-color: #4a2a2a;
|
||||
}
|
||||
|
||||
.btn-unbind:hover {
|
||||
background: #3a1a1a;
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 600px) {
|
||||
.settings-view {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-bind,
|
||||
.btn-reset {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -886,7 +886,7 @@ canvas {
|
||||
|
||||
.hero-carousel {
|
||||
position: relative;
|
||||
padding-bottom: 6px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* ==================== 主海报横幅 ==================== */
|
||||
@ -1625,6 +1625,42 @@ canvas {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 刮削状态面板 - 新增字段样式 */
|
||||
.scrape-status-overview {
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(99, 102, 241, 0.05);
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #6366f1;
|
||||
}
|
||||
|
||||
.scrape-status-label {
|
||||
font-weight: 700;
|
||||
color: #6366f1;
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.scrape-status-value {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.scrape-status-meta {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.scrape-status-meta span {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.folder-tree-actions__btn {
|
||||
padding: 10px 20px;
|
||||
border: 2px dashed var(--line);
|
||||
@ -2285,6 +2321,91 @@ canvas {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* 用户详情项 */
|
||||
.detail-item--user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(249, 250, 251, 0.8);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 消息列表容器 */
|
||||
.detail-item__messages {
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(99, 102, 241, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 单条消息项 */
|
||||
.message-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.message-item:hover {
|
||||
background: rgba(249, 250, 251, 0.5);
|
||||
}
|
||||
|
||||
/* 消息状态指示器 */
|
||||
.message-item__indicator {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 成功消息 */
|
||||
.message-item__indicator.success {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
/* 失败消息 */
|
||||
.message-item__indicator.failed {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
/* 跳过/未知消息 */
|
||||
.message-item__indicator.skipped {
|
||||
background: rgba(100, 116, 139, 0.1);
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 消息文本 */
|
||||
.message-item__text {
|
||||
flex: 1;
|
||||
color: #374151;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.message-item__text:hover {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
@ -3507,7 +3628,6 @@ canvas {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.scrape-status-poster img,
|
||||
@ -3518,8 +3638,22 @@ canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 海报图 - 竖版展示(标准海报比例 2:3) */
|
||||
.scrape-status-poster {
|
||||
max-height: 400px;
|
||||
max-height: 500px;
|
||||
aspect-ratio: 2 / 3;
|
||||
max-width: 280px;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
.scrape-status-poster img {
|
||||
aspect-ratio: 2 / 3;
|
||||
}
|
||||
|
||||
/* 背景图 - 横版展示(宽屏比例 16:9) */
|
||||
.scrape-status-backdrop {
|
||||
max-height: 320px;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.scrape-status-meta {
|
||||
@ -4077,8 +4211,9 @@ body.dark-mode .hero-sub__thumb {
|
||||
}
|
||||
|
||||
body.dark-mode .hero-sub__card.active .hero-sub__thumb {
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.4), 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
/* 移除选中状态下的蓝色光圈 */
|
||||
border-color: rgba(51, 65, 85, 0.5);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .hero-sub__title {
|
||||
|
||||
@ -8,7 +8,8 @@ export function filterRows(rows, keyword) {
|
||||
return rows.filter((item) => {
|
||||
const merged = [
|
||||
item.title,
|
||||
item.subtitle,
|
||||
item.directors,
|
||||
item.casts,
|
||||
item.accountName,
|
||||
item.owner,
|
||||
item.sheetName,
|
||||
@ -135,7 +136,6 @@ export function createRecord(record = {}, fallbackSheetName = '') {
|
||||
return {
|
||||
id: record.id || `record-${Date.now()}`,
|
||||
title: normalizeText(record.title),
|
||||
subtitle: normalizeText(record.subtitle),
|
||||
accountName: normalizeText(record.accountName),
|
||||
owner: normalizeText(record.owner),
|
||||
updatedAt: normalizeText(record.updatedAt),
|
||||
@ -149,6 +149,8 @@ export function createRecord(record = {}, fallbackSheetName = '') {
|
||||
originalName: normalizeText(record.originalName),
|
||||
description: normalizeText(record.description),
|
||||
category: normalizeText(record.category),
|
||||
directors: normalizeText(record.directors),
|
||||
casts: normalizeText(record.casts),
|
||||
driveId: record.driveId ?? null,
|
||||
folderId: record.folderId ?? null,
|
||||
minioUrl: normalizeText(record.minioUrl),
|
||||
@ -163,7 +165,6 @@ export function mapMovieToRecord(movie = {}, fallbackSheetName = '') {
|
||||
return createRecord({
|
||||
id: movie.id,
|
||||
title: movie.title,
|
||||
subtitle: movie.subtitle || '',
|
||||
accountName: movie.resourceLibrary || '',
|
||||
owner: movie.maintainer || '',
|
||||
updatedAt: movie.updateTime || '',
|
||||
@ -176,6 +177,8 @@ export function mapMovieToRecord(movie = {}, fallbackSheetName = '') {
|
||||
type: 'file',
|
||||
category: movie.category,
|
||||
description: movie.description || '',
|
||||
directors: movie.directors || '',
|
||||
casts: movie.casts || '',
|
||||
}, fallbackSheetName)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user