|
@@ -679,6 +679,95 @@ const fetchMaterials = async () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 生成画布预览图(base64)
|
|
|
|
|
+const generateCanvasPreview = async () => {
|
|
|
|
|
+ if (!canvasWidth.value || !canvasHeight.value) return null
|
|
|
|
|
+
|
|
|
|
|
+ const exportCanvas = document.createElement('canvas')
|
|
|
|
|
+ exportCanvas.width = canvasWidth.value
|
|
|
|
|
+ exportCanvas.height = canvasHeight.value
|
|
|
|
|
+ const ctx = exportCanvas.getContext('2d')
|
|
|
|
|
+ if (!ctx) return null
|
|
|
|
|
+
|
|
|
|
|
+ // 背景填充为白色
|
|
|
|
|
+ ctx.fillStyle = '#ffffff'
|
|
|
|
|
+ ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height)
|
|
|
|
|
+
|
|
|
|
|
+ // 预加载图片图层
|
|
|
|
|
+ const imageLayers = layers.value.filter(l => (l.type || 'image') !== 'text' && l.url)
|
|
|
|
|
+ await Promise.all(
|
|
|
|
|
+ imageLayers.map(layer => {
|
|
|
|
|
+ return new Promise(resolve => {
|
|
|
|
|
+ const img = new Image()
|
|
|
|
|
+ img.crossOrigin = 'anonymous'
|
|
|
|
|
+ img.onload = () => {
|
|
|
|
|
+ layer._previewImg = img
|
|
|
|
|
+ resolve()
|
|
|
|
|
+ }
|
|
|
|
|
+ img.onerror = () => resolve()
|
|
|
|
|
+ img.src = layer.url
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 按当前顺序绘制所有可见图层
|
|
|
|
|
+ for (const layer of layers.value) {
|
|
|
|
|
+ if (!layer.visible) continue
|
|
|
|
|
+
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.globalAlpha = (layer.opacity ?? 100) / 100
|
|
|
|
|
+
|
|
|
|
|
+ const w = layer.width || 0
|
|
|
|
|
+ const h = layer.height || 0
|
|
|
|
|
+ const cx = (layer.x || 0) + w / 2
|
|
|
|
|
+ const cy = (layer.y || 0) + h / 2
|
|
|
|
|
+
|
|
|
|
|
+ ctx.translate(cx, cy)
|
|
|
|
|
+ ctx.rotate(((layer.rotation || 0) * Math.PI) / 180)
|
|
|
|
|
+
|
|
|
|
|
+ if ((layer.type || 'image') === 'text') {
|
|
|
|
|
+ const fontSize = layer.fontSize || 16
|
|
|
|
|
+ const fontFamily = layer.fontFamily || 'Arial'
|
|
|
|
|
+ const fontWeight = layer.fontWeight || 'normal'
|
|
|
|
|
+ const fontStyle = layer.fontStyle || 'normal'
|
|
|
|
|
+ ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`
|
|
|
|
|
+ ctx.textAlign = layer.textAlign || 'left'
|
|
|
|
|
+ ctx.textBaseline = 'top'
|
|
|
|
|
+ ctx.fillStyle = layer.color || '#000000'
|
|
|
|
|
+
|
|
|
|
|
+ const lineHeightPx = (layer.lineHeight || 1.5) * fontSize
|
|
|
|
|
+ const lines = (layer.text || '').split('\n')
|
|
|
|
|
+
|
|
|
|
|
+ let startX = 0
|
|
|
|
|
+ if (ctx.textAlign === 'left') {
|
|
|
|
|
+ startX = -w / 2 + 4
|
|
|
|
|
+ } else if (ctx.textAlign === 'right') {
|
|
|
|
|
+ startX = w / 2 - 4
|
|
|
|
|
+ } // center 默认 0
|
|
|
|
|
+
|
|
|
|
|
+ let y = -h / 2 + 4
|
|
|
|
|
+ for (const line of lines) {
|
|
|
|
|
+ ctx.fillText(line, startX, y)
|
|
|
|
|
+ y += lineHeightPx
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const img = layer._previewImg
|
|
|
|
|
+ if (img) {
|
|
|
|
|
+ ctx.drawImage(img, -w / 2, -h / 2, w, h)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清理临时图片引用
|
|
|
|
|
+ for (const layer of imageLayers) {
|
|
|
|
|
+ delete layer._previewImg
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return exportCanvas.toDataURL('image/png')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
let materialSearchTimer = null
|
|
let materialSearchTimer = null
|
|
|
const handleMaterialSearchInput = () => {
|
|
const handleMaterialSearchInput = () => {
|
|
|
if (materialSearchTimer) clearTimeout(materialSearchTimer)
|
|
if (materialSearchTimer) clearTimeout(materialSearchTimer)
|
|
@@ -784,10 +873,13 @@ const saveTemplate = async () => {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const previewImage = await generateCanvasPreview()
|
|
|
|
|
+
|
|
|
const templateData = {
|
|
const templateData = {
|
|
|
canvasWidth: canvasWidth.value,
|
|
canvasWidth: canvasWidth.value,
|
|
|
canvasHeight: canvasHeight.value,
|
|
canvasHeight: canvasHeight.value,
|
|
|
canvasRatio: canvasRatio.value,
|
|
canvasRatio: canvasRatio.value,
|
|
|
|
|
+ previewImage, // 画布整体预览图(base64)
|
|
|
layers: layers.value.map(layer => ({
|
|
layers: layers.value.map(layer => ({
|
|
|
id: layer.id,
|
|
id: layer.id,
|
|
|
name: layer.name,
|
|
name: layer.name,
|