edit.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <script setup lang="ts">
  2. import {
  3. WebtopoProjectApi,
  4. type WebtopoProjectDetailVO,
  5. type WebtopoProjectUpdateReqVO
  6. } from '@/api/pms/maotu'
  7. import type { IExportJson } from '@/components/mt-edit/components/types'
  8. import { useGenThumbnail } from '@/components/mt-edit/composables/thumbnail'
  9. import { MtEdit } from '@/export'
  10. import { Canvg } from 'canvg'
  11. import html2canvas from 'html2canvas'
  12. import { ElMessage } from 'element-plus'
  13. import { nextTick, onMounted, ref } from 'vue'
  14. import { useRoute, useRouter } from 'vue-router'
  15. import DeviceBindPanel from './components/DeviceBindPanel.vue'
  16. const route = useRoute()
  17. const router = useRouter()
  18. const MtEditRef = ref<InstanceType<typeof MtEdit>>()
  19. const projectDetail = ref<WebtopoProjectDetailVO>()
  20. const saveLoading = ref(false)
  21. const getProjectId = () => {
  22. const id = Number(route.params.id)
  23. return Number.isFinite(id) && id > 0 ? id : undefined
  24. }
  25. const getLinkedDeviceIds = (detail: WebtopoProjectDetailVO) => {
  26. return detail.linkedDeviceIds || detail.linkedDevices?.map((item) => item.id) || []
  27. }
  28. const loadProject = async () => {
  29. const id = getProjectId()
  30. if (!id) {
  31. return
  32. }
  33. try {
  34. const data = await WebtopoProjectApi.getWebtopoProject(id)
  35. projectDetail.value = data
  36. if (data?.dataModel) {
  37. await nextTick()
  38. MtEditRef.value?.setImportJson(data.dataModel as IExportJson)
  39. }
  40. } catch {
  41. ElMessage.error('拓扑数据加载失败')
  42. }
  43. }
  44. const genThumbnailDataUrl = async (canvasId = 'mtCanvasArea') => {
  45. const el = document.querySelector<HTMLElement>(`#${canvasId}`)
  46. if (!el) {
  47. ElMessage.error('没有找到canvas元素,请检查!')
  48. return projectDetail.value?.thumbnail
  49. }
  50. const shouldRemoveSvgNodes: HTMLCanvasElement[] = []
  51. const svgElements = document.body.querySelectorAll<HTMLElement>(`#${canvasId} .mt-line-render`)
  52. for (const item of svgElements) {
  53. const svg = item.outerHTML.trim()
  54. const canvas = document.createElement('canvas')
  55. canvas.width = item.getBoundingClientRect().width
  56. canvas.height = item.getBoundingClientRect().height
  57. const ctx = canvas.getContext('2d')
  58. const v = Canvg.fromString(ctx!, svg)
  59. await v.render()
  60. if (item.style.position) {
  61. canvas.style.position += item.style.position
  62. canvas.style.left += item.style.left
  63. canvas.style.top += item.style.top
  64. }
  65. item.parentNode!.appendChild(canvas)
  66. shouldRemoveSvgNodes.push(canvas)
  67. }
  68. try {
  69. const width = el.offsetWidth
  70. const height = el.offsetHeight
  71. const canvas = await html2canvas(el, {
  72. useCORS: true,
  73. scale: 2,
  74. width,
  75. height,
  76. allowTaint: true,
  77. windowHeight: height,
  78. logging: false,
  79. ignoreElements: (element) => element.classList.contains('mt-line-render')
  80. })
  81. return canvas.toDataURL('image/png')
  82. } finally {
  83. shouldRemoveSvgNodes.forEach((item) => item.remove())
  84. }
  85. }
  86. const onPreviewClick = (exportJson: IExportJson) => {
  87. sessionStorage.setItem('exportJson', JSON.stringify(exportJson))
  88. const routeUrl = router.resolve({
  89. name: 'MaotuPreview'
  90. })
  91. window.open(routeUrl.href, '_blank')
  92. }
  93. const onSaveClick = async (dataModel: IExportJson) => {
  94. const detail = projectDetail.value
  95. if (!detail?.id) {
  96. ElMessage.error('项目信息不存在,无法保存')
  97. return
  98. }
  99. try {
  100. saveLoading.value = true
  101. const thumbnail = await genThumbnailDataUrl()
  102. const data: WebtopoProjectUpdateReqVO = {
  103. id: detail.id,
  104. projectName: detail.projectName,
  105. linkedDeviceIds: getLinkedDeviceIds(detail),
  106. remark: detail.remark,
  107. thumbnail,
  108. dataModel: JSON.stringify(dataModel)
  109. }
  110. await WebtopoProjectApi.updateWebtopoProject(data)
  111. projectDetail.value = {
  112. ...detail,
  113. thumbnail,
  114. dataModel: JSON.stringify(dataModel)
  115. }
  116. ElMessage.success('更新成功')
  117. } finally {
  118. saveLoading.value = false
  119. }
  120. }
  121. const onReturnClick = () => {
  122. router.go(-1)
  123. }
  124. const onThumbnailClick = () => {
  125. useGenThumbnail()
  126. }
  127. onMounted(() => {
  128. loadProject()
  129. })
  130. </script>
  131. <template>
  132. <div v-loading="saveLoading" class="w-1/1 h-100vh">
  133. <mt-edit
  134. ref="MtEditRef"
  135. :use-thumbnail="true"
  136. @on-preview-click="onPreviewClick"
  137. @on-return-click="onReturnClick"
  138. @on-save-click="onSaveClick"
  139. @on-thumbnail-click="onThumbnailClick">
  140. <template #deviceBind="{ item }">
  141. <DeviceBindPanel :item="item" :devices="projectDetail?.linkedDevices || []" />
  142. </template>
  143. </mt-edit>
  144. </div>
  145. </template>
  146. <style scoped></style>