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

ops/2025/2/7 8:07:14/

文章目录

    • ⭐前言
    • ⭐初始化react app
    • ⭐封装拖拽
    • ⭐页面雏形
    • ⭐下一步计划
    • ⭐结束

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-ignorethis.sourceDom.addEventListener("dragstart", (event: any) => {// 保存被拖动元素的引用 draggingthis.dragConfig.curentDrag = event.target;// 拖拽 样式  draggingevent.target.classList.add("dragging");});}public removeDragStart() {// @ts-ignorethis.sourceDom.removeEventListener("dragstart", (event: any) => {// 保存被拖动元素的引用 draggingthis.dragConfig.curentDrag = event.target;// 拖拽 样式  draggingevent.target.classList.add("dragging");});}public drag() {// @ts-ignorethis.sourceDom.addEventListener("drag", (event: any) => {// 拖拽console.log("dragging");});}public removeDrag() {// @ts-ignorethis.sourceDom.removeEventListener("drag", (event: any) => {// 拖拽console.log("dragging");});}public dragend() {// @ts-ignorethis.sourceDom.addEventListener("dragend", (event: any) => {// 在可拖动元素进入潜在的放置目标时高亮显示该目标if (event.target.classList.contains("dropzone")) {event.target.classList.add("dragover");}});}public removeDragend() {// @ts-ignorethis.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-ignorethis.targetDom.addEventListener("dragover",(event: any) => {// 阻止默认行为以允许放置event.preventDefault();},false);}public removeDragOver() {// @ts-ignorethis.targetDom.removeEventListener("dragover",(event: any) => {// 阻止默认行为以允许放置event.preventDefault();},false);}public dragenter() {// @ts-ignorethis.targetDom.addEventListener("dragenter", (event: any) => {// 拖动结束,去掉draggingevent.target.classList.remove("dragging");});}public removeDragenter() {// @ts-ignorethis.targetDom.removeEventListener("dragenter", (event: any) => {// 拖动结束,去掉draggingevent.target.classList.remove("dragging");});}public dragleave() {// @ts-ignorethis.targetDom.addEventListener("dragleave", (event: any) => {// 在可拖动元素离开潜在放置目标元素时重置该目标的背景if (event.target.classList.contains("dropzone")) {event.target.classList.remove("dragover");}});}public removeDragleave() {// @ts-ignorethis.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-ignorethis.targetDom.addEventListener("drop", this.dropEvent);}public removeDrop() {console.log("removeEventListener drop listen");//@ts-ignorethis.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-ignoretargetRef.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-ignorereturn (<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 (<divclassName="container-box-left-component basic-component"draggable="true"id={item.id}key={item.id}>{item.name}</div>);})}</div><div className="container-box-right"><divclassName="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

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


http://www.ppmy.cn/ops/156383.html

相关文章

接入 deepseek 实现AI智能问诊

1. 准备工作 注册 DeepSeek 账号 前往 DeepSeek 官网 注册账号并获取 API Key。 创建 UniApp 项目 使用 HBuilderX 创建一个新的 UniApp 项目&#xff08;选择 Vue3 或 Vue2 模板&#xff09;。 安装依赖 如果需要在 UniApp 中使用 HTTP 请求&#xff0c;推荐使用 uni.requ…

【自学笔记】网络安全-自学笔记

目录 相关网站推荐 WEB&#xff08;应用&#xff09;安全 学习路线 推荐 书籍 网站 在线靶场 基础 XSS攻击 CSRF漏洞 劫持攻击 点击劫持 SSRF漏洞 文件包含漏洞 文件上传漏洞 XXE漏洞 WebShell 解析安全 RCE漏洞 SQL注入漏洞 反序列化漏洞 条件竞争 通信…

排序算法--计数排序

唯一种没有比较的排序(指没有前后比较,还是有交换的)。统计每个元素出现的次数&#xff0c;直接计算元素在有序序列中的位置&#xff0c;要求数据是整数且范围有限。适用于数据为小范围整数&#xff08;如年龄、成绩&#xff09;&#xff0c;数据重复率较高时效率更优。可用于小…

C中静态库和动态库的使用

2.使用尖括号包括 如果要使用尖括号包括头文件,有两种方法 1.将头文件移动到标准头文件目录,linux为/usr/local/include.windows下为C:\MinGW\include 2.编译时指定头文件目录,gcc -I/头文件目录 … 编译时-I参数就是用于指定头文件目录 3.静态库 将文件编译为静态库,可以…

【大数据技术】搭建完全分布式高可用大数据集群(ZooKeeper)

搭建完全分布式高可用大数据集群(ZooKeeper) apache-zookeeper-3.8.4-bin.tar.gz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群 ZooKeeper 的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/software目录下,软件…

手机上运行AI大模型(Deepseek等)

最近deepseek的大火&#xff0c;让大家掀起新一波的本地部署运行大模型的热潮&#xff0c;特别是deepseek有蒸馏的小参数量版本&#xff0c;电脑上就相当方便了&#xff0c;直接ollamaopen-webui这种类似的组合就可以轻松地实现&#xff0c;只要硬件&#xff0c;如显存&#xf…

[免费]微信小程序智能商城系统(uniapp+Springboot后端+vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序智能商城系统(uniappSpringboot后端vue管理端)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序智能商城系统(uniappSpringboot后端vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…

UG NX二次开发(Python)-API函数介绍与应用实例(三)-UFLayer类操作

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1 前言2、UFLayer类说明3、获取当前工作图层4、移动对象到特定的图层1 前言 采用Python语言进行UG NX二次开发的帮助材料很少,采用录制的方法是一种比较容易实现的方式,但是使用UFun函数更容易上…