Files
2026-06-16 11:55:14 +08:00

518 lines
18 KiB
JavaScript

// 2. 全局变量
let currentLang = 'en'; // 默认语言
let currentPage = 1;
let totalPages = 0;
let totalVulns = 0;
let pageSize = 10; // 默认每页10条
let isLoading = false;
let paginationRange = 5; // 显示的页码数量
// 3. 多语言切换功能
function updateI18nElements(resources) {
// 替换普通文本
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (resources[key]) el.textContent = resources[key];
});
// 替换占位符
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
if (resources[key]) el.placeholder = resources[key];
});
}
function switchLanguage(lang) {
if (lang === currentLang) return;
currentLang = lang;
const resources = i18nResources[lang];
// 更新语言元素
updateI18nElements(resources);
// 更新语言按钮状态
document.getElementById('lang-en').className = `lang-switch-btn ${lang === 'en' ? 'active' : 'inactive'}`;
document.getElementById('lang-zh').className = `lang-switch-btn ${lang === 'zh' ? 'active' : 'inactive'}`;
document.getElementById('mobile-lang-en').className = `lang-switch-btn ${lang === 'en' ? 'active' : 'inactive'} text-xs`;
document.getElementById('mobile-lang-zh').className = `lang-switch-btn ${lang === 'zh' ? 'active' : 'inactive'} text-xs`;
// 更新页面语言属性
document.documentElement.lang = lang;
// 重新加载数据以更新语言相关内容
loadVulnerabilityData();
}
// 4. 筛选器功能
function initFilterPanel() {
// 初始化年份筛选器(近5年)
const yearSelect = document.getElementById('filterPublishYear');
const currentYear = new Date().getFullYear();
for (let i = 0; i < 5; i++) {
const year = currentYear - i;
const option = document.createElement('option');
option.value = year;
option.textContent = year;
yearSelect.appendChild(option);
}
// 从URL参数加载筛选条件
loadFiltersFromUrl();
// 筛选面板切换
const toggleBtn = document.getElementById('toggleFilterBtn');
const filterPanel = document.getElementById('filterPanel');
const chevron = document.getElementById('filterChevron');
toggleBtn.addEventListener('click', () => {
filterPanel.classList.toggle('hidden');
chevron.classList.toggle('rotate-180');
});
// 应用筛选
document.getElementById('applyFilterBtn').addEventListener('click', () => {
currentPage = 1; // 重置到第一页
saveFiltersToUrl();
loadVulnerabilityData();
});
// 重置筛选
document.getElementById('resetFilterBtn').addEventListener('click', resetFilters);
document.getElementById('emptyResetBtn').addEventListener('click', resetFilters);
}
function resetFilters() {
// 重置所有筛选器
document.getElementById('filterTitle').value = '';
document.getElementById('filterCvssSeverity').value = '';
document.getElementById('filterPublishYear').value = '';
document.getElementById('filterCvssAv').value = '';
document.getElementById('filterCvssAc').value = '';
document.getElementById('filterCvssPr').value = '';
document.getElementById('filterPageSize').value = '10';
// 更新页大小
pageSize = 10;
// 重置到第一页
currentPage = 1;
// 更新URL和数据
saveFiltersToUrl();
loadVulnerabilityData();
}
function getFilterParams() {
return {
title: document.getElementById('filterTitle').value.trim(),
cvssSeverity: document.getElementById('filterCvssSeverity').value,
publishYear: document.getElementById('filterPublishYear').value,
cvssAv: document.getElementById('filterCvssAv').value,
cvssAc: document.getElementById('filterCvssAc').value,
cvssPr: document.getElementById('filterCvssPr').value,
page: currentPage,
pageSize: pageSize
};
}
function saveFiltersToUrl() {
const params = getFilterParams();
const urlParams = new URLSearchParams();
// 只保存非空参数
Object.entries(params).forEach(([key, value]) => {
if (value) urlParams.append(key, value);
});
// 更新URL但不刷新页面
window.history.pushState({}, '', `${window.location.pathname}?${urlParams.toString()}`);
}
function loadFiltersFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
// 加载筛选条件
document.getElementById('filterTitle').value = urlParams.get('title') || '';
document.getElementById('filterCvssSeverity').value = urlParams.get('cvssSeverity') || '';
document.getElementById('filterPublishYear').value = urlParams.get('publishYear') || '';
document.getElementById('filterCvssAv').value = urlParams.get('cvssAv') || '';
document.getElementById('filterCvssAc').value = urlParams.get('cvssAc') || '';
document.getElementById('filterCvssPr').value = urlParams.get('cvssPr') || '';
// 加载分页参数
const pageParam = urlParams.get('page');
currentPage = pageParam ? parseInt(pageParam) : 1;
// 加载并验证pageSize参数
const pageSizeParam = urlParams.get('pageSize');
if (pageSizeParam) {
// 验证pageSize是否为允许的值
const allowedSizes = [10, 20, 50, 100];
const parsedSize = parseInt(pageSizeParam);
if (allowedSizes.includes(parsedSize)) {
pageSize = parsedSize;
document.getElementById('filterPageSize').value = pageSize;
} else {
// 非法值,使用默认值
pageSize = 10;
document.getElementById('filterPageSize').value = '10';
}
}
}
// 5. 数据加载和渲染
function loadVulnerabilityData() {
if (isLoading) return;
// 显示加载状态
isLoading = true;
showLoadingState();
// 获取筛选参数
const params = getFilterParams();
const urlParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value) urlParams.append(key, value);
});
// 模拟API请求(实际项目中替换为真实API)
fetch(`/API/SVEList.api?${urlParams.toString()}`)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
if (data.code === 0 && data.data) {
renderVulnerabilityList(data.data);
updatePagination(data.page, data.totalPages, data.totalCount);
updateStatistics(data.totalCount, data.page, data.pageSize, data.data.length);
} else {
showErrorState();
}
})
.catch(error => {
console.error('Error loading data:', error);
showErrorState();
})
.finally(() => {
isLoading = false;
});
}
function renderVulnerabilityList(vulnerabilities) {
const listContainer = document.getElementById('vulnList');
// 清除现有列表项(保留状态元素)
const stateElements = ['loadingIndicator', 'emptyState', 'errorState'];
Array.from(listContainer.children).forEach(child => {
if (!stateElements.includes(child.id)) {
listContainer.removeChild(child);
}
});
// 检查是否有数据
if (vulnerabilities.length === 0) {
showEmptyState();
return;
}
// 隐藏所有状态
hideAllStates();
// 渲染列表项
vulnerabilities.forEach(vuln => {
const listItem = createVulnerabilityItem(vuln);
listContainer.appendChild(listItem);
});
}
function createVulnerabilityItem(vuln) {
const li = document.createElement('li');
li.className = 'py-4 px-6 shadow-hover';
// 严重级别样式映射
const severityStyles = {
'CRITICAL': 'bg-danger text-white',
'HIGH': 'bg-danger text-white',
'MEDIUM': 'bg-warning text-white',
'LOW': 'bg-success text-white'
};
// 移动端显示的严重级别文本
const severityText = i18nResources[currentLang][vuln.cvss.severity.toLowerCase()] || vuln.cvss.severity;
li.innerHTML = `
<!-- 桌面端显示 -->
<div class="hidden md:grid grid-cols-12 items-center gap-4">
<div class="col-span-2 font-medium text-primary">${vuln.sveId}</div>
<div class="col-span-4 line-clamp-2">${vuln.vulnTitle}</div>
<div class="col-span-2">${vuln.cvss.score}</div>
<div class="col-span-2">
<span class="severity-badge ${severityStyles[vuln.cvss.severity]}">
${severityText}
</span>
</div>
<div class="col-span-2 text-neutral">${vuln.publishDate}</div>
<div class="col-span-2">
<a target=\"_blank\" href="/SVE/${vuln.sveId}" class="text-secondary hover:text-primary transition-colors text-sm flex items-center">
<span data-i18n="view_details">View Details</span>
<i class="fa fa-angle-right ml-1"></i>
</a>
</div>
</div>
<!-- 移动端显示 -->
<div class="md:hidden">
<div class="flex justify-between items-start mb-2">
<span class="font-medium text-primary">${vuln.sveId}</span>
<span class="severity-badge ${severityStyles[vuln.cvss.severity]}">
${severityText}
</span>
</div>
<div class="font-medium mb-2 line-clamp-2">${vuln.vulnTitle}</div>
<div class="flex flex-wrap gap-3 text-sm text-neutral mb-3">
<span><i class="fa fa-shield mr-1 text-secondary"></i> ${vuln.cvss.score}</span>
<span><i class="fa fa-calendar mr-1 text-secondary"></i> ${vuln.publishDate}</span>
</div>
<a target=\"_blank\" href="/SVE/${vuln.sveId}" class="text-secondary hover:text-primary transition-colors text-sm flex items-center">
<span data-i18n="view_details">View Details</span>
<i class="fa fa-angle-right ml-1"></i>
</a>
</div>
`;
return li;
}
// 6. 状态显示控制
function showLoadingState() {
hideAllStates();
document.getElementById('loadingIndicator').classList.remove('hidden');
}
function showEmptyState() {
hideAllStates();
document.getElementById('emptyState').classList.remove('hidden');
}
function showErrorState() {
hideAllStates();
document.getElementById('errorState').classList.remove('hidden');
}
function hideAllStates() {
document.getElementById('loadingIndicator').classList.add('hidden');
document.getElementById('emptyState').classList.add('hidden');
document.getElementById('errorState').classList.add('hidden');
}
// 7. 分页功能
function initPagination() {
// 上一页
document.getElementById('prevPageBtn').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
saveFiltersToUrl();
loadVulnerabilityData();
scrollToTop();
}
});
// 下一页
document.getElementById('nextPageBtn').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
saveFiltersToUrl();
loadVulnerabilityData();
scrollToTop();
}
});
// 重试按钮
document.getElementById('retryBtn').addEventListener('click', loadVulnerabilityData);
// 页大小变更处理 - 优化版
document.getElementById('filterPageSize').addEventListener('change', function(e) {
const newPageSize = parseInt(this.value);
// 验证新的pageSize是否有效
if ([10, 20, 50, 100].includes(newPageSize) && newPageSize !== pageSize) {
// 计算新的当前页,保持查看相同的起始记录
const firstVisibleRecord = (currentPage - 1) * pageSize + 1;
currentPage = Math.floor((firstVisibleRecord - 1) / newPageSize) + 1;
// 更新pageSize并重新加载数据
pageSize = newPageSize;
saveFiltersToUrl();
loadVulnerabilityData();
}
});
}
function updatePagination(current, total, totalCount) {
currentPage = current;
totalPages = total;
totalVulns = totalCount;
const paginationContainer = document.getElementById('paginationNumbers');
paginationContainer.innerHTML = '';
// 更新按钮状态
document.getElementById('prevPageBtn').disabled = currentPage === 1;
document.getElementById('nextPageBtn').disabled = currentPage === totalPages || totalPages === 0;
// 生成页码
if (totalPages > 0) {
generatePageNumbers();
}
}
function generatePageNumbers() {
const paginationContainer = document.getElementById('paginationNumbers');
paginationContainer.innerHTML = '';
let startPage = Math.max(1, currentPage - Math.floor(paginationRange / 2));
let endPage = startPage + paginationRange - 1;
// 调整结束页
if (endPage > totalPages) {
endPage = totalPages;
startPage = Math.max(1, endPage - paginationRange + 1);
}
// 添加第一页和省略号(如果需要)
if (startPage > 1) {
addPageNumber(1);
if (startPage > 2) {
addEllipsis();
}
}
// 添加中间页码
for (let i = startPage; i <= endPage; i++) {
addPageNumber(i);
}
// 添加最后一页和省略号(如果需要)
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
addEllipsis();
}
addPageNumber(totalPages);
}
}
function addPageNumber(page) {
const paginationContainer = document.getElementById('paginationNumbers');
const button = document.createElement('button');
button.className = `pagination-btn ${page === currentPage ? 'active' : ''}`;
button.textContent = page;
button.addEventListener('click', () => {
if (page !== currentPage) {
currentPage = page;
saveFiltersToUrl();
loadVulnerabilityData();
scrollToTop();
}
});
paginationContainer.appendChild(button);
}
function addEllipsis() {
const paginationContainer = document.getElementById('paginationNumbers');
const ellipsis = document.createElement('span');
ellipsis.className = 'px-2 text-neutral';
ellipsis.textContent = '...';
paginationContainer.appendChild(ellipsis);
}
function updateStatistics(totalCount, currentPage, pageSize, currentCount) {
document.getElementById('totalVulns').textContent = totalCount;
document.getElementById('currentPageInfo').textContent = `${currentPage} / ${totalPages}`;
document.getElementById('totalResults').textContent = totalCount;
const from = (currentPage - 1) * pageSize + 1;
const to = Math.min(currentPage * pageSize, totalCount);
document.getElementById('showingFrom').textContent = from;
document.getElementById('showingTo').textContent = to;
}
// 8. 辅助功能
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
// 9. 移动端菜单
function initMobileMenu() {
document.getElementById('mobile-menu-btn').addEventListener('click', () => {
const mobileMenu = document.getElementById('mobile-menu');
const mobileLangSwitch = document.getElementById('mobile-lang-switch');
mobileMenu.classList.toggle('hidden');
mobileLangSwitch.classList.toggle('hidden');
});
}
// 10. 导航栏滚动效果
function initScrollEffect() {
window.addEventListener('scroll', () => {
const header = document.querySelector('header');
if (window.scrollY > 10) {
header.classList.add('shadow');
header.classList.remove('shadow-sm');
} else {
header.classList.remove('shadow');
header.classList.add('shadow-sm');
}
});
}
// 11. 初始化所有功能
document.addEventListener('DOMContentLoaded', () => {
// 初始化多语言
updateI18nElements(i18nResources[currentLang]);
document.getElementById('lang-en').addEventListener('click', () => switchLanguage('en'));
document.getElementById('lang-zh').addEventListener('click', () => switchLanguage('zh'));
document.getElementById('mobile-lang-en').addEventListener('click', () => switchLanguage('en'));
document.getElementById('mobile-lang-zh').addEventListener('click', () => switchLanguage('zh'));
// 初始化筛选器
initFilterPanel();
// 初始化分页
initPagination();
// 初始化移动端菜单
initMobileMenu();
// 初始化滚动效果
initScrollEffect();
// 加载漏洞数据
loadVulnerabilityData();
});
document.addEventListener('DOMContentLoaded', function() {
// 提取URL中指定参数的值
function getUrlParam(name) {
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`);
const search = window.location.search.substr(1).match(reg);
return search ? decodeURIComponent(search[2]) : null;
}
// 获取URL中的q参数值
const qValue = getUrlParam('q');
// 获取目标输入框/元素
const filterTitleEl = document.getElementById('filterTitle');
// 存在参数且元素存在时,赋值到value属性
if (qValue && filterTitleEl) {
filterTitleEl.value = qValue;
}
});