Appearance
展示与反馈组件
@mbjia/components
展示与反馈组件
用于加载、空状态、进度、标签和轻量操作展示。
这些组件主要用于加载、空状态、进度、标签和轻量操作展示。
模块定位
提供业务页面中常见的反馈和展示能力,包括加载、空状态、进度、匹配度、图标、长文本省略和虚拟列表。
LoadingEmptyProgress
接入建议
这类组件大多依赖较少,适合普通业务页面按需使用。复杂列表场景优先考虑 CommonVirtualList。
轻业务依赖按需使用展示优先
组件索引
| 组件 | 说明 |
|---|---|
Loading | AntD Spin 封装 |
CircleLoading | 圆形 loading |
DotLoading | 点状 loading |
RedCircleLoading | 红色圆形 loading |
VeLoadingWhite | 白色 loading |
Empty | 空状态 |
ProgressBar | 普通进度条 |
SegmentedProgressBar | 分段进度条 |
MatchingDegree | 匹配度标签 |
VeIcon | 业务图标 |
ImageLoad | 图片加载、错误兜底和默认图封装 |
HoverIcon | hover 切换背景图标 |
EllipsisInMiddleOfText | 中间省略文本 |
CommonModal | 通用弹窗 |
ModalContainer | 带 tab、目录和内容区的业务弹窗容器 |
CommonSegmentedNav | 分段导航 |
CommonVirtualList | 虚拟列表 |
Actions | 表格行内操作栏,超出项进入更多菜单 |
MoreActions | 三点更多操作菜单 |
SetNumberMemo | 编辑器数值输入框 |
SetSelectAndInput | 编辑器 Select 封装 |
InfiniteSliderMemo | 拖拽式无限数值调节器 |
InfiniteAndInputNumberMemo | 无限拖拽和数值输入组合控件 |
PureTextFont | 字体选择浮层 |
效果预览
解析中 68%
Loading
AntD Spin 的轻量封装,用于区域加载态。
tsx
<Loading style={{ minHeight: 120 }} />| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
style | React.CSSProperties | 否 | {} | 透传到 loading 外层容器。 |
Empty
空状态组件。默认会优先读取主题配置里的 images.common-empty,没有主题图时才使用入参 img。
tsx
<Empty text="暂无素材" img="/empty.png" imageW={134} imageH={134} />| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
img | string | 是 | - | 兜底空状态图片地址。 |
text | React.ReactNode | 是 | - | 空状态文案。 |
backgroundColor | string | 否 | '#fff' | 图片加载背景色,传给 ImageLoad。 |
imageW | number | 否 | 134 | 图片宽度。 |
imageH | number | 否 | 134 | 图片高度。 |
customStyle | React.CSSProperties | 否 | - | 外层容器样式。 |
imgStyle | React.CSSProperties | 否 | - | 图片容器样式。 |
ProgressBar
普通进度条。percent 会直接转换为宽度百分比。
tsx
<ProgressBar percent={68} content="处理中 68%" />| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
content | React.ReactNode | 是 | - | 进度条内部展示内容。 |
percent | number | 是 | - | 进度百分比,源码中直接用于 width: ${percent}%。 |
customStyle | React.CSSProperties | 否 | - | 外层样式。 |
SegmentedProgressBar
视频时间轴分段进度条,用于展示精彩片段、分析结果和当前播放位置。
tsx
const segments = [
{ id: 's1', start: 2, end: 8, detail: '商品露出清晰', title: '镜头 1' },
]
<SegmentedProgressBar
segments={segments}
startTime={0}
endTime={30}
currentTime={6}
isPlaying={false}
onClick={(time) => seek(time)}
onPause={pause}
onPlay={play}
/>数据结构:
ts
export interface Segment {
start: number
end: number
color?: string
id: string
detail: string
title?: string
}| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
segments | Segment[] | 是 | - | 分段片段。只渲染与 [startTime, endTime] 有交集的片段。 |
startTime | number | 是 | - | 当前时间轴起始秒。 |
endTime | number | 是 | - | 当前时间轴结束秒。 |
currentTime | number | 是 | - | 当前播放秒。 |
isPlaying | boolean | 是 | - | 拖拽结束后是否恢复播放。 |
onClick | (time: number) => void | 是 | - | 点击或拖拽时间轴时触发。 |
onPause | () => void | 是 | - | 开始拖拽时触发。 |
onPlay | () => void | 是 | - | 拖拽结束且 isPlaying 为 true 时触发。 |
onSegmentClick | (segment: Segment) => void | 否 | - | 点击片段时触发。 |
offsetTime | { start: number; end: number } | 否 | { start: 0, end: 0 } | 用于展示偏移后的时间文案。 |
highLightMoment | string | 否 | - | 悬浮提示中的高光时刻文案。 |
MatchingDegree
ts
interface PropsType {
score: number
size?: 'large' | 'small'
degree?: 'vh' | 'h' | 'm' | 'l'
}用途:展示素材匹配程度。
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
score | number | 是 | - | 匹配分数。未传 degree 时会按分数计算等级。 |
size | 'large' | 'small' | 否 | 'large' | 尺寸。 |
degree | 'vh' | 'h' | 'm' | 'l' | 否 | 按 score 计算 | 指定匹配等级。 |
等级文案:
| 等级 | 文案 | 默认分数规则 |
|---|---|---|
vh | 匹配度极高 | score >= 0.6 |
h | 匹配度高 | 0.3 <= score < 0.6 |
m | 匹配度一般 | 0.2 <= score < 0.3 |
l | 匹配度低 | score < 0.2 |
EllipsisInMiddleOfText
用于文件名或长文本中间省略,保留尾部关键信息。
tsx
<EllipsisInMiddleOfText text="very-long-file-name.mp4" lastPart=".mp4" />| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
text | string | 是 | - | 完整文本。 |
lastPart | string | 否 | text | 尾部保留/提示文本。 |
className | string | 否 | - | 自定义 class。 |
ImageLoad
图片加载封装。相比普通 img,它会通过 useImageLoad 处理加载状态、错误兜底、默认图和 OEM 默认图。
tsx
<ImageLoad
src={asset.coverUrl}
fallbackSrc={asset.previewUrl}
backgroundColor="#f5f5f5"
cover
shouldDefaultImg
handleClick={() => preview(asset)}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
src | string | 否 | - | 首选图片地址。 |
dataUrl | string | 否 | - | base64 或 data URL,优先级高于 src。 |
fallbackSrc | string | 否 | - | src 加载失败后的兜底地址。 |
alt | string | 否 | '加载失败' | 图片 alt。 |
error | boolean | 否 | false | 外部控制错误态。 |
onError | () => void | 否 | - | 图片加载失败时触发。 |
handleClick | () => void | 否 | - | 点击外层容器时触发。 |
backgroundColor | string | 否 | '' | loading 或图片背景色。 |
customStyle | React.CSSProperties | 否 | {} | 外层样式。 |
customImgStyle | React.CSSProperties | 否 | { height: '100%' } | 图片样式。 |
className | string | 否 | '' | 自定义 class。 |
cover | boolean | 否 | false | 是否使用 object-fit: cover。 |
shouldDefaultImg | boolean | 否 | false | loading 时是否直接展示默认图。 |
HoverIcon
hover 时切换背景图的图标容器,适合操作按钮、卡片悬浮操作。
tsx
<HoverIcon
beforeHover="/icons/edit.svg"
afterHover="/icons/edit-active.svg"
w={16}
h={16}
pointer
onClick={() => edit()}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
beforeHover | string | 是 | - | 默认背景图。 |
afterHover | string | 是 | - | hover 背景图。 |
w | number | string | 是 | - | 宽度。 |
h | number | string | 否 | w | 高度。 |
pointer | boolean | 否 | - | 是否展示手型光标。 |
onClick | () => void | 否 | - | 点击回调。 |
style | React.CSSProperties | 否 | - | 自定义样式,会合并到背景图样式上。 |
CommonSegmentedNav
基于 AntD Segmented 的导航组件,适合切换轻量 tab。
ts
type CommonSegmentedOption = {
label: ReactNode
value: string | number
icon?: ReactNode
disabled?: boolean
className?: string
}| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
options | CommonSegmentedOption[] | 否 | 三个“导航”默认项 | 分段选项。 |
crtNav | number | string | 否 | options[0].value | 当前选中项。 |
onChange | (value) => void | 否 | - | 切换回调。 |
className | string | 否 | 'segmentedNav' | 自定义 class。 |
CommonModal
基于 AntD Modal 的业务通用弹窗。统一了居中、不可点击遮罩关闭、默认宽度和业务 wrap class,并在关闭时通过 onOpenChange 通知外层。
tsx
<CommonModal
open={open}
okText="确认"
cancelText="取消"
onOk={() => submit()}
onCancel={() => cancel()}
onOpenChange={(open, fromFooter) => console.log(open, fromFooter)}
>
<div>弹窗内容</div>
</CommonModal>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
open | boolean | 否 | - | 是否打开。 |
cancelText | string | ReactNode | 否 | - | 取消按钮文案。 |
okText | string | 否 | - | 确认按钮文案。 |
onCancel | Function | 否 | - | 点击 footer 取消按钮时触发。 |
onOk | Function | 否 | - | 点击确认按钮时触发。 |
className | string | 否 | - | 业务 class,会拼到 wrapClassName。 |
onOkNotClose | boolean | 否 | false | 点击确认后是否保持弹窗打开。 |
onCancelNotClose | boolean | 否 | - | 点击 footer 取消后是否保持弹窗打开。 |
onOpenChange | Function | 否 | - | 所有关闭动作都会触发;第二个参数表示是否来自 footer 区域。 |
width | number | string | 否 | 364 | 弹窗宽度。 |
zIndex | number | 否 | 3010 | 弹窗层级。 |
| 其他属性 | ModalProps | 否 | - | 透传给 AntD Modal。 |
ModalContainer
带 tab、左侧目录和右侧内容区的业务弹窗容器。ScriptModal 等复杂素材选择弹窗可以复用这个结构。
tsx
<ModalContainer
open={open}
zIndex={3000}
destroyOnClose
currTab="music"
tabList={[{ id: 'music', title: '音乐' }]}
dirList={[{ id: 'default', title: '默认分组', selected: true }]}
onChangeTab={(tab) => setTab(tab)}
onChangeGroup={(group) => setGroup(group)}
onClose={() => setOpen(false)}
>
<MusicList />
</ModalContainer>数据结构:
ts
export const ModalType = {
MUSIC: 'music',
STICKER: 'sticker',
CAPTION: 'caption',
SCRIPT: 'script',
} as const
export type GroupItemType = {
id: string
title: string
selected?: boolean
children?: GroupItemType[]
}| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
zIndex | number | 是 | - | 弹窗层级。 |
open | boolean | 是 | - | 弹窗是否打开。 |
destroyOnClose | boolean | 是 | false | 关闭时是否销毁内容。 |
currTab | string | 是 | - | 当前 tab id。 |
tabList | { id: string; title: string }[] | 否 | [] | 顶部 tab 列表。 |
dirList | GroupItemType[] | 否 | [] | 左侧目录列表。 |
tabBtn | string | 否 | '' | 右上角按钮文案。 |
onChangeTab | (tab: string) => void | 是 | - | 切换 tab。 |
onClose | () => void | 是 | - | 关闭弹窗。 |
onClickTabBtn | () => void | 否 | 空函数 | 点击右上角按钮。 |
onChangeGroup | (group: GroupItemType) => void | 否 | - | 切换目录。 |
children | React.ReactNode | 是 | - | 右侧内容区。 |
width | number | 否 | 1098 | 弹窗宽度。 |
contentContainerStyle | React.CSSProperties | 否 | {} | 内容区样式。 |
customDir | React.ReactNode | 否 | - | 自定义左侧目录区域,优先级高于 dirList。 |
Actions / MoreActions
表格或列表行内操作组件。Actions 会把前几个操作直接展示,剩余操作交给 MoreActions 三点菜单。
tsx
<Actions
data={row}
actions={[
{ label: '编辑', value: 'edit', onClick: ({ data }) => edit(data) },
{ label: '删除', value: 'delete', onClick: ({ data }) => remove(data) },
]}
showItems={[0]}
/>Actions Props:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
actions | { label: string; value: string; onClick: (data: any) => void; disabled?: boolean }[] | 否 | [] | 操作列表。 |
showItems | number[] | 否 | [0, 1] | 直接展示的操作下标。 |
data | any | 否 | {} | 当前行数据。点击时会包装成 { data } 传给 action。 |
disAbleFn | (data: any, item: any) => boolean | 否 | - | 动态禁用函数。 |
isLine | boolean | 否 | true | 更多按钮方向样式。 |
MoreActions Props:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
items | { label: string; value: any }[] | 否 | 删除、编辑 | AntD Dropdown 菜单项。 |
isLine | boolean | 否 | true | 三点按钮横向或纵向样式。 |
SetNumberMemo
编辑器数值输入框,基于 AntD InputNumber。支持百分比、角度、秒等格式化显示,并在键盘或步进操作结束后触发 onAfterChange。
tsx
<SetNumberMemo
value={20}
controlType={103}
acceptType="number"
onChange={(value) => setValue(value)}
onAfterChange={(value) => save(value)}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
acceptType | 'number' | 'text' | 否 | 'number' | 输入值转换方式。 |
value | number | null | undefined | '' | 否 | - | 当前值;空值会显示为空输入框。 |
controlType | number | string | 否 | - | 控制格式化规则。103/202/302 展示 %,deg 展示角度,seconds 展示秒。 |
numberPer | number | string | 否 | controlType | 控制数值精度。 |
width | number | string | 否 | 104 | 输入框宽度。 |
controls | boolean | object | 否 | 自定义上下图标 | 透传给 InputNumber。 |
onChange | (value: number | string | null) => void | 否 | - | 输入变化时触发。 |
onAfterChange | (value: number | string | null) => void | 否 | - | 键盘抬起或步进操作后触发。 |
特殊格式:
controlType | 展示形式 | 说明 |
|---|---|---|
106、201、301 | 圈数 x 角度° | 源码会拆成两个输入框,最终值按 level1 * 360 + level2 返回。 |
103、202、302 | value% | 百分比输入。 |
'deg' | value° | 角度输入。 |
'seconds' | value s | 秒输入。 |
SetSelectAndInput
编辑器 Select 封装。组件会把 optionsList 转成 AntD Select.Option,其他属性透传给 Select。
tsx
<SetSelectAndInput
optionsList={['左对齐', '居中', '右对齐']}
value="居中"
onChange={(value) => setAlign(value)}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
optionsList | any[] | 是 | - | 下拉选项列表;每一项同时作为 key、value 和展示文案。 |
className | string | 否 | - | 自定义 class。 |
| 其他 Select 属性 | SelectProps | 否 | - | 透传给 AntD Select。 |
InfiniteSliderMemo
拖拽式无限数值调节器。适合编辑器里通过横向拖拽快速调整数值,并在拖拽结束后触发保存。
tsx
<InfiniteSliderMemo
title="字号"
value={32}
min={0}
max={200}
onChange={(value) => setFontSize(value)}
onAfterChange={(value) => saveFontSize(value)}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
value | number | null | 否 | 0 | 当前值。源码会把 undefined/null 当作 0。 |
title | string | 否 | - | 拖拽控件展示文案。 |
min | number | 否 | - | 最小值,透传给 react-input-number-editor-more。 |
max | number | 否 | - | 最大值,透传给 react-input-number-editor-more。 |
controlType | number | 否 | - | 控制精度。 |
numberPer | number | string | 否 | controlType | 控制拖拽精度。 |
onChange | (value: number) => void | 是 | - | 拖拽过程中持续触发。 |
onAfterChange | (value: number) => void | 否 | - | 鼠标松开后触发。 |
InfiniteAndInputNumberMemo
组合 InfiniteSliderMemo 和 SetNumberMemo,同时支持拖拽调节和精确输入。
tsx
<InfiniteAndInputNumberMemo
title="透明度"
value={80}
min={0}
max={100}
controlType={103}
onChange={(value) => setOpacity(value)}
onAfterChange={(value) => saveOpacity(value)}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
structure | 'vertical' | string | 否 | 水平布局 | 传 vertical 时垂直排列。 |
title | string | ReactNode | 否 | '值' | 拖拽控件文案。 |
value | number | null | 否 | min 或 0 | 当前值。 |
controlType | 102 | 106 | 103 | string | 否 | - | 透传给两个子控件。 |
onChange | (value: number) => void | 否 | - | 拖拽或输入变化时触发。 |
onAfterChange | (value: number) => void | 否 | - | 操作结束后触发。 |
| 其他属性 | any | 否 | - | 透传给 InfiniteSliderMemo 和 SetNumberMemo。 |
PureTextFont
字体选择浮层。支持字体库、团队字体、最近使用、搜索字体、默认字体和按元素定位展示。
tsx
const fontRef = useRef<HTMLDivElement>(null)
<PureTextFont
ref={fontRef}
originFontName="默认字体"
currentFontName={fontName}
language={{ id: 1 }}
placementElement={buttonRef.current}
placement="bottomRight"
showTeamFont
onSelectFont={(font) => setFont(font)}
onCloseTextFont={() => setOpen(false)}
/>| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
originFontName | string | 否 | - | 素材原始默认字体名。 |
currentFontName | string | 否 | - | 当前字体名。 |
language | { id: number } | 否 | - | 当前语言。个人字体库会按该语言查询。 |
showDefault | boolean | 否 | true | 是否展示默认字体项。 |
showTeamFont | boolean | 否 | - | 是否展示团队字体 tab。 |
placementElement | HTMLElement | 否 | - | 用于定位的目标元素。 |
placement | 'bottomRight' | 'topRight' | 'bottomLeft' | 'topLeft' | 'Right' | 'Left' | 否 | - | 浮层位置。 |
gap | [number, number] | 否 | [0, 10] | 定位偏移。 |
style | React.CSSProperties | 否 | - | 浮层样式。 |
onSelectFont | (font) => void | 是 | - | 选择字体后触发。 |
onCloseTextFont | () => void | 是 | - | 点击遮罩关闭浮层。 |
依赖接口:
| 接口 | 用途 |
|---|---|
reqAssetFontList | 查询字体列表。 |
reqAssetGetFontRecentUsage | 查询最近使用字体。 |
reqAssetSetFontRecentUsage | 选择字体后记录最近使用。 |
getDepartmentListY | 判断是否展示团队字体。 |
CommonVirtualList
基于 virtua 的虚拟列表,用于长列表性能优化。