From 4b3c357f2fa24a27eac3fe59cbf64e34255ab212 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Tue, 30 Jun 2026 11:56:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/task-work-order/src/views/orderView/flyingHand/index.vue                             |   15 
 uniapps/work-app/src/manifest.json                                                                |   13 
 applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue           |   72 +-
 applications/drone-command/src/page/index/top/index.vue                                           |   26 
 uniapps/work-app/src/pages/work/index.vue                                                         |  193 ++++++
 applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue          |   67 ++
 applications/mobile-web-view/src/appPages/work/workDetail/index.vue                               |  124 ++++
 pnpm-lock.yaml                                                                                    |   18 
 applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue                 |    9 
 uniapps/work-app/src/config/env.js                                                                |    4 
 applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue            |  174 +++---
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue     |  114 +++-
 uniapps/work-app/package.json                                                                     |    8 
 applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue                  |   30 
 applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue                |   95 ++-
 applications/task-work-order/src/components/VideoPlayDialog.vue                                   |  120 +++
 applications/task-work-order/README.md                                                            |    5 
 applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue           |   62 +-
 applications/drone-command/index.html                                                             |    4 
 applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue |    6 
 README.md                                                                                         |    4 
 applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue      |    8 
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue     |   31 
 applications/task-work-order/src/views/orderView/orderManage/taskTypeOptions.js                   |   52 -
 package.json                                                                                      |    2 
 packages/utils/common/index.js                                                                    |   66 ++
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue          |  125 +++-
 applications/mobile-web-view/README.md                                                            |    8 
 applications/mobile-web-view/src/appComponents/workMap/index.vue                                  |  111 ++++
 uniapps/work-app/src/subPackages/workDetail/index.vue                                             |   14 
 30 files changed, 1,129 insertions(+), 451 deletions(-)

diff --git a/README.md b/README.md
index b869402..c69fa39 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
 
 ## 环境要求
 - Node.js >= 20
-- pnpm >= 9
+- pnpm >= 9.15.9
 
 ## 快速开始
 ```bash
@@ -44,4 +44,4 @@
 - `applications/drone-app-web-view/README.md`
 
 ## 备注
-- 目前未配置测试框架,`pnpm test` 会直接退出。
\ No newline at end of file
+- 目前未配置测试框架,`pnpm test` 会直接退出。
diff --git a/applications/drone-command/index.html b/applications/drone-command/index.html
index f676e39..2b8b39e 100644
--- a/applications/drone-command/index.html
+++ b/applications/drone-command/index.html
@@ -23,11 +23,11 @@
 	<!-- 高德地图api更新必须配合安全密钥使用 -->
 	<script>
 		window._AMapSecurityConfig = {
-			securityJsCode: '1cd61831ddce188f3129b761f57024fc',
+			securityJsCode: 'xxx',
 		}
 	</script>
 	<script type="text/javascript"
-					src='https://webapi.amap.com/maps?v=2.0&key=eb0654440644f710110547d2132b2fbe&plugin=AMap.PlaceSearch,AMap.Geocoder'></script>
+					src='https://webapi.amap.com/maps?v=2.0&key=xxx&plugin=AMap.PlaceSearch,AMap.Geocoder'></script>
 	<script src="https://webapi.amap.com/ui/1.1/main.js?v=1.0.11"></script>
 	<title>吉安市低空经济服务一体化平台</title>
 </head>
diff --git a/applications/drone-command/src/page/index/top/index.vue b/applications/drone-command/src/page/index/top/index.vue
index cacc24e..4859868 100644
--- a/applications/drone-command/src/page/index/top/index.vue
+++ b/applications/drone-command/src/page/index/top/index.vue
@@ -8,8 +8,8 @@
 
       <div class="header-right">
         <nav class="nav-menu">
-          <div v-for="(item, index) in topMenus" :key="index" class="nav-item"
-            :class="{ active: item.active }" @click="handleMenuClick(item)">
+          <div v-for="(item, index) in topMenus" :key="index" class="nav-item" :class="{ active: item.active }"
+            @click="handleMenuClick(item)">
             <span>{{ item.label }}</span>
           </div>
         </nav>
@@ -135,7 +135,7 @@
         confirmButtonClass: 'command-message-box-confirm',
         cancelButtonClass: 'command-message-box-cancel',
       }).then(() => {
-				logOutFun()
+        logOutFun()
       })
     },
     jumpMH () {
@@ -249,14 +249,22 @@
             background: url('@/assets/images/active-menu-item.png') no-repeat center / 136px 34px;
 
             span {
-              background: linear-gradient(180deg,
-                  #fff 22.11%,
-                  #ffffff 86.69%);
-              background-size: contain;
-              background-clip: text;
-              -webkit-text-fill-color: transparent;
+              /* 降级方案:所有浏览器先显示白色文字 */
+              color: #ffffff;
+
+              /* 发光效果 */
               text-shadow: 0px 0px 13px 0px #5da6ef73;
             }
+
+            /* 支持渐变文字的浏览器再启用 */
+            @supports (-webkit-background-clip: text) {
+              span {
+                background: linear-gradient(180deg, #fff 22.11%, #ffffff 86.69%);
+                background-size: contain;
+                background-clip: text;
+                -webkit-text-fill-color: transparent;
+              }
+            }
           }
         }
       }
diff --git a/applications/mobile-web-view/README.md b/applications/mobile-web-view/README.md
index 6b2625b..c222c4c 100644
--- a/applications/mobile-web-view/README.md
+++ b/applications/mobile-web-view/README.md
@@ -29,3 +29,11 @@
 
 ## 目录约定
 - 组件/页面使用 PascalCase 命名。
+
+
+
+## 打包部署流程
+
+打包,根目录下的 package.json , build:test:mobile-web-view
+
+连接shuixiong服务器  目录 /software/service/drone/web/mobile-web-view
\ No newline at end of file
diff --git a/applications/mobile-web-view/src/appComponents/workMap/index.vue b/applications/mobile-web-view/src/appComponents/workMap/index.vue
index 88313b7..71bf4e0 100644
--- a/applications/mobile-web-view/src/appComponents/workMap/index.vue
+++ b/applications/mobile-web-view/src/appComponents/workMap/index.vue
@@ -5,12 +5,25 @@
 					<van-image width="20" height="20" :src="locationIcon" />
 					<div class="label">定位</div>
 				</div>
+				<div class="nav-btn" @click="openNavigation" v-show="navigationTarget">
+					<van-image width="20" height="20" :src="mapnavIcon" />
+					<div class="label">导航</div>
+				</div>
+				<van-action-sheet
+					v-model:show="navigationSheetShow"
+					:actions="navigationActions"
+					cancel-text="取消"
+					@select="handleNavigationSelect"
+				/>
 
+
+				
 	</div>
 </template>
 
 <script setup>
 import { searchGeocoder } from '@/utils/util'
+import { showToast } from 'vant'
 import mapnavIcon from '@/appDataSource/appwork/mapnav.svg'
 import incidentPoint from '@/appDataSource/appwork/positioning1.svg'
 import userLocationIcon from '@/appDataSource/leafletMapIcon/user-location.svg'
@@ -51,6 +64,21 @@
 	})
 const currentAddress = ref('')
 const AMAP_KEY = '5b8ba312d053e4bdb44911733ece7d63'
+const navigationSheetShow = ref(false)
+const navigationActions = [{ name: '高德地图' }, { name: '百度地图' }, { name: '腾讯地图' }]
+const navigationTarget = computed(() => {
+	if (!workNavigationShow || !mapCurrentDetail?.longitude || !mapCurrentDetail?.latitude) return null
+
+	const lat = parseFloat(mapCurrentDetail.latitude)
+	const lng = parseFloat(mapCurrentDetail.longitude)
+	if (Number.isNaN(lat) || Number.isNaN(lng)) return null
+
+	return {
+		lat,
+		lng,
+		name: mapCurrentDetail.eventLocation || mapCurrentDetail.address || '工单位置',
+	}
+})
 
 const basemap0 = L.layerGroup([basemapLayer0, basemapLayer1])
 const basemap1 = L.layerGroup([basemapLayer2, basemapLayer3])
@@ -175,6 +203,42 @@
 	}
 }
 
+function openNavigation() {
+	if (!navigationTarget.value) {
+		showToast('暂无可导航的位置')
+		return
+	}
+
+	navigationSheetShow.value = true
+}
+
+function handleNavigationSelect(action) {
+	navigationSheetShow.value = false
+	const url = getNavigationUrl(action.name, navigationTarget.value)
+	if (url) {
+		window.location.href = url
+	}
+}
+
+function getNavigationUrl(type, target) {
+	const [gcjLng, gcjLat] = wgs84ToGcj02(target.lng, target.lat)
+	const name = encodeURIComponent(target.name)
+
+	if (type === '高德地图') {
+		return `https://uri.amap.com/navigation?to=${gcjLng},${gcjLat},${name}&mode=car&policy=1&coordinate=gaode&callnative=1`
+	}
+
+	if (type === '百度地图') {
+		return `https://api.map.baidu.com/direction?destination=latlng:${gcjLat},${gcjLng}|name:${name}&mode=driving&coord_type=gcj02&output=html&src=ja-web`
+	}
+
+	if (type === '腾讯地图') {
+		return `https://apis.map.qq.com/uri/v1/routeplan?type=drive&tocoord=${gcjLat},${gcjLng}&to=${name}&referer=ja-web`
+	}
+
+	return ''
+}
+
 let lastLocationMarker = null
 const getMapLocation = async () => {
 	const customIcon = L.icon({
@@ -248,6 +312,11 @@
 // 存储事件点标记实例,用于后续更新或移除
 let incidentMarker = null
 
+function formatCoordinate(value) {
+	const numberValue = Number(value)
+	return Number.isNaN(numberValue) ? value : numberValue.toFixed(6)
+}
+
 // 添加事件点标记
 function addIncidentMarker(data) {
 	// 检查地图和标记层是否已初始化
@@ -284,6 +353,19 @@
 
 	// 存储关联数据
 	incidentMarker.options.customData = data
+	incidentMarker.bindTooltip(
+		`<div class="coordinate-label">
+			<div>经度:${formatCoordinate(lng)}</div>
+			<div>纬度:${formatCoordinate(lat)}</div>
+		</div>`,
+		{
+			permanent: true,
+			direction: 'top',
+			offset: L.point(0, -16),
+			opacity: 1,
+			className: 'incident-coordinate-tooltip',
+		}
+	)
 
 	// 定位到该标记点
 	mapSetView({
@@ -359,7 +441,8 @@
 		width: 100%;
 		height: 100%;
 	}
-	.location-btn{
+	.location-btn,
+	.nav-btn{
 		display: flex;
 		flex-direction: column;
 		justify-content: center;
@@ -380,6 +463,9 @@
 	.location-btn {
 		bottom: 76px;
 	}
+	.nav-btn {
+		bottom: 28px;
+	}
 }
 
 // 位置标记闪烁效果
@@ -387,6 +473,29 @@
 	animation: blink 1s ease-in-out;
 }
 
+:deep(.incident-coordinate-tooltip) {
+	padding: 0;
+	border: 0;
+	background: transparent;
+	box-shadow: none;
+
+	&::before {
+		display: none;
+	}
+}
+
+:deep(.coordinate-label) {
+	padding: 4px 7px;
+	background: rgba(255, 255, 255, 0.95);
+	border: 1px solid rgba(76, 133, 255, 0.35);
+	border-radius: 4px;
+	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
+	font-size: 11px;
+	line-height: 16px;
+	color: #222324;
+	white-space: nowrap;
+}
+
 @keyframes blink {
 	0% {
 		transform: scale(1);
diff --git a/applications/mobile-web-view/src/appPages/work/workDetail/index.vue b/applications/mobile-web-view/src/appPages/work/workDetail/index.vue
index 56e9f74..3ed2566 100644
--- a/applications/mobile-web-view/src/appPages/work/workDetail/index.vue
+++ b/applications/mobile-web-view/src/appPages/work/workDetail/index.vue
@@ -2,12 +2,20 @@
 	<div class="workDetailContainer">
 		<div class="detailTop">
 			<div class="image-container">
-				<van-swipe :autoplay="3000" indicator-color="#4C85FF">
-				<van-swipe-item v-for="(img, index) in getImageList" :key="index">
-					<van-image class="detailImage" :src="img" fit="cover" width="100%" height="235px"
-						@click="openPreview(index)" preview-visible="false" />
-				</van-swipe-item>
-			</van-swipe>
+				<video
+					v-if="isVideoAttachment && mediaSrc"
+					ref="videoRef"
+					class="video-js vjs-default-skin vjs-big-play-centered detailVideo"
+					controls
+					preload="auto"
+					playsinline
+				></video>
+				<van-swipe v-else :autoplay="3000" indicator-color="#4C85FF">
+					<van-swipe-item v-for="(img, index) in [mediaSrc]" :key="index">
+						<van-image class="detailImage" :src="img" fit="cover" width="100%" height="235px"
+							@click="openPreview(index)" preview-visible="false" />
+					</van-swipe-item>
+				</van-swipe>
 			</div>
 		</div>
 	<!-- 工单内容 -->
@@ -15,6 +23,10 @@
       <div class="workOrderContent">
         <div class="workOrderTitle">工单内容</div>
         <div class="workOrderContainer">
+					<div class="orderRow">
+						<div class="rowTitle">工单名称</div>
+						<div>{{ workDetailData.eventName }}</div>
+					</div>
           <div class="orderRow">
             <div class="rowTitle">工单编号</div>
             <div>{{ workDetailData.eventNum }}</div>
@@ -59,11 +71,18 @@
 import { showToast, showNotify, showImagePreview } from 'vant'
 import { getShowImg, getSmallImg } from '@/utils/util'
 import { useRoute,useRouter } from 'vue-router'
+import { getAiImg } from '@ztzf/utils'
+import videojs from 'video.js'
+import 'video.js/dist/video-js.css'
 const keyword = ref('')
 const route = useRoute()
 const router = useRouter()
 
 const workDetailData = ref({})
+const videoRef = ref(null)
+let player = null
+
+const isVideoAttachment = computed(() => Number(workDetailData.value.attachmentType) === 3)
 
 // 预览图片
 const getImageList = computed(() => {
@@ -75,11 +94,10 @@
 	}
 	return imageArr
 })
-const openPreview = index => {
-	const detail = workDetailData.value
-	const showUrl = detail.eventImageUrl
+const openPreview = () => {
+	if (isVideoAttachment.value || !mediaSrc.value) return
 	showImagePreview({
-		images: [showUrl],
+		images: [mediaSrc.value],
 		startPosition: 0,
 	})
 }
@@ -88,21 +106,94 @@
 router.push({
 		path: '/webViewWrapper/mapWork',
 		query: {
-			currentItem: JSON.stringify(item)  
+			currentItem: JSON.stringify(item)
 		}
 	})
 }
+
+function replaceWithProxy(url) {
+	if (!url) return ''
+	return url.replace(
+		/^https?:\/\/[^/]+/,
+		'https://wrj.shuixiongit.com/ja-proxy'
+	)
+}
+
+function getVideoType(url) {
+	const path = (url || '').split('?')[0].toLowerCase()
+	if (path.endsWith('.m3u8')) return 'application/x-mpegURL'
+	if (path.endsWith('.mp4')) return 'video/mp4'
+	if (path.endsWith('.webm')) return 'video/webm'
+	if (path.endsWith('.ogg') || path.endsWith('.ogv')) return 'video/ogg'
+	if (path.endsWith('.mov')) return 'video/quicktime'
+	return ''
+}
+
+function getVideoSource() {
+	const type = getVideoType(mediaSrc.value)
+	return {
+		src: mediaSrc.value,
+		...(type ? { type } : {}),
+	}
+}
+
+function destroyPlayer() {
+	if (!player) return
+	player.dispose()
+	player = null
+}
+
+async function initPlayer() {
+	if (!isVideoAttachment.value || !mediaSrc.value) return
+	await nextTick()
+	if (!videoRef.value) return
+
+	if (player) {
+		player.src(getVideoSource())
+		player.load()
+		return
+	}
+
+	player = videojs(videoRef.value, {
+		controls: true,
+		preload: 'auto',
+		autoplay: false,
+		fluid: false,
+		sources: [getVideoSource()],
+	})
+	player.on('error', () => {
+		console.error('视频播放失败:', player?.error(), mediaSrc.value)
+	})
+}
+
+const mediaSrc = ref('')
+
+watch([isVideoAttachment, mediaSrc], () => {
+	if (isVideoAttachment.value) {
+		initPlayer()
+	} else {
+		destroyPlayer()
+	}
+})
 
 onMounted(async () => {
 	keyword.value = JSON.parse(route.query.workDetailData)
 	try {
 		const res = await getSharingDetailsApi({ cacheKey: keyword.value })
 		workDetailData.value = res.data.data
+		let {eventImageUrl,geojson} = workDetailData.value
+		// http://220.177.172.27:8100 改为 https://wrj.shuixiongit.com/ja-proxy
+		eventImageUrl = replaceWithProxy(eventImageUrl)
+		if (eventImageUrl){
+			mediaSrc.value = !isVideoAttachment.value && geojson ? await getAiImg(eventImageUrl,geojson) : eventImageUrl
+		}
 	} catch (error) {
 		showToast('分享链接失效')
 	}
 
 })
+
+onBeforeUnmount(destroyPlayer)
 </script>
 <style lang="scss" scoped>
 .workDetailContainer {
@@ -110,12 +201,23 @@
 		.image-container {
 			position: relative;
 			width: 100%;
+			height: 235px;
 			.detailImage {
 				width: 100%;
 				height: 100%;
 				display: block;
 				object-fit: cover;
 			}
+
+			.detailVideo {
+				width: 100%;
+				height: 235px;
+			}
+
+			:deep(.video-js .vjs-tech) {
+				width: 100%;
+				height: 100%;
+			}
 		}
 	}
 
diff --git a/applications/task-work-order/README.md b/applications/task-work-order/README.md
index 0fdf49b..f3389b3 100644
--- a/applications/task-work-order/README.md
+++ b/applications/task-work-order/README.md
@@ -25,3 +25,8 @@
 ## 资源与约定
 - 组件/页面使用 PascalCase 命名。
 - 静态资源放在 `public/` 或 `src/assets/`。
+
+## 打包部署
+根目录package.json  ,build:prod:task-work-order
+手动上传打包后文件
+连接服务器  /data/wwwtask
diff --git a/applications/task-work-order/src/components/VideoPlayDialog.vue b/applications/task-work-order/src/components/VideoPlayDialog.vue
index f6975d5..8e8e86b 100644
--- a/applications/task-work-order/src/components/VideoPlayDialog.vue
+++ b/applications/task-work-order/src/components/VideoPlayDialog.vue
@@ -1,56 +1,130 @@
 <script setup>
+import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
+import videojs from 'video.js'
+
 const props = defineProps({
 	width: {
 		type: String,
-		default: '800'
+		default: '800',
 	},
 	modelValue: {
 		type: Boolean,
-		default: false
+		default: false,
 	},
 	playUrl: {
 		type: String,
-		default: ''
+		default: '',
 	},
-	title:{
+	title: {
 		type: String,
-		default: '视频播放'
-	}
+		default: '视频播放',
+	},
 })
 
 const emit = defineEmits(['update:modelValue'])
-const handleClose = () => {
-	emit('update:modelValue', false)
+const videoRefs = ref(null)
+let player = null
+
+const dialogVisible = computed({
+	get: () => props.modelValue,
+	set: value => emit('update:modelValue', value),
+})
+
+function getVideoType(url) {
+	const path = (url || '').split('?')[0].toLowerCase()
+	if (path.endsWith('.m3u8')) return 'application/x-mpegURL'
+	if (path.endsWith('.mp4')) return 'video/mp4'
+	if (path.endsWith('.webm')) return 'video/webm'
+	if (path.endsWith('.ogg') || path.endsWith('.ogv')) return 'video/ogg'
+	if (path.endsWith('.mov')) return 'video/quicktime'
+	return ''
 }
+
+function destroyPlayer() {
+	if (!player) return
+	player.dispose()
+	player = null
+}
+
+function getSource() {
+	const type = getVideoType(props.playUrl)
+	return {
+		src: props.playUrl,
+		...(type ? { type } : {}),
+	}
+}
+
+async function initPlayer() {
+	if (!props.modelValue || !props.playUrl) return
+	await nextTick()
+	if (!videoRefs.value) return
+
+	if (player) {
+		player.src(getSource())
+		player.load()
+		return
+	}
+
+	player = videojs(videoRefs.value, {
+		controls: true,
+		preload: 'auto',
+		autoplay: false,
+		fluid: false,
+		width: '100%',
+		height: '600px',
+		sources: [getSource()],
+	})
+	player.on('error', () => {
+		console.error('视频播放失败:', player?.error(), props.playUrl)
+	})
+}
+
+watch(
+	() => props.playUrl,
+	() => {
+		if (!props.modelValue || !props.playUrl) return
+		initPlayer()
+	}
+)
+
+onBeforeUnmount(destroyPlayer)
 </script>
 
 <template>
 	<el-dialog
-		class="gd-dialog"
+		class="gd-dialog video-dialog"
 		:title="props.title"
 		append-to-body
-		v-model="props.modelValue"
+		v-model="dialogVisible"
 		width="60%"
 		destroy-on-close
 		:close-on-click-modal="false"
-		@close="handleClose"
+		@opened="initPlayer"
+		@closed="destroyPlayer"
 	>
-		<video ref="videoRefs" controls preload="auto"  :src="props.playUrl">
-			<source :src="props.playUrl" type="video/mp4" />
-		</video>
-		<!--			<el-carousel trigger="click" :autoplay="false" @change="handleCarouselChange">-->
-		<!--				<el-carousel-item v-for="(item, index) in videoList" :key="index">-->
-		<!--					<video ref="videoRefs" controls preload="auto"  :src="currentVideoUrl" @play="handleVideoPlay" @ended="handleVideoEnded(index)">-->
-		<!--						<source :src="item.smallUrl" type="video/mp4" />-->
-		<!--					</video>-->
-		<!--				</el-carousel-item>-->
-		<!--			</el-carousel>-->
+		<video
+			ref="videoRefs"
+			id="videoPlayDialogPlayer"
+			class="video-js vjs-default-skin vjs-big-play-centered"
+			controls
+			preload="auto"
+			playsinline
+		></video>
 	</el-dialog>
 </template>
 
+<style lang="scss">
+.video-dialog {
+	.video-js {
+		height: 600px;
+		width: 100%;
+	}
+}
+</style>
+
 <style scoped lang="scss">
-video {
-	height: 600px;
+:deep(.video-js .vjs-tech) {
+	height: 100%;
 	width: 100%;
 }
 </style>
diff --git a/applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue
index 79eaf5d..97cec03 100644
--- a/applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue
@@ -4,11 +4,7 @@
 		<!--		<div class="detail-row-view" v-if="dialogReadonly">-->
 		<el-table :data="detailsList" class="gd-dialog-table">
 			<el-table-column type="index" show-overflow-tooltip label="序号" />
-			<el-table-column prop="deviceStatus" show-overflow-tooltip label="设备状态" >
-				<template v-slot="{ row }">
-					{{ getStatusText(row.deviceStatus) }}
-				</template>
-			</el-table-column>
+			<el-table-column prop="deviceStatus" show-overflow-tooltip label="设备状态" />
 			<el-table-column prop="occurTime" show-overflow-tooltip label="发生时间" />
 		</el-table>
 		<div class="gd-pagination-parent">
diff --git a/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue b/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue
index 6dd91c9..0def9b4 100644
--- a/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue
+++ b/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue
@@ -30,7 +30,7 @@
       </el-col>
 	  <el-col :span="12">
         <div class="label">擅长任务类型</div>
-        <div class="val">{{ getDictLabel(formData.skilledTaskType, dictObj.workOrderType) }}</div>
+        <div class="val">{{ getTaskTypeLabel(formData.skilledTaskType, workOrderTypeXT) }}</div>
       </el-col>
 	  <el-col :span="12">
         <div class="label">项目经验</div>
@@ -159,20 +159,17 @@
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="擅长任务类型" prop="skilledTaskType">
-						<el-select
-							class="gd-select"
-							popper-class="gd-select-popper"
+						<el-cascader
+							class="gd-cascader"
+							popper-class="gd-cascader-popper"
 							v-model="formData.skilledTaskType"
+							:options="workOrderTypeXT"
+							:props="{ ...taskTypeCascaderProps, multiple: true }"
 							placeholder="请选择"
 							:disabled="dialogType === 'view'"
-						>
-							<el-option
-								v-for="item in dictObj.workOrderType"
-								:key="item.dictKey"
-								:label="item.dictValue"
-								:value="item.dictKey"
-							/>
-						</el-select>
+							clearable
+							collapse-tags
+						/>
 					</el-form-item>
 				</el-col>
 				<el-col :span="24">
@@ -207,6 +204,10 @@
 import { submitFlyingHand } from './flyingHandApi'
 import { ElMessage } from 'element-plus'
 import { getDictLabel } from '@ztzf/utils'
+import {
+	getTaskTypeLabel,
+	taskTypeCascaderProps,
+} from '../orderManage/taskTypeOptions'
 
 import { fieldRules } from '@ztzf/utils'
 
@@ -227,6 +228,7 @@
 })
 
 const dictObj = inject('dictObj') // 擅长任务类型
+const workOrderTypeXT = inject('workOrderTypeXT', ref([]))
 
 const flyingHandList = inject('flyingHandList') // 飞手列表
 const flyingHandEditList = inject('flyingHandEditList') // 飞手建档列表
@@ -262,6 +264,7 @@
 	projectExperience: '',
 	technicalStrength: '',
 	skilledUavType: '',
+	skilledTaskType: [],
 	certification: [], // 证书
 	regionCode: '', // 区域编码
 	flyerAddress: '', // 飞手地址
@@ -318,6 +321,7 @@
 		if (newData) {
 			Object.assign(formData.value, newData)
 			formData.value.flightHours = Number(newData.flightHours)
+			formData.value.skilledTaskType = newData.skilledTaskType || []
 		}
 	},
 	{ deep: true, immediate: true }
@@ -353,7 +357,7 @@
 		projectExperience: '',
 		technicalStrength: '',
 		skilledUavType: '',
-		skilledTaskType: ''
+		skilledTaskType: []
 	})
 }
 
diff --git a/applications/task-work-order/src/views/orderView/flyingHand/index.vue b/applications/task-work-order/src/views/orderView/flyingHand/index.vue
index f73f9aa..3dddd19 100644
--- a/applications/task-work-order/src/views/orderView/flyingHand/index.vue
+++ b/applications/task-work-order/src/views/orderView/flyingHand/index.vue
@@ -52,7 +52,7 @@
 			<div class="gd-table-toolbar">
 				<el-button :icon="Plus" color="#4C34FF" type="primary" @click="addFlyingHand">飞手建档</el-button>
 			</div>
-			
+
 			<div class="gd-table-content gd-table-content-bg">
 				<el-table class="gd-table" :data="list">
 					<el-table-column type="index" width="64" label="序号" />
@@ -72,7 +72,7 @@
 					</el-table-column>
 					<el-table-column prop="skilledTaskType" show-overflow-tooltip label="擅长任务类型">
 						<template v-slot="{ row }">
-							{{ getDictLabel(row.skilledTaskType, dictObj.workOrderType) }}
+							{{ getTaskTypeLabel(row.skilledTaskType, workOrderTypeXT) }}
 						</template>
 					</el-table-column>
 					<el-table-column label="操作" class-name="operation-btns" width="180">
@@ -113,10 +113,14 @@
 import { getDictionaryByCode } from '@/api/system/dictbiz'
 import { getDictLabel } from '@ztzf/utils'
 import { getFlyingHandPage, removeFlyingHand,getAllFlyerIds } from './flyingHandApi'
-import { deviceFlyerApi,deviceFlyerDetailApi } from '@/api/zkxt'
+import { deviceFlyerApi,deviceFlyerDetailApi, getDictListApi } from '@/api/zkxt'
 import FlyingHandDialog from './FlyingHandDialog.vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { prod } from 'mathjs'
+import {
+	getTaskTypeLabel,
+	normalizeTaskTypeOptions,
+} from '../orderManage/taskTypeOptions'
 
 // 初始化查询参数
 const initSearchParams = () => ({
@@ -140,8 +144,10 @@
 	skilledUavType: [], // 擅长机型
 	technicalStrength: [] // 技术特长
 })
+const workOrderTypeXT = ref([])
 
 provide('dictObj', dictObj)
+provide('workOrderTypeXT', workOrderTypeXT)
 
 // 获取列表
 async function getList() {
@@ -229,6 +235,9 @@
 	getDictionaryByCode('workOrderType,skilledUavType,technicalStrength').then(res => {
 		dictObj.value = res.data.data
 	})
+	getDictListApi('task_inspection_type').then(res => {
+		workOrderTypeXT.value = normalizeTaskTypeOptions(res.data.data || [])
+	})
 }
 
 // 获取飞手列表
diff --git a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue
index e4b392e..6ee6ffc 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue
@@ -11,6 +11,11 @@
 		<el-form class="gd-dialog-form" ref="formRef" :model="formData" :rules="rules" label-width="100px">
 			<el-row>
 				<el-col :span="24">
+					<el-form-item label="事件名称" prop="eventName">
+						<el-input class="gd-input" v-model="formData.eventName" placeholder="请输入" clearable />
+					</el-form-item>
+				</el-col>
+				<el-col :span="24">
 					<el-form-item label="处置部门" prop="disposeDept">
 						<el-tree-select
 							class="gd-select"
@@ -58,6 +63,7 @@
 
 // 初始化表单数据
 const initForm = () => ({
+	eventName: '', // 事件名称
 	disposeDept: '', // 处置部门
 	disposeUser: '', // 工单处置人
 })
@@ -76,6 +82,7 @@
 
 // 校验规则
 const rules = {
+	eventName: fieldRules(true, 50),
 	disposeDept: fieldRules(true),
 	disposeUser: fieldRules(true),
 }
@@ -109,6 +116,7 @@
 			areaCode: currentRow.value.areaCode || '',
 			disposeDept: formData.value.disposeDept,
 			disposeUser: formData.value.disposeUser,
+			eventName: formData.value.eventName,
 			latitude: currentRow.value.latitude || 0,
 			longitude: currentRow.value.longitude || 0,
 			resultId: currentRow.value.id,
diff --git a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
index 9e90a91..4f34518 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
@@ -5,14 +5,18 @@
 				<el-table-column label="线索缩略图" width="120">
 					<template v-slot="{ row }">
 						<el-image
-							v-if="row.aiImg || row.resultUrl"
-							:src="row.aiImg || row.resultUrl"
-							:preview-src-list="[row.aiImg || row.resultUrl]"
+							v-if="row.attachmentType === 1 || row.attachmentType === 2"
+							:src="row.attachmentType === 1 ? row.resultUrl : row.aiImg"
+							:preview-src-list="[row.attachmentType === 1 ? row.resultUrl : row.aiImg]"
 							fit="cover"
-							style="width: 80px; height: 60px"
+							style="width: 80px; height: 80px"
 							preview-teleported
 						/>
-						<span v-else>-</span>
+						<div class="video-btn" v-if="row.attachmentType === 3" @click="videoClick(row)">
+							<el-icon :size="30" color="#fff">
+								<VideoPlay />
+							</el-icon>
+						</div>
 					</template>
 				</el-table-column>
 				<el-table-column prop="resultCode" show-overflow-tooltip label="线索编号" />
@@ -31,8 +35,12 @@
 				<el-table-column prop="distributeUserName" show-overflow-tooltip label="分发人员" />
 				<el-table-column label="操作" class-name="operation-btns" width="140">
 					<template v-slot="{ row }">
-						<el-link type="primary" @click="openDistributeDialog(row)" v-if="permission.clueEvents_distribute && (row.distributeStatus === 0 || row.distributeStatus === 2)">
-							{{ row.distributeStatus === 2 ? '再次分发':'转为事件并分发' }}
+						<el-link
+							type="primary"
+							@click="openDistributeDialog(row)"
+							v-if="permission.clueEvents_distribute && (row.distributeStatus === 0 || row.distributeStatus === 2)"
+						>
+							{{ row.distributeStatus === 2 ? '再次分发' : '转为事件并分发' }}
 						</el-link>
 						<el-button disabled type="text" v-else>转为事件并分发</el-button>
 					</template>
@@ -51,12 +59,22 @@
 			@success="getList"
 		/>
 	</el-dialog>
+
+	<VideoPlayDialog
+		ref="videoPlayDialogRef"
+		v-if="VideoShow"
+		v-model="VideoShow"
+		:playUrl="currentVideo.resultUrl"
+	/>
 </template>
 
 <script setup>
 import { ref } from 'vue'
 import { gdTaskResultListApi } from './achievementApi'
 import DistributeDiaLog from './DistributeDiaLog.vue'
+import { getAiImg } from '@ztzf/utils'
+import { VideoPlay } from '@element-plus/icons-vue'
+import VideoPlayDialog from '@/components/VideoPlayDialog.vue'
 
 const store = useStore()
 const requester = computed(() => store.state.user.userInfo?.role_id === '2014158512610869250')
@@ -71,6 +89,8 @@
 const currentRow = ref(null) // 当前行数据
 const distributeDialogRef = ref(null)
 const distributeDialogVisible = ref(false)
+const VideoShow = ref(false)
+const currentVideo = ref({})
 
 // 分发状态选项
 const distributeStatusOptions = [
@@ -99,91 +119,18 @@
 	if (!currentRow.value?.id) return
 	loading.value = true
 	try {
-		const res = await gdTaskResultListApi({ patrolTaskId: currentRow.value.id })
-		list.value = res?.data?.data ?? []
-		list.value = await Promise.all(list.value.map(async i => {
-			const aiImg = await getAiImg(i.resultUrl)
-			return { ...i, aiImg }
-		}))
+		const res = await gdTaskResultListApi({ patrolTaskId: currentRow.value.id})
+		list.value = await Promise.all(
+			(res?.data?.data ?? []).map(async item => {
+				if (item.attachmentType !== 2) return item
+				const aiImg = await getAiImg(item.resultUrl,item.geojson)
+				return { ...item, aiImg }
+			})
+		)
 	} finally {
 		loading.value = false
 	}
 }
-
-const aiFrame = [
-	"{\"score\":0.91357421875,\"bbox\":{\"x_cen\":1246.0,\"y_cen\":209.0,\"width\":166.0,\"height\":334.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}",
-	"{\"score\":0.89697265625,\"bbox\":{\"x_cen\":370.0,\"y_cen\":694.5,\"width\":162.0,\"height\":331.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}",
-	"{\"score\":0.89501953125,\"bbox\":{\"x_cen\":396.0,\"y_cen\":343.0,\"width\":168.0,\"height\":330.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}",
-	"{\"score\":0.79296875,\"bbox\":{\"x_cen\":409.5,\"y_cen\":52.5,\"width\":167.0,\"height\":105.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}"
-]
-
-function getAiImg(url) {
-	if (!url) return ''
-	const img = new Image()
-	img.crossOrigin = 'anonymous';
-	return new Promise(resolve => {
-		img.onload = () => {
-			if (!img.naturalWidth || !img.naturalHeight) {
-				resolve('')
-				return
-			}
-
-			const canvas = document.createElement('canvas')
-			const ctx = canvas.getContext('2d')
-			if (!ctx) {
-				resolve('')
-				return
-			}
-
-			canvas.width = img.naturalWidth
-			canvas.height = img.naturalHeight
-			ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
-
-			aiFrame.forEach(item => {
-				let target = item
-				try {
-					target = typeof item === 'string' ? JSON.parse(item) : item
-				} catch (error) {
-					return
-				}
-
-				const { x_cen, y_cen, width, height } = target.bbox || {}
-				if ([x_cen, y_cen, width, height].some(value => typeof value !== 'number')) return
-
-				const x = x_cen - width / 2
-				const y = y_cen - height / 2
-				const label = target.class_name || ''
-				const fontSize = Math.max(18, Math.round(canvas.width / 80))
-				const labelHeight = fontSize + 10
-				const labelY = y - labelHeight >= 0 ? y - labelHeight : y
-
-				ctx.strokeStyle = '#FF3B30'
-				ctx.lineWidth = Math.max(3, Math.round(canvas.width / 640))
-				ctx.strokeRect(x, y, width, height)
-
-				if (label) {
-					ctx.font = `${fontSize}px Arial`
-					const labelWidth = ctx.measureText(label).width + 16
-					ctx.fillStyle = '#FF3B30'
-					ctx.fillRect(x, labelY, labelWidth, labelHeight)
-					ctx.fillStyle = '#FFFFFF'
-					ctx.textBaseline = 'middle'
-					ctx.fillText(label, x + 8, labelY + labelHeight / 2)
-				}
-			})
-
-			try {
-				resolve(canvas.toDataURL('image/jpeg', 0.92))
-			} catch (error) {
-				console.log(error)
-				resolve('')
-			}
-		}
-		img.onerror = () => resolve('')
-		img.src = url
-	})
-}
-
 
 // 打开分发弹框
 function openDistributeDialog(row) {
@@ -196,6 +143,12 @@
 	})
 }
 
+// 点击视频
+function videoClick(row) {
+	currentVideo.value = row
+	VideoShow.value = true
+}
+
 // 打开弹框
 async function open({ row } = {}) {
 	currentRow.value = row
@@ -206,8 +159,47 @@
 </script>
 
 <style lang="scss" scoped>
-	.disabled-text {
-		color: #C0C4CC;
-		cursor: not-allowed;
+.disabled-text {
+	color: #c0c4cc;
+	cursor: not-allowed;
+}
+
+.video-btn {
+	width: 80px;
+	height: 80px;
+	position: relative;
+	overflow: hidden;
+	border-radius: 4px;
+	background: linear-gradient(135deg, rgba(76, 52, 255, 0.14), rgba(76, 52, 255, 0) 48%),
+		linear-gradient(180deg, #f4f5ff 0%, #e9ecff 100%);
+	box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	cursor: pointer;
+
+	&::before {
+		content: '';
+		position: absolute;
+		inset: 0;
+		background-image: linear-gradient(
+			90deg,
+			rgba(76, 52, 255, 0.05) 0,
+			rgba(76, 52, 255, 0.05) 1px,
+			transparent 1px,
+			transparent 12px
+		);
+		opacity: 0.42;
 	}
+
+	.el-icon {
+		position: relative;
+		z-index: 1;
+		width: 42px;
+		height: 42px;
+		border-radius: 50%;
+		background: rgba(76, 52, 255, 0.72);
+		box-shadow: 0 4px 12px rgba(76, 52, 255, 0.2);
+	}
+}
 </style>
diff --git a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
index 2261862..c8a2196 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
@@ -18,7 +18,7 @@
 					popper-class="gd-select-popper"
 					v-model="searchParams.patrolTaskType"
 					:options="workOrderTypeXT"
-					:props="taskTypeFilterCascaderProps"
+					:props="taskTypeCascaderProps"
 					placeholder="请选择"
 					clearable
 					@change="handleSearch"
@@ -118,7 +118,11 @@
 import { getDeptTree } from '@/api/system/dept'
 import ViewDiaLog from './ViewDiaLog.vue'
 import { useStore } from 'vuex'
-import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeFilterCascaderProps } from '../taskTypeOptions'
+import {
+	getTaskTypeLabel,
+	normalizeTaskTypeOptions,
+	taskTypeCascaderProps,
+} from '../taskTypeOptions'
 
 // 初始化查询参数
 const initSearchParams = () => ({
@@ -164,6 +168,7 @@
 			startTime: range[0],
 			endTime: range[1],
 			taskStatus: 8,
+			patrolTaskType: searchParams?.value?.patrolTaskType?.join?.(','),
 		})
 		list.value = res?.data?.data?.records ?? []
 		total.value = res?.data?.data?.total ?? 0
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue
index b91c32b..1b1d54a 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue
@@ -13,21 +13,16 @@
 			</el-form-item>
 
 			<el-form-item label="文档类型" prop="resultType">
-				<el-select
-					class="gd-select gray"
-					popper-class="gd-select-popper"
+				<el-cascader
+					class="gd-cascader gray"
+					popper-class="gd-cascader-popper"
 					v-model="searchParams.resultType"
+					:options="workOrderTypeXT"
+					:props="taskTypeCascaderProps"
 					placeholder="请选择"
 					clearable
 					@change="handleSearch"
-				>
-					<el-option
-						v-for="item in dictObj.workOrderType"
-						:key="item.dictKey"
-						:label="item.dictValue"
-						:value="item.dictKey"
-					/>
-				</el-select>
+				/>
 			</el-form-item>
 
 			<el-form-item class="gd-search-actions">
@@ -56,7 +51,7 @@
 					<!-- <el-table-column prop="resultType" show-overflow-tooltip label="文档类型" /> -->
 					<el-table-column prop="resultType" show-overflow-tooltip label="文档类型">
 						<template v-slot="{ row }">
-							{{ getDictLabel(row.resultType, dictObj.workOrderType) }}
+							{{ getTaskTypeLabel(row.resultType, workOrderTypeXT) }}
 						</template>
 					</el-table-column>
 					<el-table-column label="操作" class-name="operation-btns">
@@ -94,20 +89,16 @@
 					<el-input v-model="editParams.nickName" class="gd-input" placeholder="请输入" />
 				</el-form-item>
 				<el-form-item label="文档类型" prop="resultType" v-if="titleTxt === '上传巡查报告'">
-					<el-select
-						class="gd-select"
-						popper-class="gd-select-popper"
+					<el-cascader
+						class="gd-cascader"
+						popper-class="gd-cascader-popper"
 						v-model="editParams.resultType"
+						:options="workOrderTypeXT"
+						:props="{ ...taskTypeCascaderProps, multiple: true }"
 						placeholder="请选择"
 						clearable
-					>
-						<el-option
-							v-for="item in dictObj.workOrderType"
-							:key="item.dictKey"
-							:label="item.dictValue"
-							:value="item.dictKey"
-						/>
-					</el-select>
+						collapse-tags
+					/>
 				</el-form-item>
 				<el-form-item label="上传文件" prop="link" v-if="titleTxt === '上传巡查报告'">
 					<el-upload class="avatar-uploader" action="" :show-file-list="false" :before-upload="onUploadFileBefore">
@@ -142,15 +133,19 @@
 	</basic-container>
 </template>
 <script setup>
-import { Search, RefreshRight, Download, Upload, Delete } from '@element-plus/icons-vue'
-import { fjPageApi, fjSubmitApi, fjRemoveApi, fjDetailApi, fjUploadApi,fjDownloadByByteApi } from './inspectionRequestApi'
+import { Search, RefreshRight, Download, Upload } from '@element-plus/icons-vue'
+import { fjPageApi, fjSubmitApi, fjRemoveApi, fjUploadApi, fjDownloadByByteApi } from './inspectionRequestApi'
 import { useStore } from 'vuex'
-import { ref, computed, inject, onMounted } from 'vue'
-import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
-import { getDictLabel } from '@ztzf/utils'
+import { ref, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import PreviewFiles from '@/components/PreviewFiles/PreviewFiles.vue'
-import { getDictionaryByCode } from '@/api/system/dictbiz'
 import { Base64 } from 'js-base64'
+import { getDictListApi } from '@/api/zkxt'
+import {
+	getTaskTypeLabel,
+	normalizeTaskTypeOptions,
+	taskTypeCascaderProps,
+} from '../taskTypeOptions'
 
 const store = useStore()
 const userInfo = computed(() => store.getters.userInfo)
@@ -173,9 +168,7 @@
 // 上传名称
 const uploadName = ref('')
 
-const dictObj = ref({
-	workOrderType: [], // 巡查任务类型
-})
+const workOrderTypeXT = ref([])
 
 const searchParams = ref(initSearchParams()) // 查询参数
 const total = ref(0) // 总条数
@@ -203,9 +196,13 @@
 
 // 获取字典
 function getDictList() {
-	getDictionaryByCode('workOrderType').then(res => {
-		dictObj.value = res.data.data
+	getDictListApi('task_inspection_type').then(res => {
+		workOrderTypeXT.value = normalizeTaskTypeOptions(res.data.data || [])
 	})
+}
+
+function toTaskTypeQueryValue(value) {
+	return Array.isArray(value) ? value.join(',') : value
 }
 
 // 将字节大小转换为可读格式的函数
@@ -221,7 +218,10 @@
 async function getList() {
 	loading.value = true
 	try {
-		const res = await fjPageApi({ ...searchParams.value })
+		const res = await fjPageApi({
+			...searchParams.value,
+			resultType: toTaskTypeQueryValue(searchParams.value.resultType),
+		})
 		list.value = res?.data?.data?.records ?? []
 		total.value = res?.data?.data?.total ?? 0
 	} finally {
@@ -401,7 +401,7 @@
 		if (valid) {
 			submitting.value = true
 			try {
-				await fjSubmitApi({ ...editParams.value })
+				await fjSubmitApi(editParams.value)
 				ElMessage.success('保存成功')
 				isShowEditView.value = false
 				getList()
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue
index 38633a3..fa7115c 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue
@@ -103,7 +103,7 @@
 									popper-class="gd-select-popper"
 									v-model="row.patrolTaskType"
 									:options="workOrderTypeXT"
-									:props="taskTypeCascaderProps"
+									:props="{...taskTypeCascaderProps,multiple:true}"
 									placeholder="请选择"
 									collapse-tags
 									@change="handlePatrolTaskTypeChange($index, $event)"
@@ -161,8 +161,7 @@
 									v-model="row.deviceFlyerId"
 									placeholder="请选择"
 									filterable
-									:disabled="dialogReadonly || !hasTaskTypeValue(taskList[$index].patrolTaskType)"
-
+									:disabled="dialogReadonly || !taskList[$index].patrolTaskType"
 								>
 									<el-option
 										v-for="item in flyerListMap[$index] || []"
@@ -273,10 +272,7 @@
 import { airlineListApi, algorithmGroupedApi } from '@/api/zkxt'
 import {
 	getTaskTypeLabel,
-	getTaskTypeLeafValues,
 	taskTypeCascaderProps,
-	toTaskTypeCascaderValue,
-	toTaskTypeRequestValue,
 } from '../taskTypeOptions'
 
 // 初始化任务行
@@ -342,14 +338,13 @@
 		// 将工单类型赋值给所有子任务行
 		const workOrderType = selectedWorkOrder.value?.workOrderType
 		if (workOrderType) {
-			const patrolTaskType = toTaskTypeCascaderValue(workOrderType)
 			taskList.value.forEach((task, index) => {
 				taskList.value[index] = {
 					...initTaskRow(),
-					patrolTaskType,
+					patrolTaskType: workOrderType,
 				}
 				// 获取对应的飞手列表
-				getFlyerList(index, patrolTaskType)
+				getFlyerList(index, workOrderType)
 			})
 		}
 	} else {
@@ -377,7 +372,7 @@
 	const newTask = initTaskRow()
 	// 如果已选择工单,自动填充工单类型
 	if (selectedWorkOrder.value?.workOrderType) {
-		newTask.patrolTaskType = toTaskTypeCascaderValue(selectedWorkOrder.value.workOrderType)
+		newTask.patrolTaskType = selectedWorkOrder.value.workOrderType
 	}
 	const newIndex = taskList.value.length
 	taskList.value.push(newTask)
@@ -441,8 +436,7 @@
 
 // 获取飞手列表
 async function getFlyerList(index, skilledTaskType) {
-	const leafValues = getTaskTypeLeafValues(skilledTaskType)
-	if (!leafValues.length) {
+	if (!skilledTaskType.length) {
 		flyerListMap.value[index] = []
 		return
 	}
@@ -450,7 +444,7 @@
 	const res = await gdFlyerPageApi({
 		size: 999,
 		current: 1,
-		skilledTaskType: leafValues.join(','),
+		skilledTaskType: skilledTaskType.map(item => item[1])?.join?.(','),
 		// longitude: obj.longitude || '',
 		// latitude: obj.latitude || '',
 	})
@@ -477,7 +471,6 @@
 function isTaskRowFilled(task) {
 	return !!(
 		task.patrolTaskName ||
-		hasTaskTypeValue(task.patrolTaskType) ||
 		task.executeTime ||
 		task.patrolRouteUrl ||
 		task.deviceFlyerId ||
@@ -485,9 +478,6 @@
 	)
 }
 
-function hasTaskTypeValue(value) {
-	return !!toTaskTypeRequestValue(value).length
-}
 
 // 提交新增
 async function handleSubmit() {
@@ -509,10 +499,6 @@
 		// 已填写的行需要校验完整性
 		if (!task.patrolTaskName) {
 			ElMessage.warning(`第${i + 1}行: 请输入巡查任务名称`)
-			return
-		}
-		if (!hasTaskTypeValue(task.patrolTaskType)) {
-			ElMessage.warning(`第${i + 1}行: 请选择巡查任务类型`)
 			return
 		}
 		if (!task.executeTime) {
@@ -538,7 +524,6 @@
 		// 只提交已填写的任务行
 		const dataList = filledTasks.map(item => ({
 			...item,
-			patrolTaskType: toTaskTypeRequestValue(item.patrolTaskType),
 			workOrderId: selectedWorkOrderId.value,
 			id: undefined,
 		}))
@@ -567,7 +552,7 @@
 		// 编辑/查看模式 - 加载已有数据
 		selectedWorkOrderId.value = row?.workOrderId
 		handleWorkOrderChange(row?.workOrderId)
-		taskList.value = [{ ...row, patrolTaskType: toTaskTypeCascaderValue(row?.patrolTaskType) }]
+		taskList.value = [{ ...row, patrolTaskType: row?.patrolTaskType || [] }]
 		flyerListMap.value = {}
 		// 如果有巡查类型,加载对应的飞手列表
 		if (row?.patrolTaskType) {
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
index 9e20e41..32f497b 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
@@ -82,15 +82,19 @@
 							任务成果({{ taskResultList.length || 0 }}条)
 						</div>
 						<div class="imgBox">
-							<div v-for="item in taskResultList">
+							<div
+								v-for="item in taskResultList.filter(
+									item1 => item1.resultUrl && [1, 2, 3].includes(item1.attachmentType)
+								)"
+							>
 								<el-image
-									v-if="item.attachmentType === 0 && item.resultUrl"
-									:src="item.resultUrl"
-									:preview-src-list="[item.resultUrl]"
+									v-if="item.attachmentType === 1 || item.attachmentType === 2"
+									:src="item.attachmentType === 1 ? item.resultUrl : item.aiImg"
+									:preview-src-list="[item.attachmentType === 1 ? item.resultUrl : item.aiImg]"
 									fit="cover"
 									preview-teleported
 								/>
-								<div class="video-btn" v-if="item.attachmentType === 1 && item.resultUrl" @click="videoClick(item)">
+								<div class="video-btn" v-if="item.attachmentType === 3" @click="videoClick(item)">
 									<el-icon :size="30" color="#fff">
 										<VideoPlay />
 									</el-icon>
@@ -116,8 +120,9 @@
 									popper-class="gd-select-popper"
 									v-model="formData.patrolTaskType"
 									:options="workOrderTypeXT"
-									:props="taskTypeCascaderProps"
+									:props="{ ...taskTypeCascaderProps, multiple: true }"
 									placeholder="请选择"
+									collapse-tags
 									clearable
 								/>
 							</el-form-item>
@@ -274,31 +279,34 @@
 		v-if="VideoShow"
 		v-model="VideoShow"
 		:playUrl="currentVideo.resultUrl"
-	>
-	</VideoPlayDialog>
+	></VideoPlayDialog>
 </template>
 
 <script setup>
 import { computed, ref, onMounted, inject } from 'vue'
 import { ElMessage } from 'element-plus'
-import { fieldRules, flyVisual, getDictLabel, geomAnalysis } from '@ztzf/utils'
+import { fieldRules, flyVisual, geomAnalysis, getAiImg } from '@ztzf/utils'
 import {
 	gdPatrolTaskRepublish,
 	gdFlyerPageApi,
 	gdPatrolTaskAuditApi,
 	gdPatrolTaskDetailApi,
 } from './inspectionRequestApi'
-import { gdWorkOrderFlowListApi, gdWorkOrderFlowPatrolListApi, gdWorkOrderDetailApi } from '../orderManage/orderManageApi'
+import {
+	gdWorkOrderFlowListApi,
+	gdWorkOrderFlowPatrolListApi,
+	gdWorkOrderDetailApi,
+} from '../orderManage/orderManageApi'
 import { gdManageDeviceListApi } from '../orderManage/gdManageDeviceApi'
 import { pxToRem } from '@/utils/rem'
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import { gdTaskResultListApi } from '@/views/orderView/orderManage/clueEvents/achievementApi'
 import RefuseOrderDialog1 from '@/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue'
-import { Check } from '@element-plus/icons-vue'
+import { Check, VideoPlay } from '@element-plus/icons-vue'
 import { queryAirById, airlineListApi, algorithmGroupedApi } from '@/api/zkxt'
 import * as Cesium from 'cesium'
 import { useStore } from 'vuex'
-import { getTaskTypeLabel, taskTypeCascaderProps, toTaskTypeCascaderValue, toTaskTypeRequestValue } from '../taskTypeOptions'
+import { getTaskTypeLabel, taskTypeCascaderProps } from '../taskTypeOptions'
 
 // 初始化表单数据
 const initForm = () => ({
@@ -355,7 +363,7 @@
 			children: (group.algorithms || []).map(alg => ({
 				id: alg.id,
 				name: alg.name,
-			}))
+			})),
 		}))
 	} catch (e) {
 		console.error('获取算法列表失败', e)
@@ -366,10 +374,12 @@
 function getAlgorithmNames(algorithmIds) {
 	if (!algorithmIds || !algorithmIds.length) return ''
 	const allAlgorithms = algorithmTreeData.value.flatMap(group => group.children || [])
-	return algorithmIds.map(id => {
-		const item = allAlgorithms.find(alg => alg.id === id)
-		return item ? item.name : id
-	}).join(', ')
+	return algorithmIds
+		.map(id => {
+			const item = allAlgorithms.find(alg => alg.id === id)
+			return item ? item.name : id
+		})
+		.join(', ')
 }
 
 const gdStatusObj = {
@@ -489,11 +499,7 @@
 	if (!isValid) return
 	submitting.value = true
 	try {
-		const payload = {
-			...formData.value,
-			patrolTaskType: toTaskTypeRequestValue(formData.value.patrolTaskType),
-		}
-		await gdPatrolTaskRepublish(payload)
+		await gdPatrolTaskRepublish(formData.value)
 		ElMessage.success('更新成功')
 		visible.value = false
 		emit('success')
@@ -508,8 +514,13 @@
 	if (!formData.value?.id) return
 	try {
 		const res = await gdTaskResultListApi({ patrolTaskId: formData.value.id })
-		taskResultList.value = res?.data?.data ?? []
-		console.log(taskResultList.value, '8989')
+		taskResultList.value = await Promise.all(
+			(res?.data?.data ?? []).map(async item => {
+				if (item.attachmentType !== 2) return item
+				const aiImg = await getAiImg(item.resultUrl, item.geojson)
+				return { ...item, aiImg }
+			})
+		)
 	} finally {
 	}
 }
@@ -527,18 +538,18 @@
 function getAirDetails() {
 	const dockHeight = formData.value.height
 	queryAirById(formData.value.patrolRouteUrl).then(res => {
-		const { airlineWaypoints, airlineSetting } = res.data.data;
+		const { airlineWaypoints, airlineSetting } = res.data.data
 		// 使用空值合并运算符 ?? 或 逻辑或 || 兜底
-		const { globalAirlineHeight = 0, airlineHeightMode } = airlineSetting || {};
+		const { globalAirlineHeight = 0, airlineHeightMode } = airlineSetting || {}
 		const relative = airlineHeightMode === 'relativeToStartPoint'
 		const list = airlineWaypoints
 		if (!list.length) return mapRef.value?.flyBoundary()
 		const result = list.map(item => {
 			let height = item?.globalHeight || globalAirlineHeight
-			if (relative){
+			if (relative) {
 				height = height + Number(dockHeight)
 			}
-			return [Number(item.longitude), Number(item.latitude),height || 0]
+			return [Number(item.longitude), Number(item.latitude), height || 0]
 		})
 		viewer.entities.add({
 			polyline: {
@@ -584,7 +595,6 @@
 
 	const res = await gdPatrolTaskDetailApi({ id: row.id })
 	formData.value = { ...initForm(), ...res.data.data }
-	formData.value.patrolTaskType = toTaskTypeCascaderValue(formData.value.patrolTaskType)
 	// 确保 algorithmIds 是数组
 	if (formData.value.algorithmIds && typeof formData.value.algorithmIds === 'string') {
 		formData.value.algorithmIds = formData.value.algorithmIds.split(',').filter(Boolean)
@@ -596,7 +606,7 @@
 	getAlgorithmList()
 	// 获取工单详情并根据geom范围获取航线列表
 	await getWorkOrderDetail(formData.value.workOrderId)
-	;['6', '7', '8'].includes(row.taskStatus) && await getTaskResultList()
+	;['6', '7', '8'].includes(row.taskStatus) && (await getTaskResultList())
 	loadList()
 	initMap()
 	getAirDetails()
@@ -624,8 +634,8 @@
 let VideoShow = ref(false)
 let currentVideo = ref({})
 function videoClick(row) {
-	VideoShow.value = true
 	currentVideo.value = row
+	VideoShow.value = true
 }
 
 // 初始化地图实例
@@ -669,6 +679,16 @@
 		width: calc((100% - 40px) / 5); /* 5列布局,减去4个10px间隙 */
 		height: 110px;
 		flex-shrink: 0;
+		position: relative;
+		overflow: hidden;
+		border-radius: 4px;
+		cursor: pointer;
+		box-shadow: inset 0 0 0 1px rgba(17, 31, 68, 0.08);
+		transition: box-shadow 0.16s ease;
+
+		&:hover {
+			box-shadow: inset 0 0 0 2px rgba(76, 52, 255, 0.48), 0 8px 20px rgba(76, 52, 255, 0.24);
+		}
 	}
 	.el-image {
 		width: 100%;
@@ -677,11 +697,41 @@
 	.video-btn {
 		width: 100%;
 		height: 100%;
-		background-color: #9E9E9E;
+		position: relative;
+		overflow: hidden;
+		//border: 1px solid #D8D6FF;
+		border-radius: 4px;
+		background: linear-gradient(135deg, rgba(76, 52, 255, 0.14), rgba(76, 52, 255, 0) 48%),
+			linear-gradient(180deg, #f4f5ff 0%, #e9ecff 100%);
+		box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7);
 		display: flex;
 		align-items: center;
 		justify-content: center;
 		cursor: pointer;
+
+		&::before {
+			content: '';
+			position: absolute;
+			inset: 0;
+			background-image: linear-gradient(
+				90deg,
+				rgba(76, 52, 255, 0.05) 0,
+				rgba(76, 52, 255, 0.05) 1px,
+				transparent 1px,
+				transparent 12px
+			);
+			opacity: 0.42;
+		}
+
+		.el-icon {
+			position: relative;
+			z-index: 1;
+			width: 42px;
+			height: 42px;
+			border-radius: 50%;
+			background: rgba(76, 52, 255, 0.72);
+			box-shadow: 0 4px 12px rgba(76, 52, 255, 0.2);
+		}
 	}
 }
 :deep(.el-timeline-item__timestamp) {
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
index af58260..fa779ae 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -6,28 +6,58 @@
 		</el-tabs>
 		<el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
 			<el-form-item label="任务名称" prop="patrolTaskName">
-				<el-input class="gd-input gray" v-model="searchParams.patrolTaskName" placeholder="请输入" clearable
-					@clear="handleSearch" />
+				<el-input
+					class="gd-input gray"
+					v-model="searchParams.patrolTaskName"
+					placeholder="请输入"
+					clearable
+					@clear="handleSearch"
+				/>
 			</el-form-item>
 
 			<el-form-item label="任务类型" prop="patrolTaskType">
-				<el-cascader class="gd-cascader gray" popper-class="gd-cascader-popper" v-model="searchParams.patrolTaskType"
-					:options="workOrderTypeXT" :props="taskTypeFilterCascaderProps" placeholder="请选择" clearable
-					@change="handleSearch" />
+				<el-cascader
+					class="gd-cascader gray"
+					popper-class="gd-cascader-popper"
+					v-model="searchParams.patrolTaskType"
+					:options="workOrderTypeXT"
+					:props="taskTypeCascaderProps"
+					placeholder="请选择"
+					clearable
+					@change="handleSearch"
+				/>
 			</el-form-item>
 
 			<el-form-item label="任务状态" prop="taskStatus">
-				<el-select class="gd-select gray" popper-class="gd-select-popper" v-model="searchParams.taskStatus"
-					placeholder="请选择" clearable @change="handleSearch">
-					<el-option v-for="item in dictObj.taskStatus" :key="item.dictKey" :label="item.dictValue"
-						:value="item.dictKey" />
+				<el-select
+					class="gd-select gray"
+					popper-class="gd-select-popper"
+					v-model="searchParams.taskStatus"
+					placeholder="请选择"
+					clearable
+					@change="handleSearch"
+				>
+					<el-option
+						v-for="item in dictObj.taskStatus"
+						:key="item.dictKey"
+						:label="item.dictValue"
+						:value="item.dictKey"
+					/>
 				</el-select>
 			</el-form-item>
 
 			<el-form-item label="执行时间" prop="executeTime">
-				<el-date-picker class="gd-date-picker gray" popper-class="gd-date-picker-popper" v-model="dateRange"
-					type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
-					value-format="YYYY-MM-DD HH:mm:ss" @change="handleSearch" />
+				<el-date-picker
+					class="gd-date-picker gray"
+					popper-class="gd-date-picker-popper"
+					v-model="dateRange"
+					type="daterange"
+					range-separator="-"
+					start-placeholder="开始日期"
+					end-placeholder="结束日期"
+					value-format="YYYY-MM-DD HH:mm:ss"
+					@change="handleSearch"
+				/>
 			</el-form-item>
 
 			<el-form-item class="gd-search-actions">
@@ -38,8 +68,7 @@
 
 		<div class="gd-table-container" v-loading="loading">
 			<div class="gd-table-toolbar">
-				<el-button v-if="permission.flyOrder_add" :icon="Plus" color="#4C34FF" type="primary"
-					@click="openForm('add')">
+				<el-button v-if="permission.flyOrder_add" :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">
 					拆分工单
 				</el-button>
 			</div>
@@ -75,15 +104,25 @@
 			</div>
 
 			<div class="gd-pagination-parent">
-				<el-pagination popper-class="gd-select-popper" v-model:current-page="searchParams.current"
-					v-model:page-size="searchParams.size" layout="total, prev, pager, next, sizes" :total="total"
-					@change="getList" />
+				<el-pagination
+					popper-class="gd-select-popper"
+					v-model:current-page="searchParams.current"
+					v-model:page-size="searchParams.size"
+					layout="total, prev, pager, next, sizes"
+					:total="total"
+					@change="getList"
+				/>
 			</div>
 		</div>
 
 		<FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
-		<ViewDiaLog ref="viewDiaLogRef" @success="getList" @refusalAccept="refusalAccept" v-if="viewDiaLogVisible"
-			v-model="viewDiaLogVisible" />
+		<ViewDiaLog
+			ref="viewDiaLogRef"
+			@success="getList"
+			@refusalAccept="refusalAccept"
+			v-if="viewDiaLogVisible"
+			v-model="viewDiaLogVisible"
+		/>
 	</basic-container>
 </template>
 <script setup>
@@ -97,7 +136,11 @@
 import { gdPatrolTaskPageApi, gdPatrolTaskRemoveApi } from './inspectionRequestApi'
 import ViewDiaLog from '@/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue'
 import { useStore } from 'vuex'
-import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeFilterCascaderProps } from '../taskTypeOptions'
+import {
+	getTaskTypeLabel,
+	normalizeTaskTypeOptions,
+	taskTypeCascaderProps,
+} from '../taskTypeOptions'
 
 const store = useStore()
 const permission = computed(() => store.state.user.permission)
@@ -136,28 +179,27 @@
 provide('workOrderTypeXT', workOrderTypeXT)
 
 const colors = {
-	0: '#F6A000',//0待签收
-	1: '#FF0202',//1拒绝签收
-	2: '#FF0202',//2已撤回
-	3: '#212BF4',//3待审核
-	4: '#FF0000',//4审核驳回
-	5: '#0068F0',//5审核通过
-	6: '#FD6716',//6待验收
-	7: '#FF0000',//7拒绝验收
-	8: '#019612',//8验收通过
+	0: '#F6A000', //0待签收
+	1: '#FF0202', //1拒绝签收
+	2: '#FF0202', //2已撤回
+	3: '#212BF4', //3待审核
+	4: '#FF0000', //4审核驳回
+	5: '#0068F0', //5审核通过
+	6: '#FD6716', //6待验收
+	7: '#FF0000', //7拒绝验收
+	8: '#019612', //8验收通过
 }
 
-
 // 获取列表
-async function getList () {
+async function getList() {
 	const range = dateRangeFormat(dateRange.value)
 	loading.value = true
 	try {
 		const res = await gdPatrolTaskPageApi({
 			...searchParams.value,
 			startTime: range[0],
-			endTime: range[1],
 			createUser: activeName.value === 'my' ? store.state.user.userInfo.user_id : '',
+			patrolTaskType: searchParams?.value?.patrolTaskType?.join?.(','),
 		})
 		list.value = res?.data?.data?.records ?? []
 		total.value = res?.data?.data?.total ?? 0
@@ -166,20 +208,20 @@
 	}
 }
 
-function tabsClick (tab, event) {
+function tabsClick(tab, event) {
 	activeName.value = tab.props.name
 	resetForm()
 	handleSearch()
 }
 
 // 查询
-function handleSearch () {
+function handleSearch() {
 	searchParams.value.current = 1
 	getList()
 }
 
 // 重置查询
-function resetForm () {
+function resetForm() {
 	queryParamsRef.value?.resetFields()
 	dateRange.value = []
 	searchParams.value.current = 1
@@ -187,7 +229,7 @@
 }
 
 // 新增/编辑/查看 弹框
-function openForm (mode, row) {
+function openForm(mode, row) {
 	dialogVisible.value = true
 	nextTick(() => {
 		dialogRef.value?.open({ mode, row })
@@ -196,7 +238,7 @@
 
 // 状态:0待签收、1拒绝签收、2已撤回、3待审核、4审核驳回
 // 5审核通过、6待验收、7拒绝验收、8验收通过
-function viewDiaLogView (row) {
+function viewDiaLogView(row) {
 	viewDiaLogVisible.value = true
 	nextTick(() => {
 		let mode = ['1', '4'].includes(row.taskStatus) && permission.value.flyOrder_add ? 'edit' : 'view'
@@ -205,13 +247,12 @@
 }
 
 // 勾选值设置
-function handleSelectionChange (rows) {
+function handleSelectionChange(rows) {
 	selectedIds.value = rows.map(item => item.id)
 }
 
-
 // 获取字典
-function getDictList () {
+function getDictList() {
 	getDictionaryByCode('workOrderType,deviceLoadDemand,taskStatus').then(res => {
 		dictObj.value = res.data.data
 	})
@@ -220,7 +261,7 @@
 	})
 }
 
-function refusalAccept (row) {
+function refusalAccept(row) {
 	dialogVisible.value = true
 	nextTick(() => {
 		dialogRef.value?.open({ mode: 'edit', row })
@@ -228,7 +269,7 @@
 }
 
 // 获取两个tab的总条数
-async function getTabTotals () {
+async function getTabTotals() {
 	const allRes = await gdPatrolTaskPageApi({
 		current: 1,
 		size: 1,
diff --git a/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
index a869392..5141c8d 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -8,12 +8,17 @@
 		destroy-on-close
 		:close-on-click-modal="false"
 	>
-	<el-tabs class="gd-tabs"  v-model="activeName" @tab-click="tabsClick" v-if="dialogReadonly && formData.workOrderStatus >= '30'">
+		<el-tabs
+			class="gd-tabs"
+			v-model="activeName"
+			@tab-click="tabsClick"
+			v-if="dialogReadonly && formData.workOrderStatus >= '30'"
+		>
 			<el-tab-pane label="基础信息" name="basic"></el-tab-pane>
 			<el-tab-pane label="成果数据" name="result">
 				<OutcomeData :workOrderId="formData.id" />
 			</el-tab-pane>
-			</el-tabs>
+		</el-tabs>
 		<div class="content" style="display: flex" v-if="activeName === 'basic'">
 			<div class="processBox" v-if="dialogMode !== 'add'">
 				<div class="detail-title">工单记录</div>
@@ -94,17 +99,16 @@
 						</el-col>
 						<el-col :span="12">
 							<el-form-item label="工单类型" prop="workOrderType">
-								<!-- <el-cascader
+								<el-cascader
 									class="gd-cascader"
 									popper-class="gd-cascader-popper"
 									v-model="formData.workOrderType"
 									:options="workOrderTypeXT"
-									:props="taskTypeCascaderProps"
-									collapse-tags
+									:props="{...taskTypeCascaderProps,multiple:true }"
+									placeholder="请选择"
 									clearable
-								/> -->
-								<el-cascader class="gd-cascader" popper-class="gd-cascader-popper" v-model="formData.workOrderType"
-					:options="workOrderTypeXT" :props="taskTypeCascaderProps" placeholder="请选择" clearable collapse-tags />
+									collapse-tags
+								/>
 							</el-form-item>
 						</el-col>
 						<el-col :span="12">
@@ -130,12 +134,7 @@
 									clearable
 									@change="loadDemandChange"
 								>
-									<el-option
-										v-for="item in payloadList"
-										:key="item.name"
-										:label="item.name"
-										:value="item.name"
-									/>
+									<el-option v-for="item in payloadList" :key="item.name" :label="item.name" :value="item.name" />
 								</el-select>
 							</el-form-item>
 						</el-col>
@@ -176,9 +175,7 @@
 				</div>
 				<el-table
 					class="separateTable"
-					:empty-text="
-						pointList.length ? '暂无数据' : '请先在地图上绘制工单范围'
-					"
+					:empty-text="pointList.length ? '暂无数据' : '请先在地图上绘制工单范围'"
 					ref="deviceTableRef"
 					:data="pointList.length ? deviceList : []"
 					row-key="id"
@@ -284,7 +281,10 @@
 import OrderStepBar from '@/views/orderView/orderManage/orderManage/OrderStepBar.vue'
 import OutcomeData from './outcomeData.vue'
 import { getDictListApi } from '@/api/zkxt'
-import { getTaskTypeLabel, taskTypeCascaderProps, toTaskTypeCascaderValue, toTaskTypeRequestValue } from '../taskTypeOptions'
+import {
+	getTaskTypeLabel,
+	taskTypeCascaderProps,
+} from '../taskTypeOptions'
 const store = useStore()
 const permission = computed(() => store.state.user.permission)
 const activeName = ref('basic')
@@ -412,7 +412,7 @@
 async function getDeviceList() {
 	try {
 		const str = [...pointList.value, pointList.value[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
-			let geom = `POLYGON((${str}))`
+		let geom = `POLYGON((${str}))`
 		const res = await gdManageDeviceListApi({
 			deviceIds: dialogMode.value === 'add' ? '' : formData.value.recommendDeviceIds,
 			devicePayload: formData.value?.deviceLoadDemand,
@@ -455,11 +455,7 @@
 		if (!formData.value.id) {
 			formData.value.workOrderStatus = '10'
 		}
-		const payload = {
-			...formData.value,
-			workOrderType: toTaskTypeRequestValue(formData.value.workOrderType),
-		}
-		await gdWorkOrderSaveApi(payload)
+		await gdWorkOrderSaveApi(formData.value)
 		ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
 		visible.value = false
 		emit('success')
@@ -481,7 +477,6 @@
 	loadList()
 	const res = await gdWorkOrderDetailApi({ id: formData.value.id })
 	formData.value = res?.data?.data ?? {}
-	formData.value.workOrderType = toTaskTypeCascaderValue(formData.value.workOrderType)
 	dateRange.value = [formData.value.executeStartTime, formData.value.executeEndTime]
 }
 
@@ -700,15 +695,15 @@
 </script>
 
 <style lang="scss">
-.orderManageFormDlg{
-		.el-dialog__body{
-			display: flex;
-			flex-direction: column;
-			.content{
-				height: 0;
-				flex: 1;
-			}
+.orderManageFormDlg {
+	.el-dialog__body {
+		display: flex;
+		flex-direction: column;
+		.content {
+			height: 0;
+			flex: 1;
 		}
+	}
 }
 </style>
 
@@ -725,7 +720,6 @@
 		}
 	}
 }
-
 
 .content {
 	display: flex;
diff --git a/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue b/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
index 6c49bcb..89a664d 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -6,28 +6,58 @@
 		</el-tabs>
 		<el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
 			<el-form-item label="工单名称" prop="workOrderName">
-				<el-input class="gd-input gray" v-model="searchParams.workOrderName" placeholder="请输入" clearable
-					@clear="handleSearch" />
+				<el-input
+					class="gd-input gray"
+					v-model="searchParams.workOrderName"
+					placeholder="请输入"
+					clearable
+					@clear="handleSearch"
+				/>
 			</el-form-item>
 
 			<el-form-item label="工单类型" prop="workOrderType">
-				<el-cascader class="gd-cascader gray" popper-class="gd-cascader-popper" v-model="searchParams.workOrderType"
-					:options="workOrderTypeXT" :props="taskTypeFilterCascaderProps" placeholder="请选择" clearable
-					@change="handleSearch" />
+				<el-cascader
+					class="gd-cascader gray"
+					popper-class="gd-cascader-popper"
+					v-model="searchParams.workOrderType"
+					:options="workOrderTypeXT"
+					:props="taskTypeCascaderProps"
+					placeholder="请选择"
+					clearable
+					@change="handleSearch"
+				/>
 			</el-form-item>
 
 			<el-form-item label="工单状态" prop="workOrderStatus">
-				<el-select class="gd-select gray" popper-class="gd-select-popper" v-model="searchParams.workOrderStatus"
-					placeholder="请选择" clearable @change="handleSearch">
-					<el-option v-for="item in dictObj.workOrderStatus" :key="item.dictKey"
-						:label="item.dictValue.split('_')[1]" :value="item.dictKey" />
+				<el-select
+					class="gd-select gray"
+					popper-class="gd-select-popper"
+					v-model="searchParams.workOrderStatus"
+					placeholder="请选择"
+					clearable
+					@change="handleSearch"
+				>
+					<el-option
+						v-for="item in dictObj.workOrderStatus"
+						:key="item.dictKey"
+						:label="item.dictValue.split('_')[1]"
+						:value="item.dictKey"
+					/>
 				</el-select>
 			</el-form-item>
 
 			<el-form-item label="执行时间" prop="executeTime">
-				<el-date-picker class="gd-date-picker gray" popper-class="gd-date-picker-popper" v-model="dateRange"
-					type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
-					value-format="YYYY-MM-DD HH:mm:ss" @change="handleSearch" />
+				<el-date-picker
+					class="gd-date-picker gray"
+					popper-class="gd-date-picker-popper"
+					v-model="dateRange"
+					type="daterange"
+					range-separator="-"
+					start-placeholder="开始日期"
+					end-placeholder="结束日期"
+					value-format="YYYY-MM-DD HH:mm:ss"
+					@change="handleSearch"
+				/>
 			</el-form-item>
 
 			<el-form-item class="gd-search-actions">
@@ -38,8 +68,7 @@
 
 		<div class="gd-table-container" v-loading="loading">
 			<div class="gd-table-toolbar">
-				<el-button v-if="permission.order_release" :icon="Plus" color="#4C34FF" type="primary"
-					@click="openForm('add')">
+				<el-button v-if="permission.order_release" :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">
 					新增工单
 				</el-button>
 			</div>
@@ -85,9 +114,14 @@
 			</div>
 
 			<div class="gd-pagination-parent">
-				<el-pagination popper-class="gd-select-popper" v-model:current-page="searchParams.current"
-					v-model:page-size="searchParams.size" layout="total, prev, pager, next, sizes" :total="total"
-					@change="getList" />
+				<el-pagination
+					popper-class="gd-select-popper"
+					v-model:current-page="searchParams.current"
+					v-model:page-size="searchParams.size"
+					layout="total, prev, pager, next, sizes"
+					:total="total"
+					@change="getList"
+				/>
 			</div>
 		</div>
 
@@ -109,7 +143,11 @@
 import testDock from '@/assets/docx/testDocx.docx'
 import testPdf from '@/assets/pdf/testPdf.pdf'
 import { useStore } from 'vuex'
-import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeFilterCascaderProps } from '../taskTypeOptions'
+import {
+	getTaskTypeLabel,
+	normalizeTaskTypeOptions,
+	taskTypeCascaderProps,
+} from '../taskTypeOptions'
 
 const store = useStore()
 const activeName = ref('all')
@@ -148,7 +186,7 @@
 provide('workOrderTypeXT', workOrderTypeXT)
 
 // 获取列表
-async function getList () {
+async function getList() {
 	const range = dateRangeFormat(dateRange.value)
 	loading.value = true
 	try {
@@ -157,6 +195,7 @@
 			startTime: range[0],
 			endTime: range[1],
 			createUser: activeName.value === 'my' ? store.state.user.userInfo.user_id : '',
+			workOrderType: searchParams?.value?.workOrderType?.join?.(','),
 		})
 		list.value = res?.data?.data?.records ?? []
 		total.value = res?.data?.data?.total ?? 0
@@ -166,27 +205,27 @@
 }
 
 // 查询
-function handleSearch () {
+function handleSearch() {
 	searchParams.value.current = 1
 	getList()
 }
 
 // 重置查询
-function resetForm () {
+function resetForm() {
 	queryParamsRef.value?.resetFields()
 	dateRange.value = []
 	searchParams.value.current = 1
 	getList()
 }
 
-function tabsClick (tab, event) {
+function tabsClick(tab, event) {
 	activeName.value = tab.props.name
 	resetForm()
 	handleSearch()
 }
 
 // 新增/编辑/查看 弹框
-function openForm (mode, row) {
+function openForm(mode, row) {
 	dialogVisible.value = true
 	nextTick(() => {
 		dialogRef.value?.open({ mode, row })
@@ -196,7 +235,7 @@
 // 21响应中_申请取消、22响应中_申请修改、23响应中_已取消、
 // 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、
 // 50验收通过_待结算、60结算完成_已结算
-function openFormChange (row) {
+function openFormChange(row) {
 	dialogVisible.value = true
 	nextTick(() => {
 		const mode = ['11'].includes(row.workOrderStatus) && permission.value.order_release ? 'edit' : 'view'
@@ -223,7 +262,7 @@
 }
 
 // 删除
-async function handleDelete (row) {
+async function handleDelete(row) {
 	const tips = row ? '该条' : '选中的项'
 	await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
 		type: 'warning',
@@ -239,12 +278,12 @@
 }
 
 // 勾选值设置
-function handleSelectionChange (rows) {
+function handleSelectionChange(rows) {
 	selectedIds.value = rows.map(item => item.id)
 }
 
 // 获取字典
-function getDictList () {
+function getDictList() {
 	getDictionaryByCode('deviceLoadDemand,workOrderType,workOrderStatus,taskStatus').then(res => {
 		dictObj.value = res.data.data
 	})
@@ -254,7 +293,7 @@
 }
 
 // 获取两个tab的总条数
-async function getTabTotals () {
+async function getTabTotals() {
 	// 获取全部tab的总数
 	const allRes = await gdWorkOrderPageApi({
 		current: 1,
diff --git a/applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue b/applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue
index b9468b6..dc6c7e4 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue
@@ -23,6 +23,24 @@
         </el-select>
       </el-form-item>
 
+      <el-form-item label="成果类型" prop="attachmentType">
+        <el-select
+          class="gd-select"
+		      popper-class="gd-select-popper"
+          v-model="searchParams.attachmentType"
+          placeholder="请选择"
+          clearable
+          @change="handleSearch"
+        >
+          <el-option
+            v-for="item in attachmentTypeOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+
       <el-form-item >
         <el-button :icon="RefreshRight" @click="resetForm"></el-button>
         <el-button :icon="Search" color="#383874" @click="handleSearch"></el-button>
@@ -38,15 +56,15 @@
           <el-table-column type="index" label="序号" width="80" />
           <el-table-column label="图片/视频" >
             <template v-slot="{ row }">
-              <el-image
-                v-if="row.attachmentType ===0 && row.resultUrl"
-                :src="row.resultUrl"
-                :preview-src-list="[row.resultUrl]"
-                fit="cover"
-                style="width: 80px; height: 80px; border-radius: 4px;"
-								preview-teleported
-              />
-							<div class="video-btn" v-if="row.attachmentType === 1 && row.resultUrl" @click="videoClick(row)">
+							<el-image
+								v-if="row.attachmentType === 1 || row.attachmentType === 2"
+								:src="row.attachmentType === 1 ? row.resultUrl : row.aiImg"
+								:preview-src-list="[row.attachmentType === 1 ? row.resultUrl : row.aiImg]"
+								fit="cover"
+		            style="width: 80px; height: 80px"
+		            preview-teleported
+							/>
+							<div class="video-btn" v-if="row.attachmentType === 3 && row.resultUrl" @click="videoClick(row)">
 								<el-icon :size="30" color="#fff">
 									<VideoPlay />
 								</el-icon>
@@ -54,6 +72,12 @@
             </template>
           </el-table-column>
           <el-table-column prop="patrolTaskName" show-overflow-tooltip label="巡查任务名称" />
+          <el-table-column label="成果类型">
+            <template v-slot="{ row }">
+              {{ getAttachmentTypeLabel(row.attachmentType) }}
+            </template>
+          </el-table-column>
+          
           <el-table-column prop="shootTime" show-overflow-tooltip label="拍摄时间" />
           <el-table-column label="操作" class-name="operation-btns" width="120">
             <template v-slot="{ row }">
@@ -89,7 +113,7 @@
 import {gdTaskResultPageApi, gdTaskResultDownloadApi, gdTaskResultRemoveApi,listByWorkOrderId}from './orderManageApi'
 import { Search, RefreshRight, Download } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { getDictLabel } from '@ztzf/utils'
+import { getAiImg, getDictLabel } from '@ztzf/utils'
 import dayjs from 'dayjs'
 import VideoPlayDialog from '@/components/VideoPlayDialog.vue'
 
@@ -106,7 +130,8 @@
 const initSearchParams = () => ({
   current: 1,
   size: 10,
-  patrolTaskIds: []
+  patrolTaskIds: [],
+  attachmentType: ''
 })
 
 const searchParams = ref(initSearchParams())
@@ -117,6 +142,16 @@
 const selectedRows = ref([])
 const patrolTaskList = ref([])
 const dictObj = inject('dictObj')
+const attachmentTypeOptions = [
+	{ label: '媒体文件', value: 1 },
+	{ label: 'AI文件', value: 2 },
+	{ label: '主视频', value: 3 },
+]
+
+function getAttachmentTypeLabel(attachmentType) {
+	return attachmentTypeOptions.find(item => item.value === Number(attachmentType))?.label || ''
+}
+
 // 导出加载状态
 const exportLoading = ref(false)
 // 获取巡查任务列表
@@ -140,7 +175,13 @@
         ? searchParams.value.patrolTaskIds.join(',')
         : ''
     })
-    list.value = res?.data?.data?.records || []
+		list.value = await Promise.all(
+			(res?.data?.data?.records ?? []).map(async item => {
+				if (item.attachmentType !== 2) return item
+				const aiImg = await getAiImg(item.resultUrl,item.geojson)
+				return { ...item, aiImg }
+			})
+		)
     total.value = res?.data?.data?.total || 0
   } finally {
     loading.value = false
@@ -246,8 +287,8 @@
 let VideoShow = ref(false)
 let currentVideo = ref({})
 function videoClick(row) {
+	currentVideo.value = row
 	VideoShow.value = true
-  currentVideo.value = row
 }
 
 // 监听 workOrderId 变化
diff --git a/applications/task-work-order/src/views/orderView/orderManage/taskTypeOptions.js b/applications/task-work-order/src/views/orderView/orderManage/taskTypeOptions.js
index 0e5f917..566af60 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/taskTypeOptions.js
+++ b/applications/task-work-order/src/views/orderView/orderManage/taskTypeOptions.js
@@ -35,38 +35,9 @@
 	)
 }
 
-function parseTaskTypeValue(value) {
-	if (typeof value !== 'string') return value
-	try {
-		const result = JSON.parse(value)
-		return Array.isArray(result) ? result : value
-	} catch (error) {
-		return value
-	}
-}
-
-export function toTaskTypeCascaderValue(value) {
-	value = parseTaskTypeValue(value)
-	if (!value) return []
-	if (typeof value === 'string') return []
-	if (!Array.isArray(value)) return []
-	if (isValidPath(value)) return [value]
-	if (value.length === 1 && Array.isArray(value[0]) && !isValidPath(value[0])) {
-		value = value[0]
-	}
-	return value.filter(isValidPath)
-}
-
-export function toTaskTypeRequestValue(value) {
-	return toTaskTypeCascaderValue(value).map(item => [item[0], item[1]])
-}
-
-export function getTaskTypeLeafValues(value) {
-	return toTaskTypeRequestValue(value).map(item => item[1])
-}
-
 export function getTaskTypeLabel(value, options = []) {
-	const paths = toTaskTypeCascaderValue(value)
+	if (!Array.isArray(value) || !value.length) return ''
+	const paths = value.filter(isValidPath)
 	if (paths.length) {
 		return paths
 			.map(path => {
@@ -77,29 +48,12 @@
 			.join('、')
 	}
 
-	if (!value) return ''
-	for (const item of options) {
-		if (item.dictValue === value) return item.dictLabel
-		const child = (item.children || []).find(option => option.dictValue === value)
-		if (child) return child.dictLabel
-	}
-
-	return value
+	return ''
 }
 
 export const taskTypeCascaderProps = {
 	label: 'dictLabel',
 	value: 'dictValue',
 	children: 'children',
-	multiple: true,
-	emitPath: true,
-	checkStrictly: false,
-}
-
-export const taskTypeFilterCascaderProps = {
-	label: 'dictLabel',
-	value: 'dictValue',
-	children: 'children',
-	emitPath: false,
 	checkStrictly: false,
 }
diff --git a/package.json b/package.json
index 91cc710..40e09ed 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"name": "my-monorepo",
 	"private": true,
-	"packageManager": "pnpm@9.0.0",
+	"packageManager": "pnpm@9.15.9",
 	"version": "1.0.0",
 	"description": "",
 	"main": "index.js",
diff --git a/packages/utils/common/index.js b/packages/utils/common/index.js
index b7e1e97..7ddd873 100644
--- a/packages/utils/common/index.js
+++ b/packages/utils/common/index.js
@@ -73,3 +73,69 @@
 			return { longitude, latitude }
 		})
 }
+
+// 图片转带ai框的图片
+export function getAiImg(url, aiFrameSource) {
+	if (!url) return url
+	if (!aiFrameSource) return url
+	// let url = 'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fportrait%2F165a01e1cd859d59133b7755623a6c29.png&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1780851600&t=d5ce5f611eafadd932b84ef14275fbda'
+	// const aiFrame1 = '[{"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 aiFrame = JSON.parse(aiFrameSource)
+	const img = new Image()
+	img.crossOrigin = 'anonymous'
+	return new Promise(resolve => {
+		img.onload = () => {
+			if (!img.naturalWidth || !img.naturalHeight) {
+				resolve('')
+				return
+			}
+
+			const canvas = document.createElement('canvas')
+			const ctx = canvas.getContext('2d')
+			if (!ctx) {
+				resolve('')
+				return
+			}
+
+			canvas.width = img.naturalWidth
+			canvas.height = img.naturalHeight
+			ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
+
+			aiFrame.forEach(item => {
+				let target = item
+				const { x_cen, y_cen, width, height } = target.bbox || {}
+				if ([x_cen, y_cen, width, height].some(value => typeof value !== 'number')) return
+
+				const x = x_cen - width / 2
+				const y = y_cen - height / 2
+				const label = target.class_name || ''
+				const fontSize = Math.max(18, Math.round(canvas.width / 80))
+				const labelHeight = fontSize + 10
+				const labelY = y - labelHeight >= 0 ? y - labelHeight : y
+
+				ctx.strokeStyle = '#FF3B30'
+				ctx.lineWidth = Math.max(3, Math.round(canvas.width / 640))
+				ctx.strokeRect(x, y, width, height)
+
+				if (label) {
+					ctx.font = `${fontSize}px Arial`
+					const labelWidth = ctx.measureText(label).width + 16
+					ctx.fillStyle = '#FF3B30'
+					ctx.fillRect(x, labelY, labelWidth, labelHeight)
+					ctx.fillStyle = '#FFFFFF'
+					ctx.textBaseline = 'middle'
+					ctx.fillText(label, x + 8, labelY + labelHeight / 2)
+				}
+			})
+
+			try {
+				resolve(canvas.toDataURL('image/jpeg', 0.92))
+			} catch (error) {
+				console.log(error)
+				resolve('')
+			}
+		}
+		img.onerror = () => resolve('')
+		img.src = url
+	})
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4d32915..7127c46 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -395,7 +395,7 @@
         version: 3.5.27
       postcss-pxtorem:
         specifier: 'catalog:'
-        version: 6.1.0(postcss@8.5.6)
+        version: 6.1.0(postcss@5.2.18)
       sass:
         specifier: 'catalog:'
         version: 1.97.2
@@ -947,6 +947,9 @@
       '@dcloudio/uni-ui':
         specifier: ^1.5.11
         version: 1.5.11
+      '@ztzf/utils':
+        specifier: workspace:*
+        version: link:../../packages/utils
       dayjs:
         specifier: ^1.11.18
         version: 1.11.19
@@ -2035,8 +2038,8 @@
   '@dcloudio/uni-app-vue@3.0.0-4070520250711001':
     resolution: {integrity: sha512-/ISF+1xrcvUtcote+LfLyk/pyizTjEY6/3HZgBsOfXchkZBz0fkTWgnj5RHYeqwD7aHLsOJX93eaazNMnzD9Zw==}
 
-  '@dcloudio/uni-app-x@0.7.100':
-    resolution: {integrity: sha512-aiRJ/A5Gak24aYKbPrEB6dJycqLpL8/24hqiUOezxFcEYVjNn18UBHSwGGuFUNRw3j3RhEaFzPL5n+reQTXRjg==}
+  '@dcloudio/uni-app-x@0.7.124':
+    resolution: {integrity: sha512-QqiIa0t7kg6xweh58QgiEx0zX6d0CBzkb8TJz3rx8N2cTH5k2w32iSyQaDzfjSmqQITZDbWNlBxlyWUY0i4pyA==}
 
   '@dcloudio/uni-app@3.0.0-4070520250711001':
     resolution: {integrity: sha512-rR1SGJknJyDy80vb47yDICXcZrV11KJ5yhNG8aC+kH2ZL338faBrdOQgqqN/XJegVxJdTfv1KqUXuaShMWTZGA==}
@@ -7843,6 +7846,7 @@
   prebuild-install@7.1.3:
     resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
     engines: {node: '>=10'}
+    deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
     hasBin: true
 
   prelude-ls@1.2.1:
@@ -10582,7 +10586,7 @@
 
   '@dcloudio/uni-app-vue@3.0.0-4070520250711001': {}
 
-  '@dcloudio/uni-app-x@0.7.100': {}
+  '@dcloudio/uni-app-x@0.7.124': {}
 
   '@dcloudio/uni-app@3.0.0-4070520250711001(@dcloudio/types@3.4.29)(@nuxt/kit@3.20.2)(@vueuse/core@9.13.0(vue@3.4.21(typescript@5.9.3)))(postcss@8.5.6)(vue@3.4.21(typescript@5.9.3))':
     dependencies:
@@ -11089,7 +11093,7 @@
   '@dcloudio/uni-uts-v1@3.0.0-4070520250711001':
     dependencies:
       '@babel/code-frame': 7.28.6
-      '@dcloudio/uni-app-x': 0.7.100
+      '@dcloudio/uni-app-x': 0.7.124
       '@dcloudio/uts': 3.0.0-4070520250711001
       '@rollup/pluginutils': 5.3.0(rollup@4.55.3)
       '@vue/shared': 3.4.21
@@ -18273,6 +18277,10 @@
     dependencies:
       postcss: 5.2.18
 
+  postcss-pxtorem@6.1.0(postcss@5.2.18):
+    dependencies:
+      postcss: 5.2.18
+
   postcss-pxtorem@6.1.0(postcss@8.5.6):
     dependencies:
       postcss: 8.5.6
diff --git a/uniapps/work-app/package.json b/uniapps/work-app/package.json
index 1d9a05f..b2ba42f 100644
--- a/uniapps/work-app/package.json
+++ b/uniapps/work-app/package.json
@@ -1,5 +1,5 @@
 {
-	"name": "work-app",
+	"name": "@ztzf/work-app",
 	"type": "module",
 	"version": "1.6.0",
 	"description": "吉安低空",
@@ -64,13 +64,14 @@
 		"@dcloudio/uni-mp-xhs": "3.0.0-4070520250711001",
 		"@dcloudio/uni-quickapp-webview": "3.0.0-4070520250711001",
 		"@dcloudio/uni-ui": "^1.5.11",
+		"@ztzf/utils": "workspace:*",
 		"dayjs": "^1.11.18",
+		"deep-pick-omit": "1.2.1",
+		"destr": "2.0.5",
 		"js-audio-recorder": "^1.0.7",
 		"js-base64": "^3.7.4",
 		"js-md5": "^0.7.3",
 		"leaflet": "^1.9.4",
-		"deep-pick-omit": "1.2.1",
-		"destr": "2.0.5",
 		"pinia": "2.2.4",
 		"pinia-plugin-persistedstate": "4.1.3",
 		"trtc-sdk-v5": "^5.14.1",
@@ -84,6 +85,7 @@
 		"@dcloudio/uni-automator": "3.0.0-4070520250711001",
 		"@dcloudio/uni-cli-shared": "3.0.0-4070520250711001",
 		"@dcloudio/uni-stacktracey": "3.0.0-4070520250711001",
+		"@dcloudio/uni-uts-v1": "3.0.0-4070520250711001",
 		"@dcloudio/vite-plugin-uni": "3.0.0-4070520250711001",
 		"@esbuild/darwin-arm64": "0.25.1",
 		"@esbuild/darwin-x64": "0.25.1",
diff --git a/uniapps/work-app/src/config/env.js b/uniapps/work-app/src/config/env.js
index ba70692..532b4d0 100644
--- a/uniapps/work-app/src/config/env.js
+++ b/uniapps/work-app/src/config/env.js
@@ -12,8 +12,8 @@
 const development = {
   VITE_APP_ENV:'development',
   // 开发环境这里改为自己的
-  // VITE_APP_WEBVIEW_URL: 'https://wrj.shuixiongit.com/mobile-web-view/#/webViewWrapper',
-  VITE_APP_WEBVIEW_URL: 'https://192.168.1.157:5179/mobile-web-view/#/webViewWrapper',
+	// VITE_APP_WEBVIEW_URL: 'https://192.168.1.157:5179/mobile-web-view/#/webViewWrapper',
+  VITE_APP_WEBVIEW_URL: 'https://wrj.shuixiongit.com/mobile-web-view/#/webViewWrapper',
   VITE_API_BASE_URL: 'http://220.177.172.27:8100/yisuanzhijian',
   VITE_API_BASE_URL_GD: 'http://220.177.172.27:8100/api/',
   // VITE_API_BASE_URL: 'https://aisky.org.cn/api',
diff --git a/uniapps/work-app/src/manifest.json b/uniapps/work-app/src/manifest.json
index b4aef82..191abfb 100644
--- a/uniapps/work-app/src/manifest.json
+++ b/uniapps/work-app/src/manifest.json
@@ -30,7 +30,10 @@
         },
         /* 模块配置 */
         "modules" : {
-            "Share" : {}
+            "Share" : {},
+            "Geolocation" : {},
+            "VideoPlayer" : {},
+            "Camera" : {}
         },
         /* 应用发布信息 */
         "distribute" : {
@@ -73,6 +76,11 @@
                         "appid" : "gh_f2fbbd1d5d2a",
                         "UniversalLinks" : ""
                     }
+                },
+                "geolocation" : {
+                    "system" : {
+                        "__platform__" : [ "android" ]
+                    }
                 }
             },
             "icons" : {
@@ -113,6 +121,9 @@
         "router" : {
             "mode" : "hash",
             "base" : "/work-app/"
+        },
+        "sdkConfigs" : {
+            "maps" : {}
         }
     },
     "locale" : "zh-Hans",
diff --git a/uniapps/work-app/src/pages/work/index.vue b/uniapps/work-app/src/pages/work/index.vue
index d0a0221..31499da 100644
--- a/uniapps/work-app/src/pages/work/index.vue
+++ b/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>
diff --git a/uniapps/work-app/src/subPackages/workDetail/index.vue b/uniapps/work-app/src/subPackages/workDetail/index.vue
index 2ea0ab9..b1b1c57 100644
--- a/uniapps/work-app/src/subPackages/workDetail/index.vue
+++ b/uniapps/work-app/src/subPackages/workDetail/index.vue
@@ -30,6 +30,10 @@
         <div class="workOrderTitle">工单内容</div>
         <div class="workOrderContainer">
           <div class="orderRow">
+            <div class="rowTitle">工单名称</div>
+            <div>{{ workDetailData.eventName }}</div>
+          </div>
+          <div class="orderRow">
             <div class="rowTitle">工单编号</div>
             <div>{{ workDetailData.eventNum }}</div>
           </div>
@@ -116,17 +120,17 @@
 const viewUrl = ref("");
 onLoad(async options => {
   eventNum.value = options.id
-  await getDataList(options.id)
+  await getDataList(options.id,options.aiImg)
   checkEnvironment()
   viewUrl.value = getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(workDetailData.value) });
 })
-const getDataList = async val => {
+const getDataList = async (val,aiImg) => {
   const params = {
     id: val,
   }
   const res = await getGddetailedData(params)
   const response = res.data.data
-  workDetailData.value = response
+  workDetailData.value = {...response,aiImg}
 }
 // 检查运行环境
 const checkEnvironment = () => {
@@ -154,10 +158,8 @@
   const imageArr = []
   const detail = workDetailData.value
   if (detail.eventImageUrl) {
-    const smallUrl = detail.eventImageUrl
-    imageArr.push(smallUrl)
+    imageArr.push(detail.aiImg || detail.eventImageUrl)
   }
-
   return imageArr
 })
 // 跳转地图

--
Gitblit v1.9.3