From 7d1925db66b42d4139a0be29cbe794fc30cc0b96 Mon Sep 17 00:00:00 2001 From: lintsinghua Date: Thu, 27 Nov 2025 18:01:57 +0800 Subject: [PATCH] feat: Refactor frontend layout with new sidebar and i18n, update backend LLM adapters, and adjust database models. --- backend/Dockerfile | 1 + backend/alembic.ini | 1 + backend/alembic/env.py | 1 + backend/alembic/script.py.mako | 1 + backend/app/api/v1/endpoints/members.py | 1 + backend/app/api/v1/endpoints/users.py | 1 + backend/app/core/security.py | 1 + backend/app/db/base.py | 1 + backend/app/db/session.py | 1 + backend/app/models/__init__.py | 1 + backend/app/models/analysis.py | 1 + backend/app/models/project.py | 1 + backend/app/models/user.py | 1 + backend/app/models/user_config.py | 1 + backend/app/schemas/token.py | 1 + backend/app/schemas/user.py | 1 + .../services/llm/adapters/baidu_adapter.py | 1 + .../services/llm/adapters/claude_adapter.py | 1 + .../services/llm/adapters/deepseek_adapter.py | 1 + .../services/llm/adapters/doubao_adapter.py | 1 + .../services/llm/adapters/minimax_adapter.py | 1 + .../services/llm/adapters/moonshot_adapter.py | 1 + .../services/llm/adapters/ollama_adapter.py | 1 + .../services/llm/adapters/openai_adapter.py | 1 + .../app/services/llm/adapters/qwen_adapter.py | 1 + .../services/llm/adapters/zhipu_adapter.py | 1 + backend/app/services/llm/base_adapter.py | 1 + backend/app/services/llm/types.py | 1 + backend/requirements.txt | 1 + frontend/Dockerfile | 1 + frontend/index.html | 25 +- frontend/package-lock.json | 90 ++++- frontend/package.json | 3 + frontend/src/App.tsx | 29 -- frontend/src/app/App.tsx | 36 +- frontend/src/app/ProtectedRoute.tsx | 1 + frontend/src/assets/styles/globals.css | 368 ------------------ .../src/components/audit/CreateTaskDialog.tsx | 107 +++-- .../components/database/DatabaseManager.tsx | 50 +-- frontend/src/components/layout/Footer.tsx | 26 -- frontend/src/components/layout/Header.tsx | 116 ------ frontend/src/components/layout/PageMeta.tsx | 6 +- frontend/src/components/layout/Sidebar.tsx | 186 +++++++++ .../components/reports/ExportReportDialog.tsx | 4 +- .../src/components/system/SystemConfig.tsx | 10 +- frontend/src/pages/AdminDashboard.tsx | 6 +- frontend/src/pages/ColorDemo.tsx | 0 frontend/src/pages/SamplePage.tsx | 16 - 48 files changed, 436 insertions(+), 673 deletions(-) delete mode 100644 frontend/src/App.tsx delete mode 100644 frontend/src/assets/styles/globals.css delete mode 100644 frontend/src/components/layout/Footer.tsx delete mode 100644 frontend/src/components/layout/Header.tsx create mode 100644 frontend/src/components/layout/Sidebar.tsx delete mode 100644 frontend/src/pages/ColorDemo.tsx delete mode 100644 frontend/src/pages/SamplePage.tsx diff --git a/backend/Dockerfile b/backend/Dockerfile index 5088842..f465183 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,3 +13,4 @@ COPY . . # Command is overridden by docker-compose for dev CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/backend/alembic.ini b/backend/alembic.ini index 40f0e9d..c250693 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -101,3 +101,4 @@ formatter = generic format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S + diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 5758dc8..1c35728 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -88,3 +88,4 @@ if context.is_offline_mode(): else: asyncio.run(run_migrations_online()) + diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako index de46926..96c9a3d 100644 --- a/backend/alembic/script.py.mako +++ b/backend/alembic/script.py.mako @@ -23,3 +23,4 @@ def upgrade() -> None: def downgrade() -> None: ${downgrades if downgrades else "pass"} + diff --git a/backend/app/api/v1/endpoints/members.py b/backend/app/api/v1/endpoints/members.py index 2ccb7eb..81a14e5 100644 --- a/backend/app/api/v1/endpoints/members.py +++ b/backend/app/api/v1/endpoints/members.py @@ -208,3 +208,4 @@ async def remove_project_member( return {"message": "成员已移除"} + diff --git a/backend/app/api/v1/endpoints/users.py b/backend/app/api/v1/endpoints/users.py index 95d6e30..96f6126 100644 --- a/backend/app/api/v1/endpoints/users.py +++ b/backend/app/api/v1/endpoints/users.py @@ -64,3 +64,4 @@ async def read_user_me( """ return current_user + diff --git a/backend/app/core/security.py b/backend/app/core/security.py index 81592dc..063000d 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -27,3 +27,4 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: def get_password_hash(password: str) -> str: return pwd_context.hash(password) + diff --git a/backend/app/db/base.py b/backend/app/db/base.py index 4e3b62c..88b8670 100644 --- a/backend/app/db/base.py +++ b/backend/app/db/base.py @@ -10,3 +10,4 @@ class Base: def __tablename__(cls) -> str: return cls.__name__.lower() + "s" + diff --git a/backend/app/db/session.py b/backend/app/db/session.py index a805a54..286df94 100644 --- a/backend/app/db/session.py +++ b/backend/app/db/session.py @@ -15,3 +15,4 @@ async def get_db(): finally: await session.close() + diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index f3b226f..77e8fc9 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -3,3 +3,4 @@ from .project import Project, ProjectMember from .audit import AuditTask, AuditIssue from .analysis import InstantAnalysis + diff --git a/backend/app/models/analysis.py b/backend/app/models/analysis.py index a0a1d1b..863e9fa 100644 --- a/backend/app/models/analysis.py +++ b/backend/app/models/analysis.py @@ -22,3 +22,4 @@ class InstantAnalysis(Base): # Relationships user = relationship("User", backref="instant_analyses") + diff --git a/backend/app/models/project.py b/backend/app/models/project.py index 29efe87..02d3838 100644 --- a/backend/app/models/project.py +++ b/backend/app/models/project.py @@ -42,3 +42,4 @@ class ProjectMember(Base): project = relationship("Project", back_populates="members") user = relationship("User", backref="project_memberships") + diff --git a/backend/app/models/user.py b/backend/app/models/user.py index e7a85c2..1d1cf1f 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -23,3 +23,4 @@ class User(Base): created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + diff --git a/backend/app/models/user_config.py b/backend/app/models/user_config.py index f739273..2f491e4 100644 --- a/backend/app/models/user_config.py +++ b/backend/app/models/user_config.py @@ -28,3 +28,4 @@ class UserConfig(Base): # Relationships user = relationship("User", backref="config") + diff --git a/backend/app/schemas/token.py b/backend/app/schemas/token.py index bfa2f54..05c722c 100644 --- a/backend/app/schemas/token.py +++ b/backend/app/schemas/token.py @@ -8,3 +8,4 @@ class Token(BaseModel): class TokenPayload(BaseModel): sub: Optional[str] = None + diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 74c2428..adc33a6 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -33,3 +33,4 @@ class UserInDBBase(UserBase): class User(UserInDBBase): pass + diff --git a/backend/app/services/llm/adapters/baidu_adapter.py b/backend/app/services/llm/adapters/baidu_adapter.py index c5f809c..13b6d29 100644 --- a/backend/app/services/llm/adapters/baidu_adapter.py +++ b/backend/app/services/llm/adapters/baidu_adapter.py @@ -135,3 +135,4 @@ class BaiduAdapter(BaseLLMAdapter): def get_model(self) -> str: return self.config.model or "ERNIE-3.5-8K" + diff --git a/backend/app/services/llm/adapters/claude_adapter.py b/backend/app/services/llm/adapters/claude_adapter.py index 9da018a..d324be3 100644 --- a/backend/app/services/llm/adapters/claude_adapter.py +++ b/backend/app/services/llm/adapters/claude_adapter.py @@ -91,3 +91,4 @@ class ClaudeAdapter(BaseLLMAdapter): raise Exception(f"无效的Claude模型: {self.config.model}") return True + diff --git a/backend/app/services/llm/adapters/deepseek_adapter.py b/backend/app/services/llm/adapters/deepseek_adapter.py index c26192f..5472366 100644 --- a/backend/app/services/llm/adapters/deepseek_adapter.py +++ b/backend/app/services/llm/adapters/deepseek_adapter.py @@ -79,3 +79,4 @@ class DeepSeekAdapter(BaseLLMAdapter): raise Exception("未指定DeepSeek模型") return True + diff --git a/backend/app/services/llm/adapters/doubao_adapter.py b/backend/app/services/llm/adapters/doubao_adapter.py index 31cde9e..8801b48 100644 --- a/backend/app/services/llm/adapters/doubao_adapter.py +++ b/backend/app/services/llm/adapters/doubao_adapter.py @@ -85,3 +85,4 @@ class DoubaoAdapter(BaseLLMAdapter): def get_model(self) -> str: return self.config.model or "doubao-pro-32k" + diff --git a/backend/app/services/llm/adapters/minimax_adapter.py b/backend/app/services/llm/adapters/minimax_adapter.py index a99c78b..239d86a 100644 --- a/backend/app/services/llm/adapters/minimax_adapter.py +++ b/backend/app/services/llm/adapters/minimax_adapter.py @@ -82,3 +82,4 @@ class MinimaxAdapter(BaseLLMAdapter): def get_model(self) -> str: return self.config.model or "abab6.5-chat" + diff --git a/backend/app/services/llm/adapters/moonshot_adapter.py b/backend/app/services/llm/adapters/moonshot_adapter.py index 2c1bed3..bf02320 100644 --- a/backend/app/services/llm/adapters/moonshot_adapter.py +++ b/backend/app/services/llm/adapters/moonshot_adapter.py @@ -77,3 +77,4 @@ class MoonshotAdapter(BaseLLMAdapter): raise Exception("未指定Moonshot模型") return True + diff --git a/backend/app/services/llm/adapters/ollama_adapter.py b/backend/app/services/llm/adapters/ollama_adapter.py index 69b47d5..708ca19 100644 --- a/backend/app/services/llm/adapters/ollama_adapter.py +++ b/backend/app/services/llm/adapters/ollama_adapter.py @@ -80,3 +80,4 @@ class OllamaAdapter(BaseLLMAdapter): raise Exception("未指定Ollama模型") return True + diff --git a/backend/app/services/llm/adapters/openai_adapter.py b/backend/app/services/llm/adapters/openai_adapter.py index 48d5714..48fd7ef 100644 --- a/backend/app/services/llm/adapters/openai_adapter.py +++ b/backend/app/services/llm/adapters/openai_adapter.py @@ -90,3 +90,4 @@ class OpenAIAdapter(BaseLLMAdapter): raise Exception("未指定OpenAI模型") return True + diff --git a/backend/app/services/llm/adapters/qwen_adapter.py b/backend/app/services/llm/adapters/qwen_adapter.py index af6d33b..667287c 100644 --- a/backend/app/services/llm/adapters/qwen_adapter.py +++ b/backend/app/services/llm/adapters/qwen_adapter.py @@ -77,3 +77,4 @@ class QwenAdapter(BaseLLMAdapter): raise Exception("未指定通义千问模型") return True + diff --git a/backend/app/services/llm/adapters/zhipu_adapter.py b/backend/app/services/llm/adapters/zhipu_adapter.py index d78b0a8..6069502 100644 --- a/backend/app/services/llm/adapters/zhipu_adapter.py +++ b/backend/app/services/llm/adapters/zhipu_adapter.py @@ -77,3 +77,4 @@ class ZhipuAdapter(BaseLLMAdapter): raise Exception("未指定智谱AI模型") return True + diff --git a/backend/app/services/llm/base_adapter.py b/backend/app/services/llm/base_adapter.py index 2314d08..6eb4a69 100644 --- a/backend/app/services/llm/base_adapter.py +++ b/backend/app/services/llm/base_adapter.py @@ -132,3 +132,4 @@ class BaseLLMAdapter(ABC): await self._client.aclose() self._client = None + diff --git a/backend/app/services/llm/types.py b/backend/app/services/llm/types.py index 605288d..50d438f 100644 --- a/backend/app/services/llm/types.py +++ b/backend/app/services/llm/types.py @@ -118,3 +118,4 @@ DEFAULT_BASE_URLS: Dict[LLMProvider, str] = { LLMProvider.CLAUDE: "https://api.anthropic.com/v1", } + diff --git a/backend/requirements.txt b/backend/requirements.txt index 3bf7b70..4db4873 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,3 +10,4 @@ python-jose[cryptography] python-multipart httpx + diff --git a/frontend/Dockerfile b/frontend/Dockerfile index abfd6ca..c24674d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -16,3 +16,4 @@ EXPOSE 5173 CMD ["npm", "run", "dev", "--", "--host"] + diff --git a/frontend/index.html b/frontend/index.html index 3db4bf3..7dcb069 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,16 @@ - - - - - XCodeReviewer - - -
- - - + + + + + + XCodeReviewer + + + +
+ + + + \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a2f2994..985af57 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,6 +45,8 @@ "embla-carousel-react": "^8.6.0", "eventsource-parser": "^3.0.6", "fflate": "^0.8.2", + "i18next": "^25.6.3", + "i18next-browser-languagedetector": "^8.2.0", "input-otp": "^1.4.2", "ky": "^1.9.1", "lucide-react": "^0.525.0", @@ -57,6 +59,7 @@ "react-dom": "^18.0.0", "react-helmet-async": "^2.0.5", "react-hook-form": "^7.56.1", + "react-i18next": "^16.3.5", "react-resizable-panels": "^2.1.8", "react-router": "^7.1.5", "react-router-dom": "^6.30.0", @@ -6391,6 +6394,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -6411,6 +6423,46 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/i18next": { + "version": "25.6.3", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-25.6.3.tgz", + "integrity": "sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8602,6 +8654,33 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-i18next": { + "version": "16.3.5", + "resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-16.3.5.tgz", + "integrity": "sha512-F7Kglc+T0aE6W2rO5eCAFBEuWRpNb5IFmXOYEgztjZEuiuSLTe/xBIEG6Q3S0fbl8GXMNo+Q7gF8bpokFNWJww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", @@ -9684,7 +9763,7 @@ "version": "5.7.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10091,6 +10170,15 @@ "vite": ">=2.6.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index d1b077c..e48bf1b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,6 +52,8 @@ "embla-carousel-react": "^8.6.0", "eventsource-parser": "^3.0.6", "fflate": "^0.8.2", + "i18next": "^25.6.3", + "i18next-browser-languagedetector": "^8.2.0", "input-otp": "^1.4.2", "ky": "^1.9.1", "lucide-react": "^0.525.0", @@ -64,6 +66,7 @@ "react-dom": "^18.0.0", "react-helmet-async": "^2.0.5", "react-hook-form": "^7.56.1", + "react-i18next": "^16.3.5", "react-resizable-panels": "^2.1.8", "react-router": "^7.1.5", "react-router-dom": "^6.30.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index ede903c..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; -import { Toaster } from "sonner"; -import Header from "@/components/layout/Header"; -import routes, { type RouteConfig } from "./app/routes"; - -function App() { - return ( - - -
-
-
- - {routes.map((route: RouteConfig) => ( - - ))} - } /> - -
-
-
- ); -} - -export default App; diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index 3ff66ed..99b50ef 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -1,6 +1,7 @@ -import { BrowserRouter, Routes, Route, Navigate, Outlet } from "react-router-dom"; +import { useState } from "react"; +import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; import { Toaster } from "sonner"; -import Header from "@/components/layout/Header"; +import Sidebar from "@/components/layout/Sidebar"; import routes from "./routes"; import { AuthProvider } from "@/shared/context/AuthContext"; import { ProtectedRoute } from "./ProtectedRoute"; @@ -9,12 +10,19 @@ import Register from "@/pages/Register"; import NotFound from "@/pages/NotFound"; function AppLayout() { + const [collapsed, setCollapsed] = useState(false); + return (
-
-
- -
+ +
+
+ +
+
); } @@ -28,17 +36,17 @@ function App() { {/* Public Routes */} } /> } /> - + {/* Protected Routes */} }> }> - {routes.map((route) => ( - - ))} + {routes.map((route) => ( + + ))} diff --git a/frontend/src/app/ProtectedRoute.tsx b/frontend/src/app/ProtectedRoute.tsx index 5333e9a..764f102 100644 --- a/frontend/src/app/ProtectedRoute.tsx +++ b/frontend/src/app/ProtectedRoute.tsx @@ -16,3 +16,4 @@ export const ProtectedRoute = () => { return ; }; + diff --git a/frontend/src/assets/styles/globals.css b/frontend/src/assets/styles/globals.css deleted file mode 100644 index 0be2dc4..0000000 --- a/frontend/src/assets/styles/globals.css +++ /dev/null @@ -1,368 +0,0 @@ -/* stylelint-disable */ -@tailwind base; -@tailwind components; -@tailwind utilities; - -/* Definition of the design system. All colors, gradients, fonts, etc should be defined here. -All colors MUST be HSL. - -专业深红色配色方案: -- 主色调:深红色 (0 75% 25%) - 体现专业性和权威感 -- 强调色:暗红色 (0 60% 35%) - 用于交互元素和重点突出 -- 背景色:浅灰到黑色渐变 - 提供高对比度和现代感 -- 状态色:保持语义化的绿色(成功)、红色(错误)、黄色(警告) -*/ - -@layer base { - :root { - --background: 0 0% 98%; - --foreground: 0 0% 5%; - - --card: 0 0% 100%; - --card-foreground: 0 0% 5%; - - --popover: 0 0% 100%; - --popover-foreground: 0 0% 5%; - - --primary: 0 75% 25%; - --primary-foreground: 0 0% 98%; - - --secondary: 0 0% 95%; - --secondary-foreground: 0 75% 25%; - - --muted: 0 0% 94%; - --muted-foreground: 0 0% 40%; - - --accent: 0 60% 35%; - --accent-foreground: 0 0% 98%; - - --destructive: 0 84% 60%; - --destructive-foreground: 0 0% 98%; - - --border: 0 0% 90%; - --input: 0 0% 90%; - --ring: 0 75% 25%; - - --radius: 0.5rem; - - /* 专业渐变效果 */ - --gradient-primary: linear-gradient(135deg, hsl(0 75% 25%) 0%, hsl(0 60% 35%) 100%); - --gradient-card: linear-gradient(145deg, hsl(0 0% 100%) 0%, hsl(0 0% 98%) 100%); - --gradient-background: linear-gradient(180deg, hsl(0 0% 98%) 0%, hsl(0 0% 95%) 100%); - - /* 专业阴影效果 */ - --shadow-card: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); - --shadow-hover: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1); - --shadow-focus: 0 0 0 3px hsl(0 75% 25% / 0.1); - - --sidebar-background: 0 0% 8%; - --sidebar-foreground: 0 0% 85%; - --sidebar-primary: 0 75% 35%; - --sidebar-primary-foreground: 0 0% 98%; - --sidebar-accent: 0 0% 12%; - --sidebar-accent-foreground: 0 0% 85%; - --sidebar-border: 0 0% 15%; - --sidebar-ring: 0 75% 45%; - } - - .dark { - --background: 0 0% 3%; - --foreground: 0 0% 95%; - - --card: 0 0% 5%; - --card-foreground: 0 0% 95%; - - --popover: 0 0% 5%; - --popover-foreground: 0 0% 95%; - - --primary: 0 75% 45%; - --primary-foreground: 0 0% 5%; - - --secondary: 0 0% 8%; - --secondary-foreground: 0 0% 95%; - - --muted: 0 0% 8%; - --muted-foreground: 0 0% 60%; - - --accent: 0 60% 50%; - --accent-foreground: 0 0% 5%; - - --destructive: 0 75% 55%; - --destructive-foreground: 0 0% 95%; - - --border: 0 0% 12%; - --input: 0 0% 12%; - --ring: 0 75% 45%; - - /* 暗色模式专业渐变效果 */ - --gradient-primary: linear-gradient(135deg, hsl(0 75% 45%) 0%, hsl(0 60% 50%) 100%); - --gradient-card: linear-gradient(145deg, hsl(0 0% 5%) 0%, hsl(0 0% 3%) 100%); - --gradient-background: linear-gradient(180deg, hsl(0 0% 3%) 0%, hsl(0 0% 1%) 100%); - - /* 暗色模式专业阴影效果 */ - --shadow-card: 0 1px 3px 0 hsl(0 0% 0% / 0.3), 0 1px 2px -1px hsl(0 0% 0% / 0.3); - --shadow-hover: 0 4px 6px -1px hsl(0 0% 0% / 0.4), 0 2px 4px -2px hsl(0 0% 0% / 0.4); - --shadow-focus: 0 0 0 3px hsl(0 75% 45% / 0.2); - - --sidebar-background: 0 0% 2%; - --sidebar-foreground: 0 0% 85%; - --sidebar-primary: 0 75% 50%; - --sidebar-primary-foreground: 0 0% 5%; - --sidebar-accent: 0 0% 6%; - --sidebar-accent-foreground: 0 0% 85%; - --sidebar-border: 0 0% 8%; - --sidebar-ring: 0 75% 55%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - font-feature-settings: "rlig" 1, "calt" 1; - } -} - -@layer components { - /* 页面标题样式 */ - .page-title { - @apply text-2xl md:text-3xl font-bold text-foreground tracking-tight; - } - - .page-subtitle { - @apply text-sm md:text-base text-muted-foreground mt-1; - } - - /* 现代化卡片样式 */ - .card-modern { - @apply bg-card rounded-xl border border-border shadow-sm hover:shadow-md transition-all duration-200; - } - - /* 统计卡片样式 */ - .stat-card { - @apply bg-card rounded-xl border border-border shadow-sm hover:shadow-lg transition-all duration-300 hover:border-accent/30; - } - - .stat-label { - @apply text-xs font-medium text-muted-foreground uppercase tracking-wide; - } - - .stat-value { - @apply text-2xl md:text-3xl font-bold text-foreground mt-1; - } - - .stat-icon { - @apply w-12 h-12 rounded-lg bg-gradient-to-br from-primary/10 to-accent/10 flex items-center justify-center shadow-sm; - } - - /* 按钮样式 */ - .btn-primary { - @apply bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200; - } - - .btn-secondary { - @apply border-border hover:border-accent/50 hover:bg-accent/5 transition-all duration-200; - } - - /* 空状态样式 */ - .empty-state { - @apply flex flex-col items-center justify-center text-center; - } - - .empty-icon { - @apply w-16 h-16 rounded-full bg-muted flex items-center justify-center mb-4; - } - - /* 动画 */ - .animate-fade-in { - animation: fadeIn 0.5s ease-out; - } - - @keyframes fadeIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - - /* 渐变背景 */ - .gradient-bg { - @apply bg-gradient-to-br from-background via-muted/30 to-accent/5; - } - - /* 响应式容器 */ - .container-responsive { - @apply container mx-auto px-4 sm:px-6 lg:px-8; - } - - /* 文本截断 */ - .line-clamp-2 { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - } - - /* 毛玻璃效果 */ - .backdrop-blur-md { - backdrop-filter: blur(12px); - } - - /* 平滑滚动 */ - html { - scroll-behavior: smooth; - } - - /* 自定义滚动条 */ - ::-webkit-scrollbar { - width: 6px; - height: 6px; - } - - ::-webkit-scrollbar-track { - background: transparent; - } - - ::-webkit-scrollbar-thumb { - background: hsl(var(--muted-foreground) / 0.3); - border-radius: 3px; - } - - ::-webkit-scrollbar-thumb:hover { - background: hsl(var(--accent) / 0.6); - } - - /* 专业高级感样式 */ - .professional-card { - @apply bg-gradient-card border border-border/50 shadow-card hover:shadow-hover transition-all duration-300; - } - - .professional-button { - @apply bg-gradient-primary text-primary-foreground font-semibold px-6 py-3 rounded-lg shadow-sm hover:shadow-md transition-all duration-200 focus:ring-2 focus:ring-primary/20 focus:outline-none; - } - - .professional-input { - @apply bg-background border border-border rounded-lg px-4 py-2 focus:border-primary focus:ring-2 focus:ring-primary/10 transition-all duration-200; - } - - .status-indicator { - @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; - } - - .status-success { - @apply bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400; - } - - .status-warning { - @apply bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400; - } - - .status-error { - @apply bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400; - } - - .status-info { - @apply bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400; - } - - /* 高级导航样式 */ - .nav-link { - @apply flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-accent/10 hover:text-accent-foreground; - } - - .nav-link.active { - @apply bg-primary/10 text-primary border-r-2 border-primary; - } - - /* 专业表格样式 */ - .professional-table { - @apply w-full border-collapse bg-card rounded-lg overflow-hidden shadow-card; - } - - .professional-table th { - @apply bg-muted/50 px-6 py-3 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider border-b border-border; - } - - .professional-table td { - @apply px-6 py-4 whitespace-nowrap text-sm text-foreground border-b border-border/50; - } - - .professional-table tr:hover { - @apply bg-accent/5; - } - - /* 图表和图标专用样式 */ - .chart-primary { - color: hsl(var(--primary)); - } - - .chart-accent { - color: hsl(var(--accent)); - } - - .chart-success { - color: #10b981; - } - - .chart-warning { - color: #f59e0b; - } - - .chart-error { - color: #ef4444; - } - - .icon-primary { - @apply text-primary; - } - - .icon-accent { - @apply text-accent; - } - - .icon-muted { - @apply text-muted-foreground; - } - - /* 加载动画 */ - .loading-spinner { - @apply animate-spin rounded-full border-2 border-primary border-t-transparent; - } - - /* 状态指示器颜色 */ - .status-running { - @apply bg-red-50 text-red-800 border-red-200; - } - - .status-completed { - @apply bg-green-50 text-green-800 border-green-200; - } - - .status-failed { - @apply bg-red-100 text-red-900 border-red-300; - } - - /* 渐变图标背景 */ - .icon-bg-primary { - @apply bg-gradient-to-br from-primary/10 to-accent/10; - } - - .icon-bg-success { - @apply bg-gradient-to-br from-green-100 to-emerald-100; - } - - .icon-bg-warning { - @apply bg-gradient-to-br from-yellow-100 to-orange-100; - } - - .icon-bg-error { - @apply bg-gradient-to-br from-red-100 to-red-200; - } -} diff --git a/frontend/src/components/audit/CreateTaskDialog.tsx b/frontend/src/components/audit/CreateTaskDialog.tsx index ae73121..4d6d70a 100644 --- a/frontend/src/components/audit/CreateTaskDialog.tsx +++ b/frontend/src/components/audit/CreateTaskDialog.tsx @@ -8,11 +8,11 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { - GitBranch, - Settings, - FileText, - AlertCircle, +import { + GitBranch, + Settings, + FileText, + AlertCircle, Info, Zap, Shield, @@ -43,7 +43,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr const [zipFile, setZipFile] = useState(null); const [loadingZipFile, setLoadingZipFile] = useState(false); const [hasLoadedZip, setHasLoadedZip] = useState(false); - + const [taskForm, setTaskForm] = useState({ project_id: "", task_type: "repository", @@ -77,7 +77,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr // 后端 MAX_FILE_SIZE_BYTES 是 200 * 1024 = 204800 bytes = 200KB // 转换为KB用于前端显示 const maxFileSizeKB = 200; // 后端默认值 200KB - + setTaskForm(prev => ({ ...prev, scan_config: { @@ -111,14 +111,14 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr useEffect(() => { const autoLoadZipFile = async () => { if (!taskForm.project_id || hasLoadedZip) return; - + const project = projects.find(p => p.id === taskForm.project_id); if (!project || project.repository_type !== 'other') return; - + try { setLoadingZipFile(true); const savedFile = await loadZipFile(taskForm.project_id); - + if (savedFile) { setZipFile(savedFile); setHasLoadedZip(true); @@ -167,11 +167,11 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr try { setCreating(true); - - console.log('🎯 开始创建审计任务...', { - projectId: project.id, + + console.log('🎯 开始创建审计任务...', { + projectId: project.id, projectName: project.name, - repositoryType: project.repository_type + repositoryType: project.repository_type }); let taskId: string; @@ -183,7 +183,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr toast.error("请上传ZIP文件进行扫描"); return; } - + console.log('📦 调用 scanZipFile...'); taskId = await scanZipFile({ projectId: project.id, @@ -194,7 +194,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr } else { // GitHub/GitLab等远程仓库 console.log('📡 调用 runRepositoryAudit...'); - + // 后端会从用户配置中读取 GitHub/GitLab Token,前端不需要传递 taskId = await runRepositoryAudit({ projectId: project.id, @@ -204,9 +204,9 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr createdBy: 'local-user' }); } - + console.log('✅ 任务创建成功:', taskId); - + // 记录用户操作 import('@/shared/utils/logger').then(({ logger, LogCategory }) => { logger.logUserAction('创建审计任务', { @@ -218,25 +218,25 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr hasZipFile: !!zipFile, }); }); - + // 关闭创建对话框 onOpenChange(false); resetForm(); onTaskCreated(); - + // 显示终端进度窗口 setCurrentTaskId(taskId); setShowTerminalDialog(true); - + toast.success("审计任务已创建并启动"); } catch (error) { console.error('❌ 创建任务失败:', error); - + // 记录错误并显示详细信息 import('@/shared/utils/errorHandler').then(({ handleError }) => { handleError(error, '创建审计任务失败'); }); - + const errorMessage = error instanceof Error ? error.message : '未知错误'; toast.error(`创建任务失败: ${errorMessage}`); } finally { @@ -336,13 +336,12 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr ) : filteredProjects.length > 0 ? ( filteredProjects.map((project) => ( - setTaskForm({ ...taskForm, project_id: project.id })} > @@ -415,11 +414,11 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr

已准备就绪

使用保存的ZIP文件: {zipFile.name} ( - {zipFile.size >= 1024 * 1024 + {zipFile.size >= 1024 * 1024 ? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB` : zipFile.size >= 1024 - ? `${(zipFile.size / 1024).toFixed(2)} KB` - : `${zipFile.size} B` + ? `${(zipFile.size / 1024).toFixed(2)} KB` + : `${zipFile.size} B` })

@@ -445,7 +444,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr

- +
= 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`; - + toast.success(`已选择文件: ${file.name} (${sizeText})`); } }} @@ -491,8 +490,8 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
- +