react H5实现可拖拽的steps步骤条

拖拽实现基础可见:https://blog.csdn.net/qq_40761136/article/details/125021437

先上效果图和需求说明:
1、初始效果图
开始节点和结束节点为固定的。中间的环节由后台返回
中间的环节可拖拽修改顺序,开始和结束节点不允许拖拽

——左键键选中环节不放,拖拽到步骤图上其他环节之间的【+】处放开,即可改变环节在流程图上的顺序
 ——拖拽到当前环节前后【+】上,则拖拽不生效

在这里插入图片描述
2、单击某个环节后,选中环节样式如下
鼠标移入环节处,会展示允许编辑和删除的按钮
开始和结束节点不会出现该按钮提示
在这里插入图片描述
3、拖拽某个环节
在这里插入图片描述
4、拖拽后的效果。环节的顺序已发生了变化
在这里插入图片描述
代码如下:

/* eslint-disable eqeqeq */
import React from 'react';
import { Icon, Dropdown, Menu, Modal } from '@whalecloud/fdx';
import styles from './index.less';

const fixedStepData = [
  { tacheId: '开始', tacheName: '开始' },
  { tacheId: '结束', tacheName: '结束' },
];
const { confirm } = Modal;

class TacheStepConfig extends React.PureComponent {
  state = {
    tacheList: [], // 环节列表
    stepCurrent: 0, // 当前选中的环节index

    selectTache: {}, // 当前选中的环节数据
    type: '', // add新增 edit编辑
    step: '1', // 新增、编辑时选中的顺序
    stepData: {}, // 编辑时选中的环节数据
  };

  srcTache = null; // 拖拽开始时选中的环节

  componentDidMount() {
    // 假装这里后台返回了数据。。
    this.setState({
      tacheList: [
        { tacheId: '环节1', tacheName: '环节1', test: '1' },
        { tacheId: '环节222', tacheName: '环节222', test: '2' },
        { tacheId: '环节3', tacheName: '环节3', test: '2' },
        { tacheId: '环节4', tacheName: '他的文字很多啊超多啊', test: '2' },
      ],
    });

    // 默认开始节点为初始选中的环节
    this.onChangeStep(this.state.stepCurrent);
  }

  // 单击环节
  onChangeStep = step => {
    // 确保点结束时step数据会展示结束节点数据
    this.setState({
      stepCurrent: step,
      selectTache: {
        stepCurrent: step,
        ...this.finalStepData[step],
      },
    });
  };

  // 点击加号新增环节;index数字是几就是当前加号前有几个环节
  addTache = index => {
    this.setState({ step: index + 1, type: 'add' });
    // 点新增之后的事件由自己添加
  }

  // 删除环节
  deleteTache = item => {
    confirm({
      title: '提示',
      content: '确认删除该环节?',
      onOk() {
        // 删除的请求
        console.log('item', item);
      },
      onCancel() {},
    });
  }

  // 当拖动时触发
  drag = (ev, tache) => {
    this.srcTache = tache;
  };

  allowDrop = ev => {
    ev.preventDefault();
  };

  // 当放下后触发;newTache:拖拽后+加号前方的环节;newIndex拖拽后前方环节的数量
  drop = (ev, newTache, newIndex) => {
    ev.preventDefault();

    // 拖拽至环节前一个加号,不处理
    if (this.finalStepData[newIndex + 1]?.tacheId === this.srcTache?.tacheId) {
      return;
    }
    // 拖拽至环节后一个加号,不处理
    if (this.finalStepData[newIndex]?.tacheId === this.srcTache?.tacheId) {
      return;
    }

    const newTacheList = [];
    if (newIndex == 0) {
      newTacheList.push(this.srcTache);
    }
    // 拖拽后,先改变环节顺序数组
    this.finalStepData.forEach((item, index) => {
      if (item.tacheId !== '开始' && item.tacheId !== '结束') {
        if (newIndex == index) {
          newTacheList.push(item);
          newTacheList.push(this.srcTache);
        } else if (item.tacheId != this.srcTache?.tacheId) {
          newTacheList.push(item);
        }
      }
    });

    // 把当前选中环节设为拖拽的元素
    this.setState({ tacheList: newTacheList });
    this.onChangeStep(0);

    // 此处直接改了state,如需请求后台,请自行添加代码
  };

  render() {
    const { stepCurrent, tacheList } = this.state;

    const finalStepData = [fixedStepData[0], ...tacheList, fixedStepData[1]];
    this.finalStepData = finalStepData;

    const menu = (item, index) => (
      <Menu>
        <Menu.Item>
          <a onClick={() => this.setState({ step: index, stepData: item, type: 'edit' })}><Icon type="edit" style={{ marginRight: '10px' }} />编辑</a>
        </Menu.Item>
        <Menu.Item>
          <a onClick={() => this.deleteTache(item)}><Icon type="delete" style={{ marginRight: '10px' }} />删除</a>
        </Menu.Item>
      </Menu>
    );

    // 获取图标
    const getIcon = index => {
      if (index === 0) return 'play-circle';
      if (index >= finalStepData.length - 1) return 'minus-circle';
      return 'check-circle';
    };

    // 获取图标颜色
    const getImgBgClass = index => {
      if (index === 0 || index >= finalStepData.length - 1) return styles.processImg;
      if (index === stepCurrent) return styles.processImgCurrent; // 当前选中环节,橙色
      if (index < stepCurrent) return styles.processImgDone; // 当前选中环节前的环节,绿色
      return styles.processImgNoDone;
    };

    const getCustomDot = index => (
      <div className={getImgBgClass(index)}>
        <span>
          <Icon type={getIcon(index)} theme="filled" />
        </span>
      </div>
    );

    return (
      <div className={styles.Nav}>
        {
          finalStepData && finalStepData.map((item, index) => (
            <div style={{ display: 'flex', position: 'relative', height: '53px' }}>
              <Dropdown overlay={(index !== 0 && index < finalStepData.length - 1) ? menu(item, index) : <div />}>
                <div
                  className={styles.NavContent}
                  onClick={() => this.onChangeStep(index)}
                  draggable={index !== 0 && index < finalStepData.length - 1} // 判断:不是开始节点和结束节点才允许拖拽
                  onDragStart={event => this.drag(event, item)} // 拖拽开始触发
                >
                  {getCustomDot(index)}
                  <div style={{ marginTop: 7 }}>{item.tacheName}</div>
                </div>
              </Dropdown>
              {/* 箭头 */}
              {
                index < finalStepData.length - 1 ?
                  <div className={stepCurrent > index ? styles.NavArrowActive : styles.NavArrow} />
                  : null
              }
              {/* 箭头中的加号 */}
              {
                index < finalStepData.length - 1 ?
                  <div
                    className={stepCurrent > index ? styles.NavAddActive : styles.NavAdd}
                    onClick={() => this.addTache(index)}
                    onDrop={event => this.drop(event, item, index)} // 环节拖拽至此处后触发
                    onDragOver={event => this.allowDrop(event)}
                  >
                    <Icon type="plus-circle" theme="filled" />
                  </div>
                  : null
              }
            </div>
          ))
        }
      </div>
    );
  }
}

export default TacheStepConfig;


样式代码如下:

@imgWidth: 26px;
@imgPadding: 2px;
@imgDivWidth: @imgWidth + @imgPadding * 2 + 1px;

@imgShadowWidth: 8px;
@imgHoverShadowWidth: 10px;

@arrowWidth: 100px;
@arrowHeight: 2px;
@arrowSize: 4px;
@normalColor: #5A91FF;
@doneColor: #1ABE61;
@noDoneColor: #c9c9c9;
@activeColor: #FF7F15;

.Nav {
  width: 100%;
  padding: 12px 20px;
  // background-image: url("../../../../../../img/bgImg.png");
  overflow-x: auto;
  height: 85px;

  display: flex;
  white-space: nowrap;

    .processImg {
      border-radius: 50%;
      border: 1px solid #dedede;
      background: #fff;
      box-shadow: 0 2px @imgShadowWidth @normalColor;
      width: @imgDivWidth;
      height: @imgDivWidth;
      padding: @imgPadding;
      transition: box-shadow 0.3s;
      margin: 0 auto;
      &:hover {
        box-shadow: 0 0 @imgShadowWidth @normalColor;
      }
      > span {
        display: flex;
        font-size: 12px;
        width: 100%;
        height: 100%;
        border: none;
        margin: 0;
        border-radius: 50%;
        background: @normalColor;
        color: #fff;
        align-items: center;
        justify-content: center;
      }
    }
    .processImgCurrent {
      .processImg;
      box-shadow: 0 2px @imgShadowWidth @activeColor;
      &:hover {
        box-shadow: 0 0 @imgShadowWidth @activeColor;
      }
      > span {
        background: @activeColor;
        font-size: 14px;
      }
    }
    .processImgDone {
      .processImg;
      box-shadow: 0 2px @imgShadowWidth @doneColor;
      &:hover {
        box-shadow: 0 0 @imgShadowWidth @doneColor;
      }
      > span {
        background: @doneColor;
        font-size: 14px;
      }
    }
    .processImgNoDone {
      .processImg;
      box-shadow: 0 2px @imgShadowWidth @noDoneColor;
      &:hover {
        box-shadow: 0 0 @imgShadowWidth @noDoneColor;
      }
      > span {
        background: @noDoneColor;
        font-size: 14px;
      }
    }

    .NavContent {
      text-align: center;
      cursor: pointer;
      &:hover {
        color: @doneColor;
      }
    }

    .NavArrow {
      height: 2px;
      width: 64px;
      background: @noDoneColor;
      align-self: center;
      margin: 0px 15px 26px 15px;
      position: relative;
      &::before {
        content: '';
        width: 5px;
        height: 4px;
        border-width: 4px;
        border-color: transparent transparent transparent @noDoneColor;
        border-style: solid;
        position: absolute;
        top: -3px;
        right: -7px;
        transition-delay: 0.3s;
        transition: border-left-color 0.2s;
      }
    }

    .NavArrowActive {
      .NavArrow;
      background: #9ee7be;
      &::before {
        border-color: transparent transparent transparent #9ee7be;
      }
    }

    .NavAdd {
      color: #8693A7;
      margin-bottom: 26px;
      position: absolute;
      right: 39px;
      top: 2px;
      cursor: pointer;
      font-size: 16px;
    }
    .NavAddActive {
      .NavAdd;
      color: @doneColor;
    }
}

最后就完美实现效果啦~
不过个人认为有个缺陷是,使用这样的H5拖拽写法,虽然简单。就是在当前页面中,只允许存在一个拖拽事件。如果有多个类似的拖拽事件,则一个事件的物品拖拽至另一个拖拽事件的物品中时,不会有效果(甚至可能看放入事件出现问题)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值