antd-react 组件库工作以来的总结
- 我的博客
- http://wangxince.site/my-demo-markdown/
准备阶段
bug
# form
当与modal结合的时候 initValus数据会异步
需要使用form.setValues 设置数据
具体见 http://wangxince.site/my-demo-markdown/debug
组件
通用
button
<Button
# 基础属性
type={'default |primary | dashed | text | link'}
shape={'default|circle|round'}
size={'large|middle|small'}
ghost 按钮背景透明
danger 红色外观
disabled 禁用效果
block 宽度为父宽度
icon={<xxx/>}
# 其他属性
loading={boolean}
/>
# 组合按钮
<Dropdown.Button overlay={组件名} />
# bug 移除按钮文件之间的空格
import { Button, ConfigProvider } from 'antd';
<ConfigProvider autoInsertSpaceInButton = { false }>
<Button type="primary">
空格
</Button>
</ConfigProvider>
icon
# 默认图标
import { xxx } from '@ant-design/icons';
# 自定义图标
import Icon from '@ant-design/icons';
<Icon component={xxx} />
# 使用 iconfont
import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js',
});
<IconFont type="icon-tuichu" />
# 三类图标名字区别
xxxOutlined
Fuiled
TwoTone
# spin 旋转动画
# rotate={180} 旋转角度
# twoToneColor="#eb2f96" 双色图标设置颜色
排版
布局
分割线
<Divider >
dashed
plain
orientation={'left|right'}
type={'horizontal|vertical'}
/>
Grid
# row
<Row
gutter={16}
gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}
gutter={[16, 24]} 水平间距 垂直间距
justify='start|center|spacce-between....'
align='top|middle|bottom'
wrap={false}
/>
# col
<Col
span={6}
offset={6}
pull={18}
push={2}
/>
<Row>
# 手动指定栅格宽度
默认一格为 8.33333%
style="width: 12.499999995%;
flex: 0 0 12.499%;max-width: 12.499%;">
Layout
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
<Layout>
<Header>Header</Header>
<Content>Content</Content>
<Footer>Footer</Footer>
<Layout>
<Sider>Sider</Sider>
<Content>Content</Content>
</Layout>
</Layout>
Space
<Space
size='small|middle|large|number'
size={[8,16]}
align='start|end|center|baseline'
direction="vertical|horizontal"
wrap
split={<Divider type="vertical" />}
/>
导航
Affix 固钉
<Affix
offsetTop={10}
offsetBottom={10}
target={()=>dom}
onChange
/>
Breadcrumb
<Breadcrumb
separator=">"
>
<Breadcrumb.Item>
<a href="">Application Center</a>
</Breadcrumb.Item>
<Breadcrumb.Separator>:</Breadcrumb.Separator>
</Breadcrumb>
Dropdown
<Dropdown
overlay={ 封装的Menu组件 }
arrow
placement="bottomLeft"
trigger={['click'|'hover|contextMenu']}
>
<Button>这是按钮</Button>
</Dropdown>
Menu
<Menu
mode='horizontal|vertical|inline'
theme="dark|light"
inlineCollapsed={true | false}
inlineIndent={number} 缩进的宽度
defaultSelectedKeys={['MenuItemKeys']}
defaultOpenKeys={['SubMenuKeys']}
openKeys={['SubMenuKeys']}
onOpenChange={(openKeys)=>string[]}
>
<SubMenu
key='SubMenu'
icon
title
>
<ItemGroup
key=''
title=''
>
<MenuItem
key=''
icon
>
这是子菜单
</MenuItem>
</ItemGroup>
</SubMenu>
</Menu>
PageHeader
Pagination
# 逻辑
当前页面 一页放几条数据 总共多少数据 当前页码
pageSize total current
当点击的时候 将点击的页码切换
通过当前页面 发起网络请求 覆盖数据
#
<Pagination
pageSize={每页条数}
total={数据总数}
current={当前页数}
pageSizeOptions=[]
onChange={(page,pageSize)=> }
showTotal: (total) => `共 ${total} 条数据`,
defaultPageSize
defaultCurrent
/>
# 页码重置问题
https://stackoverflow.com/questions/69638994/how-to-reset-the-paginations-current-page-when-pagesize-changes-in-ant-design
Steps
<Steps
current={1}
size='small'
>
<Step
title
subTitle
description
status="finish|process|wait"
icon
>
</Step>
</Steps>
数据录入
AutoComplete
#
<AutoComplete
placeholder
style
options
value
onSelect
onSearch
onChange
showSearch={{ filter }}
/>
filter(inputValue, path) {
return path.some(option => option.name.indexOf(inputValue) > -1);
},
DatePicker
import moment from 'moment'
import { DatePicker } from 'antd';
const { RangePicker } = DatePicker;
import 'moment/locale/zh-cn';
import locale from 'antd/es/date-picker/locale/zh_CN';
<RangePicker
## 基础配置
disabled={[false,true]}
renderExtraFooter={() => 'extra footer'}
bordered={false}
size={'large|miaddle|small'}
locale={locale}
## 具体时间配置
value={""}
picker="year|quarter|month|week|date|time"
showTime={{
format: "hh[时]mm[分]ss[秒]",
defaultValue:[
moment('01:00:00', 'HH:mm:ss'),
moment('11:59:59', 'HH:mm:ss')
]
}}
搭配 showTime 使用
defaultPickerValue={moment("2021-05-01")}
format="YYYY/MM/DD HH:mm:ss"
ranges={{
Today: [moment(), moment()],
'当月':[
moment().startOf('month'),
moment().endOf('month')
]
}}
dateRender={current => {
const style = {};
if (current.date() === 1) {
style.border = '1px solid #1890ff';
style.borderRadius = '50%';
}
return (
<div className="ant-picker-cell-inner" style={style}>
{current.date()}
</div>
)
}}
disabledDate={disabledDate}
## 事件
onChange={(date:moment,dateString:string)=>}
onCalendarChange={val => setDates((val))}
/>
禁用年月日时分秒
## 禁用 年月日 时分秒 => 限制时间选择范围、
<RangePicker
defaultPickerValue={moment("2021-05-01")}
showTime={{format: "hh时mm分ss秒"}}
disabledDate={disabledDate}
disabledTime={disabledTime}
/>
const disabledDate = (currentDate) => {
const start = moment("2021-05-01")
const end = moment("2021-06-01")
return currentDate && currentDate < start
|| currentDate > end
return currentDate && !(currentDate < start
|| currentDate > end)
return current && current < moment().subtract(8, 'day');
}
function range(start, end) {const result = [];
for (let i = start; i < end; i++) { result.push(i);
}return result;}
const disabledTime = () => {
return {
disabledHours: () => range(0, 24).splice(4, 20),
disabledMinutes: () => range(30, 60),
disabledSeconds: () => [55, 56],
}
}
禁用7天范围
# 禁用7天范围
<RangePicker
defaultPickerValue={moment("2021-05-01")}
showTime={{format: "hh时mm分ss秒"}}
disabledDate={disabledDate}
onCalendarChange={val => setDates((val))}
/>
const [dates, setDates] = useState([]);
const disabledDate = (current) => {
if (!dates || dates.length === 0) {return false;}
const tooLate = dates[0] && current.diff(dates[0],'days') > 7;
const tooEarly=dates[1] && dates[1].diff(current, 'days') > 7;
return tooEarly || tooLate;
if (!dates || dates.length === 0) {return false;}
const start = moment("2021-05-01")
const end = moment("2021-06-01")
const tooLate=dates[0] && current.diff(dates[0], 'days') > 7;
const tooEarly=dates[1]&&(dates[1]).diff(current, 'days') > 7;
return current && (tooEarly || tooLate)
|| current < start || current > end
};
禁用年份
- 需要升级最新版
- “ant-design-vue”: “^1.7.8”,
<DatePicker format="YYYY" disabledDate={disabledYear} />
function disabledYear(current) {
return current.year() === 2021;
}
demo
import { useState } from 'react'
import { DatePicker } from 'antd';
import 'moment/locale/zh-cn';
import locale from 'antd/es/date-picker/locale/zh_CN';
import moment from 'moment'
const { RangePicker } = DatePicker;
const Test = () => {
const [dates, setDates] = useState([]);
const disabledDate = (current) => {
if (!dates || dates.length === 0) { return false; }
const start = moment("2021-05-01")
const end = moment("2021-06-01")
const tooLate = dates[0] && current.diff(dates[0], 'days') > 7;
const tooEarly = dates[1] && (dates[1]).diff(current, 'days') > 7;
return current && (tooEarly || tooLate)
|| current < start || current > end
};
function range(start, end) {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
} return result;
}
const disabledTime = () => {
return {
disabledHours: () => range(0, 24).splice(4, 20),
disabledMinutes: () => range(30, 60),
disabledSeconds: () => [55, 56],
}
}
return (
<>
<RangePicker
renderExtraFooter={() => '这是页脚'}
bordered={false}
size={'large'}
locale={locale}
showTime={{
format: "hh时mm分ss秒",
defaultValue: [
moment('01:00:00', 'HH:mm:ss'),
moment('11:59:59', 'HH:mm:ss')
]
}}
defaultPickerValue={moment("2021-05-01")}
format="YYYY/MM/DD HH:mm:ss"
ranges={{
Today: [moment(), moment()],
'当月': [
moment().startOf('month'),
moment().endOf('month')
]
}}
dateRender={current => {
const style = {};
if (current.date() === 1) {
style.border = '1px solid #1890ff';
style.borderRadius = '50%';
}
return (
<div className="ant-picker-cell-inner" style={style}>
{current.date()}
</div>
)
}}
disabledDate={disabledDate}
onCalendarChange={val => setDates((val))}
onChange={(date, dateString) => console.log(date, dateString)}
/>
</>
)
}
export default Test
选中多个日期
#
import React, { useState } from "react";
import { DatePicker, Button } from "antd";
import moment from "moment";
import styles from './index.less';
const MultipleDatePicker = () => {
const [selectedDate, setSelectedDate] = useState([])
const onValueChange = (date) => {
const newDate = moment(date).startOf("day").valueOf()
if (selectedDate.includes(newDate)) {
setSelectedDate([...selectedDate.filter(item => item !== newDate)])
} else {
setSelectedDate([...selectedDate, newDate])
}
};
const dateRender = (currentDate) => {
const isSelected = selectedDate.includes(moment(currentDate).startOf("day").valueOf())
let selectStyle = isSelected ?
{
position: 'relative',
zIndex: 2,
display: 'inlineBlock',
width: "24px",
height: "22px",
lineHeight: "22px",
backgroundColor: "#1890ff",
color: "#fff",
margin: "auto",
borderRadius: "2px",
transition: "background 0.3s, border 0.3s"
}
: {}
return (<div style={selectStyle} > {currentDate.date()} </div >)
}
return (
<>
<div className={styles.multipleDatePicker}>
<DatePicker
open
dateRender={dateRender}
onChange={onValueChange}
showToday={false}
value={""}
/>
<Button type='primary' onClick={() => console.log(selectedDate)}>确定</Button>
</div>
</>
)
}
export default MultipleDatePicker
#
.multipleDatePicker {
:global {
.ant-picker-input {
display: none !important;
}
.ant-picker {
border: none;
padding: 0;
}
}
}
# 配合 select
https://codesandbox.io/s/antd-reproduction-template-forked-1mos9?file=/index.js
禁用到某个确切时间
class
import moment from 'moment';
export default class ToolClass {
protected _range = (start: number, end: number) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
}
disabledDate = (startTime: moment.MomentInput, current: moment.MomentInput) => {
if (startTime == undefined) {
return false;
}
if (current) {
return current < moment(startTime).startOf('days')
}
return false
};
disabledTime = (StartFormMoment: moment.MomentInput, CurrentFormMoment: moment.MomentInput) => {
let startHours = moment(StartFormMoment).hour()
let startMinutes = moment(StartFormMoment).minute()
let startSeconds = moment(StartFormMoment).second()
let startDate = moment(StartFormMoment).date()
let currentHours = moment(CurrentFormMoment).hour()
let currentMinutes = moment(CurrentFormMoment).minute()
let currentDate = moment(CurrentFormMoment).date()
if (CurrentFormMoment == undefined) {
return {};
}
if (CurrentFormMoment && currentDate === startDate) {
if (currentHours === startHours) {
if (currentMinutes === startMinutes) {
return {
disabledHours: () => this._range(0, startHours),
disabledMinutes: (selectedHour: number) => selectedHour >= startHours ? this._range(0, startMinutes) : [],
disabledSeconds: (selectedHour: number, selectedMinute: number) =>
selectedHour >= startHours && selectedMinute >= startMinutes
? this._range(0, startSeconds)
: []
};
}
return {
disabledHours: () => this._range(0, startHours),
disabledMinutes: (selectedHour: any) => selectedHour >= startHours ? this._range(0, startMinutes) : [],
disabledSeconds: () => []
}
} else {
return {
disabledHours: () => this._range(0, startHours),
disabledMinutes: () => [],
disabledSeconds: () => []
};
}
}
return {}
}
}
demo1
import moment from 'moment'
import { Form, DatePicker } from 'antd'
import ToolClass from '../toolClass'
const Picker1 = () => {
const [form] = Form.useForm();
const transformFn = new ToolClass()
const disabledTime = (CurrentFormMoment: moment.MomentInput) => {
const StartTime = form.getFieldValue('startTime')
return transformFn.disabledTime(StartTime, CurrentFormMoment)
}
const disabledDate = (CurrentFormMoment: moment.MomentInput) => {
const StartTime = form.getFieldValue('startTime')
return transformFn.disabledDate(StartTime, CurrentFormMoment)
}
return (
<>
<h2>Picker1</h2>
<Form form={form}>
<Form.Item name='startTime'>
<DatePicker
style={{ minWidth: '100%' }}
showToday={false}
showTime
showNow={false}
onChange={() => form.setFieldsValue({ endTime: null })}
/>
</Form.Item>
<Form.Item name='endTime'>
<DatePicker
style={{ minWidth: '100%' }}
showToday={false}
showTime={{
hideDisabledOptions: true
}}
showNow={false}
disabledDate={disabledDate}
disabledTime={disabledTime}
/>
</Form.Item>
</Form>
</>
)
}
export default Picker1
demo2
import { useState } from 'react'
import moment from 'moment'
import { Form, DatePicker } from 'antd'
const { RangePicker } = DatePicker;
import ToolClass from '../toolClass'
const Picker2 = () => {
const [form] = Form.useForm();
const transformFn = new ToolClass()
const [disabledTimeDates1, setDisabledTimeDates1] = useState([]);
const [disabledTimeDates2, setDisabledTimeDates2] = useState([]);
const disabledRangeTime1 = (CurrentFormMoment: moment.MomentInput, type: string) => {
let StartTime1 = disabledTimeDates1?.[0];
if (type === 'end') {
return transformFn.disabledTime(StartTime1, CurrentFormMoment)
}
return {}
};
const disabledRangeTime2 = (CurrentFormMoment: moment.MomentInput, type: string) => {
let StartTime1 = form.getFieldValue('startTime')?.[0];
let endTime1 = disabledTimeDates2?.[0];
if (type === 'start') {
return transformFn.disabledTime(StartTime1, CurrentFormMoment)
}
if (type === 'end') {
return transformFn.disabledTime(endTime1, CurrentFormMoment)
}
return {}
};
const disabledRangeDate = (CurrentFormMoment: moment.MomentInput) => {
let StartTime = form.getFieldValue('startTime')?.[0];
return transformFn.disabledDate(StartTime, CurrentFormMoment);
}
return (
<>
<h2>Picker2</h2>
<Form form={form}>
<Form.Item name='startTime'>
<RangePicker
onChange={() => form.setFieldsValue({ endTime: null })}
onCalendarChange={(val: any) => setDisabledTimeDates1(val)}
disabledTime={disabledRangeTime1}
showTime={{
hideDisabledOptions: true
}}
/>
</Form.Item>
<Form.Item name='endTime'>
<RangePicker
onCalendarChange={(val: any) => setDisabledTimeDates2(val)}
disabledDate={disabledRangeDate}
disabledTime={disabledRangeTime2}
showTime={{
hideDisabledOptions: true
}}
/>
</Form.Item>
</Form>
</>
)
}
export default Picker2
动态点击时候的默认时间
当点击时间选择框会默认 选择当前的时间。但是禁用关系和它冲突的时候就需要定义defaultValue
defaultValue 只会渲染一次 因此只能写一个固定值值
如果想传入变量 需要对该组件进行重新渲染
解决方法: 直接利用 Form.Item shouldUpdate 方法进行 动态条件渲染
在Form.Item内部通过注入的 getFieldValue 方法来拿到最新的值
<Form.Item shouldUpdate={(pre, cru) => cru.xxx != pre.xxx }>
{({ getFieldValue }) => {
return (
<Form.Item name='xxx'>
<DatePicker
showTime={{
defaultValue: moment(
getFieldValue('xxx') == null
? moment().add(2, 'hours').format('HH:00:00')
: moment(getFieldValue('xxx')).add(1, 'hours').format('HH:00:00'),
'HH:mm:ss')
}}
/>
</Form.Item>
)
}
</Form.Item>
检查是否跨区间
const _checkIsBetweenSection = (
arr: ITimesArr[],
startTime: MomentType,
endTime: MomentType,
) => {
const minStartTime = moment(startTime).startOf('day');
const maxEndTime = moment(endTime).endOf('day');
const result = arr.find(
(item: any) =>
moment(item.startTime).isBetween(minStartTime, maxEndTime) ||
moment(item.endTime).isBetween(minStartTime, maxEndTime),
);
const isExist = typeof result == 'object' ? true : false;
return isExist;
};
const _disabledDateHasListScope = (
arr: ITimesArr[],
currentDate: MomentType = moment(),
) => {
function getIsExitSection() {
if (arr.length == 0) return false;
const result = arr.find(
(item: ITimesArr) =>
!(currentDate! <= item.startTime! || currentDate! >= item.endTime!),
);
const isExist = typeof result == 'object' ? true : false;
return isExist;
}
const isExist = getIsExitSection();
return currentDate && isExist;
};
onChange的时候
const handleClearBeforeDateAndCheckIsBetween = (
date: [MomentType, MomentType],
index: number,
) => {
if (!date) return false;
const values: ITableFormValues[] = form.getFieldValue('tableForm');
const isBetween = _checkIsBetweenSection(
getSelectDates(),
date[0],
date[1],
);
if (isBetween) {
values[index].date = [];
form.setFieldsValue({ tableForm: values });
return message.error('不能横跨已经禁用的时间段');
}
const newValues = values.map((e, i) =>
i > index ? { ...e, date: [] } : { ...e },
);
form.setFieldsValue({ tableForm: newValues });
};
const _removeEmptyObject = (arr: any[]) => {
return arr.filter((item) => {
return Object.keys(item).length > 0;
});
};
const getSelectDates = useCallback(() => {
if (!form?.getFieldValue('tableForm')) return [];
const values = form.getFieldValue('tableForm');
const timeArr = values.map((item: ITableFormValues) => {
if (!item?.date) return {};
const startTime = moment(item.date[0]).startOf('day');
const endTime = moment(item.date[1]).endOf('day');
return { startTime, endTime };
});
return _removeEmptyObject(timeArr);
}, [form.getFieldValue('tableForm')]);
const handleDisabledDate = (currentDate: MomentType) => {
if (currentDate! < moment().endOf('day')) return true;
return _disabledDateHasListScope(getSelectDates(), currentDate);
};
Form
组件结构
<Form>
<Form.Item name=''>
<Input/>
</Form.Item>
<span>辅助信息<span/>
<Form.Item>
<Form.Item name=''>
<InputNumber/>
</Form.Item>
</Form.Item>
<Form.Item
name=''
valuePropName="fileList"
getValueFromEvent={(e)=>{
if(Array.isArray(e)) return e
return e && e.filelist
}}
>
<Upload/>
</Form.Item>
</Form>
不显示label
label=" " colon={false}
Form
props
const [form] = Form.useForm();
<Form
# 常用配置
form={form}
labelCol={{ span:8,offset: 4 }}
wrapperCol={{ span:8 }}
size={"large"|"default"|"large"}
initialValues={{ Form.item.name: xxx }}
# 可能用到的配置
scrollToFirstError
preserve={false}
# 使用很少的配置
name='xxx'
layout={"horizontal"|"vertical"|"inline"}
requiredMark='optional|true|false'
validateMessages={validateMessages}
# 事件
onFinish={value=> {} }
onFinishFailed={errorValue=> {} }
onValuesChange={(changeValue,allValues)=> {} }
>
<Form>
# 其他 label右对齐
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 7 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 17 },
},
};
{...formItemLayout}
#
labelCol: {
flex: "0 0 100px"
},
API
form.setFieldsValue({
form.item.name : 'xxx',
})
form.resetFields();
form.getFieldValue()
form.getFieldValue(form.item.name)
form.getFieldInstance('formItemName')
form.validateFields()
.then(values=>{
success
})
.catch(err=>console.log(err))
Form.Item
静态表单
<Form.Item
# 常用配置
colon={false}
label={'username'}
name={'username'}
required
valuePropName="fileList"
getValueFromEvent={(e)=>{
if(Array.isArray(e)) return e
return e && e.filelist
}}
rules={[
{
required: true,
message: '',
pattern: /^[3]{1}/,
max: 5,
min: 3,
validateTrigger: "onChange",
warningOnly: true,
validator: (usernameFormRules,value)=>{
if (value.length > 5) {
return Promise.reject(new Error('不能大于5个字符'))
}
return Promise.resolve("格式正确")
}
}
]}
# 可能用到的配置
noStyle
help={ReactNode}
hasFeedback
validateStatus={'success'|'warning'|'eror'|'validating'}
dependencies={['Form.Item.name']}
# 使用很少的配置
tooltip={{title:'xxx',icon:</> }}
>
<Form.Item>
动态表单
<Form.Item
shouldUpdate={ (prevValues,curValues)=>
prevValues.表单name !== curValues.表单name }
>
{ ({getFieldValue}) =>
getFieldValue('Form.Item.name') === 'zhangsan' ? (
<Form.Item .../>
) : null
}
</Form.Item>
Form.List
<Form form={form}>
<Form.List name='zs' initialValue={[1, 2, 3]}>
{
(fields, { remove, add, move }) => (
<>
{
fields.map((field, index) => (
<Form.Item key={field.key} noStyle>
<Form.Item
{...field}
name={[field.name,'名字后缀']}
fieldKey={[field.fieldKey,'名字后缀']}
>
<Input />
</Form.Item>
{
fields.length > 1 ?
<Button onClick={() => remove(field.name)}> 删除 </Button>
: null
}
<Button onClick={() => move(index, index - 1)}>
上移
</Button>
</Form.Item>
))
}
<Form.Item >
<Button onClick={() => add(表单的值,位置)}>添加</Button>
</Form.Item>
</>
)
}
</Form.List>
</Form>
Form.Provider
<Form.Provider
onFormChange
onFormFinish={(name,{values,forms}) => {
if (name === 'form1') {
const {baseForm} = forms
const 变量 = baseForm.getFieldValue('变量') || []
baseForm.setFieldsValue({变量:[...变量,values]})
}
}}
>
<Form name='baseForm'>
<Form name="form1">...</Form>
<Form name="form2">...</Form>
</Form>
</Form.Provider>
基本校验demo
import React from 'react'
import { Form, Input, Button, Checkbox } from 'antd';
const Test = () => {
const [form] = Form.useForm();
const myValidator = (usernameFormRules, value) => {
if (value.length > 5) {
return Promise.reject(new Error('不能大于5个字符'))
}
return Promise.resolve("格式正确")
}
const formRules = {
username: [
{
required: true,
message: "用户名是必填项",
pattern: /^[3]{1}/
}
],
password: [
{
required: true,
message: "密码"
}
]
}
function successFormSubmit (value) {
console.log(value);
}
function errorFormSubmit (errorValue) {
console.log(errorValue);
}
const ResetForm = () => {
console.log(form.getFieldsValue());
form.setFieldsValue({ username: "" })
form.resetFields()
}
return (
<Form form={form} wrapperCol={{ span: 5 }} onFinish={successFormSubmit} onFinishFailed={errorFormSubmit} >
<Form.Item label='username' name='username' rules={formRules.username} >
<Input />
</Form.Item>
<Form.Item label='password' name='password' rules={formRules.password}>
<Input.Password />
</Form.Item>
<Form.Item>
<Button htmlType='submit'>提交</Button>
<Button htmlType="button" onClick={ResetForm}>重置</Button>
</Form.Item>
</Form>
);
}
export default Test
动态添加demo
import React from 'react'
import { Form, Button, Select } from 'antd';
const { Option } = Select
const Test = () => {
const [form] = Form.useForm();
let sights = {
beijing: ['长城', '故宫'],
hangzhou: ['西湖', '雷峰塔']
}
return (
<Form form={form} >
<Form.Item label="地点" name='place'>
<Select onChange={() => form.setFieldsValue({ att: [] })}>
<Option value='beijing'>北京</Option>
<Option value='hangzhou'>杭州</Option>
</Select>
</Form.Item>
<Form.List name='att' >
{
(fields, { remove, add, move }) => (
<>
{
fields.map((field) => (
<Form.Item key={field.key} >
<Form.Item
noStyle
shouldUpdate={(pre, cur) => pre.place !== cur.place || pre.sights !== cur.place}
>
<Form.Item
{...field}
label="风景"
name={[field.name, 'sights']}
fieldKey={[field.fieldKey, 'sights']}
>
<Select disabled={!form.getFieldValue('place')} style={{ width: 130 }}>
{(sights[form.getFieldValue('place')] || []).map(item => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</Form.Item>
<Button onClick={() => remove(field.name)} >删除</Button>
</Form.Item>
</Form.Item>
))
}
<Button type="dashed" onClick={() => add()} block > 添加</Button>
</>
)
}
</Form.List>
<Button type="primary" htmlType="submit" onClick={() => console.log(form.getFieldValue())}>
Submit
</Button>
</Form >
);
}
export default Test
一行多个表单
<Form.Item label="名称一" style={{ marginBottom: 0 }}>
<Form.Item
style={{ display: 'inline-flex',
width: 'calc(45% - 4px)' }}
>
<Input />
</Form.Item>
<Form.Item
style={{ display: 'inline-flex',
width: 'calc(55% - 4px)', marginLeft: '8px' }}
name="name2"
>
<Input />
</Form.Item>
<Form.Item label="InputNumber表单" style={{ height: 32 }}>
<Space align="baseline" size="large">
<Space align="baseline">
<Form.Item name='inputNumber1' >
<InputNumber
className={styles.modalInputNumber}
min={0}
max={100}
/>
</Form.Item>
<span>%</span>
</Space>
</Space>
</Form.Item>
</Form.Item>
#
4. 直接通过 Row Col 控制布局
<Row>
<Col span={12}>
<Form.Item />
</Col>
<Col span={12}>
<Form.Item />
</Col>
</Row>
5. labelCol wrapperCol 可以指定px宽度
labelCol={{flex:'0 0 100px'}}
wrapperCol={{flex:'0 0 100px'}}
style={{ display: 'inline-flex', width: 'calc(45% - 4px)' }}
# 使用栅格的
offset push pull
拖动排序
npm install react-sortable-hoc --save
npm i array-move
# SortableTable.jsx
import { Table } from 'antd';
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
import { arrayMoveImmutable } from 'array-move';
import React, { useState, useEffect } from 'react';
const SortableTable = (props) => {
const { columns = [], data = [], DragHandle, ...restProps } = props;
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
props.getNewDataSource(dataSource);
}, [dataSource]);
useEffect(() => {
setDataSource(data);
}, []);
const SortableItem = sortableElement((props) => <tr {...props} />);
const SortableContainer = sortableContainer((props) => <tbody {...props} />);
const DraggableContainer = (props) => {
const onSortEnd = ({ oldIndex, newIndex }) => {
if (oldIndex !== newIndex) {
const newData = arrayMoveImmutable(
[].concat(dataSource),
oldIndex,
newIndex,
).filter((el) => !!el);
setDataSource(newData);
}
};
return (
<SortableContainer
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={onSortEnd}
{...props}
/>
);
};
const DraggableBodyRow = ({ className, style, ...restProps }) => {
const index = dataSource.findIndex(
(x) => x.index === restProps['data-row-key'],
);
return <SortableItem index={index} {...restProps} />;
};
return (
<>
<Table
pagination={false}
dataSource={dataSource}
columns={columns}
rowKey="index"
components={{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
},
}}
{...restProps}
/>
</>
);
};
export default SortableTable;
#
import SortableTable from './SortableTable'
import { sortableHandle } from 'react-sortable-hoc';
import { MenuOutlined } from '@ant-design/icons';
import React from 'react'
const Demo = () => {
const DragHandle = sortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />);
const [reduceList, setReduceList] = React.useState([]);
let columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Sort',
dataIndex: 'sort',
width: 30,
className: 'drag-visible',
render: () => <DragHandle />,
},
];
const data = [
{
key: '1',
name: "全站-新用户满减活动",
code: 1,
index: 0
},
{
key: '2',
name: "全站-所有用户满折活动",
code: 2,
index: 1
},
{
key: '3',
name: "G站上新-站点订单立减活动",
code: 3,
index: 2
},
{
key: '4',
name: "G站上新-站点订单立减活动",
code: 4,
index: 3
},
];
return (
<>
<SortableTable
data={data}
columns={columns}
DragHandle={DragHandle}
getNewDataSource={(list) => setReduceList(list)}
showHeader={false}
/>
</>
)
}
export default Demo
upload
#
initialValue
#
const fileList = [
{
uid: '-1',
name: 'image.png',
status: 'done',
url: 'https://download.ococmall.com/cheshi-test/site-photo/2A970169120E486A9AD60735B5F9ACD6.jpeg',
},
]
<Form.Item
...
valuePropName="fileList"
getValueFromEvent={normFile}
initialValue={fileList}
>
<Upload
onPreview={onPreview}
beforeUpload={(file) => {
return false;
}}
name="avatar"
listType="picture-card"
maxCount={1}
accept='image/*'
>
上传图片
</Upload>
</Form.Item>
#
const onPreview = async file => {
let src = file.url;
if (!src) {
src = await new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () => resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow.document.write(image.outerHTML);
};
只获取变化的表单
<Form onFieldsChange={passChangeFormItems} />
const [changeFormItems, setChangeFormItems] = useState({});
const passChangeFormItems = (changedFields, allFields) => {
let newChangeFieldsName = changedFields?.[0]?.name?.[0];
let newChangeValue = changedFields?.[0]?.value;
setChangeFormItems((preval) => {
let obj = { ...preval, [newChangeFieldsName]: newChangeValue };
return obj;
});
};
表单组件
Input
# Input
readOnly
<input
allowClear
prefix
suffix
bordered
addonBefore={ReactNode}
addonAfter={ReactNode}
value={inputValue}
type="text"
placeholder
defaultValue=''
maxLength={20}
disabled
style
size
onChange
onPressEnter
/>
# Input.Password
<Input.Password
iconRender={(visible)=> visible?隐藏图标:显示图标 }
visibilityToggle={false}
/>
# Input.TextArea
<Input.TextArea
autoSize={boolean | {minRows:2,maxRows:6}}
showCount
value
onPressEnter
onResize
allowClear
bordered
defaultValue
maxLength
/>
# Input.Search
<Input.Search
enterButton={boolean|ReactNode}
loading
onSearch
/>
# Input.Group
<Input.Group
compact={boolean}
size="large|default|small"
/>
readOnly 属性回来回显
Inputnumber
# InputNumber
<InputNumber
defaultValue
min
max
size
disabled
addonBefore
addonAfter
bordered
keyboard={boolean}
step="0.00000000000001"
stringMode
precision={1}
formatter={(value)=>}
parser={(value)=>}
onChange
/>
Checkbox
const CheckboxGroup = Checkbox.Group;
<Checkbox
disabled
indeterminate
defaultChecked
onChange
>
全选
</Checkbox>
<CheckboxGroup
option={['a','b','c']}
value={['a', 'b', 'c']}
onChange={(checkArr)=>}
diabled
/>
# 全选demo
import { useState } from 'react'
import { Checkbox } from 'antd';
const CheckboxGroup = Checkbox.Group;
const Test = () => {
const option = ['Apple', 'Pear', 'Orange']
const [state, setState] = useState(false)
const [data, setData] = useState([])
const checkAll = () => {
setState(!state)
state ? setData([]) : setData(option)
}
return (
<>
<Checkbox
indeterminate={state}
onChange={checkAll}
>
全选
</Checkbox>
<CheckboxGroup
onChange={(e) => setData(e)}
options={option}
value={data}
/>
</>
)
}
Select
#
<Select
defaultValue='string|['a','b']'
style
disabled
loading
allowClear
showSearch
optionFilterProp={children|label}
filterOption={(input,option)=>option.children}
mode="multiple"
optionLabelProp={Option.name | lable ...}
mode="tags"
labelInValue
tagRender={(props)=>}
notFoundContent={ReactNode}
defaultActiveFirstOption={boolean}
labelInValue
onDropdownVisibleChange={(open)=>open&&Ajax}
onChange
onSearch
>
<Option value='' />
</Select>
# 分组
<Select
>
<OptGroup label='xxx'>
<Option/>
</OptGroup>
</Select>
# 点击禁用的按钮
getPopupContainer dom渲染到当前页面
const handlebindDisabledElementsClick = () => {
console.Log(!form.getFieldValue('custName' ) ? .1abel)
if(!form.getFieldValue('custName')?.value) return false
const container = document.getElementById('salesMgrName_Container')
const disabledElements = [...container.getElementsByClassName('ant-select-item-option-disabled')] if(disabledElements.length != 0 && disabledElements[0] != undefined) {
disabledElements.forEach(item => item.onclick = function () { message.error('未完成经办人审批')}) }
}
Radio
# Radio.Button
<Radio.Group
options=[
{label:"",value:"",disabled:true}
]
optionType="button"
buttonStyle="solid"
name
onChange
>
<Radio.Button
value=''
defaultChecked={boolean}
disabled
checked
/>
</Radio.Group>
# 垂直的单选按钮
<Radio.Group>
<Space direction="vertical" />
<Radio/>
</Radio.Group>
包裹每一个子项就行
Cascader
<Cascader
option={数据}
fieldNames={{
label: 'name', value: 'code', children: 'items'
}}
onChange={onChange}
placeholder
size="large|small"
在数据里加入 diabled:true
defaultValue={['一级','二级','三级']}
expandTrigger="hover"
/>
#加载省市区
:fieldNames="item.fieldNames"
fieldNames: {
label: 'name', value: 'code', children: 'cascadeDataVoList'
},
:showSearch='{ filter }'
filter: (inputValue, path) => {
return path.some(option => option.name.indexOf(inputValue) > -1);
},
# 异步
# 回显
form.setFieldsValue([1,2,3])
通过displayRender自定义显示
表单确认的时候 typeof判断类型 取值
Upload
- 如果使用它的上传 必须受控
- https://github.com/ant-design/ant-design/issues/2423#issuecomment-233523579
- 组件设置唯一的key
逻辑
# 逻辑
1. 模拟点击事件
2. change 事件 input type=file 元素的 files[0] 属性
3. 生成内存地址 URL.creareObjectURL(files[0]) 让img.src = xxx
4. 上传图片
A => 二进制形式 以multpart/form-data
B => 转换成 Base64位字符串 传给服务器
5. 图片裁切
前端裁切 => 将裁切区域生成单独的图片 获取图片的临时路径
后端裁切 => 获取裁切框相对于原图的坐标 将裁切区域坐标以及原图临时文件传给后端进行图片裁切
props
# upload
import { Upload } from 'antd';
<Upload
## 基础属性
action = "htttp://xxx"
headers={{ authorization: 'xxx' }}
maxCount={1}
multiple
accept='.xls, .xlsx'
showUploadList= { false }
directory
listType='text | picture | picture-card'
showUploadList = {
showDownloadIcon: true,
downloadIcon: 'download ',
showRemoveIcon: true,
removeIcon: <自定义Icon onClick={e => />
}
progress:{
strokeColor: {
'0%': '#108ee9',
'100%': '#87d068',
},
strokeWidth: 3,
format: percent =>
`${parseFloat(percent.toFixed(2))}%`,
}
defaultFileList:[
{
uid:"1",
name:"",
status:"done",
response:"Server Error 500",
url:"xxx"
}
]
## 事件
- onChange={(info)=>
info.file.status == 'uploading |done|error'}
- beforeUpload(file){
file.type == "images/jpeg"
file.size / 1024 / 1024 < 2
return file.type === 'image/png' ? true
: Upload.LIST_IGNORE; }
onPreview={(file)=>{}}
onRemove(file)
onDrop
onDownload
/>
拖拽 上传与排序
# 拖拽上传
const { Dragger } = Upload;
<Dragger
onDrop(e)=>{}
/>
# 上传列表拖拽排序
react-dnd
上传前裁切图片
# 上传前裁切图片
yarn add antd-img-crop
import 'antd/es/modal/style';
import 'antd/es/slider/style';
import ImgCrop from 'antd-img-crop';
<ImgCrop
rotate
grid
shape='rect | round'
quality={0-1}
modalTitle='编辑图片'
modalWidth={520}
modalOk='确定'
modalCancel='取消'
onModalOK
onModalCancel
>
<Upload>+ Add image</Upload>
</ImgCrop>
const onPreview = async file => {
let src = file.url;
if (!src) {
src = await new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () =>
resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow.document.write(image.outerHTML);
};
# 点击模态框依然能点击及其他区域
point-events: none;
getContainer 挂载到当前页面
图片加水印
# 图片加水印
beforupload(file)=>{
return new Promise(resolve)=>{
const reader = new FileReader();
reader.readAsDataURL(file)
reader.onload=()=>{
const img = document.createElement('img');
img.src = reader.result;
img.onload=()=>{
const canvas =
document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
ctx.fillStyle = 'red';
ctx.textBaseline = 'middle';
ctx.font = '33px Arial';
ctx.fillText('Ant Design', 20, 20);
canvas.toBlob(resolve);
}
}
}
}
转换bas64
# 转换 base 64
const getBase64 = (img, cb) => {
const reader = new FileReader();
reader.addEventListener("load", () => cb(reader.result));
reader.readAsDataURL(img);
};
getBase64(info.file.originFileObj,(imgUrl)=>{
})
excel demo
const normFile = (e) => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
<Form.Item
valuePropName="fileList"
label="选择文件"
name="uploadFile"
required
getValueFromEvent={normFile}
>
<Upload
listType='text'
action="/dpm/customerProblemImport/importExcel"
accept=".xls, .xlsx"
maxCount={1}
beforeUpload={(file) => {
return true;
}}
showUploadList={{
showRemoveIcon: true,
removeIcon: (
<CloseOutlined
onClick={(e) => console.log(e, 'custom removeIcon event')}
/>
),
}}
progress={{
strokeColor: {
'0%': '#229DED',
'100%': '#229DED',
},
strokeWidth: 2,
format: (percent) =>
`${parseFloat(percent.toFixed(0))}%`,
}}
onChange={(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
message.success(
`${info.file.name} file uploaded successfully`,
);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}}
/>
</Form.Item>
#
form.validateFields().then(()=>{
const formData = new FormData()
formData.append('file', file[0].originFileObj);
dispatch({
type: 'total/multiSheetUpload',
payload: formData,
});
progress样式
.ant-progress-inner {
margin-top: 20px;
width: 235px;
}
.ant-progress-text {
display: none !important;
}
.ant-upload-list-item-info {
width: 270px;
.ant-upload-list-item-card-actions-btn.ant-btn-sm {
height: 28px !important;
line-height: 28px !important;
}
}
.ant-popover-inner {
width: 200px;
}
.ant-upload-list-item .anticon-close:hover {
color: rgba(0, 0, 0, 0.45);
}
.ant-upload-list-item-info::before {
background-color: #fff;
}
.ant-upload-list-item:hover .ant-upload-list-item-info {
background: #fff;
}
.ant-btn-text:hover,
.ant-btn-text:focus {
background-color: #fff;
}
Mentions
Rate
Slider
Switch
TimePicker
Transfer
TreeSelect
数据展示
Avatar
#
<Avatar
size="large|small"
shape="circle|square"
gap=''
src='string|ReactNode'
icon='ReactNode'
/>
Badge
#
<Badge
color=""
count={5}
overflowCount={10}
dot
status="success|error| default| processing| warning"
text=''
/>
# 绸带
<Badge.Ribbon text="" color='' placement="start |end ">
<Card title="Pushes open the window" size="small" />
</Badge.Ribbon>
Calendar
Card
<Card
bordered={false}
extra={ <a/> }
title
style={{}}
bodyStyle={{}}
headStyle={{}}
/>
Carousel
Collapse
Comment
Descriptions
#
<Descriptions
bordered
column={2}
style
>
<Descriptions.Item
label
span
contentStyle
/>
<Descriptions/>
Empty
<Empty
image={Empty.PRESENTED_IMAGE_DEFAULT}
/>
Image
<Image
width
src
placeholder
preview
src='error'
fallback=''
>
</Image>
#
<Image.PreviewGroup preview>
<Image/>
</>
List
Popover
Statistic
<Statistic
title=""
value={112893}
valueStyle={{}}
precision={2}
prefix={<Icon />}
suffix={元素节点}
/>
# 倒计时组件
const { Countdown } = Statistic;
let deadline=Date.now() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30;
<Countdown title="Countdown" value={deadline} onFinish={onFinish} format="D 天 HH:mm:ss:SSS"/>
Table
Table
<Table
# 样式配置
bordered={true}
size={'default'|'middle'|'small'}
tableLayout={'auto'|'fixed'}
rowClassName={(record,index)=>index%2===0?'light':'dark'}
showHeader={boolean}
title={(当页数据)=>'title'}
footer={(当页数据)=>'title'}
summary={(当页数据)=>ReactNode}
# 基本配置
dataSource={dataSource}
columns={columns}
loading
pagination:{{
total,
current,
pageSize,
pageSizeOptions:[10, 20, 50, 100]
showTotal: (total) => `共 ${total} 条数据`,
onChange=((currentPage)=>fetch)
}}
onChange={(pagination,filters,sorter,extra)}
# 可能会用到的配置
rowKey={record => record.uid}
locale={}
sortDirections=['ascend', 'descend', 'ascend']
components={{
body:{ cell:ReactNode,row:ReactNode }
}}
expandable={{
expandedRowRender:(record)=> ReactNode,
rowExpandable:(record)=> record.name !== 'xxx'
}}
onRow={record => {
return {
onClick: event => {},
onDoubleClick: event => {},
onContextMenu: event => {},
onMouseEnter: event => {},
onMouseLeave: event => {},
};
}}
onHeaderRow={(columns, index) => {
return {
onClick: (e) => {},
};
}}
rowSelection={{
type: 'radio'| 'checkbox' ,
selections: [
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
{key:'',text:"",onSelect:(changeRowKeys)=>xxx}
]
fixed: true,
columnWidth:string | number,
hideSelectAll:true,
defaultSelectedRowKeys: string[] | number[],
getCheckboxProps:(record)=>{
disabled: record.name === 'Disabled User',
name: record.name,
}
renderCell:(checked,record,index,originNode),
selectedRowKeys:[],
onChange:(selectedRowKeys,selectedRows)=>{
setData(selectedRowKeys)
},
}}
# scroll
scroll={{ x: 1800,y:240 }}
/>
columns
- 纵向单元格 https://segmentfault.com/a/1190000021124610
const columns = [
{
# 样式配置
align:'left center right',
fixed:'left'|'right'|true,
width:100,
className:"",
colSpan:number,
editable: true,
ellipsis: boolean | {showTitle:false}
# 基本配置
title:"",
dataIndex:"",
key:"",
onCell: (record, rowIndex)
=> rowIndex % 2 === 0 ? rowColorOdd : rowColorEven
render:(text,record,index)=>{
return {
children: ReactNode,
props:{
colSpan:3
}
}
},
# filter
filters:[
{text:"",value:"",children:[{...}]}
]
filteredValue: string[],
onFilter:(value,record)=>{record.name.includes(value)}
defaultFilteredValue: string[]
filterMode: 'tree' | 'menu',
filterSearch: true
filterIcon:(filtered)=> <Icon />
filterMultiple: true
filterDropdown:()=>ReactNode
filterDropdownVisible={boolean}
onFilterDropdownVisibleChange=(visible)=>{}
filtered={true}
# sort
defaultSortOrder: 'descend' | 'ascend',
sortDirections:['descend','ascend'],
sortOrder: boolean | 'descend' | 'ascend',
sorter:(a, b) => a.key - b.key
}
# 表头分组
children:[
...
]
]
# 筛选时间
sorter: (a,b) => {
const time1 = moment(a.changeTime, 'YYYY.MM.dd HH:mm:ss')
const time2 = moment(a.changeTime, 'YYYY.MM.dd HH:mm:ss')
}
return moment(time2).isAfter(moment(time1)) ? -1 : 1
},
sortDirections: ['descend', 'ascend']
modal
*fetch({payload:{page=1,pageSize=10}},{call,put}){
const{data:{records=[],total}}=
yield call(service.getPage,{pageNumber:page,pageSize })
yield put({
type: 'save',
payload: {
tabelList: records,
page,
pageSize,
total,
},
});
},
*upload({ payload: excelData }, { call, put }) {
yield call(service.importExcel, excelData);
message.success('上传成功!');
yield put({
type: 'fetch',
payload: {},
});
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/backStageMaintain/feedback') {
dispatch({
type: 'fetch',
payload: {}
});
}
});
},
},
<Table
columns={columns}
dataSource={tabelList}
rowKey="id"
pagination={{
total,
current: page,
pageSize,
onChange: (page, pageSize) => {
dispatch({
type: 'feedback/fetch',
payload: { page, pageSize },
});
},
showTotal: (total) => `总共${total}条数据`,
}}
/>
受控的sort
#
<div
className={styles['dropDown_container_zzz']}
ref={refContainer}
/>
<Table
getPopupContainer={() => refContainer?.current}
onChange={handleTableChange}
/>
#
{
key: 'availableOrderRange',
filters: [
{ text: '全部站点', value: 0 },
{ text: '指定站点', value: 1 },
],
filteredValue: filterValueKey?.availableOrderRange,
filterMultiple: false,
},
const [filterValueKey, setFilterValueKey] = useState(null);
const refContainer = useRef(null);
const handleTableChange = (pagination, sortFields) => {
setFilterValueKey(sortFields);
}
#
.dropDown_container_zzz {
:global {
.ant-table-filter-dropdown-btns {
display: none;
}
}
}
setFilterValueKey({
activeStatus: null,
...
});
#
function transformObj_ArrayToValue(obj) {
let newObj = {};
Object.keys(obj).forEach((key) => {
if (obj[key] !== null) {
newObj[key] = obj[key].length > 0 ? obj[key][0] : null;
}
});
return newObj;
}
checkbox
<Table
rowKey={(record) => record.id}
rowSelection={{
type: 'checkbox',
preserveSelectedRowKeys: true,
selectedRowKeys: tableSelectKey,
onChange: (selectedRowKeys, selectedRows) => {
setTableSelectKey(selectedRowKeys);
},
}}
pagination={{
total,
current: page,
pageSize,
showTotal: (total) => `总共${total}条数据`,
}}
onChange={handleTableChange}
/>
#
const [tableSelectKey, setTableSelectKey] = useState([]);
此时 tableSelectKey 中存储的就是 rowKey 指定的字段
合并单元格
const sharedOnCell = (_, index) => {
if (index === 4) {
return { colSpan: 0 };
}
};
const columns = [
{
title: 'Name',
dataIndex: 'name',
render: (text, row, index) => <a>{text}</a>,
onCell: (_, index) => ({
colSpan: index < 4 ? 1 : 3,
}),
},
{
title: 'Age',
dataIndex: 'age',
onCell: sharedOnCell
},
{
title: 'Home phone',
colSpan: 2,
dataIndex: 'tel',
onCell: sharedOnCell
},
]
Tabs
#
<Tabs
activeKey
defaultActiveKey
centered
size
tabPosition
type="line|card|editable-card"
onChange
>
<TabPane
tab={ReactNode}
key
disabled
>
</TabPane>
</Tabs>
Tag
<Tag
closable
color="success|processing|error|warning|default"
visible
icon={ReactNode}
onClose
/>
Timeline
#
<Timeline>
<Timeline.Item
color=''
/>
</Tooltip>
Tooltip
<Tooltip
title="提示文字"
placement="topLeft"
arrowPointAtCenter
color
>
...
</Tooltip>
<span className={styles.left} style={remark && remark.length > 13 ? {} : remark.length == 13 ? { marginRight: 10 } : { marginRight: (13 - remark.length) * 25 }}>
{remark && remark.length > 13 ?
(< Tooltip title={remark}>
<span>{remark.slice(0, 13) + '...' ?? '--'}</span>
</Tooltip>
) : remark ?? '--'}
</span>
Tree
<Tree
treeData={}
在数据里面加入 disabled:true
在数据里面加入 disableCheckbox:true
在数据里面加入 icon
checkable
defaultExpandAll
onCheck={(keys,e)=>}
checkedKeys
/>
反馈
Alert
import { Alert } from 'antd';
<Alert
message=""
type="success info warning error"
description
showIcon
closable
onClose={()=>}
/>
Drawer
#
import { Drawer } from 'antd';
<Drawer
title="Basic Drawer"
placement="right top bottom left"
width={300}
height
mask={boolean}
closable={boolean}
visible={boolean}
footer
onClose={()=>}
>
Message
message.info("内容",time);
.success({content:"文本内容",className:"",style:{}},time)
.error
.warning
.loading()
Modal
<Modal
title=''
centered
style:{{top:0}}
maskClosable
visible={boolean}
width={1000}
okText="确认"
cancelText="取消"
okButtonProps={{ disabled: true }}
cancelButtonProps={{ disabled: true }}
onOk
onCancel
footer={null}
/>
#
Modal.success({
title:"",
content: '可以放元素标签 或string',
});
modal.destroy();
# modal 确定按钮变成表单的确定
form.validateFields()
.then((values)=>{})
.catch((err)=>)
Notification
# 外观样式
notification["success info warning error"]({
message: "",
description:""
onClick:()=>{
},
icon:ReactNode,
className:"",
stule:{},
duration: 0,
placement:'bottomRight bottomLeft topRight topLeft',
btn:ReactNode(notification.close(key))
key:`open${Date.now()}`
})
# 位置
placement:"bottomRight"
bottomLeft
topRight
topLeft
# 增加 dom
btn
key
Popconfirm
<Popconfirm
title:"",
onText='Yes'
cancelText="No"
onConfirm
onCancel
/>
Progress
<Progress
size
title
percent={}
status="active exception"
showInfo={false}
format={ (percent)=> 自定义提示文本 }
strokeColor={{ form:'颜色',to:"颜色" }}
success={{ percent: 30 }}
type='circle dashboard'
gapDegree={30}
strokeLinecap="square"
steps={3}
/>
Result
<Result
status="success",
title:"",
subTitle:"",
extra={[ DOM元素 ]}
/>
Skeleton
<Skeleton
avatar
paragraph={{ rows: 4 }}
active
/>
Spin
<Spin
tip="Loading..."
indicator={ <icon/> } />
size
spinning={true}
/>
其他
Anchor
import { Anchor } from 'antd';
const { Link } = Anchor;
<Link href="#">
BackTop
import { BackTop } from 'antd';
<BackTop />
# 自定义样式 最大40px*40px
<BackTop >
<div style={xxx} />
</BackTop>
ConfigProvider
# 全局配置
import { ConfigProvider } from 'antd';
<ConfigProvider direction="rtl">
<App />
</ConfigProvider>