You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
analysiscode/src/view/frontend.html

575 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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>