react+antd,实现Tree组件的hover显示编辑删除、长字符隐藏功能、以及选中项拉通功能

1,核心是利用好tree组件的titleRender属性,该属性可以将tree已有数据进行二次更改,将原本的title更换为(nodeData) => ReactNode

const renderTitle = (node: any) => {
    const isHovered = node.key === treeEditStatusKey;
    const regionWidth = getTextWidth(node.title, '14px');
    return (
      <div
        className="tree-span"
        onMouseEnter={() => {
          setTreeEditStatusKey(node.key);
        }}
        onMouseLeave={() => {
          setTreeEditStatusKey(null);
        }}
      >
        <Tooltip title={regionWidth > 80 ? node.title : ''}>
          <div className="hidden-box" ref={divRef}>
            {node.title}
          </div>
        </Tooltip>
        {isHovered && (
          <>
            <div>
              {node?.parentId !== '0' && (
                <Space>
                  <div
                    className="del-icon"
                    onClick={() => {
                      Modal.confirm({
                        title: t.formatMessage({
                          id: `columns.handel.delete`,
                          defaultMessage: '删除',
                        }),
                        icon: <ExclamationCircleOutlined />,
                        content: t.formatMessage({
                          id: 'device.deviceManage.delAreaReminds',
                        }),
                        onOk() {
                          regionDel({ id: node.key }).then(() => {
                            setReloadNumTree(reloadNumTree + 1);
                            setReloadNum(reloadNum + 1);
                          });
                        },
                        onCancel() {},
                        okText: t.formatMessage({
                          id: `form.handel.ok`,
                          defaultMessage: '确认',
                        }),
                        cancelText: t.formatMessage({
                          id: `form.handel.cancel`,
                          defaultMessage: '取消',
                        }),
                      });
                    }}
                  />
                  <div
                    className="edit-icon"
                    onClick={() => {
                      setTreeAddOrEdit('edit');
                      setDeviceAddModal(true);
                    }}
                  />
                </Space>
              )}
            </div>
          </>
        )}
      </div>
    );
  };

2,我们通过在每一项renderTitle上面定义onMouseEnter和onMouseLeave,enter的时候保存该项的nodeKey,移除的时候删除nodeKey,再将其与renderTitle的node项进行唯一值匹配,可控制该项的编辑和删除图标的显示和隐藏,

const [treeEditStatusKey, setTreeEditStatusKey] = useState<any>();
onMouseEnter={() => {
          setTreeEditStatusKey(node.key);
        }}
        onMouseLeave={() => {
          setTreeEditStatusKey(null);
        }}

3,并且通过getTextWidth函数,对title的长度进行校验,过长的文字隐藏,并使用Tooltip展示,

  //判断regionName长度隐藏
  const getTextWidth = (text: any, fontSize: any) => {
    const span = document.createElement('span');
    span.style.fontSize = fontSize;
    span.style.visibility = 'hidden';
    span.style.position = 'absolute';
    span.style.whiteSpace = 'nowrap';
    span.innerText = text;
    document.body.appendChild(span);
    const width = span.offsetWidth;
    document.body.removeChild(span);
    return width;
  };

4,选中拉通:选中项拉通功能需要使用tree组件的 const { DirectoryTree } = Tree;属性,即可像文件列表一样选中拉通,无需更改样式

5,效果:

6,全部代码:

import { regionDel } from '@/services/common/device';
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import {
  Col,
  Empty,
  Input,
  Modal,
  Row,
  Space,
  Spin,
  Tooltip,
  Tree,
} from 'antd';
import type { TreeProps } from 'antd/es/tree';
import { useRef, useState } from 'react';
import './index.less';
const { Search } = Input;
type FormType = {
  treeData: any[];
  reloadNumTree: number;
  reloadNum: number;
  treeSpinLoading: boolean;
  setReloadNumTree: (values: any) => void;
  setInitialValuesArea: (values: any) => void;
  setSubmitAreaForm: (values: any) => void;
  setDeviceAddModal: (values: any) => void;
  setTreeAddOrEdit: (values: any) => void;
  setReloadNum: (values: any) => void;
  setTreeCheckedId: (values: any) => void;
  treeCheckedId?: any;
};
const { DirectoryTree } = Tree;
const LeftTree = ({
  treeData,
  setInitialValuesArea,
  setSubmitAreaForm,
  setTreeAddOrEdit,
  setDeviceAddModal,
  reloadNum,
  reloadNumTree,
  setReloadNum,
  setReloadNumTree,
  treeSpinLoading,
  setTreeCheckedId,
}: // treeCheckedId,
FormType) => {
  const t = useIntl();
  const divRef: any = useRef(null);
  const [onInputChange, setOnInputChange] = useState<string>('');
  const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
  const [treeEditStatusKey, setTreeEditStatusKey] = useState<any>();
  //判断regionName长度隐藏
  const getTextWidth = (text: any, fontSize: any) => {
    const span = document.createElement('span');
    span.style.fontSize = fontSize;
    span.style.visibility = 'hidden';
    span.style.position = 'absolute';
    span.style.whiteSpace = 'nowrap';
    span.innerText = text;
    document.body.appendChild(span);
    const width = span.offsetWidth;
    document.body.removeChild(span);
    return width;
  };
  // // 递归筛选
  const filterTree = (treeData: any[], searchValue: string) => {
    if (!searchValue) {
      return treeData;
    }
    return treeData.reduce((acc, { title, children, ...rest }) => {
      const isMatch = title.toLowerCase().includes(searchValue.toLowerCase());
      const childNodes = filterTree(children || [], searchValue);
      if (isMatch || childNodes.length > 0) {
        acc.push({
          title: isMatch ? (
            <span style={{ color: '#1890ff' }}>{title}</span>
          ) : (
            title
          ),
          children: childNodes,
          ...rest,
        });
      }
      return acc;
    }, []);
  };
  const filteredTreeData = filterTree(treeData, onInputChange);
  // 扁平化filter数组,用于筛选后展开项
  const getFlatArr = (treeArr: any) => {
    let res: any[] = [];
    // eslint-disable-next-line
    treeArr.map((item: any) => {
      let data = {};
      data = { children: [], child: item.children };
      const finItem = { ...item, ...data };
      res.push(finItem);
      if (item.children && Array.isArray(item.children)) {
        res = res.concat(getFlatArr(item.children));
      }
    });
    return res;
  };
  const flattenTree = getFlatArr(filteredTreeData);
  // 树选择
  const onTreeSelect: TreeProps['onSelect'] = (
    selectedKeys: any,
    info: any,
  ) => {
    setTreeCheckedId(info?.node.key);
    if (selectedKeys.length > 0) {
      setReloadNum(() => reloadNum + 1);
      setInitialValuesArea({
        id: info?.node.key,
        parentId: info?.node?.parentId,
        regionName: info?.node?.label,
        version: info?.node?.version,
      });
    } else {
      setReloadNum(() => reloadNum + 1);
    }
  };
  const onExpand = (expandedKeysValue: any[]) => {
    setExpandedKeys(expandedKeysValue);
  };
  const renderTitle = (node: any) => {
    const isHovered = node.key === treeEditStatusKey;
    const regionWidth = getTextWidth(node.title, '14px');
    return (
      <div
        className="tree-span"
        onMouseEnter={() => {
          setTreeEditStatusKey(node.key);
        }}
        onMouseLeave={() => {
          setTreeEditStatusKey(null);
        }}
      >
        <Tooltip title={regionWidth > 80 ? node.title : ''}>
          <div className="hidden-box" ref={divRef}>
            {node.title}
          </div>
        </Tooltip>
        {isHovered && (
          <>
            <div>
              {node?.parentId !== '0' && (
                <Space>
                  <div
                    className="del-icon"
                    onClick={() => {
                      Modal.confirm({
                        title: t.formatMessage({
                          id: `columns.handel.delete`,
                          defaultMessage: '删除',
                        }),
                        icon: <ExclamationCircleOutlined />,
                        content: t.formatMessage({
                          id: 'device.deviceManage.delAreaReminds',
                        }),
                        onOk() {
                          regionDel({ id: node.key }).then(() => {
                            setReloadNumTree(reloadNumTree + 1);
                            setReloadNum(reloadNum + 1);
                          });
                        },
                        onCancel() {},
                        okText: t.formatMessage({
                          id: `form.handel.ok`,
                          defaultMessage: '确认',
                        }),
                        cancelText: t.formatMessage({
                          id: `form.handel.cancel`,
                          defaultMessage: '取消',
                        }),
                      });
                    }}
                  />
                  <div
                    className="edit-icon"
                    onClick={() => {
                      setTreeAddOrEdit('edit');
                      setDeviceAddModal(true);
                    }}
                  />
                </Space>
              )}
            </div>
          </>
        )}
      </div>
    );
  };
  return (
    <>
      <div className="member-left-tree">
        <div className="table-view-tree-device">
          <div className="tree-container">
            <div className="tree-title-box">
              <div className="tree-title">区域</div>
            </div>
            <div className="tree-search-box">
              <div className="search-box">
                <Row className="zone-container">
                  <Col span={20} className="right-search">
                    <Search
                      maxLength={50}
                      size="middle"
                      allowClear
                      placeholder={t.formatMessage({
                        id: 'device.deviceManage.inputArea',
                        defaultMessage: '请输入',
                      })}
                      // addonAfter={<SearchOutlined />}
                      onChange={(e) => {
                        setOnInputChange(e.target.value);
                      }}
                    />
                  </Col>
                  <Col span={4} offset={0} className="left-add">
                    <Tooltip
                      title={t.formatMessage({
                        id: 'device.deviceManage.addAreaBtn',
                      })}
                      placement="right"
                    >
                      <div
                        className="device-add-btn"
                        onClick={() => {
                          setInitialValuesArea({});
                          setSubmitAreaForm(0);
                          setDeviceAddModal(true);
                          setTreeAddOrEdit('add');
                        }}
                      >
                        <span>
                          <PlusOutlined />
                        </span>
                      </div>
                    </Tooltip>
                  </Col>
                </Row>
              </div>
            </div>
            <Spin spinning={treeSpinLoading}>
              {treeData?.length > 0 ? (
                <div className="tree-item">
                  <DirectoryTree
                    className="trees"
                    onSelect={onTreeSelect}
                    // defaultExpandAll={defaultExpandAll}
                    blockNode
                    showIcon={false}
                    treeData={treeData}
                    titleRender={renderTitle}
                    expandedKeys={
                      onInputChange
                        ? flattenTree.map((item: any) => item.key)
                        : expandedKeys
                    }
                    onExpand={onExpand}
                  />
                </div>
              ) : (
                <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
              )}
            </Spin>
          </div>
        </div>
      </div>
    </>
  );
};

export default LeftTree;

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个简单的示例代码: 首先安装所需依赖: ```bash npm install react antd axios --save ``` 然后创建一个 `Login` 组件和一个 `Register` 组件,分别用于登录和注册的页面展示。其中,我们使用了 `Form` 和 `Input` 组件实现表单输入,以及 `Button` 组件实现按钮的功能。 ```jsx // Login.jsx import React, { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; import axios from 'axios'; const Login = () => { const [loading, setLoading] = useState(false); const onFinish = async (values) => { setLoading(true); try { const response = await axios.post('/api/login', values); message.success(response.data.message); setLoading(false); } catch (error) { message.error(error.response.data.message); setLoading(false); } }; return ( <Form name="login" onFinish={onFinish}> <Form.Item name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input placeholder="Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password placeholder="Password" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading}> Login </Button> </Form.Item> </Form> ); }; export default Login; ``` ```jsx // Register.jsx import React, { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; import axios from 'axios'; const Register = () => { const [loading, setLoading] = useState(false); const onFinish = async (values) => { setLoading(true); try { const response = await axios.post('/api/register', values); message.success(response.data.message); setLoading(false); } catch (error) { message.error(error.response.data.message); setLoading(false); } }; return ( <Form name="register" onFinish={onFinish}> <Form.Item name="name" rules={[{ required: true, message: 'Please input your name!' }]} > <Input placeholder="Name" /> </Form.Item> <Form.Item name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input placeholder="Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password placeholder="Password" /> </Form.Item> <Form.Item name="confirmPassword" rules={[ { required: true, message: 'Please confirm your password!' }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('The two passwords do not match!')); }, }), ]} > <Input.Password placeholder="Confirm Password" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading}> Register </Button> </Form.Item> </Form> ); }; export default Register; ``` 在这里我们使用了 `axios` 来发送登录和注册的请求,需要注意的是,这里的请求地址是 `/api/login` 和 `/api/register`,而不是实际的后端接口地址,根据实际情况进行修改。 最后在需要展示登录和注册功能的页面中引入 `Login` 和 `Register` 组件即可。 ```jsx import React from 'react'; import Login from './Login'; import Register from './Register'; const LoginPage = () => { return ( <div> <h1>Login</h1> <Login /> <hr /> <h1>Register</h1> <Register /> </div> ); }; export default LoginPage; ``` 以上就是一个简单的使用 `react` 和 `antd` 实现登录和注册功能的示例代码,希望对你有帮助。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值