fix(zhst/meta、zhst/biz、zhst/material): 修改图片标注组件
This commit is contained in:
parent
37c34eb785
commit
8dc81e1743
@ -1,5 +1,16 @@
|
|||||||
# @zhst/biz
|
# @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
|
## 0.21.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@zhst/biz",
|
"name": "@zhst/biz",
|
||||||
"version": "0.21.5",
|
"version": "0.22.0",
|
||||||
"description": "业务库",
|
"description": "业务库",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"business",
|
"business",
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
# @zhst/material
|
# @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
|
## 0.17.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@zhst/material",
|
"name": "@zhst/material",
|
||||||
"version": "0.17.4",
|
"version": "0.18.0",
|
||||||
"description": "物料库",
|
"description": "物料库",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"business",
|
"business",
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
# @zhst/utils
|
# @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
|
## 0.20.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -176,6 +176,9 @@ export default {
|
|||||||
h: this.image.height
|
h: this.image.height
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getImage: function getImage() {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
calcFitScreen: function calcFitScreen() {
|
calcFitScreen: function calcFitScreen() {
|
||||||
if (!this.image) return;
|
if (!this.image) return;
|
||||||
var w = this.containerData.width;
|
var w = this.containerData.width;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@zhst/meta",
|
"name": "@zhst/meta",
|
||||||
"version": "0.20.3",
|
"version": "0.21.0",
|
||||||
"description": "原子组件",
|
"description": "原子组件",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"meta",
|
"meta",
|
||||||
|
@ -26,14 +26,16 @@ const props = {
|
|||||||
objects: [
|
objects: [
|
||||||
{
|
{
|
||||||
"bboxRatio": {
|
"bboxRatio": {
|
||||||
|
"id": "123",
|
||||||
"x": 0.5519352,
|
"x": 0.5519352,
|
||||||
"y": 0.2965385,
|
"y": 0.2965385,
|
||||||
"w": 0.05185461,
|
"w": 0.05185461,
|
||||||
"h": 0.24698898
|
"h": 0.24698898,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bboxRatio": {
|
"bboxRatio": {
|
||||||
|
"id": "456",
|
||||||
"x": 0.58543766,
|
"x": 0.58543766,
|
||||||
"y": 0.3203356,
|
"y": 0.3203356,
|
||||||
"w": 0.052037954,
|
"w": 0.052037954,
|
||||||
|
@ -153,6 +153,10 @@ export default {
|
|||||||
return { w: this.image.width, h: this.image.height };
|
return { w: this.image.width, h: this.image.height };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getImage() {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
calcFitScreen() {
|
calcFitScreen() {
|
||||||
if (!this.image) return;
|
if (!this.image) return;
|
||||||
const w = this.containerData.width;
|
const w = this.containerData.width;
|
||||||
|
@ -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 classNames from 'classnames'
|
||||||
import { fabric } from 'fabric'
|
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 Viewer from '../ImageEditor/viewer';
|
||||||
import './index.less'
|
import './index.less'
|
||||||
import { ConfigContext } from '../config-provider';
|
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 { Rect } from '../ImageEditor/viewer/shape';
|
||||||
import { checkPointInRect, drawArrowLine, getImageDataByPosition, percentToLength } from './cropperImagehelper';
|
import {
|
||||||
import Align from 'rc-align';
|
checkPointInRect,
|
||||||
|
drawArrowLine,
|
||||||
interface RectPro extends Rect {
|
getImageDataByPosition,
|
||||||
imageRect?: string;
|
checkMouseInRect,
|
||||||
type?: 'line' | 'rect'; // line:线,rect:矩形
|
createFabricShape,
|
||||||
}
|
originPercentToShapeLength,
|
||||||
|
changeColor,
|
||||||
|
shapeLengthToPercent
|
||||||
|
} from './cropperImagehelper';
|
||||||
|
import { getTransformRect } from '../BigImagePreview/bigImagePreviewHelper';
|
||||||
|
import { IArrowLinePosition, IRectData, ITransform, RectPro } from './type'
|
||||||
|
|
||||||
export interface CropperImageProps {
|
export interface CropperImageProps {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
@ -30,27 +36,22 @@ export interface CropperImageProps {
|
|||||||
// 是否展示框选拓展框
|
// 是否展示框选拓展框
|
||||||
showToast?: boolean;
|
showToast?: boolean;
|
||||||
// 自定义拓展框
|
// 自定义拓展框
|
||||||
customToast?: (data?: any) => React.JSX.Element
|
customToast?: (data: any) => React.JSX.Element;
|
||||||
|
toastStyle?: CSSStyleSheet
|
||||||
type?: 'line' | 'rect'; // 编辑类型
|
type?: 'line' | 'rect'; // 编辑类型
|
||||||
onMouseDown?: (data: { x: number; y: number }) => void;
|
onMouseDown?: (data: { x: number; y: number }) => void;
|
||||||
onMouseUp?: (data: {
|
onMouseUp?: (e?: fabric.IEvent<MouseEvent>) => void;
|
||||||
startX: number;
|
onMouseMove?: (e?: fabric.IEvent<MouseEvent>) => void;
|
||||||
startY: number;
|
|
||||||
endX: number;
|
|
||||||
endY: number;
|
|
||||||
imageDom?: HTMLImageElement,
|
|
||||||
targetTransform?: {
|
|
||||||
translateX: number
|
|
||||||
translateY: number
|
|
||||||
scale: number
|
|
||||||
rotate: number
|
|
||||||
}
|
|
||||||
}) => void;
|
|
||||||
onShapeSelected?: (id: string, shapeData?: RectPro & {
|
onShapeSelected?: (id: string, shapeData?: RectPro & {
|
||||||
originData: Rect
|
originData: Rect
|
||||||
}) => void
|
}) => void
|
||||||
onCropStart?: () => void
|
onCropStart?: () => void
|
||||||
onCropEnd?: (data: RectPro) => void
|
onCropEnd?: (data: Partial<IRectData> & Partial<IArrowLinePosition> & {
|
||||||
|
type: RectPro['type'],
|
||||||
|
rectImageBase64?: string
|
||||||
|
targetTransform?: ITransform
|
||||||
|
targetData?: Rect
|
||||||
|
}) => void
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +76,7 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
selectedItem,
|
selectedItem,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
onMouseUp,
|
onMouseUp,
|
||||||
|
onMouseMove,
|
||||||
onCropStart,
|
onCropStart,
|
||||||
onCropEnd,
|
onCropEnd,
|
||||||
editAble,
|
editAble,
|
||||||
@ -82,7 +84,8 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
selectAble = true,
|
selectAble = true,
|
||||||
showToast = false,
|
showToast = false,
|
||||||
customToast = () => <div>无</div>,
|
customToast = () => <div>无</div>,
|
||||||
type = 'ract',
|
toastStyle = {},
|
||||||
|
type = 'line',
|
||||||
scaleAble = false,
|
scaleAble = false,
|
||||||
lineConfig = {
|
lineConfig = {
|
||||||
stroke: '#09f',
|
stroke: '#09f',
|
||||||
@ -96,202 +99,68 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
const [isDrawing, setIsDrawing] = useState(false) // 矩形是否在移动
|
const [isDrawing, setIsDrawing] = useState(false) // 矩形是否在移动
|
||||||
|
|
||||||
const canvasRef = useRef<any>(null);
|
const canvasRef = useRef<any>(null);
|
||||||
const currentShapeRef = useRef<any>(null)
|
|
||||||
const imageRef = useRef<HTMLDivElement>(null)
|
const imageRef = useRef<HTMLDivElement>(null)
|
||||||
const viewerRef = useRef<any>(null)
|
const viewerRef = useRef<any>(null)
|
||||||
const currentFabricRef = useRef<CanvasPro>(null)
|
const currentFabricRef = useRef<CanvasPro>(null)
|
||||||
|
const currentShapeRef = useRef<fabric.Object>(null)
|
||||||
// 自定义弹框
|
const [isImgReady, setIsImgReady] = useState(false);
|
||||||
const alginContainerRef: any = useRef(null);
|
|
||||||
const alignRef: any = useRef(null);
|
|
||||||
const imageRectRef: any = useRef(null)
|
|
||||||
|
|
||||||
// 初始化 - 图片
|
// 初始化 - 图片
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const handleReady = addEventListenerWrapper(imageRef.current, EVENT_VIEWER_READY, () => {
|
||||||
|
setIsImgReady(true)
|
||||||
|
});
|
||||||
|
|
||||||
viewerRef.current = new Viewer(imageRef.current!!, {
|
viewerRef.current = new Viewer(imageRef.current!!, {
|
||||||
image: url,
|
image: url,
|
||||||
scaleAble,
|
scaleAble,
|
||||||
selectAble,
|
selectAble,
|
||||||
|
height: 600,
|
||||||
|
fitScaleAsMinScale: true,
|
||||||
dragAble: false,
|
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 () => {
|
return () => {
|
||||||
// 再次加载,销毁原来的实例
|
// 再次加载,销毁原来的实例
|
||||||
viewerRef?.current?.destroy?.();
|
viewerRef?.current?.destroy?.();
|
||||||
viewerRef.current = null;
|
viewerRef.current = null;
|
||||||
imageRectRef.current?.remove();
|
|
||||||
viewerRef.current?.clearShape?.();
|
viewerRef.current?.clearShape?.();
|
||||||
|
handleReady.remove?.()
|
||||||
}
|
}
|
||||||
}, [url])
|
}, [url])
|
||||||
|
|
||||||
const cropStartRef = useRef<any>(null)
|
/**
|
||||||
const cropEndRef = useRef<any>(null)
|
* 监听图形选中事件
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
const handleSelected = (e: fabric.IEvent<MouseEvent>) => {
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
const _viewer = viewerRef?.current || {}
|
const _viewer = viewerRef?.current || {}
|
||||||
// 判断是否可编辑 - 非编辑态
|
const { containerData = {}, targetTransform = {} } = _viewer
|
||||||
if (!editAble) {
|
const _imgSize = _viewer.getImgSize() || {}
|
||||||
_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<MouseEvent>) => {
|
|
||||||
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()
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
currentFabricRef.current = new fabric.Canvas(
|
currentFabricRef.current = new fabric.Canvas(
|
||||||
canvasRef.current,
|
canvasRef.current,
|
||||||
@ -302,10 +171,206 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
selection: false,
|
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<MouseEvent>) => {
|
||||||
|
// 阻止对象移动到画布外面
|
||||||
|
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.on('mouse:down', function(options) {
|
||||||
|
currentFabric.startDraw = true
|
||||||
|
setIsDrawing(true)
|
||||||
|
currentFabric.clear()
|
||||||
var evt = options.e;
|
var evt = options.e;
|
||||||
|
|
||||||
// 检查鼠标是否按下左键并且没有按住Ctrl键(Windows系统)
|
// 检查鼠标是否按下左键并且没有按住Ctrl键(Windows系统)
|
||||||
@ -328,41 +393,13 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
x: pointer.x,
|
x: pointer.x,
|
||||||
y: pointer.y
|
y: pointer.y
|
||||||
};
|
};
|
||||||
currentFabric.startDraw = true
|
|
||||||
currentFabric.renderAll();
|
currentFabric.renderAll();
|
||||||
onMouseDown?.(currentFabric.selectionStart)
|
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) {
|
currentFabric.on('mouse:move', function(options) {
|
||||||
// 存在起始点,开始绘制
|
if (!currentFabric.startDraw) return;// 存在起始点,开始绘制
|
||||||
if (currentFabric.selectionStart && currentFabric.startDraw) {
|
|
||||||
|
|
||||||
// 阻止默认行为
|
// 阻止默认行为
|
||||||
options.e.preventDefault();
|
options.e.preventDefault();
|
||||||
var endPointer = options.pointer!!;
|
var endPointer = options.pointer!!;
|
||||||
@ -373,28 +410,45 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
...imageSize
|
...imageSize
|
||||||
}
|
}
|
||||||
)) return
|
)) return
|
||||||
|
|
||||||
// 更新选区大小
|
// 更新选区大小
|
||||||
currentFabric.selectionEnd = {
|
currentFabric.selectionEnd = {
|
||||||
x: endPointer?.x || 0,
|
x: endPointer?.x || 0,
|
||||||
y: endPointer?.y || 0
|
y: endPointer?.y || 0
|
||||||
};
|
};
|
||||||
|
currentFabric.clear()
|
||||||
// 清除之前的选择框
|
arrowLine = drawArrowLine({
|
||||||
currentFabric.clear();
|
startX: currentFabric?.selectionStart?.x as number,
|
||||||
let group = drawArrowLine(
|
startY: currentFabric.selectionStart?.y as number,
|
||||||
currentFabric.selectionStart.x,
|
endX: currentFabric.selectionEnd?.x as number,
|
||||||
currentFabric.selectionStart.y,
|
endY: currentFabric.selectionEnd?.y as number,
|
||||||
endPointer?.x || 0,
|
}, {
|
||||||
endPointer?.y || 0,
|
fill: '#09f',
|
||||||
lineConfig
|
...lineConfig
|
||||||
)
|
})
|
||||||
|
currentFabric.add(arrowLine)
|
||||||
currentFabric.add(group)
|
// @ts-ignore
|
||||||
}
|
currentShapeRef.current = arrowLine
|
||||||
|
// 停止绘制
|
||||||
currentFabric.renderAll();
|
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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@ -409,6 +463,7 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const { left: shapeLeft, width: shapeWidth = 0, top: shapeTop, scaleX: shapeScaleX = 0 } = currentShapeRef.current || {}
|
||||||
return (
|
return (
|
||||||
<div className={classNames(`${componentName}`)}>
|
<div className={classNames(`${componentName}`)}>
|
||||||
{/* 图片 */}
|
{/* 图片 */}
|
||||||
@ -416,49 +471,27 @@ const CropperImage = forwardRef<CropperImageRefProps, CropperImageProps>((props,
|
|||||||
ref={imageRef}
|
ref={imageRef}
|
||||||
className={classNames(`${componentName}_img`)}
|
className={classNames(`${componentName}_img`)}
|
||||||
/>
|
/>
|
||||||
{showToast && selectedItem && !isDrawing && (<>
|
|
||||||
<div
|
|
||||||
ref={alginContainerRef}
|
|
||||||
// @ts-ignore
|
|
||||||
style={Object.assign(
|
|
||||||
{
|
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
width: selectedItem.w,
|
|
||||||
height: selectedItem.h,
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none'
|
|
||||||
},
|
|
||||||
getTransforms({
|
|
||||||
translateX: selectedItem.x,
|
|
||||||
translateY: selectedItem.y,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<Align
|
|
||||||
ref={alignRef}
|
|
||||||
monitorWindowResize
|
|
||||||
align={{
|
|
||||||
points: ['bl', 'br'],
|
|
||||||
offset: [6, 0],
|
|
||||||
overflow: {
|
|
||||||
adjustX: true,
|
|
||||||
adjustY: true,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
target={function () {
|
|
||||||
return alginContainerRef.current;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{customToast?.({
|
|
||||||
selectedItem
|
|
||||||
})}
|
|
||||||
</Align>
|
|
||||||
</>)}
|
|
||||||
<canvas ref={canvasRef} className={classNames(`${componentName}_draw`)}></canvas>
|
<canvas ref={canvasRef} className={classNames(`${componentName}_draw`)}></canvas>
|
||||||
{children}
|
{children}
|
||||||
|
<div
|
||||||
|
// @ts-ignore
|
||||||
|
className={classNames(`${componentName}_toast`)}
|
||||||
|
style={{
|
||||||
|
display: (showToast && currentShapeRef.current && !isDrawing) ? 'block' : 'none',
|
||||||
|
left:currentShapeRef.current ? (shapeLeft || 0) + (shapeWidth * shapeScaleX || 0) : 0,
|
||||||
|
top: currentShapeRef.current ? shapeTop : 0,
|
||||||
|
...toastStyle
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{customToast?.({
|
||||||
|
...selectedItem,
|
||||||
|
}) || (
|
||||||
|
<div>
|
||||||
|
测试
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { isString } from "@zhst/func";
|
import { isString, omit } from "@zhst/func";
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { ILineOptions } from "fabric/fabric-impl";
|
import { ILineOptions } from "fabric/fabric-impl";
|
||||||
import { Rect } from '../ImageEditor/viewer/shape';
|
import { Rect } from '../ImageEditor/viewer/shape';
|
||||||
|
import { IArrowLinePosition, IRectData, IShape, ITransform, RectPro } from "./type";
|
||||||
|
|
||||||
export const getImage = (propImage: HTMLImageElement | string) => {
|
export const getImage = (propImage: HTMLImageElement | string) => {
|
||||||
return new Promise<HTMLElement>((resolve, reject) => {
|
return new Promise<HTMLElement>((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 鼠标对象实例
|
* @param o 鼠标对象实例
|
||||||
@ -28,8 +67,8 @@ export const getImage = (propImage: HTMLImageElement | string) => {
|
|||||||
* @returns Boolean
|
* @returns Boolean
|
||||||
*/
|
*/
|
||||||
export const checkMouseInRect = (o: fabric.IEvent<MouseEvent>, _fabricCanvas: fabric.Canvas) => {
|
export const checkMouseInRect = (o: fabric.IEvent<MouseEvent>, _fabricCanvas: fabric.Canvas) => {
|
||||||
var pointer = _fabricCanvas.getPointer(o.e);
|
let pointer = _fabricCanvas.getPointer(o.e);
|
||||||
var point = new fabric.Point(pointer.x, pointer.y);
|
let point = new fabric.Point(pointer.x, pointer.y);
|
||||||
let inRect = false
|
let inRect = false
|
||||||
_fabricCanvas.forEachObject(function(obj) {
|
_fabricCanvas.forEachObject(function(obj) {
|
||||||
if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) {
|
if (obj.containsPoint(point) || obj._findTargetCorner(pointer)) {
|
||||||
@ -47,7 +86,7 @@ export const checkMouseInRect = (o: fabric.IEvent<MouseEvent>, _fabricCanvas: fa
|
|||||||
* @param rect 原始画布宽高:w、h;缩放比例:scale;整体偏移的坐标:translateX,translateY
|
* @param rect 原始画布宽高:w、h;缩放比例:scale;整体偏移的坐标:translateX,translateY
|
||||||
* @returns boolean
|
* @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, 'x' | 'y'>, rect: { w: number; h: number, translateX?: number; translateY?: number, scale: number }) => {
|
||||||
const { w, h, translateX = 0, translateY = 0, scale = 1 } = rect;
|
const { w, h, translateX = 0, translateY = 0, scale = 1 } = rect;
|
||||||
const limitStartX = translateX
|
const limitStartX = translateX
|
||||||
const limitEndX = translateX + (w * scale)
|
const limitEndX = translateX + (w * scale)
|
||||||
@ -62,61 +101,16 @@ export const checkPointInRect = (point: { x: number; y: number;}, rect: { w: num
|
|||||||
return false
|
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: {
|
export const getImageDataByPosition = (position: IShape, opt: {
|
||||||
w: number;
|
|
||||||
h: number;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}, opt: {
|
|
||||||
canvas: HTMLCanvasElement
|
canvas: HTMLCanvasElement
|
||||||
fileType?: string
|
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 { fileType = 'image/jpg', canvas } = opt
|
||||||
const _canvas = canvas
|
const _canvas = canvas
|
||||||
const ctx = _canvas.getContext('2d')
|
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 newCanvas = document.createElement('canvas')
|
||||||
const newCtx = newCanvas.getContext('2d')
|
const newCtx = newCanvas.getContext('2d')
|
||||||
newCanvas.width = imageData?.width || 0
|
newCanvas.width = imageData?.width || 0
|
||||||
@ -124,3 +118,136 @@ export const getImageDataByPosition = (position: {
|
|||||||
newCtx?.putImageData(imageData!!, 0, 0)
|
newCtx?.putImageData(imageData!!, 0, 0)
|
||||||
return newCanvas.toDataURL(fileType)
|
return newCanvas.toDataURL(fileType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限制矩形绘制范围
|
||||||
|
* @param e fabric鼠标指针对象
|
||||||
|
* @param opt 配置
|
||||||
|
*/
|
||||||
|
export const limitPointMove = (
|
||||||
|
e: fabric.IEvent<MouseEvent>,
|
||||||
|
opt: {
|
||||||
|
limitRect: IArrowLinePosition,
|
||||||
|
containerSize: Pick<IRectData, 'width' | 'height'>
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
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<RectPro> & IShape & { endLeft?: number; endTop?: number }, config: Partial<fabric.Object>) => {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -23,6 +23,16 @@ export default () => {
|
|||||||
"y": 0.3203356,
|
"y": 0.3203356,
|
||||||
"w": 0.052037954,
|
"w": 0.052037954,
|
||||||
"h": 0.2664015
|
"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<any>()
|
const [selectedItem, setSelectedItem] = useState<any>()
|
||||||
@ -64,23 +74,23 @@ export default () => {
|
|||||||
url="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
url="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||||
onMouseUp={data => console.log('箭头绘制结束:', data)}
|
onMouseUp={data => console.log('箭头绘制结束:', data)}
|
||||||
onShapeSelected={(id, shapeData) => {
|
onShapeSelected={(id, shapeData) => {
|
||||||
console.log('矩形选择', id, shapeData)
|
console.log('shapeData', shapeData)
|
||||||
setImgUrl(shapeData?.imageRect as string)
|
setImgUrl(shapeData?.imageRect as string)
|
||||||
}}
|
}}
|
||||||
onCropStart={() => console.log('矩形开始绘制')}
|
onCropStart={() => console.log('矩形开始绘制')}
|
||||||
onCropEnd={(data) => {
|
onCropEnd={(data) => {
|
||||||
console.log('data', data)
|
console.log('绘制完成', data)
|
||||||
setSelectedItem({ x: data.left, y: data.top, h: data.height, w: data.width })
|
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}
|
selectedItem={selectedItem}
|
||||||
showToast={editAble}
|
showToast={editAble}
|
||||||
customToast={() => (
|
customToast={() => (
|
||||||
<div style={{ marginLeft: '12px', color: '#fff', fontSize: '24px' }}>自定义框</div>
|
<div style={{ marginLeft: '12px', color: '#fff', fontSize: '24px' }}><Button>自定义DOM</Button></div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Image src={imgUrl} />
|
{imgUrl && <Image src={imgUrl} />}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import CropperImage from "./CropperImage";
|
import CropperImage from "./CropperImage";
|
||||||
|
|
||||||
export type { CropperImageRefProps, CropperImageProps } from './CropperImage'
|
export type { CropperImageRefProps, CropperImageProps } from './CropperImage'
|
||||||
|
export * from './cropperImagehelper'
|
||||||
|
|
||||||
export default CropperImage
|
export default CropperImage
|
||||||
|
34
packages/meta/src/cropperImage/type.ts
Normal file
34
packages/meta/src/cropperImage/type.ts
Normal file
@ -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
|
||||||
|
}
|
@ -27,7 +27,10 @@
|
|||||||
"lib": ["dom", "es2017"],
|
"lib": ["dom", "es2017"],
|
||||||
"stripInternal": true,
|
"stripInternal": true,
|
||||||
"resolvePackageJsonExports": true,
|
"resolvePackageJsonExports": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true,
|
||||||
|
"types": [
|
||||||
|
// "@zhst/meta" 全局使用的工具包,不建议写到 npm 包中去
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": [".dumirc.ts", "src/**/*", "packages/**/*"],
|
"include": [".dumirc.ts", "src/**/*", "packages/**/*"],
|
||||||
"exclude": ["node_modules", "lib", "es", ".dumi"]
|
"exclude": ["node_modules", "lib", "es", ".dumi"]
|
||||||
|
Loading…
Reference in New Issue
Block a user