vue-wiring-diagram 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-wiring-diagram",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "A Vue 3.x component for wiring diagram",
5
5
  "type": "module",
6
6
  "main": "dist/vue-wiring-diagram.umd.js",
@@ -12,7 +12,7 @@ export const baseShape = [
12
12
  },
13
13
  data: {
14
14
  type: 'base-shape'
15
- }
15
+ },
16
16
  },
17
17
  {
18
18
  shape: 'rect',
@@ -23,7 +23,7 @@
23
23
  </template>
24
24
  </el-dropdown>
25
25
  </div>
26
- <div class="action">
26
+ <div class="action" style="margin-right: 50px">
27
27
  <el-tooltip content="预览" placement="bottom">
28
28
  <el-button size="small" :icon="View" @click="viewJson">
29
29
  </el-button>
@@ -89,6 +89,8 @@ import {portsOptions} from "../portsOptions.js";
89
89
  import {Document, View, ArrowDown} from "@element-plus/icons-vue";
90
90
  import WiringDiagramPreview from "../preview/index.vue";
91
91
  import ImageManagement from "../image-control/image-management.vue";
92
+ import {get} from "packages/http.js";
93
+ import towerPoles from "packages/assets/image/tower-poles.svg";
92
94
 
93
95
  defineOptions({
94
96
  name: 'editor'
@@ -215,6 +217,13 @@ const initGraph = () => {
215
217
  controlsKey.value = moment().valueOf()
216
218
  })
217
219
 
220
+ // graph.value.on('node:added', ({node}) => {
221
+ // if(!node.label) {
222
+ // return
223
+ // }
224
+ // node.label = ''
225
+ // })
226
+
218
227
  //选中节点
219
228
  graph.value.on('cell:click', ({cell}) => {
220
229
  console.log('cell:click', cell, cell?.data?.type ?? cell?.shape)
@@ -290,7 +299,26 @@ const stencil = ref()
290
299
  const initStencil = () => {
291
300
  stencil.value = new Stencil({
292
301
  target: graph.value,
293
- ...stencilOptions
302
+ ...stencilOptions,
303
+ groups: [
304
+ {
305
+ name: '基础组件',
306
+ graphPadding: 30
307
+ },
308
+ ],
309
+ search(cell, keyword) {
310
+ return cell.label?.indexOf(keyword) !== -1
311
+ },
312
+ placeholder: '搜索',
313
+ notFoundText: '未找到匹配项',
314
+ getDragNode: (node) => {
315
+ const copyNode = node.clone({ keepId: true })
316
+ copyNode.label = ''
317
+ return copyNode
318
+ },
319
+ getDropNode: (node) => {
320
+ return node.clone({keepId: true})
321
+ },
294
322
  })
295
323
  const text = graph.value.createNode({
296
324
  ...textOptions
@@ -299,47 +327,66 @@ const initStencil = () => {
299
327
  item.ports = portsOptions
300
328
  return graph.value.createNode(item)
301
329
  })
302
- const image = imageOptions.map(item => {
303
- item.ports = portsOptions
304
- return graph.value.createNode(item)
305
- })
306
330
  stencil.value.load([text, ...baseShapes], '基础组件')
307
- stencil.value.load(image, 'group2')
308
- document.getElementById('stencil').appendChild(stencil.value.container)
309
-
310
- // 添加 hover 事件监听器
311
- setTimeout(() => {
312
- const nodes = stencil.value.container.querySelectorAll('.x6-node');
313
- nodes.forEach(node => {
314
- const div = document.createElement('div');
315
- div.className = 'node-label';
316
- // 添加 hover 事件监听器
317
- node.addEventListener('mouseenter', (e) => {
318
- div.style.position = 'absolute';
319
- div.innerText = Math.random().toString(36).substring(2, 7);
320
- div.style.fontSize = '12px';
321
- div.style.backgroundColor = '#000';
322
- div.style.color = '#fff';
323
- div.style.padding = '2px 4px';
324
- div.style.borderRadius = '4px';
325
- div.style.zIndex = '9999';
326
- div.style.textAlign = 'center';
327
- div.style.width = div.innerText.length * 12 + 'px';
328
- const divWidth = Number(div.style.width.substring(0, div.style.width.length - 2));
329
- div.style.top = e.target.getBoundingClientRect().bottom + 4 + 'px';
330
- div.style.left = e.target.getBoundingClientRect().left + e.target.getBoundingClientRect().width / 2 - divWidth / 2 + 'px';
331
- document.body.appendChild(div);
331
+ get('/cny/custom/groupList').then(res => {
332
+ const data = res?.data || []
333
+ data?.forEach(group => {
334
+ stencil.value.addGroup({
335
+ name: group?.groupName,
336
+ graphPadding: 30
332
337
  })
333
- node.addEventListener('mouseleave', (e) => {
334
- const divs = document.querySelectorAll('.node-label');
335
- if (divs.length)
336
- divs?.forEach(div => {
337
- document.body.removeChild(div);
338
- })
338
+ const image = group?.pictureList.map(item => {
339
+ const nodeOption = {
340
+ shape: 'image',
341
+ imageUrl: item?.imageUrl,
342
+ ports: portsOptions,
343
+ width: 64,
344
+ height: 64,
345
+ label: item.imageName
346
+ }
347
+ return graph.value.createNode(nodeOption)
339
348
  })
340
- });
341
- }, 1000)
342
-
349
+ stencil.value.load(image, group?.groupName)
350
+ document.getElementById('stencil').appendChild(stencil.value.container)
351
+ })
352
+ }).finally(() => {
353
+ // 添加 hover 事件监听器
354
+ setTimeout(() => {
355
+ const nodes = stencil.value.container.querySelectorAll('.x6-node');
356
+ nodes.forEach((node,index) => {
357
+ if(index > baseShape.length) {
358
+ const text = node.querySelector('text');
359
+ node.style.position = 'relative';
360
+ if(text) {
361
+ text.style.display = 'none';
362
+ // text 为 svg 控制位置
363
+ text.setAttribute('x', '0%');
364
+ text.setAttribute('y', '30%');
365
+ text.style.fontSize = '12px';
366
+ text.style.fontWeight = 'bold';
367
+ text.style.fill = '#ffffff';
368
+ }
369
+ }
370
+ // 添加 hover 事件监听器
371
+ node.addEventListener('mouseenter', (e) => {
372
+ if(index > baseShape.length) {
373
+ const text = node.querySelector('text');
374
+ if(text) {
375
+ text.style.display = 'block';
376
+ }
377
+ }
378
+ })
379
+ node.addEventListener('mouseleave', (e) => {
380
+ if(index > baseShape.length) {
381
+ const text = node.querySelector('text');
382
+ if(text) {
383
+ text.style.display = 'none';
384
+ }
385
+ }
386
+ })
387
+ });
388
+ }, 20)
389
+ })
343
390
  }
344
391
 
345
392
  /**
@@ -4,14 +4,44 @@
4
4
  */
5
5
  <template>
6
6
  <div class="form-container">
7
- <el-form ref="form" :model="model" :rules="rules" label-width="90px" label-suffix=":"></el-form>
8
-
7
+ <el-form ref="form" :model="model" :rules="rules" label-width="90px" label-suffix=":">
8
+ <el-form-item label="图片名称" prop="imageName">
9
+ <el-input v-model="model.imageName" placeholder="请输入图片名称"></el-input>
10
+ </el-form-item>
11
+ <el-form-item label="排序" prop="sort">
12
+ <el-input-number v-model="model.sort" :min="0" :max="999" :step="1" controls-position="right"/>
13
+ </el-form-item>
14
+ <el-form-item label="是否显示" prop="isShow">
15
+ <el-switch v-model="model.isShow" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"/>
16
+ </el-form-item>
17
+ <el-form-item label="上传" prop="image">
18
+ <el-upload
19
+ action=""
20
+ list-type="picture"
21
+ class="avatar-uploader"
22
+ :show-file-list="false"
23
+ :on-change="changeImage"
24
+ :auto-upload="false"
25
+ >
26
+ <el-image v-if="imageUrl" :src="imageUrl" fit="cover" style="width: 64px; height: 64px;"/>
27
+ <el-icon v-else class="avatar-uploader-icon">
28
+ <Plus/>
29
+ </el-icon>
30
+ </el-upload>
31
+ </el-form-item>
32
+ <div class="footer">
33
+ <el-button type="primary" @click="saveBtnClick">保存</el-button>
34
+ <el-button @click="cancelBtnClick">取消</el-button>
35
+ </div>
36
+ </el-form>
9
37
  </div>
10
38
  </template>
11
39
 
12
40
  <script setup>
13
- import {reactive, ref} from 'vue'
14
- import {ElMessage} from 'element-plus'
41
+ import {onMounted, reactive, ref, shallowRef} from 'vue'
42
+ import {ElMessage} from "element-plus";
43
+ import {instance, post, upload} from "packages/http.js";
44
+ import {Plus} from "@element-plus/icons-vue";
15
45
 
16
46
  const emits = defineEmits(['close'])
17
47
  const props = defineProps({
@@ -22,11 +52,126 @@ const props = defineProps({
22
52
  }
23
53
  }
24
54
  })
25
- const form = ref(null)
26
- const model = reactive({})
27
- const rules = reactive({})
55
+ const form = shallowRef(null)
56
+ const model = reactive({
57
+ groupId: '',
58
+ id: '',
59
+ imageName: '',
60
+ sort: 0,
61
+ isShow: 1,
62
+ imageUrl: '',
63
+ })
64
+ const rules = ({
65
+ imageName: [
66
+ {required: true, message: '请输入图片名称', trigger: 'blur'},
67
+ {min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur'}
68
+ ],
69
+ sort: [
70
+ {required: true, message: '请输入排序', trigger: 'blur'},
71
+ {type: 'number', message: '排序必须为数字值'}
72
+ ]
73
+ })
74
+
75
+ const imageUrl = ref('')
76
+
77
+ /**
78
+ * 图片改变事件
79
+ * @param file
80
+ */
81
+ const changeImage = (file) => {
82
+ if (file.raw.type !== 'image/svg+xml') {
83
+ ElMessage.error('上传图片只能是 svg 格式!');
84
+ return false;
85
+ }
86
+ const formData = new FormData();
87
+ formData.append('file', file.raw);
88
+ upload('/cny/file/fileUpload', formData).then(res => {
89
+ if (res?.isOk) {
90
+ imageUrl.value = instance.defaults.baseURL + '/cny/upload/' + res?.data
91
+ model.imageUrl = res?.data
92
+ } else {
93
+ ElMessage.error(res?.msg || '上传失败')
94
+ }
95
+ })
96
+ }
97
+
98
+
99
+ /**
100
+ * 保存按钮点击事件
101
+ */
102
+ const saveBtnClick = () => {
103
+ console.log(model)
104
+ form.value.validate((valid) => {
105
+ if (valid) {
106
+ post('/cny/custom/savePicture', model).then(res => {
107
+ if (res?.isOk) {
108
+ ElMessage.success('保存成功')
109
+ emits('close', true)
110
+ } else {
111
+ ElMessage.error(res?.msg || '保存失败')
112
+ }
113
+ })
114
+ } else {
115
+ return false;
116
+ }
117
+ })
118
+ }
119
+
120
+ /**
121
+ * 取消按钮点击事件
122
+ */
123
+ const cancelBtnClick = () => {
124
+ emits('close', false)
125
+ }
126
+
127
+ onMounted(() => {
128
+ if (props.payload?.id) {
129
+ model.id = props.payload.id
130
+ model.imageName = props.payload.imageName
131
+ model.sort = props.payload.sort
132
+ model.isShow = props.payload.isShow
133
+ model.imageUrl = props.payload.imageUrl
134
+ imageUrl.value = model.imageUrl
135
+ }
136
+ model.groupId = props.payload.groupId
137
+ })
28
138
  </script>
29
139
 
30
140
  <style scoped lang="scss">
141
+ @use "../../styles/dialog.scss";
142
+
143
+ .avatar-uploader {
144
+ width: 64px;
145
+ height: 64px;
146
+
147
+
148
+ :deep(.el-upload) {
149
+ width: 100%;
150
+ height: 100%;
151
+ border: 1px dashed rgb(35, 100, 221);
152
+ border-radius: 6px;
153
+ cursor: pointer;
154
+ position: relative;
155
+ overflow: hidden;
156
+ transition: var(--el-transition-duration-fast);
157
+
158
+ :deep(.el-upload:hover) {
159
+ border-color: var(--el-color-primary);
160
+ }
161
+
162
+ .el-icon {
163
+ color: rgb(35, 100, 221);
164
+ }
165
+
166
+
167
+ :deep(.el-icon).avatar-uploader-icon {
168
+ font-size: 28px;
169
+ color: rgb(35, 100, 221);
170
+ width: 178px;
171
+ height: 178px;
172
+ text-align: center;
173
+ }
174
+ }
175
+ }
31
176
 
32
177
  </style>
@@ -11,6 +11,7 @@
11
11
  <div style="padding: 0 10px;display: flex;flex-direction: column;gap: 5px;align-items: flex-start">
12
12
  <el-button type="primary" size="small" @click="addPicture(props.row.id)">新增图片</el-button>
13
13
  <el-table :data="props.row.pictureList" border size="small">
14
+ <el-table-column label="名称" prop="imageName" align="center"></el-table-column>
14
15
  <el-table-column label="图片" prop="imageUrl" align="center">
15
16
  <template #default="scope">
16
17
  <img :src="scope.row.imageUrl" alt="" style="width: 64px; height: 64px;">
@@ -22,6 +23,12 @@
22
23
  {{ scope.row.isShow === 1 ? '是' : '否' }}
23
24
  </template>
24
25
  </el-table-column>
26
+ <el-table-column label="操作" align="center">
27
+ <template #default="scope">
28
+ <el-button type="primary" size="small" @click="editPicture(scope.row)">编辑</el-button>
29
+ <el-button type="danger" size="small" @click="deletePicture(scope.row.id)">删除</el-button>
30
+ </template>
31
+ </el-table-column>
25
32
  </el-table>
26
33
  </div>
27
34
  </template>
@@ -42,11 +49,11 @@
42
49
  </el-table>
43
50
  <el-dialog v-model="groupDialog.show" :title="groupDialog.title" width="300" :close-on-click-modal="false" :close-on-press-escape="false"
44
51
  >
45
- <group-form :payload="groupDialog.payload" @close="closeDialog"/>
52
+ <group-form v-if="groupDialog.show" :payload="groupDialog.payload" @close="closeDialog"/>
46
53
  </el-dialog>
47
54
  <el-dialog v-model="imageDialog.show" :title="imageDialog.title" width="300" :close-on-click-modal="false" :close-on-press-escape="false"
48
55
  >
49
- <image-form :payload="imageDialog.payload" @close="closeDialog"/>
56
+ <image-form v-if="imageDialog.show" :payload="imageDialog.payload" @close="closeDialog"/>
50
57
  </el-dialog>
51
58
  </div>
52
59
  </template>
@@ -55,7 +62,7 @@
55
62
  import {onMounted, reactive} from "vue";
56
63
  import groupForm from "./group-form.vue";
57
64
  import imageForm from "./image-form.vue";
58
- import {get, del} from "../../http.js";
65
+ import {get, del, instance} from "../../http.js";
59
66
  import {ElMessage, ElMessageBox} from "element-plus";
60
67
 
61
68
  // 数据集
@@ -143,6 +150,44 @@ const addPicture = (id) => {
143
150
  }
144
151
  }
145
152
 
153
+ /**
154
+ * 编辑图片
155
+ * @param row
156
+ */
157
+ const editPicture = (row) => {
158
+ imageDialog.show = true
159
+ imageDialog.title = '编辑图片'
160
+ imageDialog.payload = {
161
+ id: row.id,
162
+ groupId: row.groupId,
163
+ imageName: row.imageName,
164
+ sort: row.sort,
165
+ isShow: row.isShow,
166
+ imageUrl: row.imageUrl
167
+ }
168
+ }
169
+
170
+ /**
171
+ * 删除图片
172
+ * @param id
173
+ */
174
+ const deletePicture = (id) => {
175
+ ElMessageBox.confirm('是否确定要删除该图片?', '提示', {
176
+ confirmButtonText: '确定',
177
+ cancelButtonText: '取消',
178
+ type: 'warning'
179
+ }).then(() => {
180
+ del('/cny/custom/removePicture/' + id).then(res => {
181
+ if (res.code === 200) {
182
+ ElMessage.success('删除成功')
183
+ loadImageRecord()
184
+ } else {
185
+ ElMessage.error(res.msg || '删除失败')
186
+ }
187
+ })
188
+ })
189
+ }
190
+
146
191
  /**
147
192
  * 关闭弹窗
148
193
  * @param action
@@ -30,16 +30,6 @@ export const stencilOptions = {
30
30
  columnWidth: 70,
31
31
  resizeToFit: true
32
32
  },
33
- groups: [
34
- {
35
- name: '基础组件',
36
- graphPadding: 30
37
- },
38
- {
39
- name: 'group2',
40
- graphPadding: 30
41
- },
42
- ],
43
33
  }
44
34
 
45
35
  export const portOption = {
package/packages/http.js CHANGED
@@ -6,7 +6,7 @@ import {ElMessage} from "element-plus";
6
6
  let baseURL = 'http://localhost:8001'
7
7
 
8
8
  // 创建一个 axios 实例
9
- const instance = axios.create({
9
+ export const instance = axios.create({
10
10
  baseURL: baseURL, // 你的 API 基础 URL
11
11
  timeout: 10000, // 请求超时时间
12
12
  headers: {
@@ -93,3 +93,12 @@ export const put = (url, data = {}) => {
93
93
  export const del = (url, params = {}) => {
94
94
  return instance.delete(url, {params});
95
95
  };
96
+
97
+ // 上传文件
98
+ export const upload = (url, data = {}) => {
99
+ return instance.post(url, data, {
100
+ headers: {
101
+ 'Content-Type': 'multipart/form-data',
102
+ },
103
+ });
104
+ };
@@ -7,6 +7,7 @@
7
7
  display: grid;
8
8
  grid-template-rows: auto 1fr;
9
9
  overflow: hidden;
10
+ //background: url("/packages/assets/image/background.png") #091757 no-repeat center center / cover;
10
11
  background: url("/packages/assets/image/background.png") #091757 no-repeat center center / cover;
11
12
 
12
13
  ::-webkit-scrollbar {
@@ -54,6 +55,17 @@
54
55
  :deep(.x6-widget-stencil-title) {
55
56
  display: none;
56
57
  }
58
+
59
+ :deep(.x6-widget-stencil.searchable > .x6-widget-stencil-content) {
60
+ top: 50px
61
+ }
62
+
63
+ :deep(.x6-widget-stencil-search-text){
64
+ background: rgba(35, 100, 221, 0.3);
65
+ color: #ffffff;
66
+ border-radius: 5px;
67
+ border: 1px solid rgb(35, 100, 221);
68
+ }
57
69
  }
58
70
 
59
71
  #controls {