feat(ui): 功能区重构阶段一——六组导航 + 资源/工具一级页 + 卡片操作分层
信息架构按三公理重排(名实一致/层级对齐/频率分层,设计见 ui-restructure 模块): - Sidebar 六组:总览/项目/资源/洞察/工具/设置 - 新增资源页(服务器/软件仓库/云产品/API密钥/源码库 5 tab),从"项目管理" 拆出寄生的资源类 tabs;新增工具页(开发环境/WSL代理 合并) - Dashboard 看板只留产品组/独立项目;洞察页阶段一暂挂健康中心(N3 扩充) - ProjectCard 底部 10 个平铺操作收敛为:🗺️蓝图/🎓导师/编辑/动态 + ⋯更多下拉 (带文字标签收纳 Claude配置/备忘/服务器/CICD/环境扫描/工具管理) 零后端变更:纯组件挂载点迁移,面板组件与 queryKey 原样复用。 阶段二(N3 洞察页扩充 / N4 Gitea 实例迁设置 / N5 仓库注册表归位)已拆卡待启动。
This commit is contained in:
parent
d14b348845
commit
bc07dd41bf
@ -397,6 +397,14 @@ modules:
|
|||||||
updated: 2026-07-02
|
updated: 2026-07-02
|
||||||
position: [3750, 150]
|
position: [3750, 150]
|
||||||
|
|
||||||
|
- id: ui-restructure
|
||||||
|
name: 功能区 UI 重构
|
||||||
|
area: frontend
|
||||||
|
status: in_progress
|
||||||
|
progress: 40
|
||||||
|
note: "阶段一完成(N1 六组导航 + N2 卡片分层);阶段二 N3/N4/N5 待用一周后启动"
|
||||||
|
position: [3750, 300]
|
||||||
|
|
||||||
edges:
|
edges:
|
||||||
# 前端依赖
|
# 前端依赖
|
||||||
- from: workspace-mgmt
|
- from: workspace-mgmt
|
||||||
|
|||||||
77
.blueprint/modules/ui-restructure.md
Normal file
77
.blueprint/modules/ui-restructure.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# 功能区 UI 信息架构重构
|
||||||
|
|
||||||
|
对照宪章"名实一致"做的一次 UI 自洽重构。核心问题:资源类功能寄生在"项目管理"页、
|
||||||
|
BlueprintModal 巨型容器混装全局/跨项目/单项目三层内容、ProjectCard 十个操作无分层、
|
||||||
|
Gitea 全局实例配置错位在单项目弹窗内。
|
||||||
|
|
||||||
|
## 三条公理
|
||||||
|
|
||||||
|
1. **名实一致**:页面叫什么就只放什么
|
||||||
|
2. **层级对齐**:全局配置 → 设置页;跨项目 → 一级导航;单项目 → 项目弹窗/卡片
|
||||||
|
3. **频率分层**:高频直达,低频收进"更多"
|
||||||
|
|
||||||
|
## 目标信息架构(六组一级导航)
|
||||||
|
|
||||||
|
```
|
||||||
|
🏠 总览 行动项:跨项目进行中、可派发、Buff 项目、启动警报
|
||||||
|
📁 项目 产品组看板 / 独立项目 / 管理(项目·产品组·模板·标签·仓库注册表)
|
||||||
|
🖥️ 资源 服务器 / 软件仓库 / 云产品 / API 密钥 / 源码库
|
||||||
|
♻️ 洞察 飞轮(梦核·归档·git 质量)/ 健康中心 / CONVENTIONS 全局治理
|
||||||
|
🔧 工具 开发环境扫描 / WSL 代理
|
||||||
|
⚙️ 设置 编辑器·更新·MCP + Gitea 实例管理
|
||||||
|
```
|
||||||
|
|
||||||
|
设计文档全文见 2026-07-02 会话 /architect 输出(方案 A,零后端变更,纯前端挂载点迁移,分两阶段)。
|
||||||
|
|
||||||
|
## 任务卡(2026-07-02 /splitter 拆解)
|
||||||
|
|
||||||
|
### ✅ N1. 资源/工具一级页 + Sidebar 六组重组 + Dashboard 瘦身
|
||||||
|
- status: done
|
||||||
|
- complexity: M
|
||||||
|
- files: src/components/resources/ResourcesPage.tsx(新增), src/components/tools/ToolsPage.tsx(新增), src/components/layout/Sidebar.tsx, src/components/dashboard/Dashboard.tsx, src/App.tsx
|
||||||
|
- acceptance: Sidebar 显示六组导航(总览/项目/资源/洞察/工具/设置);资源页 5 tab(服务器/软件仓库/云产品/API密钥/源码库)复用现有面板组件且 queryKey 不变;工具页 2 tab(开发环境/WSL代理);洞察暂挂现健康中心内容(N3 再扩充);Dashboard 看板只剩产品组/独立项目两个 tab;typecheck 通过
|
||||||
|
|
||||||
|
### ✅ N2. ProjectCard 操作分层(4 明按钮 + ⋯ 更多下拉)
|
||||||
|
- status: done
|
||||||
|
- complexity: M
|
||||||
|
- files: src/components/dashboard/ProjectCard.tsx
|
||||||
|
- acceptance: 底部操作区只显示 🗺️ 蓝图 / 🎓 导师 / 编辑 / 动态 四个明按钮 + ⋯ 更多下拉(收纳 🤖 Claude 配置、📝 个人备忘、🖥️ 关联服务器、🚀 CI/CD、🔬 环境扫描、🧰 工具管理,带文字标签);下拉点击外部自动关闭;所有原功能可达;typecheck 通过
|
||||||
|
|
||||||
|
### 📋 N3. 洞察页:飞轮跨项目内容迁出 BlueprintModal + 健康中心并入
|
||||||
|
- status: todo
|
||||||
|
- complexity: M
|
||||||
|
- depends: N1
|
||||||
|
- files: src/components/insight/InsightPage.tsx(新增), src/components/dashboard/BlueprintModal.tsx, src/components/health/HealthPage.tsx
|
||||||
|
- acceptance: 洞察页 3 tab:飞轮 / 健康 / 全局治理;FlywheelPanel 的跨项目内容(梦核提示词、归档候选、全项目 Git 质量表、全量导入按钮)迁至飞轮 tab;CONVENTIONS 版本管理与应用梦核建议迁至全局治理 tab;BlueprintModal 侧板只保留单项目内容;typecheck 通过
|
||||||
|
|
||||||
|
### 📋 N4. Gitea 实例管理迁设置页(GiteaPanel 拆全局/项目级两半)
|
||||||
|
- status: todo
|
||||||
|
- complexity: S
|
||||||
|
- depends: N1
|
||||||
|
- files: src/components/dashboard/GiteaPanel.tsx, src/components/settings/SettingsPage.tsx
|
||||||
|
- acceptance: 实例管理区(添加/测试/删除)拆为独立组件挂到设置页;BlueprintModal 治理面板只保留项目级绑定与 webhook 区(无实例时提示去设置页添加);两处共用 ["gitea-instances"] queryKey;typecheck 通过
|
||||||
|
|
||||||
|
### 📋 N5. 仓库注册表并入项目页管理 tab + 总览收敛为行动项
|
||||||
|
- status: todo
|
||||||
|
- complexity: S
|
||||||
|
- depends: N1
|
||||||
|
- files: src/components/manage/ManagePage.tsx, src/components/layout/Sidebar.tsx, src/components/overview/OverviewPage.tsx, src/components/dashboard/RepoRegistryModal.tsx
|
||||||
|
- acceptance: ManagePage 新增"仓库"tab 复用 RepoRegistry 列表内容;Sidebar 底部"仓库管理"按钮移除;总览页标题与文案对齐"行动项"定位(进行中任务/Buff 项目/警报,不新增统计类内容);typecheck 通过
|
||||||
|
|
||||||
|
## 决策记录
|
||||||
|
|
||||||
|
- 选六组导航全面重组而非最小纠偏:资源拆分只解决问题 1,弹窗过载(问题 2)必须有"洞察页"作为跨项目内容的去处才能解
|
||||||
|
- 零后端变更是硬约束:所有迁移只挪组件挂载点,queryKey 保持不变(React Query 缓存天然共享,避免状态重复)
|
||||||
|
- 分两阶段落地(N1+N2 先行):给肌肉记忆留适应期,也让阶段二可以基于阶段一的真实使用反馈微调
|
||||||
|
|
||||||
|
## 复盘笔记
|
||||||
|
|
||||||
|
【复盘】N1+N2 阶段一:导航重组 + 卡片分层(2026-07-02)
|
||||||
|
任务类型: 前端组件
|
||||||
|
复杂度: M×2
|
||||||
|
实际轮数: 1轮(预估 1 轮)
|
||||||
|
是否返工: 否
|
||||||
|
有效策略: 零后端约束使迁移退化为纯挂载点搬运——新容器页只做 tab 壳,面板组件与 queryKey 原样复用,typecheck 一次通过
|
||||||
|
遗留风险: ① 洞察页暂挂健康中心(N3 未做前名实略不符,导航先行占位);② ProjectCard 更多菜单向上弹出,卡片在视口顶部时可能被裁剪(overflow 场景待实际使用观察)
|
||||||
|
auto_applied_rules: R01
|
||||||
|
rule_effectiveness: R01=未触发(无新 command,纯前端)
|
||||||
19
src/App.tsx
19
src/App.tsx
@ -2,11 +2,10 @@ import { useState, useEffect } from "react";
|
|||||||
import { OverviewPage } from "./components/overview/OverviewPage";
|
import { OverviewPage } from "./components/overview/OverviewPage";
|
||||||
import { Dashboard } from "./components/dashboard/Dashboard";
|
import { Dashboard } from "./components/dashboard/Dashboard";
|
||||||
import { ProjectModal } from "./components/dashboard/ProjectModal";
|
import { ProjectModal } from "./components/dashboard/ProjectModal";
|
||||||
import { DevToolsPage } from "./components/devtools/DevToolsPage";
|
import { ResourcesPage } from "./components/resources/ResourcesPage";
|
||||||
import { ProxyPage } from "./components/proxy/ProxyPage";
|
import { ToolsPage } from "./components/tools/ToolsPage";
|
||||||
import { SettingsPage } from "./components/settings/SettingsPage";
|
import { SettingsPage } from "./components/settings/SettingsPage";
|
||||||
import { HealthPage } from "./components/health/HealthPage";
|
import { HealthPage } from "./components/health/HealthPage";
|
||||||
import { SourceLibraryPage } from "./components/source-library/SourceLibraryPage";
|
|
||||||
import { Sidebar, type Page } from "./components/layout/Sidebar";
|
import { Sidebar, type Page } from "./components/layout/Sidebar";
|
||||||
import { Toast } from "./components/ui/Toast";
|
import { Toast } from "./components/ui/Toast";
|
||||||
import { getStartupAlerts, type StartupAlert } from "./lib/commands";
|
import { getStartupAlerts, type StartupAlert } from "./lib/commands";
|
||||||
@ -66,13 +65,13 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 overflow-auto min-h-0">
|
<div className="flex-1 overflow-auto min-h-0">
|
||||||
{page === "overview" && <OverviewPage />}
|
{page === "overview" && <OverviewPage />}
|
||||||
{page === "dashboard" && <Dashboard />}
|
{page === "dashboard" && <Dashboard />}
|
||||||
{page === "source-library" && <SourceLibraryPage />}
|
{page === "resources" && <ResourcesPage />}
|
||||||
{page === "devtools" && <DevToolsPage />}
|
{/* 洞察页阶段一暂挂健康中心,N3 扩充为 飞轮/健康/全局治理 三 tab */}
|
||||||
{page === "proxy" && <ProxyPage />}
|
{page === "insight" && <HealthPage />}
|
||||||
{page === "settings" && <SettingsPage />}
|
{page === "tools" && <ToolsPage />}
|
||||||
{page === "health" && <HealthPage />}
|
{page === "settings" && <SettingsPage />}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,6 @@ import { ProjectCard } from "./ProjectCard";
|
|||||||
import { DeployModal } from "./DeployModal";
|
import { DeployModal } from "./DeployModal";
|
||||||
import { GroupResourceCanvas } from "./GroupResourceCanvas";
|
import { GroupResourceCanvas } from "./GroupResourceCanvas";
|
||||||
import { ManagePage } from "../manage/ManagePage";
|
import { ManagePage } from "../manage/ManagePage";
|
||||||
import { ServerManagerPanel } from "./ServerManagerPanel";
|
|
||||||
import { ServerSoftwarePanel } from "./ServerSoftwareBlock";
|
|
||||||
import { ApiKeysPanel } from "./ApiKeysPanel";
|
|
||||||
import { CloudProductsPanel } from "./CloudProductsPanel";
|
|
||||||
import { BatchAddModal } from "./BatchAddModal";
|
import { BatchAddModal } from "./BatchAddModal";
|
||||||
import { MountRepoModal } from "./MountRepoModal";
|
import { MountRepoModal } from "./MountRepoModal";
|
||||||
import { ProjectToolsModal } from "./ProjectToolsModal";
|
import { ProjectToolsModal } from "./ProjectToolsModal";
|
||||||
@ -18,7 +14,7 @@ import { openUrl } from "@tauri-apps/plugin-opener";
|
|||||||
import { useUIStore } from "../../store/ui";
|
import { useUIStore } from "../../store/ui";
|
||||||
|
|
||||||
type DashView = "board" | "manage";
|
type DashView = "board" | "manage";
|
||||||
type BoardTab = "groups" | "standalone" | "servers" | "software" | "apikeys" | "cloud";
|
type BoardTab = "groups" | "standalone";
|
||||||
type GroupView = "default" | "resource";
|
type GroupView = "default" | "resource";
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
@ -130,46 +126,6 @@ export function Dashboard() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => setBoardTab("servers")}
|
|
||||||
className={`px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors ${
|
|
||||||
boardTab === "servers"
|
|
||||||
? "border-blue-500 text-blue-600"
|
|
||||||
: "border-transparent text-gray-400 hover:text-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
服务器
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setBoardTab("software")}
|
|
||||||
className={`px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors ${
|
|
||||||
boardTab === "software"
|
|
||||||
? "border-blue-500 text-blue-600"
|
|
||||||
: "border-transparent text-gray-400 hover:text-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
软件仓库
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setBoardTab("apikeys")}
|
|
||||||
className={`px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors ${
|
|
||||||
boardTab === "apikeys"
|
|
||||||
? "border-blue-500 text-blue-600"
|
|
||||||
: "border-transparent text-gray-400 hover:text-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
API 管理
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setBoardTab("cloud")}
|
|
||||||
className={`px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors ${
|
|
||||||
boardTab === "cloud"
|
|
||||||
? "border-blue-500 text-blue-600"
|
|
||||||
: "border-transparent text-gray-400 hover:text-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
云产品
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GlobalQuickLaunchBar />
|
<GlobalQuickLaunchBar />
|
||||||
@ -219,22 +175,6 @@ export function Dashboard() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── 服务器管理标签 ── */}
|
|
||||||
{boardTab === "servers" && <ServerManagerPanel />}
|
|
||||||
|
|
||||||
{/* ── 软件仓库标签 ── */}
|
|
||||||
{boardTab === "software" && (
|
|
||||||
<div className="w-full px-8 py-8 pb-16">
|
|
||||||
<ServerSoftwarePanel />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── API 管理标签 ── */}
|
|
||||||
{boardTab === "apikeys" && <ApiKeysPanel />}
|
|
||||||
|
|
||||||
{/* ── 云产品标签 ── */}
|
|
||||||
{boardTab === "cloud" && <CloudProductsPanel />}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -72,6 +72,8 @@ export function ProjectCard({ project: p }: Props) {
|
|||||||
const [newBranchName, setNewBranchName] = useState("");
|
const [newBranchName, setNewBranchName] = useState("");
|
||||||
const [creatingBranch, setCreatingBranch] = useState(false);
|
const [creatingBranch, setCreatingBranch] = useState(false);
|
||||||
const branchMenuRef = useRef<HTMLDivElement>(null);
|
const branchMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [showMoreMenu, setShowMoreMenu] = useState(false);
|
||||||
|
const moreMenuRef = useRef<HTMLDivElement>(null);
|
||||||
const showToast = useUIStore((s) => s.showToast);
|
const showToast = useUIStore((s) => s.showToast);
|
||||||
const openEdit = useUIStore((s) => s.openEdit);
|
const openEdit = useUIStore((s) => s.openEdit);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -107,6 +109,17 @@ export function ProjectCard({ project: p }: Props) {
|
|||||||
return () => document.removeEventListener("mousedown", handler);
|
return () => document.removeEventListener("mousedown", handler);
|
||||||
}, [showBranchMenu]);
|
}, [showBranchMenu]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showMoreMenu) return;
|
||||||
|
const handler = (e: MouseEvent) => {
|
||||||
|
if (moreMenuRef.current && !moreMenuRef.current.contains(e.target as Node)) {
|
||||||
|
setShowMoreMenu(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("mousedown", handler);
|
||||||
|
return () => document.removeEventListener("mousedown", handler);
|
||||||
|
}, [showMoreMenu]);
|
||||||
|
|
||||||
const pullMutation = useMutation({
|
const pullMutation = useMutation({
|
||||||
mutationFn: () => gitPull(localPath!),
|
mutationFn: () => gitPull(localPath!),
|
||||||
onSuccess: (msg) => {
|
onSuccess: (msg) => {
|
||||||
@ -573,44 +586,52 @@ export function ProjectCard({ project: p }: Props) {
|
|||||||
<div className="border-t border-gray-100 px-5 py-2.5 flex items-center justify-between">
|
<div className="border-t border-gray-100 px-5 py-2.5 flex items-center justify-between">
|
||||||
<span className="text-[11px] text-gray-300">{(p.updated_at ?? "").slice(0, 10)}</span>
|
<span className="text-[11px] text-gray-300">{(p.updated_at ?? "").slice(0, 10)}</span>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<button onClick={() => setShowClaudeConfig(true)}
|
{/* 高频明按钮 */}
|
||||||
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="Claude 自主性状态">
|
<button onClick={() => setShowBlueprint(true)}
|
||||||
🤖
|
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="项目蓝图">
|
||||||
|
🗺️ 蓝图
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setShowMentor(true)}
|
<button onClick={() => setShowMentor(true)}
|
||||||
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="项目导师">
|
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="项目导师">
|
||||||
🎓
|
🎓 导师
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowTodos((v) => !v)}
|
|
||||||
className={`px-2.5 py-1.5 rounded-md text-xs border transition-colors ${showTodos ? "border-amber-300 bg-amber-50 text-amber-600" : "border-gray-200 text-gray-500 hover:bg-gray-50"}`}
|
|
||||||
title="个人备忘"
|
|
||||||
>
|
|
||||||
📝
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setShowBlueprint(true)}
|
|
||||||
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="项目蓝图">
|
|
||||||
🗺️
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowServers((v) => !v)}
|
|
||||||
className={`px-2.5 py-1.5 rounded-md text-xs border transition-colors ${showServers ? "border-cyan-300 bg-cyan-50 text-cyan-600" : "border-gray-200 text-gray-500 hover:bg-gray-50"}`}
|
|
||||||
title="关联服务器"
|
|
||||||
>
|
|
||||||
🖥️
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setShowCicd(true)}
|
|
||||||
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="CI/CD 控制台">
|
|
||||||
🚀
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setShowEnvScan(true)}
|
|
||||||
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="环境扫描">
|
|
||||||
🔬
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setShowToolsMgr(true)}
|
|
||||||
className="px-2.5 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors" title="工具管理">
|
|
||||||
🧰
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* 低频操作收进更多菜单 */}
|
||||||
|
<div className="relative" ref={moreMenuRef}>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowMoreMenu((v) => !v)}
|
||||||
|
className={`px-2.5 py-1.5 rounded-md text-xs border transition-colors ${
|
||||||
|
showMoreMenu || showTodos || showServers
|
||||||
|
? "border-gray-300 bg-gray-100 text-gray-700"
|
||||||
|
: "border-gray-200 text-gray-500 hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
|
title="更多操作"
|
||||||
|
>
|
||||||
|
⋯
|
||||||
|
</button>
|
||||||
|
{showMoreMenu && (
|
||||||
|
<div className="absolute right-0 bottom-full mb-1 w-40 bg-white border border-gray-200 rounded-lg shadow-lg z-50 py-1 text-xs">
|
||||||
|
{([
|
||||||
|
{ icon: "🤖", label: "Claude 自主性", action: () => setShowClaudeConfig(true) },
|
||||||
|
{ icon: "📝", label: showTodos ? "收起备忘" : "个人备忘", action: () => setShowTodos((v) => !v) },
|
||||||
|
{ icon: "🖥️", label: showServers ? "收起服务器" : "关联服务器", action: () => setShowServers((v) => !v) },
|
||||||
|
{ icon: "🚀", label: "CI/CD 控制台", action: () => setShowCicd(true) },
|
||||||
|
{ icon: "🔬", label: "环境扫描", action: () => setShowEnvScan(true) },
|
||||||
|
{ icon: "🧰", label: "工具管理", action: () => setShowToolsMgr(true) },
|
||||||
|
]).map(({ icon, label, action }) => (
|
||||||
|
<button
|
||||||
|
key={label}
|
||||||
|
onClick={() => { action(); setShowMoreMenu(false); }}
|
||||||
|
className="w-full text-left px-3 py-1.5 hover:bg-gray-50 text-gray-600 transition-colors flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<span>{icon}</span>
|
||||||
|
<span>{label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="w-px h-4 bg-gray-200 mx-0.5" />
|
<div className="w-px h-4 bg-gray-200 mx-0.5" />
|
||||||
<button onClick={() => openEdit(p)}
|
<button onClick={() => openEdit(p)}
|
||||||
className="px-3 py-1.5 rounded-md text-xs border border-gray-200 text-gray-600 hover:bg-gray-50 transition-colors">
|
className="px-3 py-1.5 rounded-md text-xs border border-gray-200 text-gray-600 hover:bg-gray-50 transition-colors">
|
||||||
|
|||||||
@ -7,16 +7,15 @@ import {
|
|||||||
import { useUIStore } from "../../store/ui";
|
import { useUIStore } from "../../store/ui";
|
||||||
import { RepoRegistryModal } from "../dashboard/RepoRegistryModal";
|
import { RepoRegistryModal } from "../dashboard/RepoRegistryModal";
|
||||||
|
|
||||||
export type Page = "overview" | "dashboard" | "source-library" | "devtools" | "proxy" | "settings" | "health";
|
export type Page = "overview" | "dashboard" | "resources" | "insight" | "tools" | "settings";
|
||||||
|
|
||||||
const NAV_ITEMS: { id: Page; label: string }[] = [
|
const NAV_ITEMS: { id: Page; label: string }[] = [
|
||||||
{ id: "overview", label: "仪表盘" },
|
{ id: "overview", label: "总览" },
|
||||||
{ id: "dashboard", label: "项目管理" },
|
{ id: "dashboard", label: "项目" },
|
||||||
{ id: "source-library", label: "源码库" },
|
{ id: "resources", label: "资源" },
|
||||||
{ id: "devtools", label: "开发环境" },
|
{ id: "insight", label: "洞察" },
|
||||||
{ id: "proxy", label: "WSL 代理" },
|
{ id: "tools", label: "工具" },
|
||||||
{ id: "health", label: "健康中心" },
|
{ id: "settings", label: "设置" },
|
||||||
{ id: "settings", label: "设置" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
56
src/components/resources/ResourcesPage.tsx
Normal file
56
src/components/resources/ResourcesPage.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 资源页:服务器 / 软件仓库 / 云产品 / API 密钥 / 源码库
|
||||||
|
// 从 Dashboard 内嵌 tabs 迁出的一级页面(ui-restructure N1),面板组件原样复用
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ServerManagerPanel } from "../dashboard/ServerManagerPanel";
|
||||||
|
import { ServerSoftwarePanel } from "../dashboard/ServerSoftwareBlock";
|
||||||
|
import { ApiKeysPanel } from "../dashboard/ApiKeysPanel";
|
||||||
|
import { CloudProductsPanel } from "../dashboard/CloudProductsPanel";
|
||||||
|
import { SourceLibraryPage } from "../source-library/SourceLibraryPage";
|
||||||
|
|
||||||
|
type ResourceTab = "servers" | "software" | "cloud" | "apikeys" | "source";
|
||||||
|
|
||||||
|
const TABS: { id: ResourceTab; label: string }[] = [
|
||||||
|
{ id: "servers", label: "服务器" },
|
||||||
|
{ id: "software", label: "软件仓库" },
|
||||||
|
{ id: "cloud", label: "云产品" },
|
||||||
|
{ id: "apikeys", label: "API 密钥" },
|
||||||
|
{ id: "source", label: "源码库" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function ResourcesPage() {
|
||||||
|
const [tab, setTab] = useState<ResourceTab>("servers");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full min-h-0">
|
||||||
|
<div className="shrink-0 border-b border-gray-100 bg-white px-8 flex items-center gap-1">
|
||||||
|
{TABS.map(({ id, label }) => (
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
onClick={() => setTab(id)}
|
||||||
|
className={`px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors ${
|
||||||
|
tab === id
|
||||||
|
? "border-blue-500 text-blue-600"
|
||||||
|
: "border-transparent text-gray-400 hover:text-gray-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto min-h-0">
|
||||||
|
{/* 源码库自带页面级布局,其余面板沿用看板时代的容器内边距 */}
|
||||||
|
{tab === "source" ? (
|
||||||
|
<SourceLibraryPage />
|
||||||
|
) : (
|
||||||
|
<div className="w-full px-8 py-8 pb-16">
|
||||||
|
{tab === "servers" && <ServerManagerPanel />}
|
||||||
|
{tab === "software" && <ServerSoftwarePanel />}
|
||||||
|
{tab === "cloud" && <CloudProductsPanel />}
|
||||||
|
{tab === "apikeys" && <ApiKeysPanel />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
39
src/components/tools/ToolsPage.tsx
Normal file
39
src/components/tools/ToolsPage.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 工具页:开发环境扫描 / WSL 代理
|
||||||
|
// 两个低频独立页合并为 tabs(ui-restructure N1),页面组件原样复用
|
||||||
|
import { useState } from "react";
|
||||||
|
import { DevToolsPage } from "../devtools/DevToolsPage";
|
||||||
|
import { ProxyPage } from "../proxy/ProxyPage";
|
||||||
|
|
||||||
|
type ToolTab = "devtools" | "proxy";
|
||||||
|
|
||||||
|
export function ToolsPage() {
|
||||||
|
const [tab, setTab] = useState<ToolTab>("devtools");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full min-h-0">
|
||||||
|
<div className="shrink-0 border-b border-gray-100 bg-white px-8 flex items-center gap-1">
|
||||||
|
{([
|
||||||
|
{ id: "devtools" as ToolTab, label: "开发环境" },
|
||||||
|
{ id: "proxy" as ToolTab, label: "WSL 代理" },
|
||||||
|
]).map(({ id, label }) => (
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
onClick={() => setTab(id)}
|
||||||
|
className={`px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors ${
|
||||||
|
tab === id
|
||||||
|
? "border-blue-500 text-blue-600"
|
||||||
|
: "border-transparent text-gray-400 hover:text-gray-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto min-h-0">
|
||||||
|
{tab === "devtools" && <DevToolsPage />}
|
||||||
|
{tab === "proxy" && <ProxyPage />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user