吉安感知网项目-前端
chenyao
2026-07-31 150ba60e3436b3e6a418b0a66723938b5dc2cfb8
uniapps/work-app/src/pages/work/index.vue
@@ -1,18 +1,23 @@
<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
   <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="[1, 2].includes(item.attachmentType)"
                     :src="item?.aiImg || item?.eventImageUrl"
                     mode="aspectFill"
@@ -21,88 +26,112 @@
                  <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"
         :style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }"
      ></canvas>
  </div>
   </div>
</template>
<script setup>
import { getCurrentInstance, nextTick } 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 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\"}]"
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
   height: 1,
})
const parseAiFrame = aiFrameSource => {
@@ -125,26 +154,29 @@
         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) => {
@@ -200,202 +232,224 @@
}
const getDataList = () => {
  if (loading.value || !hasMore.value) return
   if (loading.value || !hasMore.value) return
  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 })
   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(async res => {
         const resData = res.data.data
         const response = resData.records
         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
    // } 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 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
         }else if(tab.key === 'myTickets'){
            tab.badge.value = (myCount || 0).toString()
        tab.badge.showZero = true
         // 根据当前页码决定是替换还是追加数据
         if (listParams.value.current === 1) {
            dataList.value = list
         } else {
            dataList.value = [...dataList.value, ...list]
         }
         // 判断是否还有更多数据
         if (list.length < listParams.value.size || resData.current >= resData.pages) {
            hasMore.value = false
         } else {
            hasMore.value = true
         }
      })
      .finally(() => {
         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) => {
  currentTab.value = item.key
const handleClick = item => {
   currentTab.value = item.key
   listParams.value.eventStatus = tabStatusMap[item.key] ?? ''
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
   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 = val => {
   uni.navigateTo({
      url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''),
   })
}
// 搜索功能
const handleSearch = () => {
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
   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()
   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()
   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;
@@ -422,61 +476,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;