深度画布

This commit is contained in:
lzp
2026-03-18 17:25:19 +08:00
parent 0f99ea809d
commit 2df168aec7
9 changed files with 159 additions and 100 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="fill-repeat"> <div class="fill-repeat h">
<div> <div>
<div class="title">Image</div> <div class="title">Image</div>
<div class="content"> <div class="content">
@@ -99,6 +99,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue' import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue'
import DepthOffsetTool from '../tools/depth-offset-tool.vue' import DepthOffsetTool from '../tools/depth-offset-tool.vue'
import DepthSlider from '../tools/depth-slider.vue'
import DepthInput from '../tools/depth-input.vue'
import { getTransformScaleAngle } from '../../manager/ObjectManager' import { getTransformScaleAngle } from '../../manager/ObjectManager'
const objectManager = inject('objectManager') as any const objectManager = inject('objectManager') as any
const stateManager = inject('stateManager') as any const stateManager = inject('stateManager') as any
@@ -159,7 +161,7 @@
const options = { const options = {
opacity: opacity.value / 100 opacity: opacity.value / 100
} }
objectManager.updateOpacity(id.value, options, isRecord) objectManager.updateProperty(id.value, options, isRecord)
} }
stateManager.event.add('canvas:undo', updateData) stateManager.event.add('canvas:undo', updateData)

View File

@@ -10,6 +10,7 @@
<div class="content" v-if="isShow" v-show="show"> <div class="content" v-if="isShow" v-show="show">
<!-- <basic-info :object="activeObject" /> --> <!-- <basic-info :object="activeObject" /> -->
<fill-repeat :object="activeObject" v-if="isRepeat" /> <fill-repeat :object="activeObject" v-if="isRepeat" />
<shape-setting :object="activeObject" v-if="isShape && !isRepeat" />
</div> </div>
</div> </div>
</template> </template>
@@ -17,6 +18,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject, computed, watch, onMounted } from 'vue' import { ref, inject, computed, watch, onMounted } from 'vue'
import FillRepeat from './fill-repeat.vue' import FillRepeat from './fill-repeat.vue'
import ShapeSetting from './shape-setting.vue'
const props = defineProps({}) const props = defineProps({})
const layerManager = inject('layerManager') as any const layerManager = inject('layerManager') as any
const canvasManager = inject('canvasManager') as any const canvasManager = inject('canvasManager') as any
@@ -25,8 +27,10 @@
const layers = computed(() => layerManager.layers.value) const layers = computed(() => layerManager.layers.value)
const activeObject = ref(null) const activeObject = ref(null)
const shapes = ['rect', 'line', 'path', 'triangle', 'polygon', 'ellipse']
const isShape = computed(() => shapes.includes(activeObject.value?.type))
const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat') const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat')
const isShow = computed(() => isRepeat.value) const isShow = computed(() => isRepeat.value || isShape.value)
const updateActiveObject = () => { const updateActiveObject = () => {
const obj = layers.value.find((v: any) => v.info.id === activeID.value) const obj = layers.value.find((v: any) => v.info.id === activeID.value)
@@ -85,7 +89,12 @@
overflow-y: auto; overflow-y: auto;
&:deep(> div) { &:deep(> div) {
> div { > div {
margin-bottom: 1.6rem; margin-bottom: var(--details-item-margin-bottom, 1.6rem);
&:last-child {
margin-bottom: 0;
}
}
&.h > div {
> .title { > .title {
font-size: 1.4rem; font-size: 1.4rem;
color: #000; color: #000;
@@ -97,6 +106,20 @@
padding: 0 1.4rem; padding: 0 1.4rem;
} }
} }
&.v > div {
display: flex;
align-items: center;
justify-content: center;
> .label {
min-width: 6rem;
margin-right: 0.8rem;
font-size: 1.2rem;
text-align: right;
}
> .value {
flex: 1;
}
}
} }
} }
} }

View File

@@ -0,0 +1,68 @@
<template>
<div class="shape-setting v">
<!-- <div>
<div class="label">填充颜色</div>
<div class="value">
<el-color-picker
v-model="data.fill"
show-alpha
:predefine="['transparent', '#000', '#f00', '#0f0', '#00f']"
@change="onChange"
/>
</div>
</div> -->
</div>
</template>
<script setup lang="ts">
import { ref, inject, computed, nextTick, onBeforeUnmount, reactive } from 'vue'
import { ElColorPicker } from 'element-plus'
import { getTransformScaleAngle } from '../../manager/ObjectManager'
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
import DepthSlider from '../tools/depth-slider.vue'
import DepthInput from '../tools/depth-input.vue'
const objectManager = inject('objectManager') as any
const stateManager = inject('stateManager') as any
const props = defineProps({
object: {
type: Object,
default: () => ({})
}
})
const id = computed(() => props.object.info.id)
const data = reactive({
fill: '',
stroke: '',
strokeWidth: 0
})
const updateData = async () => {
await nextTick()
data.fill = props.object.fill
data.stroke = props.object.stroke
data.strokeWidth = props.object.strokeWidth
}
updateData()
const onInpot = () => setPriority(false)
const onChange = () => setPriority(true)
const setPriority = (isRecord: boolean) => {
const options = { ...data }
objectManager.updateProperty(id.value, options, isRecord)
}
stateManager.event.add('canvas:undo', updateData)
stateManager.event.add('canvas:redo', updateData)
onBeforeUnmount(() => {
stateManager.event.remove('canvas:undo', updateData)
stateManager.event.remove('canvas:redo', updateData)
})
</script>
<style lang="less" scoped>
.shape-setting {
--details-item-margin-bottom: 1rem;
> div {
}
}
</style>

View File

@@ -158,12 +158,16 @@
const exportCanvas = () => { const exportCanvas = () => {
// 导出图片 // 导出图片
exportCanvasToImage(canvasManager.canvas).then((url) => { // exportCanvasToImage(canvasManager.canvas).then((url) => {
const a = document.createElement('a') // const a = document.createElement('a')
a.href = url // a.href = url
a.download = 'canvas.png' // a.download = 'canvas.png'
a.click() // a.click()
}) // })
const object = canvasManager.getCanvasDisUrlJSON()
console.log(object)
const canvas = canvasManager.processCanvasDisUrlJSON(object)
console.log(canvas)
} }
// 导出到本地存储 // 导出到本地存储
const exportCanvasToLocalStorage = () => { const exportCanvasToLocalStorage = () => {

View File

@@ -121,21 +121,12 @@ export class CanvasManager {
this.setupBrushEvents() this.setupBrushEvents()
/** 测试-开始 */ /** 测试-开始 */
// this.stateManager.setIsRecord(false) this.stateManager.setIsRecord(false)
// // 创建矩形 const rect = await this.layerManager.createRectLayer({ left: 200 })
// const rect = await this.layerManager.createRectLayer({ await this.layerManager.createStarLayer({ left: 400 })
// left: 400, await this.layerManager.createArrowLayer({ left: 600 })
// top: 100, this.layerManager.setActiveID(rect.info.id)
// }) this.stateManager.setIsRecord(true)
// //创建圆形
// const circle = await this.layerManager.createCircleLayer({
// left: 200,
// top: 200,
// })
// // 文字
// const text = await this.layerManager.createTextLayer('Hello World');
// this.layerManager.setActiveID(text.info.id)
// this.stateManager.setIsRecord(true)
/** 测试-结束 */ /** 测试-结束 */
this.resetZoom(false, true)// 画布居中 this.resetZoom(false, true)// 画布居中
@@ -289,6 +280,39 @@ export class CanvasManager {
callback?.(true) callback?.(true)
}) })
} }
/** 导出画布为处理图片的JSON */
getCanvasDisUrlJSON() {
const canvas = this.canvas.toJSON()
const images = [];
var i = 0;
const create = (url) => {
const logo = `xxxxxxxx_${i++}_xxxxxxxx`;
images.push({ index: logo, url })
return logo
}
canvas.objects.forEach((object: any) => {
if (object.thumbnail) {
object.thumbnail = create(object.thumbnail)
}
if (object.src) {
object.src = create(object.src)
}
if (object.fill?.source) {
object.fill.source = create(object.fill.source)
}
if (object.info?.fill?.source) {
object.info.fill.source = create(object.info.fill.source)
}
})
return { canvas: JSON.stringify(canvas), images }
}
/** 处理JSON为正常画布 */
processCanvasDisUrlJSON(obj: { canvas: string, images: Object[] }) {
var json = obj.canvas;
const images = obj.images || []
images.forEach((v: any) => json = json.replace(new RegExp(v.index), v.url))
return JSON.parse(json)
}
dispose() { dispose() {
this.animationManager?.dispose() this.animationManager?.dispose()
this.eventManager?.dispose() this.eventManager?.dispose()

View File

@@ -173,6 +173,7 @@ export class LayerManager {
width: 100, width: 100,
height: 100, height: 100,
fill: '#000', fill: '#000',
strokeWidth: 0,
...(options || {}), ...(options || {}),
info: { info: {
id: createId("rect"), id: createId("rect"),
@@ -212,6 +213,7 @@ export class LayerManager {
const ellipseObject = new fabric.Ellipse({ const ellipseObject = new fabric.Ellipse({
radius: 50, radius: 50,
fill: '#000', fill: '#000',
strokeWidth: 0,
...(options || {}), ...(options || {}),
info: { info: {
id: createId("ellipse"), id: createId("ellipse"),
@@ -230,6 +232,7 @@ export class LayerManager {
width: 100, width: 100,
height: 100, height: 100,
fill: '#000', fill: '#000',
strokeWidth: 0,
...(options || {}), ...(options || {}),
info: { info: {
id: createId("triangle"), id: createId("triangle"),
@@ -249,6 +252,7 @@ export class LayerManager {
delete options.points delete options.points
const starObject = new fabric.Polygon(getStarArr(width, height), { const starObject = new fabric.Polygon(getStarArr(width, height), {
fill: '#000', fill: '#000',
strokeWidth: 0,
...(options || {}), ...(options || {}),
info: { info: {
id: createId("star"), id: createId("star"),

View File

@@ -207,18 +207,15 @@ export class ObjectManager {
} }
} }
/** 修改属性
/** 修改透明度
* @param id 目标对象ID * @param id 目标对象ID
* @param options 透明度参数 * @param options 参数
* @param options.opacity 透明度
* @param isRecord 是否记录 * @param isRecord 是否记录
*/ */
async updateOpacity(id: string, options: any, isRecord: boolean) { async updateProperty(id: string, options: any, isRecord: boolean) {
const object = this.getFillRepeatObject(id) const object = this.canvasManager.getObjectById(id)
if (!object) return null if (!object) return null
const opacity = options.opacity object.set(options);
object.set("opacity", opacity);
this.canvasManager.renderAll() this.canvasManager.renderAll()
if (isRecord) { if (isRecord) {
this.stateManager.recordState() this.stateManager.recordState()

View File

@@ -1,67 +0,0 @@
import { OperationType, OperationTypes } from "../tools/layerHelper";
import { fabric } from 'fabric-with-all'
/** 矩形工具管理器 */
export class RectToolManager {
// 管理器
canvasManager: any
stateManager: any
layerManager: any
isDragging: boolean = false
startX: number = 0
startY: number = 0
demoObject: any
tools = [
OperationType.RECTANGLE
]
constructor(options) {
this.canvasManager = options.canvasManager
this.stateManager = options.stateManager
this.layerManager = options.layerManager
}
mouseDownEvent(e) {
this.isDragging = true
this.startX = e.absolutePointer.x
this.startY = e.absolutePointer.y
const rect = new fabric.Rect({
left: this.startX,
top: this.startY,
width: 0,
height: 0,
fill: '#000',
evented: false,
})
this.demoObject = rect
this.canvasManager.canvas.add(rect)
this.canvasManager.canvas.renderAll()
}
mouseMoveEvent(e) {
if (!this.isDragging) return;
var width = e.absolutePointer.x - this.startX
var height = e.absolutePointer.y - this.startY
var left = this.startX
var top = this.startY
if (width < 0) {
left += width
width = -width
}
if (height < 0) {
top += height
height = -height
}
this.demoObject.set({ width, height, left, top })
this.canvasManager.canvas.renderAll()
}
mouseUpEvent(e) {
if (!this.isDragging) return;
this.isDragging = false;
const object = this.demoObject.toJSON("evented")
if (object.width === 0) object.width = 100
if (object.height === 0) object.height = 100
this.layerManager.createRectLayer(object, true)
this.canvasManager.canvas.remove(this.demoObject)
this.canvasManager.canvas.renderAll()
}
dispose() { }
}

View File

@@ -120,6 +120,7 @@ export class ShapeToolManager {
width: 0, width: 0,
height: 0, height: 0,
fill: '#000', fill: '#000',
strokeWidth: 0,
}) })
return rect return rect
} }
@@ -159,6 +160,7 @@ export class ShapeToolManager {
left: this.startX, left: this.startX,
top: this.startY, top: this.startY,
fill: '#000', fill: '#000',
strokeWidth: 0,
}) })
return circle return circle
} }
@@ -180,6 +182,7 @@ export class ShapeToolManager {
width: 0, width: 0,
height: 0, height: 0,
fill: '#000', fill: '#000',
strokeWidth: 0,
}) })
return triangle return triangle
} }
@@ -203,6 +206,7 @@ export class ShapeToolManager {
fill: '#000', fill: '#000',
strokeLineJoin: 'round', // 圆角连接 strokeLineJoin: 'round', // 圆角连接
strokeLineCap: 'round', // 圆角端点 strokeLineCap: 'round', // 圆角端点
strokeWidth: 0,
}); });
return star return star