Files
workdizhi/script.js

1102 lines
39 KiB
JavaScript
Raw Normal View History

2025-12-20 21:39:14 +08:00
// 网址管理器应用
class UrlManager {
constructor() {
this.urls = [];
this.categories = [];
this.selectedUrls = new Set();
this.currentCategory = 'all';
this.currentSearch = '';
this.currentTag = '';
this.viewMode = 'grid';
this.editingUrlId = null;
}
async init() {
await this.loadData();
2025-12-20 21:39:14 +08:00
this.setupEventListeners();
this.render();
this.setupKeyboardShortcuts();
}
// 数据存储
async loadData() {
try {
// 优先从服务器加载数据
const response = await fetch('/api/urls');
if (response.ok) {
const data = await response.json();
this.urls = data.urls && data.urls.length > 0 ? data.urls : this.getDefaultUrls();
this.categories = data.categories && data.categories.length > 0 ? data.categories : this.getDefaultCategories();
// 同时保存到 localStorage 作为备份
localStorage.setItem('urlManager_urls', JSON.stringify(this.urls));
localStorage.setItem('urlManager_categories', JSON.stringify(this.categories));
return;
}
} catch (error) {
console.warn('从服务器加载数据失败,尝试从 localStorage 加载:', error);
}
// 降级到 localStorage
2025-12-20 21:39:14 +08:00
const savedUrls = localStorage.getItem('urlManager_urls');
const savedCategories = localStorage.getItem('urlManager_categories');
if (savedUrls) {
this.urls = JSON.parse(savedUrls);
} else {
// 添加一些示例数据
this.urls = this.getDefaultUrls();
}
if (savedCategories) {
this.categories = JSON.parse(savedCategories);
} else {
this.categories = this.getDefaultCategories();
}
}
async saveData() {
2025-12-20 21:39:14 +08:00
try {
const dataToSave = {
urls: this.urls || [],
categories: this.categories || []
};
console.log('保存数据:', {
urlsCount: dataToSave.urls.length,
categoriesCount: dataToSave.categories.length
});
// 优先保存到服务器
const response = await fetch('/api/urls', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dataToSave)
});
if (response.ok) {
const result = await response.json();
console.log('服务器保存成功:', result);
// 服务器保存成功,同时保存到 localStorage 作为备份
localStorage.setItem('urlManager_urls', JSON.stringify(this.urls));
localStorage.setItem('urlManager_categories', JSON.stringify(this.categories));
return true;
} else {
const errorText = await response.text();
console.error('服务器返回错误:', response.status, errorText);
throw new Error(`服务器返回错误: ${response.status} - ${errorText}`);
}
2025-12-20 21:39:14 +08:00
} catch (error) {
console.error('保存到服务器失败:', error);
// 降级到 localStorage
try {
localStorage.setItem('urlManager_urls', JSON.stringify(this.urls));
localStorage.setItem('urlManager_categories', JSON.stringify(this.categories));
console.warn('已降级保存到 localStorage');
} catch (e) {
console.error('保存数据到localStorage也失败:', e);
}
throw error; // 重新抛出错误,让调用者知道保存失败
2025-12-20 21:39:14 +08:00
}
}
getDefaultUrls() {
return [
{
id: '1',
title: 'Android开发者文档',
url: 'https://developer.android.com',
description: '官方Android开发文档和API参考',
category: 'dev-docs',
icon: 'fas fa-book',
tags: ['文档', '官方', 'Android'],
favorite: true,
createdAt: new Date().toISOString(),
lastAccessed: null,
accessCount: 0
},
{
id: '2',
title: 'GitHub',
url: 'https://github.com',
description: '代码托管和版本控制平台',
category: 'tools',
icon: 'fas fa-code',
tags: ['工具', '代码', '版本控制'],
favorite: true,
createdAt: new Date().toISOString(),
lastAccessed: null,
accessCount: 0
},
{
id: '3',
title: 'Stack Overflow',
url: 'https://stackoverflow.com',
description: '开发者问答社区',
category: 'community',
icon: 'fas fa-comments',
tags: ['社区', '问答', '帮助'],
favorite: false,
createdAt: new Date().toISOString(),
lastAccessed: null,
accessCount: 0
},
{
id: '4',
title: 'Material Design',
url: 'https://material.io',
description: 'Google的设计系统',
category: 'design',
icon: 'fas fa-palette',
tags: ['设计', 'UI', 'Material'],
favorite: false,
createdAt: new Date().toISOString(),
lastAccessed: null,
accessCount: 0
}
];
}
getDefaultCategories() {
return [
{ id: 'dev-docs', name: '开发文档', color: '#3498db', icon: 'fas fa-book' },
{ id: 'tools', name: '工具', color: '#2ecc71', icon: 'fas fa-tools' },
{ id: 'community', name: '社区', color: '#9b59b6', icon: 'fas fa-comments' },
{ id: 'design', name: '设计', color: '#e74c3c', icon: 'fas fa-palette' }
];
}
// 网址管理方法
addUrl(urlData) {
const newUrl = {
id: Date.now().toString(),
createdAt: new Date().toISOString(),
lastAccessed: null,
accessCount: 0,
...urlData
};
// 先保存数据到内存和localStorage核心操作不能失败
this.urls.push(newUrl);
this.saveData();
// 如果当前有分类过滤或搜索过滤,切换到"全部网址"以确保新添加的网址能显示
if (this.currentCategory !== 'all' || this.currentSearch || this.currentTag) {
this.currentCategory = 'all';
this.currentSearch = '';
this.currentTag = '';
// 清除搜索框
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.value = '';
}
}
// 渲染页面(必须执行,确保页面刷新)
// render() 会更新分类选择器的UI状态所以不需要单独更新
this.render();
return newUrl;
}
updateUrl(id, updates) {
const index = this.urls.findIndex(url => url.id === id);
if (index !== -1) {
// 先保存数据到内存和localStorage核心操作不能失败
this.urls[index] = { ...this.urls[index], ...updates };
this.saveData();
// 渲染页面(必须执行,确保页面刷新)
this.render();
return true;
}
return false;
}
deleteUrl(id) {
const index = this.urls.findIndex(url => url.id === id);
if (index !== -1) {
this.urls.splice(index, 1);
this.selectedUrls.delete(id);
this.saveData();
this.render();
return true;
}
return false;
}
deleteSelectedUrls() {
this.urls = this.urls.filter(url => !this.selectedUrls.has(url.id));
this.selectedUrls.clear();
this.saveData();
this.render();
this.hideBulkActions();
}
toggleFavorite(id) {
const url = this.urls.find(url => url.id === id);
if (url) {
url.favorite = !url.favorite;
this.saveData();
this.render();
}
}
// 分类管理
addCategory(categoryData) {
const newCategory = {
id: Date.now().toString(),
...categoryData
};
this.categories.push(newCategory);
this.saveData();
this.renderCategories();
return newCategory;
}
// 过滤和搜索
getFilteredUrls() {
let filtered = [...this.urls];
// 按分类过滤
if (this.currentCategory === 'favorites') {
filtered = filtered.filter(url => url.favorite);
} else if (this.currentCategory === 'recent') {
filtered = filtered.filter(url => url.lastAccessed)
.sort((a, b) => new Date(b.lastAccessed) - new Date(a.lastAccessed))
.slice(0, 20);
} else if (this.currentCategory !== 'all') {
filtered = filtered.filter(url => url.category === this.currentCategory);
}
// 按搜索词过滤
if (this.currentSearch) {
const searchLower = this.currentSearch.toLowerCase();
filtered = filtered.filter(url =>
url.title.toLowerCase().includes(searchLower) ||
url.url.toLowerCase().includes(searchLower) ||
url.description?.toLowerCase().includes(searchLower) ||
url.tags?.some(tag => tag.toLowerCase().includes(searchLower))
);
}
// 按标签过滤
if (this.currentTag) {
filtered = filtered.filter(url =>
url.tags?.some(tag => tag.toLowerCase() === this.currentTag.toLowerCase())
);
}
return filtered;
}
// 渲染方法
render() {
// 分别处理每个渲染方法,确保即使某个方法失败,其他方法也能执行
try {
this.renderCategories();
} catch (error) {
console.warn('渲染分类时出错:', error);
}
try {
this.renderUrls();
} catch (error) {
console.warn('渲染网址列表时出错:', error);
}
try {
this.renderStats();
} catch (error) {
console.warn('渲染统计信息时出错:', error);
}
try {
this.renderTags();
} catch (error) {
console.warn('渲染标签时出错:', error);
}
}
renderCategories() {
const categoriesList = document.getElementById('categoriesList');
if (!categoriesList) return;
const allCount = this.urls.length;
const favoritesCount = this.urls.filter(url => url.favorite).length;
const recentCount = this.urls.filter(url => url.lastAccessed).length;
let html = `
<div class="category-item ${this.currentCategory === 'all' ? 'active' : ''}" data-id="all">
<i class="fas fa-globe"></i>
<span>全部网址</span>
<span class="count-badge">${allCount}</span>
</div>
<div class="category-item ${this.currentCategory === 'favorites' ? 'active' : ''}" data-id="favorites">
<i class="fas fa-star"></i>
<span>收藏夹</span>
<span class="count-badge">${favoritesCount}</span>
</div>
<div class="category-item ${this.currentCategory === 'recent' ? 'active' : ''}" data-id="recent">
<i class="fas fa-history"></i>
<span>最近访问</span>
<span class="count-badge">${recentCount}</span>
</div>
`;
// 添加自定义分类
this.categories.forEach(category => {
const count = this.urls.filter(url => url.category === category.id).length;
html += `
<div class="category-item ${this.currentCategory === category.id ? 'active' : ''}" data-id="${category.id}">
<i class="${category.icon}" style="color: ${category.color}"></i>
<span>${category.name}</span>
<span class="count-badge">${count}</span>
</div>
`;
});
categoriesList.innerHTML = html;
// 更新分类选择器
this.updateCategorySelector();
}
renderUrls() {
const urlsContainer = document.getElementById('urlsContainer');
const emptyState = document.getElementById('emptyState');
if (!urlsContainer) {
console.warn('urlsContainer 元素不存在');
return;
}
const filteredUrls = this.getFilteredUrls();
if (filteredUrls.length === 0) {
if (emptyState) {
emptyState.style.display = 'block';
}
urlsContainer.innerHTML = '';
if (emptyState) {
urlsContainer.appendChild(emptyState);
}
return;
}
if (emptyState) {
emptyState.style.display = 'none';
}
let html = '';
filteredUrls.forEach(url => {
const category = this.categories.find(c => c.id === url.category);
const isSelected = this.selectedUrls.has(url.id);
html += `
<div class="url-card ${isSelected ? 'selected' : ''}" data-id="${url.id}">
<input type="checkbox" ${isSelected ? 'checked' : ''}>
<div class="url-card-header">
<div style="display: flex; align-items: center;">
<i class="${url.icon} url-icon"></i>
<div>
<div class="url-title">${url.title}</div>
<div class="url-address">${url.url}</div>
</div>
</div>
<div class="url-actions">
<button class="favorite-btn ${url.favorite ? 'active' : ''}" title="${url.favorite ? '取消收藏' : '收藏'}">
<i class="fas fa-star"></i>
</button>
<button class="edit-url" title="编辑">
<i class="fas fa-edit"></i>
</button>
<button class="delete-url" title="删除">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
${url.description ? `<div class="url-description">${url.description}</div>` : ''}
<div class="url-footer">
<div class="url-tags">
${url.tags?.map(tag => `<span class="url-tag">${tag}</span>`).join('') || ''}
</div>
<div class="url-meta">
${category ? `<span style="color: ${category.color}"><i class="${category.icon}"></i> ${category.name}</span>` : ''}
<button class="open-url" title="打开网址">
<i class="fas fa-external-link-alt"></i>
</button>
</div>
</div>
</div>
`;
});
urlsContainer.innerHTML = html;
}
renderStats() {
const totalUrlsEl = document.getElementById('totalUrls');
const totalCategoriesEl = document.getElementById('totalCategories');
const totalFavoritesEl = document.getElementById('totalFavorites');
if (totalUrlsEl) totalUrlsEl.textContent = this.urls.length;
if (totalCategoriesEl) totalCategoriesEl.textContent = this.categories.length;
if (totalFavoritesEl) totalFavoritesEl.textContent = this.urls.filter(url => url.favorite).length;
}
renderTags() {
const tagsContainer = document.getElementById('tagsContainer');
if (!tagsContainer) return;
// 收集所有标签
const tagCounts = {};
this.urls.forEach(url => {
url.tags?.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
});
const tags = Object.entries(tagCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
let html = '';
tags.forEach(([tag, count]) => {
const isActive = this.currentTag === tag;
html += `<span class="tag ${isActive ? 'active' : ''}" data-tag="${tag}">${tag} (${count})</span>`;
});
tagsContainer.innerHTML = html;
}
updateCategorySelector() {
try {
const categorySelect = document.getElementById('urlCategory');
if (!categorySelect) return;
let html = '<option value="">选择分类</option>';
this.categories.forEach(category => {
html += `<option value="${category.id}">${category.name}</option>`;
});
categorySelect.innerHTML = html;
} catch (error) {
console.warn('更新分类选择器时出错:', error);
// 不影响其他操作
}
}
// 事件监听器
setupEventListeners() {
// 主题切换
document.getElementById('themeToggle').addEventListener('click', () => {
this.toggleTheme();
});
// 添加网址按钮
document.getElementById('addUrlBtn').addEventListener('click', () => {
this.showUrlModal();
});
document.getElementById('addFirstUrlBtn').addEventListener('click', () => {
this.showUrlModal();
});
// 添加分类按钮
document.getElementById('addCategoryBtn').addEventListener('click', () => {
this.showCategoryModal();
});
// 搜索
document.getElementById('searchInput').addEventListener('input', (e) => {
this.currentSearch = e.target.value;
this.renderUrls();
});
document.getElementById('clearSearch').addEventListener('click', () => {
document.getElementById('searchInput').value = '';
this.currentSearch = '';
this.renderUrls();
});
// 视图切换
document.getElementById('gridViewBtn').addEventListener('click', () => {
this.setViewMode('grid');
});
document.getElementById('listViewBtn').addEventListener('click', () => {
this.setViewMode('list');
});
// 批量操作
document.getElementById('bulkActionsBtn').addEventListener('click', () => {
this.showBulkActions();
});
document.getElementById('bulkCancelBtn').addEventListener('click', () => {
this.hideBulkActions();
});
document.getElementById('bulkDeleteBtn').addEventListener('click', () => {
if (confirm(`确定要删除选中的 ${this.selectedUrls.size} 个网址吗?`)) {
this.deleteSelectedUrls();
}
});
document.getElementById('bulkOpenBtn').addEventListener('click', () => {
this.openSelectedUrls();
});
// 导入/导出
document.getElementById('importBtn').addEventListener('click', () => {
this.showImportExportModal('import');
});
document.getElementById('exportBtn').addEventListener('click', () => {
this.showImportExportModal('export');
});
// 模态框关闭
document.querySelectorAll('.close-modal').forEach(btn => {
btn.addEventListener('click', () => {
this.closeAllModals();
});
});
// 网址表单提交 - 使用bind确保正确的this上下文
document.getElementById('urlForm').addEventListener('submit', (e) => {
e.preventDefault();
this.handleUrlFormSubmit();
});
// 分类表单提交
document.getElementById('categoryForm').addEventListener('submit', (e) => {
e.preventDefault();
this.handleCategoryFormSubmit();
});
// 标签输入
document.getElementById('tagsInput').addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.addTagToInput(e.target.value);
e.target.value = '';
}
});
// 文件导入
document.getElementById('jsonFileInput').addEventListener('change', (e) => {
this.handleFileImport(e.target.files[0]);
});
// 添加调试日志
console.log('事件监听器已设置完成');
// 事件委托
document.addEventListener('click', (e) => {
// 分类点击
if (e.target.closest('.category-item')) {
const categoryItem = e.target.closest('.category-item');
const categoryId = categoryItem.dataset.id;
this.setCurrentCategory(categoryId);
}
// 标签点击
if (e.target.closest('.tag')) {
const tag = e.target.closest('.tag').dataset.tag;
this.setCurrentTag(tag);
}
// 网址卡片操作
if (e.target.closest('.url-card')) {
const urlCard = e.target.closest('.url-card');
const urlId = urlCard.dataset.id;
// 复选框选择
if (e.target.type === 'checkbox') {
this.toggleUrlSelection(urlId, e.target.checked);
}
// 收藏按钮
if (e.target.closest('.favorite-btn')) {
e.preventDefault();
e.stopPropagation();
this.toggleFavorite(urlId);
}
// 编辑按钮
if (e.target.closest('.edit-url')) {
e.preventDefault();
e.stopPropagation();
this.editUrl(urlId);
}
// 删除按钮
if (e.target.closest('.delete-url')) {
e.preventDefault();
e.stopPropagation();
if (confirm('确定要删除这个网址吗?')) {
this.deleteUrl(urlId);
}
}
// 打开网址按钮
if (e.target.closest('.open-url')) {
e.preventDefault();
e.stopPropagation();
this.openUrl(urlId);
}
}
// 导入/导出标签页切换
if (e.target.closest('.tab-btn')) {
const tabBtn = e.target.closest('.tab-btn');
const tab = tabBtn.dataset.tab;
this.switchImportExportTab(tab);
}
// 下载JSON按钮
if (e.target.id === 'downloadJsonBtn') {
this.downloadJson();
}
});
}
// 模态框方法
showUrlModal(urlId = null) {
this.editingUrlId = urlId;
const modal = document.getElementById('urlModal');
const title = document.getElementById('modalTitle');
const form = document.getElementById('urlForm');
if (urlId) {
// 编辑模式
title.textContent = '编辑网址';
const url = this.urls.find(u => u.id === urlId);
if (url) {
document.getElementById('urlTitle').value = url.title;
document.getElementById('urlAddress').value = url.url;
document.getElementById('urlDescription').value = url.description || '';
document.getElementById('urlCategory').value = url.category || '';
document.getElementById('urlIcon').value = url.icon || 'fas fa-globe';
document.getElementById('urlFavorite').checked = url.favorite || false;
// 设置标签
const tagsDisplay = document.getElementById('tagsDisplay');
tagsDisplay.innerHTML = '';
if (url.tags) {
url.tags.forEach(tag => {
this.addTagToDisplay(tag);
});
}
}
} else {
// 添加模式
title.textContent = '添加网址';
form.reset();
document.getElementById('tagsDisplay').innerHTML = '';
}
modal.classList.add('active');
}
showCategoryModal() {
const modal = document.getElementById('categoryModal');
modal.classList.add('active');
}
showImportExportModal(tab = 'export') {
const modal = document.getElementById('importExportModal');
const title = document.getElementById('importExportTitle');
if (tab === 'export') {
title.textContent = '导出数据';
this.updateExportPreview();
} else {
title.textContent = '导入数据';
}
this.switchImportExportTab(tab);
modal.classList.add('active');
}
closeAllModals() {
document.querySelectorAll('.modal').forEach(modal => {
modal.classList.remove('active');
});
this.editingUrlId = null;
// 强制触发重排,确保弹窗关闭
void document.body.offsetHeight;
}
// 表单处理
handleUrlFormSubmit() {
const title = document.getElementById('urlTitle').value.trim();
const url = document.getElementById('urlAddress').value.trim();
const description = document.getElementById('urlDescription').value.trim();
const category = document.getElementById('urlCategory').value;
const icon = document.getElementById('urlIcon').value;
const favorite = document.getElementById('urlFavorite').checked;
// 获取标签
const tags = [];
document.querySelectorAll('#tagsDisplay .tag-input-tag').forEach(tagEl => {
const span = tagEl.querySelector('span');
if (span) {
tags.push(span.textContent);
}
});
if (!title || !url) {
alert('标题和网址是必填项');
return false;
}
const urlData = {
title,
url,
description,
category,
icon,
tags,
favorite
};
// 保存数据(只对核心保存操作进行错误处理)
try {
if (this.editingUrlId) {
const success = this.updateUrl(this.editingUrlId, urlData);
if (!success) {
alert('更新失败:未找到要更新的网址');
return false;
}
} else {
this.addUrl(urlData);
}
// 如果执行到这里说明保存成功addUrl/updateUrl 没有抛出异常)
// render() 已经在 addUrl/updateUrl 中调用,页面应该已经刷新
} catch (error) {
console.error('保存网址时出错:', error);
// 只有在真正的保存操作失败时才提示
alert('保存失败,请重试');
return false;
}
// 重置表单(这些操作不应该影响保存结果)
try {
const form = document.getElementById('urlForm');
if (form) {
form.reset();
}
const tagsDisplay = document.getElementById('tagsDisplay');
if (tagsDisplay) {
tagsDisplay.innerHTML = '';
}
} catch (error) {
console.warn('重置表单时出错:', error);
// 不影响保存结果,继续执行
}
// 关闭弹窗addUrl/updateUrl 已经调用了 render() 刷新页面)
this.closeAllModals();
return false;
}
handleCategoryFormSubmit() {
const name = document.getElementById('categoryName').value.trim();
const color = document.getElementById('categoryColor').value;
const icon = document.getElementById('categoryIcon').value;
if (!name) {
alert('分类名称是必填项');
return;
}
const categoryData = {
name,
color,
icon
};
this.addCategory(categoryData);
this.closeAllModals();
}
// 标签处理
addTagToInput(tagText) {
if (!tagText.trim()) return;
const tagsDisplay = document.getElementById('tagsDisplay');
const existingTags = Array.from(tagsDisplay.querySelectorAll('.tag-input-tag span'))
.map(span => span.textContent);
if (!existingTags.includes(tagText.trim())) {
this.addTagToDisplay(tagText.trim());
}
}
addTagToDisplay(tagText) {
const tagsDisplay = document.getElementById('tagsDisplay');
const tagEl = document.createElement('div');
tagEl.className = 'tag-input-tag';
tagEl.innerHTML = `
<span>${tagText}</span>
<button type="button">&times;</button>
`;
tagEl.querySelector('button').addEventListener('click', () => {
tagEl.remove();
});
tagsDisplay.appendChild(tagEl);
}
// 文件导入/导出
handleFileImport(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
2025-12-20 21:39:14 +08:00
try {
const data = JSON.parse(e.target.result);
console.log('导入数据:', data);
2025-12-20 21:39:14 +08:00
if (confirm('导入数据将覆盖当前的所有数据,确定要继续吗?')) {
// 更新数据
if (data.urls) {
this.urls = data.urls;
console.log('导入网址数量:', this.urls.length);
} else {
this.urls = [];
}
if (data.categories) {
this.categories = data.categories;
console.log('导入分类数量:', this.categories.length);
} else {
this.categories = [];
}
// 等待数据保存到服务器
console.log('开始保存数据到服务器...');
await this.saveData();
console.log('数据保存完成');
// 验证保存是否成功
const verifyResponse = await fetch('/api/urls');
if (verifyResponse.ok) {
const savedData = await verifyResponse.json();
console.log('服务器上的数据:', savedData);
console.log('服务器网址数量:', savedData.urls ? savedData.urls.length : 0);
}
2025-12-20 21:39:14 +08:00
this.render();
this.closeAllModals();
alert(`数据导入成功!已导入 ${this.urls.length} 个网址,${this.categories.length} 个分类,并已保存到服务器!`);
2025-12-20 21:39:14 +08:00
}
} catch (error) {
console.error('导入数据失败:', error);
alert('文件格式错误或保存失败: ' + error.message);
2025-12-20 21:39:14 +08:00
}
};
reader.readAsText(file);
}
updateExportPreview() {
const preview = document.getElementById('exportDataPreview');
const data = {
urls: this.urls,
categories: this.categories,
exportedAt: new Date().toISOString()
};
preview.textContent = JSON.stringify(data, null, 2);
}
downloadJson() {
const data = {
urls: this.urls,
categories: this.categories,
exportedAt: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `url-manager-backup-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
switchImportExportTab(tab) {
// 更新按钮状态
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.tab === tab);
});
// 更新内容显示
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.toggle('active', content.id === `${tab}Tab`);
});
}
// 其他方法
setCurrentCategory(categoryId) {
this.currentCategory = categoryId;
this.render();
// 更新UI状态
document.querySelectorAll('.category-item').forEach(item => {
item.classList.toggle('active', item.dataset.id === categoryId);
});
}
setCurrentTag(tag) {
if (this.currentTag === tag) {
this.currentTag = ''; // 取消选择
} else {
this.currentTag = tag;
}
this.renderUrls();
this.renderTags();
}
setViewMode(mode) {
this.viewMode = mode;
const urlsContainer = document.getElementById('urlsContainer');
const gridBtn = document.getElementById('gridViewBtn');
const listBtn = document.getElementById('listViewBtn');
urlsContainer.classList.remove('grid-view', 'list-view');
urlsContainer.classList.add(`${mode}-view`);
gridBtn.classList.toggle('active', mode === 'grid');
listBtn.classList.toggle('active', mode === 'list');
}
toggleUrlSelection(urlId, checked) {
if (checked) {
this.selectedUrls.add(urlId);
} else {
this.selectedUrls.delete(urlId);
}
this.updateBulkActions();
this.renderUrls();
}
updateBulkActions() {
const selectedCount = document.getElementById('selectedCount');
const bulkPanel = document.getElementById('bulkActionsPanel');
selectedCount.textContent = `已选择 ${this.selectedUrls.size} 个项目`;
if (this.selectedUrls.size > 0) {
bulkPanel.classList.add('active');
} else {
bulkPanel.classList.remove('active');
}
}
showBulkActions() {
// 显示批量操作面板
const bulkPanel = document.getElementById('bulkActionsPanel');
bulkPanel.classList.add('active');
}
hideBulkActions() {
// 清除选择并隐藏面板
this.selectedUrls.clear();
const bulkPanel = document.getElementById('bulkActionsPanel');
bulkPanel.classList.remove('active');
this.renderUrls();
}
openUrl(urlId) {
const url = this.urls.find(u => u.id === urlId);
if (url) {
window.open(url.url, '_blank');
// 更新访问记录
url.lastAccessed = new Date().toISOString();
url.accessCount = (url.accessCount || 0) + 1;
this.saveData();
}
}
openSelectedUrls() {
this.urls.forEach(url => {
if (this.selectedUrls.has(url.id)) {
window.open(url.url, '_blank');
// 更新访问记录
url.lastAccessed = new Date().toISOString();
url.accessCount = (url.accessCount || 0) + 1;
}
});
this.saveData();
}
editUrl(urlId) {
this.showUrlModal(urlId);
}
toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
const themeIcon = document.querySelector('#themeToggle i');
document.documentElement.setAttribute('data-theme', newTheme);
themeIcon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
localStorage.setItem('urlManager_theme', newTheme);
}
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Ctrl+F 聚焦搜索框
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
document.getElementById('searchInput').focus();
}
// Ctrl+N 添加新网址
if (e.ctrlKey && e.key === 'n') {
e.preventDefault();
this.showUrlModal();
}
// Esc 关闭模态框或取消批量选择
if (e.key === 'Escape') {
if (this.selectedUrls.size > 0) {
this.hideBulkActions();
} else {
this.closeAllModals();
}
}
// Ctrl+S 保存数据(导出)
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
this.showImportExportModal('export');
}
});
// 加载保存的主题
const savedTheme = localStorage.getItem('urlManager_theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
const themeIcon = document.querySelector('#themeToggle i');
themeIcon.className = savedTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', async () => {
2025-12-20 21:39:14 +08:00
window.urlManager = new UrlManager();
await window.urlManager.init();
2025-12-20 21:39:14 +08:00
});