Просмотр исходного кода

Merge branch 'develop' of http://47.108.150.237:10000/www/taais-web into develop

WANGKANG 8 месяцев назад
Родитель
Сommit
87b011bc75

+ 6 - 0
src/api/modules/upload.ts

@@ -11,6 +11,12 @@ export const uploadImg = (params: FormData) => {
   return http.post<any>('/common/upload', params, { cancel: false })
 }
 
+// 图片上传
+export const uploadPure = (params: FormData) => {
+  // return http.post<Upload.ResFileUrl>(PORT1 + `/file/upload/img`, params, { cancel: false })
+  return http.post<any>('/common/uploadPure', params, { cancel: false })
+}
+
 // 视频上传
 export const uploadVideo = (params: FormData) => {
   // return http.post<any>(PORT1 + `/file/upload/video`, params, { cancel: false })

+ 1 - 0
src/components/ProForm/index.vue

@@ -29,6 +29,7 @@
             </template>
             <template v-else-if="item.compOptions.elTagName === 'img-upload'">
               <uploadImg v-model:image-url="formModel[item.prop]" />
+<!--              <el-button @click="() => {console.log(formModel[item.prop])}"/>-->
             </template>
 
             <template v-else-if="item.compOptions.elTagName === 'imgs-upload'">

+ 8 - 1
src/components/Upload/Img.vue

@@ -43,6 +43,11 @@
     <div class="el-upload__tip">
       <slot name="tip"></slot>
     </div>
+<!--    <el-dialog id="dialog" v-if="isShowData" :visible="imgViewVisible">-->
+<!--      <el-image-viewer :url-list="['/api' + imageUrl]" />-->
+<!--    </el-dialog>-->
+<!--    <el-image-viewer v-else-if="imgViewVisible" :url-list="['/api' + imageUrl]" @close="imgViewVisible = false" />-->
+
     <el-image-viewer v-if="imgViewVisible" :url-list="['/api' + imageUrl]" @close="imgViewVisible = false" />
   </div>
 </template>
@@ -64,6 +69,7 @@ interface UploadFileProps {
   height?: string // 组件高度 ==> 非必传(默认为 150px)
   width?: string // 组件宽度 ==> 非必传(默认为 150px)
   borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
+  isShowData?: boolean
 }
 
 // 接受父组件参数
@@ -75,7 +81,8 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
   fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
   height: '150px',
   width: '150px',
-  borderRadius: '8px'
+  borderRadius: '8px',
+  isShowData: false
 })
 
 // 生成组件唯一id

+ 86 - 23
src/views/demo/components/img-detect.vue

@@ -1,6 +1,10 @@
 <template>
   <el-dialog class="modal-canvas" v-model="visible" title="标注" width="980px" height="700px">
-    <ImgMaker ref="imgMaker" v-if="visible" :src="cover" :area="area" :width="width" :height="height"></ImgMaker>
+    <ImgMaker ref="imgMaker" v-if="visible" :src="cover" :area="area" :width="width" :height="height" :class-def="classDef" :json-data="jsonData"></ImgMaker>
+    <div style="margin-top: 10px; text-align: center">
+      标签
+      <el-button v-for="(item, index) in classes" :key="index" type="primary" @click="onClassChange(item)" :disabled="classDef.label===item.label">{{ item.name }}</el-button>
+    </div>
     <div style=" margin-top: 10px;text-align: center">
       <!-- <el-space> -->
       <el-button type="primary" @click="onClearLast">撤销最后一次的操作</el-button>
@@ -12,7 +16,7 @@
   </el-dialog>
 </template>
 <script lang="ts" setup>
-import { reactive, ref, toRefs, defineProps, watchEffect } from 'vue'
+import {reactive, ref, toRefs, defineProps, watchEffect, watch} from 'vue'
 import ImgMaker from './img-maker.vue'
 const props = defineProps({
   area: {
@@ -30,19 +34,40 @@ const props = defineProps({
   height: {
     type: Number,
     default: 1080
+  },
+  classes: {
+    type: Array
+  },
+  jsonData: {
+    type: Array
   }
 })
 const emit = defineEmits(['success'])
 const imgMaker = ref()
 const state = reactive({
   visible: false,
-  cover: ''
+  cover: '',
+  classDef: props.classes && props.classes.length > 0 ? props.classes[0] : { name: 'default', color: '#FF00FF', classValue: 0 }
 })
-const { visible, cover } = toRefs(state)
+
+watch(
+  () => props.classes,
+  value => {
+    state.classDef = (value && value.length > 0) ? value[0] : { name: 'default', color: '#FF00FF', classValue: 0 }
+    // console.log(state.classDef)
+  }
+)
+
+const { visible, cover, classDef } = toRefs(state)
 watchEffect(() => {
   state.cover = props.img
 })
 
+const onClassChange = item => {
+  // console.log('change class choice', item)
+  state.classDef = item
+}
+
 const onClearAll = () => {
   imgMaker.value.clearAll()
 }
@@ -58,31 +83,69 @@ const onSubmit = () => {
   let points = imgMaker.value.getData()
   // console.log('points', points)
 
-  let datas = []
-  if (points.length > 0) {
-    datas = points.map(point => {
-      if (point.br) {
-        let w = point.tr.x - point.tl.x
-        let h = point.bl.y - point.tl.y
+  let emitData = {
+    data: '',
+    jsonData: points[0],
+    url: points[1]
+  }
+
+  for (let i = 2; i < points.length; i++) {
+    let label = points[i]['label']
+    // console.log(label)
+    emitData.data += label + ' '
+    let index = -1, yMin = 10000000, xMax = -1
+    for (let j = 0; j < points[i]['nodes'].length; j++) {
+      if (points[i]['nodes'][j].y < yMin) {
+        index = j
+        yMin = points[i]['nodes'][j].y
+        xMax = points[i]['nodes'][j].x
+      } else if (points[i]['nodes'][j].y === yMin && points[i]['nodes'][j].x > xMax) {
+        index = j
+        xMax = points[i]['nodes'][j].x
+      }
+    }
 
-        const algoData = `${point.tl.x / 1920};${point.tl.y / 1080};${w / 1920};${h / 1080}`
-        // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y};${point.br.x};${point.br.y};${point.bl.x};${point.bl.y}`
-        return algoData
-      } else {
-        let coors = ''
-        Object.keys(point).forEach(item => {
-          coors += `${point[item].x};${point[item].y};`
-        })
-        coors = coors.slice(0, -1)
-        return coors
+    for (let j = 0; j < points[i]['nodes'].length; j++) {
+      let ptr = (j + index) % points[i]['nodes'].length
+      emitData.data += points[i]['nodes'][ptr].x + ' ' + points[i]['nodes'][ptr].y
+      if (j + 1 !== points[i]['nodes'].length) {
+        emitData.data += ' '
       }
-      // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y}`
-    })
+    }
+
+    if (i + 1 !== points.length) {
+      emitData.data += ' \r\n'
+    }
+    // if ()
+    // datas += points[i][]
   }
+
+  // if (points.length > 0) {
+  //   datas = points.map(point => {
+  //     if (point.br) {
+  //       let w = point.tr.x - point.tl.x
+  //       let h = point.bl.y - point.tl.y
+  //
+  //       const algoData = `${point.tl.x / 1920};${point.tl.y / 1080};${w / 1920};${h / 1080}`
+  //       // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y};${point.br.x};${point.br.y};${point.bl.x};${point.bl.y}`
+  //       return algoData
+  //     } else {
+  //       console.log('all else', point)
+  //       let coors = ''
+  //       Object.keys(point).forEach(item => {
+  //         coors += `${point[item].x};${point[item].y};`
+  //       })
+  //       coors = coors.slice(0, -1)
+  //       return coors
+  //     }
+  //     // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y}`
+  //   })
+  // }
   // console.log('datas', datas)
   // 归一化
+  // datas.push(points[0])
 
-  emit('success', datas)
+  emit('success', emitData)
   onClearAll()
   state.visible = false
 }

+ 326 - 83
src/views/demo/components/img-maker.vue

@@ -1,7 +1,22 @@
 <template>
   <div>
     <div style="margin-bottom: 10px">
-      <el-button plain type="primary" class="shape-border" @click="drawTypeChange('rectangle')">矩形</el-button>
+      <el-button plain type="primary" class="shape-border" @click="drawTypeChange('rectangle')" :disabled="!state.dragMode">标注模式</el-button>
+      <el-button plain type="primary" class="shape-border" @click="enableDragMode" :disabled="state.dragMode">移动图片</el-button>
+
+      <el-button plain type="primary" style="margin-left: 30px" class="shape-border" @click="selectLastObject" :disabled="!!state.activeTarget">选择最后一个标注(w)</el-button>
+      <el-button
+        plain
+        type="primary"
+        class="shape-border"
+        @click="rotate(8)"
+        :disabled="!state.activeTarget"
+      >
+        顺时针旋转(r)
+      </el-button>
+      <el-button plain type="primary" class="shape-border" @click="rotate(-8)" :disabled="!state.activeTarget">逆时针旋转(e)</el-button>
+      <el-button plain type="primary" style="margin-left: 30px" class="shape-border" @click="changeZoom(1.1)">放大图片</el-button>
+      <el-button plain type="primary" class="shape-border" @click="changeZoom(0.9)">缩小图片</el-button>
       <!-- <el-button plain type="primary" class="shape-border" @click="drawPolygon('polygon')">多边形</el-button> -->
     </div>
     <canvas id="canvas" :width="cWidth" :height="cHeight"></canvas>
@@ -10,6 +25,7 @@
 <script lang="ts" setup>
 import { fabric } from 'fabric'
 import { reactive, watch, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
 // import { sortPoints } from '@/utils/fabric'
 
 const props = defineProps({
@@ -36,11 +52,17 @@ const props = defineProps({
   cHeight: {
     type: Number,
     default: 540
+  },
+  classDef: {
+    type: Object
+  },
+  jsonData: {
+    type: Array
   }
 })
 const state = reactive({
   loading: true,
-  radio: 0.5,
+  radio: 1,
   realRadioX: 0.5,
   realRadioY: 0.5,
   imgPoint: { x: 0, y: 0 },
@@ -48,11 +70,16 @@ const state = reactive({
   canvas: {} as any,
   mouseFrom: { x: 0, y: 0 } as canvasPoint,
   mouseTo: { x: 0, y: 0 } as canvasPoint,
+  dragMode: false,
+  isDragging: false,
+  lastPosX: 0,
+  lastPosY: 0,
   // drawType: 'rectangle' as string, //当前绘制图像的种类
   drawType: null as any, //当前绘制图像的种类
-  drawWidth: 1, //笔触宽度
+  drawWidth: 5, //笔触宽度
   color: '#E34F51', //画笔颜色
   drawingObject: null as any, //当前绘制对象
+  activeTarget: null as any, // 当前点击活跃对象
   moveCount: 1, //绘制移动计数器
   doDrawing: false as boolean, // 绘制状态
   rectPath: '' as string, //矩形绘制路径
@@ -64,9 +91,17 @@ const state = reactive({
   activeLine: '' as any,
   line: {} as canvasPoint
 })
+watch(
+  () => props.classDef,
+  value => {
+    // console.log(value)
+    state.color = value && value.color ? value.color : '#E34F51'
+  }
+)
 watch(
   () => state.drawType,
   value => {
+    // console.log(value)
     state.canvas.selection = !value
   }
 )
@@ -83,7 +118,39 @@ watch(
   }
 )
 
+const enableDragMode = () => {
+  state.dragMode = true
+  state.activeTarget = null
+}
+
+const changeZoom = multi => {
+  // 放大
+  state.radio = state.canvas.getZoom() * multi
+  state.canvas.setZoom(state.radio)
+  // state.canvas.setViewport(state.canvas.getCenter()); // 更新视口
+  state.canvas.renderAll()
+}
+
 const loadInit = () => {
+  drawTypeChange('rectangle')
+  state.color = props.classDef && props.classDef.color ? props.classDef.color : '#E34F51'
+  document.addEventListener('keydown', function (e) {
+    if (e.key === 'w' || e.key === 'W') {
+      selectLastObject()
+      return
+    }
+    if (state.activeTarget) {
+      if (e.key === 'r' || e.key === 'R') {
+        // 旋转矩形对象
+        state.activeTarget.rotate(state.activeTarget.angle + 5)
+        state.canvas.renderAll()
+      } else if (e.key === 'e' || e.key === 'E') {
+        // 旋转矩形对象
+        state.activeTarget.rotate(state.activeTarget.angle - 5)
+        state.canvas.renderAll()
+      }
+    }
+  })
   if (props.src == '') {
     return
   }
@@ -93,6 +160,44 @@ const loadInit = () => {
   state.canvas.on('mouse:down', mousedown)
   state.canvas.on('mouse:move', mousemove)
   state.canvas.on('mouse:up', mouseup)
+  // // 添加鼠标滚轮缩放功能
+  // state.canvas.on('mouse:wheel', event => {
+  //   const delta = event.delta
+  //   const zoom = state.canvas.getZoom()
+  //   const point = new fabric.Point(event.x, event.y)
+  //
+  //   if (delta > 0) {
+  //     // 放大
+  //     state.canvas.zoomToPoint(point, zoom * 1.1)
+  //     // state.radio *= 1.1
+  //   } else {
+  //     // 缩小
+  //     state.canvas.zoomToPoint(point, zoom * 0.9)
+  //     // state.radio *= 0.9
+  //   }
+  //   state.canvas.renderAll()
+  //   event.e.preventDefault()
+  //   event.e.stopPropagation()
+  // })
+
+  // nice try!
+  // console.log(props.jsonData)
+  // if (props.jsonData && props.jsonData.length > 0) {
+  //   // console.log('load')
+  //   state.canvas.loadFromJSON(props.jsonData, () => {
+  //     let objects = state.canvas.getObjects()
+  //     // console.log(objects)
+  //     objects[0].selectable = false
+  //     for (let i = 1; i < objects.length; i++) {
+  //       objects[i].selectable = true
+  //     }
+  //     state.canvas.renderAll()
+  //   })
+  //   // console.log(state.canvas)
+  //   state.loading = false
+  //   return
+  // }
+
   let imgElement = new Image()
   imgElement.src = props.src
   imgElement.onload = () => {
@@ -110,6 +215,7 @@ const loadInit = () => {
 
     state.realPoint.x = Math.floor(props.width / 2)
     state.realPoint.y = Math.floor(props.height / 2)
+
     let imgInstance = new fabric.Image(imgElement, {
       selectable: false,
       width: imgElement.width,
@@ -118,75 +224,105 @@ const loadInit = () => {
       scaleY: state.radio
     })
     state.canvas.add(imgInstance)
-    drawImage()
-    state.canvas.renderAll()
+    if (props.jsonData && props.jsonData.length > 0) {
+      for (let i = 0; i < props.jsonData.length; i++) {
+        let config = props.jsonData[i]
+        let obj = new fabric.Path(config.pathString, config)
+        state.canvas.add(obj)
+      }
+      state.canvas.renderAll()
+    }
+    state.radio = state.canvas.getZoom()
     state.loading = false
+    // console.log(state.realRadioX)
+    // console.log(state.realRadioY)
   }
 }
-const drawImage = () => {
-  if (props.area === '') {
-    clearAll()
-    return
-  }
-  let points = props.area.split(',').map(item => {
-    let areas = item.split(';')
-    let data = areas.map((ars, index) => {
-      let arp = 0
-      let ar = Number(ars)
-      if (index % 2 == 0) {
-        let dx = Math.abs(state.realPoint.x > ar ? state.realPoint.x - ar : state.realPoint.x + ar) / state.realRadioX
-        let rdx = Math.abs(state.imgPoint.x - dx)
-        arp = rdx
-      } else {
-        let dy = Math.abs(state.realPoint.y > ar ? state.realPoint.y - ar : state.realPoint.y + ar) / state.realRadioY
-        let rdy = Math.abs(state.imgPoint.y - dy)
-        arp = rdy
-      }
-      return Number(arp) * state.radio
-    })
-    return data
-  })
-  points.forEach(point => {
-    drawImageObj(point)
-  })
+// const drawImage = () => {
+//   if (props.area === '') {
+//     clearAll()
+//     return
+//   }
+//   // console.log(props.area)
+//   let points = props.area.split(',').map(item => {
+//     let areas = item.split(';')
+//     let data = areas.map((ars, index) => {
+//       let arp = 0
+//       let ar = Number(ars)
+//       if (index % 2 == 0) {
+//         let dx = Math.abs(state.realPoint.x > ar ? state.realPoint.x - ar : state.realPoint.x + ar) / state.realRadioX
+//         let rdx = Math.abs(state.imgPoint.x - dx)
+//         arp = rdx
+//       } else {
+//         let dy = Math.abs(state.realPoint.y > ar ? state.realPoint.y - ar : state.realPoint.y + ar) / state.realRadioY
+//         let rdy = Math.abs(state.imgPoint.y - dy)
+//         arp = rdy
+//       }
+//       return Number(arp) * state.radio
+//     })
+//     return data
+//   })
+//   points.forEach(point => {
+//     drawImageObj(point)
+//   })
+// }
+// const drawImageObj = data => {
+//   let path = 'M '
+//   // debugger
+//   let points = [] as any
+//   let len = data.length / 2
+//   for (let i = 0; i < len; i++) {
+//     let idx = i * 2
+//     points.push({ x: data[idx], y: data[idx + 1] })
+//     path += `${data[idx]} ${data[idx + 1]} L `
+//   }
+//   let canvasObject = null as any
+//   if (points[0]?.y === points[1]?.y && points[2]?.y === points[3]?.y && points[0]?.x === points[3]?.x && points[1]?.x - points[2]?.x) {
+//     path = path.replace(/L\s$/g, 'z')
+//     canvasObject = new fabric.Path(path, {
+//       left: data[0],
+//       top: data[1],
+//       stroke: state.color,
+//       selectable: false,
+//       strokeWidth: state.drawWidth,
+//       fill: 'rgba(255, 255, 255, 0)',
+//       hasControls: false
+//     })
+//   } else {
+//     canvasObject = new fabric.Polygon(points, {
+//       stroke: state.color,
+//       strokeWidth: state.drawWidth,
+//       fill: 'rgba(255, 255, 255, 0)',
+//       opacity: 1,
+//       hasBorders: false,
+//       hasControls: false,
+//       evented: false
+//     })
+//   }
+//   canvasObject['points'] = points
+//   state.canvas.add(canvasObject)
+// }
+
+const selectLastObject = () => {
+  let objects = state.canvas.getObjects()
+  state.activeTarget = objects[objects.length - 1]
+  state.activeTarget.set('hasRotatingPoint', true)
+  state.canvas.setActiveObject(state.activeTarget)
+  state.canvas.renderAll()
 }
-const drawImageObj = data => {
-  let path = 'M '
-  // debugger
-  let points = [] as any
-  let len = data.length / 2
-  for (let i = 0; i < len; i++) {
-    let idx = i * 2
-    points.push({ x: data[idx], y: data[idx + 1] })
-    path += `${data[idx]} ${data[idx + 1]} L `
-  }
-  let canvasObject = null as any
-  if (points[0]?.y === points[1]?.y && points[2]?.y === points[3]?.y && points[0]?.x === points[3]?.x && points[1]?.x - points[2]?.x) {
-    path = path.replace(/L\s$/g, 'z')
-    canvasObject = new fabric.Path(path, {
-      left: data[0],
-      top: data[1],
-      stroke: state.color,
-      selectable: false,
-      strokeWidth: state.drawWidth,
-      fill: 'rgba(255, 255, 255, 0)',
-      hasControls: false
-    })
+
+const rotate = val => {
+  if (!state.activeTarget) {
+    ElMessage.error('请先选择旋转对象!')
   } else {
-    canvasObject = new fabric.Polygon(points, {
-      stroke: state.color,
-      strokeWidth: state.drawWidth,
-      fill: 'rgba(255, 255, 255, 0)',
-      opacity: 1,
-      hasBorders: false,
-      hasControls: false,
-      evented: false
-    })
+    state.activeTarget.rotate(state.activeTarget.angle + val)
+    state.canvas.renderAll()
   }
-  canvasObject['points'] = points
-  state.canvas.add(canvasObject)
 }
+
 const drawTypeChange = e => {
+  state.dragMode = false
+  state.activeTarget = null
   state.drawType = e
   state.canvas.skipTargetFind = !!e
   if (e == 'pen') {
@@ -198,12 +334,36 @@ const drawTypeChange = e => {
 }
 // 鼠标按下时触发
 const mousedown = e => {
+  if (state.dragMode) {
+    state.isDragging = true
+    state.lastPosX = e.e.clientX - state.canvas.getElement().offsetLeft
+    state.lastPosY = e.e.clientY - state.canvas.getElement().offsetTop
+    return
+  }
+  // console.log(state.canvas.getObjects()[1])
+  // console.log(e)
+  const target = e.target
+  // console.log(target)
+  if (target) {
+    if (target.type === 'rect') {
+      state.activeTarget = target
+      state.activeTarget.set('hasRotatingPoint', true)
+      state.canvas.setActiveObject(state.activeTarget)
+      return
+    } else {
+      state.activeTarget = null
+      // state.canvas.discardActiveObject()
+    }
+  }
+  // console.log(state.lastPosX, state.lastPosY)
+  // console.log(state.canvas.viewportTransform)
   // 记录鼠标按下时的坐标
   let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
-
-  state.mouseFrom.x = xy.x
-  state.mouseFrom.y = xy.y
+  // console.log(xy)
+  state.mouseFrom.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
+  state.mouseFrom.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
   state.doDrawing = true
+  // console.log(state.drawType)
   // 绘制多边形
   if (state.drawType == 'polygon') {
     state.canvas.skipTargetFind = false
@@ -226,9 +386,19 @@ const mousedown = e => {
 }
 // 鼠标松开执行
 const mouseup = e => {
+  if (state.dragMode) {
+    state.isDragging = false
+    return
+  }
+
+  if (state.activeTarget) {
+    state.activeTarget.setCoords()
+    state.canvas.requestRenderAll()
+    return
+  }
   let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
-  state.mouseTo.x = xy.x
-  state.mouseTo.y = xy.y
+  state.mouseTo.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
+  state.mouseTo.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
   state.drawingObject = null
   state.moveCount = 1
   if (state.drawType != 'polygon' && state.drawType != 'line') {
@@ -242,6 +412,43 @@ const mouseup = e => {
 }
 //鼠标移动过程中已经完成了绘制
 const mousemove = e => {
+  if (state.dragMode) {
+    if (state.isDragging) {
+      const deltaMove = {
+        x: e.e.clientX - state.canvas.getElement().offsetLeft - state.lastPosX,
+        y: e.e.clientY - state.canvas.getElement().offsetTop - state.lastPosY
+      }
+      state.lastPosX = e.e.clientX - state.canvas.getElement().offsetLeft
+      state.lastPosY = e.e.clientY - state.canvas.getElement().offsetTop
+      state.canvas.relativePan(new fabric.Point(deltaMove.x, deltaMove.y))
+      // state.canvas.viewportTransform[4] += deltaMove.x
+      // state.canvas.viewportTransform[5] += deltaMove.y
+      // state.canvas.getObjects().forEach(obj => {
+      //   if (obj.type !== 'rect') {
+      //     return
+      //   }
+      //   obj.left = (obj.left - state.canvas.viewportTransform[4]) / state.radio;
+      //   obj.top = (obj.top - state.canvas.viewportTransform[5]) / state.radio;
+      //   obj.setCoords();
+      //   obj.setOptions({
+      //     selectable: true,
+      //     evented: true
+      //   });
+      // });
+      state.canvas.requestRenderAll();
+    }
+    return
+  }
+
+  if (state.activeTarget) {
+    // const pointer = state.canvas.getPointer(e.e)
+    // state.activeTarget.set({
+    //   left: pointer.x,
+    //   top: pointer.y
+    // });
+    state.canvas.requestRenderAll()
+    return
+  }
   if (state.moveCount % 2 && !state.doDrawing) {
     //减少绘制频率
     return
@@ -249,8 +456,8 @@ const mousemove = e => {
   state.moveCount++
   let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
   if (xy.y >= 0 && xy.x <= props.cWidth && xy.y >= 0 && xy.y <= props.cHeight) {
-    state.mouseTo.x = xy.x
-    state.mouseTo.y = xy.y
+    state.mouseTo.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
+    state.mouseTo.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
     // 矩形
     if (state.drawType == 'rectangle') {
       if (state.mouseFrom.x < state.mouseTo.x && state.mouseFrom.y < state.mouseTo.y) {
@@ -314,14 +521,31 @@ const drawing = () => {
     mouseFrom.y +
     ' z'
   state.rectPath = path
-  canvasObject = new fabric.Path(path, {
+  canvasObject = new fabric.Rect({
     left: left,
     top: top,
+    width: Math.abs(mouseTo.x - left),
+    height: Math.abs(mouseTo.y - top),
     stroke: state.color,
-    selectable: false,
     strokeWidth: state.drawWidth,
     fill: 'rgba(255, 255, 255, 0)',
-    hasControls: false
+    centeredRotation: true,
+    angle: 0,
+    label: props.classDef.label
+  })
+  // canvasObject = new fabric.Path(path, {
+  //   left: left,
+  //   top: top,
+  //   stroke: state.color,
+  //   selectable: false,
+  //   strokeWidth: state.drawWidth,
+  //   fill: 'rgba(255, 255, 255, 0)',
+  //   hasControls: false
+  // })
+  // console.log(canvasObject)
+  canvasObject.on('selected', event => {
+    // console.log("selected")
+    state.activeTarget = event.target
   })
   if (canvasObject) {
     state.canvas.add(canvasObject)
@@ -445,8 +669,11 @@ const generatePolygon = () => {
   // })
   // state.canvas.add(polygon)
   let points = [{}]
-  console.log('state.pointArray', state.pointArray)
-
+  // console.log('state.pointArray', state.pointArray)
+  for (let point in state.pointArray) {
+    point = state.pointArray[point]
+    // console.log(point.left, point.top)
+  }
   state.pointArray.map(point => {
     points.push({
       x: point.left,
@@ -535,10 +762,18 @@ const getData = () => {
   // if (state.lineArray.length > 0 && state.lineArray.length < 4) {
   //   clearPolygonLines()
   // }
+  datas.push(JSON.stringify(state.canvas.toJSON(['label'])))
+  datas.push(state.canvas.toDataURL('image/png'))
+
   state.canvas.getObjects().forEach((item, index) => {
+    // console.log(item, index)
     if (index > 0) {
       let aCoords = item.aCoords
-      let point = {}
+      let point = {
+        nodes: [],
+        angle: item.angle,
+        label: item.label
+      }
       if (item.points) {
         item.points.forEach((item, idx) => {
           // if (aCoords[marks[idx]]) {
@@ -558,17 +793,25 @@ const getData = () => {
             x: getPoint(aCoords[mark].x),
             y: getPoint(aCoords[mark].y)
           }
-          point[mark] = getRealPoint(poi)
+          // console.log(poi)
+          poi = getRealPoint(poi)
+          // console.log(poi)
+          poi.x = poi.x / state.realRadioX / 1920
+          poi.y = poi.y / state.realRadioY / 1080
+          // console.log(poi)
+          point['nodes'].push(poi)
+          // point['nodes'].push(getRealPoint(poi))
         })
       }
-
-      if (item.points && item.points.length === 2) {
-        delete point['br']
-        delete point['bl']
-      }
+      // if (item.points && item.points.length === 2) {
+      //   delete point['br']
+      //   delete point['bl']
+      // }
       datas.push(point)
+      // console.log(point)
     }
   })
+  // console.log()
   return datas
 }
 // 获取归一化数据比例

+ 211 - 38
src/views/demo/data/index.vue

@@ -2,7 +2,8 @@
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataApi" :init-param="initParam">
       <template #yuan="scope">
-        <el-image style="width: 100px" :src="'/api' + scope.row.url" @click="markImg(scope.row)" />
+        <uploadImg :is-show-data="true" :disabled="true" :image-url="scope.row.url" />
+<!--        <el-image style="width: 100px" :src="'/api' + scope.row.url" @click="markImg(scope.row)" />-->
       </template>
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
@@ -23,6 +24,7 @@
       <!-- 表格操作 -->
       <template #operation="scope">
         <!-- <el-button type="primary" link :icon="EditPen" v-auth="['demo:data:edit']" @click="openDialog(2, '数据标注', scope.row)"> 标注 </el-button> -->
+        <el-button type="primary" link :icon="EditPen" v-auth="['demo:data:edit']" @click="markImg(scope.row)"> 标注 </el-button>
         <el-button type="primary" link :icon="EditPen" v-auth="['demo:data:edit']" @click="openDialog(2, '数据编辑', scope.row)"> 编辑 </el-button>
         <el-button type="primary" link :icon="View" v-auth="['demo:data:query']" @click="openDialog(3, '数据查看', scope.row)"> 查看 </el-button>
         <el-button type="primary" link :icon="Delete" v-auth="['demo:data:remove']" @click="deleteData(scope.row)"> 删除 </el-button>
@@ -31,12 +33,22 @@
 
     <FormDialog ref="formDialogRef" />
     <ImportPicDataset ref="dialogRef" />
-    <ImgDetect ref="imgDetect" :img="cover" :area="area" :width="width" :height="height" @success="handleImgSuccess"></ImgDetect>
+    <ImgDetect
+      ref="imgDetect"
+      :img="cover"
+      :area="area"
+      :width="width"
+      :height="height"
+      @success="handleImgSuccess"
+      :classes="classes"
+      :json-data="jsonData"
+    >
+    </ImgDetect>
   </div>
 </template>
 
 <script setup lang="tsx" name="Data">
-import { ref, reactive, toRefs } from 'vue'
+import { ref, reactive, toRefs, onMounted } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -46,9 +58,10 @@ import ImportPicDataset from '@/components/ImportPicDataset/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { Delete, EditPen, Download, Upload, View, CirclePlus } from '@element-plus/icons-vue'
 // import { fabric } from 'fabric'
-import { useDrawArea } from '@/utils/fabric'
+// import { useDrawArea } from '@/utils/fabric'
 import { getDictsApi } from '@/api/modules/system/dictData'
 import ImgDetect from '../components/img-detect.vue'
+import uploadImg from '@/components/Upload/Img.vue'
 import {
   listDataApi,
   delDataApi,
@@ -59,14 +72,43 @@ import {
   exportDataApi,
   getDataApi
 } from '@/api/modules/demo/data'
+import { listDataApi as listDictDataApi } from '@/api/modules/system/dictData'
+import { uploadPure } from '@/api/modules/upload'
+import http from '@/api'
+
+onMounted (() => {
+  state.cacheData.url = 'http://localhost:9090/profile/upload/2024/08/08/144745610/test.png'
+  // handleImgSuccess({
+  //   data: 'test change data',
+  //   jsonData: 'jsonData12345 test change',
+  //   url: 'testurl'
+  // })
+})
+
 const imgDetect = ref()
 const state = reactive({
   area: '' as string,
   width: 1920 as number,
   height: 1080 as number,
-  cover: ''
+  cover: '',
+  classes: [],
+  //   [
+  //   {
+  //     name: '飞机',
+  //     color: '#ea5413',
+  //     label: 'plane'
+  //   },
+  //   {
+  //     name: '汽车',
+  //     color: '#ff00ff',
+  //     label: 'car'
+  //   }
+  // ],
+  jsonData: [],
+  cacheData: {}
+    // '{"version":"5.3.0","objects":[{"type":"image","version":"5.3.0","originX":"left","originY":"top","left":0,"top":0,"width":918,"height":789,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":0.68,"scaleY":0.68,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"cropX":0,"cropY":0,"src":"http://localhost:8848/api/profile/upload/2024/08/08/144745610/3-3.jpg","crossOrigin":null,"filters":[]},{"type":"rect","version":"5.3.0","originX":"left","originY":"top","left":181.38,"top":251.38,"width":244,"height":162,"fill":"rgba(255, 255, 255, 0)","stroke":"#E34F51","strokeWidth":5,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":-25,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0}]}'
 })
-const { area, width, height, cover } = toRefs(state)
+const { area, width, height, cover, classes, jsonData } = toRefs(state)
 
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
@@ -83,45 +125,176 @@ const deleteData = async (params: any) => {
 
 // 标注图片
 const markImg = data => {
-  state.cover = '/api' + data.url
-  // area 代表后端的传来的标注数据
-  const area = []
-  if (state.cover != '') {
-    if (area.length != 0) {
-      handleImgSuccess(area)
-    }
-    imgDetect.value.visible = true
-  } else {
+  // console.log(data)
+  state.cacheData = data
+  state.jsonData = []
+    if (!data.url || data.url.length === 0) {
     ElMessage.warning('缺失图像,暂时不能进行区域添加!')
+    return
   }
-}
-const getList = () => {
-  useDrawArea({
-    src: state.cover,
-    width: state.width,
-    height: state.height,
-    area: state.area
+
+  listDictDataApi({
+    pageNum: 1,
+    pageSize: 10,
+    dictType: 'class_definition'
   })
-    .then(url => {
-      state.cover = url as string
+    .then(res => {
+      // console.log(res)
+      state.classes = []
+      for (let i = 0; i < res.data.list.length; i++) {
+        state.classes.push({
+          name: res.data.list[i].dictLabel,
+          color: res.data.list[i].cssClass,
+          label: res.data.list[i].dictValue
+        })
+      }
+
+      if (state.cacheData.labelurl && state.cacheData.labelurl.length > 0) {
+        // console.log('get label jsonData')
+        http.get<any>(state.cacheData.labelurl)
+          .then(res => {
+            state.jsonData = []
+            console.log(res)
+            let arr = res.split('\r\n')
+            console.log(arr)
+            for (let i = 0; i < arr.length; i++) {
+              let subArr = arr[i].split(' ')
+              // console.log(subArr)
+              let cssVal = '#000000'
+              let label = '-1'
+              for (let j = 0; j < state.classes.length; j++) {
+                if (state.classes[j].label === subArr[0]) {
+                  cssVal = state.classes[j].color
+                  label = state.classes[j].label
+                  break
+                }
+              }
+              state.jsonData.push({
+                pathString: 'M ' + subArr[1] * 1920 + ' ' + subArr[2] * 1080 +
+                  ' L ' + subArr[3] * 1920 + ' ' + subArr[4] * 1080 +
+                  ' L ' + subArr[5] * 1920 + ' '  + subArr[6] * 1080 +
+                  ' L ' + subArr[7] * 1920 + ' '  + subArr[8] * 1080 +
+                  ' z',
+                // left: 0,
+                // top: 0,
+                fill: '',
+                stroke: cssVal,
+                strokeWidth: 5,
+                label: label
+              })
+            }
+            // console.log(state.jsonData)
+            imgDetect.value.visible = true
+          })
+          .catch(err => {
+            console.log(err)
+          })
+      } else {
+        imgDetect.value.visible = true
+      }
     })
-    .catch(error => {
-      console.log(error)
+    .catch(err => {
+      console.log(err)
     })
+
+  state.cover = '/api' + data.url
+
+  // area 代表后端的传来的标注数据
+  // console.log(state.cover)
+
+  // const area = []
+  // if (state.cover != '') {
+  //   if (area.length != 0) {
+  //     handleImgSuccess(area)
+  //   }
+  //
+  //   console.log("true???")
+  // } else {
+  //   ElMessage.warning('缺失图像,暂时不能进行区域添加!')
+  // }
 }
+// const getList = () => {
+//   useDrawArea({
+//     src: state.cover,
+//     width: state.width,
+//     height: state.height,
+//     area: state.area
+//   })
+//     .then(url => {
+//       state.cover = url as string
+//     })
+//     .catch(error => {
+//       console.log(error)
+//     })
+// }
 const handleImgSuccess = data => {
-  data.forEach(item => {
-    const area = item.split(';')
-    // try=tly blx=tlx brx=trx bry=bly
-    const tlx = Math.round(Number(area[0]) * 1920)
-    const tly = Math.round(Number(area[1]) * 1080)
-    const trx = tlx + Math.round(Number(area[2]) * 1920)
-    const bly = tly + Math.round(Number(area[3]) * 1080)
-    state.area += `${tlx};${tly};${trx};${tly};${trx};${bly};${tlx};${bly},`
-  })
-  // console.log('state.area', state.area)
-  state.area.slice(0, -1)
-  getList()
+  // console.log(data)
+  state.jsonData = data['jsonData']
+  state.cover = data['url']
+
+  state.cacheData.labelurl = data['data']
+
+  state.cacheData.increment = 'NONE'
+
+  let arr = state.cacheData.url.split('upload')
+  // console.log(arr)
+  let filenames = (arr[arr.length - 1]).split('.')
+  filenames[filenames.length - 1] = 'txt'
+  // console.log(filenames.join('.'))
+  let filename = filenames.join('.')
+  filename = filename.replace(/\\/g, "/")
+  if (filename.startsWith('/')) {
+    filename = filename.substring(1)
+  }
+  // console.log(filename)
+
+  labelFile(data['data'], filename)
+    .then(res => {
+      // console.log(res)
+      if (res.code === 200) {
+        state.cacheData.labelurl = res.data.url
+        updateDataApi(state.cacheData)
+          .then(res => {
+            // console.log(res)
+            if (res.data) {
+              ElMessage.success('操作成功')
+            }
+          })
+          .catch(err => {
+            console.log(err)
+          })
+      }
+    })
+
+  // data.forEach(item => {
+  //   if (item.startsWith('{')) {
+  //     return
+  //   }
+  //   const area = item.split(';')
+  //   // try=tly blx=tlx brx=trx bry=bly
+  //   // mark: 当前用的应该是左上角定点位置和长宽,应该替换为存储点
+  //   const tlx = Math.round(Number(area[0]) * 1920)
+  //   const tly = Math.round(Number(area[1]) * 1080)
+  //   const trx = tlx + Math.round(Number(area[2]) * 1920)
+  //   const bly = tly + Math.round(Number(area[3]) * 1080)
+  //   state.area += `${tlx};${tly};${trx};${tly};${trx};${bly};${tlx};${bly},`
+  // })
+  // // console.log('state.area', state.area)
+  // state.area.slice(0, -1)
+  // getList()
+}
+
+// 创建并提交标注txt文件
+const labelFile = (data, filename) => {
+  // 创建Blob对象
+  const blob = new Blob([data], { type: 'text/plain' })
+
+  // 创建表单数据并添加文件
+  const formData = new FormData()
+
+  formData.append('file', blob, filename)
+
+  return uploadPure(formData)
 }
 
 const labeledTypeData = [