diff --git a/src/designer/Viewport.ts b/src/designer/Viewport.ts index 6aa4395..c50d8bc 100644 --- a/src/designer/Viewport.ts +++ b/src/designer/Viewport.ts @@ -2,13 +2,14 @@ import _ from 'lodash' import * as THREE from 'three' import { AxesHelper, GridHelper, OrthographicCamera, Raycaster, Scene, WebGLRenderer } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' +import EsDragControls from './model2DEditor/EsDragControls' import Stats from 'three/examples/jsm/libs/stats.module' import type WorldModel from '@/designer/WorldModel.ts' import $ from 'jquery' import { reactive, watch } from 'vue' import MouseMoveInspect from '@/designer/model2DEditor/tools/MouseMoveInspect.ts' import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts' -import RulerInspect from '@/designer/model2DEditor/tools/RulerInspect.ts' +import RulerTool from '@/designer/model2DEditor/tools/RulerTool.ts' /** * 编辑器对象 @@ -25,9 +26,10 @@ export default class Viewport { controls: OrbitControls worldModel: WorldModel raycaster: Raycaster + dragControl: EsDragControls animationFrameId: any = null - cursorTool: ITool | null = null + currentTool: ITool | null = null tools: ITool[] = [ new MouseMoveInspect() ] @@ -62,6 +64,10 @@ export default class Viewport { this.worldModel = worldModel } + dispatchSignal(signal: string, data?: any) { + console.log('signal', signal, data) + } + /** * 初始化 THREE 渲染器 */ @@ -141,10 +147,8 @@ export default class Viewport { metalness: 0.9, roughness: 0.1 }) - const cube = new THREE.Mesh(geometry, material) scene.add(cube) - this.camera.position.z = 5 this.animate() @@ -159,23 +163,26 @@ export default class Viewport { if (!this.state.isReady) { return } - if (this.cursorTool) { - this.cursorTool.destory() - this.cursorTool = null + if (this.currentTool) { + this.currentTool.destory() + this.currentTool = null } if (newVal === 'normal' || !newVal) { + this.dragControl.dragControls.enabled = true return } - if (newVal === 'Ruler') { + if (newVal === 'Ruler' || newVal === 'RulerArea' || newVal === 'RulerAngle') { // 选择标尺工具 - this.cursorTool = new RulerInspect() + this.currentTool = new RulerTool() + this.dragControl.dragControls.enabled = false + } else { system.showErrorDialog(`当前鼠标模式 ${newVal} 不支持`) } - if (this.cursorTool) { - this.cursorTool.init(this) + if (this.currentTool) { + this.currentTool.init(this) } })) @@ -193,6 +200,9 @@ export default class Viewport { tool.init(this) } + // 注册拖拽组件 + this.dragControl = new EsDragControls(this) + this.state.isReady = true } @@ -383,7 +393,15 @@ export default class Viewport { getGridHelpAtPosition(param: { x: number; z: number }) { const pickPosition = new THREE.Vector2(param.x, param.z) this.raycaster.setFromCamera(pickPosition, this.camera) - return this.raycaster.intersectObject(this.gridHelper) + return this.raycaster.intersectObject(this.gridHelper, false) + } + + getIntersects(point: THREE.Vector2) { + const mouse = new THREE.Vector2() + mouse.set((point.x * 2) - 1, -(point.y * 2) + 1) + this.raycaster.setFromCamera(mouse, this.camera) + + return this.raycaster.intersectObjects([this.gridHelper], false) } } @@ -401,7 +419,7 @@ export interface ViewportState { /** * 鼠标模式 */ - cursorMode: 'normal' | 'ALink' | 'SLink' | 'PointCallback' | 'PointAdd' | 'LinkAdd' | 'LinkAdd2' | 'Ruler' | 'selectByRec', + cursorMode: CursorMode, /** * 相机状态 diff --git a/src/designer/model2DEditor/DragControls.js b/src/designer/model2DEditor/DragControls.js new file mode 100644 index 0000000..4aa6886 --- /dev/null +++ b/src/designer/model2DEditor/DragControls.js @@ -0,0 +1,211 @@ +import { + EventDispatcher, + Matrix4, + Plane, + Raycaster, + Vector2, + Vector3 +} from 'three'; + +const _plane = new Plane(); +const _raycaster = new Raycaster(); + +const _pointer = new Vector2(); +const _offset = new Vector3(); +const _intersection = new Vector3(); +const _worldPosition = new Vector3(); +const _inverseMatrix = new Matrix4(); + +class DragControls extends EventDispatcher { + constructor( _objects, _camera, _domElement ) { + super(); + + _domElement.style.touchAction = 'none'; // disable touch scroll + + let _selected = null, _hovered = null; + + const _intersections = []; + + // + + let isMove = false; + + const scope = this; + + function activate() { + _domElement.addEventListener( 'pointermove', onPointerMove ); + _domElement.addEventListener( 'pointerdown', onPointerDown ); + _domElement.addEventListener( 'pointerup', onPointerCancel ); + _domElement.addEventListener( 'pointerleave', onPointerCancel ); + } + + function deactivate() { + _domElement.removeEventListener( 'pointermove', onPointerMove ); + _domElement.removeEventListener( 'pointerdown', onPointerDown ); + _domElement.removeEventListener( 'pointerup', onPointerCancel ); + _domElement.removeEventListener( 'pointerleave', onPointerCancel ); + + _domElement.style.cursor = ''; + } + + function dispose() { + deactivate(); + } + + function setObjects( objects ) { + _objects = objects; + } + + function getObjects() { + return _objects; + } + + function getRaycaster() { + return _raycaster; + } + + function onPointerMove( event ) { + + if ( !scope.enabled || !scope.enabledMove) return; + + isMove = true; + + updatePointer( event ); + + _raycaster.setFromCamera( _pointer, _camera ); + + if ( _selected ) { + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) ); + + } + + scope.dispatchEvent( { type: 'drag', object: _selected } ); + + return; + + } + + // hover support + + if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) { + + _intersections.length = 0; + + _raycaster.setFromCamera( _pointer, _camera ); + _raycaster.intersectObjects( _objects, true, _intersections ); + + if ( _intersections.length > 0 ) { + + const object = _intersections[ 0 ].object; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) ); + + if ( _hovered !== object && _hovered !== null ) { + + scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + _domElement.style.cursor = 'auto'; + _hovered = null; + + } + + if ( _hovered !== object ) { + + scope.dispatchEvent( { type: 'hoveron', object: object } ); + + _domElement.style.cursor = 'pointer'; + _hovered = object; + + } + + } else { + + if ( _hovered !== null ) { + + scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + _domElement.style.cursor = 'auto'; + _hovered = null; + + } + + } + + } + + } + + function onPointerDown( event ) { + if (scope.enabled === false) return; + + updatePointer(event); + + _intersections.length = 0; + + _raycaster.setFromCamera( _pointer, _camera ); + let objects = _objects; + + _raycaster.intersectObjects( objects, true, _intersections ); + + if (_intersections.length > 0) { + _selected = (scope.transformGroup === true) ? _objects[ 0 ] : _intersections[0].object; + + if(scope.enabledMove) { + _plane.setFromNormalAndCoplanarPoint(_camera.getWorldDirection(_plane.normal), _worldPosition.setFromMatrixPosition(_selected.matrixWorld)); + if (_raycaster.ray.intersectPlane(_plane, _intersection)) { + _inverseMatrix.copy(_selected.parent.matrixWorld).invert(); + _offset.copy(_intersection).sub(_worldPosition.setFromMatrixPosition(_selected.matrixWorld)); + } + + _domElement.style.cursor = 'move'; + } + scope.dispatchEvent( { type: 'dragstart', object: _selected,e:event } ); + } + + isMove = false; + } + + function onPointerCancel(event) { + if ( scope.enabled === false ) return; + + if ( _selected ) { + scope.dispatchEvent( { type: 'dragend', object: _selected,e:event } ); + _selected = null; + }else if(!isMove){ + // 添加点击空白处的事件 + scope.dispatchEvent( { type: 'clickblank',e:event } ); + } + + _domElement.style.cursor = _hovered ? 'pointer' : 'auto'; + } + + function updatePointer( event ) { + const rect = _domElement.getBoundingClientRect(); + + _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; + _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + } + + activate(); + + // API + + this.enabled = true; + this.enabledMove = true; + this.transformGroup = false; + + this.activate = activate; + this.deactivate = deactivate; + this.dispose = dispose; + this.setObjects = setObjects; + this.getObjects = getObjects; + this.getRaycaster = getRaycaster; + + } + +} + +export { DragControls }; diff --git a/src/designer/model2DEditor/EsDragControls.ts b/src/designer/model2DEditor/EsDragControls.ts new file mode 100644 index 0000000..cf0e12d --- /dev/null +++ b/src/designer/model2DEditor/EsDragControls.ts @@ -0,0 +1,129 @@ +import { DragControls } from './DragControls.js' +import * as THREE from 'three' +import type Viewport from '@/designer/Viewport.ts' + +// dragControls 绑定函数 +let dragStartFn, dragFn, dragEndFn, clickblankFn + +export default class EsDragControls { + protected _dragObjects: THREE.Object3D[] = [] // 拖拽对象 + dragControls: any + private onDownPosition: { x: number; y: number } = { x: -1, y: -1 } + + viewport: Viewport + isDragging = false + + constructor(viewport) { + this.viewport = viewport + + // 物体拖拽控制器 + this.dragControls = new DragControls(this._dragObjects, viewport.camera, viewport.renderer.domElement) + this.dragControls.deactivate() // 默认禁用 + dragStartFn = this.dragControlsStart.bind(this) + this.dragControls.addEventListener('dragstart', dragStartFn) + dragFn = this.drag.bind(this) + this.dragControls.addEventListener('drag', dragFn) + dragEndFn = this.dragControlsEnd.bind(this) + this.dragControls.addEventListener('dragend', dragEndFn) + // 点击可拖拽物体之外 + clickblankFn = this.clickblank.bind(this) + this.dragControls.addEventListener('clickblank', clickblankFn) + } + + set domElement(element: HTMLElement) { + this.dragControls.setDomElement(element) + } + + setDragObjects(objects: THREE.Object3D[], type: 'eq' | 'push' | 'remove' = 'eq') { + // 当前拖拽对象为空时加入对象需激活控制器 + if (this._dragObjects.length === 0) { + if (objects.length > 0) { + this.dragControls.activate() + } + + this._dragObjects = objects + } else { + // 当前拖拽对象不为空时 + if (type === 'eq') { + // 是清空拖拽对象的设置,则禁用控制器 + if (objects.length === 0) { + this.dragControls.deactivate() + } + + this._dragObjects = objects + } else if (type === 'push') { + this._dragObjects.push(...objects) + } else if (type === 'remove') { + this._dragObjects = this._dragObjects.filter((item) => !objects.includes(item)) + } + } + + this.dragControls.setObjects(this._dragObjects) + } + + // 拖拽开始 + dragControlsStart(e) { + // 右键拖拽不响应 + if (e.e.button === 2 || !e.object.userData.type || !e.object.visible) return + + e.e.preventDefault() + + // 拖拽时禁用其他控制器 + this.viewport.controls.enabled = false + + this.isDragging = true + + // 记录拖拽按下的位置和对象 + this.onDownPosition = { x: e.e.clientX, y: e.e.clientY } + + // switch (e.object.userData.type) { + // case 'measure-marker': + // this.viewport.modules.measure.redraw(e.object) + // break + // } + } + + // 拖拽中 + drag(e) { + this.viewport.dispatchSignal('objectChanged', e.object) + } + + // 拖拽结束 + dragControlsEnd(e) { + // 右键拖拽不响应 + if (e.e.button === 2 || !e.object.visible) return + + // 拖拽结束启用其他控制器 + this.viewport.controls.enabled = true + + this.isDragging = false + + if (!e.object.userData.type) return + + // 判断位置是否有变化,没有变化则为点击 + if (this.onDownPosition.x === e.e.clientX && this.onDownPosition.y === e.e.clientY) { + if (e.object.userData.onClick) { + e.object.userData.onClick(e) + } + } + + // switch (e.object.userData.type) { + // case 'measure-marker': + // this.viewport.modules.measure.redrawComplete() + // break + // } + } + + // 点击可拖拽物体之外 + clickblank(e) { + if (e.e.button === 2) return + } + + dispose() { + this._dragObjects = [] + + this.dragControls.removeEventListener('dragstart', dragStartFn) + this.dragControls.removeEventListener('dragend', dragEndFn) + this.dragControls.dispose() + } +} \ No newline at end of file diff --git a/src/designer/model2DEditor/Model2DEditorJs.js b/src/designer/model2DEditor/Model2DEditorJs.js index c4f6cfe..a80a46e 100644 --- a/src/designer/model2DEditor/Model2DEditorJs.js +++ b/src/designer/model2DEditor/Model2DEditorJs.js @@ -15,11 +15,11 @@ export default defineComponent({ } }, mounted() { - window['editor'] = this + window['viewport'] = this }, beforeMount() { this.initByFloor('') - delete window['editor'] + delete window['viewport'] }, methods: { renderIcon, diff --git a/src/designer/model2DEditor/tools/RulerInspect.ts b/src/designer/model2DEditor/tools/RulerInspect.ts deleted file mode 100644 index 3ec0792..0000000 --- a/src/designer/model2DEditor/tools/RulerInspect.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts' -import Viewport from '@/designer/Viewport.ts' - -export default class RulerInspect implements ITool { - viewport: Viewport - - mouseMove(event: MouseEvent) { - const viewer = document.querySelector('.viewer') as HTMLElement - const rect = viewer.getBoundingClientRect() - - const mouseX = event.clientX - rect.left - const mouseY = event.clientY - rect.top - - console.log(`Mouse Position: (${mouseX}, ${mouseY})`) - } - - init(viewport: Viewport) { - this.viewport = viewport - const viewerDom = this.viewport.viewerDom - - system.msg('进入鼠标测距模式') - } - - destory(): void { - const viewerDom = this.viewport.viewerDom - viewerDom.removeEventListener('mousemove', this.mouseMove) - system.msg('退出鼠标测距模式') - } -} \ No newline at end of file diff --git a/src/designer/model2DEditor/tools/RulerTool.ts b/src/designer/model2DEditor/tools/RulerTool.ts new file mode 100644 index 0000000..9ee3e95 --- /dev/null +++ b/src/designer/model2DEditor/tools/RulerTool.ts @@ -0,0 +1,318 @@ +import _ from 'lodash' +import PointPng from '@/assets/images/logo.png' +import * as THREE from 'three' +import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts' +import Viewport from '@/designer/Viewport.ts' +import { numberToString, getUnitString } from '@/utils/webutils' +import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' +import { Vector3 } from 'three' + +let pdFn, pmFn, puFn, kdFn + +export default class RulerTool implements ITool { + static OBJ_NAME = 'object_for_measure' + static LABEL_NAME = 'label_for_measure' + static MAX_DISTANCE = 500 //当相交物体的距离太远时,忽略它 + + viewport: Viewport + // 当前测绘内容组 + group: THREE.Group + isCompleted = false + mouseMoved = false + canvas: HTMLCanvasElement + + // 用户在测量时绘制的线的当前实例 + protected polyline?: THREE.Line + + // 用于存储临时点 + protected tempPointMarker?: THREE.Mesh + + // 用于存储临时线条,用于在鼠标移动时绘制线条/区域/角度 + protected tempLine?: THREE.Line + + // 用于在鼠标移动时存储临时标签,只有测量距离时才有 + protected tempLabel?: CSS2DObject + + // 存储点 + protected pointArray: THREE.Vector3[] = [] + + //保存上次点击时间,以便检测双击事件 + protected lastClickTime: number = 0 + + + static LINE_MATERIAL = new THREE.LineBasicMaterial({ + color: 0xE63C17, + linewidth: 2, + opacity: 0.9, + transparent: true, + side: THREE.DoubleSide, + depthWrite: false, + depthTest: false + }) + + init(viewport: Viewport) { + this.viewport = viewport + const viewerDom = this.viewport.viewerDom + this.canvas = $(viewerDom).children('canvas')[0] as HTMLCanvasElement + + pdFn = this.mousedown.bind(this) + this.canvas.addEventListener('pointerdown', pdFn) + pmFn = this.mousemove.bind(this) + this.canvas.addEventListener('pointermove', pmFn) + puFn = this.mouseup.bind(this) + this.canvas.addEventListener('pointerup', puFn) + + // 初始化group + this.group = new THREE.Group() + this.group.name = `${RulerTool.OBJ_NAME}_group` + this.group.userData = { + mode: this.viewport.state.cursorMode + } + this.viewport.scene.add(this.group) + + // 测量距离、面积和角度需要折线 + this.polyline = this.createLine() + this.group.add(this.polyline) + this.isCompleted = false + this.viewport.viewerDom.style.cursor = 'crosshair' + + // 当次绘制点 + this.pointArray = [] + + // 测量距离、面积和角度需要折线 + this.polyline = this.createLine() + this.group.add(this.polyline) + + this.viewport.viewerDom.style.cursor = 'crosshair' + + system.msg('进入鼠标测距模式') + } + + + destory(): void { + system.msg('退出鼠标测距模式') + + const viewerDom = this.viewport.viewerDom + + this.isCompleted = true + viewerDom.style.cursor = '' + + this.canvas.removeEventListener('pointerdown', pdFn) + pdFn = undefined + this.canvas.removeEventListener('pointermove', pmFn) + pmFn = undefined + this.canvas.removeEventListener('pointerup', puFn) + puFn = undefined + + this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker) + this.tempLine && this.viewport.scene.remove(this.tempLine) + this.tempLabel && this.viewport.scene.remove(this.tempLabel) + this.tempPointMarker = undefined + this.tempLine = undefined + this.tempLabel = undefined + } + + mousedown = () => { + this.mouseMoved = false + } + + + // 鼠标移动,创建对应的临时点与线 + mousemove = (e: MouseEvent) => { + if (this.isCompleted) return + + this.mouseMoved = true + + const point = this.getClosestIntersection(e) + if (!point) { + return + } + + // 在鼠标移动时绘制临时点 + if (this.tempPointMarker) { + this.tempPointMarker.position.set(point.x, point.y, point.z) + } else { + this.tempPointMarker = this.createPointMarker(point) + this.viewport.scene.add(this.tempPointMarker) + } + + // 移动时绘制临时线 + if (this.pointArray.length > 0) { + const p0 = this.pointArray[this.pointArray.length - 1] // 获取最后一个点 + const line = this.tempLine || this.createLine() + const geom = line.geometry + const startPoint = this.pointArray[0] + const lastPoint = this.pointArray[this.pointArray.length - 1] + if (this.viewport.state.cursorMode === 'RulerArea') { + geom.setFromPoints([lastPoint, point, startPoint]) + } else { + geom.setFromPoints([lastPoint, point]) + } + if (this.viewport.state.cursorMode === 'Ruler') { + const dist = p0.distanceTo(point) + const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}` + const position = new THREE.Vector3((point.x + p0.x) / 2, (point.y + p0.y) / 2, (point.z + p0.z) / 2) + this.addOrUpdateTempLabel(label, position) + } + // tempLine 只需添加到场景一次 + if (!this.tempLine) { + this.viewport.scene.add(line) + this.tempLine = line + } + } + + // this.viewport.dispatchSignal('sceneGraphChanged') + } + + mouseup = (e: MouseEvent) => { + // 如果mouseMoved是true,那么它可能在移动,而不是点击 + if (!this.mouseMoved) { + // 右键点击表示完成绘图操作 + if (e.button === 2) { + this.viewport.state.cursorMode = 'normal' + + } else if (e.button === 0) { // 左键点击表示添加点 + this.onMouseClicked(e) + } + } + } + + onMouseClicked = (e: MouseEvent) => { + if (this.isCompleted) { + return + } + + const point = this.getClosestIntersection(e) + if (!point) { + return + } + + // 双击触发两次点击事件,我们需要避免这里的第二次点击 + const now = Date.now() + if (this.lastClickTime && (now - this.lastClickTime < 100)) return + + this.lastClickTime = now + + this.pointArray.push(point) + console.log('pointArray', this.pointArray) + + const count = this.pointArray.length + const marker = this.createPointMarker(point) + marker.userData.point = point + marker.userData.pointIndex = count - 1 + this.group.add(marker) + // 把点加入拖拽控制器 + this.viewport.dragControl.setDragObjects([marker], 'push') + + if (this.polyline) { + this.polyline.geometry.setFromPoints(this.pointArray) + if (this.tempLabel && count > 1) { + const p0 = this.pointArray[count - 2] + this.tempLabel.position.set((p0.x + point.x) / 2, (p0.y + point.y) / 2, (p0.z + point.z) / 2) + this.group.add(this.tempLabel) + + // 创建距离测量线时,此处的 临时label 将作为正式的使用,不在this.clearTemp()中清除,故置为undefined + this.tempLabel = undefined + } + } + + // this.viewport.dispatchSignal('sceneGraphChanged') + } + + /** + * Creates THREE.Line + */ + private createLine(): THREE.Line { + const geom = new THREE.BufferGeometry() + const obj = new THREE.Line(geom, RulerTool.LINE_MATERIAL) + obj.frustumCulled = false + obj.name = RulerTool.OBJ_NAME + obj.userData = { + type: 'line' + } + return obj + } + + /** + * 创建点标记 + */ + createPointMarker(position?: THREE.Vector3): THREE.Mesh { + const p = position + const scale = 0.25 + + const tt = new THREE.BoxGeometry(1, 1, 1) + const t2 = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 }) + const obj = new THREE.Mesh(tt, t2) + obj.scale.set(scale, 0.1, scale) + if (p) { + obj.position.set(p.x, p.y, p.z) + } + + obj.name = RulerTool.OBJ_NAME + + obj.userData = { + mode: this.viewport.state.cursorMode, + type: 'measure-marker' + } + return obj + } + + + /** + * 获取按下对应三维位置 + */ + getClosestIntersection(e: MouseEvent) { + const _point = new THREE.Vector2() + _point.x = e.offsetX / this.viewport.renderer.domElement.offsetWidth + _point.y = e.offsetY / this.viewport.renderer.domElement.offsetHeight + + const intersects = this.viewport.getIntersects(_point) + if (intersects && intersects.length > 2) { + if (intersects.length > 0 && intersects[0].distance < RulerTool.MAX_DISTANCE) { + return new Vector3( + intersects[0].point.x, + 0.1, + intersects[1].point.z + ) + } + } + return null + } + + /** + * 添加或更新临时标签和位置 + */ + addOrUpdateTempLabel(label: string, position: THREE.Vector3) { + if (!this.tempLabel) { + this.tempLabel = this.createLabel(label) + this.viewport.scene.add(this.tempLabel) + } + this.tempLabel.position.set(position.x, position.y, position.z) + this.tempLabel.element.innerHTML = label + } + + + /** + * 创建标签 + */ + createLabel(text: string): CSS2DObject { + const div = document.createElement('div') + div.className = 'css2dObjectLabel' + div.innerHTML = text + div.style.padding = '5px 8px' + div.style.color = '#fff' + div.style.fontSize = '14px' + div.style.position = 'absolute' + div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)' + div.style.borderRadius = '12px' + div.style.top = '0px' + div.style.left = '0px' + // div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件 + const obj = new CSS2DObject(div) + obj.name = RulerTool.LABEL_NAME + obj.userData = { + type: 'label' + } + return obj + } +} \ No newline at end of file diff --git a/src/types/Types.d.ts b/src/types/Types.d.ts new file mode 100644 index 0000000..017a840 --- /dev/null +++ b/src/types/Types.d.ts @@ -0,0 +1,12 @@ +type CursorMode = + 'normal' + | 'ALink' + | 'SLink' + | 'PointCallback' + | 'PointAdd' + | 'LinkAdd' + | 'LinkAdd2' + | 'Ruler' + | 'RulerArea' + | 'RulerAngle' + | 'selectByRec' diff --git a/src/utils/webutils.ts b/src/utils/webutils.ts index fb5c7ad..785a044 100644 --- a/src/utils/webutils.ts +++ b/src/utils/webutils.ts @@ -5,6 +5,33 @@ import { ElIcon } from 'element-plus' import * as FaIcon from '@vicons/fa' import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +/** + * 将数字转换为具有适当分数数字的字符串 + */ +export function numberToString(num: number) { + if (num < 0.0001) { + return num.toString(); + } + let fractionDigits = 2; + if (num < 0.01) { + fractionDigits = 4; + } else if (num < 0.1) { + fractionDigits = 3; + } + return num.toFixed(fractionDigits); +} + +/** + * 获取距离、面积或角度的单位字符串 + */ +export function getUnitString(mode: CursorMode): string { + if (mode === 'Ruler') return "m"; + if (mode === 'RulerArea') return "m²"; + if (mode === 'RulerAngle') return "°"; + return ""; +} + export function normalizeShortKey(key: string): string { // 如果 menu.tip 中包含 ctrl/shift/alt/key- 等修饰键,并且 click 事件存在,则需要使用 hotkeys 绑定 if (key && /ctrl|shift|alt|key-/i.test(_.toLower(key))) {