diff --git a/src/backend.js b/src/backend.js
index ad6bdb2..3f47e85 100644
--- a/src/backend.js
+++ b/src/backend.js
@@ -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 {
diff --git a/src/frontend/css/style.css b/src/frontend/css/style.css
index c181939..1af6225 100644
--- a/src/frontend/css/style.css
+++ b/src/frontend/css/style.css
@@ -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;
diff --git a/src/frontend/index.html b/src/frontend/index.html
index eebe868..212ff31 100644
--- a/src/frontend/index.html
+++ b/src/frontend/index.html
@@ -1,5 +1,6 @@
+
@@ -7,6 +8,7 @@
+