@zhangqingcq/vgce 0.1.14 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhangqingcq/vgce",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Vector graphics configure editor. svg组态编辑器。基于vue3.3+ts+element-plus+vite",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -109,7 +109,39 @@ body {
109
109
 
110
110
  .tree-v {
111
111
  --el-color-primary-light-9: @listActiveColor;
112
+
112
113
  &.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
113
114
  color: #fff;
114
115
  }
115
116
  }
117
+
118
+ .coverLayer {
119
+ position: absolute;
120
+ left: 0;
121
+ top: 0;
122
+ bottom: 0;
123
+ right: 0;
124
+ }
125
+
126
+ .can-not-select {
127
+ user-select: none;
128
+ }
129
+
130
+ .canvasInfoTxt {
131
+ color: #bbb;
132
+ position: absolute;
133
+ left: 20px;
134
+ bottom: 10px;
135
+ font-size: 12px;
136
+
137
+ .icoP {
138
+ margin-left: 10px;
139
+ position: relative;
140
+ top: 2px;
141
+ cursor: pointer;
142
+
143
+ &:hover {
144
+ color: @activeIconOut;
145
+ }
146
+ }
147
+ }
@@ -18,7 +18,8 @@
18
18
  randomString,
19
19
  resetHandlePointOld,
20
20
  setSvgActualInfo,
21
- stopEvent
21
+ getZoomPosition,
22
+ myFixed
22
23
  } from '@/utils'
23
24
  import {
24
25
  calculateBottom,
@@ -494,10 +495,10 @@
494
495
  //算出缩放倍数
495
496
  if (globalStore.handle_svg_info && new_length.width > 0 && new_length.height > 0) {
496
497
  const scale_x = !new_length.is_old_width
497
- ? parseFloat((new_length.width / globalStore.handle_svg_info.info.actual_bound.width).toFixed(3))
498
+ ? myFixed(new_length.width / globalStore.handle_svg_info.info.actual_bound.width, 3)
498
499
  : 1
499
500
  const scale_y = !new_length.is_old_height
500
- ? parseFloat((new_length.height / globalStore.handle_svg_info.info.actual_bound.height).toFixed(3))
501
+ ? myFixed(new_length.height / globalStore.handle_svg_info.info.actual_bound.height, 3)
501
502
  : 1
502
503
  const newCenterPoint = getCenterPoint(curPosition, globalStore.scale_info.symmetric_point)
503
504
  if (
@@ -549,8 +550,9 @@
549
550
  globalStore.handle_svg_info.info.x
550
551
  ) /
551
552
  (Math.PI / 180)
552
- globalStore.handle_svg_info.info.rotate = parseFloat(
553
- (globalStore.rotate_info.angle + rotateDegreeAfter - rotateDegreeBefore).toFixed(2)
553
+ globalStore.handle_svg_info.info.rotate = myFixed(
554
+ globalStore.rotate_info.angle + rotateDegreeAfter - rotateDegreeBefore,
555
+ 2
554
556
  )
555
557
  } else if (globalStore.intention === EGlobalStoreIntention.Connection && globalStore.handle_svg_info) {
556
558
  //鼠标移动的实时位置(相对于连线起始点,只在创建第一个点时记录了鼠标原始位置)
@@ -766,9 +768,11 @@
766
768
  function onMousewheel(e: any) {
767
769
  if (e?.wheelDelta) {
768
770
  if (e.wheelDelta > 0) {
769
- configStore.svg.scale = parseFloat((configStore.svg.scale + 0.1).toFixed(1))
770
- } else {
771
- configStore.svg.scale = parseFloat((configStore.svg.scale - 0.1).toFixed(1))
771
+ configStore.svg.scale = myFixed(configStore.svg.scale + 0.1, 1)
772
+ getZoomPosition(e, configStore.svg.scale, svgEditLayoutStore.center_offset, true)
773
+ } else if (configStore.svg.scale > 0.1) {
774
+ configStore.svg.scale = myFixed(configStore.svg.scale - 0.1, 1)
775
+ getZoomPosition(e, configStore.svg.scale, svgEditLayoutStore.center_offset, false)
772
776
  }
773
777
  }
774
778
  }
@@ -817,10 +821,10 @@
817
821
  scale_y: number
818
822
  ) => {
819
823
  return {
820
- x: parseFloat((actual_bound.x - (actual_bound.width / 2) * scale_x + actual_bound.width / 2).toFixed(1)),
821
- y: parseFloat((actual_bound.y - (actual_bound.height / 2) * scale_y + actual_bound.height / 2).toFixed(1)),
822
- width: parseFloat((actual_bound.width * scale_x).toFixed(1)),
823
- height: parseFloat((actual_bound.height * scale_y).toFixed(1))
824
+ x: myFixed(actual_bound.x - (actual_bound.width / 2) * scale_x + actual_bound.width / 2, 1),
825
+ y: myFixed(actual_bound.y - (actual_bound.height / 2) * scale_y + actual_bound.height / 2, 1),
826
+ width: myFixed(actual_bound.width * scale_x, 1),
827
+ height: myFixed(actual_bound.height * scale_y, 1)
824
828
  }
825
829
  }
826
830
  const onHandleKeyDown = (e: KeyboardEvent) => {
@@ -959,7 +963,7 @@
959
963
  @mousewheel="onMousewheel"
960
964
  >
961
965
  <slot name="background" />
962
- <div style="position: absolute; left: 0; top: 0; bottom: 0; right: 0">
966
+ <div class="coverLayer">
963
967
  <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
964
968
  <defs>
965
969
  <pattern id="pattern_grid" patternUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
@@ -980,7 +984,6 @@
980
984
  :key="item.id"
981
985
  :transform="`translate(${item.x},${item.y})rotate(0)scale(1)`"
982
986
  v-show="item.display"
983
- @mousewheel="stopEvent"
984
987
  >
985
988
  <g :class="`${getCommonClass(item)}`">
986
989
  <g
@@ -313,7 +313,8 @@
313
313
  <el-form-item label="行为" size="small">
314
314
  <el-select v-model="item.action" @change="addEventList($event, item)">
315
315
  <el-option value="ChangeAttr" label="改变属性" />
316
- <el-option value="JavaScript" label="执行JavaScript" />
316
+ <el-option value="JavaScript" label="执行 JavaScript" />
317
+ <el-option value="Emit" label="emit 事件" />
317
318
  </el-select>
318
319
  </el-form-item>
319
320
 
@@ -1,11 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import { pinia } from '@/hooks'
3
+ import { ElIcon } from 'element-plus'
4
+ import { Aim } from '@element-plus/icons-vue'
3
5
  import { useGlobalStore } from '@/stores/global'
4
6
  import { EGlobalStoreIntention, EMouseInfoState } from '@/stores/global/types'
5
7
  import type { IDoneJson } from '@/stores/global/types'
6
8
  import {
7
9
  componentsRegister,
8
10
  getCommonClass,
11
+ getZoomPosition,
12
+ myFixed,
9
13
  preventDefault,
10
14
  prosToVBind,
11
15
  setArrItemByID,
@@ -25,11 +29,12 @@
25
29
 
26
30
  setEditorLoadTime()
27
31
 
28
- const emit = defineEmits(['onMessage'])
32
+ const emit = defineEmits(['onMessage', 'onEvent'])
29
33
  const props = withDefaults(
30
- defineProps<{ vueComp?: Record<string, any>; data?: IDataModel; canvasDrag?: boolean }>(),
34
+ defineProps<{ vueComp?: Record<string, any>; data?: IDataModel; canvasDrag?: boolean; showCanvasInfo?: boolean }>(),
31
35
  {
32
- canvasDrag: true
36
+ canvasDrag: true,
37
+ showCanvasInfo: true
33
38
  }
34
39
  )
35
40
  const globalStore = useGlobalStore(pinia)
@@ -72,14 +77,18 @@
72
77
  return
73
78
  }
74
79
  const { clientX, clientY } = e
75
- globalStore.mouse_info.new_position_x =
76
- globalStore.mouse_info.now_position_x + clientX - globalStore.mouse_info.position_x
77
- globalStore.mouse_info.new_position_y =
78
- globalStore.mouse_info.now_position_y + clientY - globalStore.mouse_info.position_y
80
+ globalStore.mouse_info.new_position_x = Math.round(clientX / preview_data.config.svg.scale)
81
+ globalStore.mouse_info.new_position_y = Math.round(clientY / preview_data.config.svg.scale)
82
+ const x = Math.round(
83
+ globalStore.mouse_info.new_position_x - globalStore.mouse_info.position_x + globalStore.mouse_info.now_position_x
84
+ )
85
+ const y = Math.round(
86
+ globalStore.mouse_info.new_position_y - globalStore.mouse_info.position_y + globalStore.mouse_info.now_position_y
87
+ )
79
88
  if (globalStore.intention == EGlobalStoreIntention.MoveCanvas) {
80
89
  //移动画布
81
- preview_data.layout_center.x = globalStore.mouse_info.new_position_x
82
- preview_data.layout_center.y = globalStore.mouse_info.new_position_y
90
+ preview_data.layout_center.x = x
91
+ preview_data.layout_center.y = y
83
92
  }
84
93
  }
85
94
  const onCanvasMouseUp = () => {
@@ -106,21 +115,23 @@
106
115
  globalStore.intention = EGlobalStoreIntention.MoveCanvas
107
116
  globalStore.mouse_info = {
108
117
  state: EMouseInfoState.Down,
109
- position_x: clientX,
110
- position_y: clientY,
118
+ position_x: Math.round(clientX / preview_data.config.svg.scale),
119
+ position_y: Math.round(clientY / preview_data.config.svg.scale),
111
120
  now_position_x: preview_data.layout_center.x,
112
121
  now_position_y: preview_data.layout_center.y,
113
- new_position_x: preview_data.layout_center.x,
114
- new_position_y: preview_data.layout_center.y
122
+ new_position_x: 0,
123
+ new_position_y: 0
115
124
  }
116
125
  }
117
126
 
118
127
  function onMousewheel(e: any) {
119
128
  if (e?.wheelDelta) {
120
129
  if (e.wheelDelta > 0) {
121
- preview_data.config.svg.scale = parseFloat((preview_data.config.svg.scale + 0.1).toFixed(1))
130
+ preview_data.config.svg.scale = myFixed(preview_data.config.svg.scale + 0.1, 1)
131
+ getZoomPosition(e, preview_data.config.svg.scale, preview_data.layout_center, true)
122
132
  } else {
123
- preview_data.config.svg.scale = parseFloat((preview_data.config.svg.scale - 0.1).toFixed(1))
133
+ preview_data.config.svg.scale = myFixed(preview_data.config.svg.scale - 0.1, 1)
134
+ getZoomPosition(e, preview_data.config.svg.scale, preview_data.layout_center, false)
124
135
  }
125
136
  }
126
137
  }
@@ -156,7 +167,6 @@
156
167
  return { cursor: t ? 'pointer' : 'default' }
157
168
  }
158
169
  const eventHandle = (root: IDoneJson, type: EEventType) => {
159
- console.log(root.events, type)
160
170
  if (root.events?.length > 0) {
161
171
  for (let e of root.events) {
162
172
  if (e.type === type) {
@@ -204,6 +214,28 @@
204
214
  } else if (e.action === EEventAction.JavaScript) {
205
215
  const t = new Function(e.scripts)
206
216
  t()
217
+ } else if (e.action === EEventAction.Emit) {
218
+ const _k: Array<keyof IDoneJson> = [
219
+ 'id',
220
+ 'name',
221
+ 'common_animations',
222
+ 'display',
223
+ 'props',
224
+ 'title',
225
+ 'type',
226
+ 'x',
227
+ 'y'
228
+ ]
229
+ const _r: Record<string, any> = {}
230
+ _k.forEach((x) => {
231
+ if (root?.hasOwnProperty?.(x)) {
232
+ _r[x] = root[x]
233
+ }
234
+ })
235
+ emit('onEvent', {
236
+ event: e,
237
+ target: _r
238
+ })
207
239
  }
208
240
  }
209
241
  }
@@ -230,6 +262,13 @@
230
262
  }
231
263
  }
232
264
 
265
+ const resetCanvas = () => {
266
+ preview_data.layout_center = {
267
+ x: 0,
268
+ y: 0
269
+ }
270
+ }
271
+
233
272
  const connectNet = () => {
234
273
  const m = preview_data.config.net.mqtt
235
274
  if (m && m.url && m.user && m.pwd && m.topics) {
@@ -240,12 +279,12 @@
240
279
  //用户拿到消息后可以配合setNodeAttrByID方法更新界面
241
280
  //setNodeAttrByID的参数id可以在传给本组件的props.data(用户传进来的,自然知道值是多少)里done_json找到
242
281
  /*如何找到指定组件的两种方案:
243
- 1.用你的项目里前后端约定的svg组件唯一标识符替换掉编辑器生成的id(必须保证唯一),然后调用setNodeAttrByID改变组件属性。
244
- 2.如果不想改动id(避免因不能保证手动改过的id唯一性导致编辑器功能异常),可以在config里给想要改变的组件的配置文件的props里增加一个字段,
245
- 如deviceCode(svg-text的配置文件里有被注释的例子),然后在编辑组态时,给对应组件填上对应的deviceCode(这样deviceCode就和组件id实现
246
- 了映射关系),并保存,后台给前台推MQTT消息时带上指定的deviceCode,前台预览时,在收到MQTT消息后,凭借消息里的deviceCode在done_json
247
- 找到组件的id(可以用vue的computed计算一份deviceCode和id的映射关系存到一个对象里,这样在需要id时可直接在计算出的对象凭借deviceCode
248
- 直接取到),即可用setNodeAttrByID改变组件属性*/
282
+ 1.用你的项目里前后端约定的svg组件唯一标识符替换掉编辑器生成的id(必须保证唯一),然后调用setNodeAttrByID改变组件属性。
283
+ 2.如果不想改动id(避免因不能保证手动改过的id唯一性导致编辑器功能异常),可以在config里给想要改变的组件的配置文件的props里增加一个字段,
284
+ 如deviceCode(svg-text的配置文件里有被注释的例子),然后在编辑组态时,给对应组件填上对应的deviceCode(这样deviceCode就和组件id实现
285
+ 了映射关系),并保存,后台给前台推MQTT消息时带上指定的deviceCode,前台预览时,在收到MQTT消息后,凭借消息里的deviceCode在done_json
286
+ 找到组件的id(可以用vue的computed计算一份deviceCode和id的映射关系存到一个对象里,这样在需要id时可直接在计算出的对象凭借deviceCode
287
+ 直接取到),即可用setNodeAttrByID改变组件属性*/
249
288
  emit('onMessage', {
250
289
  topics,
251
290
  message
@@ -287,12 +326,12 @@
287
326
  @contextmenu="preventDefault"
288
327
  >
289
328
  <slot name="background" />
290
- <div style="position: absolute; left: 0; top: 0; bottom: 0; right: 0">
329
+ <div class="coverLayer">
291
330
  <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
292
331
  <g
293
- :transform="`translate(${preview_data.layout_center.x},${preview_data.layout_center.y})rotate(${0})scale(${
294
- preview_data.config.svg.scale
295
- })`"
332
+ :transform="`translate(${preview_data.layout_center.x * preview_data.config.svg.scale},${
333
+ preview_data.layout_center.y * preview_data.config.svg.scale
334
+ })rotate(${0})scale(${preview_data.config.svg.scale})`"
296
335
  >
297
336
  <g
298
337
  v-for="item in preview_data.done_json"
@@ -304,7 +343,6 @@
304
343
  @mousedown="stopEvent"
305
344
  @mousemove="stopEvent"
306
345
  @mouseup="stopEvent"
307
- @mousewheel="stopEvent"
308
346
  >
309
347
  <g :class="`${getCommonClass(item)}`">
310
348
  <g
@@ -371,6 +409,15 @@
371
409
  </g>
372
410
  </svg>
373
411
  </div>
412
+
413
+ <div class="can-not-select canvasInfoTxt" v-show="showCanvasInfo"
414
+ >缩放倍数:{{ preview_data.config.svg.scale }}倍,画布位置:{{ myFixed(preview_data.layout_center.x, 2) }},{{
415
+ myFixed(preview_data.layout_center.y, 2)
416
+ }}
417
+ <el-icon class="icoP" :size="14" @click="resetCanvas" title="重置位置">
418
+ <Aim />
419
+ </el-icon>
420
+ </div>
374
421
  </div>
375
422
  </template>
376
423
 
@@ -2,7 +2,7 @@
2
2
  import { pinia } from '@/hooks'
3
3
  import { useSvgEditLayoutStore } from '@/stores/svg-edit-layout'
4
4
  import { useConfigStore } from '@/stores/config'
5
- import { preventDefault, stopEvent } from '@/utils'
5
+ import { myFixed, preventDefault } from '@/utils'
6
6
 
7
7
  const emit = defineEmits(['onLineMouseUp'])
8
8
  const props = withDefaults(
@@ -144,12 +144,12 @@
144
144
  if (array.length === 0) {
145
145
  for (let i = 0; i < length; i += props.stepLength) {
146
146
  if (i % props.stepLength === 0) {
147
- array.push({ id: Number((i / configStore.svg.scale - start).toFixed(0)) })
147
+ array.push({ id: myFixed(i / configStore.svg.scale - start, 0) })
148
148
  }
149
149
  }
150
150
  } else {
151
151
  array.forEach((e, i) => {
152
- e.id = Number(((i * props.stepLength) / configStore.svg.scale - start).toFixed(0))
152
+ e.id = myFixed((i * props.stepLength) / configStore.svg.scale - start, 0)
153
153
  })
154
154
  }
155
155
  }
@@ -311,10 +311,14 @@
311
311
  <span v-for="(item, index) in yScale" :style="{ top: index * stepLength + 'px' }" class="n">{{ item.id }}</span>
312
312
  </div>
313
313
  <div :style="{ top: verticalDottedTop + 'px' }" class="vue-ruler-ref-dot-h"
314
- ><span>{{ Math.round((verticalDottedTop - rulerHeight) / configStore.svg.scale) }}</span></div
314
+ ><span>{{
315
+ Math.round((verticalDottedTop - rulerHeight) / configStore.svg.scale - svgEditLayoutStore.center_offset.y)
316
+ }}</span></div
315
317
  >
316
318
  <div :style="{ left: horizontalDottedLeft + 'px' }" class="vue-ruler-ref-dot-v"
317
- ><span>{{ Math.round((horizontalDottedLeft - rulerWidth) / configStore.svg.scale) }}</span></div
319
+ ><span>{{
320
+ Math.round((horizontalDottedLeft - rulerWidth) / configStore.svg.scale - svgEditLayoutStore.center_offset.x)
321
+ }}</span></div
318
322
  >
319
323
  <div
320
324
  v-for="item in lineList"
@@ -80,6 +80,7 @@ export enum EEventType {
80
80
  export enum EEventAction {
81
81
  ChangeAttr = 'ChangeAttr',
82
82
  JavaScript = 'JavaScript',
83
+ Emit = 'Emit',
83
84
  Null = ''
84
85
  }
85
86
 
@@ -18,6 +18,10 @@ export const preventDefault = (e: any) => {
18
18
  e.preventDefault()
19
19
  }
20
20
 
21
+ export const myFixed = (d: number, n: number) => {
22
+ return Number(d.toFixed(n))
23
+ }
24
+
21
25
  export function componentsRegister(data?: Record<string, any>) {
22
26
  //注册所有组件
23
27
  const instance = getCurrentInstance()
@@ -95,19 +99,17 @@ export const calculateRotatedPointCoordinate = (
95
99
  */
96
100
 
97
101
  return {
98
- x: parseFloat(
99
- (
100
- (point.x - center.x) * Math.cos(angleToRadian(rotate)) -
102
+ x: myFixed(
103
+ (point.x - center.x) * Math.cos(angleToRadian(rotate)) -
101
104
  (point.y - center.y) * Math.sin(angleToRadian(rotate)) +
102
- center.x
103
- ).toFixed(1)
105
+ center.x,
106
+ 1
104
107
  ),
105
- y: parseFloat(
106
- (
107
- (point.x - center.x) * Math.sin(angleToRadian(rotate)) +
108
+ y: myFixed(
109
+ (point.x - center.x) * Math.sin(angleToRadian(rotate)) +
108
110
  (point.y - center.y) * Math.cos(angleToRadian(rotate)) +
109
- center.y
110
- ).toFixed(1)
111
+ center.y,
112
+ 1
111
113
  )
112
114
  }
113
115
  }
@@ -190,10 +192,10 @@ export const setSvgActualInfo = (done_json: IDoneJson, resize?: boolean) => {
190
192
  }
191
193
  } else {
192
194
  const BBox = (queryBbox as SVGGraphicsElement).getBBox()
193
- x = parseFloat(BBox.x.toFixed(0))
194
- y = parseFloat(BBox.y.toFixed(0))
195
- width = parseFloat(BBox.width.toFixed(0))
196
- height = parseFloat(BBox.height.toFixed(0))
195
+ x = myFixed(BBox.x, 0)
196
+ y = myFixed(BBox.y, 0)
197
+ width = myFixed(BBox.width, 0)
198
+ height = myFixed(BBox.height, 0)
197
199
  }
198
200
  if (
199
201
  rectBBox &&
@@ -568,3 +570,19 @@ export const createLine = (e: MouseEvent, type?: ELineBindAnchors, itemInfo?: ID
568
570
  new_position_y: 0
569
571
  }
570
572
  }
573
+
574
+ export const getZoomPosition = (e: Record<string, any>, scale: any, center: Record<string, any>, isAdd: boolean) => {
575
+ /*鼠标位置*/
576
+ const offsetX = e.layerX
577
+ const offsetY = e.layerY
578
+ /*上次的画布位置*/
579
+ const tx = center.x
580
+ const ty = center.y
581
+ /*上次的scale*/
582
+ const ts = myFixed(isAdd ? scale - 0.1 : scale + 0.1, 1)
583
+ /*画布在没有缩放的时候移动位置*/
584
+ const lx = myFixed(tx + (offsetX * (ts - 1)) / ts, 1)
585
+ const ly = myFixed(ty + (offsetY * (ts - 1)) / ts, 1)
586
+ center.x = myFixed(lx / scale - ((offsetX - lx) * (scale - 1)) / scale, 2)
587
+ center.y = myFixed(ly / scale - ((offsetY - ly) * (scale - 1)) / scale, 2)
588
+ }
@@ -15,7 +15,7 @@
15
15
  <template>
16
16
  <div class="previewPage">
17
17
  <svg-viewer :data="store.data" />
18
- <el-button @click="back" class="backBtn" :icon="ArrowLeftBold">返回</el-button>
18
+ <el-button type="text" @click="back" class="backBtn" :icon="ArrowLeftBold">返回</el-button>
19
19
  </div>
20
20
  </template>
21
21