T恤DIY定制系统实战:拖拽画布与下单逻辑如何闭环D2C

📅 发布时间:2026/10/18 17:10:10
T恤DIY定制系统实战:拖拽画布与下单逻辑如何闭环D2C
简介这是一套专为移动端设计的T恤在线DIY定制系统源码面向前端学习者、交互设计爱好者以及电商平台开发者解决用户在线挑选模板、添加文字图形并实时预览的个性化定制需求。压缩包约14.19MB内含页面入口、图片素材、JavaScript脚本、字体与样式表等核心前端文件目录结构清晰便于直接运行与二次开发。系统基于HTML5 Canvas实现绘图交互核心代码展示了图形绘制、文字叠加、事件绑定及移动端适配等处理思路虽然当前版本尚未将文字完整写入画布但恰好为开发者预留了完善空间可继续实现设计合并、保存与下单流程。整套代码覆盖了前端页面布局、DOM操作、Canvas 2D绘图等常见知识点适合有一定HTML/CSS/JS基础的学习者对照练习。目前已有797人浏览学习可作为课程设计、毕业设计或在线定制商城的前端原型参考。1. t恤diy定制系统当拖拽画布遇上下单逻辑D2C定制才真正闭环做一套t恤diy定制系统最容易被低估的其实不是后端那套订单逻辑而是前端那个拖拽画布。很多团队照着设计稿开工结果画布拖拽卡顿、图层叠放错乱、用户辛辛苦苦排好的版面一刷新全丢。这套t恤diy定制系统把这些环节完整串了起来从用户选空白T恤、上传图片、拖拽缩放排版到 T 恤 3D 预览、加购下单后端统一接收设计数据并生成订单。它是一套前后端分离、能直接部署跑通的设计 电商复合项目。适合正在做服装定制类商城、商品自定义编辑器、或者想研究 Canvas 图像合成方案的中级开发者也适合拿它当毕设或者 D2C 业务脚手架。2. 架构与目录前端负责所见即所得后端负责存得住能交付2.1 技术选型为什么是前后端分离 JSON 描述设计稿t恤定制的核心矛盾在于设计稿是实时生成的视觉状态而订单是长期保存的业务数据。如果把画布上的元素挨个儿存字段光坐标、层级、旋转、透明度就能把表结构撑爆而且每加一种素材就得改表。这套系统换了个思路——设计稿存一份 JSON由前端画布编辑器负责把 JSON 渲染成像素后端只负责保存这份 JSON在下单时把 JSON 合成为一张平面设计图存档。前端技术栈是 Vue 3 Canvas。画布内部基于 Canvas 自绘事件系统参考了 fabric.js 的交互模型每个元素对象有 left、top、scaleX、scaleY、angle 这些属性渲染时统一经过 matrix 变换。后端用的是 Spring Boot 风格的 REST 接口因为定制类业务的订单状态流转比较多待支付、待制作、已完成、已取消Java 系的枚举和状态校验写起来更稳。数据库选型是 MySQL单表单库就够用没必要上 MongoDB真正上传的图片素材默认走本地目录存储量大之后可以平滑切到对象存储接口不用改。数据流转路径整个串起来是这样的用户在前端拖拽一个元素画布编辑器实时重绘 - 用户点保存设计前端把当前画布上所有元素序列化成 JSON 字符串 - 后端校验 JSON 结构写入 t_design 表和 t_order 表 - 后端异步把设计稿合成 PNG 缩略图 - 用户在订单列表看到的就是设计稿缩略图。整体上是一个前端生产内容后端托管内容的分工模型。2.2 目录结构前端编辑器与后端服务怎么分家这套系统的目录拆分得很清楚前端和后端是两个独立工程只在接口层交互。拆包之后看到的目录结构大致是这样的t-shirt-diy/ ├── frontend/ # Vue 3 前端工程 │ ├── src/ │ │ ├── components/ │ │ │ ├── editor/ # 画布编辑器核心组件 │ │ │ │ ├── DiyCanvas.vue # 画布容器 │ │ │ │ ├── LayerPanel.vue # 图层面板 │ │ │ │ ├── ToolbarPanel.vue # 工具栏上传/字体/颜色 │ │ │ │ └── Preview3D.vue # T恤3D预览 │ │ │ ├── order/ # 订单相关组件 │ │ │ └── common/ # 通用UI组件 │ │ ├── api/ # axios 接口层 │ │ ├── store/ # Pinia 状态管理 │ │ └── utils/ │ │ ├── canvas-transform.js # 坐标/矩阵变换工具 │ │ └── serialize.js # 设计稿 JSON 序列化/反序列化 │ └── package.json ├── backend/ # Spring Boot 后端工程 │ ├── src/main/java/com/diy/ │ │ ├── controller/ # 设计保存、订单、素材上传接口 │ │ ├── service/ # 业务逻辑层 │ │ ├── entity/ # 数据实体 │ │ ├── mapper/ # MyBatis Plus Mapper │ │ └── config/ # 跨域、文件上传、静态资源配置 │ └── src/main/resources/ │ ├── application.yml # 数据源、文件路径配置 │ └── mapper/ # XML 映射文件 ├── sql/ │ └── init.sql # 建库建表脚本 └── README.md前端把编辑器拆成四个核心组件DiYCanvas 管渲染和拖拽LayerPanel 管图层顺序ToolbarPanel 管素材操作Preview3D 管 T 恤贴图效果预览。这样分开是有道理的拖拽逻辑改动频率高和图层状态管理解耦之后不容易互相踩。后端则完全是经典的三层结构Controller 层做参数校验和路由Service 层处理状态流转Mapper 层做数据持久化。2.3 表结构设计一张设计表 一张订单表解决定制业务t恤定制涉及的实体不多核心就是两张表。设计表存设计稿本身订单表存交易数据。看表结构就能理解它的业务边界。表名字段说明t_designid设计稿ID主键user_id用户IDdesign_json画布序列化后的 JSON 字符串thumb_url合成后的缩略图访问路径create_time创建时间t_orderid订单IDorder_no订单编号业务号design_id关联 t_design 表tshirt_colorT恤底色白/黑/藏青tshirt_size尺码 S/M/L/XLamount订单金额status订单状态 0待支付/1待制作/2已完成/3已取消create_time下单时间设计表和订单表分开的最大好处是同一个设计稿可以重复下单用户改了 A 设计可以下三件不同颜色的单只要改 tshirt_color 就行设计稿不用复制三份。这在定制商城场景里非常实用比把 JSON 直接怼进订单表要灵活得多。3. 核心模块拆解画布拖拽、图层管理、设计稿保存与订单合成3.1 画布编辑器用事件委托实现元素拖拽与缩放画布编辑器的核心是一个 Canvas 实例加一套自绘的交互事件。所有元素图片、文字、贴纸都挂在一个 elements 数组上每个元素是一个普通对象拖拽的本质就是改这个对象的坐标属性然后重绘画布。这套系统的画布交互用事件委托处理不单独给每个元素绑定监听减少内存开销。// canvas-transform.js 核心坐标转换逻辑 export function getCanvasMousePos(e, canvas) { const rect canvas.getBoundingClientRect(); return { x: e.clientX - rect.left, y: e.clientY - rect.top }; } export function hitTest(elements, pos) { // 倒序遍历最先命中最上层元素 for (let i elements.length - 1; i 0; i--) { const el elements[i]; if ( pos.x el.left pos.x el.left el.width * el.scaleX pos.y el.top pos.y el.top el.height * el.scaleY ) { return { index: i, element: el }; } } return null; }鼠标按下时记录起始坐标鼠标移动时计算偏移量并实时更新元素位置鼠标抬起时把最终位置写回数据源。这里有个容易踩的细节Canvas 的坐标是相对画布容器的必须用 getBoundingClientRect 减去偏移量直接拿 e.clientX 当画布坐标会导致拖拽错位。元素属性中 scaleX 和 scaleY 不是直接改 width 和 height好处是旋转、镜像这类变换可以和缩放叠加计算。比如用户水平翻转一个图案只需要把 scaleX 乘 -1不用重新计算宽度。图片上传进来之后统一设置一个最大初始尺寸防止用户传了 4000px 的图把画布局部撑爆。常见做法是上传时做一次图片压缩把超过 2000px 的长边缩到 2000px 以内再放进画布画布性能会好很多。3.2 图层管理数组顺序即视觉叠放顺序图层管理的逻辑比较直白就是维护一个数组的增删改。越靠数组末尾的元素越晚绘制视觉上就越靠上。用户在图层面板拖拽某个图层时底层做的是 splice 数组元素的位置。// LayerPanel.vue 中图层排序的封装 function moveLayer(targetIndex, direction) { const list editorState.elements; const newIndex targetIndex direction; // direction 为 -1 上移1 下移 if (newIndex 0 || newIndex list.length) return; const [removed] list.splice(targetIndex, 1); list.splice(newIndex, 0, removed); canvasRenderer.render(list); }render 方法就是清空画布然后按数组顺序把每个元素画一遍先设置 transform再 drawImage 或者 fillText。为了显示效果画布底色与 T 恤底色保持一致这样用户看到的设计图和最终 T 恤效果相差无几。透明度、旋转角度、元素显隐这类属性也全部存在元素对象里序列化时直接整个对象转 JSON所以这套系统天然支持取消上一步——只需要在前端维护一个元素数组的历史快照栈每次 push 一下CtrlZ 的时候就 pop 回来重绘。虽然代码量不大但这是 DIY 编辑器最常用的功能。3.3 设计稿保存前后端怎么约定 JSON 格式保存设计稿是整个系统最关键的接口。前端把画布上的所有元素放进一个对象里包括画布尺寸、背景色、元素列表三个部分。后端拿到 JSON 后只做结构合法性校验然后原样存库。关键点在于接口约定要稳定前端加新功能比如新增一个滤镜字段时后端不能因为不认识新字段就报错。{ canvasWidth: 750, canvasHeight: 900, backgroundColor: #ffffff, elements: [ { type: image, src: /upload/2024/abc.png, left: 120, top: 80, width: 300, height: 300, scaleX: 1, scaleY: 1, angle: 15, opacity: 0.9 }, { type: text, text: 自由之路, fontFamily: 思源黑体, fontSize: 42, fontColor: #ff6600, left: 210, top: 430 } ] }我一般把保存接口设计成宽容写入后端校验层级只检查 elements 是不是数组、每个元素有没有 type 和 left、top 坐标至于图片滤镜、字体间距这些扩展字段原样保留不去动它。这样前端迭代新交互时后端接口不用跟着发版。订单表通过 design_id 关联设计表用户下单时后端会先把 design_json 取出来调用合成服务生成一张 PNG 缩略图存到本地文件目录再把访问路径写到 t_design 的 thumb_url 字段。这个合成过程放在 Service 层异步执行核心代码用 Java 的 ImageIO 直接画// 设计稿缩略图合成服务 public String renderDesignThumb(String designJson, String outputDir) { JSONObject obj JSON.parseObject(designJson); int width obj.getInteger(canvasWidth); int height obj.getInteger(canvasHeight); BufferedImage image new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d image.createGraphics(); g2d.setColor(parseColor(obj.getString(backgroundColor))); g2d.fillRect(0, 0, width, height); JSONArray elements obj.getJSONArray(elements); for (int i 0; i elements.size(); i) { drawElement(g2d, elements.getJSONObject(i), outputDir); } g2d.dispose(); String fileName System.currentTimeMillis() .png; ImageIO.write(image, png, new File(outputDir, fileName)); return fileName; }这段代码的意义在于后端用自己的渲染逻辑还原了前端画布的视觉结果不依赖前端截图也不依赖 Puppeteer 那种重方案。注意 drawElement 里加载图片要用 ImageIO.read 的绝对路径因为 Java 的 ImageIO 不认识相对路径的 src。如果图片存储将来迁到 OSS这个方法的入参 src 会变成完整的 http 地址逻辑不用改。3.4 购物车与下单状态流转的边界约束前端把选好的 T 恤颜色、尺码、数量连同设计稿 ID 一起提交到订单接口。后端 Service 层有明确的订单状态机约束新建订单时状态为待支付支付回调后变为待制作制作完成后变为已完成用户可在待支付状态下取消。核心是禁止非法状态跳转比如待支付直接跳到已完成这在代码里用枚举校验挡掉。public Order createOrder(CreateOrderRequest req) { Design design designMapper.selectById(req.getDesignId()); if (design null) { throw new BizException(设计稿不存在); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setDesignId(design.getId()); order.setTshirtColor(req.getTshirtColor()); order.setTshirtSize(req.getTshirtSize()); order.setAmount(calcAmount(req)); order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); orderMapper.insert(order); return order; }createOrder 里的参数校验分两层第一层是基础必填校验比如 designId 是否为空、尺码是否是枚举内的值这些可以放在 Controller 层用注解处理第二层是业务校验比如设计稿是否存在、T 恤颜色和尺码组合是否在商品配置表里有对应库存。真实项目里如果把全部校验堆在一起报错信息会很模糊用户根本不知道是设计稿丢了还是库存不够。分开之后每层只对特定问题报错排查问题效率高很多。4. 运行指南从建库到下单全流程跑通4.1 环境依赖清单运行这套系统需要以下环境JDK 1.8 以上、Maven 3.6、Node.js 14 以上、MySQL 5.7 以上。前端构建工具用的 Vite后端构建工具用的 Maven都比较常规。首次拿到源码包先看根目录 README.md 有没有特殊说明因为 Spring Boot 的配置文件和环境强相关主要调整数据源路径和文件上传目录。项目默认的本地运行端口配置是这样后端 8080前端 5173前端通过 Vite 的代理把 /api 开头的请求转发到 8080避免本地开发时跨域问题。改端口的话要同步修改前端 vite.config.js 里的 proxy target 配置。4.2 初始化数据库建库脚本在 sql/init.sql 里包含建库、建表、插入基础商品数据三个部分。执行之前先确定 MySQL 的字符集要设成 utf8mb4因为 design_json 字段里可能存 Emoji 表情或者生僻字utf8 字符集存不进去会出现 Incorrect string value 报错。mysql -uroot -p sql/init.sql执行完检查一下 t_design 表和 t_order 表是否创建成功。如果脚本里用的库名不是你想用的直接改 init.sql 第一行的 CREATE DATABASE 和 USE 语句。后端 application.yml 里的数据库连接地址要和你实际库名一致。4.3 启动后端服务后端是标准的 Spring Boot 工程用 Maven 打包后运行 jar 包即可。先在 application.yml 里配置好 MySQL 账号密码。本地缺少数据源时 Spring Boot 会启动失败报错日志里会明确提示 Failed to configure a DataSource。# application.yml 关键配置 server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/t_shirt_diy?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 10MB custom: upload-dir: D:/data/t-shirt-diy # 上传文件与合成缩略图的根目录custom.upload-dir 这个自定义配置项很容易被忽略但它是整套系统能不能正常显示图片的关键。上传的素材图和后端合成缩略图都会写到这个目录前端访问图片时通过项目里的一个静态资源映射把 /files/** 路径指向这个目录。目录不存在时启动类里最好加一个初始化逻辑自动创建避免上传时才报错。打包和启动命令如下cd backend mvn clean package -DskipTests java -jar target/t-shirt-diy-0.0.1-SNAPSHOT.jar启动成功之后可以先访问 http://localhost:8080/api/health 确认服务在线。4.4 启动前端并开始设计前端启动前要先装依赖。前端代码里和后端相关的地方只有 API 请求路径没有硬编码 IP所以本地启动很简单cd frontend npm install npm run dev浏览器打开 http://localhost:5173进到编辑器页传一张图片把图片拖到 T 恤图案区域之外再拖回来测试拖拽和缩放是否顺滑。然后点保存设计再到订单管理页提交一单看后端缩略图是否生成。如果缩略图没出来先看后端的 outputDir 目录里有没有 PNG 文件有文件但浏览器访问不到就是静态资源映射路径问题。整个流程跑通后你才能真正验证这套系统不是只有设计器能看而是设计稿和订单数据确实闭环了。5. 避坑指南我在跑这套系统时踩过的五个坑5.1 画布保存后刷新设计稿丢失现象用户辛辛苦苦排好的版面点保存再刷新页面画布空白。去数据库看t_design 表里 record 有数据但前端画布渲染不出来。原因前端序列化时把图片元素的 src 存成了相对路径 /upload/xxx.png重新加载设计稿时这个相对路径在开发环境能访问但画布渲染依赖的图片还没加载完成就开始绘制 Canvas导致绘制时拿不到图片对象画布全部空白。解决画布重绘前要先保证所有图片元素加载完成。常见的做法是序列化时把 src 转存成完整路径渲染前用 Promise.all 预加载全部图片全部成功后统一 render。加载失败的元素也不应该直接跳过要给用户一个图片缺失的占位状态防止他误以为设计丢了。5.2 大图上传后本地能跑部署到服务器却报目录权限错误现象本地开发一切正常部署到 Linux 服务器后用户一上传图片就报错日志显示 No such file or directory。原因前文提到 custom.upload-dir 配置的是本地绝对路径部署到服务器后没人创建这个目录Java 在写入文件时发现目录不存在直接抛 IOException。Spring Boot 默认不会自动创建不存在的目录这个问题本地几乎发现不了。解决在启动类中加一个 ApplicationRunner启动时读取 custom.upload-dir 配置并判断目录是否存在不存在就用 Files.createDirectories 递归创建。从那次之后我再也不手写 mkdir 命令了让程序保证目录就绪才是稳妥的。5.3 用户传的是中文名图片下载后无法打开现象上传一张命名为 我的设计稿.png 的图片之后前端能正常显示合成效果但用户下载订单里的设计图时打不开。原因上传文件名中包含中文和空格后端保存文件时把中文保留了生成的访问 URL 没有做 URLEncoder 编码浏览器访问时 URL 里的中文路径被解析成乱码导致下载的文件损坏。解决后端保存文件时统一重命名用 UUID 或者时间戳做文件名原始文件名只保留在后端实体表的 originalName 字段里供用户下载时做 Content-Disposition 回显。这样文件名是 ASCII 字符URL 解析完全不会有编码问题。5.4 T 恤 3D 预览贴图方向不对现象用户在设计画布上把图案旋转了 15 度切到 T 恤 3D 预览发现图案整体方向没错但局部区域有明显拉伸变形。原因3D 预览是基于 T 恤前片平面图做贴图模拟使用的是简单的 canvas drawImage 到某个矩形区域没有做透视变换。当图案靠近领口或下摆这类曲率较大的区域时平面图直接贴上就像粘贴纸一样不自然。解决在不引入 Three.js 这类重型依赖的情况下最快办法是把预览模型切成领口、前胸、下摆几个区域分别对 mockup 模板上的对应位置做适配并在代码注释里写明预览效果和真实样衣的区别。这个系统的做法是提供一个平铺预览模式用户能看平面设计图也能看模拟上身效果两种模式切换在视觉上减轻了失真感。5.5 订单状态被外部接口回调改乱了现象支付回调到达后订单状态被改成待制作但此时用户在前台点了取消订单后台也能改成功导致仓库制作了一件已取消订单的 T 恤。原因状态更新逻辑里只判断了当前状态没有控制谁在什么时候允许改到什么状态。支付回调线程和用户请求线程同时操作同一条订单记录后到的写入覆盖了先到的状态丢失了中间的取消操作。解决Service 层更新状态时加上条件 UPDATESQL 里带 WHERE status 当前状态影响行数为 0 则说明状态已被其他操作改变直接抛出异常。逻辑上的教训是状态机跳转必须要带乐观锁不能先 SELECT 再 UPDATE。6. 二次开发建议从 T 恤定制扩展到通用 D2C 设计器这套系统最有价值的扩展点是把设计稿 JSON这个核心抽象独立出来。T 恤只是第一个品类产品里可以加卫衣、帆布袋、手机壳只要新增一套 mockup 模板和一个规格配置后端表结构完全不用动。我的建议是优先新增一个多规格模板文件里面声明产品类型、可定制区域坐标、限制的图片最小尺寸、可选的颜料/材质颜色。前端根据这个配置动态生成画布尺寸和操作选项后端订单表的 tshirt_type 字段改成更通用的 product_type现有存量数据做一个映射迁移就能平滑升级。具体到一个完整流程的验证方法每次改完画布交互逻辑我会手动跑一遍——新建空白画布导入一张设计稿 JSON拖拽元素、旋转、改透明度保存再打开订单详情确认缩略图和设计稿 JSON 一致。这套回归流程虽然原始但覆盖了画布编辑、序列化、存储、渲染四个关键链路能拦住九成的基本回归问题。另一个可落地的扩展是接入设计设计模板功能把一套好看的版面存成模板 JSON用户选模板后用 JSON 深拷贝生成自己的画布替换模板中的图片即可。实现上没有特殊难度关键是深拷贝要保留元素数组的引用独立性别让用户改模板图时把模板本身也改了。可以再加一层图层锁定标记模板底层的图层不允许编辑这样既能保护版权又给用户保留了自由发挥空间。从那以后我每次接手这类自定义编辑器项目都会先把设计稿 JSON 的版本兼容摆到第一优先级每次改字段都强制走一遍增量兼容检查和回归流程。这套系统的边界虽然朴素但它把所有关键的技能点都串起来了。希望帮到你。本文还有配套的精品资源点击获取