feat: 全面UI优化 — 5套主题系统 + 设计体系 + 菜单重组 + 页面优化

1. 主题系统扩展 (5套主题)
   - ocean-deep(深海蓝) / light-professional(明亮白) / midnight-purple(午夜紫)
   - pine-green(松林绿) / orange-vitality(活力橙)
   - useTheme hook 重写支持5主题 + 向后兼容
   - AppearanceSettings 5主题预览卡片

2. 设计体系 (index.css)
   - 按钮系统: dg-btn-primary/secondary/ghost/danger + 尺寸变体
   - 徽章系统: dg-badge-* (8色)
   - 卡片/表格: dg-card-section, dg-table-row, dg-table-header
   - 状态点: dg-status-dot-* (4色)
   - 动效关键帧: slideInLeft, scaleIn

3. 菜单重组 (menu.ts)
   - 5分组: 概览/数据源/工单/权限/系统
   - 6个图标更新: Code2/FileEdit/ShieldCheck/FolderLock/ScrollText/Timer
   - 新增 permission 权限码字段

4. Layout 侧边栏增强
   - 左侧渐变指示条 + 发光效果
   - hover 微动画 + 图标变色
   - 面包屑导航头部
   - 主题切换循环5主题

5. 核心页面优化
   - TicketsPage: 设计系统类 + 颜色对齐
   - QueriesPage: 去Card依赖 + dg-table/dg-btn统一
   - Breadcrumb组件 + useBreadcrumb hook
This commit is contained in:
logan committed 2026-05-27 01:59:08 +08:00
1 parent 4e2df4d3d7
commit 746a000e8a
25 files changed
+7718 -178

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
import { useLocation, useNavigate } from 'react-router-dom';
import { ChevronRight, Home } from 'lucide-react';
import { menuItems, menuSections } from '@/constants/menu';
interface BreadcrumbItem {
label: string;
path?: string;
}
interface BreadcrumbProps {
items: BreadcrumbItem[];
}
export default function Breadcrumb({ items }: BreadcrumbProps) {
const navigate = useNavigate();
if (items.length === 0) return null;
return (
<nav aria-label="breadcrumb" className="flex items-center text-xs">
<ol className="flex items-center gap-1">
{items.map((item, index) => {
const isLast = index === items.length - 1;
return (
<li key={index} className="flex items-center gap-1">
{index > 0 && (
<ChevronRight className="w-3 h-3 text-[var(--dg-text-muted)] flex-shrink-0" />
)}
{isLast ? (
<span className="text-[var(--dg-text-primary)] font-medium">
{item.label}
</span>
) : item.path ? (
<button
type="button"
onClick={() => navigate(item.path!)}
className="text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] transition-colors inline-flex items-center gap-1 cursor-pointer"
>
{index === 0 && <Home className="w-3 h-3" />}
{item.label}
</button>
) : (
<span className="text-[var(--dg-text-secondary)]">
{item.label}
</span>
)}
</li>
);
})}
</ol>
</nav>
);
}
/**
* Hook that builds breadcrumb items based on the current route.
*
* It matches the current pathname against menuItems, then constructs:
* Home > Section label > Page label
*
* For detail/sub pages (e.g. /instances/:id, /tickets/:id, /tickets/create),
* it prepends the parent breadcrumb entries.
*/
export function useBreadcrumb(): { items: BreadcrumbItem[] } {
const location = useLocation();
const pathname = location.pathname;
const items: BreadcrumbItem[] = [];
// Find a direct menu item match by id (exact route)
const directMatch = menuItems.find(
(item) => item.id === pathname.replace(/^\//, '')
);
if (directMatch) {
// Exact menu match: Home > Section > Page
const section = menuSections.find((s) => s.id === directMatch.section);
items.push({ label: '首页', path: '/dashboard' });
if (section && section.id !== 'overview') {
items.push({ label: section.label });
}
items.push({ label: directMatch.label });
return { items };
}
// Parse the pathname for sub/detail pages
const segments = pathname.split('/').filter(Boolean); // e.g. ['instances', '123'] or ['tickets', 'create']
if (segments.length === 0) {
return { items: [{ label: '首页', path: '/dashboard' }] };
}
const rootSegment = segments[0];
const parentMenuItem = menuItems.find((item) => item.id === rootSegment);
if (!parentMenuItem) {
// Unknown route — just show Home
return { items: [{ label: '首页', path: '/dashboard' }] };
}
const section = menuSections.find((s) => s.id === parentMenuItem.section);
// Home
items.push({ label: '首页', path: '/dashboard' });
// Section (skip for overview section like dashboard)
if (section && section.id !== 'overview') {
items.push({ label: section.label });
}
// Parent page (clickable for detail pages)
if (segments.length > 1) {
items.push({ label: parentMenuItem.label, path: `/${rootSegment}` });
}
// Sub-page label
if (segments.length > 1) {
const subSegment = segments[1];
const isDetailPage = !isNaN(Number(subSegment));
if (isDetailPage) {
// Numeric ID — use a generic "详情" label
items.push({ label: '详情' });
} else {
// Named sub-pages like 'create'
const subPageLabels: Record<string, string> = {
create: '新建',
edit: '编辑',
};
items.push({ label: subPageLabels[subSegment] || subSegment });
}
} else {
// Single segment — this is the current page
items.push({ label: parentMenuItem.label });
}
return { items };
}