From 066cd466143fa56eff56de0baeece797a9b2f303 Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Sat, 11 May 2024 18:22:01 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(zhst/meta-cropperImage):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meta/src/cropperImage/CropperImage.tsx | 163 ++++++++++++++---- packages/meta/src/cropperImage/demo/basic.tsx | 12 +- 2 files changed, 139 insertions(+), 36 deletions(-) diff --git a/packages/meta/src/cropperImage/CropperImage.tsx b/packages/meta/src/cropperImage/CropperImage.tsx index c83342f..ea8acba 100644 --- a/packages/meta/src/cropperImage/CropperImage.tsx +++ b/packages/meta/src/cropperImage/CropperImage.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, forwardRef, useImperativeHandle, useContext, useState, ReactNode } from 'react' +import React, { useRef, useEffect, forwardRef, useImperativeHandle, useContext, useState, ReactNode, useMemo } from 'react' import classNames from 'classnames' import { fabric } from 'fabric' import { addEventListenerWrapper, getTransforms, pick } from '@zhst/func' @@ -12,7 +12,8 @@ import Align from 'rc-align'; import { getTransformRect } from '../BigImagePreview/bigImagePreviewHelper'; interface RectPro extends Rect { - imageRect: string + imageRect?: string; + type?: 'line' | 'rect'; // line:线,rect:矩形 } export interface CropperImageProps { @@ -20,7 +21,7 @@ export interface CropperImageProps { url?: string; width?: number; height?: number; - odList?: Rect[] // od框 + odList?: RectPro[] // od框 lineConfig?: fabric.Line; // 线条配置 editAble?: boolean; // 是否可编辑 selectedItem?: RectPro @@ -93,7 +94,7 @@ const CropperImage = forwardRef((props, } = props; const { getPrefixCls } = useContext(ConfigContext); const componentName = getPrefixCls('cropper-view', customizePrefixCls); - const [isMove, setIsMove] = useState(false) // 矩形是否在移动 + const [isDrawing, setIsDrawing] = useState(false) // 矩形是否在移动 const canvasRef = useRef(null); const currentShapeRef = useRef(null) @@ -103,6 +104,7 @@ const CropperImage = forwardRef((props, // 自定义弹框 const alginContainerRef: any = useRef(null); const alignRef: any = useRef(null); + const imageRectRef: any = useRef(null) // 初始化 - 图片 useEffect(() => { @@ -114,23 +116,37 @@ const CropperImage = forwardRef((props, }); // 监听形状选择事件 - addEventListenerWrapper(imageRef.current, EVENT_SHAPE_SELECT, (e: { detail: any; }) => { + imageRectRef.current = addEventListenerWrapper(imageRef.current, EVENT_SHAPE_SELECT, (e: { detail: any; }) => { // 选中的od const id = e.detail; if (id) { - const selectRectData = odList!.filter(_od => _od.id === id)?.[0] + // 获取选中的形状 + // const shapes = imgIns.getSelectShape(); + + const selectRectData = odList!.filter(_od => _od.id === id)?.[0] || {} const _data = percentToLength(selectRectData, viewerRef.current.canvas) const imageRect = getImageDataByPosition( { x: _data.x, y: _data.y, w: _data.w, h: _data.h }, { canvas: viewerRef.current.canvas } ) + // TODO: 换算成屏幕坐标 + // const axisRect = imgIns.imgRectAxisToCanvasAxisRect(selectShape); + // const rect = { + // x: axisRect.x2 > axisRect.x ? axisRect.x : axisRect.x2, + // y: axisRect.y2 > axisRect.y ? axisRect.y : axisRect.y2, + // w: Math.abs(axisRect.x2 - axisRect.x), + // h: Math.abs(axisRect.y2 - axisRect.y), + // }; + // getTransformRect(imageRectRef.current.image, ) id && onShapeSelected?.(id, { ..._data, imageRect, originData: selectRectData }) } }) return () => { + // 再次加载,销毁原来的实例 viewerRef?.current?.destroy?.(); viewerRef.current = null; + imageRectRef.current?.remove(); viewerRef.current?.clearShape?.(); } }, [url]) @@ -149,26 +165,22 @@ const CropperImage = forwardRef((props, }) return } else { + // 编辑模式 _viewer?.clearShape?.(); - const { targetTransform = {} } = _viewer if (type === 'rect') { + // 编辑模式 - 矩形绘制 currentShapeRef.current = initRect() - // 矩形 - 开始绘制实践 - cropStartRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_START, () => { - setIsMove(true) - onCropStart?.() - }); - // 矩形 - 结束绘制实践 - cropEndRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_END, (event: { detail: any; }) => { - const data = event.detail; - const targetPosition = { x: data.left, y: data.top, w: data.width, h: data.height } - const imageRect = getImageDataByPosition(targetPosition, { canvas: viewerRef.current.canvas }) - const targetData = getTransformRect(_viewer.image, targetTransform, targetPosition) - onCropEnd?.({ ...data , imageRect, targetTransform, targetData }) - setIsMove(false) - }) + // cropEndRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_END, (event: { detail: any; }) => { + // const data = event.detail; + // const targetPosition = { x: data.left, y: data.top, w: data.width, h: data.height } + // const imageRect = getImageDataByPosition(targetPosition, { canvas: viewerRef.current.canvas }) + // const targetData = getTransformRect(_viewer.image, targetTransform, targetPosition) + // onCropEnd?.({ ...data , imageRect, targetTransform, targetData }) + // setIsMove(false) + // }) } else { + // 编辑模式 - 线绘制 currentShapeRef.current = initLine() } } @@ -179,17 +191,110 @@ const CropperImage = forwardRef((props, currentShapeRef.current?.destroy?.() currentShapeRef.current?.dispose?.() } - },[type, editAble, odList]) + },[type, editAble]) // 初始化 - 矩形圈选工具 const initRect = () => { - const currentCropper = new Cropper(imageRef.current, { - showMask: true, - scaleAble: false, - viewer: viewerRef.current, + const viewer = viewerRef?.current || {} + const { containerData = {}, targetTransform = {} } = viewer + let currentFabric: CanvasPro = new fabric.Canvas( + canvasRef.current, + { + backgroundColor: 'transparent', + width: containerData.width, + height: containerData.height, + selection: false, + } + ) + let rect: fabric.Rect + let origX: number, origY: number + + function addOrReplaceRect(newRect) { + // 移除画布上所有的矩形对象 + const objects = currentFabric.getObjects(); + for (let i = objects.length - 1; i >= 0; i--) { + if (objects[i].type === 'rect') { + currentFabric.remove(objects[i]); + } + } + // 添加新的矩形对象 + currentFabric.add(newRect); + } + + const checkPointInRect = (o: fabric.IEvent) => { + var pointer = currentFabric.getPointer(o.e); + var point = new fabric.Point(pointer.x, pointer.y); + let inRect = false + currentFabric.forEachObject(function(obj) { + if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) { + inRect = true + } else { + inRect = false + } + }); + return inRect + } + + // 鼠标按下事件 + currentFabric.on('mouse:down', function(o) { + currentFabric.startDraw = true + var pointer = currentFabric.getPointer(o.e); + origX = pointer.x; + origY = pointer.y; + + // 创建一个矩形对象 + rect = new fabric.Rect({ + left: origX, + top: origY, + originX: 'left', + originY: 'top', + borderColor: '#09f', + cornerColor: '#09f', + cornerSize: 6, + width: pointer.x - origX, + height: pointer.y - origY, + angle: 0, + fill: 'transparent', + hasControls: true, + hasBorders: true, + lockRotation: true, // 锁定旋转 + hasRotatingPoint: false // 隐藏旋转控制点 + }); + + // 判断存在实例,并且鼠标点击在实例上 + if (checkPointInRect(o) && rect) { + } else { + addOrReplaceRect(rect) + // 监听移动 + rect.on('moving', o => console.log('o', o)) + rect.on('resizing', o => console.log('o', o)) + // 监听缩放 + rect.on('scaling', o => console.log('o', o)) + } + currentFabric.setActiveObject(currentFabric.item(0)); + }); + // 鼠标移动事件 + currentFabric.on('mouse:move', function(o) { + if (!currentFabric.startDraw) return; + var pointer = currentFabric.getPointer(o.e); + + if(origX > pointer.x){ + rect.set({ left: Math.abs(pointer.x) }); + } + + if(origY > pointer.y){ + rect.set({ top: Math.abs(pointer.y) }); + } + rect.set({ width: Math.abs(origX - pointer.x) }); + rect.set({ height: Math.abs(origY - pointer.y) }); + currentFabric.renderAll(); + }); + // 鼠标松开事件 + currentFabric.on('mouse:up', function(o) { + currentFabric.startDraw = false }); - return currentCropper + return currentFabric } // 初始化线条 @@ -316,7 +421,7 @@ const CropperImage = forwardRef((props, ref={imageRef} className={classNames(`${componentName}_img`)} /> - {showToast && selectedItem && !isMove && (<> + {showToast && selectedItem && !isDrawing && (<>
((props, })} )} - + {children}
); diff --git a/packages/meta/src/cropperImage/demo/basic.tsx b/packages/meta/src/cropperImage/demo/basic.tsx index 25ba03f..292958e 100644 --- a/packages/meta/src/cropperImage/demo/basic.tsx +++ b/packages/meta/src/cropperImage/demo/basic.tsx @@ -30,11 +30,10 @@ export default () => { odList={[ { "id": "123", - "x": 0.5519352, - "y": 0.2965385, - "w": 0.05185461, - "h": 0.24698898, - selectAble: false, + h: 0.30666666666666664, + w: 0.27170650730411683, + x: 0.19064741035856572, + y: 0.09703124999999999, }, { "id": "456", @@ -53,9 +52,8 @@ export default () => { }} onCropStart={() => console.log('矩形开始绘制')} onCropEnd={(data) => { - console.log('矩形结束绘制', data) setSelectedItem({ x: data.left, y: data.top, h: data.height, w: data.width }) - setImgUrl(data.imageRect as string) + setImgUrl(data?.imageRect as string) }} selectedItem={selectedItem} showToast={editAble} From 37c34eb78507d49605371dd9b842e17c2049e1b7 Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Tue, 14 May 2024 14:01:51 +0800 Subject: [PATCH 2/5] Merge branch 'develop' into feat/cropper-upgrade # Conflicts: # packages/meta/src/cropperImage/CropperImage.tsx # packages/meta/src/cropperImage/demo/basic.tsx --- .npmrc | 2 +- .../src/algorithmConfig/demo/basic.tsx | 5 ++- .../meta/src/cropperImage/CropperImage.tsx | 34 ------------------- .../src/cropperImage/cropperImagehelper.ts | 27 ++++++++++++++- packages/meta/src/cropperImage/demo/basic.tsx | 13 ++++--- packages/meta/src/cropperImage/index.less | 11 ++++++ packages/meta/src/relatedImage/index.md | 7 +++- 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/.npmrc b/.npmrc index c6057e7..e29504a 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,4 @@ registry="https://registry.npmmirror.com" -@zhst:registry="http://10.0.0.77:4874" +@zhst:registry="http://10.0.0.77:4874/:autoToken=VbcnvB0eKGAqsT5ZNbfKiw==" strict-peer-dependencies=false ignore-workspace-root-check=true diff --git a/packages/material/src/algorithmConfig/demo/basic.tsx b/packages/material/src/algorithmConfig/demo/basic.tsx index 6f1ace9..ac034df 100644 --- a/packages/material/src/algorithmConfig/demo/basic.tsx +++ b/packages/material/src/algorithmConfig/demo/basic.tsx @@ -2,6 +2,7 @@ import React, { useRef, useState } from 'react'; import { AlgorithmConfig, AlgorithmConfigRef } from '@zhst/material'; import type { AlgorithmConfigProps } from '@zhst/material'; import { Button, Space, Switch } from 'antd'; +import { CropperImageRefProps } from '@zhst/meta'; const algorithmTableDataSource: any = [] @@ -47,10 +48,11 @@ const demo = () => { const [tableType, setTableType] = useState('multiple') const [editAble, setEditAble] = useState(false) const algorithmConfigRef = useRef(null) + const cropperImageRef = useRef(null) // 绘画事件 const handleDraw = (id: any, info: any) => { - console.log('箭头圈选事件', id, info) + console.log('cropperImageRef', cropperImageRef) setEditAble(true) setCropType('line') } @@ -113,6 +115,7 @@ const demo = () => { cropperImageProps={{ type: cropType, editAble, + ref: cropperImageRef, url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' }} /> diff --git a/packages/meta/src/cropperImage/CropperImage.tsx b/packages/meta/src/cropperImage/CropperImage.tsx index 1733e97..ca8ae23 100644 --- a/packages/meta/src/cropperImage/CropperImage.tsx +++ b/packages/meta/src/cropperImage/CropperImage.tsx @@ -9,7 +9,6 @@ import { Cropper, EVENT_CROP_END, EVENT_CROP_START, EVENT_SHAPE_SELECT } from '. import { Rect } from '../ImageEditor/viewer/shape'; import { checkPointInRect, drawArrowLine, getImageDataByPosition, percentToLength } from './cropperImagehelper'; import Align from 'rc-align'; -import { getTransformRect } from '../BigImagePreview/bigImagePreviewHelper'; interface RectPro extends Rect { imageRect?: string; @@ -119,30 +118,6 @@ const CropperImage = forwardRef((props, // 监听形状选择事件 imageRectRef.current = addEventListenerWrapper(imageRef.current, EVENT_SHAPE_SELECT, (e: { detail: any; }) => { // 选中的od -<<<<<<< HEAD - const id = e.detail; - if (id) { - // 获取选中的形状 - // const shapes = imgIns.getSelectShape(); - - const selectRectData = odList!.filter(_od => _od.id === id)?.[0] || {} - const _data = percentToLength(selectRectData, viewerRef.current.canvas) - const imageRect = getImageDataByPosition( - { x: _data.x, y: _data.y, w: _data.w, h: _data.h }, - { canvas: viewerRef.current.canvas } - ) - // TODO: 换算成屏幕坐标 - // const axisRect = imgIns.imgRectAxisToCanvasAxisRect(selectShape); - // const rect = { - // x: axisRect.x2 > axisRect.x ? axisRect.x : axisRect.x2, - // y: axisRect.y2 > axisRect.y ? axisRect.y : axisRect.y2, - // w: Math.abs(axisRect.x2 - axisRect.x), - // h: Math.abs(axisRect.y2 - axisRect.y), - // }; - // getTransformRect(imageRectRef.current.image, ) - id && onShapeSelected?.(id, { ..._data, imageRect, originData: selectRectData }) - } -======= const id = e.detail; if (id) { const selectRectData = odList!.filter(_od => _od.id === id)?.[0] @@ -153,7 +128,6 @@ const CropperImage = forwardRef((props, ) id && onShapeSelected?.(id, { ..._data, imageRect, originData: selectRectData }) } ->>>>>>> develop }) return () => { @@ -180,19 +154,11 @@ const CropperImage = forwardRef((props, }) return } else { -<<<<<<< HEAD // 编辑模式 _viewer?.clearShape?.(); if (type === 'rect') { // 编辑模式 - 矩形绘制 -======= - // 编辑态 - const { targetTransform = {} } = _viewer - - if (type === 'rect') { - _viewer.clearShape() ->>>>>>> develop currentShapeRef.current = initRect() // cropEndRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_END, (event: { detail: any; }) => { // const data = event.detail; diff --git a/packages/meta/src/cropperImage/cropperImagehelper.ts b/packages/meta/src/cropperImage/cropperImagehelper.ts index 1d5f65e..4968675 100644 --- a/packages/meta/src/cropperImage/cropperImagehelper.ts +++ b/packages/meta/src/cropperImage/cropperImagehelper.ts @@ -21,7 +21,32 @@ export const getImage = (propImage: HTMLImageElement | string) => { }); } -// 检查是否在规定区域内 +/** + * 检查鼠标是否在矩形中、矩形编辑器上 + * @param o 鼠标对象实例 + * @param _fabricCanvas fabric实例 + * @returns Boolean + */ +export const checkMouseInRect = (o: fabric.IEvent, _fabricCanvas: fabric.Canvas) => { + var pointer = _fabricCanvas.getPointer(o.e); + var point = new fabric.Point(pointer.x, pointer.y); + let inRect = false + _fabricCanvas.forEachObject(function(obj) { + if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) { + inRect = true + } else { + inRect = false + } + }); + return inRect +} + +// 检查是否在可绘制区域内 +/** + * @param point 需要检查的点的坐标 + * @param rect 原始画布宽高:w、h;缩放比例:scale;整体偏移的坐标:translateX,translateY + * @returns boolean + */ export const checkPointInRect = (point: { x: number; y: number;}, rect: { w: number; h: number, translateX?: number; translateY?: number, scale: number }) => { const { w, h, translateX = 0, translateY = 0, scale = 1 } = rect; const limitStartX = translateX diff --git a/packages/meta/src/cropperImage/demo/basic.tsx b/packages/meta/src/cropperImage/demo/basic.tsx index 45fed68..74d40c1 100644 --- a/packages/meta/src/cropperImage/demo/basic.tsx +++ b/packages/meta/src/cropperImage/demo/basic.tsx @@ -36,10 +36,10 @@ export default () => { setOdList([ { "id": "456", - "x": 0.58543766, - "y": 0.3203356, - "w": 0.052037954, - "h": 0.2664015 + x: 0.27903386454183265, + y: 0.16203125, + w: 0.42065405046480747, + h: 0.3783333333333333, } ]) }}>修改坐标 @@ -69,12 +69,15 @@ export default () => { }} onCropStart={() => console.log('矩形开始绘制')} onCropEnd={(data) => { + console.log('data', data) setSelectedItem({ x: data.left, y: data.top, h: data.height, w: data.width }) setImgUrl(data?.imageRect as string) }} selectedItem={selectedItem} showToast={editAble} - customToast={() =>
多功能框
} + customToast={() => ( +
自定义框
+ )} /> diff --git a/packages/meta/src/cropperImage/index.less b/packages/meta/src/cropperImage/index.less index 8162f42..c1b8ac2 100644 --- a/packages/meta/src/cropperImage/index.less +++ b/packages/meta/src/cropperImage/index.less @@ -12,6 +12,17 @@ height: 100%; } + &_toast { + position: absolute; + left: 0; + top: 0; + width: 150px; + height: 100px; + font-size: 16px; + z-index: 99; + cursor: pointer; + } + .canvas-container { position: absolute; left: 0; diff --git a/packages/meta/src/relatedImage/index.md b/packages/meta/src/relatedImage/index.md index 11db16c..f2fc866 100644 --- a/packages/meta/src/relatedImage/index.md +++ b/packages/meta/src/relatedImage/index.md @@ -6,7 +6,9 @@ toc: content title: RelatedImage 模型碰撞轮播图 --- -# RelatedImage 模型碰撞轮播图 +## RelatedImage 模型碰撞轮播图 + +## 示例 基本 @@ -23,3 +25,6 @@ title: RelatedImage 模型碰撞轮播图 | onItemSelected | 图片选中 | string | - | | | prefixCls | 图片 | string | - | | +## 组件设计思路 + +功能灵感是出自蜂鸟3.0的人脸碰撞模型的 demo。 From 8dc81e1743311258b7023982fd59de7c8b2dec05 Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Thu, 16 May 2024 18:02:10 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(zhst/meta=E3=80=81zhst/biz=E3=80=81zhst?= =?UTF-8?q?/material):=20=E4=BF=AE=E6=94=B9=E5=9B=BE=E7=89=87=E6=A0=87?= =?UTF-8?q?=E6=B3=A8=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/biz/CHANGELOG.md | 11 + packages/biz/package.json | 2 +- packages/material/CHANGELOG.md | 12 + packages/material/package.json | 2 +- packages/meta/CHANGELOG.md | 11 + packages/meta/es/ImageEditor/viewer/render.js | 3 + packages/meta/package.json | 2 +- .../meta/src/BigImagePreview/demo/base.tsx | 4 +- .../meta/src/ImageEditor/viewer/render.ts | 4 + .../meta/src/cropperImage/CropperImage.tsx | 629 +++++++++--------- .../src/cropperImage/cropperImagehelper.ts | 231 +++++-- packages/meta/src/cropperImage/demo/basic.tsx | 20 +- packages/meta/src/cropperImage/index.tsx | 1 + packages/meta/src/cropperImage/type.ts | 34 + tsconfig.json | 5 +- 15 files changed, 611 insertions(+), 360 deletions(-) create mode 100644 packages/meta/src/cropperImage/type.ts diff --git a/packages/biz/CHANGELOG.md b/packages/biz/CHANGELOG.md index dda3b31..a499c5d 100644 --- a/packages/biz/CHANGELOG.md +++ b/packages/biz/CHANGELOG.md @@ -1,5 +1,16 @@ # @zhst/biz +## 0.22.0 + +### Minor Changes + +- fix: zhst/meta、zhst/biz、zhst/material-修改图片标注组件 + +### Patch Changes + +- Updated dependencies + - @zhst/meta@0.21.0 + ## 0.21.5 ### Patch Changes diff --git a/packages/biz/package.json b/packages/biz/package.json index 47f25ff..5e3485b 100644 --- a/packages/biz/package.json +++ b/packages/biz/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/biz", - "version": "0.21.5", + "version": "0.22.0", "description": "业务库", "keywords": [ "business", diff --git a/packages/material/CHANGELOG.md b/packages/material/CHANGELOG.md index f657bba..b7d4782 100644 --- a/packages/material/CHANGELOG.md +++ b/packages/material/CHANGELOG.md @@ -1,5 +1,17 @@ # @zhst/material +## 0.18.0 + +### Minor Changes + +- fix: zhst/meta、zhst/biz、zhst/material-修改图片标注组件 + +### Patch Changes + +- Updated dependencies + - @zhst/meta@0.21.0 + - @zhst/biz@0.22.0 + ## 0.17.4 ### Patch Changes diff --git a/packages/material/package.json b/packages/material/package.json index 92bd58d..70cd047 100644 --- a/packages/material/package.json +++ b/packages/material/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/material", - "version": "0.17.4", + "version": "0.18.0", "description": "物料库", "keywords": [ "business", diff --git a/packages/meta/CHANGELOG.md b/packages/meta/CHANGELOG.md index 2e371f6..c61c3cf 100644 --- a/packages/meta/CHANGELOG.md +++ b/packages/meta/CHANGELOG.md @@ -1,5 +1,16 @@ # @zhst/utils +## 0.21.0 + +### Minor Changes + +- fix: zhst/meta、zhst/biz、zhst/material-修改图片标注组件 + +### Patch Changes + +- Updated dependencies + - @zhst/meta@0.21.0 + ## 0.20.3 ### Patch Changes diff --git a/packages/meta/es/ImageEditor/viewer/render.js b/packages/meta/es/ImageEditor/viewer/render.js index aac7cbd..7ac936f 100644 --- a/packages/meta/es/ImageEditor/viewer/render.js +++ b/packages/meta/es/ImageEditor/viewer/render.js @@ -176,6 +176,9 @@ export default { h: this.image.height }; }, + getImage: function getImage() { + return this; + }, calcFitScreen: function calcFitScreen() { if (!this.image) return; var w = this.containerData.width; diff --git a/packages/meta/package.json b/packages/meta/package.json index 4d8dba5..6a60c95 100644 --- a/packages/meta/package.json +++ b/packages/meta/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/meta", - "version": "0.20.3", + "version": "0.21.0", "description": "原子组件", "keywords": [ "meta", diff --git a/packages/meta/src/BigImagePreview/demo/base.tsx b/packages/meta/src/BigImagePreview/demo/base.tsx index 75197ad..db158c7 100644 --- a/packages/meta/src/BigImagePreview/demo/base.tsx +++ b/packages/meta/src/BigImagePreview/demo/base.tsx @@ -26,14 +26,16 @@ const props = { objects: [ { "bboxRatio": { + "id": "123", "x": 0.5519352, "y": 0.2965385, "w": 0.05185461, - "h": 0.24698898 + "h": 0.24698898, }, }, { "bboxRatio": { + "id": "456", "x": 0.58543766, "y": 0.3203356, "w": 0.052037954, diff --git a/packages/meta/src/ImageEditor/viewer/render.ts b/packages/meta/src/ImageEditor/viewer/render.ts index 7a65f0b..4f06555 100644 --- a/packages/meta/src/ImageEditor/viewer/render.ts +++ b/packages/meta/src/ImageEditor/viewer/render.ts @@ -153,6 +153,10 @@ export default { return { w: this.image.width, h: this.image.height }; }, + getImage() { + return this + }, + calcFitScreen() { if (!this.image) return; const w = this.containerData.width; diff --git a/packages/meta/src/cropperImage/CropperImage.tsx b/packages/meta/src/cropperImage/CropperImage.tsx index ca8ae23..2b75756 100644 --- a/packages/meta/src/cropperImage/CropperImage.tsx +++ b/packages/meta/src/cropperImage/CropperImage.tsx @@ -1,19 +1,25 @@ -import React, { useRef, useEffect, forwardRef, useImperativeHandle, useContext, useState, ReactNode, useMemo } from 'react' +import React, { useRef, useEffect, forwardRef, useImperativeHandle, useContext, useState, ReactNode } from 'react' import classNames from 'classnames' import { fabric } from 'fabric' -import { addEventListenerWrapper, getTransforms, pick } from '@zhst/func' +import { addEventListenerWrapper, pick } from '@zhst/func' +import { useDebounceFn } from '@zhst/hooks' import Viewer from '../ImageEditor/viewer'; import './index.less' import { ConfigContext } from '../config-provider'; -import { Cropper, EVENT_CROP_END, EVENT_CROP_START, EVENT_SHAPE_SELECT } from '../ImageEditor'; +import { EVENT_VIEWER_READY } from '../ImageEditor'; import { Rect } from '../ImageEditor/viewer/shape'; -import { checkPointInRect, drawArrowLine, getImageDataByPosition, percentToLength } from './cropperImagehelper'; -import Align from 'rc-align'; - -interface RectPro extends Rect { - imageRect?: string; - type?: 'line' | 'rect'; // line:线,rect:矩形 -} +import { + checkPointInRect, + drawArrowLine, + getImageDataByPosition, + checkMouseInRect, + createFabricShape, + originPercentToShapeLength, + changeColor, + shapeLengthToPercent +} from './cropperImagehelper'; +import { getTransformRect } from '../BigImagePreview/bigImagePreviewHelper'; +import { IArrowLinePosition, IRectData, ITransform, RectPro } from './type' export interface CropperImageProps { prefixCls?: string; @@ -30,27 +36,22 @@ export interface CropperImageProps { // 是否展示框选拓展框 showToast?: boolean; // 自定义拓展框 - customToast?: (data?: any) => React.JSX.Element + customToast?: (data: any) => React.JSX.Element; + toastStyle?: CSSStyleSheet type?: 'line' | 'rect'; // 编辑类型 onMouseDown?: (data: { x: number; y: number }) => void; - onMouseUp?: (data: { - startX: number; - startY: number; - endX: number; - endY: number; - imageDom?: HTMLImageElement, - targetTransform?: { - translateX: number - translateY: number - scale: number - rotate: number - } - }) => void; + onMouseUp?: (e?: fabric.IEvent) => void; + onMouseMove?: (e?: fabric.IEvent) => void; onShapeSelected?: (id: string, shapeData?: RectPro & { originData: Rect }) => void onCropStart?: () => void - onCropEnd?: (data: RectPro) => void + onCropEnd?: (data: Partial & Partial & { + type: RectPro['type'], + rectImageBase64?: string + targetTransform?: ITransform + targetData?: Rect + }) => void children?: ReactNode } @@ -75,6 +76,7 @@ const CropperImage = forwardRef((props, selectedItem, onMouseDown, onMouseUp, + onMouseMove, onCropStart, onCropEnd, editAble, @@ -82,7 +84,8 @@ const CropperImage = forwardRef((props, selectAble = true, showToast = false, customToast = () =>
, - type = 'ract', + toastStyle = {}, + type = 'line', scaleAble = false, lineConfig = { stroke: '#09f', @@ -96,202 +99,68 @@ const CropperImage = forwardRef((props, const [isDrawing, setIsDrawing] = useState(false) // 矩形是否在移动 const canvasRef = useRef(null); - const currentShapeRef = useRef(null) const imageRef = useRef(null) const viewerRef = useRef(null) const currentFabricRef = useRef(null) - - // 自定义弹框 - const alginContainerRef: any = useRef(null); - const alignRef: any = useRef(null); - const imageRectRef: any = useRef(null) + const currentShapeRef = useRef(null) + const [isImgReady, setIsImgReady] = useState(false); // 初始化 - 图片 useEffect(() => { + const handleReady = addEventListenerWrapper(imageRef.current, EVENT_VIEWER_READY, () => { + setIsImgReady(true) + }); + viewerRef.current = new Viewer(imageRef.current!!, { image: url, scaleAble, selectAble, + height: 600, + fitScaleAsMinScale: true, dragAble: false, }); - // 监听形状选择事件 - imageRectRef.current = addEventListenerWrapper(imageRef.current, EVENT_SHAPE_SELECT, (e: { detail: any; }) => { - // 选中的od - const id = e.detail; - if (id) { - const selectRectData = odList!.filter(_od => _od.id === id)?.[0] - const _data = percentToLength(selectRectData, viewerRef.current.canvas) - const imageRect = getImageDataByPosition( - { x: _data.x, y: _data.y, w: _data.w, h: _data.h }, - { canvas: viewerRef.current.canvas } - ) - id && onShapeSelected?.(id, { ..._data, imageRect, originData: selectRectData }) - } - }) - return () => { // 再次加载,销毁原来的实例 viewerRef?.current?.destroy?.(); viewerRef.current = null; - imageRectRef.current?.remove(); viewerRef.current?.clearShape?.(); + handleReady.remove?.() } }, [url]) - const cropStartRef = useRef(null) - const cropEndRef = useRef(null) + /** + * 监听图形选中事件 + * @param e + */ + const handleSelected = (e: fabric.IEvent) => { + const _viewer = viewerRef?.current || {} + const { containerData = {}, targetTransform = {} } = _viewer + changeColor(e.selected!, 'rgba(255, 0, 0, 1)') + const selectedItem = e.selected?.[0] || 0 + // @ts-ignore + const _originData = selectedItem?.originData || selectedItem + const _data = shapeLengthToPercent(_originData, { + sourceImageWidth: containerData.width, + sourceImageHeight: containerData.height, + targetTransform + }) + let rectImageBase64 = '' + + if (_data.w && _data.h) { + rectImageBase64 = getImageDataByPosition( + _originData, + { canvas: viewerRef.current.canvas } + ) + } + onShapeSelected?.(_originData?.id, { ..._data, imageRect: rectImageBase64, originData: _originData }) + } // 初始化 - 编辑器 useEffect(() => { const _viewer = viewerRef?.current || {} - // 判断是否可编辑 - 非编辑态 - if (!editAble) { - _viewer.clearShape() - // 判定是否存在od框 - odList && odList.forEach(_od => { - _viewer?.addShape?.(_od); - }) - return - } else { - // 编辑模式 - _viewer?.clearShape?.(); - - if (type === 'rect') { - // 编辑模式 - 矩形绘制 - currentShapeRef.current = initRect() - // cropEndRef.current = addEventListenerWrapper(imageRef.current, EVENT_CROP_END, (event: { detail: any; }) => { - // const data = event.detail; - // const targetPosition = { x: data.left, y: data.top, w: data.width, h: data.height } - // const imageRect = getImageDataByPosition(targetPosition, { canvas: viewerRef.current.canvas }) - // const targetData = getTransformRect(_viewer.image, targetTransform, targetPosition) - // onCropEnd?.({ ...data , imageRect, targetTransform, targetData }) - // setIsMove(false) - // }) - } else { - // 编辑模式 - 线绘制 - currentShapeRef.current = initLine() - } - } - - return () => { - cropStartRef.current?.remove?.() - cropEndRef.current?.remove?.() - currentShapeRef.current?.destroy?.() - currentShapeRef.current?.dispose?.() - } - },[type, editAble]) - - // 初始化 - 矩形圈选工具 - const initRect = () => { - const viewer = viewerRef?.current || {} - const { containerData = {}, targetTransform = {} } = viewer - let currentFabric: CanvasPro = new fabric.Canvas( - canvasRef.current, - { - backgroundColor: 'transparent', - width: containerData.width, - height: containerData.height, - selection: false, - } - ) - let rect: fabric.Rect - let origX: number, origY: number - - function addOrReplaceRect(newRect) { - // 移除画布上所有的矩形对象 - const objects = currentFabric.getObjects(); - for (let i = objects.length - 1; i >= 0; i--) { - if (objects[i].type === 'rect') { - currentFabric.remove(objects[i]); - } - } - // 添加新的矩形对象 - currentFabric.add(newRect); - } - - const checkPointInRect = (o: fabric.IEvent) => { - var pointer = currentFabric.getPointer(o.e); - var point = new fabric.Point(pointer.x, pointer.y); - let inRect = false - currentFabric.forEachObject(function(obj) { - if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) { - inRect = true - } else { - inRect = false - } - }); - return inRect - } - - // 鼠标按下事件 - currentFabric.on('mouse:down', function(o) { - currentFabric.startDraw = true - var pointer = currentFabric.getPointer(o.e); - origX = pointer.x; - origY = pointer.y; - - // 创建一个矩形对象 - rect = new fabric.Rect({ - left: origX, - top: origY, - originX: 'left', - originY: 'top', - borderColor: '#09f', - cornerColor: '#09f', - cornerSize: 6, - width: pointer.x - origX, - height: pointer.y - origY, - angle: 0, - fill: 'transparent', - hasControls: true, - hasBorders: true, - lockRotation: true, // 锁定旋转 - hasRotatingPoint: false // 隐藏旋转控制点 - }); - - // 判断存在实例,并且鼠标点击在实例上 - if (checkPointInRect(o) && rect) { - } else { - addOrReplaceRect(rect) - // 监听移动 - rect.on('moving', o => console.log('o', o)) - rect.on('resizing', o => console.log('o', o)) - // 监听缩放 - rect.on('scaling', o => console.log('o', o)) - } - currentFabric.setActiveObject(currentFabric.item(0)); - }); - // 鼠标移动事件 - currentFabric.on('mouse:move', function(o) { - if (!currentFabric.startDraw) return; - var pointer = currentFabric.getPointer(o.e); - - if(origX > pointer.x){ - rect.set({ left: Math.abs(pointer.x) }); - } - - if(origY > pointer.y){ - rect.set({ top: Math.abs(pointer.y) }); - } - rect.set({ width: Math.abs(origX - pointer.x) }); - rect.set({ height: Math.abs(origY - pointer.y) }); - currentFabric.renderAll(); - }); - // 鼠标松开事件 - currentFabric.on('mouse:up', function(o) { - currentFabric.startDraw = false - }); - - return currentFabric - } - - // 初始化线条 - const initLine = () => { - const viewer = viewerRef?.current || {} - const { containerData = {}, targetTransform = {} } = viewer - const imageSize = viewer.getImgSize() - + const { containerData = {}, targetTransform = {} } = _viewer + const _imgSize = _viewer.getImgSize() || {} // @ts-ignore currentFabricRef.current = new fabric.Canvas( canvasRef.current, @@ -302,10 +171,206 @@ const CropperImage = forwardRef((props, selection: false, } ) - const currentFabric = currentFabricRef.current - // 事件监听: 鼠标抬起事件 + // 判断是否可编辑 - 非编辑态 + if (!editAble) { + currentFabricRef.current.clear() + let originWidth = (_imgSize.w * targetTransform.scale) + let originHeight = (_imgSize.h * targetTransform.scale) + odList && odList?.forEach(od => { + const _shapeData = originPercentToShapeLength(od, { sourceImageWidth: originWidth, sourceImageHeight: originHeight, targetTransform }) + let _data = { + id: od.id, + type: od.type || 'rect', + originData: od, + ..._shapeData + } + let item = createFabricShape(_data, { + }) + currentFabricRef.current?.add(item) + }) + currentFabricRef.current?.renderAll() + // 当矩形被选中时 + currentFabricRef.current?.on('selection:created', e => handleSelected(e)); + currentFabricRef.current?.on('selection:updated', function(e) { + handleSelected(e) + changeColor(e.deselected!, '#FFF566') + }); + // 当取消选中时 + currentFabricRef.current?.on('selection:cleared', function(e) { + changeColor(e.deselected!, '#FFF566') + }); + } else { + if (type === 'rect') { + // 编辑模式 - 矩形绘制 + initRect(currentFabricRef.current) + } else { + // 编辑模式 - 线绘制 + initLine(currentFabricRef.current) + } + } + return () => { + currentFabricRef.current?.removeListeners?.() + currentFabricRef.current?.dispose?.() + } + // TODO: 监听odList 需要优化 + },[type, editAble, isImgReady]) + + // 监听矩形拖动,防抖 + const { run: handleRectChange } = useDebounceFn( + (data: IRectData) => { + const _viewer = viewerRef.current + const { targetTransform = {} } = _viewer || {} + const { left = 0, top = 0, width = 0, height = 0, scaleX = 1, scaleY = 1 } = data || {} + const targetPosition = { x: Math.abs(left), y: Math.abs(top), w: Math.abs(width * scaleX), h: Math.abs(height * scaleY) } + let rectImageBase64 = '' + + if (width && height) { + rectImageBase64 = getImageDataByPosition(data, { canvas: _viewer.canvas }) + } + const targetData = getTransformRect(_viewer.image, targetTransform, targetPosition) + onCropEnd?.({ type: 'rect', left, top, width, height, rectImageBase64, targetTransform, targetData }) + }, + { + wait: 500, + }, + ); + + // 初始化 - 矩形圈选工具 + const initRect = (_fabricCanvas: CanvasPro) => { + const viewer = viewerRef?.current || {} + const { targetTransform = {} } = viewer + const imageSize = viewer.getImgSize() || { x: 0, y: 0 } + let currentFabric = _fabricCanvas + let rect: fabric.Rect + let origX: number, origY: number + // 坐标限制 + let limitStartX = targetTransform.translateX + let limitStartY = targetTransform.translateY + let limitEndX = limitStartX + (imageSize.w * targetTransform.scale) + let limitEndY = limitStartY + (imageSize.h * targetTransform.scale) + + function _getLimitPointer(_pointer: { x: number; y: number; } = { x: 0, y: 0 }) { + return { + x: Math.min(Math.max(_pointer.x, limitStartX), limitEndX), + y: Math.min(Math.max(_pointer.y, limitStartY), limitEndY) + }; + } + + const _handleMove = (e: fabric.IEvent) => { + // 阻止对象移动到画布外面 + var obj = e.target! + var top = obj.top || 0 + var left = obj.left || 0 + var w = obj.width! * (obj?.scaleX || 1)! + var h = obj.height! * (obj?.scaleY || 1)! + var top_bound = limitStartY; + var bottom_bound = limitEndY - h; + var left_bound = limitStartX; + var right_bound = limitEndX - w; + + if( w > currentFabric.width! ) { + obj.set('left', left_bound) + } else { + obj.set('left', (Math.min(Math.max(left, left_bound), right_bound))) + } + + if( h > currentFabric.height! ) { + obj.set('top', top_bound) + } else { + obj.set('top', (Math.min(Math.max(top, top_bound), bottom_bound))) + } + // @ts-ignore + handleRectChange(pick(obj, 'width', 'height', 'left', 'top', 'scaleX', 'scaleY')) + } + // 鼠标:按下事件 + currentFabric.on('mouse:down', function(o) { + currentFabric.startDraw = true + setIsDrawing(true) + var pointer = currentFabric.getPointer(o.e); + + if (pointer.x < limitStartX || pointer.x > limitEndX + || pointer.y < limitStartY || pointer.y > limitEndY + ) return + + origX = pointer.x; + origY = pointer.y; + // 创建一个矩形对象 + rect = createFabricShape({ + left: origX, + top: origY, + width: pointer.x - origX, + height: pointer.y - origY, + }, { + borderColor: '#09f', + stroke: '#09f', + cornerColor: '#09f', + fill: 'transparent', + hasControls: true, + hasBorders: true, + lockMovementX: false, + lockMovementY: false + }); + + // 判断存在实例,并且鼠标点击在实例上 + if (checkMouseInRect(o, currentFabric) && rect) { + } else { + currentFabric.clear() + currentFabric.setActiveObject(rect) + currentFabric.add(rect); + } + onCropStart?.() + }); + // 鼠标:移动事件 + currentFabric.on('mouse:move', function(o) { + if (!currentFabric.startDraw || !rect) return; + const pointer = currentFabric.getPointer(o.e); + const limitPointer = _getLimitPointer(pointer) // 限制绘制的图形大小 + const width = limitPointer.x - (rect.left || 0) + const height = limitPointer.y - (rect.top || 0) + + if(origX > pointer.x) { + rect.set({ left: Math.abs(pointer.x) }); + } + + if(origY > pointer.y){ + rect.set({ top: Math.abs(pointer.y) }); + } + rect.set({ width }); + rect.set({ height }); + currentFabric.renderAll(); + onMouseMove?.(o) + }); + // 鼠标:松开事件 + currentFabric.on('mouse:up', function(e) { + currentFabric.startDraw = false + const currentRef = currentFabric.getActiveObject() + if (!currentRef) return + setIsDrawing(false) + // @ts-ignore + currentShapeRef.current = currentRef + // @ts-ignore + handleRectChange(pick(currentRef, 'width', 'height', 'left', 'top', 'scaleX', 'scaleY')) + onMouseUp?.(e) + }); + currentFabric.on('object:moving', (e) => _handleMove(e)); + currentFabric.on('object:scaling', (e) => _handleMove(e)); + } + + // 初始化线条 + const initLine = (_fabricCanvas: CanvasPro) => { + if (!currentFabricRef.current) return + const viewer = viewerRef?.current || {} + const { targetTransform = {} } = viewer + const imageSize = viewer.getImgSize() + let arrowLine: fabric.Object + const currentFabric = _fabricCanvas + + // 事件监听: 鼠标按下 currentFabric.on('mouse:down', function(options) { + currentFabric.startDraw = true + setIsDrawing(true) + currentFabric.clear() var evt = options.e; // 检查鼠标是否按下左键并且没有按住Ctrl键(Windows系统) @@ -328,73 +393,62 @@ const CropperImage = forwardRef((props, x: pointer.x, y: pointer.y }; - currentFabric.startDraw = true currentFabric.renderAll(); onMouseDown?.(currentFabric.selectionStart) }); - // 事件监听:鼠标松开事件 - currentFabric.on('mouse:up', async function(_options) { - currentFabric.clear() - let group = drawArrowLine( - currentFabric?.selectionStart?.x as number, - currentFabric.selectionStart?.y as number, - currentFabric.selectionEnd?.x as number, - currentFabric.selectionEnd?.y as number, - lineConfig - ) - - currentFabric.add(group) - // 停止绘制 - currentFabric.startDraw = false - currentFabric.renderAll(); - - const _shapeData = { - startX: currentFabric.selectionStart?.x as number, - startY: currentFabric.selectionStart?.y as number, - endX: currentFabric.selectionEnd?.x as number, - endY: currentFabric.selectionEnd?.y as number - } - onMouseUp?.({ ..._shapeData, targetTransform }) - }); - // 事件监听:鼠标移动事件 currentFabric.on('mouse:move', function(options) { - // 存在起始点,开始绘制 - if (currentFabric.selectionStart && currentFabric.startDraw) { + if (!currentFabric.startDraw) return;// 存在起始点,开始绘制 + // 阻止默认行为 + options.e.preventDefault(); + var endPointer = options.pointer!!; - // 阻止默认行为 - options.e.preventDefault(); - var endPointer = options.pointer!!; - - // 限定绘制区域 - if (!checkPointInRect(endPointer, { - ...pick(targetTransform, 'scale', 'translateX', 'translateY'), - ...imageSize - } - )) return - - // 更新选区大小 - currentFabric.selectionEnd = { - x: endPointer?.x || 0, - y: endPointer?.y || 0 - }; - - // 清除之前的选择框 - currentFabric.clear(); - let group = drawArrowLine( - currentFabric.selectionStart.x, - currentFabric.selectionStart.y, - endPointer?.x || 0, - endPointer?.y || 0, - lineConfig - ) - - currentFabric.add(group) - } + // 限定绘制区域 + if (!checkPointInRect(endPointer, { + ...pick(targetTransform, 'scale', 'translateX', 'translateY'), + ...imageSize + } + )) return + // 更新选区大小 + currentFabric.selectionEnd = { + x: endPointer?.x || 0, + y: endPointer?.y || 0 + }; + currentFabric.clear() + arrowLine = drawArrowLine({ + startX: currentFabric?.selectionStart?.x as number, + startY: currentFabric.selectionStart?.y as number, + endX: currentFabric.selectionEnd?.x as number, + endY: currentFabric.selectionEnd?.y as number, + }, { + fill: '#09f', + ...lineConfig + }) + currentFabric.add(arrowLine) + // @ts-ignore + currentShapeRef.current = arrowLine + // 停止绘制 currentFabric.renderAll(); + onMouseMove?.(options) + }); + + // 事件监听:鼠标松开事件 + currentFabric.on('mouse:up', async function(e) { + currentFabric.startDraw = false + setIsDrawing(false) + const targetTransform = viewerRef.current?.targetTransform || {} + // @ts-ignore + onCropEnd?.({ + type: 'line', + startX: currentFabric.selectionStart?.x, + startY: currentFabric.selectionStart?.y, + endX: currentFabric.selectionEnd?.x, + endY: currentFabric.selectionEnd?.y, + targetTransform + }) + onMouseUp?.(e) }); - return currentFabric } useImperativeHandle(ref, () => ({ @@ -409,6 +463,7 @@ const CropperImage = forwardRef((props, } })); + const { left: shapeLeft, width: shapeWidth = 0, top: shapeTop, scaleX: shapeScaleX = 0 } = currentShapeRef.current || {} return (
{/* 图片 */} @@ -416,49 +471,27 @@ const CropperImage = forwardRef((props, ref={imageRef} className={classNames(`${componentName}_img`)} /> - {showToast && selectedItem && !isDrawing && (<> -
-
- - {customToast?.({ - selectedItem - })} - - )} {children} +
+ {/* @ts-ignore */} + {customToast?.({ + ...selectedItem, + }) || ( +
+ 测试 +
+ )} +
); }) diff --git a/packages/meta/src/cropperImage/cropperImagehelper.ts b/packages/meta/src/cropperImage/cropperImagehelper.ts index 4968675..749035b 100644 --- a/packages/meta/src/cropperImage/cropperImagehelper.ts +++ b/packages/meta/src/cropperImage/cropperImagehelper.ts @@ -1,7 +1,8 @@ -import { isString } from "@zhst/func"; +import { isString, omit } from "@zhst/func"; import { fabric } from 'fabric' import { ILineOptions } from "fabric/fabric-impl"; import { Rect } from '../ImageEditor/viewer/shape'; +import { IArrowLinePosition, IRectData, IShape, ITransform, RectPro } from "./type"; export const getImage = (propImage: HTMLImageElement | string) => { return new Promise((resolve, reject) => { @@ -21,6 +22,44 @@ export const getImage = (propImage: HTMLImageElement | string) => { }); } +// OD源数据转图形坐标 +export const originPercentToShapeLength = (odData: Rect & { x2?: number; y2?: number }, opt: { + sourceImageWidth: number + sourceImageHeight: number + targetTransform?: ITransform +}) => { + const { targetTransform, sourceImageWidth = 0, sourceImageHeight = 0 } = opt || {} + const { translateX = 0, translateY = 0 } = targetTransform || {} + return { + left: odData.x * sourceImageWidth + translateX, + top: odData.y * sourceImageHeight + translateY, + width: odData.w * sourceImageWidth, + height: odData.h * sourceImageHeight, + endLeft: (odData.x2 || 0) * sourceImageWidth + translateX, + endTop: (odData.y2 || 0) * sourceImageHeight + translateY, + } +} + +// 图形坐标转OD源数据 +export const shapeLengthToPercent = (shapeData: IShape & { endLeft?: number, endTop?: number }, opt: { + sourceImageWidth: number + sourceImageHeight: number + targetTransform?: ITransform +}) => { + const { left = 0, width = 0, top = 0, height = 0, endLeft = 0, endTop = 0 } = shapeData + const { targetTransform, sourceImageWidth = 0, sourceImageHeight = 0 } = opt || {} + const { translateX = 0, translateY = 0 } = targetTransform || {} + + return { + x: (left - translateX) / sourceImageWidth, + y: (top - translateY) / sourceImageHeight, + w: width / sourceImageWidth, + h: height / sourceImageHeight, + x2: (endLeft - translateX) / sourceImageWidth, + y2: (endTop - translateX) / sourceImageWidth, + } +} + /** * 检查鼠标是否在矩形中、矩形编辑器上 * @param o 鼠标对象实例 @@ -28,8 +67,8 @@ export const getImage = (propImage: HTMLImageElement | string) => { * @returns Boolean */ export const checkMouseInRect = (o: fabric.IEvent, _fabricCanvas: fabric.Canvas) => { - var pointer = _fabricCanvas.getPointer(o.e); - var point = new fabric.Point(pointer.x, pointer.y); + let pointer = _fabricCanvas.getPointer(o.e); + let point = new fabric.Point(pointer.x, pointer.y); let inRect = false _fabricCanvas.forEachObject(function(obj) { if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) { @@ -47,7 +86,7 @@ export const checkMouseInRect = (o: fabric.IEvent, _fabricCanvas: fa * @param rect 原始画布宽高:w、h;缩放比例:scale;整体偏移的坐标:translateX,translateY * @returns boolean */ -export const checkPointInRect = (point: { x: number; y: number;}, rect: { w: number; h: number, translateX?: number; translateY?: number, scale: number }) => { +export const checkPointInRect = (point: Pick, rect: { w: number; h: number, translateX?: number; translateY?: number, scale: number }) => { const { w, h, translateX = 0, translateY = 0, scale = 1 } = rect; const limitStartX = translateX const limitEndX = translateX + (w * scale) @@ -62,61 +101,16 @@ export const checkPointInRect = (point: { x: number; y: number;}, rect: { w: num return false } -// 绘制带箭头的直线函数 -export const drawArrowLine = (startX: number, startY: number, endX: number, endY: number, lineConfig: ILineOptions) => { - - var angle = Math.atan2(endY - startY, endX - startX); - - var line = new fabric.Line([startX, startY, endX, endY], lineConfig); - - var arrowLength = 20; - var arrowWidth = 20; - - var arrow = new fabric.Triangle({ - left: endX - arrowLength / 2 * Math.cos(angle), - top: endY - arrowLength / 2 * Math.sin(angle), - width: arrowWidth, - height: arrowWidth, - originX: 'center', - originY: 'center', - fill: '#09f', - angle: angle * 180 / Math.PI + 90 - }); - - return new fabric.Group([line, arrow], { - selectable: false, - }); - } - -// 百分比转长度 -export const percentToLength = (originData: Rect, canvas: HTMLCanvasElement) => { - const { x = 0, y = 0, w = 0, h = 0 } = originData - const canvasW = canvas.width - const canvasH = canvas.height - - return { - x: x * canvasW, - y: y * canvasH, - w: w * canvasW, - h: h * canvasH - } -} - // 通过位置截取图片 -export const getImageDataByPosition = (position: { - w: number; - h: number; - x: number; - y: number; -}, opt: { +export const getImageDataByPosition = (position: IShape, opt: { canvas: HTMLCanvasElement fileType?: string }) => { - const { x =0, y = 0, w = 0, h = 0 } = position + const { left = 0, top = 0, width = 0, height = 0 } = position const { fileType = 'image/jpg', canvas } = opt const _canvas = canvas const ctx = _canvas.getContext('2d') - const imageData = ctx?.getImageData(x, y, w, h) + const imageData = ctx?.getImageData(left, top, width, height) const newCanvas = document.createElement('canvas') const newCtx = newCanvas.getContext('2d') newCanvas.width = imageData?.width || 0 @@ -124,3 +118,136 @@ export const getImageDataByPosition = (position: { newCtx?.putImageData(imageData!!, 0, 0) return newCanvas.toDataURL(fileType) } + +/** + * 限制矩形绘制范围 + * @param e fabric鼠标指针对象 + * @param opt 配置 + */ +export const limitPointMove = ( + e: fabric.IEvent, + opt: { + limitRect: IArrowLinePosition, + containerSize: Pick + } +) => { + const { limitRect, containerSize } = opt + const { startX, startY, endX, endY } = limitRect + // 阻止对象移动到画布外面 + var obj = e.target! + var top = obj.top || 0 + var left = obj.left || 0 + var w = obj.width! * obj.scaleX! + var h = obj.height! * obj.scaleY! + + var top_bound = startY; + var bottom_bound = endY - h; + var left_bound = startX; + var right_bound = endX - w; + + if( w >= containerSize.width! ) { + obj.set('left', left_bound) + } else { + obj.set('left', (Math.min(Math.max(left, left_bound), right_bound))) + } + + if( h >= containerSize.height! ) { + obj.set('top', top_bound) + } else { + obj.set('top', (Math.min(Math.max(top, top_bound), bottom_bound))) + } +} + +// 绘制带箭头的直线函数 +export const drawArrowLine = (arrowPosition: IArrowLinePosition, lineConfig: ILineOptions) => { + const { startX = 0, startY = 0, endX = 0, endY = 0 } = arrowPosition + let angle = Math.atan2(endY - startY, endX - startX); + let line = new fabric.Line([startX, startY, endX, endY], { + perPixelTargetFind: true, // 启用逐像素检测 + selectable: true, + ...lineConfig + }); + let arrowLength = 20; + let arrowWidth = 20; + + let arrow = new fabric.Triangle({ + left: endX - arrowLength / 2 * Math.cos(angle), + top: endY - arrowLength / 2 * Math.sin(angle), + width: arrowWidth, + height: arrowWidth, + fill: '#FFF566', + originX: 'center', + originY: 'center', + angle: angle * 180 / Math.PI + 90, + perPixelTargetFind: true, // 启用逐像素检测 + ...lineConfig + }); + + return new fabric.Group([line, arrow], { + selectable: true, + evented: false, + }); +} + +/** + * 通过参数绘制fabric图形 + * @param data 需要绘制的图形参数 + * @returns fabric 图形对象 + */ +export const createFabricShape = (data: Partial & IShape & { endLeft?: number; endTop?: number }, config: Partial) => { + let shape: any + let { type = 'rect', left = 0, width = 0, top = 0, height = 0, endLeft = 0, endTop = 0 } = data + const defaultConfig = { + borderColor: '#FFF566', + cornerColor: '#FFF566', + cornerSize: 8, + stroke: '#FFF566', + strokeWidth: 2, + hasControls: false, + hasBorders: false, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, // 锁定旋转 + hasRotatingPoint: false, // 隐藏旋转控制点 + ...config + } + + switch (type) { + case 'line': + shape = drawArrowLine({ + startX: left, + startY: top, + endX: endLeft, + endY: endTop + }, { + ...defaultConfig + }) + break; + case 'rect': + shape = new fabric.Rect({ + left, + top, + width, + height, + originX: 'left', + originY: 'top', + angle: 0, + fill: 'transparent', + ...defaultConfig + }) + default: + break; + } + return shape +} + +/** + * 修改fabric图形颜色 + * @param shapes 图形 + * @param color 颜色 + */ +export const changeColor = (shapes: fabric.Object[], color: string) => { + shapes?.forEach(obj => { + obj.set('stroke', color) + }) +} diff --git a/packages/meta/src/cropperImage/demo/basic.tsx b/packages/meta/src/cropperImage/demo/basic.tsx index 74d40c1..7ff5c06 100644 --- a/packages/meta/src/cropperImage/demo/basic.tsx +++ b/packages/meta/src/cropperImage/demo/basic.tsx @@ -23,6 +23,16 @@ export default () => { "y": 0.3203356, "w": 0.052037954, "h": 0.2664015 + }, + { + "id": "46", + type: 'line', + "x": 0.18543766, + "y": 0.1203356, + "w": 0.62037954, + "h": 0.5864015, + "x2": 0.62037954, + "y2": 0.5864015 } ]) const [selectedItem, setSelectedItem] = useState() @@ -64,23 +74,23 @@ export default () => { url="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" onMouseUp={data => console.log('箭头绘制结束:', data)} onShapeSelected={(id, shapeData) => { - console.log('矩形选择', id, shapeData) + console.log('shapeData', shapeData) setImgUrl(shapeData?.imageRect as string) }} onCropStart={() => console.log('矩形开始绘制')} onCropEnd={(data) => { - console.log('data', data) + console.log('绘制完成', data) setSelectedItem({ x: data.left, y: data.top, h: data.height, w: data.width }) - setImgUrl(data?.imageRect as string) + setImgUrl(data?.rectImageBase64 as string) }} selectedItem={selectedItem} showToast={editAble} customToast={() => ( -
自定义框
+
)} /> - + {imgUrl && } ) } diff --git a/packages/meta/src/cropperImage/index.tsx b/packages/meta/src/cropperImage/index.tsx index d985aec..e383ca9 100644 --- a/packages/meta/src/cropperImage/index.tsx +++ b/packages/meta/src/cropperImage/index.tsx @@ -1,5 +1,6 @@ import CropperImage from "./CropperImage"; export type { CropperImageRefProps, CropperImageProps } from './CropperImage' +export * from './cropperImagehelper' export default CropperImage diff --git a/packages/meta/src/cropperImage/type.ts b/packages/meta/src/cropperImage/type.ts new file mode 100644 index 0000000..919f75b --- /dev/null +++ b/packages/meta/src/cropperImage/type.ts @@ -0,0 +1,34 @@ +import { Rect } from '../ImageEditor/viewer/shape'; + +export type IShapeType = 'line' | 'rect' + +export interface IShape { + top?: number; + left?: number + width: number; + height: number +} + +export interface RectPro extends Rect { + imageRect?: string; + type?: IShapeType; // line:线,rect:矩形 +} + +export interface IArrowLinePosition { + startX: number + startY: number + endX: number + endY: number +} + +export interface IRectData extends IShape { + scaleX?: number; + scaleY?: number; +} + +export interface ITransform { + translateX: number + translateY: number + scale: number + rotate: number +} diff --git a/tsconfig.json b/tsconfig.json index 8aef6e6..28e0062 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,10 @@ "lib": ["dom", "es2017"], "stripInternal": true, "resolvePackageJsonExports": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "types": [ + // "@zhst/meta" 全局使用的工具包,不建议写到 npm 包中去 + ] }, "include": [".dumirc.ts", "src/**/*", "packages/**/*"], "exclude": ["node_modules", "lib", "es", ".dumi"] From fe321d1c3b3267adec3585c62515ed1aa3b95f3c Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Thu, 16 May 2024 18:12:04 +0800 Subject: [PATCH 4/5] fix conflict --- packages/meta/src/cropperImage/cropperImagehelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/meta/src/cropperImage/cropperImagehelper.ts b/packages/meta/src/cropperImage/cropperImagehelper.ts index 749035b..5eaef06 100644 --- a/packages/meta/src/cropperImage/cropperImagehelper.ts +++ b/packages/meta/src/cropperImage/cropperImagehelper.ts @@ -1,4 +1,4 @@ -import { isString, omit } from "@zhst/func"; +import { isString } from "@zhst/func"; import { fabric } from 'fabric' import { ILineOptions } from "fabric/fabric-impl"; import { Rect } from '../ImageEditor/viewer/shape'; From a65d6e062d06ba06e8fc7950382432b1ed1346a2 Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Thu, 16 May 2024 18:13:40 +0800 Subject: [PATCH 5/5] fix conflict --- packages/biz/CHANGELOG.md | 8 ++++++++ packages/biz/package.json | 2 +- packages/material/CHANGELOG.md | 9 +++++++++ packages/material/package.json | 2 +- packages/meta/CHANGELOG.md | 8 ++++++++ packages/meta/package.json | 2 +- 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/biz/CHANGELOG.md b/packages/biz/CHANGELOG.md index a499c5d..917b5e3 100644 --- a/packages/biz/CHANGELOG.md +++ b/packages/biz/CHANGELOG.md @@ -1,5 +1,13 @@ # @zhst/biz +## 0.22.1 + +### Patch Changes + +- fix: 修改 cropperHelper 的无效引用 +- Updated dependencies + - @zhst/meta@0.21.1 + ## 0.22.0 ### Minor Changes diff --git a/packages/biz/package.json b/packages/biz/package.json index 5e3485b..84a6621 100644 --- a/packages/biz/package.json +++ b/packages/biz/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/biz", - "version": "0.22.0", + "version": "0.22.1", "description": "业务库", "keywords": [ "business", diff --git a/packages/material/CHANGELOG.md b/packages/material/CHANGELOG.md index b7d4782..51676ae 100644 --- a/packages/material/CHANGELOG.md +++ b/packages/material/CHANGELOG.md @@ -1,5 +1,14 @@ # @zhst/material +## 0.18.1 + +### Patch Changes + +- fix: 修改 cropperHelper 的无效引用 +- Updated dependencies + - @zhst/meta@0.21.1 + - @zhst/biz@0.22.1 + ## 0.18.0 ### Minor Changes diff --git a/packages/material/package.json b/packages/material/package.json index 70cd047..db1ab3f 100644 --- a/packages/material/package.json +++ b/packages/material/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/material", - "version": "0.18.0", + "version": "0.18.1", "description": "物料库", "keywords": [ "business", diff --git a/packages/meta/CHANGELOG.md b/packages/meta/CHANGELOG.md index c61c3cf..623a6e9 100644 --- a/packages/meta/CHANGELOG.md +++ b/packages/meta/CHANGELOG.md @@ -1,5 +1,13 @@ # @zhst/utils +## 0.21.1 + +### Patch Changes + +- fix: 修改 cropperHelper 的无效引用 +- Updated dependencies + - @zhst/meta@0.21.1 + ## 0.21.0 ### Minor Changes diff --git a/packages/meta/package.json b/packages/meta/package.json index 6a60c95..acfec6e 100644 --- a/packages/meta/package.json +++ b/packages/meta/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/meta", - "version": "0.21.0", + "version": "0.21.1", "description": "原子组件", "keywords": [ "meta",