基于 AntD 封装的可编辑 tabs
AntD 的 Tabs 只支持新增/删除页签,而不支持修改,故我们需要扩展一下
# 功能
新增:点击最右侧“+”新增,默认名称为:“未命名”(可通过 defaultTabName 配置),如名称已重复,则命名为“未命名(1)”,括号中数字累加,新增后选中该 tabs
切换和改名:点击页签切换到该页签,聚焦并选中页签文字,并可进行更改页签名称,重名则改名失败,恢复原名称,toast 提示:名称已存在!;如清空页签文字,默认名称为“未命名”,重名与新增处理一致
删除:只有当前选中的页签显示删除图标:“X”。点击“X”可以删除页签
编辑时 Input 默认无边框,可通过传入 inputProps.bordered 配置;默认名称字符个数不限制,可通过传入 inputProps.maxLength 限制;inputProps 支持大部分 Input 属性;
默认名称全部显示,可通过传入 tabPaneWidth 控制宽度,默认超长显示 ...;宽度超出屏幕后,样式与 Tabs 一致
重名处理时,tabs 全部名称默认取当前所有页签名称,可通过传入 tabNames 自定义
页签前可通过传入 icon 展示额外状态:两种方式:
- 通过传入 statusIcon (type: Dom | Function) 方式,自定义程度高
- 组件预置几种状态,通过传入 statusIcon (type: string) 方式,受限于预置,自定义程度低
# 定义 Props
import { TabsProps, TabPaneProps } from 'antd/lib/tabs'
import RealInput, { RealInputProps } from '../RealInput'
export interface EditableTabPaneProps extends Omit<TabPaneProps, 'tab'> {
tab: string
key: string
statusIcon?: 'error' | 'success' | React.ReactNode | Function
style?: React.CSSProperties
}
export interface EditableTabsInputProps extends Omit<RealInputProps, 'value'> {}
export interface EditableTabsProps extends Omit<TabsProps, 'activeKey' | 'onEdit'> {
activeKey: string
inputProps?: EditableTabsInputProps
defaultTabName?: string
tabNames?: string[]
tabPaneWidth?: number
children: React.ReactElement<EditableTabPaneProps>[]
onEdit?: (
e: React.MouseEvent | React.KeyboardEvent | string,
action: 'add' | 'remove' | 'rename',
value?: string
) => void
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 接收 Props,定义 state
const {
inputProps,
defaultTabName = '未命名',
tabNames,
tabPaneWidth,
children,
activeKey,
type = 'editable-card',
onEdit,
...res
} = props
const [state, updateState] = useImmer({
tabNames: [],
tabKey: Date.now()
})
const inputRef = useRef(null)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 监听事件
useEffect(() => {
if (tabNames) return
updateState(draft => {
draft.tabNames = children.map(item => item.props.tab)
})
}, [children])
useEffect(() => {
const { current } = inputRef
current?.focus()
current?.select()
}, [activeKey])
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 处理原始 onEdit
/**
* 编辑
* @param e event | activeKey
* @param action add | remove
*/
function handleEdit(
e: string | React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>,
action: 'add' | 'remove'
) {
if (action === 'add') {
const value = generateName(tabNames || state.tabNames, defaultTabName)
onEdit?.(e, action, value)
} else {
onEdit?.(e, action)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 渲染 TabPane
/**
* 渲染 TabPane
* @returns TabPane
*/
function renderTabPane() {
return children.map(item => {
const { children, tab, disabled, style, ...res } = item.props
return (
<TabPane
tab={tabDom(item.key as string, item.props)}
key={item.key}
closable={activeKey === item.key}
{...res}>
{children}
</TabPane>
)
})
}
/**
* 渲染 tab 标签头
* @param currentKey tab key
* @param props EditableTabPaneProps
* @returns tab 标签头
*/
function tabDom(currentKey: string, props: GITabPaneProps) {
const { disabled, tab, statusIcon, style } = props
const active = activeKey === currentKey
let icon = null
let iconClass = null
if (statusIcon) {
switch (typeof statusIcon) {
case 'string':
icon = statusIconMap[statusIcon as string]
iconClass = statusIcon
break
case 'function':
icon = (statusIcon as Function)()
break
default:
icon = statusIcon
break
}
}
let result = (
<div
className={`ant-tabs-tab-btn-content ant-tabs-tab-btn-${iconClass}`}
style={{ width: tabPaneWidth ? `${tabPaneWidth}px` : 'auto', ...style }}>
{icon} <TextEllipsis title={tab} />
</div>
)
if (type === 'editable-card' && active && !disabled) {
result = (
<RealInput
value={tab}
ref={inputRef}
bordered={false}
style={{
width: tabPaneWidth ? `${tabPaneWidth}px` : 'auto'
}}
onKeyDown={e => {
// Prevent "space" blocking from rc-tabs: https://github.com/react-component/tabs/issues/309
e.stopPropagation()
}}
onChange={(value: string) => {
handleInputChange(value, currentKey)
}}
{...inputProps}
/>
)
}
return result
}
/**
* input change
* @param value value
* @param currentKey tab key
*/
function handleInputChange(value: string, currentKey: string) {
if (!value) {
value = generateName(tabNames || state.tabNames, defaultTabName)
onEdit?.(currentKey, 'rename', value)
} else {
if ((tabNames || state.tabNames).includes(value)) {
message.warning('名称已存在!')
updateState(draft => {
draft.tabKey = Date.now()
})
} else {
onEdit?.(currentKey, 'rename', value)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# 重命名逻辑
/**
* 生成新增节点名称
* @param namePool
* @param addName 新增的名字
* @returns
*/
export function generateName(namePool: string[], addName: string) {
if (!namePool || !namePool.length) return addName
const getName = addName => {
let result = ''
const repeatName = namePool.find(item => item === addName)
if (repeatName) {
if (/\(\d+\)$/.test(addName)) {
let addStr = addName.match(/\(([^)]+)\)/)[1]
result = addName.replace(/\(\d+\)$/, `(${Number(addStr) + 1})`)
} else {
result = addName + '(1)'
}
return getName(result)
} else {
return addName
}
}
return getName(addName)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 使用
const [state, updateState] = useImmer<State>({
tabs: [
{ tab: 'Tab 1', key: 'first', statusIcon: <WifiOutlined /> },
{ tab: 'Tab 2', key: 'second', style: { width: '50px', color: 'red' } },
{ tab: 'Tab 3', key: 'third', statusIcon: 'error' },
{ tab: 'Tab 4', key: 'fourth' }
],
contents: [1, 2, 3, 4]
})
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<GIEditableTabs activeKey="{state.activeKey}" onChange="{handleChange}" onEdit="{handleEdit}">
{state.tabs.map((item, index) => (
<div key="{item.key}" {...item}>
content: {state.contents[index] || '找不到啦~'}
</div>
))}
</GIEditableTabs>
1
2
3
4
5
6
7
2
3
4
5
6
7
编辑 (opens new window)
上次更新: 8/8/2022, 2:50:50 AM
- 01
- 搭配 Jenkins 实现自动化打包微前端多个项目09-15
- 02
- 自动化打包微前端多个项目09-15
- 03
- el-upload 直传阿里 oss 并且显示自带进度条和视频回显封面图06-05