SwitchStatus.vue 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. <template>
  2. <div class="mapdata">
  3. <div id="container" ref="container" style="width: 1600px"></div>
  4. <div class="left">
  5. <div
  6. class="bottombtn"
  7. style="width: 100%; height: 35px; padding: 10px;display: flex;flex-direction: column;"
  8. >
  9. <!-- <el-button-->
  10. <!-- v-no-more-click-->
  11. <!-- @click="close"-->
  12. <!-- type="primary"-->
  13. <!-- icon="el-icon-close"-->
  14. <!-- style="align-self: flex-end;margin-top: 10px"-->
  15. <!-- >关闭-->
  16. <!-- </el-button>-->
  17. <!-- <el-button-->
  18. <!-- v-no-more-click-->
  19. <!-- @click="save"-->
  20. <!-- type="primary"-->
  21. <!-- icon="el-icon-check"-->
  22. <!-- style="align-self: flex-end;margin-top: 10px"-->
  23. <!-- >保存-->
  24. <!-- </el-button>-->
  25. <!-- <el-button-->
  26. <!-- v-no-more-click-->
  27. <!-- @click="reset"-->
  28. <!-- type="primary"-->
  29. <!-- icon="el-icon-setting"-->
  30. <!-- style="align-self: flex-end;margin-top: 10px"-->
  31. <!-- >重置-->
  32. <!-- </el-button>-->
  33. </div>
  34. </div>
  35. </div>
  36. </template>
  37. <script>
  38. import Konva from 'konva'
  39. import {
  40. selectLotoMapById,
  41. selectIsLotoSwitchMapById,
  42. updateIsLotoSwitchMap,
  43. updatePointsBindingSwitchMap
  44. } from '@/api/mes/switchmanagement/switchmanagement'
  45. import {getIsIsolationPointPage} from '@/api/mes/spm/segregationPoint'
  46. import {getIsMapPointPage, selectIsMapPointById, updateMapPointList} from '@/api/system/mappoint'
  47. import {selectIsMapById} from '@/api/system/mapconfig'
  48. export default {
  49. name: 'KonvaExample',
  50. data() {
  51. return {
  52. stage: null,
  53. layer: null,
  54. selectedStates: [], // 用于存储每个元素的选中状态
  55. selectedText: [], // 用于存储未选中的元素文本
  56. rects: [], // 白色rect合集
  57. texts: [], // 白色text合集
  58. redrects: [], // 红色rect合集
  59. redtexts: [], // 白色text合集
  60. value: '',
  61. form: {}, //拿到单个数据
  62. originData: null, //原始数据
  63. filterData: null, //用来过滤掉已经渲染出来的隔离点
  64. leftPoints: [], //绑定的但未指定位置的集合
  65. orgLeftPoints: [], //原始左边数据
  66. rightPoints: [], //解绑的数据集合
  67. orgRightPoints: [], //原始右边数据
  68. groups: [], //组移动数据
  69. bindingPointIds: [], //存放从未绑定中放入物资柜的数据 id集合
  70. unbindPointIds: [], //解绑的数据接口参数 id集合
  71. isSave: true,
  72. isInitialized: false, // 添加初始化标志
  73. imageUrl: '',//获取底图
  74. width: '',//底图宽
  75. height: '',//底图高
  76. x: '',//底图横坐标
  77. y: '',//底图纵坐标
  78. mapId: null,//地图Id
  79. mapType: 2,//地图类型
  80. pointList: null,//接口给的所有点位数据
  81. bindingPoints: [],//给地图点位界面更新的绑定隔离点
  82. movePoints: [],//给地图点位界面更新位置
  83. unbindingPoints: [],//给地图点位界面更新解绑数据
  84. blinkLights : [], // 所有需要闪烁的 light 节点
  85. globalBlinkTimer : null
  86. }
  87. },
  88. // watch: {
  89. // bindingPointIds(newVal, oldVal) {
  90. // if (newVal) {
  91. // this.isSave = false;
  92. // }
  93. // },
  94. // unbindPointIds(newVal, oldVal) {
  95. // if (newVal) {
  96. // this.isSave = false;
  97. // }
  98. // },
  99. // // value: {
  100. // // handler(newVal, oldVal) {
  101. // // if (this.isInitialized && newVal) {
  102. // // // 只有在初始化后才监听 value 变化
  103. // // const parsedValue = JSON.parse(newVal);
  104. // // console.log(parsedValue, "deep watch for value");
  105. // // this.isSave = false;
  106. // // }
  107. // // },
  108. // // deep: true,
  109. // // },
  110. // },
  111. created() {
  112. // this.getIsIsolationPointPage()
  113. this.isInitialized = true
  114. },
  115. beforeRouteEnter(to, from, next) {
  116. next((vm) => {
  117. // vm.getIsIsolationPointPage()
  118. vm.addPointsToRightPointsBox()
  119. })
  120. },
  121. mounted() {
  122. this.$nextTick(() => {
  123. this.getInfo()
  124. this.getIsIsolationPointPage()
  125. this.addPointsToRightPointsBox()
  126. })
  127. console.log(this.$route.query.switchMapId, 'switchMapId')
  128. },
  129. methods: {
  130. getInfo() {
  131. const switchMapId = this.$route.query.switchMapId
  132. selectIsLotoSwitchMapById(switchMapId).then((response) => {
  133. console.log(response, '作业区域信息')
  134. this.form = response.data
  135. this.mapId = response.data.mapId
  136. // 获取不同底图 如地图或者柜子
  137. selectIsMapById(response.data.mapId).then((response) => {
  138. console.log(response, '获取底图')
  139. if (response.data) {
  140. try {
  141. this.value = JSON.stringify(response.data.pointList, null, 4)
  142. this.originData = this.value
  143. } catch (err) {
  144. console.error(err)
  145. }
  146. }
  147. this.imageUrl = response.data.imageUrl
  148. this.width = response.data.width
  149. this.height = response.data.height
  150. this.x = response.data.x
  151. this.y = response.data.y
  152. this.pointList = response.data.pointList
  153. const data = {
  154. current: 1,
  155. size: -1,
  156. switchMapId: this.$route.query.switchMapId
  157. }
  158. getIsIsolationPointPage(data).then((res) => {
  159. const data1 = res.data.records // 该柜子或地图所有点
  160. const data2 = this.pointList // 该柜子里 json 拿到的点位(已渲染)
  161. console.log(data1, '该柜子或地图所有点')
  162. console.log(data2, '柜子里json拿到的点位')
  163. // 当前柜子已经存在的点位 id
  164. const pointListIds = new Set(this.pointList.map((item) => item.pointId))
  165. // 过滤掉已经在 pointList 里的点
  166. const filterData = data1.filter(item => !pointListIds.has(item.pointId))
  167. console.log([...pointListIds], '已有点位 ID')
  168. console.log(filterData, 'filterData-需要显示在左侧的数据')
  169. // 左侧需要显示的数据
  170. // this.leftPoints = filterData.map((item) => {
  171. // return {
  172. // pointId: item.pointId,
  173. // entityId: item.pointId,
  174. // entityName: item.pointName,
  175. // pointName: item.pointName,
  176. // remark: item.remark,
  177. // prePointId: item.prePointId,
  178. // pointType: item.pointType,
  179. // pointTypeName: item.pointTypeName,
  180. // powerType: item.powerType,
  181. // powerTypeName: item.powerTypeName,
  182. // pointIcon: item.pointIcon,
  183. // status: false,
  184. // pointPicture: item.pointPicture,
  185. // mapImg: null,
  186. // mapId:this.form.mapId,
  187. // mapType: 2,
  188. // mapName:'你好4',
  189. // x: 0,
  190. // y: 0,
  191. // }
  192. // })
  193. // 调用你已有的渲染逻辑
  194. // this.addPointsToLeftPointsBox(filterData)
  195. // 保存完整数据
  196. this.orgLeftPoints = res.data.records.map((item) => {
  197. return {
  198. pointId: item.pointId,
  199. entityId: item.pointId,
  200. entityName: item.pointName,
  201. pointName: item.pointName,
  202. remark: item.remark,
  203. prePointId: item.prePointId,
  204. pointType: item.pointType,
  205. pointTypeName: item.pointTypeName,
  206. powerType: item.powerType,
  207. powerTypeName: item.powerTypeName,
  208. pointIcon: item.pointIcon,
  209. status: false,
  210. pointPicture: item.pointPicture,
  211. mapImg: null,
  212. mapId:this.form.mapId,
  213. mapType: this.form.mapType,
  214. }
  215. })
  216. })
  217. this.initKonva()
  218. })
  219. })
  220. },
  221. // 获取未绑定的所有隔离点
  222. getIsIsolationPointPage() {
  223. // 拿到解绑的隔离点数据
  224. const data1 = {
  225. current: 1,
  226. size: -1,
  227. switchMapId: 0
  228. }
  229. getIsIsolationPointPage(data1).then((res) => {
  230. this.rightPoints = res.data.records.map((item) => {
  231. return {
  232. entityId: item.pointId,
  233. entityName: item.pointName,
  234. pointId: item.pointId,
  235. pointName: item.pointName,
  236. remark: item.remark,
  237. prePointId: item.prePointId,
  238. pointType: item.pointType,
  239. pointTypeName: item.pointTypeName,
  240. powerType: item.powerType,
  241. powerTypeName: item.powerTypeName,
  242. pointIcon: item.pointIcon,
  243. status: false,
  244. pointPicture: item.pointPicture,
  245. mapImg: null,
  246. mapId: this.mapId,
  247. mapType: this.mapType
  248. }
  249. })
  250. this.orgRightPoints = res.data.records.map((item) => {
  251. return {
  252. entityId: item.pointId,
  253. entityName: item.pointName,
  254. pointId: item.pointId,
  255. pointName: item.pointName,
  256. remark: item.remark,
  257. prePointId: item.prePointId,
  258. pointType: item.pointType,
  259. pointTypeName: item.pointTypeName,
  260. powerType: item.powerType,
  261. powerTypeName: item.powerTypeName,
  262. pointIcon: item.pointIcon,
  263. status: false,
  264. pointPicture: item.pointPicture,
  265. mapImg: null,
  266. mapId: this.mapId,
  267. mapType: this.mapType
  268. }
  269. })
  270. })
  271. },
  272. // 重置
  273. reset() {
  274. this.value = this.originData
  275. // 清空并重新赋值
  276. this.rightPoints = JSON.parse(JSON.stringify(this.orgRightPoints)) // 深拷贝
  277. this.leftPoints = JSON.parse(JSON.stringify(this.orgLeftPoints)) // 深拷贝
  278. this.initKonva() // 重新初始化 Konva
  279. },
  280. close() {
  281. // this.$router.push('/mes/dv/lotoStation')
  282. this.getInfo()
  283. // this.initKonva()
  284. },
  285. save() {
  286. this.$confirm('请确认是否保存修改内容', '提示', {
  287. confirmButtonText: '确定',
  288. cancelButtonText: '取消',
  289. type: 'warning'
  290. })
  291. .then(() => {
  292. // 校验 this.value 是否为有效的 JSON
  293. if (this.isJson(this.value)) {
  294. const mapData =
  295. typeof this.value === 'string'
  296. ? this.value
  297. : JSON.stringify(this.value)
  298. const formData = {
  299. ...this.form,
  300. map: mapData
  301. }
  302. console.log(formData, 'map')
  303. updateIsLotoSwitchMap(formData).then((response) => {
  304. console.log(response, '修改车间区域地图')
  305. this.$message({
  306. type: 'success',
  307. message: '保存成功!'
  308. })
  309. })
  310. let dataMap = {
  311. bindingPoints: this.leftPoints,
  312. movePoints: this.movePoints,
  313. unbindingPoints: this.rightPoints
  314. }
  315. console.log(dataMap, '先拿到数据看看再说')
  316. updateMapPointList(dataMap).then((res) => {
  317. console.log(res, '拿到的新绑定数据')
  318. })
  319. const data = {
  320. bindingPointIds: this.bindingPointIds,
  321. switchMapId: this.$route.query.switchMapId,
  322. unbindPointIds: this.unbindPointIds
  323. }
  324. console.log(data, '解绑与绑定数据参数')
  325. updatePointsBindingSwitchMap(data).then((res) => {
  326. console.log(res, '解绑接口返回值')
  327. this.bindingPointIds = []
  328. this.unbindPointIds = []
  329. })
  330. // this.close()
  331. } else {
  332. this.$message({
  333. type: 'error',
  334. message: '地图数据格式不正确,请输入有效的 JSON 格式!'
  335. })
  336. }
  337. })
  338. .catch(() => {
  339. // 取消操作
  340. })
  341. },
  342. // 校验字符串是否为有效的 JSON
  343. isJson(str) {
  344. try {
  345. JSON.parse(str)
  346. return true
  347. } catch (e) {
  348. return false
  349. }
  350. },
  351. initKonva() {
  352. // 创建舞台
  353. this.stage = new Konva.Stage({
  354. container: this.$refs.container, // 容器元素
  355. width: 1600,
  356. height: 860
  357. })
  358. // 创建图层
  359. this.layer = new Konva.Layer()
  360. // 绘制隔离点等其他内容
  361. this.drawGrid(50, 50, '#e0e0e0') // 每个单元格50x50,浅灰色网格
  362. // 创建物资柜底图
  363. const bgImage = new Image()
  364. const imageConfig = {
  365. x: this.x,
  366. y: this.y,
  367. width: this.width,
  368. height: this.height,
  369. draggable: false
  370. }
  371. bgImage.src = this.imageUrl
  372. bgImage.onload = () => {
  373. const knovaImage = new Konva.Image({
  374. ...imageConfig,
  375. image: bgImage
  376. })
  377. this.layer.add(knovaImage)
  378. // 创建背景图并添加到图层
  379. // 创建所有隔离点父盒子 放置于网格线上
  380. // const rightPointsBox = new Konva.Rect({
  381. // x: 1100,
  382. // y: 15,
  383. // width: 200,
  384. // height: 800,
  385. // cornerRadius: 5,
  386. // stroke: 'black',
  387. // strokeWidth: 2,
  388. // fill: 'white'
  389. // })
  390. // const rightnoLoto = new Konva.Text({
  391. // x: 1110, // 调整位置以适应网格
  392. // y: 20, // 调整位置以适应网格
  393. // text: '未绑定锁定站的隔离点数据',
  394. // fontSize: 15,
  395. // fill: 'black'
  396. // })
  397. // this.layer.add(rightPointsBox)
  398. // this.layer.add(rightnoLoto)
  399. // 将隔离点添加到 rightPointsBox
  400. // this.addPointsToRightPointsBox(rightPointsBox)
  401. // 渲染数据
  402. const imageSrc = require('@/assets/images/localSetIcon.jpg') // 图片路径
  403. this.renderGrid(imageSrc, 6, 3, 450, 100, 120, 100, 50, 50, 60, 25)
  404. // 将图层添加到舞台
  405. this.stage.add(this.layer)
  406. this.layer.draw()
  407. }
  408. // 禁止舞台拖拽
  409. this.stage.draggable(false)
  410. },
  411. // 绘制无限网格
  412. drawGrid(cellWidth, cellHeight, gridColor) {
  413. const width = 1600
  414. const height = 860
  415. // 绘制竖线
  416. for (let i = 0; i <= width; i += cellWidth) {
  417. const verticalLine = new Konva.Line({
  418. points: [i, 0, i, height],
  419. stroke: gridColor,
  420. strokeWidth: 1
  421. })
  422. this.layer.add(verticalLine)
  423. }
  424. // 绘制横线
  425. for (let j = 0; j <= height; j += cellHeight) {
  426. const horizontalLine = new Konva.Line({
  427. points: [0, j, width, j],
  428. stroke: gridColor,
  429. strokeWidth: 1
  430. })
  431. this.layer.add(horizontalLine)
  432. }
  433. // 添加网格坐标文本
  434. // for (let row = 0; row < height / cellHeight; row++) {
  435. // for (let col = 0; col < width / cellWidth; col++) {
  436. // const text = new Konva.Text({
  437. // x: col * cellWidth + 5, // 调整位置以适应网格
  438. // y: row * cellHeight + 5, // 调整位置以适应网格
  439. // text: `(${col},${row})`,
  440. // fontSize: 10,
  441. // fill: 'gray',
  442. // });
  443. // this.layer.add(text);
  444. // }
  445. // }
  446. this.layer.draw()
  447. },
  448. // 绘制柜内所有点
  449. renderGrid(imageSrc) {
  450. this.selectedStates = [] // 用数组来存储选中状态
  451. this.rects = []
  452. this.texts = []
  453. this.bgrects = {}
  454. this.redrects = []
  455. this.redtexts = []
  456. this.selectedText = []
  457. // ✅ 每次渲染前清空
  458. this.rightPoints = []
  459. this.leftPoints = []
  460. this.bindingPointIds = []
  461. this.unbindPointIds = []
  462. this.movePoints = []
  463. // 点位数据
  464. const positions = (this.pointList || []).map(item => ({
  465. row: item.x,
  466. col: item.y,
  467. id: item.id,
  468. pointId: item.entityId,
  469. pointName: item.entityName,
  470. entityId: item.entityId,
  471. entityName: item.entityName,
  472. mapId: item.mapId,
  473. mapType: parseInt(item.mapType),
  474. x: item.x,
  475. y: item.y,
  476. remark: item.remark,
  477. pointIcon: item.pointIcon,
  478. pointPicture: item.pointPicture,
  479. pointSerialNumber:item.pointSerialNumber,
  480. switchStatus: item.switchStatus,
  481. switchLastUpdateTime:item.switchLastUpdateTime
  482. }))
  483. console.log(positions, 'positions')
  484. positions.forEach((pos) => {
  485. const x = pos.x * 50
  486. const y = pos.y * 50
  487. const labelText = pos.entityName
  488. const point = new Image()
  489. point.src = pos.pointIcon
  490. point.onload = () => {
  491. const group = new Konva.Group({
  492. x: x,
  493. y: y,
  494. draggable: true
  495. })
  496. const bgrect = new Konva.Rect({
  497. x: -1,
  498. y: -5,
  499. width: 50,
  500. height: 78,
  501. cornerRadius: 5,
  502. stroke: 'white',
  503. strokeWidth: 2,
  504. fill: 'white'
  505. })
  506. const rect = new Konva.Rect({
  507. x: 1,
  508. y: -1,
  509. width: 45,
  510. height: 70,
  511. cornerRadius: 5,
  512. stroke: 'red',
  513. strokeWidth: 2,
  514. fill: 'white'
  515. })
  516. // 确定灯的颜色
  517. let lightColor, shadowColor, stroke;
  518. console.log(pos,'点位')
  519. if (pos.switchStatus == "0") {
  520. // lightColor = '#ff000d'; // Red
  521. // shadowColor = '#ffcae8';
  522. // stroke = '#ffcae8';
  523. lightColor = '#e0e0e0'; // Gray (unknown)
  524. shadowColor = '#e0e0e0';
  525. stroke = '#e0e0e0';
  526. } else if (pos.switchStatus == "1") {
  527. lightColor = '#0ea562'; // Green
  528. shadowColor = '#3ab890';
  529. stroke = '#3ab890';
  530. } else {
  531. lightColor = 'white';
  532. shadowColor="white";
  533. stroke="white"
  534. // lightColor = '#e0e0e0'; // Gray (unknown)
  535. // shadowColor = '#e0e0e0';
  536. // stroke = '#e0e0e0';
  537. }
  538. const isRed = pos.switchStatus == "0";
  539. const isGrey = pos.switchStatus == null;
  540. // Create the light
  541. const light = new Konva.Circle({
  542. x: 22.5, // Circle center position
  543. y: 25, // Circle center position
  544. radius: 12,
  545. fill: lightColor,
  546. stroke: stroke,
  547. strokeWidth: 2,
  548. shadow: {
  549. color: shadowColor,
  550. blur: 5,
  551. offset: {x: 0, y: 0},
  552. opacity: 0.8
  553. }
  554. });
  555. // const knovaImage = new Konva.Image({
  556. // x: 1,
  557. // y: 0,
  558. // image: point,
  559. // width: 45,
  560. // height: 45
  561. // })
  562. const text = new Konva.Text({
  563. x: 8,
  564. y: 50,
  565. fontSize: 17,
  566. text: labelText,
  567. fontFamily: 'Calibri',
  568. fill: 'red'
  569. })
  570. group.add(bgrect)
  571. group.add(rect)
  572. // group.add(knovaImage)
  573. // 添加灯闪
  574. group.add(light)
  575. group.add(text)
  576. this.layer.add(group)
  577. // 定义右侧盒子的范围
  578. // const rightBoxBounds = { x: 1100, y: 15, width: 200, height: 800 }
  579. // group.on('dragend', () => {
  580. // const groupPos = group.getAbsolutePosition()
  581. // const movedLabel = labelText
  582. //
  583. // const isInRightBox =
  584. // groupPos.x >= rightBoxBounds.x &&
  585. // groupPos.x <= rightBoxBounds.x + rightBoxBounds.width &&
  586. // groupPos.y >= rightBoxBounds.y &&
  587. // groupPos.y <= rightBoxBounds.y + rightBoxBounds.height
  588. //
  589. // const indexToRemove = positions ? positions.findIndex(item => item.entityName === movedLabel) : -1
  590. //
  591. // if (indexToRemove !== -1) {
  592. // const movedPoint = positions[indexToRemove]
  593. // if (isInRightBox) {
  594. // // 移动到右侧
  595. // positions.splice(indexToRemove, 1)
  596. // if (!this.rightPoints.some(p => p.entityName === movedPoint.entityName)) {
  597. // this.rightPoints.push(movedPoint)
  598. // this.unbindPointIds.push(movedPoint.entityId)
  599. // this.bindingPointIds = this.bindingPointIds.filter(id => id !== movedPoint.entityId)
  600. // }
  601. // } else {
  602. // // 在柜子内/外更新位置
  603. // const newCol = Math.round(groupPos.y / 50)
  604. // const newRow = Math.round(groupPos.x / 50)
  605. //
  606. // const boundedCol = Math.max(0, Math.min(newCol, Math.floor(860 / 50) - 1))
  607. // const boundedRow = Math.max(0, Math.min(newRow, Math.floor(1200 / 50) - 1))
  608. //
  609. // const updatedPoint = {
  610. // ...movedPoint,
  611. // row: boundedRow,
  612. // col: boundedCol,
  613. // x: boundedRow,
  614. // y: boundedCol,
  615. // mapId: this.form.mapId,
  616. // mapType: this.form.mapType,
  617. // }
  618. //
  619. // positions[indexToRemove] = updatedPoint
  620. //
  621. // // ✅ 只有位置不为 0 才加入 movePoints
  622. // if (!(updatedPoint.row == 0 && updatedPoint.col == 0 && updatedPoint.x == 0 && updatedPoint.y == 0)) {
  623. // if (!this.movePoints) this.movePoints = []
  624. // // 防止重复 push,先找一下
  625. // const existIdx = this.movePoints.findIndex(p => p.pointId === updatedPoint.pointId)
  626. // if (existIdx !== -1) {
  627. // this.movePoints.splice(existIdx, 1, updatedPoint) // 覆盖旧的
  628. // } else {
  629. // this.movePoints.push(updatedPoint)
  630. // }
  631. // }
  632. // }
  633. // } else {
  634. // // movedLabel 不在 positions,可能是从右侧拖出来
  635. // const rightIndex = this.rightPoints.findIndex(item => item.entityName === movedLabel)
  636. // if (rightIndex !== -1) {
  637. // const movedPoint = this.rightPoints.splice(rightIndex, 1)[0]
  638. // const newCol = Math.round(groupPos.y / 50)
  639. // const newRow = Math.round(groupPos.x / 50)
  640. // const boundedCol = Math.max(0, Math.min(newCol, Math.floor(860 / 50) - 1))
  641. // const boundedRow = Math.max(0, Math.min(newRow, Math.floor(1200 / 50) - 1))
  642. //
  643. // const newPoint = {
  644. // ...movedPoint,
  645. // row: boundedRow,
  646. // col: boundedCol,
  647. // x: boundedRow,
  648. // y: boundedCol,
  649. // mapId: this.form.mapId,
  650. // mapType: this.form.mapType,
  651. // }
  652. //
  653. // positions.push(newPoint)
  654. //
  655. // if (!this.leftPoints.some(p => p.entityName === movedPoint.entityName)) {
  656. // this.leftPoints.push(movedPoint)
  657. // }
  658. // this.bindingPointIds.push(movedPoint.entityId)
  659. // this.unbindPointIds = this.unbindPointIds.filter(id => id !== movedPoint.entityId)
  660. //
  661. // // ✅ 判断 row/col/x/y 不为 0 再 push
  662. // if (!(newPoint.row == 0 && newPoint.col == 0 && newPoint.x == 0 && newPoint.y == 0)) {
  663. // if (!this.movePoints) this.movePoints = []
  664. // const existIdx = this.movePoints.findIndex(p => p.pointId === newPoint.pointId)
  665. // if (existIdx !== -1) {
  666. // this.movePoints.splice(existIdx, 1, newPoint)
  667. // } else {
  668. // this.movePoints.push(newPoint)
  669. // }
  670. // }
  671. // }
  672. // }
  673. //
  674. // // 清理无效数据
  675. // this.rightPoints = (this.rightPoints || []).filter(Boolean)
  676. // this.leftPoints = (this.leftPoints || []).filter(Boolean)
  677. //
  678. // this.value = JSON.stringify(positions, null, 4)
  679. // this.layer.draw()
  680. //
  681. // console.log('Updated positions:', positions)
  682. // console.log('MovePoints:', this.movePoints)
  683. // })
  684. // 添加绿灯或红灯闪烁动画
  685. this.addBlinkAnimation(light, isRed,isGrey);
  686. }
  687. })
  688. },
  689. // 全局控制闪烁频率同步函数
  690. startGlobalBlinkTimer() {
  691. if (this.globalBlinkTimer) return; // 已经启动了
  692. this.globalBlinkTimer = setInterval(() => {
  693. const currentSecond = Math.floor(Date.now() / 200) % 2;
  694. const isOn = currentSecond === 1;
  695. this.blinkLights.forEach(light => {
  696. light.opacity(isOn ? 1 : 0.6);
  697. light.scale({ x: isOn ? 1 : 1.1, y: isOn ? 1 : 1.1 });
  698. });
  699. if (this.blinkLights.length > 0) {
  700. this.blinkLights[0].getLayer().batchDraw(); // 统一刷新一次就行
  701. }
  702. }, 50);
  703. },
  704. // 绿灯动画
  705. addBlinkAnimation(light, isRed, isGrey) {
  706. if (!isGrey&&!isRed) {
  707. if (!this.blinkLights.includes(light)) {
  708. this.blinkLights.push(light);
  709. }
  710. this.startGlobalBlinkTimer();
  711. }
  712. },
  713. // 左侧的列表 现在左侧列表通过地图点位接口获取pointList里直接有左侧的数据 不用再去隔离点管理接口拿数据
  714. addPointsToLeftPointsBox(filterData) {
  715. // 获取接口返回的 leftPoints 数据
  716. const pointsData = filterData
  717. let row = 1 // 当前行
  718. let col = 1 // 当前列
  719. // 遍历 pointsData 并根据是否存在于 this.value 中来决定位置
  720. pointsData.forEach((point) => {
  721. const existingPoint = JSON.parse(this.value).find(
  722. (item) => item.pointId == point.pointId
  723. )
  724. // 如果该点在 this.value 中,使用它的原始位置
  725. if (existingPoint) {
  726. point.row = existingPoint.row
  727. point.col = existingPoint.col
  728. } else {
  729. // 否则,按顺序从 (0, 0) 位置开始,每行三个点
  730. point.row = 0
  731. point.col = 0
  732. }
  733. // 渲染该点
  734. this.renderPoint(point)
  735. })
  736. },
  737. // 渲染每个点
  738. renderPoint(point) {
  739. const x = point.col * 50 // 每个单元格宽度为50
  740. const y = point.row * 50 // 每个单元格高度为50
  741. const labelText = point.pointName // 对应的文字
  742. const pointImage = new Image()
  743. pointImage.src = point.pointIcon
  744. pointImage.onload = () => {
  745. // 创建一个新的 Group 来包含整个隔离点
  746. const group = new Konva.Group({
  747. x: x,
  748. y: y,
  749. draggable: true // 设置为可拖拽
  750. })
  751. // 背景矩形
  752. const bgrect = new Konva.Rect({
  753. x: -6,
  754. y: -5,
  755. width: 62,
  756. height: 80,
  757. cornerRadius: 5,
  758. stroke: 'white',
  759. strokeWidth: 2,
  760. fill: 'white'
  761. })
  762. // 普通矩形
  763. const rect = new Konva.Rect({
  764. x: 0,
  765. y: -1,
  766. width: 50,
  767. height: 72,
  768. cornerRadius: 5,
  769. stroke: 'red',
  770. strokeWidth: 2,
  771. fill: 'white'
  772. })
  773. // 图片
  774. const knovaImage = new Konva.Image({
  775. x: 0,
  776. y: 0,
  777. image: pointImage,
  778. width: 50,
  779. height: 50
  780. })
  781. // 文字
  782. const text = new Konva.Text({
  783. x: 8,
  784. y: 50,
  785. fontSize: 17,
  786. text: labelText,
  787. fontFamily: 'Calibri',
  788. fill: 'red'
  789. })
  790. // 将所有元素添加到 group 中
  791. group.add(bgrect)
  792. group.add(rect)
  793. group.add(knovaImage)
  794. group.add(text)
  795. // 将 group 添加到 layer
  796. this.layer.add(group)
  797. // 定义右侧盒子的范围
  798. const rightBoxBounds = {
  799. x: 1100,
  800. y: 15,
  801. width: 200,
  802. height: 800
  803. }
  804. // 定义物资柜的范围
  805. const cabinetBounds = {
  806. x: 330,
  807. y: 10,
  808. width: 500,
  809. height: 790
  810. }
  811. // 处理拖拽事件
  812. // group.on('dragend', () => {
  813. // const groupPos = group.getAbsolutePosition()
  814. // const isInRightBox =
  815. // groupPos.x >= rightBoxBounds.x &&
  816. // groupPos.x <= rightBoxBounds.x + rightBoxBounds.width &&
  817. // groupPos.y >= rightBoxBounds.y &&
  818. // groupPos.y <= rightBoxBounds.y + rightBoxBounds.height
  819. //
  820. // const isInCabinet =
  821. // groupPos.x >= cabinetBounds.x &&
  822. // groupPos.x <= cabinetBounds.x + cabinetBounds.width &&
  823. // groupPos.y >= cabinetBounds.y &&
  824. // groupPos.y <= cabinetBounds.y + cabinetBounds.height
  825. //
  826. // // 如果点进入右侧列表,执行删除操作
  827. // if (isInRightBox) {
  828. // this.removePointFromJson(point)
  829. // } else if (isInCabinet) {
  830. // // 如果点回到物资柜,执行更新操作
  831. // this.updatePointInJson(point, groupPos)
  832. // } else if (!isInCabinet && !isInRightBox) {
  833. // // 如果点位在物资柜外但不在右侧列表中,进行位置更新
  834. // this.updatePointInJson(point, groupPos)
  835. // }
  836. // })
  837. this.layer.draw()
  838. }
  839. },
  840. // 从 json 删除对应的点
  841. removePointFromJson(point) {
  842. // 更新 leftPoints 和 rightPoints
  843. this.rightPoints.push(point)
  844. this.unbindPointIds.push(point.pointId) // 给接口传递需要解绑的数据Id
  845. // 删除 JSON 中对应的点
  846. const updatedData = JSON.parse(this.value).filter(
  847. (item) => item.pointId !== point.pointId
  848. )
  849. this.value = JSON.stringify(updatedData, null, 4)
  850. // console.log('Updated value after removal:', this.value)
  851. console.log('removePointFromJson', updatedData)
  852. },
  853. // 更新 JSON 中对应点的位置
  854. updatePointInJson(point, groupPos) {
  855. // 计算新的位置
  856. const newCol = Math.round(groupPos.x / 50)
  857. const newRow = Math.round(groupPos.y / 50)
  858. // 更新 positions 数组中的点位
  859. const updatedPosition = {
  860. row: newRow,
  861. col: newCol,
  862. x: newRow,
  863. y: newCol,
  864. pointId: point.pointId,
  865. pointName: point.pointName,
  866. entityId: point.pointId,
  867. entityName: point.pointName,
  868. remark: point.remark,
  869. prePointId: point.prePointId,
  870. pointType: point.pointType,
  871. pointTypeName: point.pointTypeName,
  872. powerType: point.powerType,
  873. powerTypeName: point.powerTypeName,
  874. state: point.state,
  875. pointIcon: point.pointIcon,
  876. pointPicture: point.pointPicture,
  877. mapImg: point.mapImg,
  878. mapId: this.form.mapId,
  879. mapType: this.form.mapType,
  880. mapName:'你好5'
  881. }
  882. let positions = JSON.parse(this.value)
  883. const index = positions.findIndex(
  884. (item) => item.pointId === point.pointId
  885. )
  886. if (index !== -1) {
  887. // positions[index] = updatedPosition;
  888. // this.value = JSON.stringify(positions, null, 4);
  889. const updatedPositionCopy = JSON.parse(JSON.stringify(updatedPosition))
  890. positions[index] = updatedPositionCopy
  891. console.log(updatedPositionCopy, positions[index], 'updatedPosition-1')
  892. this.value = JSON.stringify(positions, null, 4)
  893. } else {
  894. // 如果点位不在 this.value 中,则重新插入
  895. positions.push(updatedPosition)
  896. this.value = JSON.stringify(positions, null, 4)
  897. }
  898. // console.log('Updated value after update:', this.value)
  899. },
  900. // 解绑隔离点函数
  901. addPointsToRightPointsBox(rightPointsBox) {
  902. if (this.rightPoints && this.rightPoints.length > 0) {
  903. const boxWidth = rightPointsBox.width()
  904. const boxHeight = rightPointsBox.height()
  905. const boxX = rightPointsBox.x()
  906. const boxY = rightPointsBox.y()
  907. const padding = 10 // 每个隔离点之间的间距
  908. const pointWidth = 50 // 每个隔离点的宽度
  909. const pointHeight = 70 // 每个隔离点的高度(包括图片和文字)
  910. let currentX = boxX + padding
  911. let currentY = boxY + padding
  912. const rightBoxBounds = {
  913. x: 1100,
  914. y: 15,
  915. width: 200,
  916. height: 800
  917. }
  918. const cabinetBounds = {
  919. x: 330,
  920. y: 10,
  921. width: 500,
  922. height: 790
  923. }
  924. this.rightPoints.forEach((point) => {
  925. // 创建一个组来组合红色边框、图片和文字
  926. const group = new Konva.Group({
  927. x: currentX,
  928. y: currentY + 14,
  929. draggable: true // 启用拖拽功能
  930. })
  931. // 创建红色边框
  932. const borderRect = new Konva.Rect({
  933. x: 0,
  934. y: 0,
  935. width: pointWidth,
  936. height: pointHeight,
  937. cornerRadius: 5,
  938. stroke: 'red',
  939. strokeWidth: 2,
  940. fill: 'white'
  941. })
  942. group.add(borderRect)
  943. // 创建图片
  944. const image = new Image()
  945. image.src = point.pointIcon
  946. image.onload = () => {
  947. const knovaImage = new Konva.Image({
  948. x: 1, // 图片在组内的位置
  949. y: 5, // 图片在组内的位置
  950. image: image,
  951. width: 50, // 图片宽度
  952. height: 50 // 图片高度
  953. })
  954. group.add(knovaImage)
  955. // 创建文字
  956. const pointText = new Konva.Text({
  957. x: 12, // 文字在组内的位置
  958. y: 53, // 文字在组内的位置
  959. text: point.pointName,
  960. fontSize: 12,
  961. fill: 'red'
  962. })
  963. group.add(pointText)
  964. // 将组添加到图层
  965. this.layer.add(group)
  966. this.groups[point.pointName] = group // 用文字作为键存储
  967. // 监听组的拖拽移动事件
  968. group.on('dragmove', () => {
  969. // 获取当前组的位置
  970. const groupPos = group.getAbsolutePosition()
  971. // 更新组的位置
  972. group.x(groupPos.x)
  973. group.y(groupPos.y)
  974. })
  975. // 监听组的拖拽结束事件
  976. group.on('dragend', () => {
  977. const gridX = 50 // 网格单元格宽度
  978. const gridY = 50 // 网格单元格高度
  979. // 计算最近的网格点位置
  980. const snappedX = Math.round(group.x() / gridX) * gridX
  981. const snappedY = Math.round(group.y() / gridY) * gridY
  982. // 设置组到最近的网格点位置
  983. group.x(snappedX)
  984. group.y(snappedY)
  985. // 计算网格坐标
  986. const row = Math.floor(snappedY / gridY)
  987. const col = Math.floor(snappedX / gridX)
  988. // 更新点位数据
  989. const updatedPointData = {
  990. row: row,
  991. col: col,
  992. pointId: point.pointId,
  993. entityId: point.entityId,
  994. entityName: point.entityName,
  995. pointName: point.pointName,
  996. remark: point.remark,
  997. prePointId: point.prePointId,
  998. pointType: point.pointType,
  999. pointTypeName: point.pointTypeName,
  1000. powerType: point.powerType,
  1001. powerTypeName: point.powerTypeName,
  1002. state: point.status,
  1003. pointIcon: point.pointIcon,
  1004. pointPicture: point.pointPicture,
  1005. mapImg: null,
  1006. mapId: point.mapId,
  1007. mapName:'你好6',
  1008. mapType: point.mapType,
  1009. }
  1010. // 解析 this.value 为数组
  1011. let valueArray = []
  1012. try {
  1013. valueArray = JSON.parse(this.value)
  1014. } catch (e) {
  1015. console.error('Failed to parse value:', e)
  1016. }
  1017. // 判断拖拽目标区域
  1018. if (
  1019. snappedX >= rightBoxBounds.x &&
  1020. snappedX <= rightBoxBounds.x + rightBoxBounds.width &&
  1021. snappedY >= rightBoxBounds.y &&
  1022. snappedY <= rightBoxBounds.y + rightBoxBounds.height
  1023. ) {
  1024. // 进入右侧盒子区域
  1025. console.log('进入右侧盒子区域')
  1026. // 更新 point 对象的 col 和 row 值
  1027. point.row = col
  1028. point.col = row
  1029. point.x = col
  1030. point.y = row
  1031. // 如果之前已在右侧区域,需要移除值,并更新绑定点ID
  1032. const index = valueArray.findIndex(
  1033. (item) => item.pointId === point.pointId
  1034. )
  1035. if (index !== -1) {
  1036. valueArray.splice(index, 1) // 从 valueArray 中删除该点
  1037. }
  1038. this.value = JSON.stringify(valueArray, null, 4)
  1039. // 删除绑定点
  1040. this.bindingPointIds = this.bindingPointIds.filter(
  1041. (id) => id !== point.pointId
  1042. )
  1043. // 将该点加入解绑点ID
  1044. this.unbindPointIds.push(point.pointId)
  1045. // 将点重新添加到 rightPoints
  1046. this.rightPoints.push(point)
  1047. }
  1048. // 这里的if判断是为了 移动在物资柜内部才做的操作 现在我只要离开右侧 就做这个操作
  1049. // if (
  1050. // snappedX >= cabinetBounds.x &&
  1051. // snappedX <= cabinetBounds.x + cabinetBounds.width &&
  1052. // snappedY >= cabinetBounds.y &&
  1053. // snappedY <= cabinetBounds.y + cabinetBounds.height
  1054. // )
  1055. else if (snappedX < rightBoxBounds.x ||
  1056. snappedX > rightBoxBounds.x + rightBoxBounds.width ||
  1057. snappedY < rightBoxBounds.y ||
  1058. snappedY > rightBoxBounds.y + rightBoxBounds.height
  1059. ) {
  1060. // 进入物资柜区域
  1061. console.log('进入物资柜区域')
  1062. // 更新 point 对象的 col 和 row 值
  1063. point.row = col
  1064. point.col = row
  1065. point.x = col
  1066. point.y = row
  1067. // 检查点是否已经存在于 valueArray 中
  1068. const index = valueArray.findIndex(
  1069. (item) => item.pointId === point.pointId
  1070. )
  1071. if (index === -1) {
  1072. // 如果点位不存在,则新增
  1073. valueArray.push(updatedPointData)
  1074. this.value = JSON.stringify(valueArray, null, 4)
  1075. }
  1076. // 添加到绑定点ID数组
  1077. this.bindingPointIds.push(point.pointId)
  1078. // 从右侧盒子移除点
  1079. this.rightPoints = this.rightPoints.filter(
  1080. (item) => item.pointId !== point.pointId
  1081. )
  1082. this.leftPoints.push(point)
  1083. console.log(point, '进入左侧区域进行隔离点绑定操作!')
  1084. } else {
  1085. // 未进入任何目标区域,保持原状态
  1086. console.log('未进入目标区域,保持原状态')
  1087. // 更新 point 对象的 col 和 row 值
  1088. point.row = col
  1089. point.col = row
  1090. point.x = col
  1091. point.y = row
  1092. // 如果点不在目标区域,从 valueArray 中移除
  1093. // this.value = JSON.stringify(
  1094. // valueArray.filter((item) => item.pointId !== point.pointId),
  1095. // null,
  1096. // 4
  1097. // );
  1098. // 检查点是否已经存在于 valueArray 中
  1099. const index = valueArray.findIndex(
  1100. (item) => item.pointId === point.pointId
  1101. )
  1102. if (index === -1) {
  1103. // 如果点位不存在,则新增
  1104. valueArray.push(updatedPointData)
  1105. this.value = JSON.stringify(valueArray, null, 4)
  1106. }
  1107. // 添加到绑定点ID数组
  1108. this.bindingPointIds.push(point.pointId)
  1109. // 从右侧盒子移除点
  1110. this.rightPoints = this.rightPoints.filter(
  1111. (item) => item.pointId !== point.pointId
  1112. )
  1113. // 如果点不在目标区域,重新添加到 rightPoints
  1114. this.rightPoints.push(point)
  1115. }
  1116. // 重新绘制图层
  1117. this.layer.draw()
  1118. })
  1119. // 重新绘制图层
  1120. this.layer.draw()
  1121. }
  1122. // 更新下一个隔离点的位置
  1123. currentX += pointWidth + padding
  1124. if (currentX + pointWidth > boxX + boxWidth) {
  1125. currentX = boxX + padding
  1126. currentY += pointHeight + padding
  1127. }
  1128. })
  1129. }
  1130. }
  1131. }
  1132. }
  1133. </script>
  1134. <style scoped lang="scss">
  1135. #container {
  1136. width: 100%;
  1137. height: 100%;
  1138. }
  1139. .mapdata {
  1140. width: 100%;
  1141. height: 100%;
  1142. display: flex;
  1143. }
  1144. .left {
  1145. flex: 1;
  1146. display: flex;
  1147. flex-direction: column;
  1148. justify-content: flex-start;
  1149. margin-bottom: 20px;
  1150. }
  1151. </style>