MapData.vue 45 KB

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