记录使用 Ant Design 过程中遇到的问题

542 阅读3分钟

养成记录的习惯,方便自己,方便他人

1、给<Drawer>title设置省略,并且鼠标悬停展示完整的标题

(1)先 实现鼠标悬停展示完整标题

<Drawer>title设置<Tooltip>,一定要记得给他设置 className

<Drawer
    title={
        <Tooltip
          trigger="hover"
          title={`容器名称:${containerInfo.container_full_name}`}
          placement="bottom"
        >
        {`容器名称:${containerInfo.container_full_name}`}
        </Tooltip>
    }
    ...
    width={870}
    className={containerInfoDrawer}
>
    ...
</Drawer>

(2)再给title设置省略

css 文件中设置:

.containerInfoDrawer [class~='ant-drawer-title'] {
  cursor: pointer;
  width: 780px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

(3)运行效果

运行结果

2、react使用Antd<Form>中的getFieldDecorator之后,onchange中执行setFieldsValue不管事了,如何解决

const [singleImageScanTimeout, setSingleImageScanTimeout] = useState(10);

<FormItem>
 {getFieldDecorator('scan_layer_timeout')(
      <InputNumber 
          min={1} 
          disabled={authStatus} 
          onChange={(val)=>{
          // 这个地方按照自己业务需求来
            if (e > singleImageScanTimeout) {
                setFieldsValue({
                    single_image_scan_timeout: val,
                });
            }
          }}
    />)
 }
</FormItem>

通过onChange来实现对scan_single_layer_timeout进行修改,但发现并没有成功。

解决办法:在getValueFromEvent中调用setFieldsValue来做数据数据同步(

const [singleImageScanTimeout, setSingleImageScanTimeout] = useState(10);


<FormItem>
    {getFieldDecorator('scan_layer_timeout', {
        getValueFromEvent: (e) => {
            if (e >= singleImageScanTimeout) {
                setSingleImageScanTimeout(e);
                setFieldsValue({
                  single_image_scan_timeout: e,
                });
            }
            return e;
        },
    })(<InputNumber min={1} max={10} disabled={authStatus} />)}
</FormItem>

<FormItem>
    {getFieldDecorator('single_image_scan_timeout', {
        getValueFromEvent: (e) => {
          setSingleImageScanTimeout(e);
          return e;
        },
    })(<InputNumber min={1} disabled={authStatus} />)}
</FormItem>

3、<Mobal><Select>结合使用,鼠标滚动,蒙层下的页面随之滚动,发现Select的下拉框也随之滚动。

就像这样子: 截屏2022-04-21 下午3.55.08.png

解决办法:给Select添加 getPopupContainer属性,例如 getPopupContainer={(trigger) => trigger.parentNode}

<Modal
  ...
>
    <Select
        mode="multiple"
        size="small"
        onChange={(v) => {
            ...
        }}
        showArrow
        maxTagCount={2}
        maxTagPlaceholder={'...'}// 超过两个选项后以...隐藏起来
        getPopupContainer={(trigger) => trigger.parentNode}// 防止下拉框随着页面的滚动而滚动
        value={...}
        >
            ...
    </Select>
</Modal>

截屏2022-04-21 下午3.38.10.png

4、切换Switch,变化状态来实现其他项目的正则校验

目的: 想要校验 三个输入框 的值,看这些值是否是正则表达式

4.1 初步实现:没有成功

截屏2022-04-28 下午7.47.50.png

相关代码:

import { Button, Drawer, Form, message, Select, Switch } from 'antd';
......

const FormItem = Form.Item;
const Option = Select.Option;
const ActionModalEdit = ({
  visible,
  onClose,
  tabSelect,
  form,
  id,
  setShouldUpdate,
}) => {
  const { getFieldDecorator, setFieldsValue, validateFields, resetFields } = form;
  const [saveButLoading, setSaveButLoading] = useState(false);
  const [supportReg, setSupportReg] = useState(false);
  const [canSave, setCanSave] = useState(true);

  // 判断是否是正则表达式
  const isReg = (reg) => {
    let isReg;
    try {
      isReg = eval(reg) instanceof RegExp;
    } catch (e) {
      isReg = false;
    }
    return isReg;
  };
  
  // 正则校验
  const isRegExp = async (rule, value = [], callback) => {
    console.log(`value-------1`, value);
    console.log(`supportReg--1`, supportReg);
    if (supportReg) {
      value.forEach((element) => {
        console.log('1');
        if (!isReg(element)) {
          setCanSave(false);
          callback(`${element} 错误,请书写正确的正则表达式!`);
        } else {
          callback && callback();
          setCanSave(true);
        }
      });
      callback && callback();
    } else {
        callback && callback();
    }
  };
  
  ......
  
  const dedupAndTrim = (value) => {
    return Array.from(
      new Set(value.map((item) => item.trim()).filter((item) => item !== '')),
    );
  };
  useEffect(() => {
    if (supportReg) validateFields();
  }, [supportReg]);
  
  return (
    <Drawer
      title={`${visible.title ? '添加' : '编辑'}行为模型`}
      visible={!!Object.keys(visible).length}
      onClose={onClose}
      width="700"
    >
      <Form className={css.policyform}>
  
  
        <FormItem label="用户名">
          {getFieldDecorator('user_name', {
            rules: [
              {
                required: true,
                message: '用户名不能为空',
              },
              {
                validator: isRegExp,
              },
            ],
            getValueFromEvent: dedupAndTrim,
          })(
            <Select
              placeholder="请输入用户信息,可输入多个"
              mode="tags"
              dropdownStyle={{ display: 'none' }}
              style={{ width: '100%' }}
            />,
          )}
        </FormItem>
        <FormItem label="进程路径">
          {getFieldDecorator('binary_path', {
            rules: [
              {
                required: true,
                message: '进程路径不能为空',
              },
              {
                validator: isRegExp,
              },
            ],
            getValueFromEvent: dedupAndTrim,
          })(
            <Select
              placeholder="请输入进程路径,可输入多个"
              mode="tags"
              dropdownStyle={{ display: 'none' }}
              style={{ width: '100%' }}
            />,
          )}
        </FormItem>
        <FormItem label="进程命令行">
          {getFieldDecorator('cmdline', {
            rules: [
              {
                required: true,
                message: '进程命令行不能为空',
              },
              {
                validator: isRegExp,
              },
            ],
            getValueFromEvent: dedupAndTrim,
          })(
            <Select
              placeholder="请输入进程命令行,可输入多个"
              mode="tags"
              dropdownStyle={{ display: 'none' }}
              style={{ width: '100%' }}
            />,
          )}
        </FormItem>
     
        <FormItem>
          <span style={{ color: 'rgba(0, 0, 0, 0.85)', lineHeight: '22px' }}>
            {' '}
            是否支持正则校验:
          </span>
          <Switch
            size="small"
            onChange={(val) => {
              setSupportReg(val);
            }}
          />
        </FormItem>
        
      </Form>
      <div className={`${css.btn} ${css.actionbtn}`}>
        <Button onClick={onClose}>取消</Button>
        <Button
          onClick={onSave}
        >
          保存
        </Button>
      </div>
    </Drawer>
  );
};
export default Form.create()(ActionModalEdit);

4.2 调试过程中发现:

console.log(`value-------1`, value);
console.log(`supportReg--1`, supportReg);
  • 第一次form表单本身校验是: value值是正确的,但是supportReg还是上一次的状态

  • 第二次更新的时候是,useEffect监听到supportReg的变化而触发的校验 此时value值为[],但是supportReg是正确的的值。

4.3 猜想

可能存在异步问题

4.4 经过几番探索

自己尝试在isRegExp函数采用异步来实现,后来发现并没有成功,还是原来的样子。

猜想,会不会是因为form表单没有没有监听到Switch的数据导致。

然后尝试给Switch 加上getFieldDecorator,然后采用 getFieldValue(supportReg)来获取值,进行判断。后来发现还是不行。

然后有猜想是不是因为 options.valuePropName 子节点的值的属性,如 Switch 的是 'checked' 没有给他配置valuePropName这个属性,加上之后并没有效果。

4.5 最终效果:

截屏2022-04-28 下午9.28.37.png

贴了重要的三处:

 const isRegExp = (rule, value = [], callback) => {
    if (supportReg) {
      value.forEach((element) => {
        if (!isReg(element)) {
          callback(`${element} 错误,请书写正确的正则表达式!`);
        }
      });
      callback();
    }
    callback();
  };

 useEffect(() => {
    validateFields();
  }, [supportReg]);
  
  
<FormItem label="是否支持正则校验:">
  {getFieldDecorator('supportReg')(
    <Switch
      checked={supportReg}
      size="small"
      onChange={(val) => {
        setSupportReg(val);
      }}
    />,
  )}
</FormItem>

我猜想很大的可能是和getFieldDecorator这个有关,苦思冥想+验证 ,没有找到真正的原因。各位大佬有谁知道可以指点迷津吗?

5、<Selcet>实现模糊查询

实现效果:

聚焦时展示所有的数据:

聚焦时展示所有的数据

查询关键字时展示的数据:

查询关键字时展示的数据

关键代码

// 说明 secondSelectOption 的前提是已经根据接口获得了

const [ready, setReady] = useState(true); // 控制useRequest的触发
const [secondSelectValue, setSecondSelectValue] = useState([]);
const [secondSelectOpen, setSecondSelectOpen] = useState(false);
const [firstText, setFirstText] = useState(searchConfig[0].text);
const [secondSelectOption, setSecondSelectOption] = useState([]);
const [secSelectOptionHasChange, setSecSelectOptionHasChange] = useState([]); // secondSelectOption备份

// 依赖某个状态进行请求
const { run, loading } = useRequest(
    () => {
      return request(selectReq.url, {
        method: 'GET',
        params: { search: selectData, page: -1, page_size: -1 },
      });
    },
    {
      manual: true,// 是否需要手动触发,若为false则立刻产生请求,若为true
      ready,// 重要 - 当manual为true时生效,为true时才产生请求
      debounceInterval: 500,
      onSuccess: async (res, params) => {
       // TODO
      },
    },
);


// selectData 变化时过滤已经得到的options集合(这里大家也可以根据自己的需要,请求接口也行,记得加一个防抖)
useEffect(() => {
    if (secSelectOptionHasChange.length > 5 && selectData) {
      let options = _.filter(secSelectOptionHasChange, (item) => {
        return item.text.toLowerCase().indexOf(selectData.toLowerCase()) >= 0;
      });
      setSecondSelectOption(options);
    }
    if (!selectData) {
      setSecondSelectOption(secSelectOptionHasChange);
    }
}, [selectData, secSelectOptionHasChange]);


 <Select
      mode="multiple"
      ref={urlSelectRef}
      notFoundContent={loading ? <Spin size="small" /> : null}
      value={secondSelectValue}
      showArrow
      onChange={(e) => {
        secondSelectFunc(e);
        setSecondSelectOpen(false);
        urlSelectRef.current.blur();
      }}
      onFocus={() => {
        setReady(false);
        setSecondSelectOpen(true);
      }}
      onBlur={() => {
        setReady(true);
        setSecondSelectOpen(false);
      }}
      onSearch={setSelectData}//监听输入框的数据变化
      filterOption={false} //下拉框实现模糊搜索,这个过滤⼀定要设置为fasle要不然功能不实现
      optionFilterProp="children"
      open={secondSelectOpen && secondSelectOption.length > 0}
      style={{ width: '65%' }}
    >
      {secondSelectOption.map((items) => {
        return (
          <Option value={String(items.args)} key={items.args}>
            {items.text}
          </Option>
        );
      })}
</Select>

6、message多次触发弹出多个信息提示 ,但只想要一个message

import { message } from "antd";
// react antd message多次触发弹出多个信息提示 
message.config({                  
    top: 100,                         
    duration: 2,                      
    maxCount: 1,                      
});                               
message.destroy();

7、上传组件 Upload 的使用

image.png

// 判断域名/ip是否正确
const checkIpv6 = (ip) => {
    let reg1 = /^(?:\*\.)?[a-z0-9]+(?:[\-.][a-z0-9]+)*\.[a-z]{2,6}$/i;
    let reg2 =
    /^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$/;
    let valdata = ip.split(',');
    let isReal = true;
    try {
        for (let i = 0; i < valdata.length; i++) {
        if (!(reg1.test(valdata[i]) || reg2.test(valdata[i]))) {
            isReal = false;
            return isReal;
        }
        }
    } catch (error) {
        console.log(error);
    }
    return isReal;
};

// 判断exel文件中的数据是否满足要求
const canSaveData = (list) => {
    let err = false;
    list.map((i) => {
        if (
        !i.name ||
        !i.address ||
        !i.danger_level ||
        !i.type ||
        !checkIpv6(i.address)
        ) {
            err = true;
        }
    });
    return err;
};
// Upload 的属性配置
const uploadProps = {
    name: 'file',
    accept: '.xlsx',
    action: server.url + '/v1/container/parse_spite_ip_xlsx',
    headers: {
        'X-Requested-With': null,
        'Access-Token': localStorage.getItem('AccessToken'),
    },
    withCredentials: true,
    fileList: fileList,
    beforeUpload: (file) => {// 预上传,条件只能上传一个
        let arr = [...fileList, file];
        if (fileList.length === 0) {
            setfileList(arr);
        } else {
            message.error('只能上传一个文件');
            setfileList([...fileList]);
        }
        return arr.length === 1;
    },
    onRemove: (file) => {// 删除多余的文件
        let list = fileList.filter((item) => item.uid !== file.uid);
        setfileList(list);
    },
    onChange: (info) => {
        let { fileList } = info;
        let data = [];
        let err = false;
        let filelist = [];
        // 上传成功之后,获取返回来的解析好的数据,判断数据是否正确,正确的数据就保存起来方便后续使用
        fileList.forEach((item) => {
            if (
            item.response &&
            item.response instanceof Object &&
            item.response.code === 0
            ) {
                data = _.get(fileList, [0, 'response', 'data', 'spite_ip'], []);
                err = canSaveData(data);
                setError(err);
                if (err) {
                    item.status = 'error';
                    filelist.push(item);
                    setfileList(filelist);
                    message.error('导入的表格数据不正确!');
                } else {
                    setFileData(data);
                }
            } else {
                item.status = 'error';
                if (item.response) {
                    item.response = item.response.msg;
                }
            }
        });
    },
};

<Upload {...uploadProps}>
    <Button size="small" style={{ width: '68px' }}>
        <Icon type="upload" />
        上传
    </Button>
</Upload>