|
@@ -13,41 +13,271 @@ import { buildRenderPlans, normalizeGoods } from './generateImagesPlan'
|
|
|
* @param {Array} skus normalizeGoods(goodsList) 的结果
|
|
* @param {Array} skus normalizeGoods(goodsList) 的结果
|
|
|
* @returns {Promise<Array<{canvasIndex:number,dataUrl:string}>>}
|
|
* @returns {Promise<Array<{canvasIndex:number,dataUrl:string}>>}
|
|
|
*/
|
|
*/
|
|
|
|
|
+// export async function renderImagesByPlans(plans, canvasList, skus) {
|
|
|
|
|
+// const results = []
|
|
|
|
|
+
|
|
|
|
|
+// // 工具:从 canvas_json 创建一个离屏 fabric.Canvas,并应用一次性的渲染逻辑
|
|
|
|
|
+// const renderOne = (canvasItem, planForCanvas, imagePlan) =>
|
|
|
|
|
+// new Promise((resolve) => {
|
|
|
|
|
+// if (!canvasItem) {
|
|
|
|
|
+// return resolve(null)
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const { canvasIndex } = planForCanvas
|
|
|
|
|
+// const { skuIndexes } = imagePlan
|
|
|
|
|
+
|
|
|
|
|
+// // 模特/场景占位:直接返回类型,不渲染
|
|
|
|
|
+// if (canvasItem.canvas_json === 'model') {
|
|
|
|
|
+// return resolve({
|
|
|
|
|
+// canvasIndex,
|
|
|
|
|
+// dataUrl: 'model',
|
|
|
|
|
+// })
|
|
|
|
|
+// }
|
|
|
|
|
+// if (canvasItem.canvas_json === 'scene') {
|
|
|
|
|
+// return resolve({
|
|
|
|
|
+// canvasIndex,
|
|
|
|
|
+// dataUrl: 'scene',
|
|
|
|
|
+// })
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// // 解析 JSON 为可修改的对象
|
|
|
|
|
+// let json
|
|
|
|
|
+// try {
|
|
|
|
|
+// json = typeof canvasItem.canvas_json === 'string'
|
|
|
|
|
+// ? JSON.parse(canvasItem.canvas_json)
|
|
|
|
|
+// : JSON.parse(JSON.stringify(canvasItem.canvas_json))
|
|
|
|
|
+// } catch (e) {
|
|
|
|
|
+// console.warn('[generateImagesRender] parse canvas_json failed', e)
|
|
|
|
|
+// return resolve(null)
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const width = Number(canvasItem.width) || 395
|
|
|
|
|
+// const height = Number(canvasItem.height) || 600
|
|
|
|
|
+// const bgColor = canvasItem.bg_color || '#fff'
|
|
|
|
|
+
|
|
|
|
|
+// try {
|
|
|
|
|
+// const mode = canvasItem.multi_goods_mode || ''
|
|
|
|
|
+// const perCanvasSlots = planForCanvas.perCanvasSlots || 1
|
|
|
|
|
+// const usedSkus = (skuIndexes || []).map((idx) => (idx != null ? skus[idx] : null))
|
|
|
|
|
+
|
|
|
|
|
+// // 先在原始 JSON 上做数据替换,避免 setSrc 的异步问题;
|
|
|
|
|
+// // loadFromJSON 会在所有图片加载完成后才触发回调。
|
|
|
|
|
+// const objs = (json && Array.isArray(json.objects)) ? json.objects : []
|
|
|
|
|
+
|
|
|
|
|
+// // 1) 处理图片占位(data-type = img)
|
|
|
|
|
+// const imgPlaceholders = objs.filter((o) => o && o['data-type'] === 'img')
|
|
|
|
|
+
|
|
|
|
|
+// if (mode === 'multiple') {
|
|
|
|
|
+// // 多个货号同角度:同一画布中,有多个 img 占位,按顺序对应 skuIndexes
|
|
|
|
|
+// imgPlaceholders.forEach((obj, idx) => {
|
|
|
|
|
+// const slotIndex = idx % perCanvasSlots
|
|
|
|
|
+// const sku = usedSkus[slotIndex]
|
|
|
|
|
+// if (!sku) {
|
|
|
|
|
+// obj.visible = false
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+// const angleKey = obj['data-key']
|
|
|
|
|
+// const url = (sku.pics && sku.pics[angleKey]) || ''
|
|
|
|
|
+// if (!url) {
|
|
|
|
|
+// obj.visible = false
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+// obj.visible = true
|
|
|
|
|
+// obj['data-value'] = url
|
|
|
|
|
+// obj.src = url
|
|
|
|
|
+// })
|
|
|
|
|
+// } else {
|
|
|
|
|
+// // 默认 / single:一个货号多角度,一张图只用一个货号
|
|
|
|
|
+// const sku = usedSkus[0]
|
|
|
|
|
+// imgPlaceholders.forEach((obj) => {
|
|
|
|
|
+// if (!sku) {
|
|
|
|
|
+// obj.visible = false
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+// const angleKey = obj['data-key']
|
|
|
|
|
+// const url = (sku.pics && sku.pics[angleKey]) || ''
|
|
|
|
|
+// if (!url) {
|
|
|
|
|
+// obj.visible = false
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+// obj.visible = true
|
|
|
|
|
+// obj['data-value'] = url
|
|
|
|
|
+// obj.src = url
|
|
|
|
|
+// })
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// // 2) 处理文字占位(data-type = text)
|
|
|
|
|
+// const textPlaceholders = objs.filter((o) => o && o['data-type'] === 'text')
|
|
|
|
|
+
|
|
|
|
|
+// if (textPlaceholders.length) {
|
|
|
|
|
+// // 通用的 key -> 文本 映射函数,
|
|
|
|
|
+// const mapKeyToText = (sku, key, defaultVal) => {
|
|
|
|
|
+// if (!sku) return defaultVal
|
|
|
|
|
+// let textVal = defaultVal || ''
|
|
|
|
|
+// if (key === '颜色') {
|
|
|
|
|
+// textVal = sku.color || textVal
|
|
|
|
|
+// } else if (key === '货号') {
|
|
|
|
|
+// textVal = sku.sku || textVal
|
|
|
|
|
+// }
|
|
|
|
|
+// // 兜底:去 raw 里找同名字段(支持 卖点 / 使用场景 / 其他自定义字段)
|
|
|
|
|
+// if ((!textVal || textVal === defaultVal) && sku.raw && sku.raw[key] != null) {
|
|
|
|
|
+// textVal = sku.raw[key]
|
|
|
|
|
+// }
|
|
|
|
|
+// // 再兜底:如果 sku 上有同名字段
|
|
|
|
|
+// if ((!textVal || textVal === defaultVal) && sku[key] != null) {
|
|
|
|
|
+// textVal = sku[key]
|
|
|
|
|
+// }
|
|
|
|
|
+// return textVal
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// if (mode === 'multiple') {
|
|
|
|
|
+// // 多个货号同角度:
|
|
|
|
|
+// // - 按 data-key + 出现顺序把文字分配给不同货号
|
|
|
|
|
+// // - 如果该 slot 没有对应货号(usedSkus[slotIndex] 为 null),则隐藏该文字层
|
|
|
|
|
+// const keyCounter = {}
|
|
|
|
|
+// textPlaceholders.forEach((obj) => {
|
|
|
|
|
+// const key = obj['data-key']
|
|
|
|
|
+// if (!key) return
|
|
|
|
|
+// const idxForKey = keyCounter[key] || 0
|
|
|
|
|
+// keyCounter[key] = idxForKey + 1
|
|
|
|
|
+
|
|
|
|
|
+// const slotIndex = idxForKey % perCanvasSlots
|
|
|
|
|
+// const sku = usedSkus[slotIndex]
|
|
|
|
|
+// if (!sku) {
|
|
|
|
|
+// obj.visible = false
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const origin = obj['data-value'] || ''
|
|
|
|
|
+// const textVal = mapKeyToText(sku, key, origin)
|
|
|
|
|
+
|
|
|
|
|
+// obj.visible = true
|
|
|
|
|
+// obj.text = textVal
|
|
|
|
|
+// obj['data-value'] = textVal
|
|
|
|
|
+// })
|
|
|
|
|
+// } else {
|
|
|
|
|
+// // 默认 / single:全部文字都使用同一个货号(默认模式只生成 1 张,用第一个货号)
|
|
|
|
|
+// const sku = usedSkus[0]
|
|
|
|
|
+// if (sku) {
|
|
|
|
|
+// textPlaceholders.forEach((obj) => {
|
|
|
|
|
+// const key = obj['data-key']
|
|
|
|
|
+// if (!key) return
|
|
|
|
|
+// const origin = obj['data-value'] || ''
|
|
|
|
|
+// const textVal = mapKeyToText(sku, key, origin)
|
|
|
|
|
+
|
|
|
|
|
+// obj.visible = true
|
|
|
|
|
+// obj.text = textVal
|
|
|
|
|
+// obj['data-value'] = textVal
|
|
|
|
|
+// })
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// // 创建离屏 canvas
|
|
|
|
|
+// const el = document.createElement('canvas')
|
|
|
|
|
+// el.width = width
|
|
|
|
|
+// el.height = height
|
|
|
|
|
+
|
|
|
|
|
+// const fcanvas = new fabric.Canvas(el, {
|
|
|
|
|
+// backgroundColor: bgColor,
|
|
|
|
|
+// width,
|
|
|
|
|
+// height,
|
|
|
|
|
+// renderOnAddRemove: false,
|
|
|
|
|
+// })
|
|
|
|
|
+
|
|
|
|
|
+// // 把已经替换好动态数据的 JSON 加载进 fabric;
|
|
|
|
|
+// // loadFromJSON 会等所有图片资源加载完之后才调用回调函数
|
|
|
|
|
+// fcanvas.loadFromJSON(json, () => {
|
|
|
|
|
+// try {
|
|
|
|
|
+// fcanvas.renderAll()
|
|
|
|
|
+// const dataUrl = fcanvas.toDataURL({
|
|
|
|
|
+// format: 'jpeg',
|
|
|
|
|
+// multiplier:2,
|
|
|
|
|
+// enableRetinaScaling: true,
|
|
|
|
|
+// })
|
|
|
|
|
+
|
|
|
|
|
+// fcanvas.dispose()
|
|
|
|
|
+
|
|
|
|
|
+// resolve({
|
|
|
|
|
+// canvasIndex,
|
|
|
|
|
+// dataUrl,
|
|
|
|
|
+// })
|
|
|
|
|
+// } catch (e) {
|
|
|
|
|
+// console.warn('[generateImagesRender] render one failed in callback', e)
|
|
|
|
|
+// try {
|
|
|
|
|
+// fcanvas.dispose()
|
|
|
|
|
+// } catch (e2) {}
|
|
|
|
|
+// resolve(null)
|
|
|
|
|
+// }
|
|
|
|
|
+// })
|
|
|
|
|
+// } catch (e) {
|
|
|
|
|
+// console.warn('[generateImagesRender] render one failed', e)
|
|
|
|
|
+// resolve(null)
|
|
|
|
|
+// }
|
|
|
|
|
+// })
|
|
|
|
|
+
|
|
|
|
|
+// for (const plan of plans || []) {
|
|
|
|
|
+// const canvasItem = canvasList[plan.canvasIndex]
|
|
|
|
|
+// if (!canvasItem) continue
|
|
|
|
|
+
|
|
|
|
|
+// for (const imgPlan of plan.images || []) {
|
|
|
|
|
+// // eslint-disable-next-line no-await-in-loop
|
|
|
|
|
+// const res = await renderOne(canvasItem, plan, imgPlan)
|
|
|
|
|
+// if (res) results.push(res)
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// return results
|
|
|
|
|
+// }
|
|
|
export async function renderImagesByPlans(plans, canvasList, skus) {
|
|
export async function renderImagesByPlans(plans, canvasList, skus) {
|
|
|
|
|
+ console.log('=== 开始生成图片 ===')
|
|
|
|
|
+ console.log('skus 数据:', skus)
|
|
|
|
|
+ console.log('canvasList:', canvasList)
|
|
|
|
|
+ console.log('plans:', plans)
|
|
|
|
|
+
|
|
|
const results = []
|
|
const results = []
|
|
|
|
|
|
|
|
- // 工具:从 canvas_json 创建一个离屏 fabric.Canvas,并应用一次性的渲染逻辑
|
|
|
|
|
const renderOne = (canvasItem, planForCanvas, imagePlan) =>
|
|
const renderOne = (canvasItem, planForCanvas, imagePlan) =>
|
|
|
new Promise((resolve) => {
|
|
new Promise((resolve) => {
|
|
|
if (!canvasItem) {
|
|
if (!canvasItem) {
|
|
|
|
|
+ console.warn('canvasItem 为空')
|
|
|
return resolve(null)
|
|
return resolve(null)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const { canvasIndex } = planForCanvas
|
|
const { canvasIndex } = planForCanvas
|
|
|
const { skuIndexes } = imagePlan
|
|
const { skuIndexes } = imagePlan
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`\n=== 渲染画布 ${canvasIndex} ===`)
|
|
|
|
|
+ console.log('canvasItem:', canvasItem)
|
|
|
|
|
+ console.log('skuIndexes:', skuIndexes)
|
|
|
|
|
+ console.log('usedSkus:', skuIndexes.map(idx => idx != null ? skus[idx]?.sku : null))
|
|
|
|
|
|
|
|
- // 模特/场景占位:直接返回类型,不渲染
|
|
|
|
|
|
|
+ // 模特/场景占位处理
|
|
|
if (canvasItem.canvas_json === 'model') {
|
|
if (canvasItem.canvas_json === 'model') {
|
|
|
|
|
+ console.log('跳过模特占位')
|
|
|
return resolve({
|
|
return resolve({
|
|
|
canvasIndex,
|
|
canvasIndex,
|
|
|
dataUrl: 'model',
|
|
dataUrl: 'model',
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
if (canvasItem.canvas_json === 'scene') {
|
|
if (canvasItem.canvas_json === 'scene') {
|
|
|
|
|
+ console.log('跳过场景占位')
|
|
|
return resolve({
|
|
return resolve({
|
|
|
canvasIndex,
|
|
canvasIndex,
|
|
|
dataUrl: 'scene',
|
|
dataUrl: 'scene',
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 解析 JSON 为可修改的对象
|
|
|
|
|
|
|
+ // 解析 JSON
|
|
|
let json
|
|
let json
|
|
|
try {
|
|
try {
|
|
|
json = typeof canvasItem.canvas_json === 'string'
|
|
json = typeof canvasItem.canvas_json === 'string'
|
|
|
? JSON.parse(canvasItem.canvas_json)
|
|
? JSON.parse(canvasItem.canvas_json)
|
|
|
: JSON.parse(JSON.stringify(canvasItem.canvas_json))
|
|
: JSON.parse(JSON.stringify(canvasItem.canvas_json))
|
|
|
|
|
+ console.log('解析 canvas_json 成功')
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- console.warn('[generateImagesRender] parse canvas_json failed', e)
|
|
|
|
|
|
|
+ console.error('解析 canvas_json 失败:', e)
|
|
|
|
|
+ console.log('原始 canvas_json:', canvasItem.canvas_json)
|
|
|
return resolve(null)
|
|
return resolve(null)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -58,122 +288,105 @@ export async function renderImagesByPlans(plans, canvasList, skus) {
|
|
|
try {
|
|
try {
|
|
|
const mode = canvasItem.multi_goods_mode || ''
|
|
const mode = canvasItem.multi_goods_mode || ''
|
|
|
const perCanvasSlots = planForCanvas.perCanvasSlots || 1
|
|
const perCanvasSlots = planForCanvas.perCanvasSlots || 1
|
|
|
- const usedSkus = (skuIndexes || []).map((idx) => (idx != null ? skus[idx] : null))
|
|
|
|
|
|
|
+ const usedSkus = (skuIndexes || []).map((idx) => {
|
|
|
|
|
+ const sku = idx != null ? skus[idx] : null
|
|
|
|
|
+ if (sku) {
|
|
|
|
|
+ console.log(`货号 ${sku.sku} 的图片:`, sku.pics)
|
|
|
|
|
+ }
|
|
|
|
|
+ return sku
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- // 先在原始 JSON 上做数据替换,避免 setSrc 的异步问题;
|
|
|
|
|
- // loadFromJSON 会在所有图片加载完成后才触发回调。
|
|
|
|
|
- const objs = (json && Array.isArray(json.objects)) ? json.objects : []
|
|
|
|
|
|
|
+ console.log('mode:', mode)
|
|
|
|
|
+ console.log('perCanvasSlots:', perCanvasSlots)
|
|
|
|
|
+ console.log('实际使用的 sku:', usedSkus.map(s => s?.sku))
|
|
|
|
|
|
|
|
- // 1) 处理图片占位(data-type = img)
|
|
|
|
|
|
|
+ // 处理图片占位
|
|
|
|
|
+ const objs = (json && Array.isArray(json.objects)) ? json.objects : []
|
|
|
|
|
+ console.log('总对象数:', objs.length)
|
|
|
|
|
+
|
|
|
const imgPlaceholders = objs.filter((o) => o && o['data-type'] === 'img')
|
|
const imgPlaceholders = objs.filter((o) => o && o['data-type'] === 'img')
|
|
|
|
|
+ console.log('图片占位符数量:', imgPlaceholders.length)
|
|
|
|
|
+
|
|
|
|
|
+ const textPlaceholders = objs.filter((o) => o && o['data-type'] === 'text')
|
|
|
|
|
+ console.log('文字占位符数量:', textPlaceholders.length)
|
|
|
|
|
+
|
|
|
|
|
+ // 详细检查每个图片占位符
|
|
|
|
|
+ imgPlaceholders.forEach((obj, idx) => {
|
|
|
|
|
+ console.log(`图片占位符 ${idx}:`, {
|
|
|
|
|
+ dataKey: obj['data-key'],
|
|
|
|
|
+ visible: obj.visible,
|
|
|
|
|
+ src: obj.src,
|
|
|
|
|
+ dataValue: obj['data-value']
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
if (mode === 'multiple') {
|
|
if (mode === 'multiple') {
|
|
|
- // 多个货号同角度:同一画布中,有多个 img 占位,按顺序对应 skuIndexes
|
|
|
|
|
|
|
+ console.log('使用 multiple 模式')
|
|
|
imgPlaceholders.forEach((obj, idx) => {
|
|
imgPlaceholders.forEach((obj, idx) => {
|
|
|
const slotIndex = idx % perCanvasSlots
|
|
const slotIndex = idx % perCanvasSlots
|
|
|
const sku = usedSkus[slotIndex]
|
|
const sku = usedSkus[slotIndex]
|
|
|
|
|
+ console.log(`占位符 ${idx} -> 货位 ${slotIndex} -> 货号:`, sku?.sku)
|
|
|
|
|
+
|
|
|
if (!sku) {
|
|
if (!sku) {
|
|
|
|
|
+ console.log(`货位 ${slotIndex} 无货号,隐藏图层`)
|
|
|
obj.visible = false
|
|
obj.visible = false
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
const angleKey = obj['data-key']
|
|
const angleKey = obj['data-key']
|
|
|
const url = (sku.pics && sku.pics[angleKey]) || ''
|
|
const url = (sku.pics && sku.pics[angleKey]) || ''
|
|
|
|
|
+ console.log(`角度 ${angleKey} 的图片URL:`, url)
|
|
|
|
|
+
|
|
|
if (!url) {
|
|
if (!url) {
|
|
|
|
|
+ console.log(`货号 ${sku.sku} 无角度 ${angleKey} 图片,隐藏图层`)
|
|
|
obj.visible = false
|
|
obj.visible = false
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`设置图片 ${idx}: ${angleKey} = ${url}`)
|
|
|
obj.visible = true
|
|
obj.visible = true
|
|
|
obj['data-value'] = url
|
|
obj['data-value'] = url
|
|
|
obj.src = url
|
|
obj.src = url
|
|
|
|
|
+
|
|
|
|
|
+ // 在 Electron 中,需要设置 crossOrigin
|
|
|
|
|
+ if (window.electron) {
|
|
|
|
|
+ obj.crossOrigin = 'anonymous'
|
|
|
|
|
+ }
|
|
|
})
|
|
})
|
|
|
} else {
|
|
} else {
|
|
|
- // 默认 / single:一个货号多角度,一张图只用一个货号
|
|
|
|
|
|
|
+ console.log('使用 single/默认模式')
|
|
|
const sku = usedSkus[0]
|
|
const sku = usedSkus[0]
|
|
|
- imgPlaceholders.forEach((obj) => {
|
|
|
|
|
|
|
+ console.log('使用的货号:', sku?.sku)
|
|
|
|
|
+
|
|
|
|
|
+ imgPlaceholders.forEach((obj, idx) => {
|
|
|
if (!sku) {
|
|
if (!sku) {
|
|
|
|
|
+ console.log('无货号,隐藏所有图层')
|
|
|
obj.visible = false
|
|
obj.visible = false
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
const angleKey = obj['data-key']
|
|
const angleKey = obj['data-key']
|
|
|
const url = (sku.pics && sku.pics[angleKey]) || ''
|
|
const url = (sku.pics && sku.pics[angleKey]) || ''
|
|
|
|
|
+ console.log(`图片 ${idx} 角度 ${angleKey}:`, url)
|
|
|
|
|
+
|
|
|
if (!url) {
|
|
if (!url) {
|
|
|
|
|
+ console.log(`无 ${angleKey} 图片,隐藏图层`)
|
|
|
obj.visible = false
|
|
obj.visible = false
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`设置图片 ${idx}: ${angleKey} = ${url}`)
|
|
|
obj.visible = true
|
|
obj.visible = true
|
|
|
obj['data-value'] = url
|
|
obj['data-value'] = url
|
|
|
obj.src = url
|
|
obj.src = url
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 2) 处理文字占位(data-type = text)
|
|
|
|
|
- const textPlaceholders = objs.filter((o) => o && o['data-type'] === 'text')
|
|
|
|
|
-
|
|
|
|
|
- if (textPlaceholders.length) {
|
|
|
|
|
- // 通用的 key -> 文本 映射函数,
|
|
|
|
|
- const mapKeyToText = (sku, key, defaultVal) => {
|
|
|
|
|
- if (!sku) return defaultVal
|
|
|
|
|
- let textVal = defaultVal || ''
|
|
|
|
|
- if (key === '颜色') {
|
|
|
|
|
- textVal = sku.color || textVal
|
|
|
|
|
- } else if (key === '货号') {
|
|
|
|
|
- textVal = sku.sku || textVal
|
|
|
|
|
- }
|
|
|
|
|
- // 兜底:去 raw 里找同名字段(支持 卖点 / 使用场景 / 其他自定义字段)
|
|
|
|
|
- if ((!textVal || textVal === defaultVal) && sku.raw && sku.raw[key] != null) {
|
|
|
|
|
- textVal = sku.raw[key]
|
|
|
|
|
- }
|
|
|
|
|
- // 再兜底:如果 sku 上有同名字段
|
|
|
|
|
- if ((!textVal || textVal === defaultVal) && sku[key] != null) {
|
|
|
|
|
- textVal = sku[key]
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (window.electron) {
|
|
|
|
|
+ obj.crossOrigin = 'anonymous'
|
|
|
}
|
|
}
|
|
|
- return textVal
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (mode === 'multiple') {
|
|
|
|
|
- // 多个货号同角度:
|
|
|
|
|
- // - 按 data-key + 出现顺序把文字分配给不同货号
|
|
|
|
|
- // - 如果该 slot 没有对应货号(usedSkus[slotIndex] 为 null),则隐藏该文字层
|
|
|
|
|
- const keyCounter = {}
|
|
|
|
|
- textPlaceholders.forEach((obj) => {
|
|
|
|
|
- const key = obj['data-key']
|
|
|
|
|
- if (!key) return
|
|
|
|
|
- const idxForKey = keyCounter[key] || 0
|
|
|
|
|
- keyCounter[key] = idxForKey + 1
|
|
|
|
|
-
|
|
|
|
|
- const slotIndex = idxForKey % perCanvasSlots
|
|
|
|
|
- const sku = usedSkus[slotIndex]
|
|
|
|
|
- if (!sku) {
|
|
|
|
|
- obj.visible = false
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const origin = obj['data-value'] || ''
|
|
|
|
|
- const textVal = mapKeyToText(sku, key, origin)
|
|
|
|
|
-
|
|
|
|
|
- obj.visible = true
|
|
|
|
|
- obj.text = textVal
|
|
|
|
|
- obj['data-value'] = textVal
|
|
|
|
|
- })
|
|
|
|
|
- } else {
|
|
|
|
|
- // 默认 / single:全部文字都使用同一个货号(默认模式只生成 1 张,用第一个货号)
|
|
|
|
|
- const sku = usedSkus[0]
|
|
|
|
|
- if (sku) {
|
|
|
|
|
- textPlaceholders.forEach((obj) => {
|
|
|
|
|
- const key = obj['data-key']
|
|
|
|
|
- if (!key) return
|
|
|
|
|
- const origin = obj['data-value'] || ''
|
|
|
|
|
- const textVal = mapKeyToText(sku, key, origin)
|
|
|
|
|
-
|
|
|
|
|
- obj.visible = true
|
|
|
|
|
- obj.text = textVal
|
|
|
|
|
- obj['data-value'] = textVal
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 创建离屏 canvas
|
|
|
|
|
|
|
+ // 创建 canvas
|
|
|
const el = document.createElement('canvas')
|
|
const el = document.createElement('canvas')
|
|
|
el.width = width
|
|
el.width = width
|
|
|
el.height = height
|
|
el.height = height
|
|
@@ -185,17 +398,42 @@ export async function renderImagesByPlans(plans, canvasList, skus) {
|
|
|
renderOnAddRemove: false,
|
|
renderOnAddRemove: false,
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- // 把已经替换好动态数据的 JSON 加载进 fabric;
|
|
|
|
|
- // loadFromJSON 会等所有图片资源加载完之后才调用回调函数
|
|
|
|
|
|
|
+ console.log('开始加载 fabric JSON...')
|
|
|
|
|
+
|
|
|
|
|
+ // 添加 fabric 加载回调
|
|
|
fcanvas.loadFromJSON(json, () => {
|
|
fcanvas.loadFromJSON(json, () => {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ console.log('fabric 加载完成,开始渲染')
|
|
|
|
|
+
|
|
|
|
|
+ // 检查实际加载的对象
|
|
|
|
|
+ const loadedObjects = fcanvas.getObjects()
|
|
|
|
|
+ console.log('实际加载的对象数:', loadedObjects.length)
|
|
|
|
|
+
|
|
|
|
|
+ loadedObjects.forEach((obj, idx) => {
|
|
|
|
|
+ if (obj.type === 'image') {
|
|
|
|
|
+ console.log(`图片对象 ${idx}:`, {
|
|
|
|
|
+ type: obj.type,
|
|
|
|
|
+ src: obj.getSrc(),
|
|
|
|
|
+ visible: obj.visible,
|
|
|
|
|
+ width: obj.width,
|
|
|
|
|
+ height: obj.height,
|
|
|
|
|
+ scaleX: obj.scaleX,
|
|
|
|
|
+ scaleY: obj.scaleY
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
fcanvas.renderAll()
|
|
fcanvas.renderAll()
|
|
|
|
|
+ console.log('渲染完成,生成 dataURL')
|
|
|
|
|
+
|
|
|
const dataUrl = fcanvas.toDataURL({
|
|
const dataUrl = fcanvas.toDataURL({
|
|
|
format: 'jpeg',
|
|
format: 'jpeg',
|
|
|
- multiplier:2,
|
|
|
|
|
|
|
+ multiplier: 2,
|
|
|
enableRetinaScaling: true,
|
|
enableRetinaScaling: true,
|
|
|
})
|
|
})
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ console.log('dataURL 生成成功,长度:', dataUrl.length)
|
|
|
|
|
+
|
|
|
fcanvas.dispose()
|
|
fcanvas.dispose()
|
|
|
|
|
|
|
|
resolve({
|
|
resolve({
|
|
@@ -203,33 +441,55 @@ export async function renderImagesByPlans(plans, canvasList, skus) {
|
|
|
dataUrl,
|
|
dataUrl,
|
|
|
})
|
|
})
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- console.warn('[generateImagesRender] render one failed in callback', e)
|
|
|
|
|
|
|
+ console.error('渲染回调中出错:', e)
|
|
|
try {
|
|
try {
|
|
|
fcanvas.dispose()
|
|
fcanvas.dispose()
|
|
|
} catch (e2) {}
|
|
} catch (e2) {}
|
|
|
resolve(null)
|
|
resolve(null)
|
|
|
}
|
|
}
|
|
|
|
|
+ }, (obj, object) => {
|
|
|
|
|
+ // fabric 加载时的回调
|
|
|
|
|
+ if (object && object.type === 'image') {
|
|
|
|
|
+ console.log('正在加载图片对象:', object.getSrc())
|
|
|
|
|
+ object.set({
|
|
|
|
|
+ crossOrigin: 'anonymous'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
})
|
|
})
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- console.warn('[generateImagesRender] render one failed', e)
|
|
|
|
|
|
|
+ console.error('渲染过程中出错:', e)
|
|
|
resolve(null)
|
|
resolve(null)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
for (const plan of plans || []) {
|
|
for (const plan of plans || []) {
|
|
|
const canvasItem = canvasList[plan.canvasIndex]
|
|
const canvasItem = canvasList[plan.canvasIndex]
|
|
|
- if (!canvasItem) continue
|
|
|
|
|
|
|
+ if (!canvasItem) {
|
|
|
|
|
+ console.warn(`plan 中 canvasIndex ${plan.canvasIndex} 在 canvasList 中不存在`)
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ console.log(`\n处理 plan ${plan.canvasIndex},图片数: ${plan.images?.length}`)
|
|
|
|
|
+
|
|
|
for (const imgPlan of plan.images || []) {
|
|
for (const imgPlan of plan.images || []) {
|
|
|
|
|
+ console.log(` 生成图片,skuIndexes: ${imgPlan.skuIndexes}`)
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
const res = await renderOne(canvasItem, plan, imgPlan)
|
|
const res = await renderOne(canvasItem, plan, imgPlan)
|
|
|
- if (res) results.push(res)
|
|
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ console.log(` 图片生成成功: canvasIndex=${res.canvasIndex}`)
|
|
|
|
|
+ results.push(res)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log(` 图片生成失败`)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ console.log('=== 图片生成结束 ===')
|
|
|
|
|
+ console.log('成功生成图片数:', results.length)
|
|
|
return results
|
|
return results
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 针对每个款号(style),按画布生成所有图片,并额外生成「所有画布组合在一起」的一张长图。
|
|
* 针对每个款号(style),按画布生成所有图片,并额外生成「所有画布组合在一起」的一张长图。
|
|
|
*
|
|
*
|