695 lines
27 KiB
JavaScript
695 lines
27 KiB
JavaScript
function renderPageMSC() {
|
|
// 渲染数学公式
|
|
document.querySelectorAll('pre code.language-math, pre code.language-katex, pre code.language-latex').forEach((block) => {
|
|
const parentPre = block.parentNode;
|
|
const displayMode = true;
|
|
const mathDiv = document.createElement('div');
|
|
mathDiv.className = 'math';
|
|
mathDiv.textContent = block.textContent;
|
|
katex.render(mathDiv.textContent, mathDiv, {
|
|
throwOnError: false,
|
|
displayMode
|
|
});
|
|
parentPre.parentNode.replaceChild(mathDiv, parentPre);
|
|
});
|
|
|
|
// 渲染流程图和序列图
|
|
document.querySelectorAll('pre code.language-flow, pre code.language-seq').forEach((block) => {
|
|
const parentPre = block.parentNode;
|
|
const diagram = document.createElement('div');
|
|
diagram.className = 'mermaid';
|
|
diagram.textContent = block.textContent;
|
|
parentPre.parentNode.replaceChild(diagram, parentPre);
|
|
});
|
|
mermaid.init();
|
|
|
|
// 为普通代码块添加行号、标题、复制按钮和演示按钮
|
|
document.querySelectorAll('pre code').forEach((block) => {
|
|
const lang = block.getAttribute('class')?.replace('hljs', '').trim();
|
|
const supportedLanguages = ['javascript', 'html', 'plaintext']; // 定义支持高亮的语言
|
|
|
|
// 只对支持的语言进行高亮处理
|
|
if (supportedLanguages.includes(lang)) {
|
|
hljs.highlightElement(block);
|
|
}
|
|
|
|
// 跳过数学公式和图表代码块
|
|
if (['math', 'katex', 'latex', 'flow', 'seq'].includes(lang)) {
|
|
return;
|
|
}
|
|
|
|
// 添加行号
|
|
if (!block.classList.contains('nohighlight')) {
|
|
const lines = block.textContent.split('\n').length;
|
|
let lineNumbersHTML = '';
|
|
|
|
for (let i = 0; i < lines; i++) {
|
|
lineNumbersHTML += '<span></span>';
|
|
}
|
|
|
|
const lineNumbersWrapper = document.createElement('div');
|
|
lineNumbersWrapper.className = 'line-numbers';
|
|
lineNumbersWrapper.innerHTML = lineNumbersHTML;
|
|
|
|
block.className += ' line-numbers-rows';
|
|
block.parentNode.insertBefore(lineNumbersWrapper, block);
|
|
}
|
|
|
|
// 添加代码块标题
|
|
const codeTitle = document.createElement('div');
|
|
codeTitle.className = 'code-title';
|
|
codeTitle.textContent = lang || 'plaintext';
|
|
|
|
// 添加复制按钮
|
|
const copyButton = document.createElement('button');
|
|
copyButton.className = 'copy-btn';
|
|
copyButton.innerHTML = '<i class="fa fa-copy"></i>';
|
|
copyButton.addEventListener('click', () => {
|
|
const text = block.textContent;
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
copyButton.innerHTML = '<i class="fa fa-check"></i>';
|
|
setTimeout(() => {
|
|
copyButton.innerHTML = '<i class="fa fa-copy"></i>';
|
|
}, 1000);
|
|
}).catch((err) => {
|
|
console.error('复制失败: ', err);
|
|
});
|
|
});
|
|
codeTitle.appendChild(copyButton);
|
|
|
|
// 添加演示按钮
|
|
if (lang === 'javascript' || lang === 'html') {
|
|
const demoButton = document.createElement('button');
|
|
demoButton.className = 'demo-btn';
|
|
demoButton.innerHTML = '<i class="fa fa-external-link"></i>';
|
|
demoButton.addEventListener('click', () => {
|
|
const code = block.textContent;
|
|
const newWindow = window.open('', '_blank');
|
|
if (lang === 'javascript') {
|
|
newWindow.document.write(`<html><body><script>${code}<\/script></body></html>`);
|
|
} else if (lang === 'html') {
|
|
newWindow.document.write(code);
|
|
}
|
|
});
|
|
codeTitle.appendChild(demoButton);
|
|
}
|
|
|
|
block.parentNode.insertBefore(codeTitle, block);
|
|
});
|
|
|
|
renderMathInElement(document.body);
|
|
}
|
|
|
|
// 3. 多语言核心功能
|
|
let currentLang = 'en'; // 默认语言
|
|
|
|
// 抽离:更新所有data-i18n元素的语言
|
|
function updateI18nElements(resources) {
|
|
// 1. 替换普通文本(data-i18n 属性)
|
|
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
const key = el.getAttribute('data-i18n');
|
|
if (resources[key]) el.textContent = resources[key];
|
|
});
|
|
|
|
// 2. 替换占位符(data-i18n-placeholder 属性)
|
|
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
|
const key = el.getAttribute('data-i18n-placeholder');
|
|
if (resources[key]) el.placeholder = resources[key];
|
|
});
|
|
|
|
// 3. 替换标题属性(data-i18n-title 属性)
|
|
document.querySelectorAll('[data-i18n-title]').forEach(el => {
|
|
const key = el.getAttribute('data-i18n-title');
|
|
if (resources[key]) el.title = resources[key];
|
|
});
|
|
}
|
|
|
|
// 切换语言(调用抽离的updateI18nElements,支持动态元素)
|
|
function switchLanguage(lang) {
|
|
if (lang === currentLang) return;
|
|
currentLang = lang;
|
|
const resources = i18nResources[lang];
|
|
|
|
// 第一步:更新所有data-i18n元素(包括动态生成的)
|
|
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`;
|
|
|
|
// 第三步:更新HTML文档语言属性
|
|
document.documentElement.lang = lang;
|
|
|
|
// 第四步:重新渲染依赖语言的动态内容(渲染后会生成新的data-i18n元素)
|
|
renderVulnDescription();
|
|
renderAffectedProducts();
|
|
renderVulnDetailsCard(); // 动态生成漏洞详情的data-i18n元素
|
|
renderRelatedSves();
|
|
updateCvssSeverityStyle();
|
|
renderCvssChart();
|
|
|
|
// 关键:重新更新一次data-i18n元素(处理动态渲染新增的元素)
|
|
updateI18nElements(resources);
|
|
}
|
|
|
|
// 4. 动态渲染核心函数
|
|
// 4.1 基础信息渲染(无需语言依赖,初始化时渲染一次)
|
|
function renderBasicInfo() {
|
|
// 页面标题
|
|
document.getElementById('pageTitle').textContent = `${sveData.sveId} - ${sveData.vulnTitle} - SVE Database`;
|
|
|
|
// 面包屑导航
|
|
document.getElementById('breadCrumbSveId').textContent = sveData.sveId;
|
|
|
|
// SVE ID和标题
|
|
document.getElementById('sveIdDisplay').textContent = sveData.sveId;
|
|
document.getElementById('vulnTitle').textContent = sveData.vulnTitle;
|
|
|
|
// 日期
|
|
document.getElementById('publishDate').textContent = sveData.publishDate;
|
|
const lastModifiedDate = sveData.lastModified;
|
|
document.getElementById('lastModified').textContent = lastModifiedDate;
|
|
|
|
// CVSS基础信息(评分、向量)
|
|
document.getElementById('cvssScore').textContent = sveData.cvss.score;
|
|
document.getElementById('cvssSeverity').textContent = i18nResources[currentLang][sveData.cvss.severity.toLowerCase()] || sveData.cvss.severity;
|
|
document.getElementById('cvssVector').textContent = sveData.cvss.vector;
|
|
|
|
// 解决方案(Markdown格式)
|
|
const solutionsContainer = document.getElementById('solutionsContent');
|
|
solutionsContainer.innerHTML = marked.parse(sveData.solutions);
|
|
|
|
// 参考资料
|
|
renderReferences();
|
|
|
|
// 更新CVSS严重级别样式
|
|
updateCvssSeverityStyle();
|
|
}
|
|
|
|
// 4.2 漏洞描述渲染(依赖语言,多语言切换时需重新渲染)
|
|
function renderVulnDescription() {
|
|
const descContainer = document.getElementById('vulnDescription');
|
|
const html = marked.parse(sveData.vulnDescription);
|
|
descContainer.innerHTML = html;
|
|
}
|
|
|
|
// 4.3 受影响产品渲染(依赖语言,多语言切换时需重新渲染)
|
|
function renderAffectedProducts() {
|
|
const productsList = document.getElementById('affectedProductsList');
|
|
productsList.innerHTML = '';
|
|
|
|
sveData.products.forEach(product => {
|
|
const li = document.createElement('li');
|
|
li.className = 'flex items-start';
|
|
|
|
// 危险图标(有漏洞)
|
|
li.innerHTML = `
|
|
<i class="fa fa-check-circle text-danger mt-1 mr-2"></i>
|
|
<div>
|
|
<span class="font-medium text-dark">${product.name}</span> ${product.version}
|
|
<div class="text-xs text-neutral mt-1">
|
|
<span data-i18n="fixed_version">Fixed Version:</span>
|
|
<span class="font-medium">${product.fixedVersion || i18nResources[currentLang].no_fixed_version}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
productsList.appendChild(li);
|
|
});
|
|
}
|
|
|
|
// 4.4 参考资料渲染(无需语言依赖,初始化时渲染一次)
|
|
function renderReferences() {
|
|
const referencesList = document.getElementById('referencesList');
|
|
referencesList.innerHTML = '';
|
|
|
|
sveData.references.forEach(ref => {
|
|
const li = document.createElement('li');
|
|
li.className = 'flex items-start';
|
|
|
|
// 参考资料类型映射(多语言)
|
|
const refTypeMap = {
|
|
'vendor': i18nResources[currentLang].vendor_advisory,
|
|
'tech_doc': i18nResources[currentLang].technical_documentation || 'Technical Documentation:',
|
|
'sve_guide': i18nResources[currentLang].sve_guide || 'SVE Guidance:',
|
|
'research': i18nResources[currentLang].research_paper || 'Research Paper:',
|
|
'other': i18nResources[currentLang].other || 'Other:'
|
|
};
|
|
const refTypeText = refTypeMap[ref.type] || ref.type + ':';
|
|
|
|
li.innerHTML = `
|
|
<span class="font-medium text-dark w-24">${refTypeText}</span>
|
|
<a href="${ref.url}" target="_blank" class="text-secondary hover:underline flex-1">
|
|
${ref.title || ref.url}
|
|
</a>
|
|
`;
|
|
referencesList.appendChild(li);
|
|
});
|
|
}
|
|
|
|
// 4.5 漏洞详情卡片渲染(依赖语言,多语言切换时需重新渲染)
|
|
function renderVulnDetailsCard() {
|
|
const detailsCard = document.getElementById('vulnDetailsCard');
|
|
detailsCard.innerHTML = '';
|
|
|
|
// CVSS指标映射(用于显示完整文本)
|
|
const cvssMappings = {
|
|
av: {
|
|
'N': 'av_network',
|
|
'A': 'av_adjacent',
|
|
'L': 'av_local',
|
|
'P': 'av_physical'
|
|
},
|
|
ac: {
|
|
'L': 'ac_low',
|
|
'H': 'ac_high'
|
|
},
|
|
pr: {
|
|
'N': 'pr_none',
|
|
'L': 'pr_low',
|
|
'H': 'pr_high'
|
|
},
|
|
ui: {
|
|
'N': 'ui_none',
|
|
'R': 'ui_required'
|
|
},
|
|
s: {
|
|
'U': 's_unchanged',
|
|
'C': 's_changed'
|
|
},
|
|
c: {
|
|
'N': 'c_none',
|
|
'L': 'c_low',
|
|
'H': 'c_high'
|
|
},
|
|
i: {
|
|
'N': 'i_none',
|
|
'L': 'i_low',
|
|
'H': 'i_high'
|
|
},
|
|
a: {
|
|
'N': 'a_none',
|
|
'L': 'a_low',
|
|
'H': 'a_high'
|
|
}
|
|
};
|
|
|
|
// 状态(原代码中span的文本写死,改为通过data-i18n动态获取)
|
|
const statusItem = document.createElement('div');
|
|
statusItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="status">Status</div>
|
|
<div class="flex items-center">
|
|
<span class="px-2 py-1 rounded-full bg-success/10 text-success text-xs font-medium" data-i18n="publicly_disclosed">
|
|
${i18nResources[currentLang].publicly_disclosed}
|
|
</span>
|
|
</div>
|
|
`;
|
|
detailsCard.appendChild(statusItem);
|
|
|
|
// 攻击向量
|
|
const avItem = document.createElement('div');
|
|
const avText = i18nResources[currentLang][cvssMappings.av[sveData.cvss.av]];
|
|
avItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="attack_vector">Attack Vector</div>
|
|
<div class="text-dark font-medium">${avText}</div>
|
|
`;
|
|
detailsCard.appendChild(avItem);
|
|
|
|
// 攻击复杂度
|
|
const acItem = document.createElement('div');
|
|
const acText = i18nResources[currentLang][cvssMappings.ac[sveData.cvss.ac]];
|
|
acItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="attack_complexity">Attack Complexity</div>
|
|
<div class="text-dark font-medium">${acText}</div>
|
|
`;
|
|
detailsCard.appendChild(acItem);
|
|
|
|
// 所需权限
|
|
const prItem = document.createElement('div');
|
|
const prText = i18nResources[currentLang][cvssMappings.pr[sveData.cvss.pr]];
|
|
prItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="privileges_required">Privileges Required</div>
|
|
<div class="text-dark font-medium">${prText}</div>
|
|
`;
|
|
detailsCard.appendChild(prItem);
|
|
|
|
// 用户交互
|
|
const uiItem = document.createElement('div');
|
|
const uiText = i18nResources[currentLang][cvssMappings.ui[sveData.cvss.ui]];
|
|
uiItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="user_interaction">User Interaction</div>
|
|
<div class="text-dark font-medium">${uiText}</div>
|
|
`;
|
|
detailsCard.appendChild(uiItem);
|
|
|
|
// 影响范围
|
|
const sItem = document.createElement('div');
|
|
const sText = i18nResources[currentLang][cvssMappings.s[sveData.cvss.s]];
|
|
sItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="scope">Scope</div>
|
|
<div class="text-dark font-medium">${sText}</div>
|
|
`;
|
|
detailsCard.appendChild(sItem);
|
|
|
|
// 机密性影响
|
|
const cItem = document.createElement('div');
|
|
const cText = i18nResources[currentLang][cvssMappings.c[sveData.cvss.c]];
|
|
cItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="confidentiality_impact">Confidentiality Impact</div>
|
|
<div class="text-dark font-medium">${cText}</div>
|
|
`;
|
|
detailsCard.appendChild(cItem);
|
|
|
|
// 完整性影响
|
|
const iItem = document.createElement('div');
|
|
const iText = i18nResources[currentLang][cvssMappings.i[sveData.cvss.i]];
|
|
iItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="integrity_impact">Integrity Impact</div>
|
|
<div class="text-dark font-medium">${iText}</div>
|
|
`;
|
|
detailsCard.appendChild(iItem);
|
|
|
|
// 可用性影响
|
|
const aItem = document.createElement('div');
|
|
const aText = i18nResources[currentLang][cvssMappings.a[sveData.cvss.a]];
|
|
aItem.innerHTML = `
|
|
<div class="text-neutral mb-1" data-i18n="availability_impact">Availability Impact</div>
|
|
<div class="text-dark font-medium">${aText}</div>
|
|
`;
|
|
detailsCard.appendChild(aItem);
|
|
}
|
|
|
|
// 4.6 相关SVE渲染(依赖语言,多语言切换时需重新渲染)
|
|
function renderRelatedSves() {
|
|
const relatedSvesList = document.getElementById('relatedSvesList');
|
|
const viewAllBtn = document.getElementById('viewAllRelatedBtn');
|
|
|
|
relatedSvesList.innerHTML = '';
|
|
|
|
if (sveData.relatedSves && sveData.relatedSves.length > 0) {
|
|
// 有相关SVE数据
|
|
sveData.relatedSves.forEach(relatedSve => {
|
|
const li = document.createElement('li');
|
|
li.innerHTML = `
|
|
<a href="#" class="flex items-center justify-between group">
|
|
<span class="text-secondary group-hover:underline">${relatedSve.sveId}</span>
|
|
<span class="text-xs px-2 py-0.5 rounded-full ${getSeverityClass(relatedSve.severity)}" data-i18n="${relatedSve.severity.toLowerCase()}">
|
|
${i18nResources[currentLang][relatedSve.severity.toLowerCase()] || relatedSve.severity}
|
|
</span>
|
|
</a>
|
|
`;
|
|
relatedSvesList.appendChild(li);
|
|
});
|
|
|
|
// 显示"查看全部"按钮(如果数据超过4条)
|
|
if (sveData.relatedSves.length > 4) {
|
|
viewAllBtn.classList.remove('hidden');
|
|
} else {
|
|
viewAllBtn.classList.add('hidden');
|
|
}
|
|
} else {
|
|
// 无相关SVE数据
|
|
const noDataDiv = document.createElement('div');
|
|
noDataDiv.className = 'text-neutral text-sm italic';
|
|
noDataDiv.setAttribute('data-i18n', 'no_related_sves');
|
|
noDataDiv.textContent = i18nResources[currentLang].no_related_sves;
|
|
relatedSvesList.appendChild(noDataDiv);
|
|
|
|
// 隐藏"查看全部"按钮
|
|
viewAllBtn.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
// 1. 全局存储雷达图实例(用于切换语言时销毁旧图表)
|
|
let cvssChartInstance = null;
|
|
|
|
// 2. 抽离:根据当前语言动态生成雷达图配置
|
|
function getCvssChartConfig() {
|
|
// CVSS指标值映射(用于图表数据,与原逻辑一致)
|
|
const cvssValueMap = {
|
|
'N': 2, 'A': 1, 'L': 0.5, 'P': 0, // AV
|
|
'L': 0, 'H': 1, // AC
|
|
'N': 0, 'L': 1, 'H': 2, // PR
|
|
'N': 0, 'R': 1, // UI
|
|
'U': 0, 'C': 1, // S
|
|
'N': 0, 'L': 1, 'H': 2, // C/I/A
|
|
};
|
|
|
|
// 3. 动态获取当前语言的雷达图轴标签(核心:多语言适配)
|
|
const cvssLabels = [
|
|
i18nResources[currentLang].attack_vector, // 攻击向量
|
|
i18nResources[currentLang].attack_complexity, // 攻击复杂度
|
|
i18nResources[currentLang].privileges_required,// 所需权限
|
|
i18nResources[currentLang].user_interaction, // 用户交互
|
|
i18nResources[currentLang].scope, // 影响范围
|
|
i18nResources[currentLang].confidentiality_impact, // 机密性影响
|
|
i18nResources[currentLang].integrity_impact, // 完整性影响
|
|
i18nResources[currentLang].availability_impact // 可用性影响
|
|
];
|
|
|
|
// 4. 动态获取当前语言的提示框(Tooltip)文本(核心:多语言适配)
|
|
const cvssMappingsI18n = {
|
|
av: {
|
|
'N': i18nResources[currentLang].av_network,
|
|
'A': i18nResources[currentLang].av_adjacent,
|
|
'L': i18nResources[currentLang].av_local,
|
|
'P': i18nResources[currentLang].av_physical
|
|
},
|
|
ac: {
|
|
'L': i18nResources[currentLang].ac_low,
|
|
'H': i18nResources[currentLang].ac_high
|
|
},
|
|
pr: {
|
|
'N': i18nResources[currentLang].pr_none,
|
|
'L': i18nResources[currentLang].pr_low,
|
|
'H': i18nResources[currentLang].pr_high
|
|
},
|
|
ui: {
|
|
'N': i18nResources[currentLang].ui_none,
|
|
'R': i18nResources[currentLang].ui_required
|
|
},
|
|
s: {
|
|
'U': i18nResources[currentLang].s_unchanged,
|
|
'C': i18nResources[currentLang].s_changed
|
|
},
|
|
c: {
|
|
'N': i18nResources[currentLang].c_none,
|
|
'L': i18nResources[currentLang].c_low,
|
|
'H': i18nResources[currentLang].c_high
|
|
},
|
|
i: {
|
|
'N': i18nResources[currentLang].i_none,
|
|
'L': i18nResources[currentLang].i_low,
|
|
'H': i18nResources[currentLang].i_high
|
|
},
|
|
a: {
|
|
'N': i18nResources[currentLang].a_none,
|
|
'L': i18nResources[currentLang].a_low,
|
|
'H': i18nResources[currentLang].a_high
|
|
}
|
|
};
|
|
|
|
// 提取CVSS指标数据(与原逻辑一致)
|
|
const cvssValues = [
|
|
cvssValueMap[sveData.cvss.av], // AV
|
|
cvssValueMap[sveData.cvss.ac], // AC
|
|
cvssValueMap[sveData.cvss.pr], // PR
|
|
cvssValueMap[sveData.cvss.ui], // UI
|
|
cvssValueMap[sveData.cvss.s], // S
|
|
cvssValueMap[sveData.cvss.c], // C
|
|
cvssValueMap[sveData.cvss.i], // I
|
|
cvssValueMap[sveData.cvss.a] // A
|
|
];
|
|
|
|
// 返回适配当前语言的图表配置
|
|
return {
|
|
type: 'radar',
|
|
data: {
|
|
labels: cvssLabels, // 动态语言标签
|
|
datasets: [{
|
|
label: 'CVSS v3.1',
|
|
data: cvssValues,
|
|
backgroundColor: getSeverityColor(sveData.cvss.severity, 0.2),
|
|
borderColor: getSeverityColor(sveData.cvss.severity, 1),
|
|
pointBackgroundColor: getSeverityColor(sveData.cvss.severity, 1),
|
|
pointBorderColor: '#fff',
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: getSeverityColor(sveData.cvss.severity, 1)
|
|
}]
|
|
},
|
|
options: {
|
|
scales: {
|
|
r: {
|
|
angleLines: { display: true },
|
|
suggestedMin: 0,
|
|
suggestedMax: 2,
|
|
ticks: { stepSize: 0.5 }
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
// 动态语言提示框文本(使用当前语言的映射)
|
|
const index = context.dataIndex;
|
|
const metricKey = ['av', 'ac', 'pr', 'ui', 's', 'c', 'i', 'a'][index];
|
|
const metricValue = sveData.cvss[metricKey];
|
|
const metricText = cvssMappingsI18n[metricKey][metricValue];
|
|
return `${cvssLabels[index]}: ${metricText}`; // 标签+值均为当前语言
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// 重构:雷达图渲染函数(支持销毁旧图表 + 用当前语言重建)
|
|
function renderCvssChart() {
|
|
const ctx = document.getElementById('cvssChart').getContext('2d');
|
|
|
|
// 1. 先销毁旧图表(避免canvas叠加)
|
|
if (cvssChartInstance) {
|
|
cvssChartInstance.destroy();
|
|
}
|
|
|
|
// 2. 用当前语言的配置创建新图表
|
|
cvssChartInstance = new Chart(ctx, getCvssChartConfig());
|
|
}
|
|
|
|
// 5. 辅助工具函数
|
|
// 5.1 根据严重级别获取颜色
|
|
function getSeverityColor(severity, alpha = 1) {
|
|
const severityLower = severity.toLowerCase();
|
|
switch (severityLower) {
|
|
case 'critical':
|
|
return `rgba(217, 48, 37, ${alpha})`; // 红色
|
|
case 'high':
|
|
return `rgba(217, 48, 37, ${alpha})`; // 红色
|
|
case 'medium':
|
|
return `rgba(244, 180, 0, ${alpha})`; // 黄色
|
|
case 'low':
|
|
return `rgba(30, 142, 62, ${alpha})`; // 绿色
|
|
default:
|
|
return `rgba(95, 99, 104, ${alpha})`; // 灰色
|
|
}
|
|
}
|
|
|
|
// 5.2 根据严重级别获取样式类
|
|
function getSeverityClass(severity) {
|
|
const severityLower = severity.toLowerCase();
|
|
switch (severityLower) {
|
|
case 'critical':
|
|
return 'bg-danger text-white';
|
|
case 'high':
|
|
return 'bg-danger text-white';
|
|
case 'medium':
|
|
return 'bg-warning text-white';
|
|
case 'low':
|
|
return 'bg-success text-white';
|
|
default:
|
|
return 'bg-neutral text-white';
|
|
}
|
|
}
|
|
|
|
// 5.3 更新CVSS严重级别样式
|
|
function updateCvssSeverityStyle() {
|
|
const cvssScoreEl = document.getElementById('cvssScore');
|
|
const cvssSeverityEl = document.getElementById('cvssSeverity');
|
|
const severity = sveData.cvss.severity.toLowerCase();
|
|
|
|
// 设置颜色
|
|
cvssScoreEl.className = `text-3xl font-bold ${getSeverityClass(severity).replace('/10', '')}`;
|
|
cvssSeverityEl.className = `${getSeverityClass(severity)}`;
|
|
}
|
|
|
|
// 6. 原有功能保留
|
|
// 6.1 复制SVE ID
|
|
function initCopyBtn() {
|
|
document.getElementById('copyBtn').addEventListener('click', function() {
|
|
const sveId = sveData.sveId;
|
|
navigator.clipboard.writeText(sveId).then(() => {
|
|
const message = document.getElementById('copyMessage');
|
|
message.classList.remove('hidden');
|
|
setTimeout(() => {
|
|
message.classList.add('hidden');
|
|
}, 2000);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 6.2 移动端导航菜单
|
|
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');
|
|
});
|
|
}
|
|
|
|
// 6.3 导航栏滚动效果
|
|
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');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 6.4 绑定语言切换事件
|
|
function initLanguageSwitch() {
|
|
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'));
|
|
}
|
|
|
|
// 7. 初始化所有功能
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// 初始化Marked.js(用于Markdown渲染)
|
|
marked.setOptions({
|
|
breaks: true,
|
|
highlight: (code, lang) => {
|
|
if (lang && Prism.languages[lang]) {
|
|
return Prism.highlight(code, Prism.languages[lang], lang);
|
|
}
|
|
return Prism.highlight(code, Prism.languages.markup, 'markup');
|
|
}
|
|
});
|
|
|
|
SVEID = window.location.pathname.split("/")[2];
|
|
fetch(`/API/GetInformation.api?SVEID=${SVEID}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.code) {
|
|
sveData = data;
|
|
renderBasicInfo();
|
|
renderVulnDescription();
|
|
renderAffectedProducts();
|
|
renderVulnDetailsCard();
|
|
renderRelatedSves();
|
|
renderCvssChart();
|
|
renderPageMSC();
|
|
} else {
|
|
alert("获取SVE详情失败");
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error("网络错误:", error);
|
|
alert("网络原因获取SVE详情失败,或渲染失败");
|
|
});
|
|
|
|
// 初始化原有功能
|
|
initCopyBtn();
|
|
initMobileMenu();
|
|
initScrollEffect();
|
|
initLanguageSwitch();
|
|
}); |