一: 表单验证:
相较vue中表单验证,antd中对输入框的验证全部放到了Form.Item中。同时触发的事件诸如onBlur,onChangeForm.Item中,(通过validateTrigger来指定)
2 对于自定义校验validator函数。它会在每次事件触发的时候执行,这样就会出现如果设置了
rules={
[
{ required: true,
message: '手机号不能为空'
},
{
required: true,
validator:(rule, value) => {
if(value){
let reg=/^1(3|4|5|6|7|8|9)\d{9}$/g;
if(reg.test(value)) {
return Promise.resolve()
}else{
return Promise.reject('手机号格式错误')
}
}else{
return Promise.reject('请输入正确的手机号')
}
}
}
]
}
两个验证规则。原本的目的是在没有输入任何值时只提示“手机号不能为空”,结果同时出现“请输入正确的手机号”“手机号格式错误”两个报错。也就是validator优先级最高。所以解决办法有两种:
(1)所有的验证放在valiator中包括为空的情况
(2)不要valiator函数,而是把为空和验证规则拆开,如下:
<Form.Item
name="account"
validateTrigger="onBlur"
rules={
[
{ required: true,
message: '手机号不能为空'
},
{
pattern:/^1(3|4|5|6|7|8|9)\d{9}$/g,
message:"请输入正确的手机号"
},
]
}
>
<Input size="large" max={11} value={account} allowClear prefix={<IconFont type="icon-zhanghao" />}/>
</Form.Item>
2:设置表单默认值问题:
“是否继承属性设置"这个选项的默认值根据第一个红色框框的"类目位置"来决定。它是一个数组。如果数组长度>1,则“否继承属性设置”默认勾选为“自定义设置”,否则则勾选伟继承上级属性设置“。因为有这层逻辑,所以一开始打算在useEffect里动态设置其defaultValue。开始的代码如下:
function CreateCatagory(props){
const attrSettingDialogRef=useRef()
const [inheritPerporty,setInheritPerporty]=useState(1)
const [setttingVal,setSetttingVal]=useState(1)
const [form] = Form.useForm()
const formRef = React.createRef();
useEffect(()=>{
let val= catagoryPositions.length>1?2:1;
setInheritPerporty(val)
},[])
<Form
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
form={form}
ref={formRef}
initialValues={formInitialValues}
>
<Form.Item label="类目名" name="type" rules={[{ required: true, message: '所属类型不能为空' }]}>
<Input style={{ width: 320 }}/>
</Form.Item>
<Form.Item label="类目编号" name="code" rules={[{ required: true, message: '所属类型不能为空' }]}>
<Input style={{ width: 320 }}/>
</Form.Item>
<Form.Item label="类目位置" name="position" rules={[{ required: true, message: '属性名不能为空' }]}>
<Input style={{ width: 320 }} disabled defaultValue={catagoryPositions.join("/")}/>
</Form.Item>
<Form.Item label="是否为末级类目" name="control" rules={[{ required: true, message: '属性控件不能为空' }]}>
<Radio.Group onChange={onChange} value={isLastCatagoryVal}>
<Radio value={1}>是</Radio>
<Radio value={2}>否</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否继承属性设置" name="inherit" rules={[{ required: true, message: '属性控件不能为空' }]}>
<Radio.Group onChange={inheritPerportyFn} defaulteValue={inheritPerporty} value={setttingVal}>
<Radio value={1}>继承上级属性设置</Radio>
<Radio value={2}>自定义设置</Radio>
</Radio.Group>
</Form.Item>
</Form>
}
export default CreateCatagory
结果报错:
顺着报错,initialValues看了下antd的官网有这样一段描述“”
- 你不再需要也不应该用
onChange来做数据收集同步(你可以使用 Form 的onValuesChange),但还是可以继续监听onChange事件。 - 你不能用控件的
value或defaultValue等属性来设置表单域的值,默认值可以用 Form 里的initialValues来设置。注意initialValues不能被setState动态更新,你需要用setFieldsValue来更新。 - 你不应该用
setState,可以使用form.setFieldsValue来动态改变表单值 所以做了一个尝试:代码改动如下:
import {Modal,Form,Input,Button,Space,Select,Radio,Tabs,Checkbox } from 'antd';
import { useState ,useRef, useEffect} from 'react';
import "./index.less"
import Iconfont from 'components/iconfont';
const list=[
{id:"0001",name:"a属性"},
{id:"0002",name:"b属性"},
{id:"0003",name:"c属性"},
{id:"0004",name:"d属性"},
{id:"0005",name:"e属性"},
{id:"0006",name:"f属性"},
]
// const catagoryPositions=["一级类目","二级类目"].join("/")
const catagoryPositions=["一级类目","二级类目"]
function CreateCatagory(props){
const attrSettingDialogRef=useRef()
const [isLastCatagoryVal,setIsLastCatagoryVal]=useState(1)
const [setttingVal,setSetttingVal]=useState(1)
const [isShowPeroptyRow,setIsShowPeroptyRow]=useState(true)
const formInitialValues=({
inherit:1
})
const [form] = Form.useForm()
const formRef = React.createRef();
useEffect(()=>{
let val= catagoryPositions.length>1?2:1;
formRef.current.setFieldsValue({
inherit:val
})
},[])
const setAttrValue= ()=>{
attrSettingDialogRef.current.showModelRef()
}
const confirmFn=(data)=>{
console.log("data:",data)
}
const inheritPerportyFn=(e)=>{
console.log("val:",e.target.value)
e.target.value ===2?setIsShowPeroptyRow(true):setIsShowPeroptyRow(false)
}
return (
<div className="add-attr-page">
<div className="head-title">
<Iconfont type="icon-down"></Iconfont>
基础属性
</div>
<div className="basic-info">
<Form
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
form={form}
ref={formRef}
initialValues={formInitialValues}
>
<Form.Item label="类目名" name="type" rules={[{ required: true, message: '所属类型不能为空' }]}>
<Input style={{ width: 320 }}/>
</Form.Item>
<Form.Item label="类目编号" name="code" rules={[{ required: true, message: '所属类型不能为空' }]}>
<Input style={{ width: 320 }}/>
</Form.Item>
<Form.Item label="类目位置" name="position" rules={[{ required: true, message: '属性名不能为空' }]}>
<Input style={{ width: 320 }} disabled defaultValue={catagoryPositions.join("/")}/>
</Form.Item>
<Form.Item label="是否为末级类目" name="control" rules={[{ required: true, message: '属性控件不能为空' }]}>
<Radio.Group onChange={onChange} value={isLastCatagoryVal}>
<Radio value={1}>是</Radio>
<Radio value={2}>否</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否继承属性设置" name="inherit" rules={[{ required: true, message: '属性控件不能为空' }]}>
<Radio.Group onChange={inheritPerportyFn} value={setttingVal}>
<Radio value={1}>继承上级属性设置</Radio>
<Radio value={2}>自定义设置</Radio>
</Radio.Group>
</Form.Item>
</Form>
</div>
</div>
)
}
export default CreateCatagory
通过表单本身去进行setFieldsValue操作。把defaultValue的逻辑改为表单的操作。终于可以正常显示。
注意改动的地方:
(1)form表单本身绑定initialValues={formInitialValues}
(2)定义一个初始值
const formInitialValues=({
inherit:1,
isLastCatagory:1
})
(3)初始化根据其他值更改其默认值
useEffect(()=>{
let val= catagoryPositions.length>1?2:1;
formRef.current.setFieldsValue({
inherit:val,
isLastCatagory:val
})
},[])
三 。在表单控件中如何传递参数?
如图:我们想点击勾选的时候,把当前的数据打印出来
实现方式:箭头函数传参,注意箭头函数第一个参数只能是e,第二个函数里传对应的数据
const selectedItem = (e,data) => {
console.log("e:",e)
console.log("data:",data)
}
{
list.map(checkboxItem=>(<Checkbox
onChange={(e)=>selectedItem(e,checkboxItem)}
checked={checkboxItem.isChecked}
>
{checkboxItem.label}
</Checkbox>))
}
四。如何取消Modal.confirm的onCancel事件?
业务开发中遇到这么一个需求。点击按钮弹出确认框。点击确认按钮以后需要再出现一个弹窗。暂且叫它弹窗B,当点击弹窗B的关闭按钮时,发现confirm已经不存在了?这个问题很诡异。找了很多办法,终于解决了这个问题:
(方案一:)
在antd的Modal.confirm-api中对onCancel和onOk的描述:
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
|---|---|---|---|---|
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 |
也就是说该函数的参数是一个函数。如果我们不让其返回一个promise,就可以阻止它的默认行为。
接下来我们进行改造:
const delCatgory= ()=>{
Modal.confirm({
title:"确认删除改属性",
closable:true,
width:420,
okText:"确认",
cancelText:"取消",
onOk:(close)=>{
delModalRef.current.showModelRef() //这里通过ref直接操作弹窗B的显示
},
onCancel:(close)=>{
return false
}
})
//注意这里onok和onCancel里传入了close函数。这样就可以再关闭弹窗B的时候让confirm依然存在
方案二:)通过自定义内容,隐藏页脚的按钮,然后通过自定义内容添加确认和取消两个按钮
const delCatgory= ()=>{
Modal.confirm({
title:"确认删除改属性",
closable:true,
width:420,
okText:"",
cancelText:"",
content: <>
<p>'执行删除类目,子级类目将同步被删除,确认删除吗?'</p>
<div>
<Button onClick={confirmDelAttrModal}>确定</Button>
<Button onClick={cancelDelAttrModal}>取消</Button>
</div>
</>,
})
}
//剩下的就是对Modal.confirm默认的底部按钮通过样式进行了隐藏
五。checkbox.Group通过options传递数据。如何精准的控制单个checkbox的状态? 如下图:勾选checkbox的时候同步显示标签。对应的checkbox同步状态更改
一开始想到改造原始数据,给每个数据添加一个isChecked状态来控制其选中状态,但是发现在checkbox的change事件里通过e.target.check值和当前值无法同步。后来改变了思路。还是利用checkbox.Group能自动获取到选中的checkbox数组的特征,通过把checkbox变成受控组件来实现。基本实现逻辑如下
import React, { useState, forwardRef, useImperativeHandle,useRef, useEffect } from 'react'
import {Modal,Checkbox,Tag, List } from "antd"
import "./quotaModalStyles/createCatagoryAttrs.less"
import { check } from 'prettier'
const data=[
{value:'001',label:"把"},
{value:'002',label:"个"},
{value:'003',label:"块"},
{value:'004',label:"打"},
{value:'005',label:"批"},
]
function Dialog(props, ref) {
const {confirmFn } = props
const [visible, setVisible] = useState(false)
const [list,setList]=useState(data)
const [checkedList,setCheckedList]=useState([])
const [arr,setArr]=useState([])
//lifecycle
useEffect(()=>{
let temp=data.map(item=>({...item,isChecked:false}))
setList(temp)
},[])
//methods--
// modal的显示与隐藏
const showModal = () => {
setVisible(true)
}
const hideModal = () => {
setVisible(false)
}
useImperativeHandle(ref, () => ({
showModelRef: showModal,
hideModelRef: hideModal
}))
const handleClose = () => {
hideModal();
let num=100;
console.log("close")
confirmFn(num)
}
const selectedItem = (values) => {
console.log("values:",values)
console.log("arr:",arr)
setArr(values)
console.log("b--:",b)
let b=[];
let temp=values
list.forEach(item=>{
for(let j=0;j<temp.length;j++){
if(item.value===temp[j]){
b.push(item)
}
}
})
setCheckedList(b)
}
const closeTag=(tag)=>{
console.log("closeTag-tag-:",tag)
let tags=checkedList.filter(item=>item.value!==tag.value)
let res=[];
tags.forEach(item=>res.push(item.value))
setArr(res)
setCheckedList(tags)
}
return (
<Modal
icon=""
title="设置属性值"
width={670}
footer={null}
visible={visible}
centered={true}
maskClosable={false}
onCancel={handleClose}
destroyOnClose={true}
>
<div className='create-catagory-attrs-modal'>
{ <Checkbox.Group
onChange={selectedItem}
value={arr}
options={list}
>
</Checkbox.Group>
}
<div className="tag-box">
{
checkedList.map(item=>(
<Tag closable onClose={()=>closeTag(item)}>
{
item.label
}
</Tag>
))
}
</div>
</div>
</Modal>
)
}
export default forwardRef(Dialog)
`` 注意这里我们给checkbox.Group的value赋值给一个数组,所谓受控组件:就是checkbox本身的状态受控于数据arr而不是再根据用户本身是否勾选来控制了。而checkbox.Group的value被绑定到了arr。arr表示被选中的数组列表,这样就意味着哪些数据被勾选了,当前就应该哪些被勾选
六:hooks中对数组push操作useState无效?
如图。每次点击添加后台类目的时候展示一条数据
原始操作:
const [types,setTypes]=useState([])
const addType=()=>{
temp.push(type);
console.log("temp:",temp)
setTypes(temp)
}
<Form.Item label="支持后台类目" name="type" rules={[{ required: true, message: '显示位置不能为空' }]}>
<div>
{
types.map((item,index)=><div key={index}>{item}</div>)
}
</div>
<div>
<Button size='small' onClick={addType}>+添加后台类目</Button>
</div>
</Form.Item>
此时temp打印发现:数组types已经是最新的数据。但是在setTypes这步操作完成以后发现完全没有任何反应。后来经过查阅资料发现。useState对于复杂数据类型的更新条件是两个不同的内存地址,如果是对同一个引用是无效的。所以改成这样:
const addType=()=>{
let type=`第${Math.random()*100+10}个类目`
let temp=JSON.parse(JSON.stringify(types))
temp.push(type);
setTypes(temp)
}
通过深拷贝得到一份新的引用地址。这时候setTypes正常更新
七:antd表格如何实现全选,如何在表格以外去控制表格全选?
****
今天碰到这样一个需求:在表格之外通过一个按钮去控制表格的全选。由于对antd组件库不熟悉。导致走了很多弯路:
antd里如何实现基本的全选的?
官方的demo文档里给了一个简单的demo
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [];
for (let i = 0; i < 10; i++) {
data.push({
key: i,
name: `天然气安装 ${i}`,
code: `00000-${i}`,
unit: `米`,
price:2349.00,
fuliao:2,
status:`${i}`,
mark:"xxxxxxxxxxx",
operate:null
});
}
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRowKeys.length===data.length){
setSelectedRowKeys(selectedRowKeys)
setIsCheckeAll(true)
}else{
setSelectedRowKeys([])
setIsCheckeAll(false)
}
},
selectedRowKeys,
getCheckboxProps: (record) => ({
disabled: record.name === 'Disabled User',
name: record.name,
}),
};
<Table
hideSelectAll={true}
rowSelection={rowSelection}
columns={columns}
dataSource={data}
>
</Table>
没错。就是这个rowSelection,因为添加了这个属性。所以table头部的复选框能实现选中全部勾选,取消勾选。全部不选中。经过进一步的摸索。发现api里还有这么一句话:
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 |
|---|
\
经过尝试发现。它就是解决这个问题的关键。table全选的状态。完全依靠这个数组。这个数组是当前数据的key 的集合。如果勾选了两条数据。那么这个selectedRowKeys的值就是这两条数据的key 的集合。如果勾选了表格所有的行。那么这个selectedRowKeys就说整个data的key的集合。知道了这个关键先生。接下来我们大胆的尝试。给表格外面checkbox的checked绑定一个值。true表示全选(勾选)。此时我们把data的key全部取出来赋值给selectedRowKeys,就能达到目的,
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [];
for (let i = 0; i < 10; i++) {
data.push({
key: i,
name: `天然气安装 ${i}`,
code: `00000-${i}`,
unit: `米`,
price:2349.00,
fuliao:2,
status:`${i}`,
mark:"xxxxxxxxxxx",
operate:null
});
}
function TypeProdPage(){
const router=useHistory()
const [isCheckedAll,setCheckAll]=useState(false)
const [selectedRowKeys,setSelectedRowKeys]=useState([])
const [isCheckeAll,setIsCheckeAll]=useState(false)
const onSelect=()=>{
}
const onCheck=()=>{
}
const selectAll=(e)=>{
if(e.target.checked){
let keys=[];
data.forEach(i=>keys.push(i.key))
setSelectedRowKeys(keys) //这里是关键
}else{
setSelectedRowKeys([])
}
setIsCheckeAll(e.target.checked)
}
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRowKeys.length===data.length){
setSelectedRowKeys(selectedRowKeys)
setIsCheckeAll(true) //同步更改表格外面checkbox的状态
}else{
setSelectedRowKeys(selectedRowKeys)
setIsCheckeAll(false) //同步更改表格外面checkbox的状态
}
},
selectedRowKeys,
getCheckboxProps: (record) => ({
disabled: record.name === 'Disabled User',
name: record.name,
}),
};
const renderTitle=(data)=>{
console.log("data:",data)
return <div className="tree-item">
<div className="tree-txt">{data.title}</div>
<div className="tree-oper-btn">a</div>
</div>
}
return (
<div className="type-prod-page">
<Tabs defaultActiveKey="1" size="large">
{
combo.map(item=>
( <TabPane tab={item.name} key={item.id} >
<div className="tab-container">
<div className="tree-menu">
<Tree
defaultExpandParent
onSelect={onSelect}
treeData={treeData}
titleRender={
renderTitle
}
/>
</div>
<div className="content">
<Table
title={(data) =>
<div className="table-title">
<Row>
<Col span={12}>
<Checkbox onChange={(e)=>selectAll(e)} checked={isCheckeAll}>全选</Checkbox>
已选<span>2</span>个对象,共1200个对象
</Col>
<Col span={12}>
<Space >
<Button type="primary">添加定额</Button>
<Button type="primary">编辑</Button>
<Button type="primary">移除</Button>
</Space>
</Col>
</Row>
</div>
}
hideSelectAll={true}
rowSelection={rowSelection}
columns={columns}
dataSource={data}
>
</Table>
</div>
</div>
</TabPane>
)
)
}
</Tabs>
</div>
)
antd很优秀但是其官网文档太草率 更多记录。持续更新中...