待看名单增加导入导出,修复待看名单比较受baseurl影响的问题

This commit is contained in:
2025-10-04 07:06:29 +08:00
parent 71c5f7ed63
commit 61159a88ef
3 changed files with 201 additions and 48 deletions
+4 -26
View File
@@ -198,33 +198,11 @@ const getAddButtonTitle = () => {
const isCurrentUrl = (url: string) => { const isCurrentUrl = (url: string) => {
const currentUrl = currentPageUrl.value; const currentUrl = currentPageUrl.value;
// 直接比较完整URL // 使用store中的路径提取方法进行比较
if (currentUrl === url) { const currentPath = watchlistStore.extractUrlPath(currentUrl);
return true; const itemPath = watchlistStore.extractUrlPath(url);
}
// 比较路径部分 return currentPath === itemPath;
try {
const currentUrlObj = new URL(currentUrl);
const currentPath = currentUrlObj.pathname + currentUrlObj.search;
// 如果是完整URL,提取路径部分比较
if (url.startsWith('http://') || url.startsWith('https://')) {
const urlObj = new URL(url);
const urlPath = urlObj.pathname + urlObj.search;
return currentPath === urlPath;
}
// 如果是相对路径,直接比较
let relativePath = url;
if (!relativePath.startsWith('/')) {
relativePath = '/' + relativePath;
}
return currentPath === relativePath;
} catch {
return false;
}
}; };
// 解析批量URL // 解析批量URL
@@ -4,6 +4,19 @@
<h3>待看名单</h3> <h3>待看名单</h3>
<div class="header-actions"> <div class="header-actions">
<span class="item-count-text">{{ itemCount }} </span> <span class="item-count-text">{{ itemCount }} </span>
<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>
<input
ref="fileInput"
type="file"
accept=".json"
style="display: none"
@change="handleFileImport"
/>
<button @click="$emit('showAddModal')" class="add-btn" title="手动添加"> <button @click="$emit('showAddModal')" class="add-btn" title="手动添加">
<SvgIcon name="add" class="add-icon" /> <SvgIcon name="add" class="add-icon" />
</button> </button>
@@ -27,6 +40,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue';
import { useWatchlistStore } from '@/stores/watchlist';
import WatchlistControls from './WatchlistControls.vue'; import WatchlistControls from './WatchlistControls.vue';
import WatchlistContent from './WatchlistContent.vue'; import WatchlistContent from './WatchlistContent.vue';
import type { WatchlistItem } from '@/services/watchlist'; import type { WatchlistItem } from '@/services/watchlist';
@@ -58,6 +73,46 @@ defineEmits<{
edit: [item: WatchlistItem]; edit: [item: WatchlistItem];
delete: [id: string]; delete: [id: string];
}>(); }>();
// 获取watchlist store实例
const watchlistStore = useWatchlistStore();
// 文件输入引用
const fileInput = ref<HTMLInputElement>();
// 导出功能
const exportWatchlist = () => {
watchlistStore.exportWatchlist();
};
// 触发导入文件选择
const triggerImport = () => {
fileInput.value?.click();
};
// 处理文件导入
const handleFileImport = async (event: Event) => {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (!file) return;
try {
const result = await watchlistStore.importWatchlist(file);
if (result.success) {
alert(result.message);
} else {
alert(`导入失败: ${result.message}`);
}
} catch (error) {
console.error('导入过程中发生错误:', error);
alert('导入过程中发生错误,请检查文件格式');
} finally {
// 清空文件输入
target.value = '';
}
};
</script> </script>
<style scoped> <style scoped>
@@ -120,7 +175,9 @@ defineEmits<{
} }
.add-btn, .add-btn,
.close-btn { .close-btn,
.export-btn,
.import-btn {
width: 28px; width: 28px;
height: 28px; height: 28px;
border: none; border: none;
@@ -139,13 +196,25 @@ defineEmits<{
color: var(--color-primary); color: var(--color-primary);
} }
.export-btn:hover {
background: var(--color-success-light);
color: var(--color-success);
}
.import-btn:hover {
background: var(--color-info-light);
color: var(--color-info);
}
.close-btn:hover { .close-btn:hover {
background: var(--color-danger-light); background: var(--color-danger-light);
color: var(--color-danger); color: var(--color-danger);
} }
.add-icon, .add-icon,
.close-icon { .close-icon,
.export-icon,
.import-icon {
width: 14px; width: 14px;
height: 14px; height: 14px;
} }
+124 -18
View File
@@ -104,34 +104,32 @@ export const useWatchlistStore = defineStore('watchlist', () => {
} }
}; };
// 检查URL是否已存在
const hasUrl = (url: string) => {
return items.value.some(item => item.url === url);
};
// 根据URL查找项目
const findByUrl = (url: string) => {
return items.value.find(item => item.url === url);
};
// 清除错误 // 清除错误
const clearError = () => { const clearError = () => {
error.value = null; error.value = null;
}; };
// 提取作者ID的工具函数 // 提取URL路径的工具函数(忽略baseURL)
const extractAuthorId = (url: string) => { const extractUrlPath = (url: string) => {
try { try {
let path = '';
// 处理完整URL // 处理完整URL
if (url.startsWith('http://') || url.startsWith('https://')) { if (url.startsWith('http://') || url.startsWith('https://')) {
const urlObj = new URL(url); const urlObj = new URL(url);
path = urlObj.pathname; return urlObj.pathname + urlObj.search + urlObj.hash;
} else { } else {
// 处理相对路径 // 处理相对路径
path = url.startsWith('/') ? url : '/' + url; return url.startsWith('/') ? url : '/' + url;
} }
} catch {
// 如果解析失败,返回原始URL
return url;
}
};
// 提取作者ID的工具函数
const extractAuthorId = (url: string) => {
try {
const path = extractUrlPath(url);
// 匹配 /artist/数字 的模式 // 匹配 /artist/数字 的模式
const match = path.match(/\/artist\/(\d+)/); const match = path.match(/\/artist\/(\d+)/);
@@ -141,14 +139,35 @@ export const useWatchlistStore = defineStore('watchlist', () => {
} }
}; };
// 检查URL是否已存在(只比较路径部分)
const hasUrl = (url: string) => {
const targetPath = extractUrlPath(url);
return items.value.some(item => {
const itemPath = extractUrlPath(item.url);
return itemPath === targetPath;
});
};
// 根据URL查找项目(只比较路径部分)
const findByUrl = (url: string) => {
const targetPath = extractUrlPath(url);
return items.value.find(item => {
const itemPath = extractUrlPath(item.url);
return itemPath === targetPath;
});
};
// 检查是否有相同作者但不同页面的项目 // 检查是否有相同作者但不同页面的项目
const findSameAuthor = (url: string) => { const findSameAuthor = (url: string) => {
const authorId = extractAuthorId(url); const authorId = extractAuthorId(url);
if (!authorId) return null; if (!authorId) return null;
const targetPath = extractUrlPath(url);
return items.value.find(item => { return items.value.find(item => {
const itemAuthorId = extractAuthorId(item.url); const itemAuthorId = extractAuthorId(item.url);
return itemAuthorId === authorId && item.url !== url; const itemPath = extractUrlPath(item.url);
return itemAuthorId === authorId && itemPath !== targetPath;
}); });
}; };
@@ -165,6 +184,88 @@ export const useWatchlistStore = defineStore('watchlist', () => {
}); });
}; };
// 导出待看名单数据
const exportWatchlist = () => {
const exportData = {
version: '1.0',
exportTime: new Date().toISOString(),
items: items.value.map(item => ({
id: item.id,
title: item.title,
url: item.url,
createdAt: item.createdAt,
updatedAt: item.updatedAt
}))
};
const dataStr = JSON.stringify(exportData, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const link = document.createElement('a');
link.href = URL.createObjectURL(dataBlob);
link.download = `watchlist-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
};
// 导入待看名单数据
const importWatchlist = async (file: File) => {
try {
const text = await file.text();
const importData = JSON.parse(text);
// 验证数据格式
if (!importData.items || !Array.isArray(importData.items)) {
throw new Error('无效的导入文件格式');
}
// 统计导入结果
let successCount = 0;
let skipCount = 0;
let errorCount = 0;
for (const item of importData.items) {
try {
// 检查是否已存在(使用路径比较)
if (hasUrl(item.url)) {
skipCount++;
continue;
}
// 添加项目
const success = await addItem({
url: item.url,
title: item.title
});
if (success) {
successCount++;
} else {
errorCount++;
}
} catch (err) {
console.error('导入项目失败:', item, err);
errorCount++;
}
}
return {
success: true,
message: `导入完成:成功 ${successCount} 项,跳过 ${skipCount} 项,失败 ${errorCount}`,
stats: { successCount, skipCount, errorCount }
};
} catch (err) {
console.error('导入失败:', err);
return {
success: false,
message: err instanceof Error ? err.message : '导入失败',
stats: { successCount: 0, skipCount: 0, errorCount: 0 }
};
}
};
return { return {
// 状态 // 状态
items, items,
@@ -185,6 +286,11 @@ export const useWatchlistStore = defineStore('watchlist', () => {
extractAuthorId, extractAuthorId,
findSameAuthor, findSameAuthor,
hasSameAuthor, hasSameAuthor,
findItemsByAuthor findItemsByAuthor,
// 导出导入功能
exportWatchlist,
importWatchlist,
// URL路径提取工具
extractUrlPath
}; };
}); });