简介:面向手机端的T恤DIY定制系统项目压缩包,定位于为前端开发者、独立设计师及电商二次开发人员提供一套可扩展的在线T恤定制交互原型。系统以HTML5 canvas为绘制核心,通过模板选择、文字与图形叠加等操作实现个性化设计,并对canvas与文字合成、字体加载、交互反馈等关键环节给出了可继续完善的基础结构。压缩包整体约14.19MB,包含页面入口、JS逻辑、CSS样式、图片素材与字体文件等目录,便于按模块拆解与改造。目前已有797人学习下载。读者可获得一套完整的T恤DIY设计前端工程雏形,包括拖拽/选择类交互逻辑、canvas绘图思路、多字体方案及模板资源配置方式,尤其适合需要快速搭建定制类电商前台或深入学习canvas交互开发的进阶学习者。
1. T恤DIY定制系统:一套能直接落地的设计交互源码
做定制类电商的人应该都有同感——T恤DIY这个品类,难的不是后端下单,而是前端那个“让用户自己动手设计”的交互过程。素材拖拽、文字编辑、图层叠加、价格联动,任何一个环节卡顿或逻辑混乱,用户都会直接放弃下单。这套T恤DIY定制系统源码,解决的就是从“用户上传图片”到“生成可下单的定制T恤”这条完整链路。它不是一个纯展示的Demo,而是包含设计画布、实时预览、SKU联动计价、订单数据落库的完整方案。适合正在做定制类商城、对前端交互有一定基础、想快速搭起一套可复用的T恤设计模块的开发者。系统的核心价值在于:把最难的画布交互部分封装好了,你拿到手之后改改接口就能嵌入自己的项目。
2. 技术选型与项目初始化:先看清整体架构再动手
2.1 技术选型:为什么是Web端优先,而不是小程序端
这套系统在技术栈的选择上走的是“快速落地”路线。前端采用Vue全家桶配合Canvas渲染方案,后端为轻量级接口服务,数据库用MySQL承接订单和SKU数据。选这套组合的原因很直接:T恤设计交互的核心场景在电脑端——用户需要高精度地拖拽素材、微调文字、切换图案,鼠标的精细操作远胜触屏。同时,Canvas在Web端的生态最成熟,对图片合成、像素处理的支持也最完善。
如果你打算同时支持小程序端,这套系统的前端绘制逻辑无法直接复用,你需要用小程序的原生Canvas重新实现一遍画布部分,而后端的订单接口和SKU逻辑是通用的。实际项目里,不少团队会先上线Web端验证设计交互,再考虑小程序端,这套源码的结构正好匹配这个节奏。
2.2 环境准备与项目初始化:三个步骤跑起本地环境
拿到源码包解压之后,项目结构分三块:前端目录、后端目录、数据库脚本。初始化顺序建议“先数据库后后端再前端”,因为前端接口需要后端先能访问。
先看数据库初始化脚本,在MySQL中执行:
CREATE DATABASE tshirt_diy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE tshirt_diy; SOURCE /你的解压路径/sql/init.sql;这里有个细节:utf8mb4字符集是必须的。素材库里有中文字体配置和可能的Emoji图案,如果用了utf8,中文能正常存,一旦用户上传的图片文件名带生僻字或特殊符号,就会写入失败。我见过不止一个项目在字符集上翻车,表现是订单提交时有概率报Incorrect string value错误。
初始化完成后,按顺序启动后端服务和前端开发服务器:
# 后端服务,默认监听3000端口 cd server npm install node app.js# 前端开发服务器,默认监听8080端口,需要配置跨域代理 cd client npm install npm run dev两个终端分别跑起来,浏览器打开前端地址就能看到设计器页面。注意前端目录里的vue.config.js有代理配置,把/api请求转发到后端3000端口,如果你改了后端端口,这里要同步修改。这套结构是本地开发和线上部署通用的,后端上线时建议用PM2或类似进程守护工具跑Node服务,避免进程意外退出。
2.3 目录结构与会话级画布存档:搞清楚每个目录的职责
解压后你会看到一套按职责划分的目录结构,理解它的分层方式比直接改代码更重要:
client/src:前端源码。其中views/designer是核心设计器页面,components/canvas下封装了画布操作组件,store/里有Vuex状态管理,专门存当前设计会话。client/src/utils:素材处理工具和价格计算函数。server/routes:后端接口路由,包括订单创建、素材列表、SKU查询。server/utils:图片合成服务,负责把用户设计好的画布导出成高清PNG。
会话级画布存档是我比较认可的一个设计:设计器中的每一次操作(拖拽、换色、素材增删)都会写入Vuex状态,组件切�时不会丢失;刷新页面时从本地缓存恢复。它把“用户设计到一半"的场景跑通了——如果用户刷新就丢设计稿,等于逼用户一次性完成,转化率会很难看。这套源码在client/src/store/modules/design.js里实现了设计数据的序列化和反序列化,本地存储的键值字段封装得也比较清晰,你可以直接复用。
3. 核心功能模块的实现逻辑:从画布交互到订单提交
3.1 画布交互:拖拽、缩放、旋转与图层管理的实现方式
设计器页面的核心交互就是素材元素的操作。用户拖拽一个图形到T恤轮廓上,除了要让它出现,还要能选中、移动、缩放、旋转、调整层级顺序。这套源码没有用现成的Canvas框架,而是自己实现了基于坐标系的矩阵变换逻辑。
看一个关键代码片段,它负责画布元素的状态管理:
// client/src/store/modules/design.js const state = { elements: [], // 画布上的元素数组 selectedElementId: null, // 当前选中的元素ID history: [] // 操作历史,用于撤销重做 }; const mutations = { UPDATE_ELEMENT(state, payload) { const index = state.elements.findIndex(el => el.id === payload.id); if (index !== -1) { state.elements[index] = { ...state.elements[index], ...payload.updates }; } }, ADD_HISTORY(state) { // 快照当前元素数组,入栈,最多保留30步 if (state.history.length >= 30) { state.history.shift(); } state.history.push(JSON.parse(JSON.stringify(state.elements))); } };这个实现的核心思路是:所有画布元素的数据都收敛到Vuex的elements数组里,Canvas渲染层只是这个数组的“投影”。每次拖拽触发的是UPDATE_ELEMENT而不是直接改DOM或Canvas坐标,这样组件间的状态同步就简单了。历史记录用数组快照,限制30步是实际的取舍——每个快照包含元素坐标、尺寸、旋转角、图层ID,存的太多会导致性能下降。
操作Canvas渲染时,坐标变换是这个模块的难点。比如拖动图片时有一个基础公式:新位置 = 旧位置 + 鼠标移动的距离。但如果元素处于缩放或旋转状态,坐标计算就要叠加变换矩阵。源码里把旋转角度和缩放比例单独存储,计算元素实际边界时用ctx.translate和ctx.rotate还原变换状态,这块建议重点看components/canvas/CanvasStage.vue里的getElementBounds方法,理解透了你就能自行扩展素材倾斜、翻转等操作。
3.2 参数定制链路:颜色、尺码、面料与SKU联动
T恤定制不能只是图案设计,还要把商品参数贯穿起来。这件T恤提供多种颜色和尺码,面料选项影响价格,用户选错尺码时还需要有友好提示。源码中的SKU联动逻辑是前后端分离实现的——前端拉取商品可变规格列表,后端校验所选组合是否有效。
看后端的SKU查询接口实现:
// server/routes/product.js const express = require('express'); const router = express.Router(); router.get('/skus/:productId', async (req, res) => { const { productId } = req.params; const { color, size, fabric } = req.query; // 组合三个维度查询有效的SKU记录 const sql = ` SELECT sku_id, price_extra, stock FROM skus WHERE product_id = ? AND color = ? AND size = ? AND fabric = ? `; const results = await db.query(sql, [productId, color, size, fabric]); if (results.length === 0) { return res.status(404).json({ code: 'SKU_NOT_FOUND', message: '该组合暂时缺货,请尝试其他搭配' }); } res.json({ code: 0, data: { skuId: results[0].sku_id, priceExtra: results[0].price_extra, stock: results[0].stock } }); });这个接口的关键在于“组合查询”和“库存校验”合二为一了。前端拿到price_extra后实时更新价格,拿到stock后控制下单按钮的可点击状态。注意接口中没有一个“查询所有SKU”的逻辑。
前端拿到SKU数据后的状态逻辑也很关键,在store/modules/sku.js中维护当前已选参数:
const state = { selectedOptions: { color: null, size: null, fabric: null } }; const getters = { currentSkuValid: (state, getters, rootState) => { return state.selectedOptions.color && state.selectedOptions.size && state.selectedOptions.fabric; }, currentSkuId: (state, getters, rootState) => { return getters.skuIdMap[`${state.selectedOptions.color}_${state.selectedOptions.size}_${state.selectedOptions.fabric}`]; } };skuIdMap是前端在页面加载时预取的所有SKU映射表,这样切换选项时不需要每次都请求后端,页面响应更快。但这也是容易出错的点:如果你在后端修改了SKU组合,常见做法是用productId维度全量刷新映射表,避免请求数据与服务端不一致。
3.3 加价规则与价格计算:梯度计价与实时刷新
T恤定制的价格逻辑通常是“基础价 + 参数加价 + 设计加价”。设计加价的规则比较复杂:允许用户免费添加1个设计图案,第二个图案起每个加收固定费用。这个梯度计价的实现在前端utils/price.js中,直接给出关键代码:
// client/src/utils/price.js export function calcPrice(basePrice, skuExtra, elementCount) { let designExtra = 0; // 第一个图案免费,第二个起每个加收15元 if (elementCount > 1) { designExtra = (elementCount - 1) * 15; } // 设计素材数量超过5个,超过部分每个加收10元 if (elementCount > 5) { designExtra += (elementCount - 5) * 10; } return basePrice + skuExtra + designExtra; }这个计价方式适合用来理解系统的设计思路——它不是固定价格,而是允许你通过配置调整加价阈值和金额。实际业务中你可能要做“免费图案数可配置”“不同素材类型不同加价”等调整,改动核心就是这个函数。要注意的是,这种梯度计价是前端展示用的,下单时后端务必基于订单明细重算一遍价格,不能直接信任前端提交的总价。
4. 常见问题与排查:四个必踩的坑
4.1 图片跨域导致的Canvas污染
现象:用户上传一张网络图片作为T恤图案,设计器里显示正常,但最终导出图片时Canvas变成空白,控制台报Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases。
原因:Canvas被跨域图片“污染”了。浏览器出于安全策略,一旦Canvas绘制过不属于当前域名下的图片资源,toDataURL()和toBlob()就会被禁用。上传到本地或用内网地址访问时正常,是因为同源。
解决:为网络图片统一走服务端代理下载,或者使用服务端接口。常见做法是在后端做一个图片转发接口,前端把图片URL传给后端,后端以流的方式返回图片二进制数据,这样前端拿到的图片资源就是同域的了。如果你只想快速排查,最简单的验证方法是:把所有测试图片放在和前端域名同域的目录下,操作就不报错了。
4.2 移动端触摸事件的坐标偏移
现象:用手机浏览器打开设计器,拖动素材时元素跑偏,触摸点对应的不是素材本体,而是某个偏移的位置。
原因:移动端触摸事件和鼠标事件的对象结构不同。鼠标事件里用的是clientX/clientY,触摸事件里要用touches[0].clientX/touches[0].clientY。如果代码里只绑定mousedown/mousemove/mouseup,在触屏设备上会异常;如果绑定了touchstart/touchmove/touchend却没适配坐标,就会出现偏移。
解决:不要同时监听鼠标和触摸事件,而是做统一的指针事件封装——判断事件类型,取对应的坐标字段。这套源码中的CanvasStage.vue用的是pointerdown/pointermove/pointerup事件,天然兼容鼠标和触摸。如果你拿到的版本还是老的鼠标事件写法,建议升级成指针事件。
4.3 订单详情里的缩略图变成空白
现象:用户下单成功后,订单列表里的缩略图显示正常,但订单详情页的缩略图偶尔空白。
原因:订单缩略图是生成图片时的异步环节。用户提交订单后,服务端异步执行图片合成任务。列表页读取的是缓存,详情页读取的是服务端最新生成结果,如果合成任务还没完成就返回了详情页数据,缩略图字段就是空的。
解决:不要让异步环节阻塞到下单返回链路中。把图片合成任务和下单步骤解耦——下单时记录设计数据JSON,图片合成通过队列异步处理,生成完成回写数据库,前端轮询或主动刷新图片地址。这套源码在后端有一个定时扫描待处理订单并生成图片的脚本,所以你拿到手后需要确认这个脚本在线上是“启用”状态。很多部署后订单图片空白的案例,排查到最后都是这个脚本被停了或者执行报错。
4.4 高并发下单时SKU库存扣减超卖
现象:促销场景下某热门尺码的T恤,实际卖出数量超出库存数量。
原因:扣减库存使用了一条“先查后改”的流程,缺乏数据库层面的原子性和事务隔离保障。
// 错误示例:先查库存再扣减 const stock = await db.query('SELECT stock FROM skus WHERE sku_id = ?', [skuId]); if (stock > 0) { await db.query('UPDATE skus SET stock = stock - 1 WHERE sku_id = ?', [skuId]); }两个请求同时查到库存为1时,两者都判断可扣减,都执行了UPDATE,最终库存变成-1。
解决:把“检查+扣减”合并成一个原子操作,且必须影响行数大于等于1才判定为成功:
// 正确做法:一条SQL原子扣减 const result = await db.query(` UPDATE skus SET stock = stock - 1 WHERE sku_id = ? AND stock > 0 `, [skuId]); if (result.affectedRows === 0) { // 扣减失败说明库存不足,中止下单 return res.status(400).json({ code: 'OUT_OF_STOCK', message: '库存不足' }); }这段代码的思路是:让数据库来保证“库存大于0才允许扣减”,而不是在应用层用SELECT判断,这样并发下每一笔扣减都在数据库层面收到约束。实际项目中如果SKU数量特别大、并发特别集中,还可以在上游加分布式锁或限流,但先把这条SQL改成原子扣减是最基本的兜底。
5. 上线前要做的验证与优化:把Demo推成能用的系统
5.1 素材防滥用与版权审核机制
做定制系统绕不开版权问题。这套源码把“用户上传图片”和“平台素材库”分成两条链路。平台素材库中的素材需要你预置,要确保这些素材是已获得授权的;用户上传的素材,则需要在提交订单时增加一个协议确认环节,声明用户对素材拥有使用权。上线前把这两点确认清楚,比任何安全补丁都重要。
5.2 图片输出的三种尺寸配置与合成效率
用户设计完成后,系统要导出一张高清图用于印刷,同时生成一张缩略图用于订单详情外还有一张供前端预览。这三种尺寸的需求不同——印刷图需要至少300dpi(建议宽度不低于2000像素),订单详情图800px左右,列表缩略图300px。后端合成脚本中建议做三个配置项,对应不同输出路径。由于合成是I/O密集型任务,建议参考固定数量的worker并发执行,避免占用过多CPU资源,导致与后端主进程抢占资源。
5.3 设计图与订单数据的强关联:别丢数据
我在一个模拟项目里吃过亏——产品演示时一切正常,但一上线,后台就能看到不少订单创建了而设计图文件却是零字节。后来排查发现,问题出在订单表和设计图文件是“引用关系”,一旦用户刷新页面或网络抖动导致上传中断,文件没存上,但订单照常创建,后续图片是空的。处理方式其实很简单:把“订单创建”和“图片上传保存”做成一个事务流程,图片文件上传成功才算订单创建成功。
从那以后,我每次要做一个带定制设计的订单系统,都会强制走一遍这个检查:设计图文件是否和订单有强一致的落下顺序?异步生成图片的脚本是否在线上出现异常时有人会立刻收到告警?如果这两点没验证,再好看的前端交互我都觉得心里没底。
这个系统的核心价值不在界面有多花哨,而在它的业务逻辑覆盖度——从设计器交互、SKU组合、价格计算、订单落地,到生产端需要的图片导出,每个环节都有落地的实现参考。希望帮到你。
本文还有配套的精品资源,点击获取