吉安感知网项目-前端
chenyao
2026-06-30 4b3c357f2fa24a27eac3fe59cbf64e34255ab212
uniapps/work-app/src/pages/work/index.vue
@@ -12,10 +12,18 @@
      <scroll-view class="eventBox" scroll-y :lower-threshold="80" >
        <div class="eventList">
          <div class="eventItem"  v-for="(item,index) in dataList" :key="index">
            <img :src="item.eventImageUrl" alt="" @click="detailHandle(item)" />
            <image
                     v-if="[1, 2].includes(item.attachmentType)"
                     :src="item?.aiImg || item?.eventImageUrl"
                     mode="aspectFill"
                     @click="detailHandle(item)"
                  />
                  <div v-if="item.attachmentType === 3" class="videoBox" @click="detailHandle(item)">
                     <div class="playIcon"></div>
                  </div>
            <div class="informationDisplay">
              <!--              <div class="itemTitle">{{item.event_name}}</div>-->
              <div class="itemContent">{{formatDate(item.createTime) }}</div>
                     <div class="itemTitle">{{ item.eventName }}</div>
              <div class="itemContent">{{ formatDate(item.createTime) }}</div>
            </div>
          </div>
        </div>
@@ -32,6 +40,12 @@
        </div>
      </scroll-view>
    </div>
      <canvas
         canvas-id="ai-image-canvas"
         id="ai-image-canvas"
         class="ai-image-canvas"
         :style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }"
      ></canvas>
  </div>
@@ -39,10 +53,13 @@
<script setup>
import { getCurrentInstance, nextTick } from 'vue'
import { useUserStore } from '@/store/index.js'
import {getGdList,getstatusCount} from '/src/api/work/index.js'
import {getGdList,getstatusCount} from '@/api/work/index.js'
import dayjs from 'dayjs';
import { getStatusBarHeight } from '@/utils/common';
const canvasOwner = getCurrentInstance()?.proxy
const userStore = useUserStore()
const userInfo = userStore.userInfo
const dataList = ref([])
@@ -82,6 +99,106 @@
  keyword:''
})
const mockGeojson = "[{\"score\":0.89990234375,\"bbox\":{\"x_cen\":195.5,\"y_cen\":326.5,\"width\":117.0,\"height\":265.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.89306640625,\"bbox\":{\"x_cen\":1194.5,\"y_cen\":559.5,\"width\":115.0,\"height\":261.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.88720703125,\"bbox\":{\"x_cen\":179.0,\"y_cen\":955.5,\"width\":124.0,\"height\":249.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.88330078125,\"bbox\":{\"x_cen\":1198.5,\"y_cen\":260.5,\"width\":115.0,\"height\":285.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.84716796875,\"bbox\":{\"x_cen\":204.5,\"y_cen\":71.5,\"width\":115.0,\"height\":143.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.83203125,\"bbox\":{\"x_cen\":186.0,\"y_cen\":657.5,\"width\":114.0,\"height\":269.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.78662109375,\"bbox\":{\"x_cen\":1205.5,\"y_cen\":49.5,\"width\":117.0,\"height\":99.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}]"
const canvasSize = ref({
   width: 1,
   height: 1
})
const parseAiFrame = aiFrameSource => {
   if (!aiFrameSource) return []
   if (Array.isArray(aiFrameSource)) return aiFrameSource
   try {
      return JSON.parse(aiFrameSource)
   } catch (error) {
      console.log(error)
      return []
   }
}
const getImageInfo = src =>
   new Promise(resolve => {
      uni.getImageInfo({
         src,
         success: resolve,
         fail: error => {
            console.log(error)
            resolve(null)
         }
      })
   })
const canvasToTempFilePath = (width, height) =>
   new Promise(resolve => {
      uni.canvasToTempFilePath({
         canvasId: 'ai-image-canvas',
         width,
         height,
         destWidth: width,
         destHeight: height,
         fileType: 'jpg',
         quality: 0.92,
         success: res => resolve(res.tempFilePath),
         fail: error => {
            console.log(error)
            resolve('')
         }
      }, canvasOwner)
   })
const drawAiImage = async (url, aiFrameSource) => {
   if (!url) return url
   const aiFrame = parseAiFrame(aiFrameSource)
   if (!aiFrame.length) return url
   const imageInfo = await getImageInfo(url)
   if (!imageInfo?.path || !imageInfo.width || !imageInfo.height) return url
   const width = Math.round(imageInfo.width)
   const height = Math.round(imageInfo.height)
   canvasSize.value = { width, height }
   await nextTick()
   const ctx = uni.createCanvasContext('ai-image-canvas', canvasOwner)
   ctx.drawImage(imageInfo.path, 0, 0, width, height)
   aiFrame.forEach(item => {
      const { x_cen, y_cen, width: boxWidth, height: boxHeight } = item.bbox || {}
      if ([x_cen, y_cen, boxWidth, boxHeight].some(value => typeof value !== 'number')) return
      const x = x_cen - boxWidth / 2
      const y = y_cen - boxHeight / 2
      const label = item.class_name || ''
      const fontSize = Math.max(18, Math.round(width / 80))
      const labelHeight = fontSize + 10
      const labelY = y - labelHeight >= 0 ? y - labelHeight : y
      const lineWidth = Math.max(3, Math.round(width / 640))
      ctx.setStrokeStyle('#FF3B30')
      ctx.setLineWidth(lineWidth)
      ctx.strokeRect(x, y, boxWidth, boxHeight)
      if (label) {
         ctx.setFontSize(fontSize)
         const labelWidth = label.length * fontSize + 16
         ctx.setFillStyle('#FF3B30')
         ctx.fillRect(x, labelY, labelWidth, labelHeight)
         ctx.setFillStyle('#FFFFFF')
         ctx.setTextBaseline('middle')
         ctx.fillText(label, x + 8, labelY + labelHeight / 2)
      }
   })
   return new Promise(resolve => {
      ctx.draw(false, async () => {
         const tempFilePath = await canvasToTempFilePath(width, height)
         resolve(tempFilePath || url)
      })
   })
}
const getDataList = () => {
  if (loading.value || !hasMore.value) return
@@ -92,9 +209,19 @@
    keyword:listParams.value.keyword,
    onlyMine:currentTab.value=== 'myTickets' ? 1 : 0
  }
  getGdList(params).then(res => {
  getGdList(params).then(async res => {
    const response = res.data.data
    dataList.value = response
      const list = []
      for (const item of response) {
         // const aiImg = await drawAiImage(item.eventImageUrl, mockGeojson)
         if (item.attachmentType !== 2) {
            list.push(item)
         }else{
            const aiImg = await drawAiImage(item.eventImageUrl, item.geojson)
            list.push({ ...item, aiImg })
         }
      }
      dataList.value = list
    // 根据当前页码决定是替换还是追加数据
    // if (listParams.value.current === 1) {
    //   dataList.value = response
@@ -150,7 +277,7 @@
const detailHandle = (val) => {
  uni.navigateTo({
    url: `/subPackages/workDetail/index?id=${val.id}`,
    url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''),
  })
}
@@ -261,12 +388,40 @@
      box-sizing: border-box;
      max-width: 100%;
      img {
      image,
         .videoBox {
        width: 100%;
        height: 208rpx;
        border-radius: 12rpx;
        overflow: hidden;
            display: block;
      }
         .videoBox {
            background: linear-gradient(135deg, #eef4ff 0%, #dfe8f8 100%);
            display: flex;
            align-items: center;
            justify-content: center;
         }
         .playIcon {
            width: 72rpx;
            height: 72rpx;
            border-radius: 50%;
            background: rgba(29, 111, 233, 0.9);
            position: relative;
            box-shadow: 0 8rpx 18rpx rgba(29, 111, 233, 0.24);
            &::after {
               content: '';
               position: absolute;
               left: 29rpx;
               top: 20rpx;
               width: 0;
               height: 0;
               border-top: 16rpx solid transparent;
               border-bottom: 16rpx solid transparent;
               border-left: 22rpx solid #fff;
            }
         }
      .informationDisplay{
        width: 100%;
        position: absolute;
@@ -275,14 +430,16 @@
        border-radius: 0rpx 0rpx 12rpx 12rpx;
        display: flex;
        align-items: center;
        justify-content: flex-end;
        padding: 10rpx 0rpx 10rpx 0rpx;
        justify-content: space-between;
        padding: 10rpx 14rpx;
            box-sizing: border-box;
        .itemTitle {
          width: 144rpx;
          flex: 1;
               min-width: 0;
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 500;
          font-size: 28rpx;
          color: #000000;
          font-size: 24rpx;
          color: #FFFFFF;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
@@ -293,7 +450,8 @@
          font-weight: 400;
          font-size: 24rpx;
          color: #FFFFFF;
          padding-right: 14rpx;
          margin-left: 12rpx;
               white-space: nowrap;
        }
      }
@@ -319,5 +477,12 @@
    color: #999;
    width: 100%;
  }
   .ai-image-canvas {
      position: fixed;
      left: -9999px;
      top: -9999px;
      pointer-events: none;
   }
}
</style>