You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
804 lines
28 KiB
804 lines
28 KiB
// 收藏网站管理类
|
|
class BookmarkManager {
|
|
constructor() {
|
|
this.currentView = 'grid';
|
|
this.currentPage = 1;
|
|
this.itemsPerPage = 12;
|
|
this.currentCategory = null;
|
|
this.currentTag = null;
|
|
this.searchQuery = '';
|
|
this.bookmarks = [];
|
|
this.categories = [];
|
|
this.tags = [];
|
|
this.editingBookmark = null;
|
|
this.totalBookmarks = 0;
|
|
this.pagination = null;
|
|
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
this.bindEvents();
|
|
await this.loadInitialData();
|
|
this.renderUI();
|
|
}
|
|
|
|
bindEvents() {
|
|
// 搜索功能
|
|
document.getElementById('searchBtn').addEventListener('click', () => this.handleSearch());
|
|
document.getElementById('searchInput').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') this.handleSearch();
|
|
});
|
|
|
|
// 添加收藏
|
|
document.getElementById('addBookmarkBtn').addEventListener('click', () => this.showAddModal());
|
|
|
|
// 视图切换
|
|
document.getElementById('gridViewBtn').addEventListener('click', () => this.switchView('grid'));
|
|
document.getElementById('listViewBtn').addEventListener('click', () => this.switchView('list'));
|
|
|
|
// 模态框
|
|
document.getElementById('closeModal').addEventListener('click', () => this.closeModal());
|
|
document.getElementById('bookmarkForm').addEventListener('submit', (e) => this.handleFormSubmit(e));
|
|
document.getElementById('addLinkBtn').addEventListener('click', () => this.addExtraLink());
|
|
|
|
// 点击模态框外部关闭
|
|
document.querySelector('.modal-overlay').addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('modal-overlay')) {
|
|
this.closeModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
async loadInitialData() {
|
|
try {
|
|
// 调用后端API加载数据
|
|
await this.loadCategories();
|
|
await this.loadTags();
|
|
await this.loadBookmarks();
|
|
} catch (error) {
|
|
console.error('加载数据失败:', error);
|
|
this.showError('加载数据失败,请刷新页面重试');
|
|
}
|
|
}
|
|
|
|
async loadCategories() {
|
|
try {
|
|
const response = await fetch('/categories', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取分类失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
this.categories = result.data;
|
|
} else {
|
|
throw new Error(result.message || '获取分类失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('加载分类失败:', error);
|
|
// 如果API调用失败,使用默认分类
|
|
this.categories = [
|
|
{ id: 1, name: '技术开发', color: '#3B82F6', icon: 'code' },
|
|
{ id: 2, name: '学习资源', color: '#10B981', icon: 'graduation-cap' },
|
|
{ id: 3, name: '设计工具', color: '#F59E0B', icon: 'palette' },
|
|
{ id: 4, name: '效率工具', color: '#8B5CF6', icon: 'zap' },
|
|
{ id: 5, name: '娱乐休闲', color: '#EC4899', icon: 'heart' }
|
|
];
|
|
}
|
|
}
|
|
|
|
async loadTags() {
|
|
try {
|
|
const response = await fetch('/tags/popular', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取标签失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
this.tags = result.data;
|
|
} else {
|
|
throw new Error(result.message || '获取标签失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('加载标签失败:', error);
|
|
// 如果API调用失败,使用默认标签
|
|
this.tags = [
|
|
{ id: 1, name: 'JavaScript', color: '#F7DF1E' },
|
|
{ id: 2, name: 'React', color: '#61DAFB' },
|
|
{ id: 3, name: 'Node.js', color: '#339933' },
|
|
{ id: 4, name: 'CSS', color: '#1572B6' },
|
|
{ id: 5, name: '设计灵感', color: '#FF6B6B' },
|
|
{ id: 6, name: '免费资源', color: '#4ECDC4' },
|
|
{ id: 7, name: 'API', color: '#45B7D1' },
|
|
{ id: 8, name: '数据库', color: '#FFA500' }
|
|
];
|
|
}
|
|
}
|
|
|
|
async loadBookmarks() {
|
|
try {
|
|
const params = new URLSearchParams({
|
|
page: this.currentPage,
|
|
limit: this.itemsPerPage
|
|
});
|
|
|
|
if (this.currentCategory) {
|
|
params.append('category_id', this.currentCategory);
|
|
}
|
|
if (this.currentTag) {
|
|
params.append('tag_id', this.currentTag);
|
|
}
|
|
if (this.searchQuery) {
|
|
params.append('search', this.searchQuery);
|
|
}
|
|
|
|
const response = await fetch(`/bookmarks?${params}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取收藏失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
this.bookmarks = result.data;
|
|
this.totalBookmarks = result.total;
|
|
this.pagination = result.pagination;
|
|
} else {
|
|
throw new Error(result.message || '获取收藏失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('加载收藏失败:', error);
|
|
// 如果API调用失败,使用默认数据
|
|
this.bookmarks = [
|
|
{
|
|
id: 1,
|
|
title: 'MDN Web Docs',
|
|
description: 'Mozilla开发者网络,提供Web技术文档和教程',
|
|
url: 'https://developer.mozilla.org/',
|
|
favicon: 'https://developer.mozilla.org/favicon-48x48.cbbd161b.png',
|
|
category_id: 1,
|
|
category_name: '技术开发',
|
|
category_color: '#3B82F6',
|
|
is_favorite: true,
|
|
click_count: 15,
|
|
tags: ['JavaScript', 'Node.js', 'CSS'],
|
|
links: [
|
|
{ title: 'JavaScript 教程', url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript', type: 'tutorial' },
|
|
{ title: 'CSS 参考', url: 'https://developer.mozilla.org/zh-CN/docs/Web/CSS', type: 'reference' }
|
|
],
|
|
created_at: '2024-01-15T10:30:00Z'
|
|
}
|
|
];
|
|
}
|
|
}
|
|
|
|
renderUI() {
|
|
this.renderCategories();
|
|
this.renderTags();
|
|
this.renderBookmarks();
|
|
this.renderPagination();
|
|
}
|
|
|
|
renderCategories() {
|
|
const categoryList = document.getElementById('categoryList');
|
|
const allCategories = [{ id: null, name: '全部', color: '#6B7280', icon: 'grid' }, ...this.categories];
|
|
|
|
categoryList.innerHTML = allCategories.map(category => `
|
|
<div class="category-item ${category.id === this.currentCategory ? 'active' : ''}"
|
|
data-id="${category.id}"
|
|
style="border-color: ${category.color}">
|
|
${category.name}
|
|
</div>
|
|
`).join('');
|
|
|
|
// 绑定分类点击事件
|
|
categoryList.querySelectorAll('.category-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
const categoryId = item.dataset.id ? parseInt(item.dataset.id) : null;
|
|
this.filterByCategory(categoryId);
|
|
});
|
|
});
|
|
}
|
|
|
|
renderTags() {
|
|
const tagList = document.getElementById('tagList');
|
|
const allTags = [{ id: null, name: '全部', color: '#6B7280' }, ...this.tags];
|
|
|
|
tagList.innerHTML = allTags.map(tag => `
|
|
<div class="tag-item ${tag.id === this.currentTag ? 'active' : ''}"
|
|
data-id="${tag.id}"
|
|
style="border-color: ${tag.color}">
|
|
${tag.name}
|
|
</div>
|
|
`).join('');
|
|
|
|
// 绑定标签点击事件
|
|
tagList.querySelectorAll('.tag-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
const tagId = item.dataset.id ? parseInt(item.dataset.id) : null;
|
|
this.filterByTag(tagId);
|
|
});
|
|
});
|
|
}
|
|
|
|
renderBookmarks() {
|
|
if (this.bookmarks.length === 0) {
|
|
this.showEmptyState();
|
|
return;
|
|
}
|
|
|
|
if (this.currentView === 'grid') {
|
|
this.renderGridView(this.bookmarks);
|
|
} else {
|
|
this.renderListView(this.bookmarks);
|
|
}
|
|
}
|
|
|
|
renderGridView(bookmarks) {
|
|
const gridContainer = document.getElementById('bookmarksGrid');
|
|
const listContainer = document.getElementById('bookmarksList');
|
|
|
|
gridContainer.style.display = 'grid';
|
|
listContainer.style.display = 'none';
|
|
|
|
gridContainer.innerHTML = bookmarks.map(bookmark => this.createBookmarkCard(bookmark)).join('');
|
|
this.bindBookmarkEvents();
|
|
}
|
|
|
|
renderListView(bookmarks) {
|
|
const gridContainer = document.getElementById('bookmarksGrid');
|
|
const listContainer = document.getElementById('bookmarksList');
|
|
|
|
gridContainer.style.display = 'none';
|
|
listContainer.style.display = 'block';
|
|
|
|
listContainer.innerHTML = bookmarks.map(bookmark => this.createBookmarkListItem(bookmark)).join('');
|
|
this.bindBookmarkEvents();
|
|
}
|
|
|
|
createBookmarkCard(bookmark) {
|
|
const tagsHtml = bookmark.tags.map(tag =>
|
|
`<span class="bookmark-tag">${tag}</span>`
|
|
).join('');
|
|
|
|
const linksHtml = bookmark.links.length > 0 ?
|
|
`<div class="bookmark-links">
|
|
<small>额外链接: ${bookmark.links.length}个</small>
|
|
</div>` : '';
|
|
|
|
return `
|
|
<div class="bookmark-card" data-id="${bookmark.id}">
|
|
<div class="bookmark-header">
|
|
<img src="${bookmark.favicon}" alt="favicon" class="bookmark-favicon"
|
|
onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 32 32%22><rect width=%2232%22 height=%2232%22 fill=%22%23666%22 rx=%224%22/></svg>'">
|
|
<h3 class="bookmark-title">${bookmark.title}</h3>
|
|
<button class="bookmark-favorite ${bookmark.is_favorite ? 'active' : ''}"
|
|
data-id="${bookmark.id}" title="特别收藏">
|
|
${bookmark.is_favorite ? '★' : '☆'}
|
|
</button>
|
|
</div>
|
|
|
|
<p class="bookmark-description">${bookmark.description}</p>
|
|
|
|
<div class="bookmark-meta">
|
|
<span class="bookmark-category" style="background: ${bookmark.category_color}20; color: ${bookmark.category_color}">
|
|
${bookmark.category_name}
|
|
</span>
|
|
<div class="bookmark-stats">
|
|
<span>👁 ${bookmark.click_count}</span>
|
|
<span>📅 ${this.formatDate(bookmark.created_at)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
${tagsHtml ? `<div class="bookmark-tags">${tagsHtml}</div>` : ''}
|
|
${linksHtml}
|
|
|
|
<div class="bookmark-actions">
|
|
<button class="bookmark-btn primary" onclick="window.open('${bookmark.url}', '_blank')">
|
|
访问网站
|
|
</button>
|
|
<button class="bookmark-btn" onclick="bookmarkManager.editBookmark(${bookmark.id})">
|
|
编辑
|
|
</button>
|
|
<button class="bookmark-btn" onclick="bookmarkManager.deleteBookmark(${bookmark.id})">
|
|
删除
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
createBookmarkListItem(bookmark) {
|
|
const tagsHtml = bookmark.tags.map(tag =>
|
|
`<span class="bookmark-tag">${tag}</span>`
|
|
).join('');
|
|
|
|
return `
|
|
<div class="bookmark-list-item" data-id="${bookmark.id}">
|
|
<img src="${bookmark.favicon}" alt="favicon" class="bookmark-list-favicon"
|
|
onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22><rect width=%2224%22 height=%2224%22 fill=%22%23666%22 rx=%223%22/></svg>'">
|
|
|
|
<div class="bookmark-list-content">
|
|
<h4 class="bookmark-list-title">${bookmark.title}</h4>
|
|
<p class="bookmark-list-description">${bookmark.description}</p>
|
|
${tagsHtml ? `<div class="bookmark-tags">${tagsHtml}</div>` : ''}
|
|
</div>
|
|
|
|
<div class="bookmark-list-meta">
|
|
<span class="bookmark-category" style="background: ${bookmark.category_color}20; color: ${bookmark.category_color}">
|
|
${bookmark.category_name}
|
|
</span>
|
|
<span>👁 ${bookmark.click_count}</span>
|
|
<button class="bookmark-btn" onclick="bookmarkManager.editBookmark(${bookmark.id})">编辑</button>
|
|
<button class="bookmark-btn" onclick="bookmarkManager.deleteBookmark(${bookmark.id})">删除</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
bindBookmarkEvents() {
|
|
// 绑定收藏按钮事件
|
|
document.querySelectorAll('.bookmark-favorite').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const bookmarkId = parseInt(btn.dataset.id);
|
|
this.toggleFavorite(bookmarkId);
|
|
});
|
|
});
|
|
}
|
|
|
|
renderPagination() {
|
|
if (!this.pagination || this.pagination.totalPages <= 1) {
|
|
document.getElementById('pagination').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
let paginationHtml = '';
|
|
|
|
// 上一页
|
|
paginationHtml += `
|
|
<button class="pagination-btn" ${!this.pagination.hasPrev ? 'disabled' : ''}
|
|
onclick="bookmarkManager.goToPage(${this.currentPage - 1})">
|
|
上一页
|
|
</button>
|
|
`;
|
|
|
|
// 页码
|
|
for (let i = 1; i <= this.pagination.totalPages; i++) {
|
|
if (i === 1 || i === this.pagination.totalPages || (i >= this.currentPage - 2 && i <= this.currentPage + 2)) {
|
|
paginationHtml += `
|
|
<button class="pagination-btn ${i === this.currentPage ? 'active' : ''}"
|
|
onclick="bookmarkManager.goToPage(${i})">
|
|
${i}
|
|
</button>
|
|
`;
|
|
} else if (i === this.currentPage - 3 || i === this.currentPage + 3) {
|
|
paginationHtml += '<span class="pagination-btn">...</span>';
|
|
}
|
|
}
|
|
|
|
// 下一页
|
|
paginationHtml += `
|
|
<button class="pagination-btn" ${!this.pagination.hasNext ? 'disabled' : ''}
|
|
onclick="bookmarkManager.goToPage(${this.currentPage + 1})">
|
|
下一页
|
|
</button>
|
|
`;
|
|
|
|
document.getElementById('pagination').innerHTML = paginationHtml;
|
|
}
|
|
|
|
getFilteredBookmarks() {
|
|
// 现在数据直接从后端获取,已经过滤过了
|
|
return this.bookmarks;
|
|
}
|
|
|
|
async handleSearch() {
|
|
this.searchQuery = document.getElementById('searchInput').value.trim();
|
|
this.currentPage = 1;
|
|
await this.loadBookmarks();
|
|
this.renderUI();
|
|
}
|
|
|
|
async filterByCategory(categoryId) {
|
|
this.currentCategory = categoryId;
|
|
this.currentPage = 1;
|
|
await this.loadBookmarks();
|
|
this.renderUI();
|
|
}
|
|
|
|
async filterByTag(tagId) {
|
|
this.currentTag = tagId;
|
|
this.currentPage = 1;
|
|
await this.loadBookmarks();
|
|
this.renderUI();
|
|
}
|
|
|
|
switchView(view) {
|
|
this.currentView = view;
|
|
|
|
// 更新按钮状态
|
|
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.view === view);
|
|
});
|
|
|
|
this.renderBookmarks();
|
|
}
|
|
|
|
async goToPage(page) {
|
|
this.currentPage = page;
|
|
await this.loadBookmarks();
|
|
this.renderUI();
|
|
|
|
// 滚动到顶部
|
|
document.querySelector('.bookmarks-section').scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
|
|
showAddModal() {
|
|
this.editingBookmark = null;
|
|
document.getElementById('modalTitle').textContent = '添加收藏';
|
|
document.getElementById('bookmarkForm').reset();
|
|
this.populateCategorySelect();
|
|
this.showModal();
|
|
}
|
|
|
|
editBookmark(bookmarkId) {
|
|
const bookmark = this.bookmarks.find(b => b.id === bookmarkId);
|
|
if (!bookmark) return;
|
|
|
|
this.editingBookmark = bookmark;
|
|
document.getElementById('modalTitle').textContent = '编辑收藏';
|
|
|
|
// 填充表单
|
|
document.getElementById('bookmarkTitle').value = bookmark.title;
|
|
document.getElementById('bookmarkUrl').value = bookmark.url;
|
|
document.getElementById('bookmarkDescription').value = bookmark.description;
|
|
document.getElementById('bookmarkCategory').value = bookmark.category_id;
|
|
document.getElementById('bookmarkTags').value = bookmark.tags.join(', ');
|
|
|
|
this.populateCategorySelect();
|
|
this.populateExtraLinks(bookmark.links);
|
|
this.showModal();
|
|
}
|
|
|
|
async handleFormSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
const bookmarkData = {
|
|
title: formData.get('bookmarkTitle'),
|
|
url: formData.get('bookmarkUrl'),
|
|
description: formData.get('bookmarkDescription'),
|
|
category_id: formData.get('bookmarkCategory') ? parseInt(formData.get('bookmarkCategory')) : null,
|
|
tags: formData.get('bookmarkTags').split(',').map(tag => tag.trim()).filter(tag => tag),
|
|
links: this.getExtraLinksData()
|
|
};
|
|
|
|
try {
|
|
if (this.editingBookmark) {
|
|
await this.updateBookmark(this.editingBookmark.id, bookmarkData);
|
|
} else {
|
|
await this.createBookmark(bookmarkData);
|
|
}
|
|
|
|
this.closeModal();
|
|
this.renderUI();
|
|
this.showSuccess(this.editingBookmark ? '收藏更新成功' : '收藏添加成功');
|
|
} catch (error) {
|
|
this.showError(error.message);
|
|
}
|
|
}
|
|
|
|
async createBookmark(data) {
|
|
try {
|
|
const response = await fetch('/bookmarks', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('创建收藏失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
const newBookmark = result.data;
|
|
this.bookmarks.unshift(newBookmark);
|
|
return newBookmark;
|
|
} else {
|
|
throw new Error(result.message || '创建收藏失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('创建收藏失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateBookmark(id, data) {
|
|
try {
|
|
const response = await fetch(`/bookmarks/${id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('更新收藏失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
const updatedBookmark = result.data;
|
|
const bookmarkIndex = this.bookmarks.findIndex(b => b.id === id);
|
|
if (bookmarkIndex !== -1) {
|
|
this.bookmarks[bookmarkIndex] = updatedBookmark;
|
|
}
|
|
return updatedBookmark;
|
|
} else {
|
|
throw new Error(result.message || '更新收藏失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('更新收藏失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async deleteBookmark(id) {
|
|
if (!confirm('确定要删除这个收藏吗?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/bookmarks/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('删除收藏失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
const bookmarkIndex = this.bookmarks.findIndex(b => b.id === id);
|
|
if (bookmarkIndex !== -1) {
|
|
this.bookmarks.splice(bookmarkIndex, 1);
|
|
}
|
|
this.renderUI();
|
|
this.showSuccess('收藏删除成功');
|
|
} else {
|
|
throw new Error(result.message || '删除收藏失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('删除收藏失败:', error);
|
|
this.showError(error.message);
|
|
}
|
|
}
|
|
|
|
async toggleFavorite(id) {
|
|
try {
|
|
const response = await fetch(`/bookmarks/${id}/favorite`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('切换收藏状态失败');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
const bookmark = this.bookmarks.find(b => b.id === id);
|
|
if (bookmark) {
|
|
bookmark.is_favorite = result.data.is_favorite;
|
|
}
|
|
this.renderUI();
|
|
this.showSuccess(result.message);
|
|
} else {
|
|
throw new Error(result.message || '切换收藏状态失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('切换收藏状态失败:', error);
|
|
this.showError(error.message);
|
|
}
|
|
}
|
|
|
|
populateCategorySelect() {
|
|
const select = document.getElementById('bookmarkCategory');
|
|
select.innerHTML = '<option value="">选择分类</option>' +
|
|
this.categories.map(category =>
|
|
`<option value="${category.id}">${category.name}</option>`
|
|
).join('');
|
|
}
|
|
|
|
populateExtraLinks(links) {
|
|
const container = document.getElementById('extraLinks');
|
|
container.innerHTML = '';
|
|
|
|
if (links && links.length > 0) {
|
|
links.forEach(link => this.addExtraLink(link.title, link.url));
|
|
} else {
|
|
this.addExtraLink();
|
|
}
|
|
}
|
|
|
|
addExtraLink(title = '', url = '') {
|
|
const container = document.getElementById('extraLinks');
|
|
const linkItem = document.createElement('div');
|
|
linkItem.className = 'extra-link-item';
|
|
linkItem.innerHTML = `
|
|
<input type="text" placeholder="链接标题" class="link-title" value="${title}">
|
|
<input type="url" placeholder="链接URL" class="link-url" value="${url}">
|
|
<button type="button" class="remove-link">删除</button>
|
|
`;
|
|
|
|
linkItem.querySelector('.remove-link').addEventListener('click', () => {
|
|
if (container.children.length > 1) {
|
|
container.removeChild(linkItem);
|
|
}
|
|
});
|
|
|
|
container.appendChild(linkItem);
|
|
}
|
|
|
|
getExtraLinksData() {
|
|
const links = [];
|
|
document.querySelectorAll('.extra-link-item').forEach(item => {
|
|
const title = item.querySelector('.link-title').value.trim();
|
|
const url = item.querySelector('.link-url').value.trim();
|
|
if (title && url) {
|
|
links.push({ title, url, type: 'link' });
|
|
}
|
|
});
|
|
return links;
|
|
}
|
|
|
|
showModal() {
|
|
document.getElementById('bookmarkModal').style.display = 'block';
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
closeModal() {
|
|
document.getElementById('bookmarkModal').style.display = 'none';
|
|
document.body.style.overflow = '';
|
|
this.editingBookmark = null;
|
|
}
|
|
|
|
showEmptyState() {
|
|
const gridContainer = document.getElementById('bookmarksGrid');
|
|
const listContainer = document.getElementById('bookmarksList');
|
|
|
|
gridContainer.style.display = 'none';
|
|
listContainer.style.display = 'none';
|
|
|
|
const emptyHtml = `
|
|
<div class="empty-state">
|
|
<h3>暂无收藏</h3>
|
|
<p>开始添加你的第一个收藏吧!</p>
|
|
<button class="empty-btn" onclick="bookmarkManager.showAddModal()">
|
|
添加收藏
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
if (this.currentView === 'grid') {
|
|
gridContainer.innerHTML = emptyHtml;
|
|
gridContainer.style.display = 'block';
|
|
} else {
|
|
listContainer.innerHTML = emptyHtml;
|
|
listContainer.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
showSuccess(message) {
|
|
this.showToast(message, 'success');
|
|
}
|
|
|
|
showError(message) {
|
|
this.showToast(message, 'error');
|
|
}
|
|
|
|
showToast(message, type = 'info') {
|
|
// 创建toast元素
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.textContent = message;
|
|
toast.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: ${type === 'success' ? '#10B981' : type === 'error' ? '#EF4444' : '#3B82F6'};
|
|
color: white;
|
|
padding: 12px 20px;
|
|
border-radius: 8px;
|
|
z-index: 10000;
|
|
animation: slideIn 0.3s ease;
|
|
`;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// 3秒后自动移除
|
|
setTimeout(() => {
|
|
toast.style.animation = 'slideOut 0.3s ease';
|
|
setTimeout(() => {
|
|
if (toast.parentNode) {
|
|
toast.parentNode.removeChild(toast);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
const now = new Date();
|
|
const diffTime = Math.abs(now - date);
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 1) return '今天';
|
|
if (diffDays === 2) return '昨天';
|
|
if (diffDays <= 7) return `${diffDays - 1}天前`;
|
|
if (diffDays <= 30) return `${Math.floor(diffDays / 7)}周前`;
|
|
if (diffDays <= 365) return `${Math.floor(diffDays / 30)}个月前`;
|
|
return `${Math.floor(diffDays / 365)}年前`;
|
|
}
|
|
}
|
|
|
|
// 添加CSS动画
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes slideIn {
|
|
from { transform: translateX(100%); opacity: 0; }
|
|
to { transform: translateX(0); opacity: 1; }
|
|
}
|
|
@keyframes slideOut {
|
|
from { transform: translateX(0); opacity: 1; }
|
|
to { transform: translateX(100%); opacity: 0; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
// 初始化收藏管理器
|
|
let bookmarkManager;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
bookmarkManager = new BookmarkManager();
|
|
});
|
|
|
|
// 全局函数
|
|
function closeModal() {
|
|
if (bookmarkManager) {
|
|
bookmarkManager.closeModal();
|
|
}
|
|
}
|
|
|