前端react——从零开始的拖拽生成email(第一章)

发布于:2025-02-10 ⋅ 阅读:(37) ⋅ 点赞:(0)


yma16-logo

⭐前言

大家好,我是yma16,本文分享 前端react——从零开始的拖拽生成email(第一章)
背景
作为2025练手项目

前端系列文章
vue3 + fastapi 实现选择目录所有文件自定义上传到服务器
前端vue2、vue3去掉url路由“ # ”号——nginx配置
csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板
认识vite_vue3 初始化项目到打包
python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示
让大模型分析csdn文章质量 —— 提取csdn博客评论在文心一言分析评论区内容
前端vue3——html2canvas给网站截图生成宣传海报
前端——html拖拽原理
前端 富文本编辑器原理——从javascript、html、css开始入门
前端老古董execCommand——操作 选中文本 样式
前端如何在30秒内实现吸管拾色器?
前端——原生Selection api操作选中文本 样式、取消样式(解决标签的无限嵌套问题)

⭐初始化react app

创建 drag-email 目录的react app

# npx create-react-app drag-email

暴露 webpack配置,方便后续引入less

# npm run eject

eject
webpack.config 配置加载less

// 加载less
const lessRegex = /\.less$/i;

配置添加

{
	test: lessRegex,
		use: getStyleLoaders(
			{
				importLoaders: 3,
				sourceMap: isEnvProduction? shouldUseSourceMap: isEnvDevelopment,
                modules: {
                	getLocalIdent: getCSSModuleLocalIdent,
                },
            },
            'less-loader'
        ),
}

less

⭐封装拖拽

主要针对当前元素和目标元素添加drag和drop事件。

interface ConfigType {
  source: any;
  target: any;
}
class DragOption {
  public sourceDom = null;
  public targetDom = null;
  public dragConfig = {
    curentDrag: null,
  };

  constructor(config: { source: any }) {
    const { source } = config;
    this.sourceDom = source;
    this.addListen();
  }
  // 初始化
  public addListen() {
    this.dragStart();
    this.drag();
    this.dragend();
  }

  public removeListen() {
    this.removeDragStart();
    this.removeDrag();
    this.removeDragend();
  }
  /* 在可拖动的目标上触发的事件 */
  public dragStart() {
    // @ts-ignore
    this.sourceDom.addEventListener("dragstart", (event: any) => {
      // 保存被拖动元素的引用 dragging
      this.dragConfig.curentDrag = event.target;
      // 拖拽 样式  dragging
      event.target.classList.add("dragging");
    });
  }

  public removeDragStart() {
    // @ts-ignore
    this.sourceDom.removeEventListener("dragstart", (event: any) => {
      // 保存被拖动元素的引用 dragging
      this.dragConfig.curentDrag = event.target;
      // 拖拽 样式  dragging
      event.target.classList.add("dragging");
    });
  }

  public drag() {
    // @ts-ignore
    this.sourceDom.addEventListener("drag", (event: any) => {
      // 拖拽中
      console.log("dragging");
    });
  }

  public removeDrag() {
    // @ts-ignore
    this.sourceDom.removeEventListener("drag", (event: any) => {
      // 拖拽中
      console.log("dragging");
    });
  }

  public dragend() {
    // @ts-ignore
    this.sourceDom.addEventListener("dragend", (event: any) => {
      // 在可拖动元素进入潜在的放置目标时高亮显示该目标
      if (event.target.classList.contains("dropzone")) {
        event.target.classList.add("dragover");
      }
    });
  }

  public removeDragend() {
    // @ts-ignore
    this.sourceDom.removeEventListener("dragend", (event: any) => {
      // 在可拖动元素进入潜在的放置目标时高亮显示该目标
      if (event.target.classList.contains("dropzone")) {
        event.target.classList.add("dragover");
      }
    });
  }
}

class DropOption {
  public targetDom = null;
  public dragConfig = {
    curentDrag: null,
  };

  constructor(config: { target: any }) {
    const { target } = config;
    this.targetDom = target;
    this.addListen();
  }
  // 初始化
  public addListen() {
    this.dragOver();
    this.dragenter();
    this.dragleave();
    this.drop();
  }

  // 去掉监听
  public removeListen() {
    this.removeDragOver();
    this.removeDragenter();
    this.removeDragleave();
    this.removeDrop();
  }

  /* 在放置目标上触发的事件 */
  public dragOver() {
    // @ts-ignore
    this.targetDom.addEventListener(
      "dragover",
      (event: any) => {
        // 阻止默认行为以允许放置
        event.preventDefault();
      },
      false
    );
  }

  public removeDragOver() {
    // @ts-ignore
    this.targetDom.removeEventListener(
      "dragover",
      (event: any) => {
        // 阻止默认行为以允许放置
        event.preventDefault();
      },
      false
    );
  }
  public dragenter() {
    // @ts-ignore
    this.targetDom.addEventListener("dragenter", (event: any) => {
      // 拖动结束,去掉dragging
      event.target.classList.remove("dragging");
    });
  }

  public removeDragenter() {
    // @ts-ignore
    this.targetDom.removeEventListener("dragenter", (event: any) => {
      // 拖动结束,去掉dragging
      event.target.classList.remove("dragging");
    });
  }

  public dragleave() {
    // @ts-ignore
    this.targetDom.addEventListener("dragleave", (event: any) => {
      // 在可拖动元素离开潜在放置目标元素时重置该目标的背景
      if (event.target.classList.contains("dropzone")) {
        event.target.classList.remove("dragover");
      }
    });
  }

  public removeDragleave() {
    // @ts-ignore
    this.targetDom.removeEventListener("dragleave", (event: any) => {
      // 在可拖动元素离开潜在放置目标元素时重置该目标的背景
      if (event.target.classList.contains("dropzone")) {
        event.target.classList.remove("dragover");
      }
    });
  }

  public dropEvent(event: any) {
    // 阻止默认行为(会作为某些元素的链接打开)
    event.preventDefault();
    console.log("add before");
    // 将被拖动元素移动到选定的目标元素中
    if (event.target.classList.contains("dropzone")) {
      event.target.classList.remove("dragover");
      // 删除自身
      // config.draged.parentNode.removeChild(config.draged);
      // event.target.appendChild(config.draged);
      // 创建虚拟dom  组件自定义的地方
      const vDom = document.createElement("div");
      console.log(vDom, "vDom");
      vDom.classList.add("container-box-left-component");
      event.target.appendChild(vDom);
      console.log("add component");
    }
  }

  public drop() {
    console.log("add drop listen");
    //@ts-ignore
    this.targetDom.addEventListener("drop", this.dropEvent);
  }

  public removeDrop() {
    console.log("removeEventListener drop listen");
    //@ts-ignore
    this.targetDom.removeEventListener("drop", this.dropEvent);
  }
}

export { DragOption, DropOption };

⭐页面雏形

页面采用简单的左右布局

import "./App.css";
import { useEffect, useRef, useMemo } from "react";

import { DragOption, DropOption } from "./config/const";

function App() {
  const sourceRef = useRef(null);
  const targetRef = useRef(null);
  const componentOptions = [
    {
      name: "文字",
      type: "text",
      content: "输入文字",
      id: "1",
    },
    {
      name: "图片",
      type: "image",
      content: "图片",
      id: "2",
    },
    {
      name: "列",
      type: "column",
      content: "",
      id: "3",
    },
    {
      name: "分割线",
      type: "line",
      content: "",
      id: "4",
    },
    {
      name: "按钮",
      type: "button",
      content: "按钮",
      id: "5",
    },
  ];
  const onClear = () => {
    if (targetRef.current) {
      // @ts-ignore
      targetRef.current.innerHTML = "";
    }
  };

  useMemo(() => {
    console.log("componentOptions", componentOptions);
  }, [componentOptions]);

  useEffect(() => {
    let sourceClassArr: any = [];
    if (sourceRef.current) {
      componentOptions.map((item) => {
        const source = document.getElementById(item.id);
        const dragOption = {
          source,
        };
        const DragClass = new DragOption(dragOption);
        console.log(DragClass);
        sourceClassArr.push(DragClass);
      });
    }

    return () => {
      sourceClassArr.forEach((item: any) => {
        item.removeListen();
        item = null;
      });
      console.log("卸载 sourceRef");
    };
  }, [sourceRef]);

  useEffect(() => {
    let dropClass: any = null;
    if (targetRef.current) {
      dropClass = new DropOption({
        target: targetRef.current,
      });
      console.log("dropClass");
    }
    return () => {
      dropClass?.removeListen();
      dropClass = null;
      console.log("卸载 targetRef");
    };
  }, [targetRef]);

  // @ts-ignore
  return (
    <div className="container">
      <div className="container-header">
        <button className="base-button">导出 邮件html</button>
        <button className="base-button" onClick={onClear}>
          清空
        </button>
      </div>
      <div className="container-box">
        <div className="container-box-left" ref={sourceRef}>
          {componentOptions.map((item) => {
            return (
              <div
                className="container-box-left-component basic-component"
                draggable="true"
                id={item.id}
                key={item.id}
              >
                {item.name}
              </div>
            );
          })}
        </div>
        <div className="container-box-right">
          <div
            className="container-box-right-box dropzone"
            id="droptarget"
            ref={targetRef}
          >
            {/* 可以拖到这里 */}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

页面基本布局
layout

⭐下一步计划

  1. 引入ui框架美化样式
  2. 组件可编辑(文字输入、图片上传、组件padding设置)
  3. 富文本编辑选中文本的样式放到下周实现 2.15 号左右

⭐结束

本文分享到这结束,如有错误或者不足之处欢迎指出!

city

👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 感谢你的阅读!


网站公告

今日签到

点亮在社区的每一天
去签到