Files
erqi-web/src/views/business/largeModel/realDialog.vue
2025-12-24 18:19:05 +08:00

378 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>