代码提交 #3

Merged
pu8crm6xf merged 1 commits from zhangyang into main 2 months ago

@ -54,6 +54,9 @@ const upload = multer({
}
});
// 装载模块化路由
const rulesRouter = require(path.join(__dirname, 'server', 'routes', 'rules.js'));
// 项目存储目录
const PROJECTS_DIR = path.join(__dirname, 'projects_data');
if (!fs.existsSync(PROJECTS_DIR)) {
@ -121,7 +124,7 @@ const TOOL_CONFIG = {
parseResult: (stdout) => {
try {
if (!stdout || stdout.trim() === '') return [];
const lines = stdout.trim().split('\n').filter(line => line.trim());
const issues = [];
@ -184,7 +187,7 @@ async function runTool(tool, filePath) {
exec(command, { shell: true, timeout: 60000 }, (error, stdout, stderr) => {
console.log(`${tool} 执行完成`);
try {
const issues = config.parseResult(stdout || '');
resolve({
@ -204,7 +207,7 @@ async function runTool(tool, filePath) {
});
}
// 运行代码检查
// 运行代码检查(单文件)
async function runCodeCheck(filePath) {
const tools = ['pylint', 'flake8', 'bandit'];
const toolsStatus = {};
@ -236,6 +239,39 @@ async function runCodeCheck(filePath) {
};
}
// 运行代码检查(多文件,生成相对路径)
async function runCodeCheckOnFiles(filePaths, baseDir) {
const aggregateIssues = [];
const toolsStatusAggregate = { pylint: 'completed', flake8: 'completed', bandit: 'completed' };
for (const filePath of filePaths) {
const result = await runCodeCheck(filePath);
// 汇总工具状态(若任一文件失败则标记)
for (const k of Object.keys(result.tools_status)) {
if (result.tools_status[k] !== 'completed') {
toolsStatusAggregate[k] = result.tools_status[k];
}
}
const rel = baseDir ? path.relative(baseDir, filePath).replace(/\\/g, '/') : path.basename(filePath);
result.all_issues.forEach(issue => {
aggregateIssues.push({ ...issue, relative_path: rel });
});
}
const errorCount = aggregateIssues.filter(i => i.type === 'error').length;
const warningCount = aggregateIssues.filter(i => i.type === 'warning').length;
const infoCount = aggregateIssues.filter(i => i.type === 'info').length;
return {
tools_status: toolsStatusAggregate,
all_issues: aggregateIssues,
total_issues: aggregateIssues.length,
error_count: errorCount,
warning_count: warningCount,
info_count: infoCount
};
}
// ==================== API 端点 ====================
// 健康检查
@ -246,6 +282,54 @@ app.get('/api/health', (req, res) => {
});
});
// 掛载 /api 下路由模块
app.use('/api', rulesRouter);
// 仪表板统计
app.get('/api/stats', (req, res) => {
try {
const numProjects = projects.length;
let totalIssues = 0;
let errorCount = 0;
let warningCount = 0;
let infoCount = 0;
let checkedProjects = 0;
projects.forEach(p => {
const lc = p.latest_check;
if (lc) {
checkedProjects += 1;
totalIssues += lc.total_issues || 0;
errorCount += lc.error_count || 0;
warningCount += lc.warning_count || 0;
infoCount += lc.info_count || 0;
}
});
// 估算“已检查文件”数量按每个已检查项目记为1或可拓展统计真实文件数
const totalFilesChecked = checkedProjects;
const complianceRate = totalIssues === 0 && checkedProjects > 0 ? 100 : Math.max(0, 100 - totalIssues * 2);
res.json({
success: true,
data: {
num_projects: numProjects,
checked_projects: checkedProjects,
total_files_checked: totalFilesChecked,
total_issues: totalIssues,
error_count: errorCount,
warning_count: warningCount,
info_count: infoCount,
compliance_rate: Number(complianceRate.toFixed(1))
}
});
} catch (error) {
console.error('统计接口失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// 文件上传端点
app.post('/api/upload', upload.array('files'), (req, res) => {
try {
@ -296,9 +380,18 @@ app.post('/api/check', async (req, res) => {
});
}
// 获取所有Python文件
const files = fs.readdirSync(temp_path).filter(f => f.endsWith('.py'));
// 获取所有Python文件包含子目录
const files = [];
(function walk(dir) {
const items = fs.readdirSync(dir);
items.forEach(item => {
const p = path.join(dir, item);
const st = fs.statSync(p);
if (st.isDirectory()) return walk(p);
if (item.endsWith('.py')) files.push(p);
});
})(temp_path);
if (files.length === 0) {
return res.json({
success: true,
@ -313,9 +406,8 @@ app.post('/api/check', async (req, res) => {
});
}
// 检查第一个文件(简化版,可以扩展为检查所有文件)
const firstFile = path.join(temp_path, files[0]);
const result = await runCodeCheck(firstFile);
// 检查所有文件
const result = await runCodeCheckOnFiles(files, temp_path);
// 清理临时文件
setTimeout(() => {
@ -463,7 +555,7 @@ app.post('/api/projects/:id/check', async (req, res) => {
// 获取项目中的所有Python文件
const projectPath = project.path;
const files = [];
function getAllPythonFiles(dir) {
const items = fs.readdirSync(dir);
items.forEach(item => {
@ -491,8 +583,8 @@ app.post('/api/projects/:id/check', async (req, res) => {
});
}
// 检查第一个文件作为示例
const result = await runCodeCheck(files[0]);
// 检查所有文件并生成相对路径
const result = await runCodeCheckOnFiles(files, projectPath);
// 更新项目的最新检查记录
project.latest_check = {
@ -570,6 +662,40 @@ app.post('/api/projects/:id/upload-files', upload.array('files'), (req, res) =>
}
});
// 按路径上传单个文件到项目(保留目录结构)
app.post('/api/projects/:id/files/upload', upload.single('file'), (req, res) => {
try {
const projectId = parseInt(req.params.id);
const project = projects.find(p => p.id === projectId);
if (!project) {
return res.status(404).json({ success: false, error: '项目不存在' });
}
if (!req.file) {
return res.status(400).json({ success: false, error: '没有上传文件' });
}
const targetSubPath = req.body.path || '';
const safeSubPath = targetSubPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
const destDir = path.join(project.path, safeSubPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
const destPath = path.join(destDir, req.file.originalname);
fs.copyFileSync(req.file.path, destPath);
fs.unlinkSync(req.file.path);
project.updated_at = new Date().toISOString();
saveProjects();
res.json({ success: true, path: path.join(safeSubPath, req.file.originalname) });
} catch (error) {
console.error('按路径上传文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// 获取项目文件列表
app.get('/api/projects/:id/files', (req, res) => {
try {

@ -1267,6 +1267,17 @@ input:checked + .slider:before {
padding: 20px;
}
/* 编辑模态框分栏与拖拽条 */
#modalResizer {
transition: background 0.1s;
}
#modalResizer:hover {
background: rgba(30,60,114,0.08);
}
.resizing #modalResizer {
background: rgba(30,60,114,0.15);
}
.modal-footer {
display: flex;
justify-content: flex-end;

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -7,6 +8,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 顶部导航栏 -->
<nav class="navbar">
@ -102,7 +104,7 @@
<div class="upload-hint">支持 .py, .pyx, .pyi 文件,最大 100MB</div>
<input type="file" class="file-input" id="fileInput" multiple accept=".py,.pyx,.pyi">
</div>
<div class="progress-container" id="progressContainer" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%;"></div>
@ -169,6 +171,52 @@
</div>
</div>
<!-- 规则集管理页面 -->
<div id="rules-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">规则集管理</h1>
<p class="page-subtitle">上传、查看、编辑与删除自定义规则配置pylint/flake8 等)</p>
</div>
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="uploadRuleBtn">
<i class="fas fa-upload"></i> 上传规则文件
</button>
<button class="btn btn-secondary" id="refreshRuleBtn">
<i class="fas fa-rotate"></i> 刷新
</button>
</div>
</div>
<div class="projects-grid" id="rulesList">
<!-- 规则项通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 编辑规则文件模态框 -->
<div id="editRuleModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 800px; width: 90%;">
<div class="modal-header">
<h2 id="editRuleTitle">编辑规则</h2>
<span class="close" id="closeEditRule">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label>内容</label>
<textarea id="editRuleTextarea" rows="18" style="width: 100%;"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelEditRule">取消</button>
<button type="button" class="btn btn-success" id="saveRuleBtn"><i class="fas fa-save"></i>
保存</button>
</div>
</div>
</div>
<!-- 项目管理页面 -->
<div id="projects-page" class="page-content">
<div class="page-header fade-in">
@ -179,8 +227,8 @@
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="createProjectBtn">
<i class="fas fa-plus"></i> 新建项目
<button class="btn btn-secondary" id="importFolderProjectBtn">
<i class="fas fa-folder-open"></i> 导入文件夹为项目
</button>
</div>
<div class="projects-search">
@ -217,8 +265,8 @@
<button class="btn btn-primary" id="runCheckBtn">
<i class="fas fa-play"></i> 运行检查
</button>
<button class="btn btn-secondary" id="editProjectBtn">
<i class="fas fa-edit"></i> 编辑项目
<button class="btn btn-secondary" id="openEditorModalBtn">
<i class="fas fa-pen"></i> 文件编辑
</button>
<button class="btn btn-danger" id="deleteProjectBtn">
<i class="fas fa-trash"></i> 删除项目
@ -229,66 +277,6 @@
<!-- 项目信息将通过 JavaScript 动态生成 -->
</div>
</div>
<div class="check-history-panel">
<div class="panel-header">
<h2 class="panel-title">检查历史</h2>
</div>
<div class="panel-content" id="checkHistoryContent">
<!-- 检查历史将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 文件浏览器面板 -->
<div class="file-browser-panel">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 class="panel-title">文件浏览器</h2>
<div class="file-actions">
<button class="btn btn-primary" id="uploadFileBtn">
<i class="fas fa-upload"></i> 上传文件
</button>
<button class="btn btn-secondary" id="createFileBtn">
<i class="fas fa-plus"></i> 新建文件
</button>
<button class="btn btn-secondary" id="createFolderBtn">
<i class="fas fa-folder-plus"></i> 新建文件夹
</button>
</div>
</div>
<div class="file-path">
<span id="currentPath">/</span>
</div>
</div>
<div class="file-browser-content">
<div class="file-tree" id="fileTree">
<!-- 文件树将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 代码编辑器面板 -->
<div class="code-editor-panel" id="codeEditorPanel" style="display: none;">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 class="panel-title" id="editorTitle">代码编辑器</h2>
<div class="editor-actions">
<button class="btn btn-success" id="saveFileBtn">
<i class="fas fa-save"></i> 保存
</button>
<button class="btn btn-secondary" id="closeEditorBtn">
<i class="fas fa-times"></i> 关闭
</button>
</div>
</div>
<div class="file-info">
<span id="currentFilePath">未选择文件</span>
</div>
</div>
<div class="editor-content">
<textarea id="codeEditor" placeholder="选择文件开始编辑..."></textarea>
</div>
</div>
<!-- 检查结果详情 -->
@ -331,17 +319,18 @@
<div class="source-type-tabs">
<input type="radio" id="sourceGithub" name="source_type" value="github" checked>
<label for="sourceGithub">GitHub</label>
<input type="radio" id="sourceGitee" name="source_type" value="gitee">
<label for="sourceGitee">Gitee</label>
<input type="radio" id="sourceUpload" name="source_type" value="upload">
<label for="sourceUpload">文件上传</label>
</div>
</div>
<div class="form-group" id="gitUrlGroup">
<label for="gitUrl">Git URL *</label>
<input type="url" id="gitUrl" name="source_url" placeholder="https://github.com/username/repository.git">
<input type="url" id="gitUrl" name="source_url"
placeholder="https://github.com/username/repository.git">
</div>
<div class="form-group" id="fileUploadGroup" style="display: none;">
<label for="fileUpload">选择文件或文件夹</label>
@ -356,6 +345,45 @@
</div>
</div>
<!-- 文件编辑模态框 -->
<div id="editorModal" class="modal" style="display: none;">
<div class="modal-content"
style="max-width: 1000px; width: 95%; height: 80vh; display: flex; flex-direction: column;">
<div class="modal-header">
<h2>项目文件编辑</h2>
<span class="close" id="closeEditorModal">&times;</span>
</div>
<div class="modal-body" style="flex:1; display:flex; overflow:hidden;">
<div id="modalSidebar" style="width: 320px; min-width: 220px; max-width: 60%; border-right: 1px solid #eee; display:flex; flex-direction:column;">
<div class="file-path" style="padding:8px 12px;">
<span id="modalCurrentPath">/</span>
</div>
<div class="file-tree" id="modalFileTree" style="flex:1; overflow:auto;"></div>
</div>
<div id="modalResizer" style="width:6px; cursor: col-resize; background: transparent; position: relative;">
<div style="position:absolute; top:0; bottom:0; left:2px; width:2px; background:#e5e7eb;"></div>
</div>
<div style="flex:1; display:flex; flex-direction:column;">
<div class="panel-header" style="padding:8px 12px;">
<div style="display:flex; align-items:center; gap:10px;">
<h3 class="panel-title" id="modalEditorTitle" style="margin:0;">代码编辑器</h3>
<div class="editor-actions" style="margin-left:auto; display:flex; gap:8px;">
<button class="btn btn-success" id="modalSaveFileBtn"><i class="fas fa-save"></i>
保存</button>
<button class="btn" id="modalCloseBtn"><i class="fas fa-times"></i> 关闭</button>
</div>
</div>
<div class="file-info"><span id="modalCurrentFilePath">未选择文件</span></div>
</div>
<div class="editor-content" style="flex:1; padding:0 12px 12px;">
<textarea id="modalCodeEditor" placeholder="选择文件开始编辑..." style="height:100%;"></textarea>
</div>
</div>
</div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,202 @@
const API_BASE_URL = window.location.origin + '/api';
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function showSuccessMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 6px; padding: 15px 20px; z-index: 3000; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px;`;
messageDiv.innerHTML = `<i class="fas fa-check-circle"></i><span>${message}</span>`;
document.body.appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 3000);
}
function showErrorMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 6px; padding: 15px 20px; z-index: 3000; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px;`;
messageDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i><span>${message}</span>`;
document.body.appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 5000);
}
async function loadRules() {
try {
const response = await fetch(`${API_BASE_URL}/rules`);
const data = await response.json();
if (!data.success) throw new Error(data.error || '加载失败');
renderRulesList(data.data || []);
} catch (e) {
console.error('加载规则失败:', e);
showErrorMessage('加载规则失败');
}
}
function renderRulesList(files) {
const container = document.getElementById('rulesList');
if (!container) return;
container.innerHTML = '';
if (!files.length) {
container.innerHTML = '<div style="text-align:center; padding: 20px; color:#666;">暂无规则文件</div>';
return;
}
files.forEach(f => {
const card = document.createElement('div');
card.className = 'project-card';
const size = formatFileSize(f.size || 0);
card.innerHTML = `
<div class="project-header">
<div>
<div class="project-title">${f.name}</div>
<div class="project-description">大小: ${size} | 更新: ${new Date(f.modified_at).toLocaleString()}</div>
</div>
</div>
<div class="project-actions">
<button class="btn btn-secondary">编辑</button>
<button class="btn btn-primary">下载</button>
<button class="btn btn-danger">删除</button>
</div>
`;
const [editBtn, downloadBtn, deleteBtn] = card.querySelectorAll('.project-actions .btn');
editBtn.addEventListener('click', (e) => { e.stopPropagation(); editRule(f.name); });
downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); downloadRule(f.name); });
deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); deleteRule(f.name); });
container.appendChild(card);
});
}
function showRuleUploadDialog() {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = '.cfg,.ini,.toml,.yaml,.yml,.json,.flake8,.pylintrc,setup.cfg,pyproject.toml,tox.ini';
input.onchange = async (e) => {
const files = Array.from(e.target.files || []);
if (!files.length) return;
const formData = new FormData();
files.forEach(f => formData.append('files', f));
try {
const resp = await fetch(`${API_BASE_URL}/rules/upload`, { method: 'POST', body: formData });
const data = await resp.json();
if (data.success) {
showSuccessMessage('规则上传成功');
loadRules();
} else {
showErrorMessage('规则上传失败:' + (data.error || ''));
}
} catch (e) {
console.error('上传规则失败:', e);
showErrorMessage('上传规则失败');
}
};
input.click();
}
let currentEditingRule = '';
async function editRule(name) {
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`);
const data = await resp.json();
if (!data.success) throw new Error(data.error || '读取失败');
currentEditingRule = name;
document.getElementById('editRuleTitle').textContent = `编辑规则 - ${name}`;
document.getElementById('editRuleTextarea').value = data.data.content || '';
document.getElementById('editRuleModal').style.display = 'block';
} catch (e) {
console.error('读取规则失败:', e);
showErrorMessage('读取规则失败');
}
}
function hideEditRuleModal() {
const modal = document.getElementById('editRuleModal');
if (modal) modal.style.display = 'none';
currentEditingRule = '';
}
async function saveEditingRule() {
if (!currentEditingRule) return;
const content = document.getElementById('editRuleTextarea').value;
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(currentEditingRule)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
const data = await resp.json();
if (data.success) {
showSuccessMessage('保存成功');
hideEditRuleModal();
loadRules();
} else {
showErrorMessage('保存失败:' + (data.error || ''));
}
} catch (e) {
console.error('保存规则失败:', e);
showErrorMessage('保存失败');
}
}
async function deleteRule(name) {
if (!confirm(`确定删除规则文件 "${name}" 吗?`)) return;
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`, { method: 'DELETE' });
const data = await resp.json();
if (data.success) {
showSuccessMessage('删除成功');
loadRules();
} else {
showErrorMessage('删除失败:' + (data.error || ''));
}
} catch (e) {
console.error('删除规则失败:', e);
showErrorMessage('删除失败');
}
}
async function downloadRule(name) {
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`);
const data = await resp.json();
if (!data.success) throw new Error(data.error || '下载失败');
const blob = new Blob([data.data.content || ''], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
a.remove();
} catch (e) {
console.error('下载规则失败:', e);
showErrorMessage('下载失败');
}
}
function bindRulesEvents() {
const uploadRuleBtn = document.getElementById('uploadRuleBtn');
const refreshRuleBtn = document.getElementById('refreshRuleBtn');
const closeEditRule = document.getElementById('closeEditRule');
const cancelEditRule = document.getElementById('cancelEditRule');
const saveRuleBtn = document.getElementById('saveRuleBtn');
if (uploadRuleBtn) uploadRuleBtn.addEventListener('click', showRuleUploadDialog);
if (refreshRuleBtn) refreshRuleBtn.addEventListener('click', loadRules);
if (closeEditRule) closeEditRule.addEventListener('click', hideEditRuleModal);
if (cancelEditRule) cancelEditRule.addEventListener('click', hideEditRuleModal);
if (saveRuleBtn) saveRuleBtn.addEventListener('click', saveEditingRule);
}
document.addEventListener('DOMContentLoaded', () => {
bindRulesEvents();
});
export { loadRules };

@ -6,7 +6,9 @@
"scripts": {
"start": "node backend.js",
"dev": "node backend.js",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"cleanup:dry": "node scripts/cleanup.js",
"cleanup": "node scripts/cleanup.js --execute"
},
"keywords": [
"code-quality",
@ -22,4 +24,4 @@
"express": "^5.1.0",
"multer": "^2.0.2"
}
}
}

@ -0,0 +1,62 @@
[flake8]
# 基础规则配置
# 启用的检查规则集(默认包含 pycodestyle (E/W)、pyflakes (F),扩展支持 bugbear (B)、docstrings (D) 等)
select = E, W, F, B, D
# 忽略的特定错误/警告代码(根据团队习惯调整)
ignore =
# 行长度超过限制默认79下方已调整为120故忽略默认E501
E501,
# 与 black 格式化工具冲突的缩进检查black 推荐忽略)
E203,
# 多余的空白行警告(允许适当空行提高可读性)
W293,
# 变量未使用警告(临时变量或调试代码可能需要)
F841,
# bugbear 中过于严格的"表达式复杂度"警告(根据业务调整)
B019,
# 文档字符串相关:忽略模块级文档缺失(小型脚本可能不需要)
D100,
# 文档字符串相关:忽略私有函数文档缺失(内部函数可简化)
D105
# 最大行长度默认79调整为120更适合现代屏幕
max-line-length = 120
# 循环复杂度最大值(超过此值提示代码可能过于复杂,需拆分)
max-complexity = 12
# 排除不需要检查的文件/目录(如虚拟环境、自动生成的代码)
exclude =
.git,
__pycache__,
venv,
.venv,
migrations,
build,
dist,
*.egg-info
# 针对特定文件的单独忽略规则(例如测试文件放宽检查)
per-file-ignores =
# 测试文件忽略"函数参数未使用"(测试用例可能有占位参数)
tests/*.py: F841,
# 配置文件忽略"模块文档缺失"(配置文件通常无需文档)
config/*.py: D100
# 以下为插件扩展配置(需提前安装对应插件)
# 1. flake8-docstrings文档字符串检查需安装pip install flake8-docstrings
[flake8-docstrings]
# 文档字符串风格(支持 google/numpy/restructuredtext
docstring-style = google
# 允许无返回值函数省略 @return 说明
ignore-missing-docstrings = False # 全局不允许缺失文档字符串(除非在 ignore 中单独排除)
# 忽略"文档字符串末尾无空行"的警告(部分团队不强制)
ignore-decorators = abc.abstractmethod # 抽象方法无需重复文档
# 2. flake8-bugbear增强错误检查需安装pip install flake8-bugbear
[flake8-bugbear]
# 允许使用 assert 语句(默认 B011 警告 assert测试代码中常用故关闭
allow-asserts = True

@ -0,0 +1,125 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const router = express.Router();
const RULE_DIR = path.join(__dirname, '..', '..', 'rule');
if (!fs.existsSync(RULE_DIR)) {
fs.mkdirSync(RULE_DIR, { recursive: true });
}
const ruleStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, RULE_DIR);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});
const ruleUpload = multer({
storage: ruleStorage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowed = [
'setup.cfg', '.pylintrc', '.flake8', 'pyproject.toml',
'pylintrc', 'flake8.cfg', 'tox.ini'
];
const extAllowed = /\.(cfg|ini|toml|yaml|yml|json)$/i;
if (allowed.includes(file.originalname) || extAllowed.test(file.originalname)) {
return cb(null, true);
}
return cb(new Error('不支持的规则文件类型'));
}
});
router.post('/rules/upload', ruleUpload.array('files'), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ success: false, error: '没有上传文件' });
}
const saved = req.files.map(f => f.originalname);
res.json({ success: true, data: { saved_files: saved, directory: RULE_DIR } });
} catch (error) {
console.error('规则文件上传失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.get('/rules', (req, res) => {
try {
if (!fs.existsSync(RULE_DIR)) {
return res.json({ success: true, data: [] });
}
const files = fs.readdirSync(RULE_DIR).map(name => {
const p = path.join(RULE_DIR, name);
const stat = fs.statSync(p);
return {
name,
size: stat.size,
modified_at: stat.mtime.toISOString(),
is_directory: stat.isDirectory()
};
}).filter(item => !item.is_directory);
res.json({ success: true, data: files });
} catch (error) {
console.error('读取规则列表失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.get('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const target = path.join(RULE_DIR, name);
if (!fs.existsSync(target)) {
return res.status(404).json({ success: false, error: '规则文件不存在' });
}
const content = fs.readFileSync(target, 'utf8');
res.json({ success: true, data: { name, content } });
} catch (error) {
console.error('读取规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.put('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const { content } = req.body || {};
if (!name) {
return res.status(400).json({ success: false, error: '规则文件名不能为空' });
}
const target = path.join(RULE_DIR, name);
const dir = path.dirname(target);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(target, content ?? '', 'utf8');
res.json({ success: true, message: '保存成功' });
} catch (error) {
console.error('保存规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.delete('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const target = path.join(RULE_DIR, name);
if (!fs.existsSync(target)) {
return res.status(404).json({ success: false, error: '规则文件不存在' });
}
fs.unlinkSync(target);
res.json({ success: true, message: '已删除' });
} catch (error) {
console.error('删除规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
module.exports = router;

@ -1,42 +0,0 @@
# 代码质量检查API文档
## API端点
- **URL**`/check`
- **方法**POST
- **请求格式**multipart/form-data
- **请求参数**
- `file` (文件)要检查的Python代码文件必需
- `tools` (字符串):逗号分隔的工具列表,例如 "bandit,flake8,pylint"(必需,至少一个)。
- **响应格式**JSON
- 成功200 OK
```
{
"results": [
{
"tool": "bandit",
"stdout": "...检查输出...",
"stderr": "...错误信息(如果有)..."
},
// 其他工具的结果
]
}
```
- 错误400 Bad Request
```
{
"error": "No file uploaded" // 或其他错误消息
}
```
- **示例请求** (使用curl)
```
curl -X POST http://localhost:3000/check \
-F "file=@D:\软件工程\代码质量检查\src\test_sample.py" \
-F "tools=bandit,flake8,pylint"
```
- **注意**
- 支持的工具bandit、flake8、pylint大小写不敏感
- 文件会临时保存并在检查后删除。
- 如果工具运行失败stderr会包含错误详情。

@ -1,36 +0,0 @@
# 代码质量检查后端启动手册
## 前提条件
- Node.js 已安装(版本 >= 14
- Python 3.9 已安装且bandit、flake8、pylint 通过pip安装。
- 依赖express、multer、cors运行`npm install express multer cors`安装)。
## 启动步骤
1. 导航到项目目录:
```
cd D:\软件工程\代码质量检查\src
```
2. 安装Node.js依赖如果尚未
```
npm install express multer cors
```
3. 启动后端服务器:
```
node view/backend.js
```
- 控制台会显示“Server running on port 3000”。
- 服务器监听http://localhost:3000。
4. 测试API
- 使用curl示例或前端页面如果有`view/frontend.html`,通过浏览器打开并提交)。
- 示例:上传`test_sample.py`选择所有工具检查响应JSON。
## 停止服务器
按Ctrl+C在终端中停止。
## 常见问题
- 如果端口3000被占用修改`app.listen(3000)`为其他端口如5000
- CORS错误确保cors已启用如果从本地文件测试前端使用Live Server扩展避免origin null。
- 工具失败检查Python PATH工具必须在系统PATH中可用

@ -1,405 +0,0 @@
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 3000;
// 配置CORS
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
preflightContinue: false,
optionsSuccessStatus: 204
}));
// 使用正则表达式处理所有OPTIONS请求
app.options(/.*/, (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(204);
});
// 解析JSON请求体
app.use(express.json());
// 请求日志中间件
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// 配置Multer文件上传
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, os.tmpdir());
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 10 * 1024 * 1024 }
});
// 工具配置
const TOOL_CONFIG = {
bandit: {
command: 'bandit',
args: (filePath) => `-r -f json -o - ${filePath}`,
parser: (stdout) => JSON.parse(stdout)
},
flake8: {
command: 'flake8',
args: (filePath) => `${filePath}`,
parser: (stdout) => {
console.log('Flake8原始输出:', stdout);
console.log('Flake8输出长度:', stdout.length);
console.log('Flake8输出是否为空:', !stdout || stdout.trim() === '');
// 处理空输出情况
if (!stdout || stdout.trim() === '') {
console.log('Flake8输出为空返回空数组');
return []; // 返回空数组
}
// 检查是否已经是JSON格式
if (stdout.trim().startsWith('[') || stdout.trim().startsWith('{')) {
try {
return JSON.parse(stdout);
} catch (parseError) {
console.log('Flake8 JSON解析失败:', parseError.message);
}
}
// 如果不是JSON格式尝试解析为文本格式
console.log('Flake8输出不是JSON格式使用文本解析');
const lines = stdout.trim().split('\n').filter(line => line.trim());
const issues = [];
for (const line of lines) {
// 跳过注释行和空行
if (line.startsWith('#') || !line.trim()) {
continue;
}
// 尝试解析flake8的标准输出格式
// 支持绝对路径和相对路径,以及不同的分隔符
const match = line.match(/^(.+?):(\d+):(\d+):\s*([A-Z]\d+)\s*(.+)$/);
if (match) {
issues.push({
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
code: match[4],
message: match[5]
});
} else if (line.includes(':')) {
// 如果不匹配标准格式,但包含冒号,尝试更宽松的解析
const parts = line.split(':');
if (parts.length >= 4) {
const file = parts[0];
const lineNum = parseInt(parts[1]);
const colNum = parseInt(parts[2]);
const codeAndMessage = parts.slice(3).join(':').trim();
// 尝试提取错误代码
const codeMatch = codeAndMessage.match(/^([A-Z]\d+)\s*(.+)$/);
if (codeMatch) {
issues.push({
file: file,
line: lineNum,
column: colNum,
code: codeMatch[1],
message: codeMatch[2]
});
} else {
issues.push({
file: file,
line: lineNum,
column: colNum,
code: 'UNKNOWN',
message: codeAndMessage
});
}
} else {
console.log('无法解析的Flake8输出行:', line);
}
} else {
// 如果行不包含任何错误信息,可能是其他类型的输出
console.log('跳过Flake8输出行:', line);
}
}
console.log('Flake8文本解析结果:', issues);
return issues;
}
},
pylint: {
command: 'pylint',
args: (filePath) => `--output-format=json ${filePath}`,
parser: (stdout) => {
// 处理空输出情况
if (!stdout || stdout.trim() === '') {
return []; // 返回空数组
}
return JSON.parse(stdout);
}
}
};
// 确保out目录存在放在项目根目录避免触发开发服务器重载
function ensureOutDir() {
// 将out目录放在项目根目录而不是view目录内
const outDir = path.join(__dirname, '..', 'out');
console.log(`检查输出目录: ${outDir}`);
if (!fs.existsSync(outDir)) {
console.log(`创建输出目录: ${outDir}`);
fs.mkdirSync(outDir, { recursive: true });
console.log(`输出目录创建成功`);
} else {
console.log(`输出目录已存在: ${outDir}`);
}
return outDir;
}
// 运行单个工具
function runTool(tool, filePath) {
return new Promise((resolve, reject) => {
const config = TOOL_CONFIG[tool];
if (!config) {
return reject(new Error(`不支持的工具: ${tool}`));
}
let actualFilePath = filePath;
let actualCommand = `${config.command} ${config.args(filePath)}`;
// 对于Flake8添加额外的调试和临时文件处理
if (tool === 'flake8') {
console.log(`Flake8原始命令: ${actualCommand}`);
console.log(`文件路径存在性: ${fs.existsSync(filePath)}`);
// 先检查文件内容
try {
const content = fs.readFileSync(filePath, 'utf8');
console.log(`Flake8检查的文件内容:\n${content}`);
// 创建本地副本进行检查
const localFileName = `temp_check_${Date.now()}.py`;
const localFilePath = path.join(process.cwd(), localFileName);
fs.writeFileSync(localFilePath, content);
console.log(`创建本地文件副本: ${localFilePath}`);
// 修改命令使用本地文件
actualFilePath = localFilePath;
actualCommand = `${config.command} ${config.args(localFilePath)}`;
console.log(`修改后的Flake8命令: ${actualCommand}`);
} catch (e) {
console.error('Flake8文件处理失败:', e);
}
}
console.log(`执行命令: ${actualCommand}`);
exec(actualCommand, { shell: true }, (error, stdout, stderr) => {
console.log(`${tool}执行结果 - 退出码: ${error ? error.code : 0}`);
let result = {
stdout: stdout ? stdout.trim() : '',
stderr: stderr ? stderr.trim() : ''
};
console.log(`${tool} stdout长度: ${result.stdout.length}`);
console.log(`${tool} stderr长度: ${result.stderr.length}`);
if (tool === 'flake8') {
console.log(`Flake8 stdout内容: "${result.stdout}"`);
console.log(`Flake8 stderr内容: "${result.stderr}"`);
// 清理本地临时文件
if (actualFilePath !== filePath) {
setTimeout(() => {
try {
if (fs.existsSync(actualFilePath)) {
fs.unlinkSync(actualFilePath);
console.log(`清理Flake8本地临时文件: ${actualFilePath}`);
}
} catch (e) {
console.error('清理Flake8本地临时文件失败:', e);
}
}, 1000);
}
}
try {
if (result.stdout) {
result.parsed = config.parser(result.stdout);
}
} catch (parseError) {
console.warn(`解析${tool}输出失败:`, parseError);
// 解析失败时返回原始输出
result.parsed = {
error: `解析失败: ${parseError.message}`,
rawOutput: result.stdout
};
}
resolve(result);
});
});
}
// 生成报告
function generateReport(results, filename) {
let report = `# 代码质量检查报告\n\n`;
report += `**文件:** ${filename}\n`;
report += `**检查时间:** ${new Date().toLocaleString()}\n\n`;
results.forEach(toolResult => {
report += `## ${toolResult.tool} 检查结果\n`;
if (toolResult.stdout) {
report += '### 输出:\n```\n';
report += toolResult.stdout;
report += '\n```\n\n';
}
if (toolResult.stderr) {
report += '### 错误:\n```\n';
report += toolResult.stderr;
report += '\n```\n\n';
}
if (toolResult.parsed) {
report += '### 解析结果:\n```json\n';
report += JSON.stringify(toolResult.parsed, null, 2);
report += '\n```\n\n';
}
});
return report;
}
// 健康检查端点
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
// 检查端点
app.post('/check', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: '未上传文件' });
}
const tools = req.body.tools ? req.body.tools.split(',') : ['bandit', 'flake8', 'pylint'];
const filePath = req.file.path;
const results = [];
// 调试:检查临时文件内容
console.log(`临时文件路径: ${filePath}`);
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
console.log(`临时文件内容 (${fileContent.length} 字符):\n${fileContent}`);
} catch (error) {
console.error('读取临时文件失败:', error);
}
for (const tool of tools) {
console.log(`开始执行工具: ${tool}`);
const result = await runTool(tool, filePath);
console.log(`${tool} 执行结果 - stdout长度: ${result.stdout.length}, stderr长度: ${result.stderr.length}`);
results.push({
tool: tool,
stdout: result.stdout,
stderr: result.stderr,
parsed: result.parsed
});
}
const outDir = ensureOutDir();
const reportContent = generateReport(results, req.file.originalname);
const reportFilename = `report_${Date.now()}.md`;
const reportPath = path.join(outDir, reportFilename);
console.log(`正在生成报告文件: ${reportPath}`);
console.log(`报告内容长度: ${reportContent.length} 字符`);
fs.writeFileSync(reportPath, reportContent);
console.log(`报告文件生成成功: ${reportPath}`);
res.json({
success: true,
results: results,
reportUrl: `/reports/${reportFilename}`
});
} catch (error) {
console.error('检查错误:', error);
res.status(500).json({
success: false,
error: error.message
});
} finally {
if (req.file && fs.existsSync(req.file.path)) {
fs.unlink(req.file.path, (err) => {
if (err) console.error('删除临时文件失败:', err);
});
}
}
});
// 报告下载端点
app.get('/reports/:filename', (req, res) => {
const filename = req.params.filename;
// 从项目根目录的out文件夹读取文件
const filePath = path.join(__dirname, '..', 'out', filename);
if (fs.existsSync(filePath)) {
res.setHeader('Content-Type', 'text/markdown');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
setTimeout(() => {
try {
fs.unlinkSync(filePath);
console.log(`已删除报告文件: ${filename}`);
} catch (err) {
console.error(`删除报告文件失败: ${err.message}`);
}
}, 5000);
} else {
res.status(404).json({
error: '报告未找到',
filename: filename
});
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
console.log(`上传文件临时目录: ${os.tmpdir()}`);
console.log(`报告输出目录: ${path.join(__dirname, '..', 'out')}`);
});
module.exports = app;

@ -1,575 +0,0 @@
<!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>

@ -1,347 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简化的代码质量检查工具</title>
<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;
}
.debug-panel {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin-top: 20px;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow: auto;
}
</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>
<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 class="debug-panel">
<strong>调试信息:</strong><br>
<div id="debug-log"></div>
</div>
</div>
<script>
// 简单的调试日志函数
function debugLog(message, data = null) {
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${message}`;
if (data) {
console.log(logEntry, data);
} else {
console.log(logEntry);
}
const debugDiv = document.getElementById('debug-log');
debugDiv.innerHTML += logEntry + '<br>';
debugDiv.scrollTop = debugDiv.scrollHeight;
}
// 页面加载
document.addEventListener('DOMContentLoaded', function () {
debugLog('页面DOM加载完成');
const submitBtn = document.getElementById('submitBtn');
const resultPre = document.getElementById('result');
const reportArea = document.getElementById('report-area');
const reportLink = document.getElementById('report-download');
const closeReportBtn = document.getElementById('close-report');
const loader = document.getElementById('loader');
const fileInput = document.getElementById('file');
let isProcessing = false;
// 初始隐藏报告区域
reportArea.style.display = 'none';
// 关闭报告区域
closeReportBtn.addEventListener('click', function (event) {
event.preventDefault();
reportArea.style.display = 'none';
debugLog('报告区域已关闭');
});
// 提交按钮处理
submitBtn.addEventListener('click', async function (event) {
debugLog('提交按钮被点击');
// 阻止所有默认行为
event.preventDefault();
event.stopPropagation();
debugLog('阻止了默认行为和冒泡');
// 防止重复提交
if (isProcessing) {
debugLog('正在处理中,忽略重复点击');
return;
}
isProcessing = true;
debugLog('开始处理请求');
// 更新UI状态
submitBtn.disabled = true;
submitBtn.textContent = '检查中...';
loader.style.display = 'block';
resultPre.textContent = '正在检查...';
reportArea.style.display = 'none';
try {
// 验证文件
if (fileInput.files.length === 0) {
throw new Error('请上传一个文件');
}
debugLog('文件验证通过');
// 获取选中的工具
const tools = [];
if (document.getElementById('bandit').checked) tools.push('bandit');
if (document.getElementById('flake8').checked) tools.push('flake8');
if (document.getElementById('pylint').checked) tools.push('pylint');
if (tools.length === 0) {
throw new Error('请至少选择一个工具');
}
debugLog('工具选择:', tools);
// 准备表单数据
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('tools', tools.join(','));
debugLog('表单数据准备完成');
// 发送请求
debugLog('开始发送网络请求...');
const response = await fetch('http://localhost:3000/check', {
method: 'POST',
body: formData
});
debugLog('收到响应,状态码:', response.status);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP错误! 状态码: ${response.status}\n${errorText}`);
}
const data = await response.json();
debugLog('成功解析响应数据');
debugLog('响应数据结构:', data);
// 显示结果
resultPre.textContent = JSON.stringify(data, null, 2);
debugLog('结果已显示在页面上');
// 显示报告下载区域
if (data.reportUrl) {
const downloadUrl = 'http://localhost:3000' + data.reportUrl;
reportLink.href = downloadUrl;
reportLink.download = `code_report_${Date.now()}.md`;
reportArea.style.display = 'block';
debugLog('报告下载链接已设置');
}
} catch (error) {
debugLog('处理过程中发生错误:', error.message);
resultPre.textContent = `错误: ${error.message}`;
console.error('详细错误信息:', error);
} finally {
// 恢复UI状态
submitBtn.disabled = false;
submitBtn.textContent = '提交检查';
loader.style.display = 'none';
isProcessing = false;
debugLog('UI状态已恢复');
}
});
// 下载链接处理
reportLink.addEventListener('click', function (event) {
event.preventDefault();
debugLog('下载链接被点击');
// 使用简单的下载方式
const link = document.createElement('a');
link.href = reportLink.href;
link.download = reportLink.download;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
debugLog('下载已启动');
});
debugLog('事件监听器设置完成');
});
// 全局错误处理
window.addEventListener('error', function (event) {
console.error('全局JavaScript错误:', event.error);
debugLog('捕获到全局错误: ' + event.message);
});
window.addEventListener('unhandledrejection', function (event) {
console.error('未处理的Promise错误:', event.reason);
debugLog('捕获到未处理Promise错误: ' + event.reason);
});
debugLog('脚本初始化完成');
</script>
</body>
</html>

@ -1,193 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防刷新测试页面</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.test-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
.btn:hover {
background-color: #2980b9;
}
.status {
margin: 10px 0;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>防刷新测试页面</h1>
<div class="test-section">
<h2>防刷新状态控制</h2>
<button id="toggleRefresh" class="btn">启用防刷新</button>
<span id="refreshStatus">防刷新: 禁用</span>
</div>
<div class="test-section">
<h2>测试按钮</h2>
<button id="testBtn1" class="btn">测试按钮1</button>
<button id="testBtn2" class="btn">测试按钮2</button>
<button id="testBtn3" class="btn">测试按钮3</button>
<a id="testLink" href="#" class="btn" style="text-decoration: none;">测试链接</a>
</div>
<div class="test-section">
<h2>结果显示</h2>
<div id="results" class="status">
点击按钮查看结果...
</div>
</div>
<div class="test-section">
<h2>键盘测试</h2>
<p>尝试按 F5 或 Ctrl+R 键测试防刷新功能</p>
<div id="keyboardResults" class="status">
键盘事件将在这里显示...
</div>
</div>
<script>
// 防刷新状态
let preventRefresh = false;
let eventCount = 0;
// DOM元素
const elements = {
toggleRefresh: document.getElementById('toggleRefresh'),
refreshStatus: document.getElementById('refreshStatus'),
testBtn1: document.getElementById('testBtn1'),
testBtn2: document.getElementById('testBtn2'),
testBtn3: document.getElementById('testBtn3'),
testLink: document.getElementById('testLink'),
results: document.getElementById('results'),
keyboardResults: document.getElementById('keyboardResults')
};
// 全局防刷新拦截器
function setupRefreshPrevention() {
// 拦截所有点击事件
document.addEventListener('click', function (event) {
if (preventRefresh) {
eventCount++;
console.log(`拦截到点击事件 #${eventCount}:`, event.target.tagName, event.target.id || event.target.textContent);
elements.results.innerHTML += `<br>点击事件 #${eventCount}: ${event.target.tagName} - ${event.target.id || event.target.textContent}`;
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截键盘事件
document.addEventListener('keydown', function (event) {
if (preventRefresh) {
console.log('拦截到键盘事件:', event.key, event.ctrlKey ? 'Ctrl+' : '');
elements.keyboardResults.innerHTML += `<br>键盘事件: ${event.key}${event.ctrlKey ? ' (Ctrl)' : ''}`;
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截链接点击
document.addEventListener('click', function (event) {
if (preventRefresh && event.target.tagName === 'A') {
console.log('拦截到链接点击:', event.target.href);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
}
// 切换防刷新状态
elements.toggleRefresh.addEventListener('click', function (event) {
event.preventDefault();
preventRefresh = !preventRefresh;
if (preventRefresh) {
elements.toggleRefresh.textContent = '禁用防刷新';
elements.toggleRefresh.style.backgroundColor = '#27ae60';
elements.refreshStatus.textContent = '防刷新: 启用';
console.log('防刷新已启用');
} else {
elements.toggleRefresh.textContent = '启用防刷新';
elements.toggleRefresh.style.backgroundColor = '#3498db';
elements.refreshStatus.textContent = '防刷新: 禁用';
eventCount = 0;
elements.results.innerHTML = '点击按钮查看结果...';
elements.keyboardResults.innerHTML = '键盘事件将在这里显示...';
console.log('防刷新已禁用');
}
});
// 添加测试按钮事件
[elements.testBtn1, elements.testBtn2, elements.testBtn3].forEach((btn, index) => {
btn.addEventListener('click', function (event) {
if (!preventRefresh) {
elements.results.innerHTML += `<br>按钮 ${index + 1} 被点击`;
}
});
});
// 添加测试链接事件
elements.testLink.addEventListener('click', function (event) {
if (!preventRefresh) {
elements.results.innerHTML += '<br>链接被点击';
}
});
// 页面事件监听器
window.addEventListener('beforeunload', function (event) {
console.log('页面即将被刷新或关闭');
if (preventRefresh) {
event.preventDefault();
event.returnValue = '';
return '';
}
});
// 初始化
setupRefreshPrevention();
console.log('防刷新测试页面初始化完成');
// 添加页面加载时间戳
const loadTime = new Date().toLocaleString();
console.log('页面加载时间:', loadTime);
document.body.insertAdjacentHTML('afterbegin',
`<div style="background: #e8f5e9; padding: 10px; margin-bottom: 20px; border-radius: 4px;">
页面加载时间: ${loadTime}
</div>`
);
</script>
</body>
</html>
Loading…
Cancel
Save