From 88a9d5b24315475ea9d0fd861721966645a98478 Mon Sep 17 00:00:00 2001 From: zhangyahui Date: Tue, 17 Mar 2026 15:39:44 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20web=20sources=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E4=B8=89=E8=A1=8C=E7=9C=81=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 3 ++ src/utils/ellispsis.ts | 52 +++++++++++++++++++++ src/views/home/agent/components/Preview.vue | 19 ++++++-- 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/utils/ellispsis.ts diff --git a/src/main.ts b/src/main.ts index f630064..9cc7718 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,9 +17,12 @@ import 'element-plus/dist/index.css' import ignoredWarning from './ignoredWarning' +import vEllipsis from './utils/ellispsis' + const app = createApp(App) ignoredWarning(app) +app.directive('ellipsis', vEllipsis) app.use(router) .use(ElementPlus) .use(store) diff --git a/src/utils/ellispsis.ts b/src/utils/ellispsis.ts new file mode 100644 index 0000000..23f6ec8 --- /dev/null +++ b/src/utils/ellispsis.ts @@ -0,0 +1,52 @@ +import type { Directive } from 'vue' +/** + * 多行文本省略指令(悬浮显示完整内容) + * @directive v-ellipsis + * @param {number} [value=3] - 超过value行数时显示省略号,不传参数默认为3 + */ + +const applyStyles = (el: HTMLElement, binding: any) => { + const lines = typeof binding.value === 'number' && binding.value > 0 ? binding.value : 3 + + el.style.display = '-webkit-box' + el.style.webkitBoxOrient = 'vertical' + el.style.overflow = 'hidden' + el.style.webkitLineClamp = lines.toString() + + el.style.maxHeight = `${lines}lh` +} + +const checkTruncated = (el: HTMLElement) => { + const isTruncated = el.scrollHeight > el.clientHeight + 1 + if (isTruncated) { + el.title = el.textContent?.trim() || '' + } else { + el.removeAttribute('title') + } +} + +const vEllipsis: Directive = { + mounted(el, binding) { + applyStyles(el, binding) + checkTruncated(el) + + const ro = new ResizeObserver(() => checkTruncated(el)) + ro.observe(el) + ;(el as any)._ellipsisObserver = ro + }, + + updated(el, binding) { + applyStyles(el, binding) + checkTruncated(el) + }, + + unmounted(el) { + const ro = (el as any)._ellipsisObserver + if (ro) { + ro.disconnect() + delete (el as any)._ellipsisObserver + } + } +} + +export default vEllipsis diff --git a/src/views/home/agent/components/Preview.vue b/src/views/home/agent/components/Preview.vue index b21caff..8fd42dd 100644 --- a/src/views/home/agent/components/Preview.vue +++ b/src/views/home/agent/components/Preview.vue @@ -53,12 +53,12 @@ @@ -328,16 +328,27 @@ .url-item { width: 24rem; height: 28.7rem; + line-height: 2rem; word-break: break-all; background: url('@/assets/images/web-card.png') no-repeat; background-size: 100% 100%; padding: 5rem 1.5rem; + row-gap: 0.6rem; + // .url-title,.url-link{ + // // 两行省略 + // display: -webkit-box; + // -webkit-line-clamp: 2; + // line-clamp: 2; + // -webkit-box-orient: vertical; + // overflow: hidden; + // text-overflow: ellipsis; + // } .url-title { cursor: pointer; font-family: 'Medium'; font-size: 1.6rem; color: #232323; - padding-bottom: 0.6rem; + max-height: 4rem; .link-outer { width: 1.2rem; height: 1.2rem; From dd27ffd229f9e3a1e38368a24dac129917e634fe Mon Sep 17 00:00:00 2001 From: zhangyahui Date: Tue, 17 Mar 2026 15:41:44 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=88=87=E6=8D=A2=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=8A=8A=E5=8F=B3=E4=BE=A7=E5=88=87=E6=8D=A2=E5=88=B0?= =?UTF-8?q?sketch=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/home/agent/index.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/home/agent/index.vue b/src/views/home/agent/index.vue index bb46454..8ce3efe 100644 --- a/src/views/home/agent/index.vue +++ b/src/views/home/agent/index.vue @@ -87,7 +87,8 @@ ) } - const handleGetProjectInfoAndHistory = () => { +const handleGetProjectInfoAndHistory = () => { + handleOpenSketch() getProjectInfo({ id: route.params.id }).then((res) => { if (res) agentRef.value.setChatInfo(res) let data = res?.project || res From f12d6aec962f7cc18fc69538c95f529e733ec291 Mon Sep 17 00:00:00 2001 From: lzp Date: Tue, 17 Mar 2026 17:17:48 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E7=BB=98=E5=88=B6=E5=BD=A2=E7=8A=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/dc/arrow.svg | 3 + src/assets/icons/dc/copy.svg | 4 + src/assets/icons/dc/ellipse.svg | 3 + src/assets/icons/dc/line.svg | 3 + src/assets/icons/dc/star.svg | 3 + src/assets/icons/dc/triangle.svg | 3 + .../components/depth-header-tools.vue | 61 ++++- .../components/details-panel/fill-repeat.vue | 24 ++ .../components/layer-panel/layer-item.vue | 2 +- .../components/layer-panel/layer-list.vue | 2 +- .../DepthCanvas/manager/LayerManager.ts | 127 ++++++++- .../DepthCanvas/manager/ObjectManager.ts | 19 ++ ...oolManager.ts => ShapeToolManager copy.ts} | 4 + .../DepthCanvas/manager/ShapeToolManager.ts | 248 ++++++++++++++++++ .../Canvas/DepthCanvas/manager/ToolManager.ts | 26 ++ .../manager/events/CanvasEventManager.js | 42 +-- .../manager/events/KeyEventManager.ts | 5 +- .../Canvas/DepthCanvas/tools/canvasMethod.js | 55 +++- .../Canvas/DepthCanvas/tools/layerHelper.js | 9 + 19 files changed, 606 insertions(+), 37 deletions(-) create mode 100644 src/assets/icons/dc/arrow.svg create mode 100644 src/assets/icons/dc/copy.svg create mode 100644 src/assets/icons/dc/ellipse.svg create mode 100644 src/assets/icons/dc/line.svg create mode 100644 src/assets/icons/dc/star.svg create mode 100644 src/assets/icons/dc/triangle.svg rename src/components/Canvas/DepthCanvas/manager/{RectToolManager.ts => ShapeToolManager copy.ts} (93%) create mode 100644 src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts diff --git a/src/assets/icons/dc/arrow.svg b/src/assets/icons/dc/arrow.svg new file mode 100644 index 0000000..4911aad --- /dev/null +++ b/src/assets/icons/dc/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/dc/copy.svg b/src/assets/icons/dc/copy.svg new file mode 100644 index 0000000..2edd3e6 --- /dev/null +++ b/src/assets/icons/dc/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/dc/ellipse.svg b/src/assets/icons/dc/ellipse.svg new file mode 100644 index 0000000..8ba4aee --- /dev/null +++ b/src/assets/icons/dc/ellipse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/dc/line.svg b/src/assets/icons/dc/line.svg new file mode 100644 index 0000000..bfc76e8 --- /dev/null +++ b/src/assets/icons/dc/line.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/dc/star.svg b/src/assets/icons/dc/star.svg new file mode 100644 index 0000000..30b0b5c --- /dev/null +++ b/src/assets/icons/dc/star.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/dc/triangle.svg b/src/assets/icons/dc/triangle.svg new file mode 100644 index 0000000..7160af0 --- /dev/null +++ b/src/assets/icons/dc/triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue b/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue index 040aac7..3d1e402 100644 --- a/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue +++ b/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue @@ -42,11 +42,13 @@ const toolManager = inject('toolManager') as any const objectManager = inject('objectManager') as any const canvasManager = inject('canvasManager') as any + const layerManager = inject('layerManager') as any const tool = computed(() => toolManager.currentTool.value) const historyIndex = computed(() => stateManager.historyIndex.value) const historyList = computed(() => stateManager.historyList.value) const isUndo = computed(() => !historyList.value[historyIndex.value - 1]) const isRedo = computed(() => !historyList.value[historyIndex.value + 1]) + const activeLayerId = computed(() => layerManager.activeID.value) const tools = ref([ { name: OperationType.SELECT, icon: 'dc-select', iconSize: 16, disabled: ref(false) }, { name: OperationType.PAN, icon: 'dc-move', iconSize: 18, disabled: ref(false) }, @@ -60,7 +62,42 @@ on: () => onImageClick() }, { name: OperationType.SELECTBOX, icon: 'dc-selectbox', iconSize: 16, disabled: ref(false) }, - { name: OperationType.RECTANGLE, icon: 'dc-rectangle', iconSize: 16, disabled: ref(false) }, + { + name: OperationType.RECTANGLE, + icon: 'dc-rectangle', + iconSize: 16, + disabled: ref(false) + }, + { + name: OperationType.LINE, + icon: 'dc-line', + iconSize: 16, + disabled: ref(false) + }, + { + name: OperationType.ARROW, + icon: 'dc-arrow', + iconSize: 16, + disabled: ref(false) + }, + { + name: OperationType.ELLIPSE, + icon: 'dc-ellipse', + iconSize: 16, + disabled: ref(false) + }, + { + name: OperationType.TRIANGLE, + icon: 'dc-triangle', + iconSize: 16, + disabled: ref(false) + }, + { + name: OperationType.STAR, + icon: 'dc-star', + iconSize: 16, + disabled: ref(false) + }, { type: 'line' }, { name: OperationType.UNDO, @@ -75,6 +112,20 @@ iconSize: 18, disabled: isRedo, on: () => stateManager.redoState() + }, + { + name: 'copy', + icon: 'dc-copy', + iconSize: 16, + disabled: computed(() => !activeLayerId.value), + on: () => onCopyActiveLayer() + }, + { + name: 'delete', + icon: 'dc-delete', + iconSize: 18, + disabled: computed(() => !activeLayerId.value), + on: () => onDeleteActiveLayer() } ]) const onClickTool = (tool: any) => { @@ -91,6 +142,14 @@ objectManager.setBlendMode(id, BlendMode.MULTIPLY) objectManager.setFillRepeat(id) } + const onCopyActiveLayer = () => { + if (!activeLayerId.value) return + layerManager.copyLayerById(activeLayerId.value) + } + const onDeleteActiveLayer = () => { + if (!activeLayerId.value) return + layerManager.deleteLayerById(activeLayerId.value) + } const onWorkbench = async () => { exportCanvasToImage(canvasManager.canvas).then((url) => { emit('workbench', { url }) diff --git a/src/components/Canvas/DepthCanvas/components/details-panel/fill-repeat.vue b/src/components/Canvas/DepthCanvas/components/details-panel/fill-repeat.vue index 7c350d8..5fa72c2 100644 --- a/src/components/Canvas/DepthCanvas/components/details-panel/fill-repeat.vue +++ b/src/components/Canvas/DepthCanvas/components/details-panel/fill-repeat.vue @@ -33,6 +33,19 @@ /> +
+
Opacity
+
+ +
+
Gap X
@@ -101,6 +114,7 @@ const gapX = ref(0) const gapY = ref(0) const offset = ref({ x: 0, y: 0 }) + const opacity = ref(100) const updateData = async () => { await nextTick() @@ -113,6 +127,7 @@ x: Math.round((fill.offsetX / props.object.width) * 100), y: Math.round((fill.offsetY / props.object.height) * 100) } + opacity.value = Math.round(props.object.opacity * 100) } updateData() @@ -138,6 +153,15 @@ objectManager.updateFillRepeatGap(id.value, options, isRecord) } + const inputOpacity = () => setOpacity(false) + const changeOpacity = () => setOpacity(true) + const setOpacity = (isRecord: boolean) => { + const options = { + opacity: opacity.value / 100 + } + objectManager.updateOpacity(id.value, options, isRecord) + } + stateManager.event.add('canvas:undo', updateData) stateManager.event.add('canvas:redo', updateData) onBeforeUnmount(() => { diff --git a/src/components/Canvas/DepthCanvas/components/layer-panel/layer-item.vue b/src/components/Canvas/DepthCanvas/components/layer-panel/layer-item.vue index 51f044a..d1a72bb 100644 --- a/src/components/Canvas/DepthCanvas/components/layer-panel/layer-item.vue +++ b/src/components/Canvas/DepthCanvas/components/layer-panel/layer-item.vue @@ -22,7 +22,7 @@ > - +
diff --git a/src/components/Canvas/DepthCanvas/components/layer-panel/layer-list.vue b/src/components/Canvas/DepthCanvas/components/layer-panel/layer-list.vue index 0f6f19f..d4852e3 100644 --- a/src/components/Canvas/DepthCanvas/components/layer-panel/layer-list.vue +++ b/src/components/Canvas/DepthCanvas/components/layer-panel/layer-list.vue @@ -63,7 +63,7 @@ layerManager.dragSort(data.info.id, newIndex) } const addLayer = () => { - layerManager.createEmptyLayer() + layerManager.createEmptyLayer(true, true) } diff --git a/src/components/Canvas/DepthCanvas/manager/LayerManager.ts b/src/components/Canvas/DepthCanvas/manager/LayerManager.ts index 0711283..b193e07 100644 --- a/src/components/Canvas/DepthCanvas/manager/LayerManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/LayerManager.ts @@ -3,6 +3,7 @@ import { fabric } from 'fabric-with-all' import { createId } from '../../tools/tools' import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod' import { OperationType } from '../tools/layerHelper' +import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod' export class LayerManager { stateManager: any @@ -53,6 +54,7 @@ export class LayerManager { this.canvasManager.renderAll() } } + /** 删除指定图层 */ deleteLayerById(id, isActive = true) { this.canvasManager.deleteObjectById(id) if (id === this.activeID.value && isActive) { @@ -60,6 +62,24 @@ export class LayerManager { } if (isActive) this.stateManager.recordState() } + /** 复制指定图层 */ + copyLayerById(id) { + const object = this.canvasManager.getObjectById(id) + if (!object) return console.warn('复制图层失败,对象不存在ID:', id) + cloneObjects([object]).then(objects => { + const newObject = objects[0] + const info = JSON.parse(JSON.stringify(newObject.info)) + info.id = createId("image") + // info.name = info.name + newObject.set({ + top: newObject.top + 15, + left: newObject.left + 15, + info: info, + }) + this.canvasManager.add(newObject) + this.setActiveID(newObject.info.id) + }) + } // 拖拽排序 dragSort(id, newIndex) { const index = Math.abs(this.layers.value.length - newIndex - 1) @@ -90,7 +110,7 @@ export class LayerManager { } } /** 创建空图层 */ - createEmptyLayer(isRecord = true) { + createEmptyLayer(isRecord = true, isActive = false) { const emptyObject = new fabric.Rect({ width: 0, height: 0, @@ -102,6 +122,7 @@ export class LayerManager { }) this.setLayerPosition(emptyObject) this.canvasManager.add(emptyObject, isRecord) + if (isActive) this.setActiveID(emptyObject.info.id, false) return emptyObject } /** 创建文本图层 */ @@ -138,23 +159,109 @@ export class LayerManager { if (isActive) this.setActiveID(rectObject.info.id) return rectObject } - /** 创建圆形图层 */ - async createCircleLayer(options?: any, isActive = false) { - const circleObject = new fabric.Circle({ + /** 创建直线图层 */ + async createLineLayer(options?: any, isActive = false) { + const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0] + delete options.x1 + delete options.y1 + delete options.x2 + delete options.y2 + const lineObject = new fabric.Line(line, { + stroke: 'black', // 线条颜色 + strokeWidth: 2, // 线条粗细 + ...(options || {}), + info: { + id: createId("line"), + name: '直线图层', + ...(options?.info || {}), + } + }) + this.setLayerPosition(lineObject, options) + await this.canvasManager.add(lineObject) + if (isActive) this.setActiveID(lineObject.info.id) + return lineObject + } + /** 创建椭圆图层 */ + async createEllipseLayer(options?: any, isActive = false) { + const ellipseObject = new fabric.Ellipse({ radius: 50, fill: '#000', ...(options || {}), info: { - id: createId("circle"), - name: '圆形图层', + id: createId("ellipse"), + name: '椭圆图层', ...(options?.info || {}), } }) - this.setLayerPosition(circleObject, options) - await this.canvasManager.add(circleObject) - if (isActive) this.setActiveID(circleObject.info.id) - return circleObject + this.setLayerPosition(ellipseObject, options) + await this.canvasManager.add(ellipseObject) + if (isActive) this.setActiveID(ellipseObject.info.id) + return ellipseObject } + /** 创建三角形图层 */ + async createTriangleLayer(options?: any, isActive = false) { + const triangleObject = new fabric.Triangle({ + width: 100, + height: 100, + fill: '#000', + ...(options || {}), + info: { + id: createId("triangle"), + name: '三角形图层', + ...(options?.info || {}), + } + }) + this.setLayerPosition(triangleObject, options) + await this.canvasManager.add(triangleObject) + if (isActive) this.setActiveID(triangleObject.info.id) + return triangleObject + } + /** 创建五角星图层 */ + async createStarLayer(options?: any, isActive = false) { + const width = options?.width || 100 + const height = options?.height || 100 + delete options.points + const starObject = new fabric.Polygon(getStarArr(width, height), { + fill: '#000', + ...(options || {}), + info: { + id: createId("star"), + name: '五角星图层', + ...(options?.info || {}), + } + }) + this.setLayerPosition(starObject, options) + await this.canvasManager.add(starObject) + if (isActive) this.setActiveID(starObject.info.id) + return starObject + } + /** 创建箭头图层 */ + async createArrowLayer(options?: any, isActive = false) { + const width = options?.width || 100 + const height = options?.height || 10 + delete options.width + delete options.height + const arrowObject = new fabric.Path(getArrowPath(width, height), { + stroke: '#000', // 只设置边框颜色 + strokeWidth: 3, // 边框宽度 + fill: 'transparent', // 不填充 + strokeLineCap: 'round', + strokeLineJoin: 'round', + ...(options || {}), + info: { + id: createId("star"), + name: '箭头图层', + ...(options?.info || {}), + } + }); + this.setLayerPosition(arrowObject, options) + this.canvasManager.add(arrowObject) + if (isActive) this.setActiveID(arrowObject.info.id) + return arrowObject + } + + + /** 创建图片图层 */ async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) { const canvasWidth = this.canvasManager.canvasWidth diff --git a/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts b/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts index e7b3e13..363808c 100644 --- a/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts @@ -207,5 +207,24 @@ export class ObjectManager { } } + + /** 修改透明度 + * @param id 目标对象ID + * @param options 透明度参数 + * @param options.opacity 透明度 + * @param isRecord 是否记录 + */ + async updateOpacity(id: string, options: any, isRecord: boolean) { + const object = this.getFillRepeatObject(id) + if (!object) return null + const opacity = options.opacity + object.set("opacity", opacity); + this.canvasManager.renderAll() + if (isRecord) { + this.stateManager.recordState() + this.layerManager.updateLayerThumbnailsById(id) + } + } + dispose() { } } \ No newline at end of file diff --git a/src/components/Canvas/DepthCanvas/manager/RectToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager copy.ts similarity index 93% rename from src/components/Canvas/DepthCanvas/manager/RectToolManager.ts rename to src/components/Canvas/DepthCanvas/manager/ShapeToolManager copy.ts index 2c5eeab..3356852 100644 --- a/src/components/Canvas/DepthCanvas/manager/RectToolManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager copy.ts @@ -1,3 +1,4 @@ +import { OperationType, OperationTypes } from "../tools/layerHelper"; import { fabric } from 'fabric-with-all' /** 矩形工具管理器 */ export class RectToolManager { @@ -10,6 +11,9 @@ export class RectToolManager { startX: number = 0 startY: number = 0 demoObject: any + tools = [ + OperationType.RECTANGLE + ] constructor(options) { this.canvasManager = options.canvasManager this.stateManager = options.stateManager diff --git a/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts new file mode 100644 index 0000000..14f749f --- /dev/null +++ b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts @@ -0,0 +1,248 @@ +import { OperationType, OperationTypes } from "../tools/layerHelper"; +import { getStarArr, getArrowPath, distance, angleBetweenPointsDegrees } from "../tools/canvasMethod"; +import { fabric } from 'fabric-with-all' +/** 形状管理器 */ +export class ShapeToolManager { + // 管理器 + canvasManager: any + stateManager: any + layerManager: any + toolManager: any + + isDragging: boolean = false + startX: number = 0 + startY: number = 0 + demoObject: any + tools = [ + OperationType.RECTANGLE, + OperationType.LINE, + OperationType.ARROW, + OperationType.ELLIPSE, + OperationType.TRIANGLE, + OperationType.STAR, + ] + constructor(options) { + this.canvasManager = options.canvasManager + this.stateManager = options.stateManager + this.layerManager = options.layerManager + this.toolManager = options.toolManager + } + mouseDownEvent(e) { + this.isDragging = false + this.demoObject = null + + this.startX = e.absolutePointer.x + this.startY = e.absolutePointer.y + const currentTool = this.toolManager.currentTool.value + if (currentTool === OperationType.RECTANGLE) { + this.demoObject = this.downRectangle() + } else if (currentTool === OperationType.LINE) { + this.demoObject = this.downLine() + } else if (currentTool === OperationType.ELLIPSE) { + this.demoObject = this.downEllipse() + } else if (currentTool === OperationType.TRIANGLE) { + this.demoObject = this.downTriangle() + } else if (currentTool === OperationType.STAR) { + this.demoObject = this.downStar() + } else if (currentTool === OperationType.ARROW) { + this.demoObject = this.downArrow() + } + if (!this.demoObject) return; + this.demoObject.set({ + evented: false, + }) + this.demoObject.set + this.isDragging = true + this.canvasManager.canvas.add(this.demoObject) + 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 + } + const currentTool = this.toolManager.currentTool.value + if (currentTool === OperationType.RECTANGLE) { + this.moveRectangle({ width, height, left, top }) + } else if (currentTool === OperationType.LINE) { + this.moveLine(e.absolutePointer) + } else if (currentTool === OperationType.ELLIPSE) { + this.moveEllipse({ width, height, left, top }) + } else if (currentTool === OperationType.TRIANGLE) { + this.moveTriangle({ width, height, left, top }) + } else if (currentTool === OperationType.STAR) { + this.moveStar({ width, height, left, top }) + } else if (currentTool === OperationType.ARROW) { + this.moveArrow(e.absolutePointer) + } + this.demoObject.set({ + evented: false, + }) + this.canvasManager.canvas.renderAll() + } + mouseUpEvent(e) { + if (!this.isDragging) return; + this.isDragging = false; + const object = this.demoObject.toJSON("evented") + const currentTool = this.toolManager.currentTool.value + if (currentTool === OperationType.RECTANGLE) { + this.upRectangle(object) + } else if (currentTool === OperationType.LINE) { + this.upLine(object) + } else if (currentTool === OperationType.ELLIPSE) { + this.upEllipse(object) + } else if (currentTool === OperationType.TRIANGLE) { + this.upTriangle(object) + } else if (currentTool === OperationType.STAR) { + this.upStar(object) + } else if (currentTool === OperationType.ARROW) { + this.upArrow(object) + } + this.canvasManager.canvas.remove(this.demoObject) + this.demoObject = null + this.canvasManager.canvas.renderAll() + } + + /** 绘制矩形 */ + downRectangle() { + const rect = new fabric.Rect({ + left: this.startX, + top: this.startY, + width: 0, + height: 0, + fill: '#000', + }) + return rect + } + moveRectangle({ width, height, left, top }) { + this.demoObject.set({ width, height, left, top }) + + } + upRectangle(object) { + if (object.width === 0) object.width = 100 + if (object.height === 0) object.height = 100 + this.layerManager.createRectLayer(object, true) + } + + /** 绘制直线 */ + downLine() { + const line = new fabric.Line([this.startX, this.startY, this.startX, this.startY], { + stroke: 'black', // 线条颜色 + strokeWidth: 2 // 线条粗细 + }) + return line + } + moveLine({ x, y }) { + this.demoObject.set({ + x1: this.startX, + y1: this.startY, + x2: x, + y2: y, + }) + } + upLine(object) { + this.layerManager.createLineLayer(object, true) + } + + /** 绘制椭圆 */ + downEllipse() { + const circle = new fabric.Ellipse({ + left: this.startX, + top: this.startY, + fill: '#000', + }) + return circle + } + moveEllipse({ width, height, left, top }) { + this.demoObject.set({ rx: width / 2, ry: height / 2, left, top }) + } + upEllipse(object) { + if (object.rx === 0) object.rx = 50 + if (object.ry === 0) object.ry = 50 + this.layerManager.createEllipseLayer(object, true) + } + + + /** 绘制三角形 */ + downTriangle() { + const triangle = new fabric.Triangle({ + left: this.startX, + top: this.startY, + width: 0, + height: 0, + fill: '#000', + }) + return triangle + } + moveTriangle({ width, height, left, top }) { + this.demoObject.set({ width, height, left, top }) + } + upTriangle(object) { + if (object.width === 0) object.width = 100 + if (object.height === 0) object.height = 100 + this.layerManager.createTriangleLayer(object, true) + } + + + /** 绘制五角星 */ + downStar() { + const star = new fabric.Polygon(getStarArr(0, 0), { + left: this.startX, + top: this.startY, + width: 0, + height: 0, + fill: '#000', + strokeLineJoin: 'round', // 圆角连接 + strokeLineCap: 'round', // 圆角端点 + }); + + return star + } + moveStar({ width, height, left, top }) { + this.demoObject.set({ left, top, width, height, points: getStarArr(width, height) }) + } + upStar(object) { + if (object.width === 0) object.width = 100 + if (object.height === 0) object.height = 100 + this.layerManager.createStarLayer(object, true) + } + + /** 绘制箭头 */ + downArrow() { + return new fabric.Path(); + } + moveArrow({ x, y }) { + const width = distance(this.startX, this.startY, x, y) + const angle = angleBetweenPointsDegrees(this.startX, this.startY, x, y) + this.canvasManager.canvas.remove(this.demoObject) + const arrow = new fabric.Path(getArrowPath(width, 10), { + left: this.startX, + top: this.startY, + stroke: '#000', // 只设置边框颜色 + strokeWidth: 3, // 边框宽度 + fill: 'transparent', // 不填充 + strokeLineCap: 'round', + strokeLineJoin: 'round', + originY: 'center', + angle: angle, + }); + this.canvasManager.canvas.add(arrow) + this.demoObject = arrow + } + upArrow(object) { + this.layerManager.createArrowLayer(object, true) + } + + + + dispose() { } +} diff --git a/src/components/Canvas/DepthCanvas/manager/ToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ToolManager.ts index 8b17c25..cd640d8 100644 --- a/src/components/Canvas/DepthCanvas/manager/ToolManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/ToolManager.ts @@ -54,6 +54,32 @@ export class ToolManager { name: OperationType.RECTANGLE, cursor: "crosshair", }, + /** 直线工具 */ + { + name: OperationType.LINE, + cursor: "crosshair", + }, + /** 箭头工具 */ + { + name: OperationType.ARROW, + cursor: "crosshair", + }, + /** 椭圆工具 */ + { + name: OperationType.ELLIPSE, + cursor: "crosshair", + }, + /** 三角形工具 */ + { + name: OperationType.TRIANGLE, + cursor: "crosshair", + }, + /** 五角星工具 */ + { + name: OperationType.STAR, + cursor: "crosshair", + }, + ] } onMounted() { diff --git a/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js b/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js index 55f0b2a..335a056 100644 --- a/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js +++ b/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js @@ -1,6 +1,6 @@ import { isBoolean } from "lodash-es"; import { OperationType, OperationTypes } from "../../tools/layerHelper"; -import { RectToolManager } from "../RectToolManager" +import { ShapeToolManager } from "../ShapeToolManager" import { AISelectboxToolManager } from "../AISelectboxToolManager" @@ -32,7 +32,7 @@ export class CanvasEventManager { toolManager: this.toolManager, layerManager: this.layerManager, } - this.rectToolManager = new RectToolManager(managers) + this.shapeToolManager = new ShapeToolManager(managers) this.aiSelectboxToolManager = new AISelectboxToolManager(managers) // 初始化所有事件 @@ -209,9 +209,9 @@ export class CanvasEventManager { } else if (currentTool === OperationType.SELECTBOX) { // 选择框模式 this.aiSelectboxToolManager.mouseDownEvent(opt); - } else if (currentTool === OperationType.RECTANGLE) { - // 矩形模式 - this.rectToolManager.mouseDownEvent(opt); + } else if (this.shapeToolManager.tools.includes(currentTool)) { + // 形状模式 + this.shapeToolManager.mouseDownEvent(opt); } else if (opt.e.altKey || opt.e.which === 2 || currentTool === OperationType.PAN) { this.canvas.isDragging = true; this.canvas.lastPosX = opt.e.clientX; @@ -237,9 +237,9 @@ export class CanvasEventManager { } else if (currentTool === OperationType.SELECTBOX) { // 选择框模式 this.aiSelectboxToolManager.mouseMoveEvent(opt); - } else if (currentTool === OperationType.RECTANGLE) { - // 矩形模式 - this.rectToolManager.mouseMoveEvent(opt); + } else if (this.shapeToolManager.tools.includes(currentTool)) { + // 形状模式 + this.shapeToolManager.mouseMoveEvent(opt); } else if (this.canvas.isDragging) { const vpt = this.canvas.viewportTransform; vpt[4] += opt.e.clientX - this.canvas.lastPosX; @@ -321,9 +321,9 @@ export class CanvasEventManager { } else if (currentTool === OperationType.SELECTBOX) { // 选择框模式 this.aiSelectboxToolManager.mouseUpEvent(opt); - } else if (currentTool === OperationType.RECTANGLE) { - // 矩形模式 - this.rectToolManager.mouseDownEvent(opt); + } else if (this.shapeToolManager.tools.includes(currentTool)) { + // 形状模式 + this.shapeToolManager.mouseDownEvent(opt); } else if (currentTool === OperationType.PAN) { // 平滑停止任何正在进行的惯性动画 @@ -386,9 +386,9 @@ export class CanvasEventManager { } else if (currentTool === OperationType.SELECTBOX) { // 选择框模式 this.aiSelectboxToolManager.mouseMoveEvent(opt); - } else if (currentTool === OperationType.RECTANGLE) { - // 矩形模式 - this.rectToolManager.mouseMoveEvent(opt); + } else if (this.shapeToolManager.tools.includes(currentTool)) { + // 形状模式 + this.shapeToolManager.mouseMoveEvent(opt); } else if (currentTool === OperationType.PAN) { // 检查是否是触摸事件 @@ -496,9 +496,9 @@ export class CanvasEventManager { } else if (currentTool === OperationType.SELECTBOX) { // 选择框模式 this.aiSelectboxToolManager.mouseUpEvent(opt); - } else if (currentTool === OperationType.RECTANGLE) { - // 矩形模式 - this.rectToolManager.mouseUpEvent(opt); + } else if (this.shapeToolManager.tools.includes(currentTool)) { + // 形状模式 + this.shapeToolManager.mouseUpEvent(opt); } else if (currentTool === OperationType.PAN) { // 重置触摸状态 @@ -668,9 +668,9 @@ export class CanvasEventManager { } else if (currentTool === OperationType.SELECTBOX) { // 选择框模式 this.aiSelectboxToolManager.mouseUpEvent(opt); - } else if (currentTool === OperationType.RECTANGLE) { - // 矩形模式 - this.rectToolManager.mouseUpEvent(opt); + } else if (this.shapeToolManager.tools.includes(currentTool)) { + // 形状模式 + this.shapeToolManager.mouseUpEvent(opt); } else if (this.canvas.isDragging) { // if (this.lastMousePositions.length > 1 && opt && opt.e) { // this.animationManager.applyInertiaEffect( @@ -1074,7 +1074,7 @@ export class CanvasEventManager { } dispose() { - this.rectToolManager?.dispose() + this.shapeToolManager?.dispose() this.aiSelectboxToolManager?.dispose() // 移除所有事件监听 this.canvas.off(); diff --git a/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts b/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts index eb347d3..6511043 100644 --- a/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts @@ -9,13 +9,14 @@ export class KeyEventManager { /** 处理键盘事件 */ _handleKeyDown: any handleKeyDown(event: any) { + const activeID = this.stateManager.layerManager.activeID.value const ctrl = event.ctrlKey ? 'ctrl-' : ""; const shift = event.shiftKey ? 'shift-' : ""; const key = event.key; const reg = new RegExp(`^${ctrl}${shift}${key}$`, 'i') const list = [ - // { key: "ctrl-c", handler: () => this.handleCopy(event) }, - // { key: "delete", handler: () => this.handleDelete(event) }, + { key: "ctrl-c", handler: () => this.stateManager.layerManager.copyLayerById(activeID) }, + { key: "delete", handler: () => this.stateManager.layerManager.deleteLayerById(activeID) }, { key: "ctrl-z", handler: () => this.stateManager.undoState() }, { key: "ctrl-shift-z", handler: () => this.stateManager.redoState() }, ] diff --git a/src/components/Canvas/DepthCanvas/tools/canvasMethod.js b/src/components/Canvas/DepthCanvas/tools/canvasMethod.js index acd568a..a506e8d 100644 --- a/src/components/Canvas/DepthCanvas/tools/canvasMethod.js +++ b/src/components/Canvas/DepthCanvas/tools/canvasMethod.js @@ -39,4 +39,57 @@ export async function getObjectsBoundingBox(objects = []) { width: box2.x - box1.x, height: box2.y - box1.y, } -} \ No newline at end of file +} + +/** 获取五角星数组 */ +export function getStarArr(width = 0, height = 0) { + const arr = [ + { x: 0, y: -0.5 }, // 顶点0 (上) + { x: 0.15, y: -0.15 }, // 顶点1 (内) + { x: 0.50, y: -0.15 }, // 顶点2 (右上外) + { x: 0.20, y: 0.10 }, // 顶点3 (内) + { x: 0.30, y: 0.50 }, // 顶点4 (右下外) + { x: 0.0, y: 0.25 }, // 顶点5 (内) + { x: -0.30, y: 0.50 }, // 顶点6 (左下外) + { x: -0.20, y: 0.10 }, // 顶点7 (内) + { x: -0.50, y: -0.15 }, // 顶点8 (左上外) + { x: -0.15, y: -0.15 } // 顶点9 (内) + ] + return arr.map(item => ({ + x: item.x * width, + y: item.y * height, + })) +} +/** 获取箭头路径 */ +export function getArrowPath(width = 0, height = 0) { + const arr = [ + ["M", 0, height / 2], + ["L", width, height / 2], + ["M", width - 8, 0], + ["L", width, height / 2], + ["L", width - 8, height], + ] + var path = "" + arr.forEach(item => { + path += item.join(" ") + " " + }) + return path +} + +/** 计算两点之间的距离 */ +export function distance(x1, y1, x2, y2) { + const dx = x2 - x1; + const dy = y2 - y1; + return Math.sqrt(dx * dx + dy * dy); +} +/** 计算两点之间的角度(角度) */ +export function angleBetweenPointsDegrees(x1, y1, x2, y2) { + const dx = x2 - x1; + const dy = y2 - y1; + + // 计算弧度并转换为角度 + const rad = Math.atan2(dy, dx); + const deg = rad * 180 / Math.PI; + + return deg; +} diff --git a/src/components/Canvas/DepthCanvas/tools/layerHelper.js b/src/components/Canvas/DepthCanvas/tools/layerHelper.js index cbead70..0a6fdd8 100644 --- a/src/components/Canvas/DepthCanvas/tools/layerHelper.js +++ b/src/components/Canvas/DepthCanvas/tools/layerHelper.js @@ -31,7 +31,16 @@ export const OperationType = { ERASER: "eraser", // 橡皮擦模式 IMAGE: "image",// 图片工具模式 SELECTBOX: "selectbox",// 选择框工具模式 + RECTANGLE: "rectangle",// 矩形工具模式 + LINE: "line",// 直线工具模式 + ARROW: "arrow",// 箭头工具模式 + ELLIPSE: "ellipse",// 椭圆工具模式 + TRIANGLE: "triangle",// 三角形工具模式 + STAR: "star",// 五角星工具模式 + + + TEXT: "text",// 文字工具模式 UNDO: "undo",// 撤销工具模式 REDO: "redo",// 重做工具模式 From efe71347442f2eb6da380a67a6789e468df2eaab Mon Sep 17 00:00:00 2001 From: lzp Date: Tue, 17 Mar 2026 17:31:51 +0800 Subject: [PATCH 4/5] 11 --- .../Canvas/DepthCanvas/manager/ShapeToolManager.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts index 14f749f..018d298 100644 --- a/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts @@ -239,7 +239,14 @@ export class ShapeToolManager { this.demoObject = arrow } upArrow(object) { - this.layerManager.createArrowLayer(object, true) + if (object.originY !== "center") { + this.layerManager.createArrowLayer({ + left: this.startX, + top: this.startY, + }, true) + } else { + this.layerManager.createArrowLayer(object, true) + } } From ce65e0e5a34ef7cddf47c8b12ca300b168578d06 Mon Sep 17 00:00:00 2001 From: lzp Date: Wed, 18 Mar 2026 10:52:06 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E7=94=BB=E5=B8=83?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/dc/dui.svg | 3 + .../components/depth-header-tools.vue | 227 ++++++++++++------ .../Canvas/DepthCanvas/tools/layerHelper.js | 5 - 3 files changed, 159 insertions(+), 76 deletions(-) create mode 100644 src/assets/icons/dc/dui.svg diff --git a/src/assets/icons/dc/dui.svg b/src/assets/icons/dc/dui.svg new file mode 100644 index 0000000..0b4d3f1 --- /dev/null +++ b/src/assets/icons/dc/dui.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue b/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue index 3d1e402..42c82f9 100644 --- a/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue +++ b/src/components/Canvas/DepthCanvas/components/depth-header-tools.vue @@ -2,14 +2,31 @@