|
|
@@ -72,6 +72,7 @@ export default {
|
|
|
color:"#fff",
|
|
|
bg_color:'#fff',
|
|
|
visible:false,
|
|
|
+ canvas_type: 'normal', // 画布类型:'normal'(普通画布), 'model'(模特图), 'scene'(场景图)
|
|
|
multi_goods_mode: '', // 多货号模式:''(默认单货号), 'single'(一个货号多角度), 'multiple'(多个货号同角度)
|
|
|
max_goods_count: null, // 多货号模式下,最多可追加多少个货号
|
|
|
}
|
|
|
@@ -83,13 +84,75 @@ export default {
|
|
|
isEmpty(){
|
|
|
return this.data.length === 0
|
|
|
},
|
|
|
+ // 按 sort 字段排序后的画布数据
|
|
|
+ sortedData(){
|
|
|
+ return [...this.data].sort((a, b) => {
|
|
|
+ const sortA = a.sort !== undefined ? a.sort : (a._sortIndex !== undefined ? a._sortIndex : 999999)
|
|
|
+ const sortB = b.sort !== undefined ? b.sort : (b._sortIndex !== undefined ? b._sortIndex : 999999)
|
|
|
+ return sortA - sortB
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 根据排序后的数据获取当前画布
|
|
|
this_canvas(){
|
|
|
- return this.data[this.index]
|
|
|
+ const sorted = this.sortedData
|
|
|
+ return sorted[this.index] || this.data[this.index]
|
|
|
+ },
|
|
|
+ // 获取当前画布在原始数据中的索引
|
|
|
+ currentCanvasIndex(){
|
|
|
+ const sorted = this.sortedData
|
|
|
+ const currentCanvas = sorted[this.index]
|
|
|
+ if(!currentCanvas) return this.index
|
|
|
+ return this.data.findIndex(item => item === currentCanvas)
|
|
|
}
|
|
|
},
|
|
|
+ methods: {
|
|
|
+ // 辅助:通过排序后的索引,找到 data 中的真实索引
|
|
|
+ getDataIndexBySortedIndex(sortedIdx){
|
|
|
+ if(sortedIdx === undefined || sortedIdx === null) return -1
|
|
|
+ const sorted = this.sortedData
|
|
|
+ const item = sorted[sortedIdx]
|
|
|
+ if(!item) return -1
|
|
|
+ if(item._uid){
|
|
|
+ const found = this.data.findIndex(d => d._uid === item._uid)
|
|
|
+ if(found !== -1) return found
|
|
|
+ }
|
|
|
+ const foundByRef = this.data.findIndex(d => d === item)
|
|
|
+ return foundByRef !== -1 ? foundByRef : -1
|
|
|
+ },
|
|
|
+ },
|
|
|
watch: {
|
|
|
index(newValue, oldValue) {
|
|
|
if (this.isEmpty) return
|
|
|
+ const sorted = this.sortedData
|
|
|
+ const targetCanvas = sorted[newValue]
|
|
|
+ // 场景图和模特图不能被激活,如果切换到这些类型,自动切换到下一个普通画布
|
|
|
+ if(targetCanvas && (targetCanvas.canvas_type === 'model' || targetCanvas.canvas_type === 'scene')){
|
|
|
+ // 查找下一个普通画布
|
|
|
+ let nextNormalIndex = -1
|
|
|
+ for(let i = newValue + 1; i < sorted.length; i++){
|
|
|
+ if(sorted[i].canvas_type === 'normal'){
|
|
|
+ nextNormalIndex = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 如果后面没有,往前找
|
|
|
+ if(nextNormalIndex === -1){
|
|
|
+ for(let i = newValue - 1; i >= 0; i--){
|
|
|
+ if(sorted[i].canvas_type === 'normal'){
|
|
|
+ nextNormalIndex = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 如果找到了普通画布,切换到它;否则保持原索引
|
|
|
+ if(nextNormalIndex !== -1){
|
|
|
+ this.$emit('update:index', nextNormalIndex)
|
|
|
+ } else {
|
|
|
+ // 如果没有普通画布,恢复到之前的索引
|
|
|
+ this.$emit('update:index', oldValue)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
this.saveCanvasSnapshot(oldValue)
|
|
|
this.destroyCanvasInstance()
|
|
|
this.$nextTick(() => {
|
|
|
@@ -99,6 +162,15 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
+ // 初始化 sort 和唯一 _uid 字段(如果不存在)
|
|
|
+ this.data.forEach((item, idx) => {
|
|
|
+ if(item.sort === undefined){
|
|
|
+ item.sort = idx
|
|
|
+ }
|
|
|
+ if(!item._uid){
|
|
|
+ item._uid = `canvas-${Date.now()}-${Math.random().toString(16).slice(2)}-${idx}`
|
|
|
+ }
|
|
|
+ })
|
|
|
if(this.$route?.name === 'editTpl'){
|
|
|
this.loadTemplate()
|
|
|
}
|
|
|
@@ -111,7 +183,17 @@ export default {
|
|
|
methods: {
|
|
|
loadTemplate(){
|
|
|
if(this.hasLoadedTpl) return
|
|
|
- this.data.splice(0, this.data.length, ...JSON.parse(JSON.stringify(canvas)))
|
|
|
+ const templateData = JSON.parse(JSON.stringify(canvas))
|
|
|
+ // 为每个画布初始化 sort 和唯一 _uid 字段
|
|
|
+ templateData.forEach((item, idx) => {
|
|
|
+ if(item.sort === undefined){
|
|
|
+ item.sort = idx
|
|
|
+ }
|
|
|
+ if(!item._uid){
|
|
|
+ item._uid = `canvas-${Date.now()}-${Math.random().toString(16).slice(2)}-${idx}`
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.data.splice(0, this.data.length, ...templateData)
|
|
|
this.$emit('update:index', 0)
|
|
|
this.hasLoadedTpl = true
|
|
|
},
|
|
|
@@ -169,6 +251,24 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 模特图和场景图不需要初始化 canvas
|
|
|
+ const canvasType = this.this_canvas?.canvas_type || 'normal'
|
|
|
+ if (canvasType === 'model' || canvasType === 'scene') {
|
|
|
+ // 如果当前是场景图/模特图,自动切换到普通画布
|
|
|
+ const sorted = this.sortedData
|
|
|
+ let nextNormalIndex = -1
|
|
|
+ for(let i = 0; i < sorted.length; i++){
|
|
|
+ if(sorted[i].canvas_type === 'normal'){
|
|
|
+ nextNormalIndex = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(nextNormalIndex !== -1 && nextNormalIndex !== this.index){
|
|
|
+ this.$emit('update:index', nextNormalIndex)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
//画布下不存在模板OSS地址
|
|
|
if(!this.this_canvas){
|
|
|
@@ -232,6 +332,7 @@ export default {
|
|
|
this.canvasForm.width = FIXED_CANVAS_WIDTH
|
|
|
this.canvasForm.height = 1024
|
|
|
this.canvasForm.bg_color = '#fff'
|
|
|
+ this.canvasForm.canvas_type = 'normal'
|
|
|
this.canvasForm.multi_goods_mode = ''
|
|
|
this.canvasForm.max_goods_count = null
|
|
|
this.canvasForm.visible = true;
|
|
|
@@ -243,24 +344,53 @@ export default {
|
|
|
this.canvasForm.width = FIXED_CANVAS_WIDTH
|
|
|
this.canvasForm.height = this.this_canvas.height
|
|
|
this.canvasForm.bg_color = this.this_canvas.bg_color
|
|
|
+ this.canvasForm.canvas_type = this.this_canvas.canvas_type || 'normal'
|
|
|
this.canvasForm.multi_goods_mode = this.this_canvas.multi_goods_mode || ''
|
|
|
this.canvasForm.max_goods_count = this.this_canvas.max_goods_count || null
|
|
|
this.canvasForm.visible = true;
|
|
|
},
|
|
|
+ handleCanvasTypeChange(type){
|
|
|
+ if(this.canvasForm.type !== 'add') return
|
|
|
+ if(type === 'model'){
|
|
|
+ this.canvasForm.name = '模特图'
|
|
|
+ }else if(type === 'scene'){
|
|
|
+ this.canvasForm.name = '场景图'
|
|
|
+ }else if(!this.canvasForm.name || this.canvasForm.name === '模特图' || this.canvasForm.name === '场景图'){
|
|
|
+ this.canvasForm.name = '画布_'+new Date().getTime().toString().substr(8)+Math.round(100)
|
|
|
+ }
|
|
|
+ },
|
|
|
submitCanvasInfo() {
|
|
|
// 假设 this.canvasForm 包含最新的 width, height 和 color
|
|
|
|
|
|
if(this.canvasForm.type === 'add'){
|
|
|
this.saveCanvasSnapshot()
|
|
|
+ // 计算新的 sort 值(取当前最大 sort + 1,或使用数组长度)
|
|
|
+ const maxSort = this.data.length > 0
|
|
|
+ ? Math.max(...this.data.map(item => item.sort !== undefined ? item.sort : (item._sortIndex !== undefined ? item._sortIndex : 0)))
|
|
|
+ : -1
|
|
|
+ // 若是模特图/场景图,名称默认改为对应名称
|
|
|
+ let canvasName = this.canvasForm.name
|
|
|
+ if(this.canvasForm.canvas_type === 'model'){
|
|
|
+ if(!canvasName || canvasName.startsWith('画布_') || canvasName === '场景图'){
|
|
|
+ canvasName = '模特图'
|
|
|
+ }
|
|
|
+ }else if(this.canvasForm.canvas_type === 'scene'){
|
|
|
+ if(!canvasName || canvasName.startsWith('画布_') || canvasName === '模特图'){
|
|
|
+ canvasName = '场景图'
|
|
|
+ }
|
|
|
+ }
|
|
|
this.data.push({
|
|
|
tpl_url:"",
|
|
|
image_path:"",
|
|
|
- name:this.canvasForm.name,
|
|
|
+ name:canvasName,
|
|
|
width:FIXED_CANVAS_WIDTH,
|
|
|
height:this.canvasForm.height,
|
|
|
bg_color:this.canvasForm.bg_color,
|
|
|
+ canvas_type: this.canvasForm.canvas_type || 'normal',
|
|
|
+ _uid: `canvas-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
|
canvas_json:'',
|
|
|
preview:'',
|
|
|
+ sort: maxSort + 1,
|
|
|
multi_goods_mode: this.canvasForm.multi_goods_mode || '',
|
|
|
max_goods_count: this.canvasForm.multi_goods_mode ? (this.canvasForm.max_goods_count || null) : null,
|
|
|
})
|
|
|
@@ -331,6 +461,7 @@ export default {
|
|
|
this.data[this.index].width = FIXED_CANVAS_WIDTH
|
|
|
this.data[this.index].height = newHeight
|
|
|
this.data[this.index].bg_color = this.canvasForm.bg_color
|
|
|
+ this.data[this.index].canvas_type = this.canvasForm.canvas_type || 'normal'
|
|
|
this.data[this.index].multi_goods_mode = this.canvasForm.multi_goods_mode || ''
|
|
|
this.data[this.index].max_goods_count = this.canvasForm.multi_goods_mode ? (this.canvasForm.max_goods_count || null) : null
|
|
|
this.canvasForm.visible = false;
|
|
|
@@ -344,9 +475,50 @@ export default {
|
|
|
},
|
|
|
handleSelectCanvas(index){
|
|
|
if(index === this.index) return
|
|
|
+ // 场景图和模特图不能被选中/激活
|
|
|
+ const sorted = this.sortedData
|
|
|
+ const targetCanvas = sorted[index]
|
|
|
+ if(targetCanvas && (targetCanvas.canvas_type === 'model' || targetCanvas.canvas_type === 'scene')){
|
|
|
+ return
|
|
|
+ }
|
|
|
this.saveCanvasSnapshot()
|
|
|
this.$emit('update:index', index)
|
|
|
},
|
|
|
+ handleMoveCanvas(currentIndex, direction){
|
|
|
+ // 先保存当前画布快照
|
|
|
+ this.saveCanvasSnapshot()
|
|
|
+
|
|
|
+ const sorted = this.sortedData
|
|
|
+ if(currentIndex < 0 || currentIndex >= sorted.length) return
|
|
|
+
|
|
|
+ let targetIndex
|
|
|
+ if(direction === 'up' && currentIndex > 0){
|
|
|
+ targetIndex = currentIndex - 1
|
|
|
+ } else if(direction === 'down' && currentIndex < sorted.length - 1){
|
|
|
+ targetIndex = currentIndex + 1
|
|
|
+ } else {
|
|
|
+ return // 无法移动
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取要移动的画布和目标位置的画布
|
|
|
+ const movedCanvas = sorted[currentIndex]
|
|
|
+ const targetCanvas = sorted[targetIndex]
|
|
|
+
|
|
|
+ // 交换 sort 值
|
|
|
+ const tempSort = movedCanvas.sort !== undefined ? movedCanvas.sort : currentIndex
|
|
|
+ const targetSort = targetCanvas.sort !== undefined ? targetCanvas.sort : targetIndex
|
|
|
+
|
|
|
+ // 更新 sort 字段
|
|
|
+ movedCanvas.sort = targetSort
|
|
|
+ targetCanvas.sort = tempSort
|
|
|
+
|
|
|
+ // 如果当前激活的是被移动的画布,更新索引
|
|
|
+ if(this.index === currentIndex){
|
|
|
+ this.$emit('update:index', targetIndex)
|
|
|
+ } else if(this.index === targetIndex){
|
|
|
+ this.$emit('update:index', currentIndex)
|
|
|
+ }
|
|
|
+ },
|
|
|
handleDeleteCanvas(targetIndex){
|
|
|
if(this.data.length <= 1){
|
|
|
this.$message.warning('至少需要保留一个画布')
|
|
|
@@ -434,6 +606,19 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
canvasBodyStyle(item){
|
|
|
+ const canvasType = item?.canvas_type || 'normal'
|
|
|
+ // 模特图和场景图显示为正方形
|
|
|
+ if(canvasType === 'model' || canvasType === 'scene'){
|
|
|
+ return {
|
|
|
+ width: `${FIXED_CANVAS_WIDTH}px`,
|
|
|
+ height: `${FIXED_CANVAS_WIDTH}px`,
|
|
|
+ margin: '0 auto',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 普通画布
|
|
|
const height = Number(item?.height)
|
|
|
const width = Number(item?.width) || FIXED_CANVAS_WIDTH
|
|
|
const style = {
|
|
|
@@ -468,9 +653,11 @@ export default {
|
|
|
width: item.width,
|
|
|
height: item.height,
|
|
|
bg_color: item.bg_color,
|
|
|
+ canvas_type: item.canvas_type || 'normal',
|
|
|
name: item.name || '',
|
|
|
tpl_url: item.tpl_url || '',
|
|
|
image_path: item.image_path || '',
|
|
|
+ sort: item.sort !== undefined ? item.sort : idx,
|
|
|
multi_goods_mode: item.multi_goods_mode || '',
|
|
|
max_goods_count: item.max_goods_count || null,
|
|
|
}
|
|
|
@@ -511,9 +698,11 @@ export default {
|
|
|
return { bundles }
|
|
|
},
|
|
|
saveCanvasSnapshot(targetIndex){
|
|
|
- const snapshotIndex = typeof targetIndex === 'number' ? targetIndex : this.index
|
|
|
- if(!this.fcanvas || snapshotIndex === undefined || snapshotIndex === null) return
|
|
|
- const canvasData = this.data[snapshotIndex]
|
|
|
+ // targetIndex 为排序后的索引,需要映射到 data 的真实索引
|
|
|
+ const sortedIdx = typeof targetIndex === 'number' ? targetIndex : this.index
|
|
|
+ const dataIndex = this.getDataIndexBySortedIndex(sortedIdx)
|
|
|
+ if(!this.fcanvas || dataIndex === undefined || dataIndex === null || dataIndex < 0) return
|
|
|
+ const canvasData = this.data[dataIndex]
|
|
|
if(!canvasData) return
|
|
|
const json = JSON.stringify(this.fcanvas.toJSON(['name','sort','mtr','id','selectable','erasable','data-key','data-type','data-value']))
|
|
|
let preview = canvasData.preview || ''
|
|
|
@@ -753,6 +942,42 @@ export default {
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
}
|
|
|
|
|
|
+.canvas-special-placeholder {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border: 2px dashed #d0d0d0;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ &.model {
|
|
|
+ border-color: #68BCA5;
|
|
|
+ background: rgba(104, 188, 165, 0.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.scene {
|
|
|
+ border-color: #409EFF;
|
|
|
+ background: rgba(64, 158, 255, 0.05);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.canvas-special-placeholder .special-placeholder-content {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #666;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.canvas-special-placeholder.model .special-placeholder-content {
|
|
|
+ color: #68BCA5;
|
|
|
+}
|
|
|
+
|
|
|
+.canvas-special-placeholder.scene .special-placeholder-content {
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
.fixed-width-tip {
|
|
|
line-height: 32px;
|
|
|
color: #666;
|