main
徐景良 1 week ago
commit 0927b4ef37

@ -0,0 +1,14 @@
import { defHttp } from '@/utils/http/axios';
enum Api {
GetAiAchievementList = '/api/AiAchievement/GetAiAchievementList',
GetAiAchievement = '/api/AiAchievement/GetAiAchievement',
}
export const GetAiAchievementList = (params) => defHttp.get({
url: Api.GetAiAchievementList,
params,
});
export const GetAiAchievement = (params) => defHttp.get({
url: Api.GetAiAchievement,
params,
});

@ -0,0 +1,25 @@
import { defHttp } from '@/utils/http/axios';
enum Api {
GetAlgoInstanceList = '/api/AlgoInstance/GetAlgoInstanceList',
AddAlgoInstance = '/api/AlgoInstance/AddAlgoInstance',
UpdateAlgoInstance = '/api/AlgoInstance/UpdateAlgoInstance',
DeleteAlgoInstance = '/api/AlgoInstance/DeleteAlgoInstance',
}
export const GetAlgoInstanceList = (params) => defHttp.get({
url: Api.GetAlgoInstanceList,
params,
});
export const AddAlgoInstance = (params) => defHttp.post({
url: Api.AddAlgoInstance,
params,
});
export const UpdateAlgoInstance = (params) => defHttp.post({
url: Api.UpdateAlgoInstance,
params,
});
export const DeleteAlgoInstance = (params) => defHttp.post({
url: Api.DeleteAlgoInstance,
data: params,
params,
});

@ -114,3 +114,12 @@ ul {
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "HarmonyOS Sans SC";
src: url("https://db.onlinewebfonts.com/t/243710884e5ebac408cd10a7a89fcc96.eot");
src: url("https://db.onlinewebfonts.com/t/243710884e5ebac408cd10a7a89fcc96.eot?#iefix") format("embedded-opentype"),
url("https://db.onlinewebfonts.com/t/243710884e5ebac408cd10a7a89fcc96.woff2") format("woff2"),
url("https://db.onlinewebfonts.com/t/243710884e5ebac408cd10a7a89fcc96.woff") format("woff"),
url("https://db.onlinewebfonts.com/t/243710884e5ebac408cd10a7a89fcc96.ttf") format("truetype"),
url("https://db.onlinewebfonts.com/t/243710884e5ebac408cd10a7a89fcc96.svg#HarmonyOS Sans SC") format("svg");
}

@ -0,0 +1,79 @@
<template>
<div ref="scrollBox" class="show-image-div">
<a-image-preview-group>
<a-image
v-for="value in props.showInfoData.aiAchievementDetailList"
:key="value"
:width="232"
:height="140"
style="border-radius: 10px;"
class="image-item"
:src="`${value.image}`"
/>
</a-image-preview-group>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, defineProps } from "vue";
import { getAppEnvConfig } from '@/utils/env';
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const props = defineProps(['showInfoData'])
const scrollBox = ref<HTMLElement | null>(null);
onMounted(() => {
const el = scrollBox.value;
if (!el) return;
const SPEED = 1.2;
const onWheel = (e: WheelEvent) => {
if (e.ctrlKey) return; //
const delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
if (!delta) return;
e.preventDefault();
const unit = e.deltaMode === 1 ? 16 : 1;
el.scrollLeft += delta * unit * SPEED;
};
el.addEventListener("wheel", onWheel, { passive: false });
onBeforeUnmount(() => el.removeEventListener("wheel", onWheel));
});
</script>
<style lang="scss" scoped>
.show-image-div {
width: calc(100% - 417px);
height: 185px;
padding: 23px 25px 22px 25px;
position: absolute;
bottom: 0px;
left: 417px;
background: #0D0E15;
backdrop-filter: blur(3px);
/* 核心:横向滚动 */
display: flex;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
gap: 22px;
overscroll-behavior: contain; /* 避免滚到边时带动页面 */
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
:deep(.ant-image) {
flex: 0 0 auto; /* 不允许收缩换行 */
border-radius: 10px;
}
}
.image-item {
border-radius: 10px;
object-fit: cover;
overflow: hidden;
}
</style>

@ -1,5 +1,10 @@
<template>
<div class="pathModal">
<!-- 地图 -->
<div style="width: 100%;height: calc(100vh - 80px);">
<Map :airLineForm="airLineForm" @mapOnLoad="mapOnLoad"/>
</div>
<!-- 左侧目录 -->
<div class="leftMenuDiv">
<PathLeftMenu
@ -7,57 +12,67 @@
@changeLeftMenuShow="changeLeftMenuShow"
/>
</div>
<!-- 地图 -->
<div class="mapDiv" :style="{ width: dynamicWidth }">
<PathMap
ref="pathMapRef"
/>
</div>
<!-- 详情 -->
<div class="areaInfoDiv" v-if="detailsInfoShow">
<PathAreaInfo
@closeDetailsInfo="closeDetailsInfo"
/>
</div>
<PathAreaInfo v-if="detailsInfoShow"
:showInfoData="showInfoData"
@closeDetailsInfo="closeDetailsInfo"
/>
<ShowImage v-if="detailsInfoShow" :showInfoData="showInfoData"/>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import { PathLeftMenu, PathMap, PathAreaInfo } from './path';
import { cloneDeep } from 'lodash-es';
import { WktToGeojson, GeojsonToWkt } from '@/components/MapboxMaps/src/WktGeojsonTransform';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage, createConfirm } = useMessage();
import Map from '@/views/demo/workmanagement/workplan/components/map.vue'
import { ref, computed, onMounted, watch } from 'vue';
import { PathLeftMenu, PathMap, PathAreaInfo, ShowImage } from './path';
import { GetAiAchievement } from '@/api/demo/aiachievement'
import * as mars3d from "mars3d";
import * as Cesium from "mars3d-cesium"
// ref
const pathMapRef = ref();
const detailsInfoShow = ref(false)
const showInfoData = ref({})
const airLineForm = ref({});
const map = ref()
const graphicLayer = ref()
//
const dynamicWidth = computed(() => {
let width = 0;
// //
// if (leftMenuShow.value) {
// width += 340;
// } else {
// width += 64;
// }
//
if (detailsInfoShow.value) {
width += 320;
watch(() => map.value, (val) => {
if (val) {
initGraphicLayer(val)
}
return '100%';
});
onMounted(() => {
});
function changeLeftMenuShow() {
detailsInfoShow.value = true;
}, { immediate: true })
function initGraphicLayer(map) {
graphicLayer.value = new mars3d.layer.GraphicLayer()
map.addLayer(graphicLayer.value)
}
const changeLeftMenuShow = (item) => {
GetAiAchievement({id:item.id}).then(res => {
showInfoData.value = res
const position:any = [res.lng, res.lat]
const pointGraphic = new mars3d.graphic.BillboardEntity({
position: position,
style: {
image: '/projecthome/project_location.png',
width: 53,
height: 97,
scale: 1,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
clampToGround: true,
}
})
map.value?.flyToPoint(position);
graphicLayer.value?.clear()
graphicLayer.value?.addGraphic(pointGraphic)
detailsInfoShow.value = true;
})
}
//
function closeDetailsInfo() {
detailsInfoShow.value = false;
pathMapRef.value.areaRestoreDefault();
graphicLayer.value?.clear()
}
const mapOnLoad = (value) => {
map.value = value
}
</script>
<style lang="less" scoped>
@ -71,10 +86,8 @@
position:absolute;
top:0px;
left:0px;
z-index:1;
display:flex;
gap:15px;
width: 340px;
width: 417px;
height: 100%;
}
@ -86,9 +99,6 @@
.areaInfoDiv {
position: absolute;
width: calc(100% - 380px);
height: 100%;
left: 340px;
}
}
</style>

@ -1,3 +1,4 @@
export { default as PathLeftMenu } from './pathLeftMenu.vue';
export { default as PathMap } from './pathMap.vue';
export { default as PathAreaInfo } from './pathAreaInfo.vue';
export { default as ShowImage } from './ShowImage.vue';

@ -10,13 +10,13 @@
<a-col :span="24">
<div class="libox">
<div class="title">内容</div>
<div class="titletext">疑似发现火情</div>
<div class="titletext">{{props.showInfoData.description}}</div>
</div>
</a-col>
<a-col :span="24">
<div class="libox yellow">
<div class="title">置信度</div>
<div class="text">95%</div>
<div class="text">{{props.showInfoData.confidenceLevel}}%</div>
</div>
</a-col>
<a-col :span="24">
@ -24,54 +24,41 @@
<div class="title">地址</div>
<div class="text">
<img class="posimg" src="/public/images/ai/location.png" />
<div class="location">临沂市费县探沂镇209国道大兴安陵西北方向山坡下</div>
<div class="location">{{ props.showInfoData.address }}</div>
</div>
</div>
</a-col>
<a-col :span="24">
<div class="libox">
<div class="title">时间</div>
<div class="titletext time">2025-05-07 13:45:23</div>
<div class="titletext time">{{props.showInfoData.createTime}}</div>
</div>
</a-col>
<a-col :span="24">
<div class="libox">
<div class="title1">拍摄<br />设备</div>
<div class="text1 time">EGUQ12212U</div>
<div class="text1 time">{{props.showInfoData.payloadModel}}</div>
</div>
</a-col>
<a-col :span="24">
<div class="libox">
<div class="title1">AI大<br />模型</div>
<div class="text1 time">AI-HUI CHUANG model2025-12-21/2U</div>
<div class="text1 time">{{ props.showInfoData.aiModel }}</div>
</div>
</a-col>
</a-row>
</div>
<div class="imgbox">
<div class="bannercontainer">
<a-image-preview-group>
<div class="bannerbox" v-for="item in bannerArr">
<div class="bannerimg">
<a-image :width="232" :height="140" :src="item" />
</div>
</div>
</a-image-preview-group>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue';
import { LeftOutlined } from '@ant-design/icons-vue';
const props = defineProps([]);
const props = defineProps(['showInfoData']);
const emits = defineEmits([
'closeDetailsInfo'
]);
const bannerArr = ref([
"/public/banner1.png", "/public/banner.png","/public/banner1.png", "/public/banner.png",
"/public/banner1.png", "/public/banner.png","/public/banner1.png", "/public/banner.png"
])
//
function closeDetailsInfo() {
emits('closeDetailsInfo');
@ -93,32 +80,7 @@
padding: 15px;
right: 0;
}
.imgbox{
width: 100%;
height: 185px;
background: #0D0E15;
position: absolute;
bottom: 0;
left: 0;
z-index: 100;
overflow-x: scroll;
.bannercontainer{
width: calc(100% - 10px);
height: 100%;
display: flex;
align-items: center;
.bannerbox{
.bannerimg{
width: 232px;
height: 140px;
border-radius: 10px;
margin-left: 25px;
cursor: pointer;
}
}
}
}
.annotationTitle {
display: flex;

@ -1,54 +1,32 @@
<template>
<div class="leftMenu">
<div class="leftMenuContent">
<div class="leftMenuContent_2">
<div class="leftMenuContent_title">
{{ showMenuInfoName }}
</div>
<div class="leftMenuContent_list">
<!-- AI成果 -->
<div v-if="showMenuInfoName == 'AI成果'" style="margin: 10px 10px 20px 16px">
<a-select
v-model:value="areatype"
style="width: 120px; margin-right: 15px;background: #151823;"
@change="handleChangeSelect"
>
<a-select-option value="all">全部类型</a-select-option>
<a-select-option value="dfence">全部作业区</a-select-option>
<a-select-option value="nfz">全部限飞区</a-select-option>
<a-select-option value="noland">全部禁降区</a-select-option>
</a-select>
<a-select
v-model:value="areastate"
style="width: 120px"
@change="handleChangeSelect"
>
<a-select-option value="all">全部状态</a-select-option>
<a-select-option value="0">已启用</a-select-option>
<a-select-option value="1">已禁用</a-select-option>
</a-select>
</div>
<!-- 列表 -->
<div v-for="(item,index) in 8" :key="item.id" @click="listsClick(index)">
<!-- AI成果 -->
<div class="showMenuInfo_area" :class="indexNum==index?'active':''">
<div class="rightbox">
<img class="fireimg" src="/public/images/ai/fire.png" />
<div class="text">置信度60%</div>
</div>
<img class="img" src="/public/banner1.png" />
<div class="bannerbox">
<div class="title">疑似发现火情</div>
<div class="line"></div>
<div class="locationbox">
<img class="posimg" src="/public/images/ai/location.png" />
<div class="location">临沂市费县探沂镇209国道大兴庄村东200米</div>
</div>
<div class="timebox">
<div class="time">2025-05-07 13:45</div>
<div class="btn">查看详情</div>
</div>
</div>
<div class="left-menu-title">AI成果</div>
<div class="left-menu-search-div">
<a-select
placeholder="选择分类"
popupClassName="ai-achievement-classify-dropdown"
class="classify-select"
:options="classifyOptions"
/>
<a-range-picker class="date-select" />
</div>
<div class="show-list-div">
<div :class="`list-item-outer ${selectItemId == item.id?'list-item-outer-select' : ''}`" v-for="(item,index) in dataList" >
<div class="list-item-inner">
<img class="item-image" :src="`${VITE_GLOB_API_URL}/${item.cover}`" >
<div class="item-info-div">
<div class="item-confidence">
<div class="confidence-icon"></div>
<div class="confidence-info">置信度{{item.confidenceLevel}}%</div>
</div>
<div class="item-title">{{item.title}}</div>
<div class="item-address">
<div class="address-icon"></div>
<div class="address-info">{{item.address}}</div>
</div>
<div class="time-button-div">
<div class="item-item">{{ item.createTime }}</div>
<a-button :class="`show-info-button ${selectItemId == item.id? 'show-info-button-select': ''}`" type="primary" @click="showInfo(item)"></a-button>
</div>
</div>
</div>
@ -57,201 +35,250 @@
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from 'vue';
import {
CloseOutlined,
AntDesignOutlined,
DoubleLeftOutlined,
DoubleRightOutlined,
EnvironmentOutlined,
CodeSandboxOutlined,
ExpandAltOutlined,
BorderOutlined,
LogoutOutlined,
EyeOutlined,
EyeInvisibleOutlined,
AimOutlined,
DeleteOutlined,
FileImageOutlined,
CheckCircleOutlined,
StopOutlined,
} from '@ant-design/icons-vue';
import { ref, onMounted } from 'vue';
import { UpdateWorkArea, UpdateAnnotation } from '@/api/demo/mediaLibrary';
import { cloneDeep } from 'lodash-es';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage, createConfirm } = useMessage();
const props = defineProps([
]);
import { GetAiAchievementList } from '@/api/demo/aiachievement'
import { getAppEnvConfig } from '@/utils/env';
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const emits = defineEmits([
'changeLeftMenuShow'
]);
const showMenuInfoName = ref('AI成果');
const areatype = ref('all')
const areastate = ref('all')
const indexNum = ref()
onMounted(() => {
GetAiAchievementList({page:1,limit:999}).then(res => {
console.log('res',res)
dataList.value = res.items
})
})
function handleChangeSelect() {
const selectItemId = ref()
const dataList = ref([])
const classifyOptions = ref([
{label:'1231231', value:'123123123'},
{label:'222', value:'222'},
{label:'333', value:'333'},
])
}
function listsClick(e){
indexNum.value = e
emits('changeLeftMenuShow');
function showInfo(item){
selectItemId.value = item.id
emits('changeLeftMenuShow',item);
}
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.leftMenu {
width:100%;
height:100%;
background: #0D0E15;
background: rgba(13, 14, 21,0.9);
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 10px 30px 1px rgba(0,0,6,0.15), inset 0px 0px 20px 8px rgba(58,87,232,0.73);
border-radius: 6px;
position:relative;
.leftMenuContent {
position: relative;
padding: 10px 17px 10px 14px;
.left-menu-title{
height: 37px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-bottom: 9px;
}
.left-menu-search-div{
display: flex;
width: 100%;
height: 100%;
.leftMenuContent_2 {
position: relative;
width: 100%;
height: 100%;
.leftMenuContent_title {
font-size: 16px;
padding:15px 15px;
color:#fff;
border-bottom:1px solid rgba(255,255,255,0.15);
height: 30px;
margin-bottom: 22px;
.classify-select{
width: 99px;
height: 30px;
background: #1D2339;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 6px;
border: 1px solid #3B4154;
margin-right: 18px;
::v-deep(.ant-select-selection-placeholder) {
color: rgba(255, 255, 255, 0.15);
}
.leftMenuContent_list {
overflow-y: auto;
max-height: calc(100% - 60px);
// AI
.showMenuInfo_area {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
height: 115px;
padding: 12px 8px;
margin: 6px 15px;
background: #313C62;
}
.date-select{
width: 269px;
height: 30px;
background: #1D2339;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 6px;
border: 1px solid #3B4154;
color: #fff;
::v-deep(.ant-picker-input > input::placeholder) {
color: rgb(255, 255, 255, 0.15);
}
::v-deep(.ant-picker-input > input) {
color: #fff;
}
:deep(.ant-picker-clear){
background: #1D2339;
color: rgba(0, 0, 0, 0.2)
}
}
:deep(.ant-select-selector){
height: 30px;
background: #1D2339;
border: 1px solid #3B4154;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #FFFFFF;
line-height: 17px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
:deep(.ant-select-selection-item){
color: #FFFFFF;
}
:deep(.ant-select-arrow){
color: #fff;
}
}
.show-list-div{
height: calc(100% - 100px);
overflow: auto;
scrollbar-width: none;
::-webkit-scrollbar{
width: 0;
height: 0;
}
.list-item-outer {
border-radius: 6px;
padding: 1px;
background: linear-gradient(7deg, rgba(49, 60, 98, 1), rgba(66, 80, 126, 1));
margin-bottom: 12px;
.list-item-inner {
position: relative;
height: 115px;
border-radius: 6px;
background: rgba(49,60,98,0.8);
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
backdrop-filter: blur(1px);
padding: 13px 10px 16px 9px;
display: flex;
.item-image{
width: 154px;
height: 86px;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 6px;
.rightbox{
width: 81px;
height: 23px;
background: url("/public/images/ai/posbg.png") no-repeat center;
background-size: 100% 100%;
position: absolute;
right: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 9px;
border-radius: 6px;
margin-right: 13px;
}
.item-info-div{
width: 200px;
.item-title{
width: 140px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
.fireimg{
width: 14px;
height: 14px;
margin-right: 3px;
margin-left: 10px;
}
line-height: 22px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
padding-bottom: 5px;
border-bottom: 1px dashed rgba(144, 161, 176,0.3);
margin-bottom: 6px;
}
.img{
width: 90px;
height: 90px;
}
.bannerbox{
flex: 1;
.item-address{
display: flex;
flex-direction: column;
margin-left: 10px;
.title{
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
line-height: 22px;
margin-bottom: 5px;
.address-icon{
width: 15px;
height: 15px;
background-image: url('/public/images/ai/location.png');
background-size: 100% 100%;
margin-right: 6px;
}
.address-info{
font-family: HarmonyOS Sans SC;
font-size: 12px;
color: #D3F1FF;
line-height: 14px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
opacity: 0.56;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 180px;
margin-bottom: 13px;
}
.line{
width: 190px;
height: 1px;
border-bottom: 1px dashed #90A1B0;
opacity: 0.3;
margin-bottom: 6px;
}
.time-button-div{
display: flex;
align-items: center;
justify-content: space-between;
.item-item{
font-family: Bebas;
font-size: 10px;
color: #FFFFFF;
line-height: 13px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
.locationbox{
.show-info-button{
width: 67px;
height: 25px;
background: #00B569;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 4px;
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 10px;
color: #FFFFFF;
line-height: 13px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
display: flex;
align-items: center;
justify-content: flex-start;
line-height: 14px;
.posimg{
width: 15px;
height: 15px;
}
.location{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 3px;
width: 180px;
font-family: HarmonyOS_Sans_SC;
font-size: 12px;
color: #D3F1FF;
line-height: 14px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
text-align: left;
font-style: normal;
text-transform: none;
}
justify-content: center;
}
.timebox{
margin-top: 13px;
display: flex;
align-items: center;
justify-content: space-between;
.time{
font-family: Bebas;
font-size: 10px;
color: #FFFFFF;
line-height: 13px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
text-align: left;
font-style: normal;
}
.btn{
width: 67px;
height: 25px;
background: #00B569;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 4px;
font-weight: 500;
font-size: 10px;
color: #FFFFFF;
text-align: center;
line-height: 25px;
margin-right: 5px;
}
.show-info-button-select{
background: #0081FF;
}
}
}
.active{
outline: 2px solid #2D8CF0;
}
.item-confidence{
position: absolute;
top: 0px;
right: 0px;
width: 77px;
height: 22px;
background-image: url('/public/images/ai/posbg.png');
background-size: 100% 100%;
padding-top: 4px;
padding-left: 12px;
display: flex;
.confidence-icon{
width: 12px;
height: 12px;
background-image: url('/public/images/ai/fire.png');
background-size: 100% 100%;
margin-right: 3px;
}
.confidence-info{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 9px;
color: #FFFFFF;
line-height: 12px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
white-space: nowrap;
}
}
}
}
}
.list-item-outer-select{
background: linear-gradient(7deg, rgba(0, 129, 255, 1), rgba(66, 80, 126, 1));
}
}
}
</style>
<style lang="scss">
.ai-achievement-classify-dropdown{
background: #1D2339;
.ant-select-item-option-content{
color: #ffffff;
}
.ant-select-item-option-selected{
background-color: #37383d !important;
}
}
</style>

@ -2,220 +2,274 @@
<div class="title-div">
<div class="title-span-div">
<div class="title-icon"></div>
<div class="title-span">创建算法实例</div>
<div class="title-span">{{`${props.modalType == 'insert'? '创建':'编辑'}算法实例`}}</div>
</div>
<div class="close-button" @click="emits('changeAddModal',false)"></div>
</div>
<div class="interval"></div>
<div class="modal-content">
<div class="content-steps-div">
<a-steps class="content-steps" :current="instanceSteps" label-placement="vertical" :items="items" />
</div>
<div class="step0-info-div" v-if="instanceSteps == 0">
<div class="left-content">
<div class="item-title">实例名称</div>
<a-input style="margin-bottom: 12px;" class="item-input" v-model:value="instanceName" placeholder="请输入的字段不得超过20字" />
<div class="item-title">设置封面</div>
<div class="instance-image-item-div">
<div class="default-image-div">
<div class="default-image"></div>
<div class="default-title-div">默认</div>
<a-spin :spinning="loading" tip="加载中...">
<div class="modal-content">
<div class="content-steps-div">
<a-steps class="content-steps" :current="instanceSteps" label-placement="vertical" :items="items" />
</div>
<div class="step0-info-div" v-if="instanceSteps == 0">
<div class="left-content">
<div class="item-title">实例名称</div>
<a-input style="margin-bottom: 12px;" class="item-input" v-model:value="name" placeholder="请输入的字段不得超过20字" />
<div class="item-title">设置封面</div>
<div class="instance-image-item-div">
<div class="default-image-div">
<div class="default-image"></div>
<div class="default-title-div">默认</div>
</div>
<div class="select-image-div" @click="changeSelectImageModal(true)">
<div style="padding-top: 26px;" v-if="cover == ''">
<div class="select-image-icon"></div>
<div style="margin-top: 9px;" class="select-image-span">支持JPGPNG格式</div>
<div class="select-image-span">推荐尺寸300*200</div>
</div>
<img v-else class="show-image" :src="`${VITE_GLOB_API_URL}/${cover}`" @click="changeSelectImageModal(true)"/>
<div class="select-title-div">本地上传</div>
</div>
</div>
<div class="select-image-div" @click="changeSelectImageModal(true)">
<div class="select-image-icon"></div>
<div style="margin-top: 9px;" class="select-image-span">支持JPGPNG格式</div>
<div class="select-image-span">推荐尺寸300*200</div>
<div class="select-title-div">本地上传</div>
<div class="item-title">显示文案</div>
<a-input style="margin-bottom: 17px;" class="item-input" v-model:value="displayScheme" placeholder="请输入的字段不得超过20字" />
</div>
<div class="right-content">
<div class="item-title">实例描述</div>
<a-input style="margin-bottom: 12px;" class="item-input" v-model:value="description" placeholder="请输入的字段不得超过200字" />
<div class="item-title">显示颜色</div>
<div class="select-item-color-div">
<div :style="`background: ${item.value}`" :class="`color-item ${displayColor == item.value? 'select-color-item': ''}`" v-for="item in colorOptions" @click="changeSelectColor(item.value)"></div>
</div>
<div class="item-title">关联事件</div>
<a-select
class="item-select"
v-model:value="selectEvents"
:options="selectEventsOptions"
/>
</div>
<div class="item-title">显示文案</div>
<a-input style="margin-bottom: 17px;" class="item-input" v-model:value="instanceDescription" placeholder="请输入的字段不得超过20字" />
</div>
<div class="right-content">
<div class="item-title">实例描述</div>
<a-input style="margin-bottom: 12px;" class="item-input" v-model:value="instanceDescription" placeholder="请输入的字段不得超过200字" />
<div class="item-title">显示颜色</div>
<div class="select-item-color-div">
<div :style="`background: ${item.value}`" class="color-item" v-for="item in colorOptions"></div>
</div>
<div class="item-title">关联事件</div>
<div class="step1-info-div" v-if="instanceSteps == 1">
<div class="item-title">AI算法</div>
<a-select
class="item-select"
v-model:value="selectEvents"
:options="selectEventsOptions"
style="margin-bottom: 6px;"
class="item-select item-multiple-select"
mode="multiple"
v-model:value="algoIdsList"
:options="props.algorithmOptions"
@change="changeSelectAlgorithm"
/>
</div>
</div>
<div class="step1-info-div" v-if="instanceSteps == 1">
<div class="item-title">AI算法</div>
<a-select
style="margin-bottom: 6px;"
class="item-select item-multiple-select"
mode="multiple"
v-model:value="selectAlgorithm"
:options="selectAlgorithmOptions"
/>
<div style="display: flex;justify-content: space-between;align-items: center;" class="item-title">
识别标签
<div class="tag-list-button">
<div class="select-all-button">全选</div>
<div class="clear-button">清空</div>
</div>
</div>
<div class="tag-show-list-div">
<div class="list-title">
<div>算法</div>
<div>名称</div>
<div>枚举值</div>
<div>颜色</div>
<div>置信度</div>
</div>
<div class="tag-list-content">
<div class="list-item" v-for="value in 10">
<a-checkbox class="item-checkbox"></a-checkbox>
<div style="width: 72px;margin-right: 60px;" class="item-span">高速排水沟水沟水沟</div>
<div style="width: 72px;margin-right: 60px;" class="item-span">高速排水沟水沟水沟</div>
<div style="width: 72px;margin-right: 64px;" class="item-span">0</div>
<div class="item-color"></div>
<div class="item-believe">0.75</div>
<div style="display: flex;justify-content: space-between;align-items: center;" class="item-title">
识别标签
<div class="tag-list-button">
<div class="select-all-button" @click="selectAllTagsShow"></div>
<div class="clear-button" @click="clearAllTagsSelect"></div>
</div>
</div>
</div>
<div class="item-title">画面识别区域</div>
<div class="identify-setting">
<div class="identify-setting-item" style="margin-right: 207px;">
<div class="item-label">水平方向</div>
<div class="item-value">
<a-input class="identify-setting-input" />
<div class="identify-setting-input-icon">%</div>
<div class="tag-show-list-div">
<div class="list-title">
<div>算法</div>
<div>名称</div>
<div>枚举值</div>
<div>颜色</div>
<div>置信度</div>
</div>
</div>
<div class="identify-setting-item">
<div class="item-label">垂直方向</div>
<div class="item-value">
<a-input class="identify-setting-input" />
<div class="identify-setting-input-icon">%</div>
<div class="tag-list-content">
<div class="list-item" v-for="item in tagsShowList">
<a-checkbox class="item-checkbox" :checked="tagsList.includes(item.id)" @click="changeChecked(item.id)"></a-checkbox>
<div style="width: 72px;margin-right: 60px;" class="item-span">{{item.pName}}</div>
<div style="width: 72px;margin-right: 60px;" class="item-span">{{item.name}}</div>
<div style="width: 72px;margin-right: 64px;" class="item-span">{{ item.enumValue }}</div>
<div class="item-color"></div>
<div class="item-believe">{{ item.reliability }}</div>
</div>
</div>
</div>
</div>
<div class="item-title">
空间约束
<a-switch v-model:checked="spaceConstraints" style="margin-left: 21px;" />
</div>
<div class="space-constraints-setting">
<div class="setting-hint">
开启后识别对象需要在制定区域内才生效该区域需要在算法实例开启时设置可用于非法人员闯入疑似非法盗采等场景
</div>
<div class="setting-content" v-if="spaceConstraints">
<div class="setting-item">
<div class="item-label">外扩距离</div>
<div class="item-title">画面识别区域</div>
<div class="identify-setting">
<div class="identify-setting-item" style="margin-right: 207px;">
<div class="item-label">水平方向</div>
<div class="item-value">
<a-input class="setting-input" />
<div class="setting-input-icon"></div>
<a-input class="identify-setting-input" v-model:value="recognitionX"/>
<div class="identify-setting-input-icon">%</div>
</div>
</div>
</div>
</div>
<div class="item-title">
时间约束
<a-switch v-model:checked="timeConstraints" style="margin-left: 21px;" />
</div>
<div class="time-constraints-setting">
<div class="setting-hint">开启后仅在设置时间内开启AI识别</div>
<div class="setting-content">
<div class="time-constraints-setting-item">
<div class="time-constraints-item-label">有效时段</div>
<div style="margin-right: 16px;" class="time-constraints-item-value">
<a-time-picker :bordered="false" placeholder="开始时间"/>
</div>
<div style="margin-right: 13px;display: flex;align-items: center;">-</div>
<div class="time-constraints-item-value">
<a-time-picker :bordered="false" placeholder="结束时间"/>
<div class="identify-setting-item">
<div class="item-label">垂直方向</div>
<div class="item-value">
<a-input class="identify-setting-input" v-model:value="recognitionY"/>
<div class="identify-setting-input-icon">%</div>
</div>
</div>
</div>
</div>
</div>
<div class="step2-info-div" v-if="instanceSteps == 2">
<div class="step2-title">飞行速度</div>
<div class="step2-setting-div">
<div class="step2-hint">取值范围1m/s-20m/s,飞行速度过快会导致画面模糊无法识别到对象</div>
<div class="step2-setting-content">
<div class="step2-input-div step2-first-input">
<a-input class="step2-input"/>
<div class="step2-input-icon">m/s</div>
<div class="item-title">
空间约束
<a-switch :checked="spaceConstraint == 1" style="margin-left: 21px;" @click="changeConstraint('spaceConstraint')"/>
</div>
<div class="space-constraints-setting">
<div class="setting-hint">
开启后识别对象需要在制定区域内才生效该区域需要在算法实例开启时设置可用于非法人员闯入疑似非法盗采等场景
</div>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input"/>
<div class="step2-input-icon">m/s</div>
<div class="setting-content" v-if="spaceConstraint == 1">
<div class="setting-item">
<div class="item-label">外扩距离</div>
<div class="item-value">
<a-input class="setting-input" v-model:value="expansionDistance"/>
<div class="setting-input-icon"></div>
</div>
</div>
</div>
</div>
</div>
<div class="step2-title">云台俯仰角</div>
<div class="step2-setting-div">
<div class="step2-hint">取值范围0-90°俯仰角决定对象在画面中的角度</div>
<div class="step2-setting-content">
<div class="step2-input-div step2-first-input">
<a-input class="step2-input"/>
<div class="step2-input-icon">°</div>
</div>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input"/>
<div class="step2-input-icon">°</div>
<div class="item-title">
时间约束
<a-switch :checked="temporalConstraints == 1" style="margin-left: 21px;" @click="changeConstraint('temporalConstraints')"/>
</div>
<div class="time-constraints-setting">
<div class="setting-hint">开启后仅在设置时间内开启AI识别</div>
<div class="setting-content" v-if="temporalConstraints == 1">
<div class="time-constraints-setting-item">
<div class="time-constraints-item-label">有效时段</div>
<div style="margin-right: 16px;" class="time-constraints-item-value">
<a-time-picker :bordered="false" placeholder="开始时间" v-model:value="tcStartTime" value-format="HH:mm:ss"/>
</div>
<div style="margin-right: 13px;display: flex;align-items: center;">-</div>
<div class="time-constraints-item-value">
<a-time-picker :bordered="false" placeholder="结束时间" v-model:value="tcEndTime" value-format="HH:mm:ss"/>
</div>
</div>
</div>
</div>
</div>
<div class="step2-title">识别对象画面占比</div>
<div class="step2-setting-div">
<div class="step2-hint">识别对象画面占比比例直接影响识别的准确率实际需要
结合航高变焦倍率保证识别对象在画面中的占比</div>
<div class="step2-setting-content">
<div class="step2-input-div step2-first-input">
<a-input class="step2-input"/>
<div class="step2-input-icon">%</div>
<div class="step2-info-div" v-if="instanceSteps == 2">
<div class="step2-title">飞行速度</div>
<div class="step2-setting-div">
<div class="step2-hint">取值范围1m/s-20m/s,飞行速度过快会导致画面模糊无法识别到对象</div>
<div class="step2-setting-content">
<div class="step2-input-div step2-first-input">
<a-input class="step2-input" v-model:value="flySpeedFirst"/>
<div class="step2-input-icon">m/s</div>
</div>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input" v-model:value="flySpeedLast"/>
<div class="step2-input-icon">m/s</div>
</div>
</div>
</div>
<div class="step2-title">云台俯仰角</div>
<div class="step2-setting-div">
<div class="step2-hint">取值范围0-90°俯仰角决定对象在画面中的角度</div>
<div class="step2-setting-content">
<div class="step2-input-div step2-first-input">
<a-input class="step2-input" v-model:value="gimbalPitchDegreeFirst"/>
<div class="step2-input-icon">°</div>
</div>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input" v-model:value="gimbalPitchDegreeLast"/>
<div class="step2-input-icon">°</div>
</div>
</div>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input"/>
<div class="step2-input-icon">%</div>
</div>
<div class="step2-title">识别对象画面占比</div>
<div class="step2-setting-div">
<div class="step2-hint">识别对象画面占比比例直接影响识别的准确率实际需要
结合航高变焦倍率保证识别对象在画面中的占比</div>
<div class="step2-setting-content">
<div class="step2-input-div step2-first-input">
<a-input class="step2-input" v-model:value="recognitionCoverageFirst"/>
<div class="step2-input-icon">%</div>
</div>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input" v-model:value="recognitionCoverageLast"/>
<div class="step2-input-icon">%</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="interval"></div>
<div class="footer-div">
<a-button class="cancel-button" @click="emits('changeAddModal',false)"></a-button>
<a-button v-if="instanceSteps != 0" style="margin-right: 14px;" type="primary" class="next-button" @click="last"></a-button>
<a-button v-if="instanceSteps != 2" type="primary" class="next-button" @click="next"></a-button>
<a-button v-if="instanceSteps == 2" type="primary" class="next-button" @click="submit"></a-button>
</div>
<div class="interval"></div>
<div class="footer-div">
<a-button class="cancel-button" @click="emits('changeAddModal',false)"></a-button>
<a-button v-if="instanceSteps != 0" style="margin-right: 14px;" type="primary" class="next-button" @click="last"></a-button>
<a-button v-if="instanceSteps != 2" type="primary" class="next-button" @click="next"></a-button>
<a-button v-if="instanceSteps == 2" type="primary" class="next-button" @click="submit"></a-button>
</div>
</a-spin>
<a-modal v-model:open="selectImageModal" title="选择封面" width="1000px" :footer="null" :destroyOnClose="true">
<SelectImageModal @changeSelectImageModal="changeSelectImageModal"/>
<SelectImageModal @changeSelectImageModal="changeSelectImageModal" @getResultImage="getResultImage"/>
</a-modal>
</template>
<script setup lang="ts">
import { ref, h, defineEmits, defineProps } from "vue"
import { ref, h, defineEmits, defineProps, onMounted } from "vue"
import SelectImageModal from "@/views/demo/resourcemanagement/ailist/SelectImageModal.vue";
const emits = defineEmits(['changeAddModal'])
import { ModelLabelsType } from '@/views/demo/resourcemanagement/ailist/utils'
import { getAppEnvConfig } from '@/utils/env';
import { AddAlgoInstance, UpdateAlgoInstance } from '@/api/demo/aiinstance'
import { message } from "ant-design-vue";
import dayjs from "dayjs";
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const props = defineProps(['algorithmOptions','modalType','showInfoData'])
const emits = defineEmits(['changeAddModal','query'])
onMounted(() => {
if(props.modalType == 'update'){
id.value = props.showInfoData.id
name.value = props.showInfoData.name
cover.value = props.showInfoData.cover
displayScheme.value = props.showInfoData.displayScheme
description.value = props.showInfoData.description
displayColor.value = props.showInfoData.displayColor
algoIdsList.value = props.showInfoData.algoIds.split(',')
changeSelectAlgorithm(algoIdsList.value)
tagsList.value = props.showInfoData.tags.split(',')
recognitionX.value = props.showInfoData.recognitionX
recognitionY.value = props.showInfoData.recognitionY
spaceConstraint.value = props.showInfoData.spaceConstraint
expansionDistance.value = props.showInfoData.expansionDistance
temporalConstraints.value = props.showInfoData.temporalConstraints
tcStartTime.value = dayjs(props.showInfoData.tcStartTime)
tcEndTime.value = dayjs(props.showInfoData.tcEndTime)
flySpeedFirst.value = props.showInfoData.flySpeed?.split('~')[0] || ''
flySpeedLast.value = props.showInfoData.flySpeed.split('~')[1] || ''
gimbalPitchDegreeFirst.value = props.showInfoData.gimbalPitchDegree?.split('~')[0] || ''
gimbalPitchDegreeLast.value = props.showInfoData.gimbalPitchDegree.split('~')[1] || ''
recognitionCoverageFirst.value = props.showInfoData.recognitionCoverage?.split('~')[0] || ''
recognitionCoverageLast.value = props.showInfoData.recognitionCoverage.split('~')[1] || ''
}
})
const instanceSteps = ref(0)
const instanceName = ref('')
const instanceDescription = ref('')
const loading = ref(false)
const id = ref('')
const name = ref('')
const cover = ref('')
const displayScheme = ref('')
const description = ref('')
const displayColor = ref('')
const selectEvents = ref('')
const selectAlgorithm = ref()
const spaceConstraints = ref(false)
const timeConstraints = ref(false)
const algoIdsList = ref()
const tagsShowList = ref<ModelLabelsType[]>([])
const tagsList = ref<string[]>([])
const recognitionX = ref('')
const recognitionY = ref('')
const spaceConstraint = ref(0)
const expansionDistance = ref('')
const temporalConstraints = ref(0)
const tcStartTime = ref('')
const tcEndTime = ref('')
const flySpeedFirst = ref('')
const flySpeedLast = ref('')
const gimbalPitchDegreeFirst = ref('')
const gimbalPitchDegreeLast = ref('')
const recognitionCoverageFirst = ref('')
const recognitionCoverageLast = ref('')
const selectImageModal = ref(false)
const selectEventsOptions = ref([
{ label: 'aaa', value: 'aaa' }
])
const selectAlgorithmOptions = ref([
{ label: 'bbb', value: 'bbb' },
{ label: 'ccc', value: 'ccc' }
])
const items = ref([
{ title: '基础信息', },
{ title: '配置算法', },
@ -251,11 +305,92 @@ const last = () => {
instanceSteps.value --
}
const submit = () => {
loading.value = true
let params = {
id: id.value,
name: name.value,
cover: cover.value,
displayScheme: displayScheme.value,
description: description.value,
displayColor: displayColor.value,
recognitionX: recognitionX.value,
recognitionY: recognitionY.value,
spaceConstraint: spaceConstraint.value,
expansionDistance: expansionDistance.value,
temporalConstraints: temporalConstraints.value,
tcStartTime: tcStartTime.value,
tcEndTime: tcEndTime.value,
flySpeed: `${flySpeedFirst.value}~${flySpeedLast.value}`,
gimbalPitchDegree: `${gimbalPitchDegreeFirst.value}~${gimbalPitchDegreeLast.value}`,
recognitionCoverage: `${recognitionCoverageFirst.value}~${recognitionCoverageLast.value}`,
tags: tagsList.value.join(','),
algoIds: algoIdsList.value.join(','),
}
console.log('params',params)
if(props.modalType == 'insert'){
AddAlgoInstance(params).then(res => {
console.log(res)
message.success('算法实例创建成功')
emits('query')
emits('changeAddModal',false)
}).finally(() => {
loading.value = false
})
}else{
UpdateAlgoInstance(params).then(res => {
console.log(res)
message.success('算法实例编辑成功')
emits('query')
emits('changeAddModal',false)
}).finally(() => {
loading.value = false
})
}
}
const changeSelectImageModal = (type: boolean) => {
selectImageModal.value = type
}
const getResultImage = (url: string) => {
cover.value = url
}
const changeSelectColor = (color) => {
displayColor.value = color
}
const changeSelectAlgorithm = (e:string[]) => {
console.log(e)
let selectAlgorithmOptions = props.algorithmOptions.filter(item => e.includes(item.value))
let list:ModelLabelsType[] = []
selectAlgorithmOptions.forEach(item => {
item.modelLabels.forEach(itemChild => {
list.push({...itemChild,pName:item.label})
})
})
tagsShowList.value = list
console.log('tagsShowList',tagsShowList.value)
}
const changeChecked = (id) => {
if(tagsList.value.includes(id)){
tagsList.value = tagsList.value.filter(item => item !== id)
}else{
tagsList.value.push(id)
}
}
const changeConstraint = (type) => {
switch(type){
case 'spaceConstraint':
spaceConstraint.value = spaceConstraint.value == 1? 0: 1
break
case 'temporalConstraints':
temporalConstraints.value = temporalConstraints.value == 1? 0: 1
break
}
}
const selectAllTagsShow = () => {
tagsList.value = tagsShowList.value.map(item => item.id)
}
const clearAllTagsSelect = () => {
tagsList.value = []
}
</script>
@ -470,7 +605,7 @@ const changeSelectImageModal = (type: boolean) => {
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border: 2px dashed rgba(28,29,34,0.08);
padding-top: 26px;
// padding-top: 26px;
cursor: pointer;
.select-image-icon{
width: 36px;
@ -507,6 +642,10 @@ const changeSelectImageModal = (type: boolean) => {
align-items: center;
justify-content: center;
}
.show-image{
width: 169px;
height: 151px;
}
}
}
}
@ -529,6 +668,10 @@ const changeSelectImageModal = (type: boolean) => {
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(0,0,0,0.06);
// border: 1px solid #FFFFFF;
border-radius: 50%;
cursor: pointer;
}
.select-color-item{
border: 1px solid;
}
}
}

@ -5,25 +5,25 @@
<div class="title-span">算法实例详情</div>
</div>
<div class="title-tools-div">
<EditOutlined style="font-size: 19px;margin-right: 19px;"/>
<EditOutlined style="font-size: 19px;margin-right: 19px;cursor: pointer;" @click="emits('changeAddModal',true,false)"/>
<div class="button-interval"></div>
<CloseOutlined style="font-size: 19px;"/>
<CloseOutlined style="font-size: 19px;cursor: pointer;" @click="emits('changeDrawerModal',false)"/>
</div>
</div>
<div class="drawer-content">
<img class="content-image" :src="`${VITE_GLOB_API_URL}/20250818\\2025081816060064980138.jpeg`"/>
<img class="content-image" :src="`${VITE_GLOB_API_URL}/${props.showInfoData.cover}`"/>
<div class="content-item-name">
<div class="item-name-label">实例名称</div>
<div class="item-name-value">公路-隔离栏破损识别</div>
<div class="item-name-value">{{ props.showInfoData.name }}</div>
</div>
<div class="content-item-description">
<div class="item-description-label">描述</div>
<div class="item-description-value">暂无描述</div>
<div class="item-description-value">{{props.showInfoData.description}}</div>
</div>
<div class="content-interval"></div>
<div class="content-item">
<div class="item-label">显示内容</div>
<div class="item-value">破损隔离栏</div>
<div class="item-value">{{ props.showInfoData.displayScheme }}</div>
</div>
<div class="content-item">
<div class="item-label">关联事件</div>
@ -31,37 +31,41 @@
</div>
<div class="content-item">
<div class="item-label">AI算法</div>
<div class="item-value">高速隔离栏破损检测算法</div>
<div class="item-value">
<div v-for="algoName in props.showInfoData.algoNames">{{ `${algoName}` }}</div>
</div>
</div>
<div class="content-interval"></div>
<div class="content-item">
<div class="item-label" style="margin-right: 0px;width: 101px;">画面识别区域</div>
<div class="item-value" style="padding-left: 22px;">水平方向100% 垂直方向100%</div>
<div class="item-value" style="padding-left: 22px;">{{`水平方向:${props.showInfoData.recognitionX}% 垂直方向:${props.showInfoData.recognitionY}%`}}</div>
</div>
<div class="content-item">
<div class="item-label">空间约束</div>
<div class="item-value">关闭</div>
<div class="item-value">{{props.showInfoData.spaceConstraint == 1?`外扩距离:${props.showInfoData.expansionDistance}`:'关闭'}}</div>
</div>
<div class="content-item">
<div class="item-label">时间约束</div>
<div class="item-value">关闭</div>
<div class="item-value">{{props.showInfoData.temporalConstraints == 1?`有效时段:${props.showInfoData.tcStartTime}~${props.showInfoData.tcEndTime}`:'关闭'}}</div>
</div>
<div class="content-item">
<div class="item-label">航飞要求</div>
<div class="item-value">
<div>飞行速度3m/s~15m/s</div>
<div>云台俯仰角-30°-90°</div>
<div>对象画面对比5%10%</div>
<div>{{`飞行速度:${props.showInfoData.flySpeed.split('~')[0]}m/s~${props.showInfoData.flySpeed.split('~')[1]}m/s`}}</div>
<div>{{`云台俯仰角:${props.showInfoData.gimbalPitchDegree.split('~')[0]}°~${props.showInfoData.gimbalPitchDegree.split('~')[1]}°`}}</div>
<div>{{`对象画面对比:${props.showInfoData.recognitionCoverage.split('~')[0]}%${props.showInfoData.recognitionCoverage.split('~')[1]}%`}}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {} from "vue"
import { defineProps, defineEmits } from "vue"
import { EditOutlined, CloseOutlined } from '@ant-design/icons-vue'
import { getAppEnvConfig } from '@/utils/env';
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const props = defineProps(['showInfoData'])
const emits = defineEmits(['changeDrawerModal','changeAddModal'])
</script>
<style lang="scss" scoped>
@ -191,7 +195,7 @@ const { VITE_GLOB_API_URL } = getAppEnvConfig();
}
.item-value{
width: 294px;
height: 27px;
// height: 27px;
// background: rgba(216, 216, 216, 0.34);
padding-left: 28px;
font-family: PingFangSC-Medium;

@ -1,8 +1,9 @@
<template>
<a-spin tip="加载中..." :spinning="loading">
<div class="page-out">
<div class="title">
<div class="search-div">
<a-input class="search-input" v-model:value="instanceName" placeholder="搜索实例名称…">
<a-input class="search-input" v-model:value="searchValue" placeholder="搜索实例名称…">
<template #prefix>
<div class="search-input-icon"></div>
</template>
@ -13,24 +14,24 @@
v-model:value="instanceAlgorithm"
:options="algorithmOptions"
/>
<a-button class="search-button" type="primary">查询</a-button>
<a-button class="search-button" type="primary" @click="query"></a-button>
</div>
<a-button class="add-instance" type="primary" :icon="h(PlusOutlined)" @click="changeAddModal(true)"></a-button>
</div>
<div class="show-list-div">
<div class="show-list">
<div class="item-list" v-for="value in 10">
<div class="item-list" v-for="item in dataList">
<div class="image-div">
<img class="image-item" :src="`${VITE_GLOB_API_URL}/20250818\\2025081816060064980138.jpeg`">
<img class="image-item" :src="`${VITE_GLOB_API_URL}/${item.cover}`">
<div class="image-icon">盖板缺失</div>
</div>
<div class="show-info-div">
<div class="info-title-div">
<div class="info-icon"></div>
<div class="info-title-inner">
<div class="info-title">公路-排水沟盖板确实是识别</div>
<div class="info-title">{{ item.name }}</div>
<div style="display: flex;justify-content: space-between;">
<div class="info-subtitle">暂无描述</div>
<div class="info-subtitle">{{ item.description }}</div>
<a-popover overlayClassName="instance-show-more-info-popover" trigger="click" placement="topRight">
<template #content>
<div class="show-more-content">
@ -38,16 +39,12 @@
<div class="content-label">关联事件</div>
<div class="content-value">
<div>-</div>
<div>-</div>
<div>-</div>
</div>
</div>
<div class="content-raw">
<div class="content-label">关联算法</div>
<div class="content-value">
<div>#高速排水沟盖板缺失检测算法</div>
<div>#高速排水沟盖板缺失检测算法</div>
<div>#高速排水沟盖板缺失检测算法</div>
<div v-for="algoName in item.algoNames">{{ `#${algoName}` }}</div>
</div>
</div>
</div>
@ -62,23 +59,26 @@
</div>
</div>
<div class="item-footer">
<a-button class="item-del">删除</a-button>
<a-button class="item-show" type="primary" @click="showInfoDrawer = true">查看</a-button>
<a-button class="item-del" @click="delData(item)"></a-button>
<a-button class="item-show" type="primary" @click="showInfo(item)"></a-button>
</div>
</div>
</div>
<div class="pagination-div">
<a-pagination
size="small"
:total="50"
:total="total"
v-model:current="page"
v-model:page-size="limit"
show-size-changer
show-quick-jumper
:show-total="total => `共 ${total} 条数据`"
@change="changePagination"
/>
</div>
</div>
<a-modal width="853px" v-model:open="addInstanceModal" :footer="false" :closable="false" :destroyOnClose="true" :maskClosable="false" :keyboard="false">
<AddInstanceModal @changeAddModal="changeAddModal"/>
<AddInstanceModal :modalType="modalType" :showInfoData="showInfoData" :algorithmOptions="algorithmOptions" @changeAddModal="changeAddModal" @query="query"/>
</a-modal>
<a-drawer
width="442px"
@ -87,35 +87,105 @@
placement="right"
rootClassName="instance-show-info-drawer"
>
<ShowInfoDrawer />
<ShowInfoDrawer :showInfoData="showInfoData" @changeDrawerModal="changeDrawerModal" @changeAddModal="changeAddModal"/>
</a-drawer>
</div>
</a-spin>
</template>
<script setup lang="ts">
import { ref, h, } from "vue"
import { PlusOutlined, } from '@ant-design/icons-vue';
import { ref, h, onMounted, createVNode } from "vue"
import { ExclamationCircleOutlined, PlusOutlined, } from '@ant-design/icons-vue';
import AddInstanceModal from "./AddInstanceModal.vue";
import ShowInfoDrawer from "./ShowInfoDrawer.vue";
import { getAppEnvConfig } from '@/utils/env';
import { GetAlgoInstanceList, DeleteAlgoInstance } from '@/api/demo/aiinstance'
import { GetAlgorithmsRepositoryList } from '@/api/demo/ailist'
import { DataListType } from './utils'
import { message, Modal } from "ant-design-vue";
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const instanceName = ref('')
const searchValue = ref('')
const instanceAlgorithm = ref()
const addInstanceModal = ref(false)
const showInfoDrawer = ref(false)
const algorithmOptions = ref([
{ label: 'aaa', value: 'aaa' },
])
const changeAddModal = (type: boolean) => {
const page = ref('1')
const limit = ref('10')
const total = ref('0')
const dataList = ref<DataListType[]>([])
const loading = ref(false)
const algorithmOptions = ref([])
const showInfoData = ref({})
const modalType = ref('')
onMounted(() => {
GetAlgorithmsRepositoryList({page:1,limit: 999}).then(res => {
algorithmOptions.value = res.items.map(item => {
return {
label: item.name,
value: item.id,
modelLabels: item.modelLabels
}
})
})
query()
})
const query = () => {
loading.value = true
let params = {
page: page.value,
limit: limit.value,
key: searchValue.value,
}
GetAlgoInstanceList(params).then(res => {
console.log('res',res)
total.value = res.total
dataList.value = res.items
}).finally(() => {
loading.value = false
})
}
const changePagination = () => {
query()
}
const changeAddModal = (type: boolean, isInsert:boolean = true) => {
console.log('type',type,'isInsert',isInsert)
if(type){
if(isInsert){
modalType.value = 'insert'
}else{
modalType.value = 'update'
}
}
addInstanceModal.value = type
}
const showInfo = (item) => {
showInfoData.value = item
changeDrawerModal(true)
}
const changeDrawerModal = (type: boolean) => {
showInfoDrawer.value = type
}
const delData = (item) => {
Modal.confirm({
title: `确认删除 ${item.name} 吗?`,
icon: createVNode(ExclamationCircleOutlined),
okText: '确认',
cancelText: '取消',
onOk() {
return DeleteAlgoInstance({id: item.id}).then(res => {
message.success("删除成功")
query()
})
},
});
}
</script>
<style lang="scss" scoped>
.page-out{
padding: 16px 30px 53px 17px;
width: 100%;
height: 100%;
height: calc(100vh - 80px);
.title{
height: 58px;
background: #fff;
@ -189,10 +259,17 @@ const changeAddModal = (type: boolean) => {
padding: 26px 26px 0px 26px;
.show-list{
min-height: calc(100% - 69px);
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 55px;
padding-bottom: 55px;
overflow: auto;
scrollbar-width: none;
::-webkit-scrollbar{
width: 0;
height: 0;
}
.item-list{
width: 300px;
height: 291px;

@ -0,0 +1,2 @@
export type DataListType = {
}

@ -72,12 +72,22 @@
<CloseOutlined @click="closePathImageInfo" style="font-size: 20px; color: white" />
</div>
<!-- 上一张下一张图片 -->
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
<a-tooltip placement="top">
<template #title>
<span>可以使用键盘左右键切换</span>
</template>
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
</a-tooltip>
<a-tooltip placement="top">
<template #title>
<span>可以使用键盘左右键切换</span>
</template>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
</a-tooltip>
<!-- 涂鸦颜色选择-提示 -->
<div @click="setGraffiti" :class="graffitiFlag ? 'graffitiButton_choose' : 'graffitiButton'">
@ -1247,6 +1257,12 @@
if (event.code === 'Escape' && graffitiFlag.value) {
setGraffiti();
}
if (event.code == 'ArrowLeft') {
clickLeftOrRightButton('left');
}
if (event.code == 'ArrowRight') {
clickLeftOrRightButton('right');
}
};
//

@ -372,13 +372,22 @@
</div>
<!-- 上一张下一张图片 -->
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
<a-tooltip placement="top">
<template #title>
<span>可以使用键盘左右键切换</span>
</template>
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
</a-tooltip>
<a-tooltip placement="top">
<template #title>
<span>可以使用键盘左右键切换</span>
</template>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
</a-tooltip>
<!-- 涂鸦颜色选择-提示 -->
<div @click="setGraffiti" :class="graffitiFlag ? 'graffitiButton_choose' : 'graffitiButton'">
<a-popover placement="left">
@ -1051,6 +1060,12 @@
if (event.code === 'Escape' && graffitiFlag.value) {
setGraffiti();
}
if (event.code == 'ArrowLeft') {
clickLeftOrRightButton('left');
}
if (event.code == 'ArrowRight') {
clickLeftOrRightButton('right');
}
};
//

@ -13,13 +13,7 @@
<div class="title">
<div>
飞行控制
<!-- {"0":"空闲中","1":"现场调试","2":"远程调试","3":"固件升级中","4":"作业中","5":"待标定"} -->
<i v-if="airportVal.mode_code == 0"> </i>
<i v-else-if="airportVal.mode_code == 1">现场调试 </i>
<i v-else-if="airportVal.mode_code == 2">远程调试 </i>
<i v-else-if="airportVal.mode_code == 3">固件升级中 </i>
<i v-else-if="airportVal.mode_code == 4">作业中 </i>
<i v-else-if="airportVal.mode_code == 5">待标定 </i>
<i>{{ getCodeName(airportVal.mode_code) }} </i>
</div>
<div @click="emits('changeFlightControl')">
<CloseOutlined />
@ -231,6 +225,32 @@
querys.connectTime = timestampToFormattedDate(new Date().getTime());
addOrUpdateRedisUser(querys);
};
const getCodeName = (code: number) => {
const obj = {
'0': '待机',
'1': '起飞准备',
'2': '起飞准备完毕',
'3': '手动飞行',
'4': '自动起飞',
'5': '航线飞行',
'6': '全景拍照',
'7': '智能跟随',
'8': 'ADS-B 躲避',
'9': '自动返航',
'10': '自动降落',
'11': '强制降落',
'12': '三桨叶降落',
'13': '升级中',
'14': '未连接',
'15': 'APAS',
'16': '虚拟摇杆状态',
'17': '指令飞行',
'18': '空中 RTK 收敛模式',
'19': '机场选址中',
'20': 'POI环绕',
};
return obj[code];
};
const isLocked = ref(false);
// redis
const lockedClient = () => {
@ -536,8 +556,10 @@
// //
getClient().on('message', (topic, message) => {
const rs = JSON.parse(message);
if (rs.data.mode_code || rs.data.mode_code == 0) {
airportVal.value.mode_code = rs.data.mode_code;
if (topic == 'thing/product/' + uav.sn + '/osd') {
if (rs.data.mode_code || rs.data.mode_code == 0) {
airportVal.value.mode_code = rs.data.mode_code;
}
}
if (rs.data.sub_device) {
uavStatus.value = rs.data.sub_device.device_online_status;

@ -6,10 +6,10 @@
<div class="content">
<div class="content-edit instantiate" @click="instantiateVisible = !instantiateVisible">
<div class="input-result">
<span :style="{ backgroundColor: instantiateItem.bgColor }">
{{ instantiateItem.type }}</span
<span :style="{ backgroundColor: instantiateItem.displayColor }">
{{ instantiateItem.displayScheme }}</span
>
{{ instantiateItem.label }}
{{ instantiateItem.name }}
</div>
<UpOutlined v-if="instantiateVisible" />
<DownOutlined v-else />
@ -19,8 +19,8 @@
:key="index"
@click="instantiateSelect(item)"
>
<span :style="{ backgroundColor: item.bgColor }">{{ item.type }}</span>
{{ item.label }}
<span :style="{ backgroundColor: item.displayColor }">{{ item.displayScheme }}</span>
{{ item.name }}
</div>
</div>
</div>
@ -99,6 +99,7 @@
PlusOutlined,
} from '@ant-design/icons-vue';
import { Map } from '../index';
import { GetAlgoInstanceList } from '@/api/demo/aiinstance';
const { createMessage } = useMessage();
const emits = defineEmits(['changePatrol']);
@ -108,7 +109,7 @@
code: '',
desc: '',
action: '1',
time: null,
time: 'day',
area: null,
});
const bgOptions = ref(['#6909B2', '#09B284', '#B2AA09', '#E240BD', '#E24040']);
@ -135,7 +136,7 @@
]);
const instantiateItem = ref({});
const instantiateSelect = (val) => {
data.instantiate = val.value;
data.instantiate = val.id;
instantiateItem.value = val;
};
const drawarea = ref(false);
@ -150,7 +151,18 @@
}
drawarea.value = false;
};
onMounted(() => {});
const getList = () => {
GetAlgoInstanceList({
page: 1,
limit: 999,
}).then((res) => {
console.log(res);
instantiateOptions.value = res.items;
});
};
onMounted(() => {
getList();
});
</script>
<style lang="less" scoped>
.shade-container {
@ -234,9 +246,12 @@
color: #fff;
border: none;
}
::v-deep .ant-select {
width: 100%;
}
::v-deep .ant-select-selector {
background: none;
width: 220px;
width: 100%;
border: none;
text-align: center;
color: #fff;

@ -107,7 +107,7 @@
border-radius: 10px;
}
.map-container {
height: 380px;
height: calc(100vh - 600px);
}
.map-container-content {

@ -64,7 +64,7 @@
width: 500px;
margin-left: 20px;
.original-video {
height: 380px;
height: 40vh;
background: linear-gradient(180deg, rgba(13, 25, 45, 0.87) 0%, #182f4e 100%);
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
@ -72,7 +72,7 @@
}
.testing-video {
margin-top: 10px;
height: 380px;
height: 40vh;
background: linear-gradient(180deg, rgba(13, 25, 45, 0.87) 0%, #182f4e 100%);
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
@ -84,10 +84,10 @@
margin: 0 20px;
border-bottom: 1px solid #4e5778;
}
.player{
video{
margin-left: 10px;
}
.player {
video {
margin-left: 10px;
}
}
}
</style>

@ -25,7 +25,11 @@
</div>
<!-- 航面航线 -->
<div v-if="props.airLineForm.airLineType == 'mapping2d'" v-show="props.editMode != 'detail'" class="airpolygon-container">
<div
v-if="props.airLineForm.airLineType == 'mapping2d'"
v-show="props.editMode != 'detail'"
class="airpolygon-container"
>
<airPolygon
:airInfo="airInfo"
:editModel="props.editMode"
@ -55,21 +59,21 @@
<!-- 航线预览信息 -->
<div class="airline-preview-container" v-if="props.editMode == 'detail'">
<div class="info-item">
<div class="info-value">46461</div>
<div class="info-label">区域面积</div>
</div>
<div class="info-item">
<div class="info-value">46461</div>
<div class="info-label">航线长度</div>
<div class="info-item">
<div class="info-value">46461</div>
<div class="info-label">区域面积</div>
</div>
<div class="info-item">
<div class="info-value">46461</div>
<div class="info-label">航线长度</div>
</div>
<div class="info-item">
<div class="info-value">34分钟</div>
<div class="info-label">预计时间</div>
<div class="info-value">34分钟</div>
<div class="info-label">预计时间</div>
</div>
<div class="info-item">
<div class="info-value">78</div>
<div class="info-label">照片数量</div>
<div class="info-value">78</div>
<div class="info-label">照片数量</div>
</div>
<div class="info-item">
<div>
@ -82,7 +86,12 @@
</div>
<!-- 面绘制 -->
<div class="draw-polygon-patrol" title="绘制面区域" v-if="props.drawArea" @click="handlerDrawPolygonPatrol">
<div
class="draw-polygon-patrol"
title="绘制面区域"
v-if="props.drawArea"
@click="handlerDrawPolygonPatrol"
>
<EditOutlined />
</div>
</div>
@ -281,18 +290,19 @@
watch(
() => props.editMode,
(newVal,oldVal)=>{
if(newVal == 'edit' || newVal == 'detail'){
if(props.airLineForm.airLineType == 'waypoint'){
handlerEditWaypointAirLine();
}else if(props.airLineForm.airLineType == 'mapping2d'){
handlerEditPolygonAirLine();
(newVal, oldVal) => {
if (newVal == 'edit' || newVal == 'detail') {
if (props.airLineForm.airLineType == 'waypoint') {
handlerEditWaypointAirLine();
} else if (props.airLineForm.airLineType == 'mapping2d') {
handlerEditPolygonAirLine();
}
}
}
)
watch(
() => props.polygonArea,
(newVal, oldVal) => {
@ -1594,43 +1604,40 @@
textLabelGraphicLayer.clear();
}
//
const handlerLoadtextLabelGraphicLayer = (info) => {
if (textLabelGraphicLayer == null) {
textLabelGraphicLayer = new mars3d.layer.GraphicLayer({
isAutoEditing: false,
});
map.addLayer(textLabelGraphicLayer);
} else {
textLabelGraphicLayer.clear();
}
//
const handlerLoadtextLabelGraphicLayer = (info) => {
if(textLabelGraphicLayer == null){
textLabelGraphicLayer = new mars3d.layer.GraphicLayer({
isAutoEditing:false
})
map.addLayer(textLabelGraphicLayer);
}else{
textLabelGraphicLayer.clear();
}
info?.forEach((item,index)=>{
const graphic = new mars3d.graphic.LabelEntity({
position: [item.center[0], item.center[1], 0],
style: {
text: item.length+"m",
font_size: 15,
scale: 1,
font_family: "微软雅黑",
color: "#ffffff",
outline: true,
outlineColor: "#000000",
outlineWidth: 2,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
visibleDepth: false,
clampToGround:true,
},
attr: { remark: "示例1" }
})
textLabelGraphicLayer.addGraphic(graphic)
})
}
}
info?.forEach((item, index) => {
const graphic = new mars3d.graphic.LabelEntity({
position: [item.center[0], item.center[1], 0],
style: {
text: item.length + 'm',
font_size: 15,
scale: 1,
font_family: '微软雅黑',
color: '#ffffff',
outline: true,
outlineColor: '#000000',
outlineWidth: 2,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
visibleDepth: false,
clampToGround: true,
},
attr: { remark: '示例1' },
});
textLabelGraphicLayer.addGraphic(graphic);
});
};
};
// 线
// 线
@ -2968,9 +2975,9 @@
const rectSensor = graphicLayer.getGraphicById('uav-route-rectSensor');
rectSensor.heading = type_subtype_gimbalindex.gimbal_yaw; //0360
rectSensor.pitch = type_subtype_gimbalindex.gimbal_pitch; //0360
rectSensor.roll = type_subtype_gimbalindex.gimbal_roll + 90; //0360
rectSensor.angle1 = 10; //1 0.1-89.9
rectSensor.angle2 = 10; //2 0.1-89.9
rectSensor.roll = type_subtype_gimbalindex.gimbal_roll; //0360 + 90
rectSensor.angle1 = type_subtype_gimbalindex.zoom_factor + 10; //1 0.1-89.9
rectSensor.angle2 = type_subtype_gimbalindex.zoom_factor + 10; //2 0.1-89.9
}
}
};
@ -3033,7 +3040,7 @@
style: {
angle1: 10,
angle2: 10,
length: 10,
length: 100,
rayEllipsoid: true,
color: 'rgba(0,255,255,0.3)',
outline: true,
@ -3139,27 +3146,27 @@
z-index: 2;
}
.airline-preview-container{
width:500px;
background:rgba(0,0,0,0.6);
.airline-preview-container {
width: 500px;
background: rgba(0, 0, 0, 0.6);
position: absolute;
bottom:100px;
left:50%;
transform: translate(0%,0);
z-index:999;
bottom: 100px;
left: 50%;
transform: translate(0%, 0);
z-index: 999;
border-radius: 10px;
display: flex;
padding:20px;
font-size:18px;
color:#fff;
padding: 20px;
font-size: 18px;
color: #fff;
}
.airline-preview-container .info-item{
flex:1;
.airline-preview-container .info-item {
flex: 1;
text-align: center;
}
.airline-preview-container .info-item .info-label{
font-size:14px;
margin-top:12px;
.airline-preview-container .info-item .info-label {
font-size: 14px;
margin-top: 12px;
}
</style>

Loading…
Cancel
Save