待看名单增加覆盖功能

This commit is contained in:
2025-10-10 12:14:52 +08:00
parent d1082a8677
commit 4e34063373
5 changed files with 227 additions and 14 deletions
+31 -2
View File
@@ -56,11 +56,13 @@
:batch-urls="batchUrls"
:auto-generate-title="autoGenerateTitle"
:parsed-urls="parsedUrls"
:import-mode="importMode"
@update:mode="addMode = $event"
@update:title="addTitle = $event"
@update:url="addUrl = $event"
@update:batchUrls="batchUrls = $event"
@update:autoGenerateTitle="autoGenerateTitle = $event"
@update:importMode="importMode = $event"
@quickAdd="fillQuickAdd"
@save="saveAdd"
@cancel="cancelAdd"
@@ -92,6 +94,7 @@ const addUrl = ref('');
const addMode = ref<'single' | 'batch'>('single');
const batchUrls = ref('');
const autoGenerateTitle = ref(true);
const importMode = ref<'merge' | 'overwrite'>('merge');
// 搜索和排序
const searchQuery = ref('');
@@ -401,7 +404,29 @@ const saveAdd = async () => {
}
} else {
// 批量添加模式
const urlsToAdd = parsedUrls.value.filter(item => !item.isDuplicate);
let urlsToAdd = parsedUrls.value;
if (importMode.value === 'merge') {
// 重合模式:只添加不重复的URL
urlsToAdd = urlsToAdd.filter(item => !item.isDuplicate);
} else if (importMode.value === 'overwrite') {
// 覆盖模式:删除所有现有项目,然后添加所有新项目
if (urlsToAdd.length === 0) return;
// 确认覆盖操作
if (!confirm('覆盖模式将删除所有现有的待看项目并导入新的项目。确定要继续吗?')) {
return;
}
// 删除所有现有项目
const allItems = items.value;
for (const item of allItems) {
await watchlistStore.deleteItem(item.id);
}
console.log(`已删除 ${allItems.length} 个现有项目`);
}
if (urlsToAdd.length === 0) return;
// 依次添加每个URL
@@ -422,7 +447,11 @@ const saveAdd = async () => {
if (successCount > 0) {
cancelAdd();
console.log(`成功添加 ${successCount} 个项目`);
if (importMode.value === 'overwrite') {
console.log(`覆盖导入完成:成功添加 ${successCount} 个项目`);
} else {
console.log(`重合导入完成:成功添加 ${successCount} 个项目`);
}
}
}
};
@@ -52,6 +52,35 @@
<!-- 批量添加模式 -->
<template v-else>
<!-- 导入模式选择 -->
<div class="form-group">
<label>导入模式</label>
<div class="import-mode-selector">
<button @click="$emit('update:importMode', 'merge')"
:class="['import-mode-btn', { active: importMode === 'merge' }]"
type="button">
<div class="mode-icon">🔄</div>
<div class="mode-info">
<div class="mode-title">重合模式</div>
<div class="mode-desc">跳过已存在的项目只添加新项目</div>
</div>
</button>
<button @click="$emit('update:importMode', 'overwrite')"
:class="['import-mode-btn', { active: importMode === 'overwrite' }]"
type="button">
<div class="mode-icon"></div>
<div class="mode-info">
<div class="mode-title">覆盖模式</div>
<div class="mode-desc">清空现有数据重新导入所有项目</div>
</div>
</button>
</div>
<small class="form-help">
<strong>重合模式</strong>保留现有数据只添加新的项目推荐<br>
<strong>覆盖模式</strong>删除所有现有数据重新导入谨慎使用
</small>
</div>
<div class="form-group">
<label>批量URL列表</label>
<textarea :value="batchUrls"
@@ -83,7 +112,8 @@ http://localhost:3001/artist/103047332
<div class="preview-list">
<div v-for="(item, index) in parsedUrls" :key="index" class="preview-item">
<div class="preview-url">{{ item.path }}</div>
<div v-if="item.isDuplicate" class="preview-status duplicate">已存在</div>
<div v-if="importMode === 'overwrite'" class="preview-status overwrite">将导入</div>
<div v-else-if="item.isDuplicate" class="preview-status duplicate">已存在</div>
<div v-else class="preview-status new">新增</div>
</div>
</div>
@@ -116,6 +146,7 @@ interface Props {
batchUrls: string;
autoGenerateTitle: boolean;
parsedUrls: ParsedUrl[];
importMode: 'merge' | 'overwrite';
}
defineProps<Props>();
@@ -128,6 +159,7 @@ const emit = defineEmits<{
'update:url': [value: string];
'update:batchUrls': [value: string];
'update:autoGenerateTitle': [value: boolean];
'update:importMode': [value: 'merge' | 'overwrite'];
quickAdd: [path: string, title: string];
}>();
@@ -378,6 +410,64 @@ const handleSave = () => {
color: white;
}
.preview-status.overwrite {
background: var(--color-primary);
color: white;
}
/* 导入模式选择器样式 */
.import-mode-selector {
display: flex;
flex-direction: column;
gap: 12px;
}
.import-mode-btn {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border: 2px solid var(--color-border);
border-radius: 8px;
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
}
.import-mode-btn:hover {
background: var(--color-bg-tertiary);
border-color: var(--color-primary-light);
}
.import-mode-btn.active {
background: var(--color-primary-light);
border-color: var(--color-primary);
color: var(--color-primary);
}
.mode-icon {
font-size: 20px;
flex-shrink: 0;
}
.mode-info {
flex: 1;
}
.mode-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 2px;
}
.mode-desc {
font-size: 12px;
opacity: 0.8;
line-height: 1.3;
}
.modal-actions {
display: flex;
gap: 12px;
@@ -7,9 +7,27 @@
<button @click="exportWatchlist" class="export-btn" title="导出待看名单">
<SvgIcon name="download" class="export-icon" />
</button>
<button @click="triggerImport" class="import-btn" title="导入待看名单">
<SvgIcon name="upload" class="import-icon" />
</button>
<div class="import-section">
<button @click="triggerImport" class="import-btn" title="导入待看名单">
<SvgIcon name="upload" class="import-icon" />
</button>
<div class="import-mode-selector">
<button
@click="importMode = 'merge'"
:class="['mode-btn', { active: importMode === 'merge' }]"
title="重合模式:跳过已存在的项目"
>
重合
</button>
<button
@click="importMode = 'overwrite'"
:class="['mode-btn', { active: importMode === 'overwrite' }]"
title="覆盖模式:删除所有现有项目后导入"
>
覆盖
</button>
</div>
</div>
<input
ref="fileInput"
type="file"
@@ -77,6 +95,9 @@ defineEmits<{
// 获取watchlist store实例
const watchlistStore = useWatchlistStore();
// 导入模式状态
const importMode = ref<'merge' | 'overwrite'>('merge');
// 文件输入引用
const fileInput = ref<HTMLInputElement>();
@@ -97,8 +118,17 @@ const handleFileImport = async (event: Event) => {
if (!file) return;
// 如果是覆盖模式,先确认
if (importMode.value === 'overwrite') {
const confirmed = confirm('覆盖模式将删除所有现有的待看项目,确定要继续吗?');
if (!confirmed) {
target.value = '';
return;
}
}
try {
const result = await watchlistStore.importWatchlist(file);
const result = await watchlistStore.importWatchlist(file, importMode.value);
if (result.success) {
alert(result.message);
@@ -219,6 +249,49 @@ const handleFileImport = async (event: Event) => {
height: 14px;
}
/* 导入区域样式 */
.import-section {
display: flex;
align-items: center;
gap: 8px;
position: relative;
}
.import-mode-selector {
display: flex;
background: var(--color-bg-tertiary);
border-radius: 4px;
padding: 2px;
gap: 2px;
}
.mode-btn {
padding: 4px 8px;
font-size: 11px;
border: none;
border-radius: 3px;
background: transparent;
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.mode-btn:hover {
background: var(--color-bg-hover);
color: var(--color-text-primary);
}
.mode-btn.active {
background: var(--color-primary);
color: white;
font-weight: 500;
}
.mode-btn.active:hover {
background: var(--color-primary-dark);
}
/* 响应式设计 */
@media (max-width: 768px) {
.watchlist-panel {
+27 -6
View File
@@ -211,7 +211,7 @@ export const useWatchlistStore = defineStore('watchlist', () => {
};
// 导入待看名单数据
const importWatchlist = async (file: File) => {
const importWatchlist = async (file: File, importMode: 'merge' | 'overwrite' = 'merge') => {
try {
const text = await file.text();
const importData = JSON.parse(text);
@@ -225,11 +225,25 @@ export const useWatchlistStore = defineStore('watchlist', () => {
let successCount = 0;
let skipCount = 0;
let errorCount = 0;
let deletedCount = 0;
// 如果是覆盖模式,先删除所有现有项目
if (importMode === 'overwrite') {
const allItems = items.value;
for (const item of allItems) {
try {
await deleteItem(item.id);
deletedCount++;
} catch (err) {
console.error('删除项目失败:', item, err);
}
}
}
for (const item of importData.items) {
try {
// 检查是否已存在(使用路径比较)
if (hasUrl(item.url)) {
// 在重合模式下检查是否已存在
if (importMode === 'merge' && hasUrl(item.url)) {
skipCount++;
continue;
}
@@ -251,17 +265,24 @@ export const useWatchlistStore = defineStore('watchlist', () => {
}
}
let message = '';
if (importMode === 'overwrite') {
message = `覆盖导入完成:删除 ${deletedCount} 项,成功添加 ${successCount} 项,失败 ${errorCount}`;
} else {
message = `重合导入完成:成功 ${successCount} 项,跳过 ${skipCount} 项,失败 ${errorCount}`;
}
return {
success: true,
message: `导入完成:成功 ${successCount} 项,跳过 ${skipCount} 项,失败 ${errorCount}`,
stats: { successCount, skipCount, errorCount }
message,
stats: { successCount, skipCount, errorCount, deletedCount }
};
} catch (err) {
console.error('导入失败:', err);
return {
success: false,
message: err instanceof Error ? err.message : '导入失败',
stats: { successCount: 0, skipCount: 0, errorCount: 0 }
stats: { successCount: 0, skipCount: 0, errorCount: 0, deletedCount: 0 }
};
}
};