Files
workdizhi/script.js
2025-12-20 21:39:14 +08:00

1025 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 网址管理器应用
class UrlManager {
constructor() {
this.urls = [];
this.categories = [];
this.selectedUrls = new Set();
this.currentCategory = 'all';
this.currentSearch = '';
this.currentTag = '';
this.viewMode = 'grid';
this.editingUrlId = null;
this.init();
}
init() {
this.loadData();
this.setupEventListeners();
this.render();
this.setupKeyboardShortcuts();
}
// 数据存储
loadData() {
// 加载默认数据或从localStorage加载
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();
}
}
saveData() {
try {
localStorage.setItem('urlManager_urls', JSON.stringify(this.urls));
localStorage.setItem('urlManager_categories', JSON.stringify(this.categories));
} catch (error) {
console.error('保存数据到localStorage失败:', error);
// 如果localStorage已满或其他错误只记录错误不抛出异常
// 因为数据已经在内存中了,只是无法持久化
// 抛出异常会导致误报"保存失败"
}
}
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 = (e) => {
try {
const data = JSON.parse(e.target.result);
if (confirm('导入数据将覆盖当前的所有数据,确定要继续吗?')) {
if (data.urls) this.urls = data.urls;
if (data.categories) this.categories = data.categories;
this.saveData();
this.render();
this.closeAllModals();
alert('数据导入成功!');
}
} catch (error) {
alert('文件格式错误请选择有效的JSON文件');
}
};
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', () => {
window.urlManager = new UrlManager();
});