吉安感知网项目-前端
罗广辉
2026-08-13 1d552a3bad95caae4af15322df28e6240b797693
uniapps/work-app/src/pages/work/index.vue
@@ -1,110 +1,146 @@
<template>
  <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }">
    <div class="searchTop">
      <up-search placeholder="请输入关键字搜索"  :animation="true"  v-model="listParams.keyword" :show-action="false" @search="handleSearch" @clear="handleClear"></up-search>
    </div>
    <div class="listBox">
      <div class="tabs-container">
        <up-tabs :list="tabList" @click="handleClick"></up-tabs>
      </div>
<!-- @scrolltolower="loadMore" -->
      <scroll-view class="eventBox" scroll-y :lower-threshold="80" >
        <div class="eventList">
          <div class="eventItem"  v-for="(item,index) in dataList" :key="index">
            <image
                     v-if="[1, 2].includes(item.attachmentType)"
                     :src="item?.aiImg || item?.eventImageUrl"
                     mode="aspectFill"
   <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }">
      <div class="searchTop">
         <up-search
            placeholder="请输入关键字搜索"
            :animation="true"
            v-model="listParams.keyword"
            :show-action="false"
            @search="handleSearch"
            @clear="handleClear"
         ></up-search>
      </div>
      <div class="listBox">
         <div class="tabs-container">
            <up-tabs :scrollable="true" :list="tabList" @click="handleClick"></up-tabs>
         </div>
         <scroll-view class="eventBox" scroll-y :lower-threshold="80" @scrolltolower="loadMore">
            <div class="eventList">
               <div class="eventItem" v-for="(item, index) in dataList" :key="index">
                  <image v-if="item.isImage" :src="item.thumbnail" mode="aspectFill" @click="detailHandle(item)" />
                  <div
                     v-else-if="item.isVideo"
                     class="videoBox"
                     :style="item.thumbnailUrl ? { backgroundImage: `url(${item.thumbnailUrl})`, backgroundSize: 'cover', backgroundPosition: 'center' } : {}"
                     @click="detailHandle(item)"
                  />
                  <div v-if="item.attachmentType === 3" class="videoBox" @click="detailHandle(item)">
                  >
                     <div class="playIcon"></div>
                  </div>
            <div class="informationDisplay">
                  <div class="informationDisplay">
                     <div class="itemTitle">{{ item.eventName }}</div>
              <div class="itemContent">{{ formatDate(item.createTime) }}</div>
            </div>
          </div>
        </div>
                     <div class="itemContent">{{ formatDate(item.createTime) }}</div>
                  </div>
               </div>
            </div>
        <!-- 暂无数据提示 -->
        <div class="noData" v-if="!loading && dataList.length === 0">
          <text>暂无数据</text>
        </div>
            <!-- 暂无数据提示 -->
            <div class="noData" v-if="!loading && dataList.length === 0">
               <text>暂无数据</text>
            </div>
        <!-- 加载提示 -->
        <div class="loadingMore">
          <text v-if="loading">加载中...</text>
        </div>
      </scroll-view>
    </div>
            <!-- 加载提示 -->
            <div class="loadingMore">
               <text v-if="loading">加载中...</text>
               <text v-else-if="!hasMore">没有更多了</text>
            </div>
         </scroll-view>
      </div>
      <canvas
         canvas-id="ai-image-canvas"
         id="ai-image-canvas"
         class="ai-image-canvas"
         :width="canvasSize.width"
         :height="canvasSize.height"
         :style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }"
      ></canvas>
  </div>
   </div>
</template>
<script setup>
import { getCurrentInstance, nextTick } from 'vue'
import { getCurrentInstance } from 'vue'
import { useUserStore } from '@/store/index.js'
import {getGdList,getstatusCount} from '@/api/work/index.js'
import dayjs from 'dayjs';
import { getStatusBarHeight } from '@/utils/common';
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([])
const currentTab=ref('all')
const currentTab = ref('all')
const loading = ref(false)
const hasMore = ref(true)
const PREVIEW_IMG_CACHE_KEY_PREFIX = 'work-detail-ai-img-'
const imageTaskMap = new Map()
let imageRenderQueue = Promise.resolve()
const tabList = ref([
  {
    name: '全部工单',
    key: 'all',
    badge: {
      value: 0,
      showZero: true
    },
  },
  {
    name: '我的工单',
    key: 'myTickets',
    badge: {
      value: 0,
      showZero: true
    }
  }
   {
      name: '全部工单',
      key: 'all',
      badge: {
         value: 0,
         showZero: true,
      },
   },
   {
      name: '我的工单',
      key: 'myTickets',
      badge: {
         value: 0,
         showZero: true,
      },
   },
   {
      name: '处置中',
      key: 'disposing',
      badge: {
         value: 0,
         showZero: true,
      },
   },
   {
      name: '已处置',
      key: 'handled',
      badge: {
         value: 0,
         showZero: true,
      },
   },
   {
      name: '已完成',
      key: 'completed',
      badge: {
         value: 0,
         showZero: true,
      },
   },
])
const formatDate = (dateString) => {
  return dayjs(dateString).format('MM/DD HH:mm');
};
// tab key 与事件状态码映射
const tabStatusMap = {
   disposing: 1,
   handled: 3,
   completed: 4,
}
const formatDate = dateString => {
   return dayjs(dateString).format('MM/DD HH:mm')
}
const listParams = ref({
  current: 1,
  size: 12,
  source: 1,
  department:'',
  keyword:''
   current: 1,
   size: 12,
   source: 1,
   department: '',
   keyword: '',
   eventStatus: '',
})
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\"}]"
let requestSeq = 0 // 请求序列号,用于丢弃快速切换tab时的过期响应
let imageGeneration = 0 // 图片任务批次,用于取消已离开列表的绘制任务
const MAX_CANVAS_EDGE = 2048
const canvasSize = ref({
   width: 1,
   height: 1
   width: MAX_CANVAS_EDGE,
   height: MAX_CANVAS_EDGE,
})
// 解析算法框数据
const parseAiFrame = aiFrameSource => {
   if (!aiFrameSource) return []
   if (Array.isArray(aiFrameSource)) return aiFrameSource
@@ -117,6 +153,7 @@
   }
}
// 获取图片信息
const getImageInfo = src =>
   new Promise(resolve => {
      uni.getImageInfo({
@@ -125,41 +162,40 @@
         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)
      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 drawAiImage = async (imageInfo, aiFrame, scaleX = 1, scaleY = 1) => {
   if (!imageInfo?.path || !imageInfo.width || !imageInfo.height || !aiFrame.length) return ''
   const canvasScale = Math.min(1, MAX_CANVAS_EDGE / imageInfo.width, MAX_CANVAS_EDGE / imageInfo.height)
   const width = Math.round(imageInfo.width * canvasScale)
   const height = Math.round(imageInfo.height * canvasScale)
   const boxScaleX = scaleX * canvasScale
   const boxScaleY = scaleY * canvasScale
   const ctx = uni.createCanvasContext('ai-image-canvas', canvasOwner)
   ctx.drawImage(imageInfo.path, 0, 0, width, height)
@@ -168,8 +204,10 @@
      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 scaledWidth = boxWidth * boxScaleX
      const scaledHeight = boxHeight * boxScaleY
      const x = x_cen * boxScaleX - scaledWidth / 2
      const y = y_cen * boxScaleY - scaledHeight / 2
      const label = item.class_name || ''
      const fontSize = Math.max(18, Math.round(width / 80))
      const labelHeight = fontSize + 10
@@ -178,7 +216,7 @@
      ctx.setStrokeStyle('#FF3B30')
      ctx.setLineWidth(lineWidth)
      ctx.strokeRect(x, y, boxWidth, boxHeight)
      ctx.strokeRect(x, y, scaledWidth, scaledHeight)
      if (label) {
         ctx.setFontSize(fontSize)
@@ -194,213 +232,336 @@
   return new Promise(resolve => {
      ctx.draw(false, async () => {
         const tempFilePath = await canvasToTempFilePath(width, height)
         resolve(tempFilePath || url)
         resolve(tempFilePath)
      })
   })
}
const getDataList = () => {
  if (loading.value || !hasMore.value) return
// 生成缩略图地址
const getThumbnailUrl = url => {
   if (!url) return ''
   return url.replace(/(\.[^./?]+)(\?.*)?$/, '_thumbnail$1$2')
}
  loading.value = true
  const params = {
    current: listParams.value.current,
    size: listParams.value.size,
    keyword:listParams.value.keyword,
    onlyMine:currentTab.value=== 'myTickets' ? 1 : 0
  }
  getGdList(params).then(async res => {
    const response = res.data.data
      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 })
         }
// 前置整理列表媒体字段
const normalizeWorkItem = item => {
   const isImage = [1, 2].includes(item.attachmentType)
   const aiFrame = item.attachmentType === 2 ? parseAiFrame(item.geojson) : []
   return {
      ...item,
      isImage,
      isVideo: item.attachmentType === 3,
      thumbnail: item.eventImageUrl,
      previewUrl: item.eventImageUrl,
      thumbnailSource: isImage ? getThumbnailUrl(item.eventImageUrl) : '',
      aiFrame,
      imageReady: !aiFrame.length,
   }
}
// 串行绘制单条 AI 原图和缩略图
const enqueueAiImages = (item, originalImageInfo, thumbnailImageInfo, generation) => {
   const task = imageRenderQueue.then(async () => {
      if (generation !== imageGeneration) return
      const previewUrl = (await drawAiImage(originalImageInfo, item.aiFrame)) || item.eventImageUrl
      if (generation !== imageGeneration) return
      item.previewUrl = previewUrl
      if (!thumbnailImageInfo) {
         item.thumbnail = previewUrl
         return
      }
      dataList.value = list
    // 根据当前页码决定是替换还是追加数据
    // if (listParams.value.current === 1) {
    //   dataList.value = response
    // } else {
    //   dataList.value = [...dataList.value, ...response]
    // }
    // // 判断是否还有更多数据
    // if (response.length < listParams.value.size || response.current >= response.pages) {
    //   hasMore.value = false
    // } else {
    //   hasMore.value = true
    // }
  }).finally(() => {
    loading.value = false
  })
      const scaleX = thumbnailImageInfo.width / originalImageInfo.width
      const scaleY = thumbnailImageInfo.height / originalImageInfo.height
      item.thumbnail = (await drawAiImage(thumbnailImageInfo, item.aiFrame, scaleX, scaleY)) || previewUrl
   })
   imageRenderQueue = task.catch(() => {})
   return task
}
const getstatusCountData=()=>{
  const params={
    keyword:listParams.value.keyword,
  }
   getstatusCount(params).then(res=>{
      const response = res.data.data
       const {  totalCount,myCount } = response
      tabList.value.forEach(tab=>{
         if(tab.key === 'all'){
        tab.badge.value = (totalCount || 0).toString()
        tab.badge.showZero = true
// 异步准备单条记录的缩略图和 AI 图片
const prepareItemImages = async (item, generation) => {
   if (!item.isImage || !item.eventImageUrl) return
   const thumbnailImagePromise = getImageInfo(item.thumbnailSource)
   if (!item.aiFrame.length) {
      const thumbnailImageInfo = await thumbnailImagePromise
      if (generation === imageGeneration && thumbnailImageInfo) item.thumbnail = item.thumbnailSource
      return
   }
         }else if(tab.key === 'myTickets'){
            tab.badge.value = (myCount || 0).toString()
        tab.badge.showZero = true
         }
      })
   const [originalImageInfo, thumbnailImageInfo] = await Promise.all([
      getImageInfo(item.eventImageUrl),
      thumbnailImagePromise,
   ])
   if (generation !== imageGeneration) return
   if (thumbnailImageInfo) item.thumbnail = item.thumbnailSource
   if (!originalImageInfo) return
   return enqueueAiImages(item, originalImageInfo, thumbnailImageInfo, generation)
}
// 启动当前页图片后台处理任务
const startImageTasks = (list, generation) => {
   list.forEach(item => {
      const task = prepareItemImages(item, generation)
         .catch(() => {})
         .finally(() => {
            item.imageReady = true
         })
      if (item.id) imageTaskMap.set(item.id, task)
   })
}
const handleClick = (item) => {
  currentTab.value = item.key
// 获取工单列表
const getDataList = () => {
   if (!hasMore.value) return
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
   // 每次请求自增序列号,响应回来时校验是否过期
   const seq = ++requestSeq
   const generation = imageGeneration
   loading.value = true
   const params = {
      current: listParams.value.current,
      size: listParams.value.size,
      keyword: listParams.value.keyword,
      onlyMine: currentTab.value === 'myTickets' ? 1 : 0,
      eventStatus: listParams.value.eventStatus,
   }
   getGdList(params)
      .then(res => {
         // 已有新请求发出,丢弃过期响应
         if (seq !== requestSeq) return
         const resData = res.data.data
         const response = resData.records
         const list = response.map(normalizeWorkItem)
         let currentPageList = []
         // 根据当前页码决定是替换还是追加数据
         if (listParams.value.current === 1) {
            dataList.value = list
            currentPageList = dataList.value
         } else {
            const startIndex = dataList.value.length
            dataList.value = [...dataList.value, ...list]
            currentPageList = dataList.value.slice(startIndex)
         }
         // 判断是否还有更多数据
         if (list.length < listParams.value.size || resData.current >= resData.pages) {
            hasMore.value = false
         } else {
            hasMore.value = true
         }
         startImageTasks(currentPageList, generation)
      })
      .finally(() => {
         // 仅在最新一次请求结束时关闭loading,避免过早关闭
         if (seq === requestSeq) loading.value = false
      })
}
// 获取工单状态数量
const getstatusCountData = () => {
   const params = {
      keyword: listParams.value.keyword,
   }
   getstatusCount(params).then(res => {
      const response = res.data.data
      const { totalCount, myCount, disposingCount, disposedCount, completedCount } = response
      tabList.value.forEach(tab => {
         if (tab.key === 'all') {
            tab.badge.value = (totalCount || 0).toString()
            tab.badge.showZero = true
         } else if (tab.key === 'myTickets') {
            tab.badge.value = (myCount || 0).toString()
            tab.badge.showZero = true
         } else if (tab.key === 'disposing') {
            tab.badge.value = (disposingCount || 0).toString()
            tab.badge.showZero = true
         } else if (tab.key === 'handled') {
            tab.badge.value = (disposedCount || 0).toString()
            tab.badge.showZero = true
         } else if (tab.key === 'completed') {
            tab.badge.value = (completedCount || 0).toString()
            tab.badge.showZero = true
         }
      })
   })
}
// 切换工单状态
const handleClick = item => {
   imageGeneration++
   imageTaskMap.clear()
   currentTab.value = item.key
   listParams.value.eventStatus = tabStatusMap[item.key] ?? ''
   listParams.value.current = 1
   hasMore.value = true
   dataList.value = []
   getDataList()
}
// 加载更多工单
const loadMore = () => {
  if (loading.value || !hasMore.value) return
  listParams.value.current++
  getDataList()
   if (loading.value || !hasMore.value) return
   listParams.value.current++
   getDataList()
}
const detailHandle = (val) => {
  uni.navigateTo({
    url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''),
  })
// 缓存预览原图后跳转详情页
const detailHandle = async val => {
   const imageTask = imageTaskMap.get(val.id)
   if (val.aiFrame.length && !val.imageReady && imageTask) {
      uni.showLoading({ title: '图片处理中', mask: true })
      try {
         await imageTask
      } finally {
         uni.hideLoading()
      }
   }
   if (val.aiFrame.length) {
      uni.setStorageSync(`${PREVIEW_IMG_CACHE_KEY_PREFIX}${val.id}`, val.previewUrl)
   }
   uni.navigateTo({ url: `/subPackages/workDetail/index?id=${val.id}` })
}
// 搜索功能
const handleSearch = () => {
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
   imageGeneration++
   imageTaskMap.clear()
   listParams.value.current = 1
   hasMore.value = true
   dataList.value = []
   getDataList()
   getstatusCountData()
}
// 清除搜索
const handleClear = () => {
  listParams.value.keyword = ''
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
   imageGeneration++
   imageTaskMap.clear()
   listParams.value.keyword = ''
   listParams.value.current = 1
   hasMore.value = true
   dataList.value = []
   getDataList()
   getstatusCountData()
}
const topMargin = getStatusBarHeight()
// 页面显示时刷新工单列表
onShow(() => {
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
   imageGeneration++
   imageTaskMap.clear()
   listParams.value.current = 1
   hasMore.value = true
   dataList.value = []
   getDataList()
   getstatusCountData()
})
</script>
<style scoped lang="scss">
.eventTickets {
  padding: 0 20rpx;
  display: flex;
  flex-direction: column;
  height: 100%;
  box-sizing: border-box;
  width: 100%;
  overflow-x: hidden;
  .searchTop {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 24rpx;
    width: 100%;
    height: 108rpx;
    :deep(){
      .u-search__content{
        background: #fff !important;
      }
      .u-search__content__input{
        background: #fff !important;
      }
    }
  }
  :deep(){
   padding: 0 20rpx;
   display: flex;
   flex-direction: column;
   height: 100%;
   box-sizing: border-box;
   width: 100%;
   overflow-x: hidden;
   .searchTop {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-right: 24rpx;
      width: 100%;
      height: 108rpx;
      :deep() {
         .u-search__content {
            background: #fff !important;
         }
         .u-search__content__input {
            background: #fff !important;
         }
      }
   }
   :deep() {
      .u-tabs__wrapper__nav__line {
         // width: 80rpx !important;
         background: #1d6fe9 !important;
      }
      .u-badge {
         background-color: #1d6fe9 !important;
         margin-top: -14px !important;
         margin-left: -5px !important;
      }
   }
   tabs-container {
      display: flex;
      align-items: center;
      height: 98rpx;
      width: 100%;
      overflow-x: auto; // 横向滚动
      overflow-y: hidden;
      white-space: nowrap; // 防止换行
      -webkit-overflow-scrolling: touch; // 移动端平滑滚动
    .u-tabs__wrapper__nav__line{
      // width: 80rpx !important;
      background: #1D6FE9 !important;
    }
    .u-badge {
      background-color: #1d6fe9 !important;
      margin-top: -14px !important;
      margin-left: -5px !important;
    }
  }
      // 隐藏滚动条(可选)
      &::-webkit-scrollbar {
         height: 0;
         display: none;
      }
   }
   // 子元素不压缩
   .tabs-container > * {
      flex-shrink: 0;
   }
   .listBox {
      position: relative;
      display: flex;
      flex-direction: column;
      flex: 1;
      width: 100%;
      box-sizing: border-box;
   }
   .eventBox {
      padding: 20rpx 0;
      overflow-y: auto;
      overflow-x: hidden;
      height: 0;
      flex-grow: 1;
      align-content: flex-start;
      width: 100%;
      box-sizing: border-box;
   }
  .tabs-container{
    display: flex;
    justify-content: center;
    height: 98rpx;
  }
  .listBox {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1;
    width: 100%;
    box-sizing: border-box;
  }
  .eventBox {
    padding: 20rpx 0;
    overflow-y: auto;
    overflow-x: hidden;
    height: 0;
    flex-grow: 1;
    align-content: flex-start;
    width: 100%;
    box-sizing: border-box;
  }
   .eventList {
      display: flex;
      flex-wrap: wrap;
      gap: 20rpx;
      width: 100%;
      box-sizing: border-box;
      justify-content: space-between;
  .eventList {
    display: flex;
    flex-wrap: wrap;
    gap: 20rpx;
    width: 100%;
    box-sizing: border-box;
    justify-content: space-between;
      .eventItem {
         width: calc(50% - 10rpx);
         background-color: #fff;
         border-radius: 12rpx;
         overflow: hidden;
         position: relative;
         box-sizing: border-box;
         max-width: 100%;
    .eventItem {
      width: calc(50% - 10rpx);
      background-color: #fff;
      border-radius: 12rpx;
      overflow: hidden;
      position: relative;
      box-sizing: border-box;
      max-width: 100%;
      image,
         image,
         .videoBox {
        width: 100%;
        height: 208rpx;
        border-radius: 12rpx;
        overflow: hidden;
            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;
            padding-bottom: 48rpx;
            box-sizing: border-box;
         }
         .playIcon {
            width: 72rpx;
@@ -422,61 +583,60 @@
               border-left: 22rpx solid #fff;
            }
         }
      .informationDisplay{
        width: 100%;
        position: absolute;
        bottom: 3px;
        background: rgba(11,11,11,0.35);
        border-radius: 0rpx 0rpx 12rpx 12rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 10rpx 14rpx;
         .informationDisplay {
            width: 100%;
            position: absolute;
            bottom: 3px;
            background: rgba(11, 11, 11, 0.35);
            border-radius: 0rpx 0rpx 12rpx 12rpx;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 10rpx 14rpx;
            box-sizing: border-box;
        .itemTitle {
          flex: 1;
            .itemTitle {
               flex: 1;
               min-width: 0;
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 500;
          font-size: 24rpx;
          color: #FFFFFF;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
        .itemContent {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 400;
          font-size: 24rpx;
          color: #FFFFFF;
          margin-left: 12rpx;
               font-family: Source Han Sans CN, Source Han Sans CN;
               font-weight: 500;
               font-size: 24rpx;
               color: #ffffff;
               white-space: nowrap;
               overflow: hidden;
               text-overflow: ellipsis;
            }
        }
      }
    }
  }
            .itemContent {
               font-family: Source Han Sans CN, Source Han Sans CN;
               font-weight: 400;
               font-size: 24rpx;
               color: #ffffff;
               margin-left: 12rpx;
               white-space: nowrap;
            }
         }
      }
   }
  .loadingMore {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 80rpx;
    font-size: 28rpx;
    color: #999;
    width: 100%;
  }
   .loadingMore {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 80rpx;
      font-size: 28rpx;
      color: #999;
      width: 100%;
   }
  .noData {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 300rpx;
    font-size: 28rpx;
    color: #999;
    width: 100%;
  }
   .noData {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 300rpx;
      font-size: 28rpx;
      color: #999;
      width: 100%;
   }
   .ai-image-canvas {
      position: fixed;