[Asm] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>书签链接检测工具</title>
<style>
:root {
--primary-color: #4285f4;
--success-color: #34a853;
--danger-color: #ea4335;
--warning-color: #fbbc05;
--light-color: #f8f9fa;
--dark-color: #343a40;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--primary-color);
color: white;
padding: 20px 0;
margin-bottom: 30px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
header h1 {
margin: 0;
text-align: center;
font-weight: 500;
}
.card {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
}
.card-title {
margin-top: 0;
color: var(--primary-color);
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input[type="number"], select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.btn {
display: inline-block;
background-color: var(--primary-color);
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #3367d6;
}
.btn-success {
background-color: var(--success-color);
}
.btn-success:hover {
background-color: #2d9249;
}
.btn-danger {
background-color: var(--danger-color);
}
.btn-danger:hover {
background-color: #d33426;
}
.btn-warning {
background-color: var(--warning-color);
}
.btn-warning:hover {
background-color: #e6ac00;
}
.progress-container {
margin-top: 20px;
background-color: #eee;
border-radius: 5px;
height: 20px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: var(--primary-color);
width: 0%;
transition: width 0.3s;
}
.stats {
display: flex;
justify-content: space-between;
margin-top: 15px;
font-size: 14px;
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-value {
font-size: 18px;
font-weight: bold;
}
.bookmark-list {
margin-top: 20px;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.bookmark-header {
display: flex;
background-color: #f8f9fa;
padding: 10px 15px;
font-weight: bold;
border-bottom: 1px solid #ddd;
}
.bookmark-header div {
flex: 1;
}
.bookmark-header .checkbox {
flex: 0 0 40px;
}
.bookmark-header .status {
flex: 0 0 100px;
}
.bookmark-header .actions {
flex: 0 0 150px;
}
.bookmark-item {
display: flex;
padding: 10px 15px;
border-bottom: 1px solid #eee;
align-items: center;
}
.bookmark-item:last-child {
border-bottom: none;
}
.bookmark-item:hover {
background-color: #f8f9fa;
}
.bookmark-item div {
flex: 1;
word-break: break-all;
}
.bookmark-item .checkbox {
flex: 0 0 40px;
}
.bookmark-item .status {
flex: 0 0 100px;
}
.bookmark-item .actions {
flex: 0 0 150px;
}
.status-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.status-valid {
background-color: #e6f4ea;
color: var(--success-color);
}
.status-invalid {
background-color: #fce8e6;
color: var(--danger-color);
}
.status-timeout {
background-color: #fef7e0;
color: var(--warning-color);
}
.status-pending {
background-color: #e8f0fe;
color: var(--primary-color);
}
.action-btn {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
margin-right: 5px;
font-size: 14px;
}
.action-btn:hover {
text-decoration: underline;
}
.footer {
text-align: center;
margin-top: 30px;
color: #777;
font-size: 14px;
}
.drag-drop-area {
border: 2px dashed #ccc;
border-radius: 5px;
padding: 30px;
text-align: center;
margin-bottom: 20px;
cursor: pointer;
transition: background-color 0.3s;
}
.drag-drop-area:hover {
background-color: #f8f9fa;
}
.drag-drop-area.active {
border-color: var(--primary-color);
background-color: #e8f0fe;
}
.tab-container {
margin-bottom: 20px;
}
.tab-buttons {
display: flex;
border-bottom: 1px solid #ddd;
}
.tab-button {
padding: 10px 20px;
background: none;
border: none;
cursor: pointer;
font-size: 16px;
color: #666;
position: relative;
}
.tab-button.active {
color: var(--primary-color);
font-weight: bold;
}
.tab-button.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background-color: var(--primary-color);
}
.tab-content {
display: none;
padding: 20px 0;
}
.tab-content.active {
display: block;
}
.checkbox {
text-align: center;
}
.select-all {
margin-right: 10px;
}
@media (max-width: 768px) {
.bookmark-header, .bookmark-item {
flex-wrap: wrap;
}
.bookmark-header div, .bookmark-item div {
flex: 0 0 100%;
margin-bottom: 5px;
}
.bookmark-header .checkbox, .bookmark-item .checkbox {
flex: 0 0 30px;
}
.bookmark-header .status, .bookmark-item .status {
flex: 0 0 80px;
}
.bookmark-header .actions, .bookmark-item .actions {
flex: 0 0 100%;
text-align: right;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>书签链接检测工具</h1>
</header>
<div class="card">
<h2 class="card-title">导入书签</h2>
<div class="drag-drop-area" id="dragDropArea">
<p>拖放浏览器导出的HTML书签文件到这里</p>
<p>或</p>
<input type="file" id="fileInput" accept=".html" style="display: none;">
<button class="btn" id="selectFileBtn">选择文件</button>
</div>
</div>
<div class="card">
<h2 class="card-title">检测设置</h2>
<div class="form-group">
<label for="timeout">超时时间 (毫秒)</label>
<input type="number" id="timeout" min="500" max="10000" value="3000">
</div>
<div class="form-group">
<label for="retryCount">每个链接检测次数 (1-5次)</label>
<input type="number" id="retryCount" min="1" max="5" value="3">
</div>
<div class="form-group">
<label for="concurrency">并发请求数 (1-10个)</label>
<input type="number" id="concurrency" min="1" max="10" value="5">
</div>
<button class="btn btn-success" id="startCheckBtn">开始检测</button>
<button class="btn btn-danger" id="stopCheckBtn" disabled>停止检测</button>
<div class="progress-container" id="progressContainer" style="display: none;">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="stats" id="stats" style="display: none;">
<div class="stat-item">
<div class="stat-label">总数</div>
<div class="stat-value" id="totalCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">已完成</div>
<div class="stat-value" id="completedCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">有效</div>
<div class="stat-value" id="validCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">无效</div>
<div class="stat-value" id="invalidCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">超时</div>
<div class="stat-value" id="timeoutCount">0</div>
</div>
</div>
</div>
<div class="card">
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" data-tab="all">全部书签</button>
<button class="tab-button" data-tab="valid">有效链接</button>
<button class="tab-button" data-tab="invalid">无效链接</button>
<button class="tab-button" data-tab="timeout">超时链接</button>
</div>
<div class="tab-content active" id="tab-all">
<div class="bookmark-actions">
<input type="checkbox" id="selectAll" class="select-all">
<label for="selectAll">全选</label>
<button class="btn btn-warning" id="deleteInvalidBtn">删除无效链接</button>
<button class="btn" id="exportSelectedBtn">导出选中</button>
<select id="exportFilter">
<option value="all">全部</option>
<option value="valid">仅有效</option>
<option value="invalid">仅无效</option>
<option value="timeout">仅超时</option>
</select>
</div>
<div class="bookmark-list">
<div class="bookmark-header">
<div class="checkbox"></div>
<div>标题</div>
<div>URL</div>
<div class="status">状态</div>
<div class="actions">操作</div>
</div>
<div id="bookmarkList">
<!-- 书签列表将在这里动态生成 -->
<div class="empty-message">请先导入书签文件</div>
</div>
</div>
</div>
<div class="tab-content" id="tab-valid">
<div class="bookmark-list">
<div class="bookmark-header">
<div class="checkbox"></div>
<div>标题</div>
<div>URL</div>
<div class="status">状态</div>
<div class="actions">操作</div>
</div>
<div id="validBookmarkList">
<!-- 有效书签列表将在这里动态生成 -->
</div>
</div>
</div>
<div class="tab-content" id="tab-invalid">
<div class="bookmark-list">
<div class="bookmark-header">
<div class="checkbox"></div>
<div>标题</div>
<div>URL</div>
<div class="status">状态</div>
<div class="actions">操作</div>
</div>
<div id="invalidBookmarkList">
<!-- 无效书签列表将在这里动态生成 -->
</div>
</div>
</div>
<div class="tab-content" id="tab-timeout">
<div class="bookmark-list">
<div class="bookmark-header">
<div class="checkbox"></div>
<div>标题</div>
<div>URL</div>
<div class="status">状态</div>
<div class="actions">操作</div>
</div>
<div id="timeoutBookmarkList">
<!-- 超时书签列表将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<p>书签链接检测工具 © 2023 | 使用HTTP HEAD方法检测链接有效性</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 全局变量
let bookmarks = [];
let isChecking = false;
let stopRequested = false;
let currentTab = 'all';
// DOM元素
const dragDropArea = document.getElementById('dragDropArea');
const fileInput = document.getElementById('fileInput');
const selectFileBtn = document.getElementById('selectFileBtn');
const startCheckBtn = document.getElementById('startCheckBtn');
const stopCheckBtn = document.getElementById('stopCheckBtn');
const progressBar = document.getElementById('progressBar');
const progressContainer = document.getElementById('progressContainer');
const stats = document.getElementById('stats');
const bookmarkList = document.getElementById('bookmarkList');
const validBookmarkList = document.getElementById('validBookmarkList');
const invalidBookmarkList = document.getElementById('invalidBookmarkList');
const timeoutBookmarkList = document.getElementById('timeoutBookmarkList');
const totalCountEl = document.getElementById('totalCount');
const completedCountEl = document.getElementById('completedCount');
const validCountEl = document.getElementById('validCount');
const invalidCountEl = document.getElementById('invalidCount');
const timeoutCountEl = document.getElementById('timeoutCount');
const selectAllCheckbox = document.getElementById('selectAll');
const deleteInvalidBtn = document.getElementById('deleteInvalidBtn');
const exportSelectedBtn = document.getElementById('exportSelectedBtn');
const exportFilter = document.getElementById('exportFilter');
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
// 事件监听器
selectFileBtn.addEventListener('click', function() {
fileInput.click();
});
fileInput.addEventListener('change', handleFileSelect);
dragDropArea.addEventListener('dragover', function(e) {
e.preventDefault();
dragDropArea.classList.add('active');
});
dragDropArea.addEventListener('dragleave', function() {
dragDropArea.classList.remove('active');
});
dragDropArea.addEventListener('drop', function(e) {
e.preventDefault();
dragDropArea.classList.remove('active');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleFileSelect({ target: fileInput });
}
});
startCheckBtn.addEventListener('click', startChecking);
stopCheckBtn.addEventListener('click', stopChecking);
selectAllCheckbox.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.bookmark-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
});
deleteInvalidBtn.addEventListener('click', deleteInvalidBookmarks);
exportSelectedBtn.addEventListener('click', exportSelectedBookmarks);
tabButtons.forEach(button => {
button.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
switchTab(tabId);
});
});
// 函数定义
function handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const content = e.target.result;
bookmarks = parseBookmarks(content);
renderBookmarkList();
updateStats();
// 重置状态
resetCheckingState();
} catch (error) {
alert('解析书签文件失败: ' + error.message);
}
};
reader.readAsText(file);
}
function parseBookmarks(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = doc.querySelectorAll('a');
return Array.from(links).map(link => {
return {
title: link.textContent.trim(),
url: link.getAttribute('href'),
status: 'pending',
retries: 0,
validCount: 0,
invalidCount: 0,
timeoutCount: 0,
selected: false
};
});
}
function renderBookmarkList() {
if (bookmarks.length === 0) {
bookmarkList.innerHTML = '<div class="empty-message">请先导入书签文件</div>';
return;
}
bookmarkList.innerHTML = '';
validBookmarkList.innerHTML = '';
invalidBookmarkList.innerHTML = '';
timeoutBookmarkList.innerHTML = '';
bookmarks.forEach((bookmark, index) => {
const bookmarkItem = createBookmarkElement(bookmark, index);
bookmarkList.appendChild(bookmarkItem);
// 根据状态添加到对应的列表
if (bookmark.status === 'valid') {
validBookmarkList.appendChild(createBookmarkElement(bookmark, index));
} else if (bookmark.status === 'invalid') {
invalidBookmarkList.appendChild(createBookmarkElement(bookmark, index));
} else if (bookmark.status === 'timeout') {
timeoutBookmarkList.appendChild(createBookmarkElement(bookmark, index));
}
});
}
function createBookmarkElement(bookmark, index) {
const item = document.createElement('div');
item.className = 'bookmark-item';
// 状态徽章
let statusBadge = '';
let statusText = '';
let statusClass = '';
if (bookmark.status === 'valid') {
statusText = '有效';
statusClass = 'status-valid';
} else if (bookmark.status === 'invalid') {
statusText = '无效';
statusClass = 'status-invalid';
} else if (bookmark.status === 'timeout') {
statusText = '超时';
statusClass = 'status-timeout';
} else {
statusText = '待检测';
statusClass = 'status-pending';
}
// 检测统计
let statsText = '';
if (bookmark.retries > 0) {
statsText = `${bookmark.validCount}√/${bookmark.invalidCount}×/${bookmark.timeoutCount}⌛`;
}
item.innerHTML = `
<div class="checkbox">
<input type="checkbox" class="bookmark-checkbox" data-index="${index}" ${bookmark.selected ? 'checked' : ''}>
</div>
<div>${bookmark.title || '无标题'}</div>
<div>${bookmark.url}</div>
<div class="status">
<span class="status-badge ${statusClass}">${statusText}</span>
${statsText ? `<br><small>${statsText}</small>` : ''}
</div>
<div class="actions">
<button class="action-btn test-btn" data-index="${index}">测试</button>
<button class="action-btn visit-btn" data-index="${index}">访问</button>
</div>
`;
// 添加事件监听器
const checkbox = item.querySelector('.bookmark-checkbox');
checkbox.addEventListener('change', function() {
bookmarks[index].selected = this.checked;
updateSelectAllState();
});
const testBtn = item.querySelector('.test-btn');
testBtn.addEventListener('click', function() {
testSingleBookmark(index);
});
const visitBtn = item.querySelector('.visit-btn');
visitBtn.addEventListener('click', function() {
window.open(bookmark.url, '_blank');
});
return item;
}
function startChecking() {
if (bookmarks.length === 0) {
alert('请先导入书签文件');
return;
}
if (isChecking) {
alert('检测正在进行中');
return;
}
isChecking = true;
stopRequested = false;
startCheckBtn.disabled = true;
stopCheckBtn.disabled = false;
const timeout = parseInt(document.getElementById('timeout').value) || 3000;
const retryCount = parseInt(document.getElementById('retryCount').value) || 3;
const concurrency = parseInt(document.getElementById('concurrency').value) || 5;
progressContainer.style.display = 'block';
stats.style.display = 'flex';
// 重置所有书签状态
bookmarks.forEach(bookmark => {
bookmark.status = 'pending';
bookmark.retries = 0;
bookmark.validCount = 0;
bookmark.invalidCount = 0;
bookmark.timeoutCount = 0;
});
renderBookmarkList();
updateStats();
// 使用并发控制检测书签
const total = bookmarks.length;
let currentIndex = 0;
let activeRequests = 0;
function processNextBatch() {
if (stopRequested || currentIndex >= total) {
if (activeRequests === 0) {
finishChecking();
}
return;
}
const batchSize = Math.min(concurrency - activeRequests, total - currentIndex);
for (let i = 0; i < batchSize; i++) {
if (currentIndex < total) {
activeRequests++;
checkBookmark(currentIndex, timeout, retryCount)
.finally(() => {
activeRequests--;
processNextBatch();
});
currentIndex++;
}
}
}
processNextBatch();
}
async function checkBookmark(index, timeout, maxRetries) {
if (stopRequested) return;
const bookmark = bookmarks[index];
bookmark.retries = 0;
bookmark.validCount = 0;
bookmark.invalidCount = 0;
bookmark.timeoutCount = 0;
for (let i = 0; i < maxRetries; i++) {
if (stopRequested) break;
bookmark.retries++;
try {
const isValid = await checkUrl(bookmark.url, timeout);
if (isValid) {
bookmark.validCount++;
} else {
bookmark.invalidCount++;
}
} catch (error) {
if (error.message === 'Timeout') {
bookmark.timeoutCount++;
} else {
bookmark.invalidCount++;
}
}
// 更新UI
updateBookmarkStatus(index);
updateStats();
}
// 根据多数结果确定最终状态
const counts = [
{ status: 'valid', count: bookmark.validCount },
{ status: 'invalid', count: bookmark.invalidCount },
{ status: 'timeout', count: bookmark.timeoutCount }
];
counts.sort((a, b) => b.count - a.count);
bookmark.status = counts[0].count > 0 ? counts[0].status : 'invalid';
updateBookmarkStatus(index);
updateStats();
}
function checkUrl(url, timeout) {
return new Promise((resolve, reject) => {
if (!url || !url.startsWith('http')) {
resolve(false);
return;
}
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
reject(new Error('Timeout'));
}, timeout);
fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
})
.then(response => {
clearTimeout(timeoutId);
// 在no-cors模式下,我们无法读取响应状态,所以假设它是有效的
resolve(true);
})
.catch(error => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
reject(new Error('Timeout'));
} else {
// 其他错误视为无效
resolve(false);
}
});
});
}
function stopChecking() {
stopRequested = true;
stopCheckBtn.disabled = true;
}
function finishChecking() {
isChecking = false;
startCheckBtn.disabled = false;
stopCheckBtn.disabled = true;
if (!stopRequested) {
progressBar.style.width = '100%';
}
// 重新渲染列表以更新分类
renderBookmarkList();
}
function updateBookmarkStatus(index) {
const bookmark = bookmarks[index];
const items = document.querySelectorAll(`.bookmark-checkbox[data-index="${index}"]`);
items.forEach(item => {
const itemContainer = item.closest('.bookmark-item');
if (itemContainer) {
const statusBadge = itemContainer.querySelector('.status-badge');
const statusText = itemContainer.querySelector('.status small');
if (statusBadge) {
// 更新状态徽章
let statusClass = '';
let text = '';
if (bookmark.status === 'valid') {
text = '有效';
statusClass = 'status-valid';
} else if (bookmark.status === 'invalid') {
text = '无效';
statusClass = 'status-invalid';
} else if (bookmark.status === 'timeout') {
text = '超时';
statusClass = 'status-timeout';
} else {
text = '检测中';
statusClass = 'status-pending';
}
statusBadge.className = `status-badge ${statusClass}`;
statusBadge.textContent = text;
// 更新统计信息
if (statusText) {
statusText.textContent = `${bookmark.validCount}√/${bookmark.invalidCount}×/${bookmark.timeoutCount}⌛`;
}
}
}
});
}
function updateStats() {
const total = bookmarks.length;
const completed = bookmarks.filter(b => b.retries > 0).length;
const valid = bookmarks.filter(b => b.status === 'valid').length;
const invalid = bookmarks.filter(b => b.status === 'invalid').length;
const timeout = bookmarks.filter(b => b.status === 'timeout').length;
totalCountEl.textContent = total;
completedCountEl.textContent = completed;
validCountEl.textContent = valid;
invalidCountEl.textContent = invalid;
timeoutCountEl.textContent = timeout;
// 更新进度条
if (total > 0) {
const progress = (completed / total) * 100;
progressBar.style.width = `${progress}%`;
}
}
function resetCheckingState() {
isChecking = false;
stopRequested = false;
startCheckBtn.disabled = false;
stopCheckBtn.disabled = true;
progressBar.style.width = '0%';
progressContainer.style.display = 'none';
stats.style.display = 'none';
}
function testSingleBookmark(index) {
const timeout = parseInt(document.getElementById('timeout').value) || 3000;
const retryCount = parseInt(document.getElementById('retryCount').value) || 3;
bookmarks[index].status = 'pending';
bookmarks[index].retries = 0;
bookmarks[index].validCount = 0;
bookmarks[index].invalidCount = 0;
bookmarks[index].timeoutCount = 0;
updateBookmarkStatus(index);
checkBookmark(index, timeout, retryCount);
}
function deleteInvalidBookmarks() {
if (confirm('确定要删除所有无效链接吗?')) {
bookmarks = bookmarks.filter(bookmark => bookmark.status !== 'invalid');
renderBookmarkList();
updateStats();
}
}
function exportSelectedBookmarks() {
const filter = exportFilter.value;
const selectedBookmarks = bookmarks.filter(bookmark => {
if (!bookmark.selected) return false;
if (filter === 'valid') return bookmark.status === 'valid';
if (filter === 'invalid') return bookmark.status === 'invalid';
if (filter === 'timeout') return bookmark.status === 'timeout';
return true;
});
if (selectedBookmarks.length === 0) {
alert('没有选中的书签');
return;
}
const html = generateBookmarkHTML(selectedBookmarks);
downloadHTML(html, 'bookmarks_export.html');
}
function generateBookmarkHTML(bookmarksToExport) {
let html = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>
`;
bookmarksToExport.forEach(bookmark => {
html += ` <DT><A HREF="${bookmark.url}" ADD_DATE="${Math.floor(Date.now()/1000)}">${bookmark.title || bookmark.url}</A>\n`;
});
html += `</DL><p>`;
return html;
}
function downloadHTML(content, filename) {
const blob = new Blob([content], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function updateSelectAllState() {
const selectedCount = bookmarks.filter(b => b.selected).length;
selectAllCheckbox.checked = selectedCount > 0 && selectedCount === bookmarks.length;
selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < bookmarks.length;
}
function switchTab(tabId) {
// 更新按钮状态
tabButtons.forEach(button => {
button.classList.remove('active');
if (button.getAttribute('data-tab') === tabId) {
button.classList.add('active');
}
});
// 更新内容区域
tabContents.forEach(content => {
content.classList.remove('active');
if (content.id === `tab-${tabId}`) {
content.classList.add('active');
}
});
currentTab = tabId;
}
});
</script>
</body>
</html>