378 lines
11 KiB
Vue
378 lines
11 KiB
Vue
<template>
|
||
<div class="dialog">
|
||
<div class="file">
|
||
<el-image
|
||
v-for="(i, index) in answers.previewImages"
|
||
:key="index"
|
||
:src="i.url"
|
||
:zoom-rate="1.2"
|
||
:max-scale="7"
|
||
:min-scale="0.2"
|
||
:scale="0.8"
|
||
:preview-src-list="answers.previewImages.map(i=>i.url)"
|
||
show-progress
|
||
:initial-index="4"
|
||
fit="object-fit"
|
||
/>
|
||
</div>
|
||
<div v-if="answers.query" class="user">
|
||
{{ answers.query }}
|
||
</div>
|
||
<div v-if="answers.answer" class="ai-message">
|
||
<img src="@/assets/images/largeModel/icon-robot.png" alt="">
|
||
<div class="content" v-html="formattedAnswer(answers.answer)"></div>
|
||
</div>
|
||
<div class="loading" v-else>
|
||
<span class="dot" v-for="i in 3" :key="i"></span>
|
||
</div>
|
||
</div>
|
||
<el-image-viewer
|
||
v-if="showPreview"
|
||
:url-list="srcList"
|
||
show-progress
|
||
:max-scale="7"
|
||
:min-scale="0.2"
|
||
:scale="0.8"
|
||
fit="object-fit"
|
||
@close="showPreview = false"
|
||
/>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||
import { marked } from 'marked'
|
||
// import { useStore } from 'vuex'
|
||
import GlobalMap from '@/components/map/js/GlobalMap.js'
|
||
|
||
// const store = useStore()
|
||
const emit = defineEmits([ 'openVideo', 'openUav' ])
|
||
const videoArr = ref([])
|
||
const uavArr = ref([])
|
||
const boatArr = ref([])
|
||
const codes = ref([])
|
||
const visible = ref(false)
|
||
// 创建自定义渲染器
|
||
const renderer = new marked.Renderer()
|
||
const showPreview = ref(false)
|
||
const srcList = ref([])
|
||
|
||
// 自定义链接渲染
|
||
renderer.link = function(e) {
|
||
if(e.href) {
|
||
return e.href.split(' ').map(i => {
|
||
return renderMedia(i)
|
||
})
|
||
}
|
||
}
|
||
|
||
// 渲染媒体元素的通用函数
|
||
const renderMedia = (url, text) => {
|
||
if (!url) {
|
||
return ''
|
||
}
|
||
console.log(url, 'url')
|
||
// 监控
|
||
if (url.startsWith('{') && url.includes('rtsp_url')) {
|
||
const videoNameMatch = JSON.parse(url).video_name
|
||
const codeMatch = JSON.parse(url).video_code
|
||
const previewUrlMatch = JSON.parse(url).rtsp_url
|
||
const videoObj = {
|
||
videoName: videoNameMatch,
|
||
videoCode: codeMatch,
|
||
previewUrl: previewUrlMatch
|
||
}
|
||
if (videoObj.videoCode) {
|
||
const exists = videoArr.value.some(item =>
|
||
item.videoCode === videoObj.videoCode && item.previewUrl === videoObj.previewUrl
|
||
)
|
||
let index
|
||
if (!exists) {
|
||
// 只有不存在时才添加
|
||
index = videoArr.value.length
|
||
videoArr.value.push(videoObj)
|
||
} else {
|
||
// 如果已存在,找到对应的索引
|
||
index = videoArr.value.findIndex(item =>
|
||
item.videoCode === videoObj.videoCode && item.previewUrl === videoObj.previewUrl
|
||
)
|
||
}
|
||
// 创建全局事件来处理视频弹窗
|
||
return `<div style="color:#1D91FE ;cursor:pointer" onclick="openVideoHandler('${index}')">
|
||
${videoObj.videoName}
|
||
</div>`
|
||
}
|
||
}
|
||
// 无人机
|
||
if(url.startsWith('{') && url.includes('droneSn')) {
|
||
if(JSON.parse(url).droneSn) {
|
||
const droneSnMatch = JSON.parse(url).droneSn
|
||
const gatewaySnMatch = JSON.parse(url).gatewaySn
|
||
const videoNameMatch = JSON.parse(url).video_name
|
||
const uavObj = {
|
||
droneSn: droneSnMatch,
|
||
gatewaySn: gatewaySnMatch,
|
||
videoName: videoNameMatch
|
||
}
|
||
if (uavObj.videoName) {
|
||
const exists = uavArr.value.some(item =>
|
||
item.videoName === uavObj.videoName
|
||
)
|
||
let index
|
||
if (!exists) {
|
||
// 只有不存在时才添加
|
||
index = uavArr.value.length
|
||
uavArr.value.push(uavObj)
|
||
} else {
|
||
// 如果已存在,找到对应的索引
|
||
index = uavArr.value.findIndex(item =>
|
||
item.droneSn === uavObj.droneSn || item.gatewaySnMatch === uavObj.gatewaySnMatch
|
||
)
|
||
}
|
||
// 创建全局事件来处理视频弹窗
|
||
return `<div style="color:#1D91FE ;cursor:pointer" onclick="openUavHandler('${index}')">
|
||
${uavObj.videoName}
|
||
</div>`
|
||
}
|
||
}
|
||
}
|
||
// 船舶智能追踪
|
||
if(url.startsWith('{') && url.includes('mmsi')) {
|
||
if(JSON.parse(url).mmsi) {
|
||
const terminalCodeMatch = JSON.parse(url).mmsi
|
||
const boatNameMatch = JSON.parse(url).boatName
|
||
const sogMatch = JSON.parse(url).sog
|
||
const cogMatch = JSON.parse(url).cog
|
||
const longitudeMatch = JSON.parse(url).longitude
|
||
const latitudeMatch = JSON.parse(url).latitude
|
||
const boatObj = {
|
||
terminalCode: terminalCodeMatch,
|
||
boatName: boatNameMatch,
|
||
sog: Number(sogMatch),
|
||
cog: 360 - Number(cogMatch),
|
||
angle: Number(cogMatch),
|
||
longitude: longitudeMatch,
|
||
latitude: latitudeMatch
|
||
}
|
||
if (boatObj.terminalCode) {
|
||
const exists = boatArr.value.some(item =>
|
||
item.terminalCode === boatObj.terminalCode
|
||
)
|
||
let index
|
||
if (!exists) {
|
||
// 只有不存在时才添加
|
||
index = boatArr.value.length
|
||
boatArr.value.push(boatObj)
|
||
} else {
|
||
// 如果已存在,找到对应的索引
|
||
index = boatArr.value.findIndex(item =>
|
||
item.terminalCode === boatObj.terminalCode || item.boatName === boatObj.boatName
|
||
)
|
||
}
|
||
// 创建全局事件来处理视频弹窗
|
||
return `<div style="color:#1D91FE ;cursor:pointer" onclick="openTrawlerHandler('${index}')">
|
||
${boatObj.boatName}
|
||
</div>`
|
||
}
|
||
}
|
||
}
|
||
// 检查是否为视频链接
|
||
if (url.match(/\.(mp4|webm|ogg)(\?.*)?$/i)) {
|
||
return `<div style="margin: 10px 0;">
|
||
<video controls loop muted style="width: 260px; height: 150px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 10px 10px 0;">
|
||
<source src="${url}" type="video/${url.split('.').pop().split('?')[0]}">
|
||
您的浏览器不支持视频播放。
|
||
</video>
|
||
</div>`
|
||
}
|
||
|
||
// 检查是否为图片链接
|
||
if (url.match(/\.(jpg|jpeg|png|gif|bmp|webp)(\?.*)?$/i)) {
|
||
return `<img onclick="openImageHandle('${url}')" src="${url}" alt="${text || ''}" style="width: 260px; height: 150px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 10px 10px 0;" />`
|
||
}
|
||
}
|
||
// 监控视频查看
|
||
const openVideoHandler = (index) => {
|
||
const data = videoArr.value[index]
|
||
// 雷云一体机一个视频
|
||
if(data.videoName.indexOf('雷云') !== -1) {
|
||
const videos = videoArr.value.filter(i => i.videoName.indexOf('雷云') !== -1).sort((a, b) => {
|
||
const index1 = a.videoName[a.videoName.length - 1]
|
||
const index2 = b.videoName[b.videoName.length - 1]
|
||
return index1 - index2
|
||
})
|
||
|
||
codes.value = [ videos[0].videoCode ]
|
||
}else{
|
||
if(data.videoName.indexOf('球机') !== -1) {
|
||
const videos = videoArr.value.filter(i => i.videoName.indexOf('球机') !== -1).sort((a, b) => {
|
||
const index1 = a.videoName[a.videoName.length - 1]
|
||
const index2 = b.videoName[b.videoName.length - 1]
|
||
return index1 - index2
|
||
})
|
||
codes.value = videos.map((i, index) => {
|
||
return i.previewUrl
|
||
})
|
||
}else{
|
||
codes.value = [ data.videoCode ]
|
||
}
|
||
}
|
||
nextTick(() => {
|
||
setTimeout(() => {
|
||
visible.value = true
|
||
emit('openVideo', { codes: codes.value, visible: visible.value })
|
||
// 再次延迟后触发窗口大小调整事件(模拟resize)
|
||
setTimeout(() => {
|
||
window.dispatchEvent(new Event('resize'))
|
||
}, 100)
|
||
}, 50)
|
||
})
|
||
}
|
||
// 预览图片
|
||
const openImageHandle = (url) => {
|
||
srcList.value = [ url ]
|
||
showPreview.value = true
|
||
}
|
||
// 开启无人机控制
|
||
const openUavHandler = (index) => {
|
||
const data = uavArr.value[index]
|
||
// store.commit('mapStore/updateUavData', { ...data })
|
||
emit('openUav', { ...data })
|
||
}
|
||
// 渔船追踪
|
||
const openTrawlerHandler = (index) => {
|
||
const data = boatArr.value[index]
|
||
// store.commit('mapStore/updateWindowInfo', {
|
||
// visible: true,
|
||
// type: '_trawler_dynamic',
|
||
// data: { ...data }
|
||
// })
|
||
GlobalMap.instance.map.animateTo(
|
||
{
|
||
zoom: 20,
|
||
center: [ data.longitude, data.latitude ]
|
||
},
|
||
{
|
||
duration: 1000 * 0.5,
|
||
easing: 'out'
|
||
}
|
||
)
|
||
}
|
||
// 配置 marked 选项并应用自定义渲染器
|
||
marked.setOptions({
|
||
renderer: renderer,
|
||
gfm: true,
|
||
breaks: true,
|
||
smartypants: false
|
||
})
|
||
const props = defineProps({
|
||
answers: {
|
||
type: Object,
|
||
default: () => {
|
||
return {}
|
||
}
|
||
}
|
||
})
|
||
const formattedAnswer = (answer) => {
|
||
if (!answer) {
|
||
return ''
|
||
}
|
||
const processedText = answer.replace(/{[^}]+}/g, (match) => {
|
||
return renderMedia(match)
|
||
})
|
||
return marked.parse(processedText)
|
||
}
|
||
onMounted(() => {
|
||
videoArr.value = []
|
||
window.openVideoHandler = openVideoHandler
|
||
window.openImageHandle = openImageHandle
|
||
// 无人机控制方法
|
||
window.openUavHandler = openUavHandler
|
||
// 渔船追踪
|
||
window.openTrawlerHandler = openTrawlerHandler
|
||
})
|
||
onUnmounted(() => {
|
||
delete window.openVideoHandler
|
||
delete window.openImageHandle
|
||
delete window.openUavHandler
|
||
delete window.openTrawlerHandler
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.dialog {
|
||
height: calc(100% - 50px);
|
||
overflow: auto;
|
||
}
|
||
|
||
.user,.file{
|
||
color: #E1F1FA;
|
||
max-width: calc(100% - 88px);
|
||
padding: 6px;
|
||
font-size: 14px;
|
||
line-height: 24px;
|
||
width: fit-content;
|
||
margin-left: auto;
|
||
|
||
background: rgba(89,175,255,0.15);
|
||
border-radius: 3px 3px 3px 3px;
|
||
}
|
||
.file{
|
||
background: none;
|
||
padding-right: 0;
|
||
.el-image{
|
||
width:100px;
|
||
height: 100px;
|
||
margin-left: 10px;
|
||
cursor: pointer;
|
||
}
|
||
}
|
||
.ai-message {
|
||
color: #E1F1FA;
|
||
border-radius: 22px;
|
||
max-width: calc(100% - 88px);
|
||
padding: 10px 0;
|
||
font-size: 14px;
|
||
line-height: 24px;
|
||
width: fit-content;
|
||
margin-right: auto;
|
||
text-align: left;
|
||
display: flex;
|
||
img{
|
||
width: 16px;
|
||
height: 16px;
|
||
margin-right: 10px;
|
||
}
|
||
.content{
|
||
margin-bottom: 10px;
|
||
background: rgba(89,175,255,0.15);
|
||
border-radius: 3px 3px 3px 3px;
|
||
padding: 6px;
|
||
}
|
||
}
|
||
.loading {
|
||
color: #0C1533;
|
||
border-radius: 22px;
|
||
max-width: calc(100% - 88px);
|
||
padding: 10px 16px;
|
||
font-size: 16px;
|
||
line-height: 24px;
|
||
width: fit-content;
|
||
margin-right: auto;
|
||
text-align: left;
|
||
}
|
||
.dot {
|
||
display: inline-block;
|
||
width: 6px;
|
||
height: 6px;
|
||
background-color: #ccc;
|
||
border-radius: 50%;
|
||
margin: 0 2px;
|
||
animation: dot-animation 1.5s infinite ease-in-out;
|
||
}
|
||
|
||
@keyframes dot-animation {
|
||
0%, 100% { opacity: 0.5; }
|
||
50% { opacity: 1; }
|
||
}
|
||
|
||
</style> |