文件
hyb-oa/client/src/components/patterns/BusinessDetailTemplate.tsx
T
wangsli 98eabdde7f
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
fix: 统一报销付款详情布局与票据展示(09-04-reimbursement-state-gap-finance-view)
2026-09-11 22:28:06 +08:00

142 行
4.0 KiB
TypeScript

import { Children, Fragment, isValidElement, type ReactNode } from "react";
import { cn } from "../../lib/utils";
import { SectionPanel } from "./SectionPanel";
export function BusinessDetailWorkspace({
children,
className,
testId,
}: {
children: ReactNode;
className?: string;
testId?: string;
}) {
return (
<div className={cn("detail-workspace-grid", className)} data-testid={testId}>
{children}
</div>
);
}
export function BusinessDetailMain({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return <div className={cn("space-y-5", className)}>{children}</div>;
}
export function BusinessDetailAside({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return <div className={cn("space-y-5", className)}>{children}</div>;
}
export function BusinessDetailFactGrid({
children,
className,
testId,
}: {
children: ReactNode;
className?: string;
testId?: string;
}) {
return (
<div className={cn("detail-fact-grid", className)} data-testid={testId}>
{children}
</div>
);
}
export function BusinessDetailFact({
label,
children,
className,
}: {
label: ReactNode;
children: ReactNode;
className?: string;
}) {
return (
<div className={cn("detail-fact-card", className)}>
<div className="detail-fact-card__label">{label}</div>
<div className="detail-fact-card__value">{children}</div>
</div>
);
}
function hasVisibleNode(node: ReactNode): boolean {
return Children.toArray(node).some((child) => {
if (isValidElement(child) && child.type === Fragment) {
return hasVisibleNode(child.props.children);
}
return true;
});
}
export function BusinessDetailActionPanel({
title = "操作",
status,
beforeActions,
primaryAction,
secondaryActions,
moreActions,
inlineActions = false,
summary,
children,
className,
testId,
}: {
title?: string;
status?: ReactNode;
beforeActions?: ReactNode;
primaryAction?: ReactNode;
secondaryActions?: ReactNode;
moreActions?: ReactNode;
inlineActions?: boolean;
summary?: ReactNode;
children?: ReactNode;
className?: string;
testId?: string;
}) {
const hasStatus = hasVisibleNode(status);
const hasBeforeActions = hasVisibleNode(beforeActions);
const hasPrimaryAction = hasVisibleNode(primaryAction);
const hasSecondaryActions = hasVisibleNode(secondaryActions);
const hasMoreActions = hasVisibleNode(moreActions);
const hasSummary = hasVisibleNode(summary);
const hasChildren = hasVisibleNode(children);
const hasActionContent = hasPrimaryAction || hasSecondaryActions || hasMoreActions;
const hasContentBeforeSummary = hasBeforeActions || hasActionContent || hasChildren;
if (!hasStatus && !hasBeforeActions && !hasActionContent && !hasSummary && !hasChildren) {
return null;
}
return (
<SectionPanel title={title} actions={status} className={cn("detail-action-panel lg:sticky lg:top-24", className)}>
<div className="detail-action-stack" data-testid={testId}>
{hasBeforeActions ? <div>{beforeActions}</div> : null}
{inlineActions && (hasPrimaryAction || hasMoreActions) ? <div className="flex flex-wrap items-center gap-2" data-testid="detail-inline-actions">{primaryAction}{moreActions}</div> : null}
{!inlineActions && hasPrimaryAction ? <div className="grid gap-2">{primaryAction}</div> : null}
{hasSecondaryActions ? <div className="workflow-action-bar">{secondaryActions}</div> : null}
{!inlineActions && hasMoreActions ? <div className="border-t border-zinc-200 pt-3">{moreActions}</div> : null}
{hasChildren ? (
<div className={cn(hasActionContent && "border-t border-zinc-200 pt-3")}>{children}</div>
) : null}
{hasSummary ? (
<div className={cn(hasContentBeforeSummary && "border-t border-zinc-200 pt-3")}>
<div className="space-y-2 text-sm text-zinc-600">{summary}</div>
</div>
) : null}
</div>
</SectionPanel>
);
}