518 lines
18 KiB
JavaScript
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;
|
|
}
|
|
}); |