
Refine v5 的 MUI Show 组件实战指南用 Card 布局构建可定制详情页【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine导读Show是 Refine v5 中用于渲染记录详情页布局的 Material UI 基础视图组件。它本身不包含任何业务逻辑只负责提供页面骨架与通用能力——例如标题、面包屑、刷新按钮、编辑/删除入口以及加载态遮罩。本文以 MUI Show 官方文档 为骨架逐条讲解其全部 Props 的用法并结合 refinedev/mui 源码实现 与 共享 CRUD 测试说明每个配置项在底层是如何生效的。读完本文你将能够独立搭建一个包含自定义标题、权限化操作按钮、多数据源、自定义面包屑与完全定制化头部/底部按钮的生产级详情页。快速上手一个完整的详情页示例Show的核心定位是布局组件它渲染一个 Material UICard内部依次放置面包屑、CardHeader标题与操作按钮、CardContent你的页面内容和CardActions页脚按钮区。数据获取本身由 core 包的useShowhook 负责Show只负责把这些数据展示在规范的布局里。下面是一个标准的帖子详情页完整示例与文档中的 live demo 一致它演示了Show与 useShow、useOne的组合用法import React from react; import { useShow, useOne } from refinedev/core; import { Show, NumberField, TextFieldComponent as TextField, MarkdownField, DateField, } from refinedev/mui; import { Stack, Typography } from mui/material; const ShowPage () { const { result: product, query: { isLoading }, } useShow(); const { result: category, query: { isLoading: categoryIsLoading }, } useOne({ resource: categories, id: product?.category?.id || , queryOptions: { enabled: !!product, }, }); return ( Show isLoading{isLoading} Stack gap{1} Typography variantbody1 fontWeightbold Id /Typography NumberField value{product?.id ?? } / Typography variantbody1 fontWeightbold Title /Typography TextField value{product?.title} / Typography variantbody1 fontWeightbold Content /Typography MarkdownField value{product?.content} / Typography variantbody1 fontWeightbold Category /Typography {categoryIsLoading ? Loading.../ : {category?.title}/} Typography variantbody1 fontWeightbold Created At /Typography DateField value{product?.createdAt} / /Stack /Show ); };要点拆解useShow从当前路由如/posts/show/123自动解析出resource与id并调用 data provider 的getOne方法拉取记录。其底层实现位于 packages/core/src/hooks/show/index.ts它通过useResourceParams解析资源与 id再委托给useOne发起查询返回{ query, result, showId, setShowId, overtime }。分类字段category属于关联记录需要单独用useOne以product?.category?.id为 id 请求并通过queryOptions.enabled控制依赖的product尚未就绪时不发起请求。Show isLoading{isLoading}将查询加载态传给组件加载期间组件会渲染一个覆盖全卡的半透明遮罩 CircularProgress转圈同时禁用头部默认按钮。该组件的 JS Doc 注释明确写道Showprovides us a layout for displaying the page. It does not contain any logic but adds extra functionalities like a refresh button.见 show/index.tsx这印证了它纯布局、无逻辑的设计哲学。核心 Props 详解title自定义页面标题title允许你在Show内部添加标题。如果不传title组件会使用Show 前缀 资源的单数友好名作为默认标题。例如对posts资源默认标题是Show post。从源码看默认标题的实际生成逻辑是title ?? ( Typography varianth5 className{RefinePageHeaderClassNames.Title} {translate( ${identifier}.titles.show, Show ${getUserFriendlyName(resource?.meta?.label ?? identifier, singular)}, )} /Typography )也就是说默认标题优先走 i18n 翻译键${identifier}.titles.show未配置翻译时才回退到Show 单数资源名。useUserFriendlyName负责把资源名转换为用户友好的单数形式。自定义标题示例import { Show } from refinedev/mui; import { Typography } from mui/material; const ShowPage: React.FC () { return ( Show title{Typography varianth5Custom Title/Typography} spanRest of your page here/span /Show ); };共享测试 packages/ui-tests/src/tests/crud/show.tsx 覆盖了三种情况不传 title 时渲染默认标题Show Post、title{false}时不渲染标题、传入titleTest Title时渲染自定义标题。resource指定自定义资源Show默认从路由读取resource信息。如果你想为组件指定一个与路由不同的资源可以传入resourcepropimport { Show } from refinedev/mui; const CustomPage: React.FC () { return ( Show resourceposts recordItemId{123} spanRest of your page here/span /Show ); };注意当你显式传入resource时组件会通过useResourceParams({ resource, id: recordItemId })解析资源参数且recordItemId必须一并提供因为此时无法从 URL 推断记录 id。同名资源的identifier场景如果你有多个同名资源例如不同命名空间下的posts可以传identifier而非name。它只会作为资源匹配的主键data provider 方法仍然基于Refine/组件中定义资源的name来工作。详细说明见 Refine 组件 identifier 文档。从源码可以看到useResourceParams返回的identifier被广泛用于按钮的resource传递如resource: identifier ?? resource?.name这保证了多个同名资源时按钮仍能指向正确的资源定义。canDelete 与 canEdit控制删除/编辑按钮canDelete和canEdit用于在Show内部添加删除与编辑按钮。如果资源本身带有canDelete或canEdit属性定义在资源的meta中Refine 会默认渲染对应按钮无需显式传 prop。行为细节点击删除按钮执行 data provider 的useDelete方法见 useDelete 文档 与 data provider 文档删除成功后通过useGo跳转到列表页。点击编辑按钮跳转到该记录的编辑页。源码中按钮可见性的判定逻辑如下const hasDelete canDelete ?? (resource?.meta?.canDelete || deleteButtonPropsFromProps); const isDeleteButtonVisible hasDelete typeof id ! undefined; const isEditButtonVisible canEdit ?? resource?.meta?.canEdit ?? !!resource?.edit;即删除按钮可见的条件是canDeleteprop 为真或资源meta.canDelete为真或传入了deleteButtonProps且 id 已解析否则没有可删除的目标记录。编辑按钮可见的条件是canEditprop 为真或资源meta.canEdit为真或资源定义了edit路由。删除按钮还内置了onSuccess回调删除完成后调用go({ to: goListPath })跳回列表页见 show/index.tsx。权限化控制示例结合 usePermissions可以按用户角色动态决定按钮是否渲染import { Show } from refinedev/mui; import { usePermissions } from refinedev/core; const ShowPage: React.FC () { const { data: permissionsData } usePermissions(); return ( Show canDelete{permissionsData?.includes(admin)} canEdit{ permissionsData?.includes(editor) || permissionsData?.includes(admin) } pRest of your page here/p /Show ); };关于优先级测试 packages/mui/src/components/crud/show/index.spec.tsx 覆盖了完整的组合矩阵组件级canDelete/canEdit会覆盖资源meta中的同名配置而传入deleteButtonProps本身就会触发删除按钮渲染即使资源canDelete为 false。deleteButtonProps定制删除按钮如果资源具备canDelete属性且你想自定义删除按钮可以使用deleteButtonProps。源码中它会被展开到删除按钮的最终 props 上...deleteButtonPropsFromProps位于 dataProviderName 之后因此你可以覆盖尺寸、图标、确认弹窗文案等任意DeleteButton支持的能力import { Show } from refinedev/mui; import { usePermissions } from refinedev/core; const ShowPage: React.FC () { const { data: permissionsData } usePermissions(); return ( Show canDelete{permissionsData?.includes(admin)} deleteButtonProps{{ size: small }} canEdit{ permissionsData?.includes(editor) || permissionsData?.includes(admin) } pRest of your page here/p /Show ); };更多按钮能力参见 DeleteButton 文档 与 EditButton 文档。recordItemId无法从 URL 读取 id 时手动指定Show默认从路由读取id。当组件被用在自定义页面、Modal 或 Drawer中URL 中没有 id时必须通过recordItemId显式传入import { Show } from refinedev/mui; const CustomPage: React.FC () { return ( Show resourceposts recordItemId{123} spanRest of your page here/span /Show ); };从源码看const id recordItemId ?? idFromParams;recordItemId的优先级高于路由参数。同时它还会影响头部按钮的渲染逻辑const hasList resource?.list !recordItemId——当显式传入recordItemId时列表按钮不会渲染listButtonProps为undefined测试 index.spec.tsx 验证了这一行为。Show组件需要id信息才能让 RefreshButton 正常工作——因为刷新按钮需要携带recordItemId重新触发getOne查询。dataProviderName多数据源下切换 data provider默认情况下 Refine 使用defaultdata provider。当你的应用配置了多个 data provider并希望详情页使用其中某个特定 provider 时传入dataProviderNameimport { Refine } from refinedev/core; import dataProvider from refinedev/simple-rest; import { Show } from refinedev/mui; const PostShow () { return Show dataProviderNameother.../Show; }; export const App: React.FC () { return ( Refine dataProvider{{ default: dataProvider(https://api.fake-rest.refine.dev/), other: dataProvider(https://other-api.fake-rest.refine.dev/), }} {/* ... */} /Refine ); };在源码中dataProviderName被透传给DeleteButton与RefreshButton见deleteButtonProps与refreshButtonProps的构造确保删除与刷新操作也走同一个 data provider。goBack自定义或禁用返回按钮默认情况下Show在CardHeader的avatar位置渲染一个ArrowBackIcon返回按钮goBack默认值为ArrowLeft /点击后调用useBack()返回。可通过goBack自定义或传null/false禁用import { Show } from refinedev/mui; import { Button } from mui/material; import { useBack } from refinedev/core; const BackButton () { const goBack useBack(); return Button onClick{goBack}BACK!/Button; }; const PostShow: React.FC () { return ( Show goBack{BackButton /} spanRest of your page here/span /Show ); };源码逻辑为typeof goBackFromProps ! undefined ? goBackFromProps : IconButton ...ArrowBackIcon //IconButton——只要显式传入goBack哪怕是null就完全替换默认的 IconButton。isLoading切换加载态isLoading用于切换Show/的加载状态。加载时组件渲染全卡遮罩与CircularProgress并将头部默认按钮全部置为disabledimport { Show } from refinedev/mui; const PostShow: React.FC () { const [loading, setLoading] React.useState(true); return ( Show isLoading{loading} spanRest of your page here/span /Show ); };底层遮罩实现见 show/index.tsx使用绝对定位铺满整卡背景色为alpha(theme.palette.background.paper, 0.4)zIndex高于抽屉层级注释特别说明这用于支持自定义主题与暗色模式。同时四个默认按钮List/Edit/Delete/Refresh在isLoading时都会附带disabled: true。布局定制wrapper、header 与 contentShow/在结构上对应 Material UI 的Card组件族因此提供了三个层级的外观定制入口分别透传到Card、CardHeader与CardContent类型定义见 packages/mui/src/components/crud/types.ts。wrapperProps定制整卡外层对应 MUICardProps例如设置整卡背景色import { Show } from refinedev/mui; const PostShow: React.FC () { return ( Show wrapperProps{{ sx: { backgroundColor: lightsteelblue, }, }} spanRest of your page here/span /Show ); };源码中wrapperProps展开在Card上且内部强制合并了position: relative遮罩绝对定位需要随后才合并你传入的sx——因此你的sx不会破坏遮罩的定位基准。headerProps定制头部对应 MUICardHeaderProps用于调整标题区域的背景、内边距等import { Show } from refinedev/mui; const PostShow: React.FC () { return ( Show headerProps{{ sx: { backgroundColor: lightsteelblue, }, }} spanRest of your page here/span /Show ); };注意源码中{...(headerProps ?? {})}位于组件自带样式之后这意味着你可以覆盖默认的display: flex; flexWrap: wrap布局以及.MuiCardHeader-action的对齐规则。contentProps定制内容区对应 MUICardContentProps控制页面主体区域的样式import { Show } from refinedev/mui; const PostShow: React.FC () { return ( Show contentProps{{ sx: { backgroundColor: lightsteelblue, }, }} spanRest of your page here/span /Show ); };头部与底部按钮区headerButtons扩展或替换头部按钮默认情况下Show/头部依次渲染 ListButton、EditButton、DeleteButton 和 RefreshButton。headerButtons接受两种形式React.ReactNode完全替换默认按钮渲染函数({ defaultButtons, deleteButtonProps, editButtonProps, listButtonProps, refreshButtonProps }) React.ReactNode保留默认按钮并追加自定义按钮。各按钮 props 的出现时机与源码判定一致若未定义list资源ListButton不渲染listButtonProps为undefined若canDelete为falseDeleteButton不渲染deleteButtonProps为undefined若canEdit为falseEditButton不渲染editButtonProps为undefinedrefreshButtonProps始终存在刷新按钮无条件渲染。保留默认按钮并追加自定义按钮import { Show } from refinedev/mui; import { Button } from mui/material; const PostShow: React.FC () { return ( Show headerButtons{({ defaultButtons }) ( {defaultButtons} Button typeprimaryCustom Button/Button / )} spanRest of your page here/span /Show ); };完全自定义按钮组合不依赖defaultButtons且可给每个按钮追加metaimport { Show, ListButton, EditButton, DeleteButton, RefreshButton, } from refinedev/mui; import { Button } from mui/material; const PostShow: React.FC () { return ( Show headerButtons{({ deleteButtonProps, editButtonProps, listButtonProps, refreshButtonProps, }) ( Button typeprimaryCustom Button/Button {listButtonProps ( ListButton {...listButtonProps} meta{{ foo: bar }} / )} {editButtonProps ( EditButton {...editButtonProps} meta{{ foo: bar }} / )} {deleteButtonProps ( DeleteButton {...deleteButtonProps} meta{{ foo: bar }} / )} RefreshButton {...refreshButtonProps} meta{{ foo: bar }} / / )} spanRest of your page here/span /Show ); };源码中默认按钮区的构建逻辑为见 show/index.tsxconst defaultHeaderButtons ( {hasList ListButton {...listButtonProps} /} {isEditButtonVisible EditButton {...editButtonProps} /} {isDeleteButtonVisible DeleteButton {...deleteButtonProps} /} RefreshButton {...refreshButtonProps} / / );共享测试 packages/ui-tests/src/tests/crud/show.tsx 验证了默认情况下canEdit/canDelete打开时四个按钮全部渲染且通过headerButtons传入普通节点即可整体替换。headerButtonProps定制头部按钮容器headerButtonProps对应 MUIBoxProps用于定制包裹头部按钮的容器样式源码中为Box displayflex gap16px {...(headerButtonProps ?? {})}import { Show } from refinedev/mui; import { Button } from mui/material; const PostShow: React.FC () { return ( Show headerButtonProps{{ sx: { backgroundColor: lightsteelblue, }, }} headerButtons{({ defaultButtons }) ( {defaultButtons} Button typeprimaryCustom Button/Button / )} spanRest of your page here/span /Show ); };footerButtons 与 footerButtonProps定制底部按钮区footerButtons同样接受React.ReactNode或渲染函数({ defaultButtons }) React.ReactNode。与头部不同底部默认没有按钮——源码中footerButtons({ defaultButtons: null })默认defaultButtons为null因此主要用途是添加自定义页脚操作import { Show } from refinedev/mui; import { Button } from mui/material; const PostShow: React.FC () { return ( Show footerButtons{({ defaultButtons }) ( {defaultButtons} Button typeprimaryCustom Button/Button / )} spanRest of your page here/span /Show ); };footerButtonProps对应 MUICardActionsProps源码中CardActions默认带sx{{ padding: 16px }}用于定制页脚按钮容器import { Show } from refinedev/mui; import { Button } from mui/material; const PostShow: React.FC () { return ( Show footerButtonProps{{ sx: { backgroundColor: lightsteelblue, }, }} footerButtons{({ defaultButtons }) ( {defaultButtons} Button typeprimaryCustom Button/Button / )} spanRest of your page here/span /Show ); };breadcrumb面包屑定制breadcrumb用于自定义或禁用面包屑。默认使用refinedev/mui包中的Breadcrumb组件你可以用breadcrumb{null}完全关闭或包裹自定义内容import { Show, Breadcrumb } from refinedev/mui; const PostShow: React.FC () { return ( Show breadcrumb{ div style{{ padding: 3px 6px, border: 2px dashed cornflowerblue, }} Breadcrumb / /div } spanRest of your page here/span /Show ); };源码逻辑为const breadcrumbComponent typeof breadcrumb ! undefined ? {breadcrumb}/ : Breadcrumb /;只要显式传入breadcrumb包括null就会替换默认Breadcrumb。测试 packages/mui/src/components/crud/show/index.spec.tsx 验证了默认渲染Posts / Show面包屑、传breadcrumb{null}时不渲染面包屑两个行为。更多信息参见 Breadcrumb 文档。常见实战组合与最佳实践详情页 关联记录加载如开篇示例用useShow拉主记录用useOne或useMany以关联 id 拉取分类等外键数据配合queryOptions.enabled避免无效请求。权限驱动的操作区用usePermissions计算canEdit/canDelete并进一步用headerButtons的渲染函数按角色裁剪按钮做到前端不可见 后端仍校验的双重安全。Modal / Drawer 内的详情视图此时组件不在/posts/show/:id路由下务必同时传入resource与recordItemId并注意此时列表按钮会自动隐藏。多 data provider 应用为不同的详情页显式指定dataProviderName并确认删除/刷新按钮同样走该 provider。与 accessControlProvider 联动测试 index.spec.tsx 演示了在接入accessControlProvider后deleteButtonProps/editButtonProps会根据can()的判定结果自动被禁用或启用——这是 Refine 内置的访问控制与按钮状态的联动机制。API ReferenceShowProps的类型定义位于 packages/mui/src/components/crud/types.ts继承自refinedev/ui-types的RefineCrudShowProps。主要 Props 与对应 MUI 类型速查Prop说明对应 MUI 类型 / 默认值title页面标题默认Show 单数资源名ReactNoderesource/identifier指定资源可传 identifier 匹配同名资源stringrecordItemId无法从 URL 读取 id 时手动指定BaseKeycanDelete/canEdit控制删除/编辑按钮显示booleandeleteButtonProps定制删除按钮DeleteButtonPropsdataProviderName指定使用的 data providerstringgoBack自定义返回按钮默认ArrowLeft /ReactNodeisLoading切换加载态遮罩 禁用按钮boolean默认falsebreadcrumb自定义面包屑默认Breadcrumb/ReactNodewrapperProps整卡外层样式CardPropsheaderProps头部样式CardHeaderPropscontentProps内容区样式CardContentPropsheaderButtons头部按钮节点或渲染函数默认ListButton、EditButton、DeleteButton、RefreshButtonheaderButtonProps头部按钮容器样式BoxPropsfooterButtons底部按钮节点或渲染函数默认无footerButtonProps底部按钮容器样式CardActionsProps如何用 Refine CLI 生成并自定义该组件该组件在文档中标记为可 swizzleswizzle: true。你可以使用 Refine CLI 的 swizzle 命令把Show的源码复制到自己的项目中之后即可完全脱离包内实现进行定制npm run refine swizzle选择refinedev/mui下的Show组件即可。swizzle 后生成的副本以你的项目代码为准不再随包升级变化——适合需要深度改造布局例如换成自定义卡片结构、注入公司级水印或审计按钮的场景。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考