fix: 修复时间显示问题,使用带时区的UTC时间
- 将所有 datetime.utcnow() 替换为 datetime.now(timezone.utc) - 修复 completed_at, started_at, updated_at, resolved_at 等时间字段 - 修复 JWT token 过期时间计算 - 修复数据导出和ZIP上传时间戳 - 调整README中项目管理和审计报告图片显示比例
This commit is contained in:
parent
4f95f65645
commit
14b7c8cccc
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
| 项目管理 | 审计报告 |
|
||||
|:---:|:---:|
|
||||
|  |  |
|
||||
| <img src="frontend/public/images/example3.png" alt="项目管理" width="400"> | <img src="frontend/public/images/审计报告示例.png" alt="审计报告" width="400"> |
|
||||
| *GitHub/GitLab 无缝集成* | *专业报告,一键导出* |
|
||||
|
||||
## ✨ 为什么选择我们?
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from sqlalchemy.future import select
|
|||
from sqlalchemy.orm import selectinload
|
||||
from pydantic import BaseModel
|
||||
import json
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.api import deps
|
||||
from app.db.session import get_db
|
||||
|
|
@ -94,7 +94,7 @@ async def export_database(
|
|||
# 7. 构建导出数据
|
||||
export_data = {
|
||||
"version": "1.0.0",
|
||||
"export_date": datetime.utcnow().isoformat(),
|
||||
"export_date": datetime.now(timezone.utc).isoformat(),
|
||||
"user": {
|
||||
"id": current_user.id,
|
||||
"email": current_user.email,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import shutil
|
||||
import os
|
||||
import uuid
|
||||
|
|
@ -236,7 +236,7 @@ async def update_project(
|
|||
for field, value in update_data.items():
|
||||
setattr(project, field, value)
|
||||
|
||||
project.updated_at = datetime.utcnow()
|
||||
project.updated_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
await db.refresh(project)
|
||||
return project
|
||||
|
|
@ -260,7 +260,7 @@ async def delete_project(
|
|||
raise HTTPException(status_code=403, detail="无权删除此项目")
|
||||
|
||||
project.is_active = False
|
||||
project.updated_at = datetime.utcnow()
|
||||
project.updated_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
return {"message": "项目已删除"}
|
||||
|
||||
|
|
@ -283,7 +283,7 @@ async def restore_project(
|
|||
raise HTTPException(status_code=403, detail="无权恢复此项目")
|
||||
|
||||
project.is_active = True
|
||||
project.updated_at = datetime.utcnow()
|
||||
project.updated_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
return {"message": "项目已恢复"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
from typing import Any, List, Optional
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
import shutil
|
||||
import os
|
||||
|
|
@ -53,7 +53,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
|
|||
|
||||
try:
|
||||
task.status = "running"
|
||||
task.started_at = datetime.utcnow()
|
||||
task.started_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
|
||||
# 创建使用用户配置的LLM服务实例
|
||||
|
|
@ -116,7 +116,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
|
|||
if task_control.is_cancelled(task_id):
|
||||
print(f"🛑 ZIP任务 {task_id} 已被取消")
|
||||
task.status = "cancelled"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
task_control.cleanup_task(task_id)
|
||||
return
|
||||
|
|
@ -173,7 +173,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
|
|||
# 如果有文件需要分析但全部失败,标记为失败
|
||||
if len(files_to_scan) > 0 and scanned_files == 0:
|
||||
task.status = "failed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
task.scanned_files = 0
|
||||
task.total_lines = total_lines
|
||||
task.issues_count = 0
|
||||
|
|
@ -182,7 +182,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
|
|||
print(f"❌ ZIP任务 {task_id} 失败: 所有 {len(files_to_scan)} 个文件分析均失败,请检查 LLM API 配置")
|
||||
else:
|
||||
task.status = "completed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
task.scanned_files = scanned_files
|
||||
task.total_lines = total_lines
|
||||
task.issues_count = total_issues
|
||||
|
|
@ -194,7 +194,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
|
|||
except Exception as e:
|
||||
print(f"❌ ZIP扫描失败: {e}")
|
||||
task.status = "failed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
task_control.cleanup_task(task_id)
|
||||
finally:
|
||||
|
|
@ -408,7 +408,7 @@ async def instant_analysis(
|
|||
# 创建使用用户配置的LLM服务实例
|
||||
llm_service = LLMService(user_config=user_config)
|
||||
|
||||
start_time = datetime.utcnow()
|
||||
start_time = datetime.now(timezone.utc)
|
||||
|
||||
try:
|
||||
result = await llm_service.analyze_code(req.code, req.language)
|
||||
|
|
@ -421,7 +421,7 @@ async def instant_analysis(
|
|||
detail=f"代码分析失败: {error_msg}"
|
||||
)
|
||||
|
||||
end_time = datetime.utcnow()
|
||||
end_time = datetime.now(timezone.utc)
|
||||
duration = (end_time - start_time).total_seconds()
|
||||
|
||||
# Save record
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.api import deps
|
||||
from app.db.session import get_db
|
||||
|
|
@ -161,7 +161,7 @@ async def cancel_task(
|
|||
|
||||
# 更新数据库状态
|
||||
task.status = "cancelled"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
|
||||
return {"message": "任务已取消", "task_id": id}
|
||||
|
|
@ -223,7 +223,7 @@ async def update_issue(
|
|||
issue.status = issue_update.status
|
||||
if issue_update.status == "resolved":
|
||||
issue.resolved_by = current_user.id
|
||||
issue.resolved_at = datetime.utcnow()
|
||||
issue.resolved_at = datetime.now(timezone.utc)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(issue)
|
||||
|
|
@ -299,7 +299,7 @@ async def export_task_report_pdf(
|
|||
pdf_bytes = ReportGenerator.generate_task_report(task_dict, issues_list, project_name)
|
||||
|
||||
# 返回 PDF 文件
|
||||
filename = f"audit-report-{task.id[:8]}-{datetime.utcnow().strftime('%Y%m%d')}.pdf"
|
||||
filename = f"audit-report-{task.id[:8]}-{datetime.now(timezone.utc).strftime('%Y%m%d')}.pdf"
|
||||
return Response(
|
||||
content=pdf_bytes,
|
||||
media_type="application/pdf",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Union
|
||||
from jose import jwt
|
||||
from passlib.context import CryptContext
|
||||
|
|
@ -12,9 +12,9 @@ def create_access_token(
|
|||
subject: Union[str, Any], expires_delta: timedelta = None
|
||||
) -> str:
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(
|
||||
expire = datetime.now(timezone.utc) + timedelta(
|
||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
to_encode = {"exp": expire, "sub": str(subject)}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"""
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ async def create_demo_data(db: AsyncSession, user: User) -> None:
|
|||
return
|
||||
|
||||
logger.info("开始创建演示数据...")
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# ==================== 创建演示项目 ====================
|
||||
projects_data = [
|
||||
|
|
|
|||
|
|
@ -215,14 +215,16 @@ class LLMService:
|
|||
2. JSON字符串值中的特殊字符必须正确转义(换行用\\n,双引号用\\",反斜杠用\\\\)
|
||||
3. code_snippet字段必须使用\\n表示换行
|
||||
|
||||
请从以下维度全面分析代码:
|
||||
- 编码规范和代码风格
|
||||
请从以下维度全面、彻底地分析代码,找出所有问题:
|
||||
- 安全漏洞(SQL注入、XSS、命令注入、路径遍历、SSRF、XXE、反序列化、硬编码密钥等)
|
||||
- 潜在的 Bug 和逻辑错误
|
||||
- 性能问题和优化建议
|
||||
- 安全漏洞和风险
|
||||
- 编码规范和代码风格
|
||||
- 可维护性和可读性
|
||||
- 最佳实践和设计模式
|
||||
|
||||
【重要】请尽可能多地找出代码中的所有问题,不要遗漏任何安全漏洞或潜在风险!
|
||||
|
||||
输出格式必须严格符合以下 JSON Schema:
|
||||
|
||||
{schema}
|
||||
|
|
@ -268,14 +270,16 @@ You are a professional code auditing assistant. Your task is to analyze code and
|
|||
2. Special characters in JSON strings must be properly escaped (\\n for newlines, \\" for quotes, \\\\ for backslashes)
|
||||
3. code_snippet field MUST use \\n for newlines
|
||||
|
||||
Please comprehensively analyze the code from the following dimensions:
|
||||
- Coding standards and code style
|
||||
Please comprehensively and thoroughly analyze the code, finding ALL issues from the following dimensions:
|
||||
- Security vulnerabilities (SQL injection, XSS, command injection, path traversal, SSRF, XXE, deserialization, hardcoded secrets, etc.)
|
||||
- Potential bugs and logical errors
|
||||
- Performance issues and optimization suggestions
|
||||
- Security vulnerabilities and risks
|
||||
- Coding standards and code style
|
||||
- Maintainability and readability
|
||||
- Best practices and design patterns
|
||||
|
||||
【IMPORTANT】Find as many issues as possible! Do NOT miss any security vulnerabilities or potential risks!
|
||||
|
||||
The output format MUST strictly conform to the following JSON Schema:
|
||||
|
||||
{schema}
|
||||
|
|
@ -358,6 +362,10 @@ Please analyze the following code:
|
|||
response = await adapter.complete(request)
|
||||
content = response.content
|
||||
|
||||
# 记录 LLM 原始响应(用于调试)
|
||||
logger.info(f"📥 LLM 原始响应长度: {len(content) if content else 0} 字符")
|
||||
logger.info(f"📥 LLM 原始响应内容:\n{content}")
|
||||
|
||||
# 检查响应内容是否为空
|
||||
if not content or not content.strip():
|
||||
error_msg = f"LLM返回空响应 - Provider: {self.config.provider.value}, Model: {self.config.model}"
|
||||
|
|
@ -368,6 +376,10 @@ Please analyze the following code:
|
|||
# 尝试从响应中提取JSON
|
||||
result = self._parse_json(content)
|
||||
|
||||
# 记录解析后的问题数量
|
||||
issues_count = len(result.get("issues", []))
|
||||
logger.info(f"📊 LLM 分析结果: 发现 {issues_count} 个问题, 质量评分: {result.get('quality_score', 'N/A')}")
|
||||
|
||||
# 检查解析结果是否有效(不是默认响应)
|
||||
if result == self._get_default_response():
|
||||
error_msg = f"无法解析LLM响应为有效的分析结果 - Provider: {self.config.provider.value}"
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ class ReportGenerator:
|
|||
.issue-item {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 10px 0; /* Reduced padding */
|
||||
break-inside: avoid;
|
||||
/* 移除 break-inside: avoid,允许问题块跨页 */
|
||||
}
|
||||
|
||||
.issue-item:last-child {
|
||||
|
|
@ -220,6 +220,8 @@ class ReportGenerator:
|
|||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 6px; /* Reduced margin */
|
||||
break-inside: avoid; /* 标题行不断开 */
|
||||
break-after: avoid; /* 标题后不断页 */
|
||||
}
|
||||
|
||||
.issue-title {
|
||||
|
|
@ -247,6 +249,7 @@ class ReportGenerator:
|
|||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
font-family: monospace;
|
||||
break-after: avoid; /* 元信息后不断页 */
|
||||
}
|
||||
|
||||
.issue-desc {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import asyncio
|
||||
import httpx
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urlparse, quote
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
try:
|
||||
# 1. 更新状态为运行中
|
||||
task.status = "running"
|
||||
task.started_at = datetime.utcnow()
|
||||
task.started_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
|
||||
# 创建使用用户配置的LLM服务实例
|
||||
|
|
@ -336,7 +336,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
if task_control.is_cancelled(task_id):
|
||||
print(f"🛑 任务 {task_id} 已被用户取消")
|
||||
task.status = "cancelled"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
task_control.cleanup_task(task_id)
|
||||
return
|
||||
|
|
@ -379,7 +379,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
if task_control.is_cancelled(task_id):
|
||||
print(f"🛑 任务 {task_id} 在LLM分析后被取消")
|
||||
task.status = "cancelled"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
task_control.cleanup_task(task_id)
|
||||
return
|
||||
|
|
@ -454,7 +454,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
# 如果所有文件都被跳过(空文件等),标记为完成但给出提示
|
||||
if len(files) > 0 and scanned_files == 0 and skipped_files == len(files):
|
||||
task.status = "completed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
task.scanned_files = 0
|
||||
task.total_lines = 0
|
||||
task.issues_count = 0
|
||||
|
|
@ -464,7 +464,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
# 如果有文件需要分析但全部失败(LLM调用失败),标记为失败
|
||||
elif len(files) > 0 and scanned_files == 0 and failed_files > 0:
|
||||
task.status = "failed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
task.scanned_files = 0
|
||||
task.total_lines = total_lines
|
||||
task.issues_count = 0
|
||||
|
|
@ -473,7 +473,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
print(f"❌ 任务 {task_id} 失败: {failed_files} 个文件分析失败,请检查 LLM API 配置")
|
||||
else:
|
||||
task.status = "completed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
task.scanned_files = scanned_files
|
||||
task.total_lines = total_lines
|
||||
task.issues_count = total_issues
|
||||
|
|
@ -485,6 +485,6 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
|
|||
except Exception as e:
|
||||
print(f"❌ 扫描失败: {e}")
|
||||
task.status = "failed"
|
||||
task.completed_at = datetime.utcnow()
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
task_control.cleanup_task(task_id)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import os
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ async def save_project_zip(project_id: str, file_path: str, original_filename: s
|
|||
meta = {
|
||||
"original_filename": original_filename,
|
||||
"file_size": file_size,
|
||||
"uploaded_at": datetime.utcnow().isoformat(),
|
||||
"uploaded_at": datetime.now(timezone.utc).isoformat(),
|
||||
"project_id": project_id
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 404 KiB |
|
|
@ -159,15 +159,15 @@ export default function InstantExportDialog({
|
|||
</div>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">质量评分:</span>
|
||||
<span className="font-bold text-black">{analysisResult.quality_score.toFixed(1)}/100</span>
|
||||
<span className="font-bold text-black">{(analysisResult.quality_score ?? 0).toFixed(1)}/100</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">发现问题:</span>
|
||||
<span className="font-bold text-orange-600">{analysisResult.issues.length}</span>
|
||||
<span className="font-bold text-orange-600">{analysisResult.issues?.length ?? 0}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">分析耗时:</span>
|
||||
<span className="font-bold text-black">{analysisTime.toFixed(2)}s</span>
|
||||
<span className="font-bold text-black">{(analysisTime ?? 0).toFixed(2)}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -633,7 +633,7 @@ class UserManager {
|
|||
'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
评分: {record.quality_score.toFixed(1)}
|
||||
评分: {(record.quality_score ?? 0).toFixed(1)}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -653,7 +653,7 @@ class UserManager {
|
|||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{record.analysis_time.toFixed(2)}s
|
||||
{(record.analysis_time ?? 0).toFixed(2)}s
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -827,7 +827,7 @@ class UserManager {
|
|||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{analysisTime.toFixed(2)}s
|
||||
{(analysisTime ?? 0).toFixed(2)}s
|
||||
</Badge>
|
||||
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
|
||||
{language}
|
||||
|
|
@ -852,10 +852,10 @@ class UserManager {
|
|||
<Target className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-primary mb-1">
|
||||
{result.quality_score.toFixed(1)}
|
||||
{(result.quality_score ?? 0).toFixed(1)}
|
||||
</div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-2">质量评分</p>
|
||||
<Progress value={result.quality_score} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||
<Progress value={result.quality_score ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
|
|
@ -863,7 +863,7 @@ class UserManager {
|
|||
<AlertTriangle className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-red-600 mb-1">
|
||||
{result.summary.critical_issues + result.summary.high_issues}
|
||||
{(result.summary?.critical_issues ?? 0) + (result.summary?.high_issues ?? 0)}
|
||||
</div>
|
||||
<p className="text-xs font-bold text-red-700 uppercase mb-1">严重问题</p>
|
||||
<div className="text-xs text-red-600 font-bold uppercase">需要立即处理</div>
|
||||
|
|
@ -874,7 +874,7 @@ class UserManager {
|
|||
<Info className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-yellow-600 mb-1">
|
||||
{result.summary.medium_issues + result.summary.low_issues}
|
||||
{(result.summary?.medium_issues ?? 0) + (result.summary?.low_issues ?? 0)}
|
||||
</div>
|
||||
<p className="text-xs font-bold text-yellow-700 uppercase mb-1">一般问题</p>
|
||||
<div className="text-xs text-yellow-600 font-bold uppercase">建议优化</div>
|
||||
|
|
@ -900,24 +900,24 @@ class UserManager {
|
|||
</h3>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.complexity}</div>
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics?.complexity ?? 0}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">复杂度</p>
|
||||
<Progress value={result.metrics.complexity} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
<Progress value={result.metrics?.complexity ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.maintainability}</div>
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics?.maintainability ?? 0}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">可维护性</p>
|
||||
<Progress value={result.metrics.maintainability} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
<Progress value={result.metrics?.maintainability ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.security}</div>
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics?.security ?? 0}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">安全性</p>
|
||||
<Progress value={result.metrics.security} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
<Progress value={result.metrics?.security ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.performance}</div>
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics?.performance ?? 0}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">性能</p>
|
||||
<Progress value={result.metrics.performance} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
<Progress value={result.metrics?.performance ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue