|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>代码质量检查工具</title>
|
|
|
<!-- 添加元标签防止缓存和刷新 -->
|
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
|
<meta http-equiv="Expires" content="0">
|
|
|
<!-- 防止自动刷新 -->
|
|
|
<meta http-equiv="refresh" content="999999">
|
|
|
<style>
|
|
|
/* 保持之前的样式不变 */
|
|
|
body {
|
|
|
font-family: 'Microsoft YaHei', sans-serif;
|
|
|
max-width: 800px;
|
|
|
margin: 0 auto;
|
|
|
padding: 20px;
|
|
|
background-color: #f8f9fa;
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
background-color: white;
|
|
|
border-radius: 8px;
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
padding: 20px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
h1 {
|
|
|
color: #2c3e50;
|
|
|
text-align: center;
|
|
|
margin-bottom: 30px;
|
|
|
}
|
|
|
|
|
|
.tool-checkbox {
|
|
|
margin: 15px 0;
|
|
|
padding: 10px;
|
|
|
background-color: #f1f8ff;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.file-upload {
|
|
|
margin: 20px 0;
|
|
|
}
|
|
|
|
|
|
.download-btn {
|
|
|
display: inline-block;
|
|
|
padding: 10px 20px;
|
|
|
background-color: #4CAF50;
|
|
|
color: white;
|
|
|
text-decoration: none;
|
|
|
border-radius: 4px;
|
|
|
font-weight: bold;
|
|
|
transition: background-color 0.3s;
|
|
|
margin-top: 15px;
|
|
|
}
|
|
|
|
|
|
.download-btn:hover {
|
|
|
background-color: #45a049;
|
|
|
}
|
|
|
|
|
|
#report-area {
|
|
|
margin-top: 20px;
|
|
|
padding: 15px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 5px;
|
|
|
display: none;
|
|
|
background-color: #e8f5e9;
|
|
|
}
|
|
|
|
|
|
#result {
|
|
|
background-color: #f5f5f5;
|
|
|
padding: 15px;
|
|
|
border-radius: 5px;
|
|
|
max-height: 400px;
|
|
|
overflow: auto;
|
|
|
white-space: pre-wrap;
|
|
|
font-family: monospace;
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
.loader {
|
|
|
border: 4px solid #f3f3f3;
|
|
|
border-top: 4px solid #3498db;
|
|
|
border-radius: 50%;
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
animation: spin 1s linear infinite;
|
|
|
margin: 20px auto;
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
@keyframes spin {
|
|
|
0% {
|
|
|
transform: rotate(0deg);
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
transform: rotate(360deg);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.btn {
|
|
|
background-color: #3498db;
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 10px 20px;
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
font-size: 16px;
|
|
|
transition: background-color 0.3s;
|
|
|
}
|
|
|
|
|
|
.btn:hover {
|
|
|
background-color: #2980b9;
|
|
|
}
|
|
|
|
|
|
.btn:disabled {
|
|
|
background-color: #95a5a6;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
<h1>代码质量检查工具</h1>
|
|
|
|
|
|
<div id="toolContainer">
|
|
|
<fieldset>
|
|
|
<legend>选择工具:</legend>
|
|
|
<div class="tool-checkbox">
|
|
|
<input type="checkbox" id="bandit" name="tools" value="bandit" checked>
|
|
|
<label for="bandit">Bandit</label>
|
|
|
</div>
|
|
|
<div class="tool-checkbox">
|
|
|
<input type="checkbox" id="flake8" name="tools" value="flake8" checked>
|
|
|
<label for="flake8">Flake8</label>
|
|
|
</div>
|
|
|
<div class="tool-checkbox">
|
|
|
<input type="checkbox" id="pylint" name="tools" value="pylint" checked>
|
|
|
<label for="pylint">Pylint</label>
|
|
|
</div>
|
|
|
</fieldset>
|
|
|
|
|
|
<div class="file-upload">
|
|
|
<label for="file">上传代码文件:</label>
|
|
|
<input type="file" id="file" name="file" accept=".py" aria-label="选择Python文件">
|
|
|
</div>
|
|
|
|
|
|
<button type="button" id="submitBtn" class="btn">提交检查</button>
|
|
|
|
|
|
<!-- 调试按钮 -->
|
|
|
<div style="margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 4px;">
|
|
|
<button type="button" id="toggleRefreshBtn" class="btn"
|
|
|
style="background-color: #e74c3c;">启用防刷新</button>
|
|
|
<span id="refreshStatus" style="margin-left: 10px;">防刷新: 禁用</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="loader" id="loader"></div>
|
|
|
|
|
|
<div id="report-area" aria-live="polite">
|
|
|
<h2>检查报告</h2>
|
|
|
<p>报告已生成,点击下载:</p>
|
|
|
<a id="report-download" href="#" class="download-btn">下载报告</a>
|
|
|
<button id="close-report" class="btn" style="margin-left: 10px;">关闭</button>
|
|
|
</div>
|
|
|
|
|
|
<h2>结果:</h2>
|
|
|
<pre id="result" role="log" aria-live="polite"></pre>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
// 创建独立的事件处理模块
|
|
|
const App = (() => {
|
|
|
// DOM元素引用
|
|
|
const elements = {
|
|
|
submitBtn: document.getElementById('submitBtn'),
|
|
|
resultPre: document.getElementById('result'),
|
|
|
reportArea: document.getElementById('report-area'),
|
|
|
reportLink: document.getElementById('report-download'),
|
|
|
closeReportBtn: document.getElementById('close-report'),
|
|
|
loader: document.getElementById('loader'),
|
|
|
fileInput: document.getElementById('file'),
|
|
|
banditCheckbox: document.getElementById('bandit'),
|
|
|
flake8Checkbox: document.getElementById('flake8'),
|
|
|
pylintCheckbox: document.getElementById('pylint'),
|
|
|
toggleRefreshBtn: document.getElementById('toggleRefreshBtn'),
|
|
|
refreshStatus: document.getElementById('refreshStatus')
|
|
|
};
|
|
|
|
|
|
// 状态管理
|
|
|
let isProcessing = false;
|
|
|
let preventRefresh = false; // 防刷新状态
|
|
|
|
|
|
// 设置防刷新拦截器
|
|
|
const setupRefreshPrevention = () => {
|
|
|
// 拦截所有点击事件
|
|
|
document.addEventListener('click', function (event) {
|
|
|
if (preventRefresh) {
|
|
|
console.log('拦截到点击事件:', event.target.tagName, event.target.id || event.target.textContent);
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
return false;
|
|
|
}
|
|
|
}, true);
|
|
|
|
|
|
// 拦截所有表单提交
|
|
|
document.addEventListener('submit', function (event) {
|
|
|
console.log('拦截到表单提交事件');
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
return false;
|
|
|
}, true);
|
|
|
|
|
|
// 拦截键盘事件(F5等)
|
|
|
document.addEventListener('keydown', function (event) {
|
|
|
if (preventRefresh && (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.ctrlKey && event.key === 'R'))) {
|
|
|
console.log('拦截到刷新快捷键:', event.key);
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
return false;
|
|
|
}
|
|
|
}, true);
|
|
|
|
|
|
// 拦截所有链接点击
|
|
|
document.addEventListener('click', function (event) {
|
|
|
if (preventRefresh && event.target.tagName === 'A') {
|
|
|
console.log('拦截到链接点击:', event.target.href);
|
|
|
// 只有非下载链接才阻止
|
|
|
if (!event.target.href.includes('reports/')) {
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}, true);
|
|
|
|
|
|
// 添加页面刷新监听器
|
|
|
window.addEventListener('beforeunload', function (event) {
|
|
|
console.log('页面即将被刷新或关闭');
|
|
|
if (preventRefresh) {
|
|
|
event.preventDefault();
|
|
|
event.returnValue = '';
|
|
|
return '';
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 初始化应用
|
|
|
const init = () => {
|
|
|
// 初始隐藏报告区域
|
|
|
elements.reportArea.style.display = 'none';
|
|
|
|
|
|
// 设置防刷新拦截器
|
|
|
setupRefreshPrevention();
|
|
|
|
|
|
// 绑定事件监听器
|
|
|
bindEventListeners();
|
|
|
};
|
|
|
|
|
|
// 绑定所有事件监听器
|
|
|
const bindEventListeners = () => {
|
|
|
// 关闭报告区域
|
|
|
elements.closeReportBtn.addEventListener('click', handleCloseReport);
|
|
|
|
|
|
// 提交按钮点击事件
|
|
|
elements.submitBtn.addEventListener('click', handleSubmit);
|
|
|
|
|
|
// 下载链接点击事件
|
|
|
elements.reportLink.addEventListener('click', handleDownload);
|
|
|
|
|
|
// 切换防刷新状态
|
|
|
elements.toggleRefreshBtn.addEventListener('click', handleToggleRefresh);
|
|
|
};
|
|
|
|
|
|
// 处理关闭报告
|
|
|
const handleCloseReport = (event) => {
|
|
|
event.preventDefault();
|
|
|
elements.reportArea.style.display = 'none';
|
|
|
};
|
|
|
|
|
|
// 处理切换防刷新状态
|
|
|
const handleToggleRefresh = (event) => {
|
|
|
event.preventDefault();
|
|
|
preventRefresh = !preventRefresh;
|
|
|
|
|
|
if (preventRefresh) {
|
|
|
elements.toggleRefreshBtn.textContent = '禁用防刷新';
|
|
|
elements.toggleRefreshBtn.style.backgroundColor = '#27ae60';
|
|
|
elements.refreshStatus.textContent = '防刷新: 启用';
|
|
|
console.log('防刷新已启用');
|
|
|
} else {
|
|
|
elements.toggleRefreshBtn.textContent = '启用防刷新';
|
|
|
elements.toggleRefreshBtn.style.backgroundColor = '#e74c3c';
|
|
|
elements.refreshStatus.textContent = '防刷新: 禁用';
|
|
|
console.log('防刷新已禁用');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 处理提交
|
|
|
const handleSubmit = async (event) => {
|
|
|
console.log('提交按钮被点击');
|
|
|
|
|
|
// 1. 阻止所有默认行为
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
console.log('阻止了默认行为和冒泡');
|
|
|
|
|
|
// 2. 防止重复提交
|
|
|
if (isProcessing) {
|
|
|
console.log('正在处理中,忽略重复点击');
|
|
|
return;
|
|
|
}
|
|
|
isProcessing = true;
|
|
|
|
|
|
// 3. 启用全局防刷新拦截器
|
|
|
preventRefresh = true;
|
|
|
console.log('启用全局防刷新拦截器');
|
|
|
console.log('开始处理请求');
|
|
|
|
|
|
// 3. 更新UI状态
|
|
|
updateUIState(true);
|
|
|
|
|
|
try {
|
|
|
// 4. 验证输入
|
|
|
console.log('验证输入...');
|
|
|
const validationError = validateInputs();
|
|
|
if (validationError) {
|
|
|
console.log('输入验证失败:', validationError);
|
|
|
elements.resultPre.textContent = validationError;
|
|
|
return;
|
|
|
}
|
|
|
console.log('输入验证通过');
|
|
|
|
|
|
// 5. 准备数据
|
|
|
console.log('准备表单数据...');
|
|
|
const formData = prepareFormData();
|
|
|
|
|
|
// 6. 发送请求
|
|
|
console.log('发送请求到后端...');
|
|
|
const data = await sendRequest(formData);
|
|
|
console.log('请求成功完成');
|
|
|
|
|
|
// 7. 处理响应
|
|
|
console.log('处理响应数据...');
|
|
|
processResponse(data);
|
|
|
console.log('响应处理完成');
|
|
|
|
|
|
} catch (error) {
|
|
|
// 8. 错误处理
|
|
|
console.log('处理过程中发生错误:', error);
|
|
|
handleError(error);
|
|
|
} finally {
|
|
|
// 9. 恢复UI状态
|
|
|
console.log('恢复UI状态');
|
|
|
updateUIState(false);
|
|
|
isProcessing = false;
|
|
|
|
|
|
// 10. 禁用全局防刷新拦截器
|
|
|
preventRefresh = false;
|
|
|
console.log('禁用全局防刷新拦截器');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 更新UI状态
|
|
|
const updateUIState = (isLoading) => {
|
|
|
if (isLoading) {
|
|
|
elements.submitBtn.disabled = true;
|
|
|
elements.submitBtn.textContent = '检查中...';
|
|
|
elements.loader.style.display = 'block';
|
|
|
elements.resultPre.textContent = '正在检查...';
|
|
|
elements.reportArea.style.display = 'none';
|
|
|
} else {
|
|
|
elements.submitBtn.disabled = false;
|
|
|
elements.submitBtn.textContent = '提交检查';
|
|
|
elements.loader.style.display = 'none';
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 验证输入
|
|
|
const validateInputs = () => {
|
|
|
if (elements.fileInput.files.length === 0) {
|
|
|
return '请上传一个文件。';
|
|
|
}
|
|
|
|
|
|
const tools = getSelectedTools();
|
|
|
if (tools.length === 0) {
|
|
|
return '请至少选择一个工具。';
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
};
|
|
|
|
|
|
// 获取选中的工具
|
|
|
const getSelectedTools = () => {
|
|
|
const tools = [];
|
|
|
if (elements.banditCheckbox.checked) tools.push('bandit');
|
|
|
if (elements.flake8Checkbox.checked) tools.push('flake8');
|
|
|
if (elements.pylintCheckbox.checked) tools.push('pylint');
|
|
|
return tools;
|
|
|
};
|
|
|
|
|
|
// 准备表单数据
|
|
|
const prepareFormData = () => {
|
|
|
const formData = new FormData();
|
|
|
formData.append('file', elements.fileInput.files[0]);
|
|
|
formData.append('tools', getSelectedTools().join(','));
|
|
|
return formData;
|
|
|
};
|
|
|
|
|
|
// 发送请求
|
|
|
const sendRequest = async (formData) => {
|
|
|
console.log('开始发送fetch请求...');
|
|
|
console.log('请求URL: http://localhost:3000/check');
|
|
|
|
|
|
try {
|
|
|
const response = await fetch('http://localhost:3000/check', {
|
|
|
method: 'POST',
|
|
|
body: formData
|
|
|
});
|
|
|
|
|
|
console.log('收到响应,状态码:', response.status);
|
|
|
|
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
|
console.error('HTTP错误详情:', {
|
|
|
status: response.status,
|
|
|
statusText: response.statusText,
|
|
|
errorText: errorText
|
|
|
});
|
|
|
throw new Error(`HTTP错误! 状态码: ${response.status}\n${errorText}`);
|
|
|
}
|
|
|
|
|
|
const data = await response.json();
|
|
|
console.log('成功解析JSON响应:', data);
|
|
|
return data;
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('网络请求异常:', error);
|
|
|
// 重新抛出错误,让上层处理
|
|
|
throw error;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 处理响应
|
|
|
const processResponse = (data) => {
|
|
|
// 显示结果
|
|
|
elements.resultPre.textContent = JSON.stringify(data, null, 2);
|
|
|
|
|
|
// 显示报告下载区域
|
|
|
if (data.reportUrl) {
|
|
|
const downloadUrl = 'http://localhost:3000' + data.reportUrl;
|
|
|
elements.reportLink.href = downloadUrl;
|
|
|
elements.reportLink.download = `code_report_${Date.now()}.md`;
|
|
|
elements.reportArea.style.display = 'block';
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 处理错误
|
|
|
const handleError = (error) => {
|
|
|
elements.resultPre.textContent = `错误: ${error.message}`;
|
|
|
console.error('请求失败:', error);
|
|
|
};
|
|
|
|
|
|
// 处理下载
|
|
|
const handleDownload = async (event) => {
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
|
|
|
console.log('开始下载报告...');
|
|
|
|
|
|
try {
|
|
|
// 使用fetch API下载文件
|
|
|
const response = await fetch(elements.reportLink.href);
|
|
|
if (!response.ok) {
|
|
|
throw new Error(`下载失败: ${response.status}`);
|
|
|
}
|
|
|
|
|
|
const blob = await response.blob();
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
|
|
// 创建临时下载链接
|
|
|
const a = document.createElement('a');
|
|
|
a.style.display = 'none';
|
|
|
a.href = url;
|
|
|
a.download = `code_report_${Date.now()}.md`;
|
|
|
document.body.appendChild(a);
|
|
|
a.click();
|
|
|
|
|
|
// 清理临时资源
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
document.body.removeChild(a);
|
|
|
|
|
|
console.log('下载成功');
|
|
|
} catch (error) {
|
|
|
console.error('下载失败:', error);
|
|
|
alert('下载失败: ' + error.message);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 公开初始化方法和状态
|
|
|
return {
|
|
|
init,
|
|
|
get isProcessing() { return isProcessing; }
|
|
|
};
|
|
|
})();
|
|
|
|
|
|
// 防刷新拦截器将在App模块内部设置
|
|
|
|
|
|
// 添加页面刷新监听器用于调试(将在App模块中设置)
|
|
|
|
|
|
// 监听页面加载完成
|
|
|
window.addEventListener('load', function () {
|
|
|
console.log('页面加载完成');
|
|
|
});
|
|
|
|
|
|
// 监听页面显示状态变化
|
|
|
document.addEventListener('visibilitychange', function () {
|
|
|
console.log('页面可见性变化:', document.visibilityState);
|
|
|
});
|
|
|
|
|
|
// 全局错误处理,防止页面重新加载
|
|
|
window.addEventListener('error', function (event) {
|
|
|
console.error('捕获到全局错误:', event.error);
|
|
|
console.error('错误信息:', event.message);
|
|
|
console.error('错误文件:', event.filename);
|
|
|
console.error('错误行号:', event.lineno);
|
|
|
console.error('错误列号:', event.colno);
|
|
|
|
|
|
// 阻止默认的错误处理行为(可能导致页面重新加载)
|
|
|
event.preventDefault();
|
|
|
return false;
|
|
|
});
|
|
|
|
|
|
// 捕获未处理的Promise错误
|
|
|
window.addEventListener('unhandledrejection', function (event) {
|
|
|
console.error('捕获到未处理的Promise错误:', event.reason);
|
|
|
console.error('Promise错误详情:', event);
|
|
|
|
|
|
// 阻止默认行为
|
|
|
event.preventDefault();
|
|
|
});
|
|
|
|
|
|
// 防止意外的页面刷新
|
|
|
window.addEventListener('beforeunload', function (event) {
|
|
|
// 如果正在处理请求,阻止页面刷新
|
|
|
if (typeof App !== 'undefined' && App.isProcessing) {
|
|
|
console.log('阻止页面刷新,因为正在处理请求');
|
|
|
event.preventDefault();
|
|
|
event.returnValue = '正在处理请求,请稍等...';
|
|
|
return '正在处理请求,请稍等...';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 初始化应用
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
console.log('DOM内容加载完成,开始初始化应用');
|
|
|
try {
|
|
|
App.init();
|
|
|
console.log('应用初始化成功');
|
|
|
} catch (error) {
|
|
|
console.error('应用初始化失败:', error);
|
|
|
// 即使初始化失败,也不要重新加载页面
|
|
|
alert('应用初始化失败,请刷新页面重试');
|
|
|
}
|
|
|
});
|
|
|
</script>
|
|
|
</body>
|
|
|
|
|
|
</html> |