refactor: 移除所有页面的 PageHeader 组件

删除 PageHeader 组件及其测试文件,清理相关不再使用的导入和变量。
This commit is contained in:
雨霖铃
2026-05-23 13:15:25 +08:00
parent cf05df7022
commit df3eee8749
11 changed files with 4 additions and 311 deletions
-104
View File
@@ -1,104 +0,0 @@
import { ReactNode, useMemo } from 'react';
import { getEntryPointType } from '@/config/features';
import { cn } from '@/lib/utils'; // shadcn 核心类名合并工具
export interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
/** 要显示的图标组件 */
icon: ReactNode;
/**
* 图标的颜色,支持:
* 1. Tailwind 颜色类名 (如 'text-blue-500', 'text-primary') -> 推荐
* 2. 原生颜色值 (如 '#3b82f6')
*/
iconColor?: string;
/** 主标题文本 */
title: string;
/** 副标题文本(可选) */
subtitle?: string;
/** 在标题右侧显示的徽章/标签组件(可选) */
badge?: ReactNode;
/** 覆盖图标容器的类名 */
iconClassName?: string;
/** 覆盖主标题的类名 */
titleClassName?: string;
/** 覆盖副标题的类名 */
subtitleClassName?: string;
}
export default function PageHeader({
icon,
iconColor = 'text-blue-500', // 默认改用类名,若需保持 Hex 可写 "#3b82f6"
title,
subtitle,
badge,
iconClassName,
titleClassName,
subtitleClassName,
className,
...props
}: PageHeaderProps) {
const entryPointType = useMemo(() => getEntryPointType(), []);
// 扩展环境判断:如果是 popup 形式则不渲染头部
if (entryPointType === 'popup') {
return null;
}
// 判断传入的是否是 Hex/RGB 等原生颜色值
const isRawColor =
iconColor.startsWith('#') || iconColor.startsWith('rgb') || iconColor.startsWith('hsl');
return (
<div className={cn('flex items-center gap-3 mb-6', className)} {...props}>
{/* 图标容器 */}
<div
className={cn(
'p-2 rounded-lg flex items-center justify-center shrink-0',
// 如果不是原生颜色,直接当作 Tailwind 类名注入
!isRawColor && iconColor,
iconClassName,
)}
style={
isRawColor
? {
color: iconColor,
// 使用 CSS inline 变量或 color-mix 安全处理透明度,不再暴力拼接 "15"
backgroundColor: `color-mix(in srgb, ${iconColor} 8%, transparent)`,
}
: undefined
}
>
{/* 确保图标大小可控,通过子元素选择器约束 SVG 宽高 */}
<div className="[&>svg]:h-5 [&>svg]:w-5">{icon}</div>
</div>
{/* 标题区域 */}
<div className="flex-1 min-w-0 flex flex-col gap-0.5">
{/* 标题行(含徽章) */}
<div className="flex justify-between items-center gap-2">
<h1
className={cn(
'text-base font-extrabold tracking-tight leading-tight text-foreground truncate',
titleClassName,
)}
>
{title}
</h1>
{badge && <div className="shrink-0">{badge}</div>}
</div>
{/* 副标题 - 使用 p 标签(block)保证换行 */}
{subtitle && (
<p
className={cn(
'text-xs font-semibold text-muted-foreground truncate',
subtitleClassName,
)}
>
{subtitle}
</p>
)}
</div>
</div>
);
}
-100
View File
@@ -1,100 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import PageHeader, { type PageHeaderProps } from '@/components/PageHeader';
vi.mock('@/config/features', () => ({
getEntryPointType: vi.fn(() => 'sidepanel'),
}));
describe('PageHeader 组件系统', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
vi.resetAllMocks();
});
const defaultProps: PageHeaderProps = {
icon: <span data-testid="test-icon"></span>,
title: '时间戳转换',
subtitle: 'Unix 毫秒数转换与格式化',
};
describe('PageHeader UI 渲染', () => {
it('应渲染页面标题栏&副标题', () => {
render(<PageHeader {...defaultProps} />);
expect(screen.getByText('时间戳转换')).toBeInTheDocument();
expect(screen.getByText('Unix 毫秒数转换与格式化')).toBeInTheDocument();
});
it('应渲染图标', () => {
render(<PageHeader {...defaultProps} />);
expect(screen.getByTestId('test-icon')).toBeInTheDocument();
});
it('应渲染自定义图标&图标颜色', () => {
render(
<PageHeader
{...defaultProps}
icon={<span data-testid="custom-icon">X</span>}
iconColor="#FF0000"
/>,
);
expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
});
it('应默认使用蓝色作为 primary 色', () => {
render(<PageHeader {...defaultProps} />);
const iconContainer = screen.getByTestId('test-icon').parentElement?.parentElement;
expect(iconContainer).toHaveClass('text-blue-500');
});
it('应渲染 badge 组件', () => {
const badge = <span data-testid="test-badge">New</span>;
render(<PageHeader {...defaultProps} badge={badge} />);
expect(screen.getByTestId('test-badge')).toBeInTheDocument();
});
it('应支持自定义 iconClassName', () => {
render(<PageHeader {...defaultProps} iconClassName="custom-icon-class" />);
const iconContainer = screen.getByTestId('test-icon').parentElement?.parentElement;
expect(iconContainer).toHaveClass('custom-icon-class');
});
it('应支持自定义 titleClassName', () => {
render(<PageHeader {...defaultProps} titleClassName="custom-title-class" />);
const title = screen.getByText('时间戳转换');
expect(title).toHaveClass('custom-title-class');
});
it('应支持自定义 subtitleClassName', () => {
render(<PageHeader {...defaultProps} subtitleClassName="custom-subtitle-class" />);
const subtitle = screen.getByText('Unix 毫秒数转换与格式化');
expect(subtitle).toHaveClass('custom-subtitle-class');
});
it('应支持自定义 className', () => {
const { container } = render(<PageHeader {...defaultProps} className="custom-page-header" />);
const outerElement = container.firstChild;
expect(outerElement).toHaveClass('custom-page-header');
});
it('无副标题时不渲染副标题区域', () => {
const { container } = render(<PageHeader icon={defaultProps.icon} title="仅标题" />);
const subtitles = container.querySelectorAll('.text-muted-foreground');
expect(subtitles.length).toBe(0);
});
});
describe('PageHeader 入口点隐藏', () => {
it('popup 模式下应返回 null', async () => {
const { getEntryPointType } = await import('@/config/features');
vi.mocked(getEntryPointType).mockReturnValue('popup');
const { container } = render(<PageHeader {...defaultProps} />);
expect(container.innerHTML).toBe('');
});
});
});