# 海报Fabric.js前端对接文档 ## 📋 概述 本文档详细说明如何在前端使用新的海报生成API返回的Fabric.js JSON数据,包括图层加载、图片替换、图层管理等功能。 ## 🔄 核心变化 ### **从PSD到Fabric.js JSON** - **原先**: 返回PSD文件的base64编码 - **现在**: 返回Fabric.js JSON文件的base64编码 + 原始JSON数据 - **优势**: 可以直接在前端使用,支持实时编辑和图片替换 ## 🎯 API接口说明 ### 1. **生成海报接口** #### **请求示例** ```javascript const generatePosterRequest = { posterNumber: 1, posters: [{ templateId: "vibrant", imagesBase64: "图片ID列表", contentId: "内容ID", productId: "产品ID", generatePsd: true, // 现在实际生成JSON numVariations: 1 }] }; const response = await fetch('/poster/generate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(generatePosterRequest) }); const result = await response.json(); ``` #### **响应结构** ```javascript { "code": 0, "message": "操作成功", "data": [{ "requestId": "poster-20250104-123456", "templateId": "vibrant", "resultImagesBase64": [{ "id": "vibrant_v1", "image": "base64编码的PNG图片", "format": "PNG", "size": [1350, 1800] }], "psdFiles": [{ // 现在是JSON文件 "id": "vibrant_v1_json", "filename": "template_fabric_v1_20250104.json", "data": "base64编码的JSON文件", "size": 15672, "format": "JSON", "jsonData": { // 原始JSON数据,可直接使用 "version": "5.3.0", "objects": [...], "background": "white", "width": 1350, "height": 1800 } }] }] } ``` ### 2. **获取Fabric.js JSON数据** ```javascript // 获取指定海报的JSON数据 const fabricJson = await fetch(`/poster/fabric-json/${posterId}`) .then(res => res.json()) .then(data => data.data); ``` ### 3. **替换底层图片** ```javascript // 单个图片替换 const replaceImage = async (posterId, newImageBase64) => { const formData = new FormData(); formData.append('posterId', posterId); formData.append('newImageBase64', newImageBase64); const response = await fetch('/poster/replace-image', { method: 'POST', body: formData }); return response.json(); }; ``` ### 4. **获取图层信息** ```javascript // 获取图层管理信息 const layers = await fetch(`/poster/layers/${posterId}`) .then(res => res.json()) .then(data => data.data); // 图层信息结构 // [{ // "name": "图片层", // "type": "image", // "level": 0, // "visible": true, // "selectable": true, // "replaceable": true // }] ``` ## 🛠️ 前端集成实现 ### 1. **基础环境准备** #### **HTML结构** ```html 海报编辑器

图层管理

图片替换

``` #### **CSS样式** ```css #poster-editor { display: flex; gap: 20px; padding: 20px; } .canvas-container { flex: 1; border: 1px solid #ddd; border-radius: 8px; padding: 10px; background: #f5f5f5; } .layer-panel, .image-replace-panel { width: 250px; border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: white; } .layer-item { padding: 8px; margin: 5px 0; border: 1px solid #eee; border-radius: 4px; cursor: pointer; } .layer-item:hover { background: #f0f0f0; } .layer-item.active { background: #e3f2fd; border-color: #2196f3; } ``` ### 2. **JavaScript核心实现** #### **初始化画布** ```javascript class PosterEditor { constructor(canvasId) { this.canvas = new fabric.Canvas(canvasId, { preserveObjectStacking: true, selection: true }); this.currentPosterId = null; this.layers = []; this.initEventListeners(); } // 加载海报JSON数据 async loadPosterFromJson(fabricJsonData, posterId) { try { console.log('开始加载Fabric.js JSON数据...'); // 清空画布 this.canvas.clear(); this.currentPosterId = posterId; // 加载JSON数据到画布 await new Promise((resolve, reject) => { this.canvas.loadFromJSON(fabricJsonData, () => { console.log('Fabric.js JSON数据加载成功'); this.canvas.renderAll(); resolve(); }, (error) => { console.error('加载JSON数据失败:', error); reject(error); }); }); // 加载图层信息 await this.loadLayerInfo(posterId); console.log('海报加载完成'); } catch (error) { console.error('加载海报失败:', error); throw error; } } // 从API获取并加载海报 async loadPosterFromApi(posterId) { try { const response = await fetch(`/poster/fabric-json/${posterId}`); const result = await response.json(); if (result.code === 0) { await this.loadPosterFromJson(result.data, posterId); } else { throw new Error(result.message || '获取海报数据失败'); } } catch (error) { console.error('从API加载海报失败:', error); throw error; } } // 加载图层信息 async loadLayerInfo(posterId) { try { const response = await fetch(`/poster/layers/${posterId}`); const result = await response.json(); if (result.code === 0) { this.layers = result.data; this.renderLayerPanel(); } } catch (error) { console.error('加载图层信息失败:', error); } } // 渲染图层管理面板 renderLayerPanel() { const layerList = document.getElementById('layer-list'); layerList.innerHTML = ''; this.layers.forEach((layer, index) => { const layerItem = document.createElement('div'); layerItem.className = 'layer-item'; layerItem.innerHTML = `
${layer.name} (${layer.type})
${layer.replaceable ? ` ` : ''}
`; layerList.appendChild(layerItem); }); } // 替换底层图片 async replaceBackgroundImage(imageFile) { try { if (!this.currentPosterId) { throw new Error('没有加载的海报'); } // 转换图片为base64 const imageBase64 = await this.fileToBase64(imageFile); // 调用后端API替换图片 const formData = new FormData(); formData.append('posterId', this.currentPosterId); formData.append('newImageBase64', imageBase64); const response = await fetch('/poster/replace-image', { method: 'POST', body: formData }); const result = await response.json(); if (result.code === 0) { // 重新加载画布 await this.loadPosterFromJson(result.data, this.currentPosterId); console.log('图片替换成功'); } else { throw new Error(result.message || '图片替换失败'); } } catch (error) { console.error('替换图片失败:', error); alert('替换图片失败: ' + error.message); } } // 文件转base64 fileToBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { // 移除data:image/xxx;base64,前缀 const base64 = reader.result.split(',')[1]; resolve(base64); }; reader.onerror = reject; reader.readAsDataURL(file); }); } // 切换图层可见性 toggleLayerVisibility(layerIndex) { const layer = this.layers[layerIndex]; if (layer) { layer.visible = !layer.visible; // 这里可以实现图层显示/隐藏的逻辑 // 需要根据具体的图层结构来实现 } } // 初始化事件监听 initEventListeners() { // 图片上传事件 document.getElementById('image-upload').addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { this.replaceBackgroundImage(file); } }); // 替换按钮事件 document.getElementById('replace-btn').addEventListener('click', () => { document.getElementById('image-upload').click(); }); } // 导出当前画布为图片 exportAsImage(format = 'png', quality = 1.0) { return this.canvas.toDataURL({ format: format, quality: quality, multiplier: 1 }); } // 获取当前的Fabric.js JSON数据 getCurrentJson() { return this.canvas.toJSON(); } } // 初始化编辑器 const editor = new PosterEditor('poster-canvas'); ``` ### 3. **完整使用示例** #### **生成海报并加载到编辑器** ```javascript // 完整的海报生成和加载流程 async function generateAndLoadPoster() { try { // 1. 生成海报 const generateRequest = { posterNumber: 1, posters: [{ templateId: "vibrant", imagesBase64: "123,456", // 图片ID列表 generatePsd: true, numVariations: 1 }] }; console.log('正在生成海报...'); const generateResponse = await fetch('/poster/generate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(generateRequest) }); const generateResult = await generateResponse.json(); if (generateResult.code !== 0) { throw new Error('海报生成失败: ' + generateResult.message); } const posterData = generateResult.data[0]; const fabricJsonData = posterData.psdFiles[0].jsonData; const posterId = posterData.psdFiles[0].id; console.log('海报生成成功,开始加载到编辑器...'); // 2. 加载到编辑器 await editor.loadPosterFromJson(fabricJsonData, posterId); console.log('海报加载到编辑器成功!'); // 3. 显示预览图 const previewImage = posterData.resultImagesBase64[0].image; showPreviewImage(previewImage); } catch (error) { console.error('生成和加载海报失败:', error); alert('操作失败: ' + error.message); } } // 显示预览图 function showPreviewImage(base64Image) { const previewDiv = document.createElement('div'); previewDiv.innerHTML = `

生成的海报预览

`; document.body.appendChild(previewDiv); } ``` #### **图片替换功能示例** ```javascript // 高级图片替换功能 class ImageReplacer { constructor(editor) { this.editor = editor; this.setupUI(); } setupUI() { const replacePanel = document.querySelector('.image-replace-panel'); replacePanel.innerHTML = `

图片替换

拖拽图片到这里或点击选择

`; this.bindEvents(); } bindEvents() { const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('image-upload'); const previewDiv = document.getElementById('image-preview'); const previewImg = document.getElementById('preview-img'); // 点击上传 uploadArea.addEventListener('click', () => { fileInput.click(); }); // 拖拽上传 uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.style.backgroundColor = '#f0f0f0'; }); uploadArea.addEventListener('dragleave', () => { uploadArea.style.backgroundColor = ''; }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.style.backgroundColor = ''; const files = e.dataTransfer.files; if (files.length > 0) { this.handleFileSelect(files[0]); } }); // 文件选择 fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { this.handleFileSelect(file); } }); // 确认替换 document.getElementById('confirm-replace').addEventListener('click', () => { this.confirmReplace(); }); // 取消 document.getElementById('cancel-replace').addEventListener('click', () => { this.cancelReplace(); }); } handleFileSelect(file) { const reader = new FileReader(); reader.onload = (e) => { const previewImg = document.getElementById('preview-img'); const previewDiv = document.getElementById('image-preview'); const uploadArea = document.getElementById('upload-area'); previewImg.src = e.target.result; previewDiv.style.display = 'block'; uploadArea.style.display = 'none'; this.selectedFile = file; }; reader.readAsDataURL(file); } async confirmReplace() { try { await this.editor.replaceBackgroundImage(this.selectedFile); this.cancelReplace(); alert('图片替换成功!'); } catch (error) { alert('图片替换失败: ' + error.message); } } cancelReplace() { document.getElementById('image-preview').style.display = 'none'; document.getElementById('upload-area').style.display = 'block'; this.selectedFile = null; } } // 初始化图片替换器 const imageReplacer = new ImageReplacer(editor); ``` ## 🚀 快速开始 ### **1. 基础集成** ```html ``` ### **2. 图片替换示例** ```javascript // 简单的图片替换 async function replaceImage() { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async (event) => { const base64 = event.target.result.split(',')[1]; try { const response = await fetch('/poster/replace-image', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `posterId=your-poster-id&newImageBase64=${encodeURIComponent(base64)}` }); const result = await response.json(); if (result.code === 0) { // 重新加载画布 canvas.loadFromJSON(result.data, () => { canvas.renderAll(); }); } } catch (error) { console.error('替换失败:', error); } }; reader.readAsDataURL(file); }; input.click(); } ``` ## ⚠️ 注意事项 ### **1. 浏览器兼容性** - 需要支持Canvas API - 需要支持File API - 建议使用现代浏览器(Chrome 80+, Firefox 75+, Safari 13+) ### **2. 性能优化** ```javascript // 优化Canvas性能 canvas.set({ renderOnAddRemove: false, // 添加/删除对象时不自动渲染 skipTargetFind: true // 跳过目标查找(如果不需要交互) }); // 批量操作后手动渲染 canvas.renderAll(); ``` ### **3. 错误处理** ```javascript // 完善的错误处理 try { await editor.loadPosterFromApi(posterId); } catch (error) { if (error.message.includes('404')) { console.error('海报不存在'); } else if (error.message.includes('网络')) { console.error('网络错误,请重试'); } else { console.error('未知错误:', error); } } ``` ### **4. 内存管理** ```javascript // 清理资源 function cleanup() { canvas.dispose(); // 销毁画布 canvas = null; } // 页面卸载时清理 window.addEventListener('beforeunload', cleanup); ``` ## 📞 技术支持 如在集成过程中遇到问题: - **API问题**: 检查请求格式和响应状态码 - **Canvas问题**: 查看浏览器控制台错误信息 - **性能问题**: 检查图片大小和Canvas尺寸 - **兼容性问题**: 确认Fabric.js版本和浏览器支持 ## 🔗 相关链接 - [Fabric.js官方文档](http://fabricjs.com/docs/) - [Canvas API文档](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) - [File API文档](https://developer.mozilla.org/en-US/docs/Web/API/File)