Compare commits
5 Commits
99a3281d58
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
250192f699 | ||
|
|
cef1ae0414 | ||
|
|
eeb5e728ff | ||
|
|
98f916bd37 | ||
|
|
80430c674b |
@@ -19,80 +19,87 @@ export default function Layout() {
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 flex h-screen w-screen overflow-hidden text-gray-800 font-sans">
|
||||
<div className="bg-[#f7f7f9] flex h-screen w-screen overflow-hidden text-txt font-sans">
|
||||
{/* 侧边栏导航 */}
|
||||
<aside className="w-64 bg-white shadow-md flex flex-col z-10 shrink-0">
|
||||
<div className="h-16 flex items-center px-6 border-b border-gray-100">
|
||||
<i className="fa-solid fa-layer-group text-[#2563EB] text-2xl mr-3"></i>
|
||||
<span className="text-xl font-bold text-gray-800">SAFe OS</span>
|
||||
<aside className="w-64 bg-white border-r border-border flex flex-col z-10 shrink-0">
|
||||
<div className="h-16 flex items-center px-6 border-b border-border">
|
||||
<i className="fa-solid fa-layer-group text-magenta text-xl mr-3"></i>
|
||||
<span className="text-lg font-extrabold text-txt">SAFe OS</span>
|
||||
</div>
|
||||
|
||||
<div className="p-4 flex-1">
|
||||
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-4">主导模块</div>
|
||||
<nav className="space-y-2">
|
||||
<div className="text-[10px] font-bold text-txt-muted uppercase tracking-widest mb-3 px-1">主导模块</div>
|
||||
<nav className="space-y-1">
|
||||
<NavLink
|
||||
to="/"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center px-4 py-3 rounded-lg font-medium transition-colors ${
|
||||
`flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all duration-150 ${
|
||||
isActive && pathname === '/'
|
||||
? "bg-blue-50 text-[#2563EB]"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-[#2563EB]"
|
||||
? "bg-magenta/10 text-magenta"
|
||||
: "text-txt hover:bg-surface-muted hover:text-magenta"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<i className="fa-solid fa-house w-6"></i> 总览控制台
|
||||
<i className="fa-solid fa-house w-4 text-center shrink-0"></i>
|
||||
<span>总览控制台</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/planning"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center px-4 py-3 rounded-lg font-medium transition-colors ${
|
||||
`flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all duration-150 ${
|
||||
isActive
|
||||
? "bg-blue-50 text-[#0EA5E9]"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-[#0EA5E9]"
|
||||
? "bg-[#0EA5E9]/10 text-[#0EA5E9]"
|
||||
: "text-txt hover:bg-surface-muted hover:text-[#0EA5E9]"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<i className="fa-solid fa-chess-knight w-6"></i> 战略规划 (Planning)
|
||||
<i className="fa-solid fa-chess-knight w-4 text-center shrink-0"></i>
|
||||
<span>战略规划</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/devops"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center px-4 py-3 rounded-lg font-medium transition-colors ${
|
||||
`flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all duration-150 ${
|
||||
isActive
|
||||
? "bg-green-50 text-[#10B981]"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-[#10B981]"
|
||||
? "bg-[#10B981]/10 text-[#10B981]"
|
||||
: "text-txt hover:bg-surface-muted hover:text-[#10B981]"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<i className="fa-solid fa-code-branch w-6"></i> 开发运维 (DevOps)
|
||||
<i className="fa-solid fa-code-branch w-4 text-center shrink-0"></i>
|
||||
<span>开发运维</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/quality/dashboard"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center px-4 py-3 rounded-lg font-medium transition-colors ${
|
||||
className={() =>
|
||||
`flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all duration-150 ${
|
||||
pathname.startsWith('/quality')
|
||||
? "bg-purple-50 text-[#8B5CF6]"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-[#8B5CF6]"
|
||||
? "bg-[#8B5CF6]/10 text-[#8B5CF6]"
|
||||
: "text-txt hover:bg-surface-muted hover:text-[#8B5CF6]"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<i className="fa-solid fa-shield-halved w-6"></i> 质量门控 (Quality Gate)
|
||||
<i className="fa-solid fa-shield-halved w-4 text-center shrink-0"></i>
|
||||
<span>质量门控</span>
|
||||
</NavLink>
|
||||
</nav>
|
||||
|
||||
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wider mt-8 mb-4">设置与支持</div>
|
||||
<nav className="space-y-2">
|
||||
<a href="#" className="flex items-center px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
<i className="fa-solid fa-gear w-6"></i> 平台设置
|
||||
<div className="text-[10px] font-bold text-txt-muted uppercase tracking-widest mt-8 mb-3 px-1">设置与支持</div>
|
||||
<nav className="space-y-1">
|
||||
<a href="#" className="flex items-center gap-3 px-4 py-2.5 text-txt text-sm font-semibold hover:bg-surface-muted rounded-xl transition-all duration-150">
|
||||
<i className="fa-solid fa-gear w-4 text-center shrink-0"></i>
|
||||
<span>平台设置</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-gray-100 flex items-center">
|
||||
<img src="https://ui-avatars.com/api/?name=Admin&background=F3F4F6&color=374151" alt="User" className="w-10 h-10 rounded-full mr-3" />
|
||||
<div className="p-4 border-t border-border flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-full bg-magenta/10 text-magenta flex items-center justify-center text-sm font-bold shrink-0">A</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold">项目管理员</p>
|
||||
<p className="text-xs text-gray-500">在线</p>
|
||||
<p className="text-sm font-semibold text-txt">项目管理员</p>
|
||||
<p className="text-xs text-txt-muted flex items-center gap-1">
|
||||
<span className="w-1.5 h-1.5 bg-green-400 rounded-full inline-block"></span>在线
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -100,24 +107,24 @@ export default function Layout() {
|
||||
{/* 主内容区 */}
|
||||
<main className="flex-1 flex flex-col h-screen overflow-y-auto relative min-w-0">
|
||||
{/* 顶部导航 */}
|
||||
<header className="h-16 bg-white shadow-sm flex items-center justify-between px-8 z-0 shrink-0">
|
||||
<h1 className="text-xl font-semibold text-gray-800">{pageTitle}</h1>
|
||||
<header className="h-14 bg-white border-b border-border flex items-center justify-between px-8 z-0 shrink-0 shadow-[0_1px_4px_rgba(0,0,0,0.04)]">
|
||||
<h1 className="text-base font-bold text-txt">{pageTitle}</h1>
|
||||
{pathname === '/' && (
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<i className="fa-solid fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
|
||||
<input type="text" placeholder="全局搜索需求、代码或 PR..." className="pl-10 pr-4 py-2 border border-gray-200 rounded-full text-sm focus:outline-none focus:border-[#2563EB] focus:ring-1 focus:ring-[#2563EB] w-64 transition-all" />
|
||||
<i className="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-txt-muted text-xs"></i>
|
||||
<input type="text" placeholder="搜索需求、代码或 PR…" className="pl-9 pr-4 py-2 border border-border rounded-xl text-sm focus:outline-none focus:border-magenta focus:ring-2 focus:ring-magenta/10 w-60 transition-all bg-[#f7f7f9]" />
|
||||
</div>
|
||||
<button className="relative p-2 text-gray-500 hover:text-[#2563EB] transition-colors">
|
||||
<i className="fa-regular fa-bell text-xl"></i>
|
||||
<span className="absolute top-1 right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white"></span>
|
||||
<button className="relative p-2 text-txt-muted hover:text-magenta transition-colors">
|
||||
<i className="fa-regular fa-bell text-base"></i>
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full border border-white"></span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* 内容画布 */}
|
||||
<div className="flex-1 w-full bg-gray-50 overflow-y-auto">
|
||||
<div className="flex-1 w-full bg-[#f7f7f9] overflow-y-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background:
|
||||
radial-gradient(circle at 20% -10%, #fff0f7 0%, transparent 35%),
|
||||
radial-gradient(circle at 20% -10%, #fff1f2 0%, transparent 35%),
|
||||
radial-gradient(circle at 90% 0%, #f5f5f5 0%, transparent 28%),
|
||||
#fff;
|
||||
color: #1a1a1a;
|
||||
|
||||
@@ -489,7 +489,7 @@ export default function DevOpsAgent() {
|
||||
<div className="flex flex-col h-full overflow-hidden bg-[#f7f7f9]">
|
||||
{/* ── 步骤栏 ── */}
|
||||
<div className="shrink-0 border-b border-border bg-white shadow-[0_1px_8px_rgba(0,0,0,0.04)]">
|
||||
<div className="flex items-center gap-0 px-8 py-0">
|
||||
<div className="flex items-center gap-0 px-[60px] py-0">
|
||||
{STEPS.map((s, i) => (
|
||||
<div key={i} className="flex items-center">
|
||||
<button
|
||||
@@ -532,15 +532,15 @@ export default function DevOpsAgent() {
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="flex items-center justify-between px-4 py-2.5 mx-8 mt-4 text-sm text-red-700 bg-red-50 border border-red-200 rounded-xl">
|
||||
<div className="flex items-center justify-between px-4 py-2.5 mx-10 mt-4 text-sm text-red-700 bg-red-50 border border-red-200 rounded-xl">
|
||||
<span className="flex items-center gap-2"><span>⚠️</span>{error}</span>
|
||||
<button className="ml-4 font-bold text-red-300 hover:text-red-500" onClick={() => setError("")}>✕</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── 内容区 ── */}
|
||||
<div className="flex-1 px-8 py-6 overflow-y-auto">
|
||||
<div className="max-w-5xl mx-auto space-y-0">
|
||||
<div className="flex-1 px-[60px] py-6 overflow-y-auto">
|
||||
<div className="space-y-0">
|
||||
|
||||
{/* ═══ Step 0: Requirement Input ═══ */}
|
||||
{step === 0 && !sessionId && (
|
||||
|
||||
@@ -3,132 +3,171 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="p-8 max-w-7xl mx-auto w-full">
|
||||
{/* 欢迎与 AI 提示 */}
|
||||
<div className="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-2xl p-8 text-white mb-8 shadow-lg flex justify-between items-center">
|
||||
<div className="p-[60px] w-full min-h-[calc(100vh-3.5rem)] flex flex-col">
|
||||
|
||||
{/* 欢迎横幅 */}
|
||||
<div className="mb-8 flex items-center justify-between overflow-hidden relative rounded-2xl border border-magenta/15 bg-gradient-to-br from-magenta/[0.06] via-white to-white shadow-[0_4px_16px_rgba(244,63,94,0.06)] p-6">
|
||||
<div className="absolute top-0 left-0 right-0 h-0.5 bg-gradient-to-r from-magenta via-magenta/60 to-transparent"></div>
|
||||
<div className="pt-1">
|
||||
<h2 className="text-2xl font-extrabold text-txt mb-1.5">欢迎回来,SAFe OS 团队 👋</h2>
|
||||
<p className="text-txt-muted text-sm mb-4">AI Agent 已准备就绪,今天想从哪个环节开始推进项目?</p>
|
||||
<div className="flex items-center gap-2 text-sm bg-surface-muted px-4 py-2 rounded-xl w-fit">
|
||||
<span className="w-2 h-2 bg-green-400 rounded-full shrink-0"></span>
|
||||
<span className="text-txt-muted">AI 状态:</span>
|
||||
<span className="text-green-600 font-semibold">运行中(随时可唤醒)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block shrink-0 ml-8 text-6xl font-extrabold text-txt opacity-[0.04] select-none">
|
||||
<i className="fa-brands fa-hubspot"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据概览 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mb-8">
|
||||
<div className="bg-white rounded-2xl border border-border shadow-[0_4px_16px_rgba(0,0,0,0.04)] overflow-hidden flex">
|
||||
<div className="w-1 shrink-0 bg-[#0EA5E9]"></div>
|
||||
<div className="flex items-center gap-4 px-5 py-5 flex-1">
|
||||
<div className="w-10 h-10 rounded-xl bg-[#0EA5E9]/10 flex items-center justify-center shrink-0">
|
||||
<i className="fa-solid fa-list-check text-[#0EA5E9] text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold mb-2">欢迎回来, SAFe OS 团队 👋</h2>
|
||||
<p className="text-blue-100 mb-4 text-lg">AI Agent 已经准备就绪,今天想从哪个环节开始推进项目?</p>
|
||||
<div className="flex items-center text-sm bg-black/20 inline-block px-4 py-2 rounded-lg backdrop-blur-sm">
|
||||
<i className="fa-solid fa-robot mr-2 text-blue-300"></i> AI 状态: <span className="text-green-300 ml-1 font-semibold">✓ 运行中 (随时可唤醒)</span>
|
||||
<p className="text-[11px] font-bold text-txt-muted uppercase tracking-wide mb-0.5">规划中需求</p>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className="text-3xl font-extrabold text-txt leading-none">24</span>
|
||||
<span className="text-xs font-semibold text-green-500">↑ 3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl border border-border shadow-[0_4px_16px_rgba(0,0,0,0.04)] overflow-hidden flex">
|
||||
<div className="w-1 shrink-0 bg-[#10B981]"></div>
|
||||
<div className="flex items-center gap-4 px-5 py-5 flex-1">
|
||||
<div className="w-10 h-10 rounded-xl bg-[#10B981]/10 flex items-center justify-center shrink-0">
|
||||
<i className="fa-solid fa-code-pull-request text-[#10B981] text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[11px] font-bold text-txt-muted uppercase tracking-wide mb-0.5">待合入 PRs</p>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className="text-3xl font-extrabold text-txt leading-none">12</span>
|
||||
<span className="text-xs font-semibold text-red-500">↓ 2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl border border-border shadow-[0_4px_16px_rgba(0,0,0,0.04)] overflow-hidden flex">
|
||||
<div className="w-1 shrink-0 bg-[#8B5CF6]"></div>
|
||||
<div className="flex items-center gap-4 px-5 py-5 flex-1">
|
||||
<div className="w-10 h-10 rounded-xl bg-[#8B5CF6]/10 flex items-center justify-center shrink-0">
|
||||
<i className="fa-solid fa-heart-pulse text-[#8B5CF6] text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[11px] font-bold text-txt-muted uppercase tracking-wide mb-0.5">质量健康度</p>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className="text-3xl font-extrabold text-txt leading-none">92%</span>
|
||||
<span className="text-xs font-semibold text-green-500">优秀</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block text-right">
|
||||
<div className="text-5xl font-bold opacity-20"><i className="fa-brands fa-hubspot"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据概览精要 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
<div className="bg-white p-5 rounded-xl text-center border border-gray-100 shadow-sm">
|
||||
<p className="text-sm text-gray-500 mb-1">规划中需求</p>
|
||||
<p className="text-2xl font-bold text-gray-800">24 <span className="text-xs text-green-500 font-normal"><i className="fa-solid fa-arrow-up"></i> 3</span></p>
|
||||
</div>
|
||||
<div className="bg-white p-5 rounded-xl text-center border border-gray-100 shadow-sm">
|
||||
<p className="text-sm text-gray-500 mb-1">待合入代码 (PRs)</p>
|
||||
<p className="text-2xl font-bold text-gray-800">12 <span className="text-xs text-red-500 font-normal"><i className="fa-solid fa-arrow-down"></i> 2</span></p>
|
||||
</div>
|
||||
<div className="bg-white p-5 rounded-xl text-center border border-gray-100 shadow-sm">
|
||||
<p className="text-sm text-gray-500 mb-1">质量健康度</p>
|
||||
<p className="text-2xl font-bold text-gray-800 focus-text">92% <span className="text-xs text-green-500 font-normal">优秀</span></p>
|
||||
</div>
|
||||
{/* 核心模块入口 */}
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<h3 className="text-base font-extrabold text-txt">核心工作流入口</h3>
|
||||
<div className="flex-1 h-px bg-border"></div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 flex-1">
|
||||
|
||||
{/* 三大核心模块入口卡片 */}
|
||||
<h3 className="text-lg font-bold text-gray-800 mb-4">核心工作流入口</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
|
||||
{/* 模块 1: 战略规划 (Planning) */}
|
||||
<div className="module-card bg-white rounded-2xl overflow-hidden border border-gray-100 shadow-sm relative group flex flex-col hover:-translate-y-1 hover:shadow-xl transition-all duration-300">
|
||||
<div className="h-2 bg-[#0EA5E9]"></div>
|
||||
{/* 模块 1: 战略规划 */}
|
||||
<div className="group flex flex-col bg-white rounded-2xl overflow-hidden border border-border shadow-[0_4px_16px_rgba(0,0,0,0.04)] hover:-translate-y-1 hover:shadow-[0_12px_32px_rgba(0,0,0,0.08)] transition-all duration-300">
|
||||
<div className="h-1 bg-[#0EA5E9]"></div>
|
||||
<div className="p-6 flex-1">
|
||||
<div className="w-12 h-12 bg-blue-50 text-[#0EA5E9] rounded-xl flex items-center justify-center text-2xl mb-4 group-hover:bg-[#0EA5E9] group-hover:text-white transition-colors">
|
||||
<div className="w-11 h-11 bg-[#0EA5E9]/10 text-[#0EA5E9] rounded-xl flex items-center justify-center text-xl mb-4 group-hover:bg-[#0EA5E9] group-hover:text-white transition-colors duration-200">
|
||||
<i className="fa-solid fa-chess-knight"></i>
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-gray-800 mb-2">战略规划</h4>
|
||||
<p className="text-gray-500 mb-4 text-sm min-h-[60px]">一站式需求管理与分析。利用自然语言或文档,AI帮助您生成验收标准、拆解任务和预测边界情况。</p>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex items-center text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="fa-solid fa-check text-[#0EA5E9] mr-2 w-4"></i> 智能需求分析与拆解
|
||||
<h4 className="text-base font-extrabold text-txt mb-2">战略规划</h4>
|
||||
<p className="text-txt-muted text-sm mb-5 leading-relaxed">一站式需求管理与分析。利用自然语言或文档,AI 帮助生成验收标准、拆解任务和预测边界情况。</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2.5 text-sm text-txt bg-surface-muted px-3 py-2 rounded-lg">
|
||||
<i className="fa-solid fa-check text-[#0EA5E9] text-xs shrink-0"></i>
|
||||
<span>智能需求分析与拆解</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="fa-solid fa-check text-[#0EA5E9] mr-2 w-4"></i> 上下文动态问题对话
|
||||
<div className="flex items-center gap-2.5 text-sm text-txt bg-surface-muted px-3 py-2 rounded-lg">
|
||||
<i className="fa-solid fa-check text-[#0EA5E9] text-xs shrink-0"></i>
|
||||
<span>上下文动态问题对话</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-gray-100">
|
||||
<Link to="/planning" className="block text-center w-full py-2 bg-white border border-gray-200 text-[#0EA5E9] font-semibold rounded-lg hover:border-[#0EA5E9] hover:bg-blue-50 transition-colors">
|
||||
进入规划看板 →
|
||||
<div className="px-6 py-4 bg-surface-muted border-t border-border">
|
||||
<Link to="/planning" className="block text-center w-full py-2 bg-white border border-border text-[#0EA5E9] text-sm font-semibold rounded-xl hover:border-[#0EA5E9] hover:bg-[#0EA5E9]/5 transition-colors">
|
||||
进入规划看板 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 模块 2: 开发运维 (DevOps) */}
|
||||
<div className="module-card bg-white rounded-2xl overflow-hidden border border-gray-100 shadow-sm relative group flex flex-col hover:-translate-y-1 hover:shadow-xl transition-all duration-300">
|
||||
<div className="h-2 bg-[#10B981]"></div>
|
||||
{/* 模块 2: 开发运维 */}
|
||||
<div className="group flex flex-col bg-white rounded-2xl overflow-hidden border border-border shadow-[0_4px_16px_rgba(0,0,0,0.04)] hover:-translate-y-1 hover:shadow-[0_12px_32px_rgba(0,0,0,0.08)] transition-all duration-300">
|
||||
<div className="h-1 bg-[#10B981]"></div>
|
||||
<div className="p-6 flex-1">
|
||||
<div className="w-12 h-12 bg-green-50 text-[#10B981] rounded-xl flex items-center justify-center text-2xl mb-4 group-hover:bg-[#10B981] group-hover:text-white transition-colors">
|
||||
<div className="w-11 h-11 bg-[#10B981]/10 text-[#10B981] rounded-xl flex items-center justify-center text-xl mb-4 group-hover:bg-[#10B981] group-hover:text-white transition-colors duration-200">
|
||||
<i className="fa-solid fa-code-branch"></i>
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-gray-800 mb-2">开发运维</h4>
|
||||
<p className="text-gray-500 mb-4 text-sm min-h-[60px]">智能研发辅助流水线。输入功能点描述即可生成规范的代码框架和单元测试代码,一键直达测试验证。</p>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex items-center text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="fa-solid fa-check text-[#10B981] mr-2 w-4"></i> AI 代码框架生成
|
||||
<h4 className="text-base font-extrabold text-txt mb-2">开发运维</h4>
|
||||
<p className="text-txt-muted text-sm mb-5 leading-relaxed">智能研发辅助流水线。输入功能点描述即可生成规范代码框架和单元测试,一键直达测试验证。</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2.5 text-sm text-txt bg-surface-muted px-3 py-2 rounded-lg">
|
||||
<i className="fa-solid fa-check text-[#10B981] text-xs shrink-0"></i>
|
||||
<span>AI 代码框架生成</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="fa-solid fa-check text-[#10B981] mr-2 w-4"></i> 自动化测试与建议
|
||||
<div className="flex items-center gap-2.5 text-sm text-txt bg-surface-muted px-3 py-2 rounded-lg">
|
||||
<i className="fa-solid fa-check text-[#10B981] text-xs shrink-0"></i>
|
||||
<span>自动化测试与建议</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-gray-100">
|
||||
<Link to="/devops" className="block text-center w-full py-2 bg-white border border-gray-200 text-[#10B981] font-semibold rounded-lg hover:border-[#10B981] hover:bg-green-50 transition-colors">
|
||||
开启开发流水线 →
|
||||
<div className="px-6 py-4 bg-surface-muted border-t border-border">
|
||||
<Link to="/devops" className="block text-center w-full py-2 bg-white border border-border text-[#10B981] text-sm font-semibold rounded-xl hover:border-[#10B981] hover:bg-[#10B981]/5 transition-colors">
|
||||
开启开发流水线 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 模块 3: 质量门控 (Quality Gate) */}
|
||||
<div className="module-card bg-white rounded-2xl overflow-hidden border border-gray-100 shadow-sm relative group flex flex-col hover:-translate-y-1 hover:shadow-xl transition-all duration-300">
|
||||
<div className="h-2 bg-[#8B5CF6]"></div>
|
||||
{/* 模块 3: 质量门控 */}
|
||||
<div className="group flex flex-col bg-white rounded-2xl overflow-hidden border border-border shadow-[0_4px_16px_rgba(0,0,0,0.04)] hover:-translate-y-1 hover:shadow-[0_12px_32px_rgba(0,0,0,0.08)] transition-all duration-300">
|
||||
<div className="h-1 bg-[#8B5CF6]"></div>
|
||||
<div className="p-6 flex-1">
|
||||
<div className="w-12 h-12 bg-purple-50 text-[#8B5CF6] rounded-xl flex items-center justify-center text-2xl mb-4 group-hover:bg-[#8B5CF6] group-hover:text-white transition-colors">
|
||||
<div className="w-11 h-11 bg-[#8B5CF6]/10 text-[#8B5CF6] rounded-xl flex items-center justify-center text-xl mb-4 group-hover:bg-[#8B5CF6] group-hover:text-white transition-colors duration-200">
|
||||
<i className="fa-solid fa-shield-halved"></i>
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-gray-800 mb-2">质量门控</h4>
|
||||
<p className="text-gray-500 mb-4 text-sm min-h-[60px]">项目质量的守护神。提供可视化的Dashboard概览,智能审查 PR 修改,拦截代码漏洞与规范问题。</p>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex items-center text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="fa-solid fa-check text-[#8B5CF6] mr-2 w-4"></i> PR 级智能安全扫描
|
||||
<h4 className="text-base font-extrabold text-txt mb-2">质量门控</h4>
|
||||
<p className="text-txt-muted text-sm mb-5 leading-relaxed">项目质量的守护神。提供可视化 Dashboard 概览,智能审查 PR,拦截代码漏洞与规范问题。</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2.5 text-sm text-txt bg-surface-muted px-3 py-2 rounded-lg">
|
||||
<i className="fa-solid fa-check text-[#8B5CF6] text-xs shrink-0"></i>
|
||||
<span>PR 级智能安全扫描</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="fa-solid fa-check text-[#8B5CF6] mr-2 w-4"></i> 对话式漏洞修复建议
|
||||
<div className="flex items-center gap-2.5 text-sm text-txt bg-surface-muted px-3 py-2 rounded-lg">
|
||||
<i className="fa-solid fa-check text-[#8B5CF6] text-xs shrink-0"></i>
|
||||
<span>对话式漏洞修复建议</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-gray-100">
|
||||
<Link to="/quality" className="block text-center w-full py-2 bg-white border border-gray-200 text-[#8B5CF6] font-semibold rounded-lg hover:border-[#8B5CF6] hover:bg-purple-50 transition-colors">
|
||||
查看质量大盘 →
|
||||
<div className="px-6 py-4 bg-surface-muted border-t border-border">
|
||||
<Link to="/quality" className="block text-center w-full py-2 bg-white border border-border text-[#8B5CF6] text-sm font-semibold rounded-xl hover:border-[#8B5CF6] hover:bg-[#8B5CF6]/5 transition-colors">
|
||||
查看质量大盘 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* 全局悬浮 AI Agent 助手入口(占位) */}
|
||||
<div className="fixed bottom-10 right-10 flex flex-col items-end z-50">
|
||||
<div className="bg-white px-4 py-3 rounded-2xl shadow-lg border border-gray-100 mb-4 relative animate-fade-in-up">
|
||||
<p className="text-sm text-gray-600">需要我帮您新建一个“提测需求”还是“审查PR”呢?</p>
|
||||
<div className="absolute -bottom-2 right-6 w-4 h-4 bg-white border-b border-r border-gray-100 transform rotate-45"></div>
|
||||
</div>
|
||||
<button className="w-16 h-16 bg-[#2563EB] text-white rounded-full shadow-2xl flex items-center justify-center text-3xl hover:bg-blue-700 transition-colors ai-pulse">
|
||||
<i className="fa-brands fa-hubspot"></i>
|
||||
{/* 悬浮 AI 问答按钮 */}
|
||||
<button
|
||||
className="fixed bottom-8 right-8 w-14 h-14 bg-magenta text-white rounded-2xl shadow-[0_8px_24px_rgba(244,63,94,0.35)] hover:shadow-[0_12px_32px_rgba(244,63,94,0.45)] hover:-translate-y-1 active:translate-y-0 transition-all duration-200 flex items-center justify-center text-xl z-50"
|
||||
title="AI 助手"
|
||||
>
|
||||
<i className="fa-solid fa-robot"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -274,8 +274,8 @@ export default function PlanningAgent() {
|
||||
<div className="h-full">
|
||||
{/* ─── Main Chat ─── */}
|
||||
<div className="h-full flex flex-col min-w-0">
|
||||
<div className="flex-1 overflow-y-auto px-10 py-8">
|
||||
<div className="flex flex-col gap-6 max-w-5xl w-full mx-auto">
|
||||
<div className="flex-1 overflow-y-auto px-[72px] py-8">
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
{state.messages.length === 0 && (
|
||||
<div className="text-center py-20 card">
|
||||
<h2 className="text-2xl font-extrabold mb-2">Planning Council Agent</h2>
|
||||
@@ -320,7 +320,7 @@ export default function PlanningAgent() {
|
||||
|
||||
{/* Error */}
|
||||
{state.error && (
|
||||
<div className="mx-10 mb-2 px-4 py-2 bg-red-50 text-red-700 text-sm flex justify-between items-center">
|
||||
<div className="mx-[72px] mb-2 px-4 py-2 bg-red-50 text-red-700 text-sm flex justify-between items-center">
|
||||
{state.error}
|
||||
<button
|
||||
className="text-red-400 hover:text-red-600 font-bold"
|
||||
|
||||
@@ -31,22 +31,216 @@ type ChangedFile = {
|
||||
deletions: number;
|
||||
};
|
||||
|
||||
type FileContent = {
|
||||
content: string;
|
||||
issues: Array<{
|
||||
type FileContentIssue = {
|
||||
line: number;
|
||||
severity: string;
|
||||
message: string;
|
||||
rule?: string;
|
||||
}>;
|
||||
scanner?: string;
|
||||
};
|
||||
|
||||
type FileContent = {
|
||||
content: string;
|
||||
issues: FileContentIssue[];
|
||||
};
|
||||
|
||||
function normalizeFileContent(data: {
|
||||
content?: string;
|
||||
issues?: Array<{ line?: number; severity?: string; message?: string; description?: string; rule?: string; scanner?: string }>;
|
||||
scan_issues?: Array<{ line?: number; severity?: string; message?: string; description?: string; rule?: string; scanner?: string }>;
|
||||
}): FileContent {
|
||||
const raw = data.scan_issues ?? data.issues ?? [];
|
||||
const issues: FileContentIssue[] = raw.map((iss) => {
|
||||
const line = typeof iss.line === "number" ? iss.line : typeof iss.line === "string" ? parseInt(iss.line, 10) : 1;
|
||||
return {
|
||||
line: Number.isFinite(line) ? line : 1,
|
||||
severity: String(iss.severity ?? "info").toLowerCase(),
|
||||
message: String(iss.message ?? iss.description ?? ""),
|
||||
rule: iss.rule,
|
||||
scanner: iss.scanner,
|
||||
};
|
||||
});
|
||||
return {
|
||||
content: data.content ?? "",
|
||||
issues,
|
||||
};
|
||||
}
|
||||
|
||||
type View = "dashboard" | "pr-list" | "settings";
|
||||
|
||||
type PRHistoryItem = {
|
||||
pr_number: number;
|
||||
error_count?: number;
|
||||
warning_count?: number;
|
||||
total_issues?: number;
|
||||
};
|
||||
|
||||
type QualityGateProps = {
|
||||
view: View;
|
||||
};
|
||||
|
||||
const TREND_SLOTS = 15;
|
||||
const TREND_PX_PER_PR = 80;
|
||||
const TREND_CHART_HEIGHT = 220;
|
||||
|
||||
function ProblemTrendChart({
|
||||
history,
|
||||
loading,
|
||||
}: {
|
||||
history: PRHistoryItem[];
|
||||
loading: boolean;
|
||||
}) {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)] p-4">
|
||||
<h3 className="text-base font-extrabold mb-3">问题趋势</h3>
|
||||
<div className="text-center py-8 text-txt-muted text-sm">加载趋势数据中...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!history || history.length === 0) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)] p-4">
|
||||
<h3 className="text-base font-extrabold mb-3">问题趋势</h3>
|
||||
<div className="text-center py-8 text-txt-muted text-sm">暂无趋势数据</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const n = history.length;
|
||||
const pad = Math.max(0, TREND_SLOTS - n);
|
||||
const labels = history.map((p) => `#${p.pr_number}`).concat(Array(pad).fill(""));
|
||||
const errorData: (number | null)[] = history.map((p) => p.error_count ?? 0).concat(Array(pad).fill(null));
|
||||
const warningData: (number | null)[] = history.map((p) => p.warning_count ?? 0).concat(Array(pad).fill(null));
|
||||
|
||||
const width = TREND_SLOTS * TREND_PX_PER_PR;
|
||||
const height = TREND_CHART_HEIGHT;
|
||||
const padding = { top: 28, right: 24, bottom: 32, left: 40 };
|
||||
const chartW = width - padding.left - padding.right;
|
||||
const chartH = height - padding.top - padding.bottom;
|
||||
|
||||
const maxVal = Math.max(
|
||||
1,
|
||||
...errorData.filter((v): v is number => v != null),
|
||||
...warningData.filter((v): v is number => v != null)
|
||||
);
|
||||
const yMax = Math.ceil(maxVal / 2) * 2 || 2;
|
||||
const yTicks = Array.from({ length: yMax + 1 }, (_, i) => i);
|
||||
|
||||
const xScale = (i: number) => padding.left + (i / (labels.length - 1 || 1)) * chartW;
|
||||
const yScale = (v: number) => padding.top + chartH - (v / yMax) * chartH;
|
||||
|
||||
const toPath = (data: (number | null)[]) => {
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const v = data[i];
|
||||
if (v == null) continue;
|
||||
const x = xScale(i);
|
||||
const y = yScale(v);
|
||||
parts.push(parts.length === 0 ? `M ${x} ${y}` : `L ${x} ${y}`);
|
||||
}
|
||||
return parts.join(" ");
|
||||
};
|
||||
|
||||
const toAreaPath = (data: (number | null)[]) => {
|
||||
const points: { i: number; v: number }[] = [];
|
||||
data.forEach((v, i) => {
|
||||
if (v != null) points.push({ i, v });
|
||||
});
|
||||
if (points.length === 0) return "";
|
||||
const linePath = points.map(({ i, v }) => `${i === 0 ? "M" : "L"} ${xScale(i)} ${yScale(v)}`).join(" ");
|
||||
const lastX = xScale(points[points.length - 1].i);
|
||||
const baseY = padding.top + chartH;
|
||||
const startX = xScale(points[0].i);
|
||||
return `${linePath} L ${lastX} ${baseY} L ${startX} ${baseY} Z`;
|
||||
};
|
||||
|
||||
const errorPath = toPath(errorData);
|
||||
const warningPath = toPath(warningData);
|
||||
const errorAreaPath = toAreaPath(errorData);
|
||||
const warningAreaPath = toAreaPath(warningData);
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)] p-4">
|
||||
<h3 className="text-base font-extrabold mb-3">问题趋势</h3>
|
||||
<div className="overflow-x-auto overflow-y-hidden" style={{ maxWidth: "100%" }}>
|
||||
<div style={{ width, minWidth: width, height }}>
|
||||
<svg width={width} height={height} className="overflow-visible">
|
||||
{/* 网格线 */}
|
||||
{yTicks.map((tick, i) => (
|
||||
<line
|
||||
key={i}
|
||||
x1={padding.left}
|
||||
y1={yScale(tick)}
|
||||
x2={padding.left + chartW}
|
||||
y2={yScale(tick)}
|
||||
stroke="rgba(0,0,0,0.12)"
|
||||
strokeWidth={1}
|
||||
strokeDasharray="4 4"
|
||||
/>
|
||||
))}
|
||||
{labels.map((_, i) => (
|
||||
<line
|
||||
key={i}
|
||||
x1={xScale(i)}
|
||||
y1={padding.top}
|
||||
x2={xScale(i)}
|
||||
y2={padding.top + chartH}
|
||||
stroke="rgba(0,0,0,0.12)"
|
||||
strokeWidth={1}
|
||||
strokeDasharray="4 4"
|
||||
/>
|
||||
))}
|
||||
{/* 图例 */}
|
||||
<g transform={`translate(${padding.left}, 8)`}>
|
||||
<rect x={0} y={2} width={14} height={10} fill="#dc3545" rx={1} />
|
||||
<text x={18} y={11} fontSize={11} fill="currentColor" className="text-txt">
|
||||
错误
|
||||
</text>
|
||||
<rect x={70} y={2} width={14} height={10} fill="#ffc107" rx={1} />
|
||||
<text x={88} y={11} fontSize={11} fill="currentColor" className="text-txt">
|
||||
警告
|
||||
</text>
|
||||
</g>
|
||||
{/* 面积填充 */}
|
||||
<path d={errorAreaPath} fill="rgba(220, 53, 69, 0.1)" />
|
||||
<path d={warningAreaPath} fill="rgba(255, 193, 7, 0.1)" />
|
||||
{/* 折线 */}
|
||||
<path d={errorPath} fill="none" stroke="#dc3545" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d={warningPath} fill="none" stroke="#ffc107" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
{/* Y 轴刻度 */}
|
||||
{yTicks.map((tick) => (
|
||||
<text
|
||||
key={tick}
|
||||
x={padding.left - 8}
|
||||
y={yScale(tick) + 4}
|
||||
textAnchor="end"
|
||||
fontSize={10}
|
||||
fill="var(--color-txt-muted, #666)"
|
||||
>
|
||||
{tick}
|
||||
</text>
|
||||
))}
|
||||
{/* X 轴刻度 */}
|
||||
{labels.map((label, i) => (
|
||||
<text
|
||||
key={i}
|
||||
x={xScale(i)}
|
||||
y={height - 8}
|
||||
textAnchor="middle"
|
||||
fontSize={10}
|
||||
fill={label ? "var(--color-txt-muted, #666)" : "transparent"}
|
||||
>
|
||||
{label || "#"}
|
||||
</text>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const BASE = API.quality;
|
||||
|
||||
async function apiGet<T>(path: string): Promise<T> {
|
||||
@@ -73,6 +267,9 @@ export default function QualityGate({ view }: QualityGateProps) {
|
||||
const [fileContent, setFileContent] = useState<FileContent | null>(null);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const [trendHistory, setTrendHistory] = useState<PRHistoryItem[]>([]);
|
||||
const [trendLoading, setTrendLoading] = useState(false);
|
||||
|
||||
const fetchPRs = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
@@ -91,6 +288,23 @@ export default function QualityGate({ view }: QualityGateProps) {
|
||||
fetchPRs();
|
||||
}, [fetchPRs]);
|
||||
|
||||
const fetchTrendHistory = useCallback(async () => {
|
||||
setTrendLoading(true);
|
||||
try {
|
||||
const data = await apiGet<PRHistoryItem[] | { history?: PRHistoryItem[] }>("/prs/history?limit=15");
|
||||
const list = Array.isArray(data) ? data : data.history ?? [];
|
||||
setTrendHistory(list);
|
||||
} catch {
|
||||
setTrendHistory([]);
|
||||
} finally {
|
||||
setTrendLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (view === "dashboard") fetchTrendHistory();
|
||||
}, [view, fetchTrendHistory]);
|
||||
|
||||
const openPRDetail = useCallback(async (prId: number) => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
@@ -116,8 +330,8 @@ export default function QualityGate({ view }: QualityGateProps) {
|
||||
useEffect(() => {
|
||||
if (!modalOpen || !selectedPR || !selectedFile) return;
|
||||
setFileContent(null);
|
||||
apiGet<FileContent>(`/prs/${selectedPR.id}/file?path=${encodeURIComponent(selectedFile)}`)
|
||||
.then(setFileContent)
|
||||
apiGet<Parameters<typeof normalizeFileContent>[0]>(`/prs/${selectedPR.id}/file?path=${encodeURIComponent(selectedFile)}`)
|
||||
.then((data) => setFileContent(normalizeFileContent(data)))
|
||||
.catch(() => setFileContent({ content: "// Failed to load file", issues: [] }));
|
||||
}, [modalOpen, selectedPR, selectedFile]);
|
||||
|
||||
@@ -183,6 +397,10 @@ export default function QualityGate({ view }: QualityGateProps) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 mb-8">
|
||||
<ProblemTrendChart history={trendHistory} loading={trendLoading} />
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-extrabold mb-4">Recent Pull Requests</h3>
|
||||
<PRTable prs={prs.slice(0, 8)} loading={loading} onView={(pr) => openPRDetail(pr.id)} />
|
||||
</>
|
||||
@@ -281,53 +499,84 @@ export default function QualityGate({ view }: QualityGateProps) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto bg-white">
|
||||
{/* 代码区:完整代码 + 每行右侧缺陷标注,一行一个滚动条(参考 index copy.html) */}
|
||||
<div className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden">
|
||||
<div className="flex-1 min-h-0 overflow-auto bg-[#1e1e1e] font-mono text-[13px] leading-[1.5]">
|
||||
{fileContent ? (
|
||||
<pre className="text-sm font-mono leading-6 p-4">
|
||||
{fileContent.content.split("\n").map((line, i) => {
|
||||
<div className="min-w-min">
|
||||
{(fileContent.content ?? "").split("\n").map((line, i) => {
|
||||
const lineNum = i + 1;
|
||||
const issues = fileContent.issues.filter((iss) => iss.line === lineNum);
|
||||
const lineIssues = (fileContent.issues ?? []).filter((iss) => iss.line === lineNum);
|
||||
const hasIssue = lineIssues.length > 0;
|
||||
const reasonText = hasIssue
|
||||
? lineIssues.map((iss) => (iss.scanner ? `[${iss.scanner}] ` : "") + (iss.message || "")).join(";")
|
||||
: "";
|
||||
const displayText = line === "" ? "\u00A0" : line;
|
||||
return (
|
||||
<div key={i} className={`flex ${issues.length > 0 ? "bg-red-50/80" : ""}`}>
|
||||
<span className="w-12 shrink-0 text-right pr-4 text-txt-muted select-none text-xs leading-6">
|
||||
{lineNum}
|
||||
</span>
|
||||
<span className="flex-1 whitespace-pre-wrap">{line}</span>
|
||||
{issues.length > 0 && (
|
||||
<span className="shrink-0 px-2 text-xs text-red-600 max-w-xs truncate" title={issues[0].message}>
|
||||
● {issues[0].message}
|
||||
<div
|
||||
key={i}
|
||||
className={`flex min-h-[1.5em] items-stretch ${hasIssue ? "code-line-has-issue" : ""}`}
|
||||
>
|
||||
{/* 行号区 */}
|
||||
<div className="w-12 min-w-[48px] shrink-0 flex items-center justify-end pr-2 text-[#6c757d] bg-[#252526] select-none">
|
||||
<span className="mr-1">{lineNum}</span>
|
||||
{hasIssue && (
|
||||
<span
|
||||
className={
|
||||
lineIssues[0]?.severity === "error" || lineIssues[0]?.severity === "high"
|
||||
? "text-[#f14c4c]"
|
||||
: "text-[#cca700]"
|
||||
}
|
||||
title={reasonText}
|
||||
>
|
||||
●
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 代码内容 */}
|
||||
<div
|
||||
className={`flex-1 min-w-0 px-3 py-0 whitespace-pre-wrap break-all text-[#d4d4d4] ${
|
||||
hasIssue ? "bg-red-900/20 border-l-2 border-l-red-500 pl-2" : ""
|
||||
}`}
|
||||
>
|
||||
{displayText}
|
||||
</div>
|
||||
{/* 虚线连接区 */}
|
||||
<div className="w-5 min-w-[20px] shrink-0 bg-[#1e1e1e] relative">
|
||||
{hasIssue && (
|
||||
<span
|
||||
className="absolute left-0 right-0 top-1/2 -mt-px block border-b border-dashed border-red-500/80"
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* 缺陷标注(与该行对齐,在右侧) */}
|
||||
<div
|
||||
className={`w-[180px] min-w-[180px] shrink-0 py-1.5 px-2 text-[11px] border-l flex items-center ${
|
||||
hasIssue
|
||||
? "bg-red-50/90 border-l-2 border-l-red-500 text-red-800"
|
||||
: "bg-[#252526] border-[#3c3c3c] text-[#9d9d9d]"
|
||||
}`}
|
||||
>
|
||||
{hasIssue && reasonText ? (
|
||||
<>
|
||||
<span className="text-red-500 mr-1.5 shrink-0">⚠</span>
|
||||
<span className="break-words leading-snug">{reasonText}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="invisible">-</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-txt-muted text-sm">
|
||||
<div className="flex items-center justify-center h-full min-h-[200px] text-[#6c757d] text-sm">
|
||||
{selectedFile ? "Loading file..." : "Select a file to inspect"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-[220px] shrink-0 border-l border-border overflow-y-auto p-4 bg-surface-muted/60">
|
||||
<h3 className="text-xs font-bold text-txt-muted uppercase mb-3">Issue Panel</h3>
|
||||
{fileContent?.issues.length === 0 && (
|
||||
<p className="text-xs text-txt-muted">No issues</p>
|
||||
)}
|
||||
{fileContent?.issues.map((iss, i) => (
|
||||
<div key={i} className="mb-3 p-2 bg-white border border-border text-xs rounded-lg">
|
||||
<div className="flex items-center gap-1 mb-1">
|
||||
<span className={`font-bold ${
|
||||
iss.severity === "error" ? "text-red-600" : iss.severity === "warning" ? "text-yellow-600" : "text-blue-600"
|
||||
}`}>
|
||||
{iss.severity === "error" ? "❌" : iss.severity === "warning" ? "⚠️" : "ℹ️"}
|
||||
</span>
|
||||
<span className="text-txt-muted">L{iss.line}</span>
|
||||
</div>
|
||||
<p className="text-txt">{iss.message}</p>
|
||||
{iss.rule && <p className="text-txt-muted mt-0.5">{iss.rule}</p>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ export default {
|
||||
extend: {
|
||||
colors: {
|
||||
magenta: {
|
||||
DEFAULT: "#E20074",
|
||||
50: "#FFF0F7",
|
||||
100: "#FFE0EF",
|
||||
600: "#E20074",
|
||||
700: "#B8005E",
|
||||
DEFAULT: "#F43F5E",
|
||||
50: "#FFF1F2",
|
||||
100: "#FFE4E6",
|
||||
600: "#F43F5E",
|
||||
700: "#BE123C",
|
||||
},
|
||||
surface: {
|
||||
DEFAULT: "#FFFFFF",
|
||||
|
||||
Reference in New Issue
Block a user