冲突解决

main
徐景良 3 weeks ago
commit 9728998757

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1,23 @@
import { defHttp } from '@/utils/http/axios';
enum Api {
// 获取算法列表
GetAlgorithmsRepositoryList = '/api/AlgorithmsRepository/GetAlgorithmsRepositoryList',
// 添加算法
AddAlgorithmsRepository = '/api/AlgorithmsRepository/AddAlgorithmsRepository',
// 修改算法
UpdateAlgorithmsRepository = '/api/AlgorithmsRepository/UpdateAlgorithmsRepository',
}
export const GetAlgorithmsRepositoryList = (params) => defHttp.get({
url: Api.GetAlgorithmsRepositoryList,
params,
});
export const AddAlgorithmsRepository = (params) => defHttp.post({
url: Api.AddAlgorithmsRepository,
params,
});
export const UpdateAlgorithmsRepository = (params) => defHttp.post({
url: Api.UpdateAlgorithmsRepository,
params,
});

@ -0,0 +1,905 @@
<template>
<div class="title-div">
<div class="title-span-div">
<div class="title-icon"></div>
<div class="title-span">创建算法实例</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>
</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>
</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>
<a-select
class="item-select"
v-model:value="selectEvents"
:options="selectEventsOptions"
/>
</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>
</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>
</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>
</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-value">
<a-input class="setting-input" />
<div class="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>
</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>
<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>
</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>
</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>
<div class="step2-input-interval">-</div>
<div class="step2-input-div">
<a-input class="step2-input"/>
<div class="step2-input-icon">%</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>
<a-modal v-model:open="selectImageModal" title="选择封面" width="1000px" :footer="null" :destroyOnClose="true">
<SelectImageModal @changeSelectImageModal="changeSelectImageModal"/>
</a-modal>
</template>
<script setup lang="ts">
import { ref, h, defineEmits, defineProps } from "vue"
import SelectImageModal from "@/views/demo/resourcemanagement/ailist/SelectImageModal.vue";
const emits = defineEmits(['changeAddModal'])
const instanceSteps = ref(0)
const instanceName = ref('')
const instanceDescription = ref('')
const selectEvents = ref('')
const selectAlgorithm = ref()
const spaceConstraints = ref(false)
const timeConstraints = ref(false)
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: '配置算法', },
{ title: '航飞要求', },
]);
const colorOptions = ref([
{ value: '#1951E0' },
{ value: '#EB8588' },
{ value: '#E9A7FA' },
{ value: '#20B0DC' },
{ value: '#ADE1FB' },
{ value: '#98E5B4' },
{ value: '#3ECE40' },
{ value: '#DFE546' },
{ value: '#FEA352' },
{ value: '#E82FB7' },
{ value: '#E96755' },
{ value: '#085758' },
{ value: '#0099A0' },
{ value: '#BEEDD8' },
{ value: '#FEEF30' },
{ value: '#82D4E5' },
{ value: '#8B87BF' },
{ value: '#D9ADBB' },
{ value: '#EAB869' },
{ value: '#CED58E' },
{ value: '#8DADB6' },
])
const next = () => {
instanceSteps.value ++
}
const last = () => {
instanceSteps.value --
}
const submit = () => {
}
const changeSelectImageModal = (type: boolean) => {
selectImageModal.value = type
}
</script>
<style lang="scss" scoped>
.title-div{
width: 100%;
height: 64px;
padding-top: 15px;
padding-left: 26px;
padding-right: 35px;
padding-bottom: 14px;
display: flex;
justify-content: space-between;
align-items: center;
.title-span-div{
display: flex;
align-items: center;
.title-icon{
width: 3px;
height: 18px;
background: #0B60BD;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
margin-right: 9px;
}
.title-span{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 20px;
color: #11131B;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
}
.close-button{
width: 18px;
height: 18px;
background-image: url('/public/ailist/add_algorithm_close_button.png');
cursor: pointer;
}
}
.interval{
width: 100%;
height: 1px;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border: 1px solid #DBDBDB;
opacity: 0.66;
}
.modal-content{
padding-top: 33px;
// padding-left: 50px;
// padding-right: 50px;
.item-title{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-bottom: 9px;
.tag-list-button{
display: flex;
.select-all-button{
width: 66px;
height: 26px;
background: #0D68CB;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border: 1px solid #0D68CB;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #FFFFFF;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
display: flex;
align-items: center;
justify-content: center;
border-radius: 13px;
user-select: none;
cursor: pointer;
margin-right: 14px;
}
.clear-button{
width: 66px;
height: 26px;
background: #FFFFFF;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border: 1px solid #0D68CB;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #0D68CB;
// line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
display: flex;
align-items: center;
justify-content: center;
border-radius: 13px;
user-select: none;
cursor: pointer;
}
}
}
.item-input{
height: 40px;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
border: 1px solid #CCCCCC;
}
.item-select{
width: 100%;
:deep(.ant-select-selector){
width: 100%;
height: 40px;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
border: 1px solid #CCCCCC;
}
:deep(.ant-select-selection-item){
line-height: 38px;
}
}
.item-multiple-select{
:deep(.ant-select-selection-item){
line-height: 24px;
}
}
.content-steps-div{
padding: 0px 28px;
margin-bottom: 31px;
.content-steps{
:deep(.ant-steps-item-icon){
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 13px;
}
:deep(.ant-steps-icon){
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 17px;
color: #FFFFFF;
line-height: 24px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
:deep(.ant-steps-item-title){
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 13px;
color: #191919;
line-height: 18px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
:deep(.ant-steps-item-active){
.ant-steps-item-title{
color: #0D68CB;
}
}
}
}
.step0-info-div{
padding-left: 28px;
display: flex;
.left-content{
width: 366px;
margin-right: 39px;
.instance-image-item-div{
display: flex;
width: 100%;
height: 155px;
margin-bottom: 12px;
.default-image-div{
position: relative;
width: 173px;
height: 155px;
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border: 2px dashed rgba(28,29,34,0.08);
margin-right: 19px;
display: flex;
align-items: center;
justify-content: center;
.default-image{
width: 116px;
height: 116px;
background-image: url('/public/instance/instance_default_image.png');
}
.default-title-div{
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
height: 40px;
background: rgb(0, 0, 0, 0.75);
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border-radius: 0px 0px 2px 2px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
display: flex;
align-items: center;
justify-content: center;
}
}
.select-image-div{
position: relative;
width: 173px;
height: 155px;
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;
cursor: pointer;
.select-image-icon{
width: 36px;
height: 36px;
background-image: url('/public/instance/instance_upload_image.png');
// margin-bottom: 9px;
margin: auto;
}
.select-image-span{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #000000;
line-height: 17px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
text-align: center;
}
.select-title-div{
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
height: 40px;
background: rgb(0, 0, 0, 0.75);
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border-radius: 0px 0px 2px 2px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
.right-content{
width: 366px;
.select-item-color-div{
height: 79px;
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
padding: 15px 25px 14px 20px;
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 18px;
.color-item{
width: 20px;
height: 20px;
background: #1951E0;
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%;
}
}
}
}
.step1-info-div{
padding-left: 90px;
padding-right: 88px;
.tag-show-list-div{
height: 139px;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
border: 1px solid #CCCCCC;
margin-bottom: 6px;
.list-title{
height: 41px;
background: #F5F5F5;
// box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
// border-radius: 2px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
padding-left: 41px;
padding-right: 67px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgb(219, 219, 219);
}
.tag-list-content{
height: calc(100% - 41px);
overflow: auto;
scrollbar-width: none;
::-webkit-scrollbar{
width: 0;
height: 0;
}
.list-item{
height: 40px;
display: flex;
align-items: center;
border-bottom: 1px solid rgb(219, 219, 219);
padding-left: 12px;
.item-checkbox{
margin-right: 12px;
}
.item-span{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: rgba(0,0,0,0.9);
line-height: 20px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.item-color{
width: 26px;
height: 26px;
background: #1951E0;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 5px;
margin-right: 67px;
}
.item-believe{
width: 106px;
height: 27px;
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), inset 0px 1px 3px 0px rgba(0,0,0,0.17);
display: flex;
align-items: center;
justify-content: center;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 12px;
color: #1C1D22;
line-height: 17px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
}
}
}
.identify-setting{
height: 50px;
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
padding-left: 40px;
display: flex;
align-items: center;
margin-bottom: 6px;
.identify-setting-item{
display: flex;
.item-label{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-right: 13px;
}
.item-value{
width: 123px;
height: 34px;
background: #FFFFFF;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border-radius: 2px 0px 0px 2px;
display: flex;
.identify-setting-input{
width: calc(100% - 24px);
height: 100%;
border: 0PX;
box-shadow: none;
font-family: Bebas;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
.identify-setting-input-icon{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
}
}
}
.space-constraints-setting{
height: 83px;
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
padding-left: 12px;
padding-right: 12px;
// padding-top: 14px;
display: flex;
align-items: center;
margin-bottom: 6px;
.setting-hint{
flex: 1;
height: 56px;
background: #EEF5FC;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(0,0,0,0.06);
border-radius: 2px;
padding: 8px 10px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #0D68CB;
line-height: 20px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
.setting-content{
width: 250px;
height: 34px;
padding-left: 50px;
.setting-item{
display: flex;
.item-label{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-right: 13px;
}
.item-value{
width: 123px;
height: 34px;
background: #FFFFFF;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border-radius: 2px 0px 0px 2px;
display: flex;
.setting-input{
width: calc(100% - 24px);
height: 100%;
border: 0PX;
box-shadow: none;
font-family: Bebas;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
.setting-input-icon{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
}
}
}
}
.time-constraints-setting{
height: 83px;
background: #F5F5F5;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
display: flex;
align-items: center;
padding-left: 13px;
padding-right: 38px;
margin-bottom: 25px;
.setting-hint{
width: 241px;
height: 56px;
background: #EEF5FC;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(0,0,0,0.06);
border-radius: 2px;
padding-top: 8px;
padding-left: 13px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #0D68CB;
line-height: 20px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-right: 39px;
}
.setting-content{
height: 34px;
.time-constraints-setting-item{
display: flex;
.time-constraints-item-label{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-right: 13px;
}
.time-constraints-item-value{
width: 123px;
height: 34px;
background: #FFFFFF;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border-radius: 2px 0px 0px 2px;
display: flex;
}
}
}
}
}
.step2-info-div{
padding: 0px 89px;
margin-bottom: 58px;
.step2-title{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #222738;
line-height: 33px;
// text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
text-align: left;
margin-bottom: 10px;
}
.step2-setting-div{
height: 83px;
background: #F5F5F5;
padding-left: 13px;
// box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 2px;
display: flex;
align-items: center;
margin-bottom: 6px;
.step2-hint{
width: 325px;
height: 56px;
background: #EEF5FC;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(0,0,0,0.06);
border-radius: 2px;
padding-left: 13px;
padding-right: 6px;
padding-top: 8px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #0D68CB;
line-height: 20px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-right: 36px;
}
.step2-setting-content{
height: 34px;
display: flex;
.step2-input-div{
display: flex;
width: 123px;
height: 34px;
background: #FFFFFF;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15), 0px 8px 6px 0px rgba(28,29,34,0.06);
border-radius: 2px 0px 0px 2px;
.step2-input{
border: 0px;
box-shadow: none;
height: 100%;
width: calc(100% - 33px);
font-family: Bebas;
font-size: 14px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
.step2-input-icon{
width: 23px;
text-align: right;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
}
}
.step2-first-input{
margin-right: 11px;
}
.step2-input-interval{
margin-right: 11px;
display: flex;
align-items: center;
}
}
}
}
}
.footer-div{
height: 86px;
display: flex;
align-items: center;
justify-content: end;
padding-right: 27px;
.cancel-button{
width: 101px;
height: 40px;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 4px;
border: 1px solid #979797;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #222738;
line-height: 33px;
text-shadow: 0px 10px 30px rgba(0,0,6,0.15);
margin-right: 14px;
}
.next-button{
width: 146px;
height: 40px;
background: #0D68CB;
box-shadow: 0px 10px 30px 0px rgba(0,0,6,0.15);
border-radius: 4px;
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);
}
}
</style>

@ -0,0 +1,212 @@
<template>
<div class="drawer-title">
<div class="title-content">
<div class="title-icon"></div>
<div class="title-span">算法实例详情</div>
</div>
<div class="title-tools-div">
<EditOutlined style="font-size: 19px;margin-right: 19px;"/>
<div class="button-interval"></div>
<CloseOutlined style="font-size: 19px;"/>
</div>
</div>
<div class="drawer-content">
<img class="content-image" src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"/>
<div class="content-item-name">
<div class="item-name-label">实例名称</div>
<div class="item-name-value">公路-隔离栏破损识别</div>
</div>
<div class="content-item-description">
<div class="item-description-label">描述</div>
<div class="item-description-value">暂无描述</div>
</div>
<div class="content-interval"></div>
<div class="content-item">
<div class="item-label">显示内容</div>
<div class="item-value">破损隔离栏</div>
</div>
<div class="content-item">
<div class="item-label">关联事件</div>
<div class="item-value">-</div>
</div>
<div class="content-item">
<div class="item-label">AI算法</div>
<div class="item-value">高速隔离栏破损检测算法</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>
<div class="content-item">
<div class="item-label">空间约束</div>
<div class="item-value">关闭</div>
</div>
<div class="content-item">
<div class="item-label">时间约束</div>
<div class="item-value">关闭</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>
</div>
</div>
</template>
<script setup lang="ts">
import {} from "vue"
import { EditOutlined, CloseOutlined } from '@ant-design/icons-vue'
</script>
<style lang="scss" scoped>
.drawer-title{
width: 100%;
height: 63px;
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 29px;
padding-right: 30px;
border-bottom: 1px solid #E7E7E7;
.title-content{
display: flex;
height: 34px;
align-items: center;
.title-icon{
width: 3px;
height: 18px;
background: #0B60BD;
margin-right: 9px;
}
.title-span{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 20px;
color: #11131B;
line-height: 33px;
}
}
.title-tools-div{
display: flex;
height: 19px;
align-items: center;
.button-interval{
width: 1px;
height: 18px;
background: #E7E7E7;
margin-right: 16px;
}
}
}
.drawer-content{
padding: 30px;
.content-image{
width: 300px;
height: 200px;
margin-bottom: 32px;
}
.content-item-name{
display: flex;
margin-bottom: 18px;
.item-name-label{
width: 68px;
height: 26px;
background: #0B60BD;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
line-height: 20px;
margin-right: 21px;
display: flex;
align-items: center;
padding-left: 7px;
}
.item-name-value{
width: 294px;
height: 27px;
background: rgba(216, 216, 216, 0.34);
padding-left: 28px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 14px;
color: #1C1D22;
line-height: 20px;
display: flex;
align-items: center;
}
}
.content-item-description{
display: flex;
// margin-bottom: 18px;
.item-description-label{
width: 68px;
height: 26px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #8D8E90;
line-height: 20px;
margin-right: 21px;
display: flex;
align-items: center;
padding-left: 7px;
}
.item-description-value{
width: 294px;
height: 27px;
// background: rgba(216, 216, 216, 0.34);
padding-left: 28px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 14px;
color: #1C1D22;
line-height: 20px;
display: flex;
align-items: center;
}
}
.content-item{
display: flex;
margin-bottom: 18px;
.item-label{
width: 68px;
height: 26px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #8D8E90;
background: #f0f0f0;
line-height: 20px;
margin-right: 21px;
display: flex;
align-items: center;
padding-left: 7px;
}
.item-value{
width: 294px;
height: 27px;
// background: rgba(216, 216, 216, 0.34);
padding-left: 28px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 14px;
color: #1C1D22;
line-height: 27px;
// display: flex;
// align-items: center;
}
}
.content-interval{
width: 100;
height: 1px;
background: #E7E7E7;
margin-bottom: 30px;
margin-top: 30px;
}
}
</style>

@ -0,0 +1,393 @@
<template>
<div class="page-out">
<div class="title">
<div class="search-div">
<a-input class="search-input" v-model:value="instanceName" placeholder="搜索实例名称…">
<template #prefix>
<div class="search-input-icon"></div>
</template>
</a-input>
<a-select
placeholder="请选择算法"
class="select-algorithm"
v-model:value="instanceAlgorithm"
:options="algorithmOptions"
/>
<a-button class="search-button" type="primary">查询</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="image-div">
<img class="image-item" src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">
<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 style="display: flex;justify-content: space-between;">
<div class="info-subtitle">暂无描述</div>
<a-popover overlayClassName="instance-show-more-info-popover" trigger="click" placement="topRight">
<template #content>
<div class="show-more-content">
<div class="content-raw">
<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>
</div>
</div>
</template>
<div class="show-more-info">
<div class="show-more-span">展开更多</div>
<div class="show-more-icon"></div>
</div>
</a-popover>
</div>
</div>
</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>
</div>
</div>
</div>
<div class="pagination-div">
<a-pagination
size="small"
:total="50"
show-size-changer
show-quick-jumper
:show-total="total => `共 ${total} 条数据`"
/>
</div>
</div>
<a-modal width="853px" v-model:open="addInstanceModal" :footer="false" :closable="false" :destroyOnClose="true" :maskClosable="false" :keyboard="false">
<AddInstanceModal @changeAddModal="changeAddModal"/>
</a-modal>
<a-drawer
width="442px"
:closable="false"
v-model:open="showInfoDrawer"
placement="right"
rootClassName="instance-show-info-drawer"
>
<ShowInfoDrawer />
</a-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, h, } from "vue"
import { PlusOutlined, } from '@ant-design/icons-vue';
import AddInstanceModal from "./AddInstanceModal.vue";
import ShowInfoDrawer from "./ShowInfoDrawer.vue";
const instanceName = ref('')
const instanceAlgorithm = ref()
const addInstanceModal = ref(false)
const showInfoDrawer = ref(false)
const algorithmOptions = ref([
{ label: 'aaa', value: 'aaa' },
])
const changeAddModal = (type: boolean) => {
addInstanceModal.value = type
}
</script>
<style lang="scss" scoped>
.page-out{
padding: 16px 30px 53px 17px;
width: 100%;
height: 100%;
.title{
height: 58px;
background: #fff;
padding-left: 28px;
padding-right: 36px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 17px;
.search-div{
display: flex;
align-items: center;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 16px;
line-height: 28px;
.search-input{
width: 232px;
height: 36px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #30384F;
margin-right: 13px;
.search-input-icon{
width: 18px;
height: 18px;
background-image: url('/public/ailist/search_input_icon.png');
}
}
.select-algorithm{
margin-right: 13px;
:deep(.ant-select-selector){
width: 191px;
height: 36px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #30384F;
}
:deep(.ant-select-selection-placeholder){
line-height: 34px;
}
}
.search-button{
width: 80px;
height: 36px;
background: #0B60BD;
border-radius: 4px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
line-height: 22px;
}
}
.add-instance{
width: 148px;
height: 36px;
background: #0B60BD;
border-radius: 4px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
line-height: 22px;
}
}
.show-list-div{
width: 100%;
height: calc(100% - 60px);
background: #fff;
padding: 26px 26px 0px 26px;
.show-list{
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 55px;
.item-list{
width: 300px;
height: 291px;
background: #FFFFFF;
box-shadow: 5px 18px 32px 0px rgba(28,29,34,0.1);
border-radius: 10px;
.image-div{
position: relative;
width: 300px;
height: 200px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
margin-bottom: 10px;
.image-item{
width: 300px;
height: 200px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.image-icon{
position: absolute;
top: 0px;
left: 0px;
width: 46px;
height: 16px;
background: linear-gradient( 320deg, #6C90F5 0%, #3A57E8 100%);
border-radius: 8px 0px 8px 0px;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 9px;
color: #FFFFFF;
line-height: 12px;
}
}
.show-info-div{
padding-left: 9px;
padding-right: 12px;
.info-title-div{
height: 40px;
display: flex;
align-items: center;
margin-bottom: 4px;
.info-icon{
width: 3px;
height: 34px;
background: #0B60BD;
margin-right: 5px;
}
.info-title-inner{
width: 100%;
.info-title{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 14px;
color: #1C1D22;
line-height: 20px;
margin-bottom: 5px;
}
.info-subtitle{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 11px;
color: rgba(28,29,34,0.5);
line-height: 15px;
}
.show-more-info{
display: flex;
align-items: center;
user-select: none;
cursor: pointer;
.show-more-span{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 11px;
color: #1C1D22;
line-height: 15px;
margin-right: 6px;
}
.show-more-icon{
width: 12px;
height: 8px;
background-image: url('/public/instance/instance_show_more_icon.png');
}
}
}
}
.info-content-div{
width: 100%;
height: 66px;
background-image: url('/public/instance/instance_list_item_info_content.png');
padding-top: 8px;
padding-left: 12px;
margin-bottom: 13px;
.content-raw{
display: flex;
.content-label{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #1C1D22;
line-height: 17px;
margin-right: 11px;
}
.content-value{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 12px;
color: #1C1D22;
line-height: 17px;
}
}
}
}
.item-footer{
width: 100%;
height: 23px;
display: flex;
align-items: center;
justify-content: end;
padding-right: 12px;
.item-del{
width: 54px;
height: 23px;
border-radius: 2px;
border: 1px solid #F2F2F2;
margin-right: 7px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 11px;
color: #E3150E;
line-height: 15px;
}
.item-show{
width: 54px;
height: 23px;
background: #0B60BD;
border-radius: 2px;
border: 1px solid #0B60BD;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 11px;
color: #FFFFFF;
line-height: 15px;
}
}
}
}
.pagination-div{
padding-right: 32px;
// width: 100%;
height: 69px;
display: flex;
align-items: center;
justify-content: end;
border-top: 1px solid rgba(28,29,34,0.08);
margin-left: -26px;
margin-right: -26px;
padding-right: 45px;
}
}
}
</style>
<style lang="scss">
.instance-show-more-info-popover{
.ant-popover-inner{
background-color: rgba(5, 5, 5, 0.56);
}
.show-more-content{
width: 232px;
.content-raw{
display: flex;
.content-label{
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #FFFFFF;
line-height: 17px;
margin-right: 11px;
}
.content-value{
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 12px;
color: #FFFFFF;
line-height: 17px;
}
}
}
}
.instance-show-info-drawer{
.ant-drawer-body{
padding: 0px;
}
}
</style>

@ -1,127 +1,156 @@
<template>
<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="left-content">
<div class="item-title">模型名称</div>
<a-input class="name-input" v-model:value="modelName" placeholder="请输入相关名称" />
<div class="item-title">模型描述</div>
<a-textarea class="item-desc" v-model:value="modelDescription" placeholder="请输入相关描述" :rows="4" />
<div class="item-title">模型封面</div>
<div class="item-image">
<div class="empty-image" @click="changeSelectImageModal(true)">
<div class="empty-image-content">
<div class="empty-image-icon"></div>
<div class="empty-image-span">请选择文件上传</div>
<a-spin tip="加载中..." :spinning="loading">
<div class="modal-content">
<div class="left-content">
<div class="item-title">模型名称</div>
<a-input class="name-input" v-model:value="name" placeholder="请输入相关名称" />
<div class="item-title">模型描述</div>
<a-textarea class="item-desc" v-model:value="describe" placeholder="请输入相关描述" :rows="4" />
<div class="item-title">模型封面</div>
<div class="item-image">
<img v-if="pic" class="show-image" :src="`${VITE_GLOB_API_URL}/${pic}`" @click="changeSelectImageModal(true)"/>
<div v-else class="empty-image" @click="changeSelectImageModal(true)">
<div class="empty-image-content">
<div class="empty-image-icon"></div>
<div class="empty-image-span">请选择文件上传</div>
</div>
</div>
</div>
</div>
</div>
<div class="right-content">
<div class="item-title">模型类型</div>
<a-segmented class="item-type" v-model:value="modelType" :options="modelTypeOptions"></a-segmented>
<template v-if="modelType == '0'">
<div class="item-title">模型规模</div>
<a-segmented class="item-type" v-model:value="modelScale" :options="modelScaleOptions"></a-segmented>
<div class="item-title">模型文件</div>
<a-upload
v-model:file-list="modelFile"
accept=".zip"
:maxCount="1"
:custom-request="customRequest"
>
<a-button class="select-model-file-button" type="primary" :icon="h(PlusOutlined)">选择模型文件</a-button>
<!-- <template #itemRender="{ file, actions }">
<a-space>
<span>{{ file.name }}</span>
</a-space>
</template> -->
</a-upload>
<div class="upload-hint">*请上传模型压缩文件支持pt格式</div>
<div class="item-title">模型标签</div>
<div class="item-label-div">
<div class="label-title-div">
<div class="label-title-span">请根据模型实际情况填写信息</div>
<div class="add-button" @click="addLabelRow">
<div class="button-icon"></div>
<div class="button-span">添加行</div>
</div>
</div>
<div class="label-interval"></div>
<div class="label-table-title">
<div class="table-title-item" style="padding-left: 6px;">枚举值</div>
<div class="table-title-item">名称</div>
<div class="table-title-item">推荐置信度</div>
<div class="table-title-item" style="width: 55px;justify-content: center;">操作</div>
</div>
<div class="label-table-content">
<div class="table-row" v-for="(item,index) in modelLabelList" :key="index">
<div class="table-item">
<a-input class="table-item-input" v-model:value="item.test"/>
<div class="right-content">
<div class="item-title">模型类型</div>
<a-segmented class="item-type" v-model:value="type" :options="modelTypeOptions"></a-segmented>
<template v-if="type == 0">
<div class="item-title">模型规模</div>
<a-segmented class="item-type" v-model:value="size" :options="modelScaleOptions"></a-segmented>
<div class="item-title">模型文件</div>
<a-upload
:file-list="modelFile"
accept=".zip,.pt"
:max-count="1"
:custom-request="customRequest"
>
<a-button class="select-model-file-button" type="primary" :icon="h(PlusOutlined)">选择模型文件</a-button>
</a-upload>
<div class="upload-hint">*请上传模型压缩文件支持pt格式</div>
<div class="item-title">模型标签</div>
<div class="item-label-div">
<div class="label-title-div">
<div class="label-title-span">请根据模型实际情况填写信息</div>
<div class="add-button" @click="addLabelRow">
<div class="button-icon"></div>
<div class="button-span">添加行</div>
</div>
<div class="table-item">
<a-input class="table-item-input" />
</div>
<div class="table-item">
<a-input class="table-item-input" />
</div>
<div class="table-item" style="width: 55px; justify-content: center">
<div class="del-table-row" @click="delLabelRow(index)"></div>
</div>
<div class="label-interval"></div>
<div class="label-table-title">
<div class="table-title-item" style="padding-left: 6px;">枚举值</div>
<div class="table-title-item">名称</div>
<div class="table-title-item">推荐置信度</div>
<div class="table-title-item" style="width: 55px;justify-content: center;">操作</div>
</div>
<div class="label-table-content">
<div class="table-row" v-for="(item,index) in modelLabels" :key="index">
<div class="table-item">
<a-input class="table-item-input" v-model:value="item.enumValue"/>
</div>
<div class="table-item">
<a-input class="table-item-input" v-model:value="item.name"/>
</div>
<div class="table-item">
<a-input class="table-item-input" v-model:value="item.reliability"/>
</div>
<div class="table-item" style="width: 55px; justify-content: center">
<div class="del-table-row" @click="delLabelRow(index)"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<div class="item-title">推流地址</div>
<a-input class="name-input" v-model:value="modelPushAddress" placeholder="请输入地址" />
<div class="item-title">拉流地址</div>
<a-input class="name-input" v-model:value="modelPullAddress" placeholder="请输入地址" />
<div class="item-title">服务地址</div>
<a-input class="name-input" v-model:value="modelServeAddress" placeholder="请输入地址" />
<div class="item-title">密钥</div>
<a-textarea class="item-key" v-model:value="modelKey" placeholder="请输入密钥" :rows="4" />
</template>
</template>
<template v-else>
<div class="item-title">推流地址</div>
<a-input class="name-input" v-model:value="pushUrl" placeholder="请输入地址" />
<div class="item-title">拉流地址</div>
<a-input class="name-input" v-model:value="pullUrl" placeholder="请输入地址" />
<div class="item-title">服务地址</div>
<a-input class="name-input" v-model:value="serviceUrl" placeholder="请输入地址" />
<div class="item-title">密钥</div>
<a-textarea class="item-key" v-model:value="secretKey" placeholder="请输入密钥" :rows="4" />
</template>
</div>
</div>
</div>
<div class="interval"></div>
<div class="footer">
<a-button class="cancel-button" @click="emits('changeAddModal',false)"></a-button>
<a-button class="save-button" type="primary">确定</a-button>
</div>
<div class="interval"></div>
<div class="footer">
<a-button class="cancel-button" @click="emits('changeAddModal',false)"></a-button>
<a-button class="save-button" type="primary" @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 { uploadFile } from '@/api/formrender/index';
import { PlusOutlined, } from '@ant-design/icons-vue';
import SelectImageModal from "./SelectImageModal.vue";
const props = defineProps([''])
const emits = defineEmits(['changeAddModal'])
const modelName = ref('')
const modelDescription = ref('')
const modelType = ref('0')
const modelScale = ref('N')
const modelPullAddress = ref('')
const modelPushAddress = ref('')
const modelServeAddress = ref('')
const modelKey = ref('')
const modelFile = ref([])
const modelFilePath = ref()
const modelLabelList = ref([])
import { ModelLabelsType } from './utils'
import { getAppEnvConfig } from '@/utils/env';
import { AddAlgorithmsRepository, UpdateAlgorithmsRepository } from '@/api/demo/ailist'
import { message } from "ant-design-vue";
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const props = defineProps(['modalType', 'algorithmInfo'])
const emits = defineEmits(['changeAddModal', 'query'])
onMounted(() => {
if(props.modalType == 'update'){
id.value = props.algorithmInfo.id
name.value = props.algorithmInfo.name
describe.value = props.algorithmInfo.describe
pic.value = props.algorithmInfo.pic
type.value = props.algorithmInfo.type
size.value = props.algorithmInfo.size
pullUrl.value = props.algorithmInfo.pullUrl
pushUrl.value = props.algorithmInfo.pushUrl
serviceUrl.value = props.algorithmInfo.serviceUrl
secretKey.value = props.algorithmInfo.secretKey
path.value = props.algorithmInfo.path
modelLabels.value = props.algorithmInfo.modelLabels
modelFile.value?.push({
uid: '-1',
name: props.algorithmInfo.path,
status: 'done',
url: VITE_GLOB_API_URL + '/' + props.algorithmInfo.path,
});
}
})
const id = ref('')
const name = ref('')
const describe = ref('')
const pic = ref('')
const type = ref(0)
const size = ref('N')
const pullUrl = ref('')
const pushUrl = ref('')
const serviceUrl = ref('')
const secretKey = ref('')
const modelFile = ref<any>([])
const path = ref()
const modelLabels = ref<ModelLabelsType[]>([])
const selectImageModal = ref(false)
const loading = ref(false)
const modelTypeOptions = ref([
{ label: '本地模型', value: '0' },
{ label: '远程模型', value: '1' }
{ label: '本地模型', value: 0 },
{ label: '远程模型', value: 1 }
])
const modelScaleOptions = ref([
{ label: 'N', value: 'N' },
@ -131,25 +160,72 @@ const modelScaleOptions = ref([
{ label: 'XI', value: 'XI' },
])
const customRequest = (file) => {
modelFile.value = []
const formData = new FormData()
formData.append('files', file.file)
uploadFile(formData).then(res => {
modelFilePath.value = res[0].filePath
// emits('update:shppath',res[0].filePath)
modelFile.value?.push({
uid: '-1',
name: res[0].filePath,
status: 'done',
url: VITE_GLOB_API_URL + '/' + res[0].filePath,
});
path.value = res[0].filePath
})
}
const addLabelRow = () => {
// TODO
modelLabelList.value.push({
test:''
modelLabels.value.push({
id: '',
name: '',
enumValue: '',
reliability: '',
pId: ''
})
}
const delLabelRow = (index) => {
modelLabelList.value.splice(index,1)
modelLabels.value.splice(index,1)
}
const changeSelectImageModal = (type: boolean) => {
selectImageModal.value = type
}
const getResultImage = (url: string) => {
pic.value = url
}
const submit = () => {
loading.value = true
let params = {
id: id.value,
name: name.value,
describe: describe.value,
pic: pic.value,
size: size.value,
type: type.value,
path: path.value,
pushUrl: pushUrl.value,
pullUrl: pullUrl.value,
serviceUrl: serviceUrl.value,
secretKey: secretKey.value,
modelLabels: modelLabels.value
}
console.log('params',params)
if(props.modalType == 'insert'){
AddAlgorithmsRepository(params).then(res => {
message.success('算法创建成功')
emits('query')
emits('changeAddModal',false)
}).finally(() => {
loading.value = false
})
}else{
UpdateAlgorithmsRepository(params).then(res => {
message.success('算法修改成功')
emits('query')
emits('changeAddModal',false)
}).finally(() => {
loading.value = false
})
}
}
</script>
<style lang="scss" scoped>
@ -235,6 +311,11 @@ 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);
.show-image{
width: 249px;
height: 151px;
cursor: pointer;
}
.empty-image{
position: absolute;
top: 50%;

@ -45,7 +45,7 @@ import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import { dataURLtoBlob } from '@/utils/file/base64Conver';
import { uploadFile } from '@/api/formrender/index';
const emits = defineEmits(['changeSelectImageModal'])
const emits = defineEmits(['changeSelectImageModal', 'getResultImage'])
const sourceImg = ref(null);
const cropper = ref(null);
const flipH = ref(1);
@ -54,7 +54,6 @@ const zoomValue = ref(1);
const croppedUrl = ref('');
const onFileChange = (e) => {
console.log(e)
const file = e
if (!file) return;
const url = URL.createObjectURL(file);
@ -113,7 +112,6 @@ const getCropped = () => {
const canvas = cropper.value.getCroppedCanvas({ width: 300, height: 200 });
if (!canvas) return;
croppedUrl.value = canvas.toDataURL('image/png');
console.log('croppedUrl.value',croppedUrl.value)
};
const clear = () => {
@ -125,12 +123,6 @@ const clear = () => {
croppedUrl.value = '';
};
const submitImage = (format='image/jpeg', quality = 0.9) => {
// const blob = dataURLtoBlob(croppedUrl.value);
// const formData = new FormData();
// formData.append('files', blob);
// uploadFile(formData).then(res => {
// console.log(res)
// })
if (!cropper.value) return;
cropper.value.getCroppedCanvas({ width: 300, height: 200 })
.toBlob(async (blob) => {
@ -141,7 +133,9 @@ const submitImage = (format='image/jpeg', quality = 0.9) => {
formData.append('files', blob, `cropped.jpeg`);
uploadFile(formData).then(res => {
console.log(res)
let url = res[0].filePath
emits('getResultImage',url)
emits('changeSelectImageModal',false)
})
}, format, quality);
}

@ -1,43 +0,0 @@
import { BasicColumn, FormSchema } from '@/components/Table';
import { h } from 'vue';
import { Tag } from 'ant-design-vue';
import Icon from '@/components/Icon/Icon.vue';
export const columns: BasicColumn[] = [
{
title: '按钮名称',
dataIndex: 'name',
},
{
title: 'DOMID',
dataIndex: 'domId',
},
{
title: '排序',
dataIndex: 'sort',
},
{
title: '样式',
dataIndex: 'class',
},
{
title: '状态',
dataIndex: 'enabledMark',
width: 80,
customRender: ({ record }) => {
const color = record.status == 1 ? 'blue' : 'red';
const text = record.status == 1 ? '启用' : '停用';
return h(Tag, { color: color }, () => text);
},
},
];
export const searchFormSchema: FormSchema[] = [
{
field: 'key',
label: '关键字',
component: 'Input',
colProps: { span: 8 },
},
];

@ -1,4 +1,5 @@
<template>
<a-spin tip="加载中..." :spinning="loading">
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<div class="w-1/4 xl:w-1/5 leftbox">
<div class="left-title-div">
@ -6,7 +7,7 @@
<div class="span-title">算法库</div>
<div class="span-subtitle">行业生态算法厂商</div>
</div>
<a-button class="add-algorithm" type="primary" :icon="h(PlusOutlined)" @click="changeAddModal(true)"></a-button>
<a-button class="add-algorithm" type="primary" :icon="h(PlusOutlined)" @click="openChangeAddModal(true)"></a-button>
</div>
<div class="left-interval"></div>
<div class="show-algorithm-list-div">
@ -19,7 +20,7 @@
<div class="empty-icon-image"></div>
<div class="empty-span-subtitle" style="display: inline;">说明书 </div>
</div>
<a-button class="empty-add-algorithm" type="primary" :icon="h(PlusOutlined)" @click="changeAddModal(true)"></a-button>
<a-button class="empty-add-algorithm" type="primary" :icon="h(PlusOutlined)" @click="openChangeAddModal(true)"></a-button>
</div>
</div>
</div>
@ -46,53 +47,105 @@
</div>
<div class="show-list-div">
<div class="list-div">
<div class="item" v-for="value in 8">
<div class="item" v-for="item in dataList">
<div class="image-div">
<img class="image-item" src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">
<img class="image-item" :src="`${VITE_GLOB_API_URL}/${item.pic}`">
<div class="image-span">
<div class="image-span-icon"></div>
<div class="image-span-info">#排水沟盖板缺</div>
<div class="image-span-info">{{ getModelLabels(item.modelLabels) }}</div>
</div>
</div>
<div class="info-div">
<div class="info-icon"></div>
<div class="info-span-div">
<div class="info-span-title">高速排水沟盖板缺失检测算法</div>
<div class="info-span-subtitle">识别高速公路两侧的排水沟盖板缺</div>
<div class="info-span-title">{{ item.name }}</div>
<div class="info-span-subtitle">{{ item.describe }}</div>
</div>
<a-button class="show-info-button" type="primary">查看</a-button>
<a-button class="show-info-button" type="primary" @click="openChangeAddModal(false,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
:page-size-options="pageSizeOptions"
:show-total="total => `共 ${total} 条数据`"
@change="changePagination"
/>
</div>
</div>
</div>
<a-modal width="631px" v-model:open="addAlgorithmModal" :footer="null" :closable="false" :destroyOnClose="true" :maskClosable="false" :keyboard="false">
<AddAlgorithmModal @changeAddModal="changeAddModal"/>
<AddAlgorithmModal :modalType="modalType" :algorithmInfo="algorithmInfo" @changeAddModal="changeAddModal" @query="query"/>
</a-modal>
</PageWrapper>
</a-spin>
</template>
<script lang="ts" setup>
import { ref,h } from 'vue';
import { ref,h, onMounted, watch } from 'vue';
import { PlusOutlined, } from '@ant-design/icons-vue';
import { PageWrapper } from '@/components/Page';
import AddAlgorithmModal from './AddAlgorithmModal.vue';
import { GetAlgorithmsRepositoryList } from '@/api/demo/ailist'
import { getAppEnvConfig } from '@/utils/env';
import { DataListType } from './utils'
const { VITE_GLOB_API_URL } = getAppEnvConfig();
onMounted(() => {
query()
})
const query = () => {
loading.value = true
let params = {
page: page.value,
limit: limit.value,
key: searchValue.value
}
GetAlgorithmsRepositoryList(params).then(res => {
console.log('res',res)
total.value = res.total
dataList.value = res.items
}).finally(() => {
loading.value = false
})
}
const searchValue = ref('');
const addAlgorithmModal = ref(false)
const page = ref('1')
const limit = ref('8')
const total = ref('0')
const dataList = ref<DataListType[]>([])
const pageSizeOptions = ref<string[]>(['8', '16', '32', '64']);
const loading = ref(false)
const modalType = ref('')
const algorithmInfo = ref()
const onSearch = () => {
query()
}
const changeAddModal = (type: boolean) => {
addAlgorithmModal.value = type
}
const openChangeAddModal = (type: boolean, item?) => {
// True False
if(type){
modalType.value = 'insert'
}else{
modalType.value = 'update'
algorithmInfo.value = item
}
changeAddModal(true)
}
const changePagination = () => {
query()
}
const getModelLabels = (data) => {
return data.map( item => `#${item.name}`).join(' ')
}
</script>
<style lang="scss" scoped>
@ -279,6 +332,7 @@
display: flex;
flex-wrap: wrap;
margin-bottom: 90px;
min-height: 576px;
.item{
width: 300px;
height: 268px;
@ -321,6 +375,9 @@
font-size: 11px;
color: #FFFFFF;
line-height: 15px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}

@ -0,0 +1,23 @@
export type ModelLabelsType = {
id?: string,
name: string,
enumValue: string,
reliability: string,
pId?: string,
}
export type DataListType = {
createTime: string,
describe: string,
id: string,
modelLabels: ModelLabelsType[],
name: string,
path: string,
pic: string,
pullUrl: string,
pushUrl: string,
secretKey: string,
serviceUrl: string,
size: string,
type: number
}

@ -28,29 +28,33 @@
name: '媒体库',
children: [
{
id: 'allResource',
id: '',
name: '全部资源',
},
{
id: 'wideAngle',
name: '广角照片',
id: '1',
name: '可见光',
},
{
id: '2',
name: '红外照片',
},
{
id: 'zoom',
id: '3',
name: '变焦照片',
},
{
id: 'infrared',
name: '红外照片',
id: '4',
name: '广角照片',
},
// {
// id: 'alone',
// name: '',
// },
{
id: 'video',
id: '5',
name: '视频资源',
},
{
id: '0',
name: '其他文件',
},
],
},
];

@ -1,4 +1,5 @@
export { default as RecordList } from './recordList.vue';
export { default as RecordListEditInfo } from './recordListEditInfo.vue';
export { default as RecordNewBuild } from './recordNewBuild.vue';
export { default as ModalChooseObject } from './modalChooseObject.vue';
export { default as ImageContrast } from './imageContrast.vue';

@ -0,0 +1,139 @@
[
{
"id": "0",
"name": "变化检测_2024-05-15 17:13:27",
"time": "2024-05-15 17:13:27",
"name1": "测试返回数据1 2025-08-02 09:59:23",
"name2": "测试返回数据2 2025-08-02 10:24:31",
"changeRegion": "47",
"airLineName": "",
"filterCar": "",
"num": "",
"imageGroup": [
{
"id": "01",
"title1": "DJL_20230825170915_0003_W_广角1",
"time1": "2023-08-25 17:22:56",
"size1": "8M",
"width1": 1024,
"height1": 576,
"url1": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"title2": "DJL_20240925170915_0003_W_广角1",
"time2": "2024-09-25 17:22:56",
"size2": "8M",
"width2": 1024,
"height2": 576,
"url2": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"imageJson": []
},
{
"id": "02",
"title1": "DJL_20230825170915_0003_W_广角1",
"time1": "2023-08-25 17:22:56",
"size1": "8M",
"width1": 1024,
"height1": 576,
"url1": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"title2": "DJL_20240925170915_0003_W_广角1",
"time2": "2024-09-25 17:22:56",
"size2": "8M",
"width2": 1024,
"height2": 576,
"url2": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"imageJson": []
},
{
"id": "03",
"title1": "DJL_20230825170915_0003_W_广角1",
"time1": "2023-08-25 17:22:56",
"size1": "8M",
"width1": 1024,
"height1": 576,
"url1": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"title2": "DJL_20240925170915_0003_W_广角1",
"time2": "2024-09-25 17:22:56",
"size2": "8M",
"width2": 1024,
"height2": 576,
"url2": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"imageJson": []
}
]
},
{
"id": "1",
"name": "变化检测_2024-05-25 17:13:27",
"time": "2024-05-25 17:13:27",
"name1": "2024-05-25 17:13:27",
"name2": "2024-05-25 17:13:27",
"changeRegion": "47",
"imageGroup": [
{
"id": "11",
"title1": "DJL_20230825170915_0003_W_广角1",
"time1": "2023-08-25 17:22:56",
"size1": "8M",
"width1": 1024,
"height1": 576,
"url1": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"title2": "DJL_20240925170915_0003_W_广角1",
"time2": "2024-09-25 17:22:56",
"size2": "8M",
"width2": 1024,
"height2": 576,
"url2": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"imageJson": []
}
]
},
{
"id": "2",
"name": "变化检测_2024-06-05 17:13:27",
"time": "2024-06-05 17:13:27",
"name1": "2024-06-05 17:13:27",
"name2": "2024-06-05 17:13:27",
"changeRegion": "47",
"imageGroup": [
{
"id": "21",
"title1": "DJL_20230825170915_0003_W_广角1",
"time1": "2023-08-25 17:22:56",
"size1": "8M",
"width1": 1024,
"height1": 576,
"url1": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"title2": "DJL_20240925170915_0003_W_广角1",
"time2": "2024-09-25 17:22:56",
"size2": "8M",
"width2": 1024,
"height2": 576,
"url2": "https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871",
"imageJson": []
}
]
},
{
"id": "3",
"name": "变化检测_2024-06-25 17:13:27",
"time": "2024-06-25 17:13:27",
"name1": "2024-06-25 17:13:27",
"name2": "2024-06-25 17:13:27",
"changeRegion": "47"
},
{
"id": "4",
"name": "变化检测_2024-06-25 17:13:27",
"time": "2024-06-25 17:13:27",
"name1": "2024-06-25 17:13:27",
"name2": "2024-06-25 17:13:27",
"changeRegion": "47"
},
{
"id": "5",
"name": "变化检测_2024-06-25 17:13:27",
"time": "2024-06-25 17:13:27",
"name1": "2024-06-25 17:13:27",
"name2": "2024-06-25 17:13:27",
"changeRegion": "47"
}
]

@ -14,11 +14,246 @@
transition: 'transform 0.2s',
width: `100%`,
height: `100%`,
background: `url(${nowImageGroup?.show1?.url}) no-repeat center center`,
background: `url(${nowImageGroup?.url1}) no-repeat center center`,
backgroundSize: 'contain',
backgroundPosition: 'center',
}"
/>
>
<!-- 创建的矩形 -->
<div
v-for="(rect, index) in graffitis"
:key="index"
:style="{
position: 'absolute',
left: rect.x + 'px',
top: rect.y + 'px',
width: rect.width + 'px',
height: rect.height + 'px',
zIndex: rect.status == 'edit' ? 1000 : 201,
}"
>
<!-- 文字标签 -->
<div
v-if="nowGraffiti == index || rect.text"
:style="{
position: 'absolute',
left: '0px',
top: rect.height + 'px',
width: '235px',
height: 30 + 'px',
background: nowGraffiti == index ? '#ffffff' : '#ffffff00',
}"
>
<div style="display: block" v-if="nowGraffiti == index">
<a-input
v-model:value="rect.text"
style="width: 110px; height: 30px; margin-right: 10px"
@keypress.enter="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CheckOutlined
style="margin-right: 10px; padding: 3px; color: green"
@click="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CloseOutlined
style="margin-right: 10px; padding: 3px; color: red"
@click="
rect.text
? ((rect.x = graffitisClone[index].x),
(rect.y = graffitisClone[index].y),
(rect.width = graffitisClone[index].width),
(rect.height = graffitisClone[index].height),
(rect.color = graffitisClone[index].color),
(rect.text = graffitisClone[index].text),
(rect.status = 'success'))
: graffitis.splice(index, 1);
nowGraffiti = -1;
"
/>
<DeleteOutlined
style="margin-right: 10px; padding: 3px"
@click="
deleteGraffiti(index, rect.text);
nowGraffiti = -1;
"
/>
<a-popover placement="top">
<template #content>
<div style="display: flex; gap: 3px">
<div
class="popoverClass"
v-for="color in ['#2D8CF0', '#19BE6B', '#FFBB00', '#E23C39', '#B620E0']"
:key="color"
:style="{ background: color }"
@click="rect.color = color"
>
<CheckOutlined
v-if="rect.color == color"
style="color: white; padding: 3px"
/>
</div>
</div>
</template>
<FontColorsOutlined style="" />
</a-popover>
</div>
<div
v-if="nowGraffiti != index"
style="
font-size: 20px;
font-weight: bold;
-webkit-text-stroke: 0.1px white;
width: fit-content;
"
:style="{
textDecoration: rect.status == 'mouse' ? 'underline' : '',
}"
@mouseenter="graffitiFlag && rect.status != 'edit' ? (rect.status = 'mouse') : ''"
@mouseleave="graffitiFlag && rect.status == 'mouse' ? (rect.status = 'success') : ''"
@click="
graffitiFlag
? ((rect.status = 'edit'),
(graffitisClone = cloneDeep(graffitis)),
(nowGraffiti = index))
: ''
"
>
{{ rect.text }}
</div>
</div>
<!-- -->
<div
:style="{
position: 'absolute',
left: rect.status == 'edit' ? '6px' : '0px',
top: '0px',
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
height: '4px',
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'top')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'top')"
/>
<!-- -->
<div
:style="{
position: 'absolute',
left: rect.status == 'edit' ? '6px' : '0px',
bottom: `0px`,
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
height: '4px',
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'bottom')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'bottom')"
/>
<!-- -->
<div
:style="{
position: 'absolute',
right: '0px',
top: rect.status == 'edit' ? '6px' : '0px',
width: '4px',
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'right')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'right')"
/>
<!-- -->
<div
:style="{
position: 'absolute',
left: '0px',
top: rect.status == 'edit' ? '6px' : '0px',
width: '4px',
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'left')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'left')"
/>
<!-- 左上 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
left: '-4px',
top: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'leftTop')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'leftTop')"
/>
<!-- 右下 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
right: '-4px',
bottom: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'rightBottom')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'rightBottom')"
/>
<!-- 右上 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
right: '-4px',
top: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'rightTop')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'rightTop')"
/>
<!-- 左下 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
left: '-4px',
bottom: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'leftBottom')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'leftBottom')"
/>
</div>
</div>
</div>
<div class="imageDiv_Comparison" style="overflow: hidden">
<div class="imageDiv_Comparison_title">视窗2</div>
@ -34,11 +269,246 @@
transition: 'transform 0.2s',
width: `100%`,
height: `100%`,
background: `url(${nowImageGroup?.show2?.url}) no-repeat center center`,
background: `url(${nowImageGroup?.url2}) no-repeat center center`,
backgroundSize: 'contain',
backgroundPosition: 'center',
}"
/>
>
<!-- 创建的矩形 -->
<div
v-for="(rect, index) in graffitis"
:key="index"
:style="{
position: 'absolute',
left: rect.x + 'px',
top: rect.y + 'px',
width: rect.width + 'px',
height: rect.height + 'px',
zIndex: rect.status == 'edit' ? 1000 : 201,
}"
>
<!-- 文字标签 -->
<div
v-if="nowGraffiti == index || rect.text"
:style="{
position: 'absolute',
left: '0px',
top: rect.height + 'px',
width: '235px',
height: 30 + 'px',
background: nowGraffiti == index ? '#ffffff' : '#ffffff00',
}"
>
<div style="display: block" v-if="nowGraffiti == index">
<a-input
v-model:value="rect.text"
style="width: 110px; height: 30px; margin-right: 10px"
@keypress.enter="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CheckOutlined
style="margin-right: 10px; padding: 3px; color: green"
@click="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CloseOutlined
style="margin-right: 10px; padding: 3px; color: red"
@click="
rect.text
? ((rect.x = graffitisClone[index].x),
(rect.y = graffitisClone[index].y),
(rect.width = graffitisClone[index].width),
(rect.height = graffitisClone[index].height),
(rect.color = graffitisClone[index].color),
(rect.text = graffitisClone[index].text),
(rect.status = 'success'))
: graffitis.splice(index, 1);
nowGraffiti = -1;
"
/>
<DeleteOutlined
style="margin-right: 10px; padding: 3px"
@click="
deleteGraffiti(index, rect.text);
nowGraffiti = -1;
"
/>
<a-popover placement="top">
<template #content>
<div style="display: flex; gap: 3px">
<div
class="popoverClass"
v-for="color in ['#2D8CF0', '#19BE6B', '#FFBB00', '#E23C39', '#B620E0']"
:key="color"
:style="{ background: color }"
@click="rect.color = color"
>
<CheckOutlined
v-if="rect.color == color"
style="color: white; padding: 3px"
/>
</div>
</div>
</template>
<FontColorsOutlined style="" />
</a-popover>
</div>
<div
v-if="nowGraffiti != index"
style="
font-size: 20px;
font-weight: bold;
-webkit-text-stroke: 0.1px white;
width: fit-content;
"
:style="{
textDecoration: rect.status == 'mouse' ? 'underline' : '',
}"
@mouseenter="graffitiFlag && rect.status != 'edit' ? (rect.status = 'mouse') : ''"
@mouseleave="graffitiFlag && rect.status == 'mouse' ? (rect.status = 'success') : ''"
@click="
graffitiFlag
? ((rect.status = 'edit'),
(graffitisClone = cloneDeep(graffitis)),
(nowGraffiti = index))
: ''
"
>
{{ rect.text }}
</div>
</div>
<!-- -->
<div
:style="{
position: 'absolute',
left: rect.status == 'edit' ? '6px' : '0px',
top: '0px',
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
height: '4px',
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'top')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'top')"
/>
<!-- -->
<div
:style="{
position: 'absolute',
left: rect.status == 'edit' ? '6px' : '0px',
bottom: `0px`,
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
height: '4px',
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'bottom')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'bottom')"
/>
<!-- -->
<div
:style="{
position: 'absolute',
right: '0px',
top: rect.status == 'edit' ? '6px' : '0px',
width: '4px',
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'right')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'right')"
/>
<!-- -->
<div
:style="{
position: 'absolute',
left: '0px',
top: rect.status == 'edit' ? '6px' : '0px',
width: '4px',
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'left')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'left')"
/>
<!-- 左上 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
left: '-4px',
top: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'leftTop')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'leftTop')"
/>
<!-- 右下 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
right: '-4px',
bottom: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'rightBottom')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'rightBottom')"
/>
<!-- 右上 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
right: '-4px',
top: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'rightTop')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'rightTop')"
/>
<!-- 左下 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
left: '-4px',
bottom: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'leftBottom')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'leftBottom')"
/>
</div>
</div>
</div>
<div class="imageDiv_BottomButton">
<div class="bottomButtonList">
@ -136,7 +606,7 @@
<template #title>
<span>涂鸦标记</span>
</template>
<EditOutlined @click="unlockOut" />
<EditOutlined @click="setGraffiti" />
</a-tooltip>
</div>
</div>
@ -156,6 +626,17 @@
RedoOutlined,
LockOutlined,
UnlockOutlined,
CloseOutlined,
ExportOutlined,
DownloadOutlined,
DeleteOutlined,
CheckOutlined,
EyeOutlined,
EyeInvisibleOutlined,
OneToOneOutlined,
BorderHorizontalOutlined,
ExpandOutlined,
CompressOutlined,
} from '@ant-design/icons-vue';
import { ref, watch, onMounted, computed } from 'vue';
import { cloneDeep } from 'lodash-es';
@ -212,25 +693,6 @@
},
);
//
const getImageWidthAndHeight = computed(() => {
let width = 1300;
let height = 800;
if (props.nowRecord.imageGroup.width > 1300 || props.nowRecord.height > 800) {
if (props.nowRecord.width / 1300 > props.nowRecord.height / 800) {
width = 1300;
height = (props.nowRecord.height / props.nowRecord.width) * 1300;
} else {
height = 800;
width = (props.nowRecord.width / props.nowRecord.height) * 800;
}
} else {
width = props.nowRecord.width;
height = props.nowRecord.height;
}
return [width, height];
});
// -----------------------------------
const scale = ref(1);
const rotationAngle = ref(0);
@ -273,8 +735,176 @@
}
}
// ---------------------------------------------------------------
const graffitiFlag = ref(false);
const graffitiColor = ref('#E23C39');
const nowGraffiti = ref(-1);
const nowMouseGraffiti = ref(0);
//
function setGraffiti() {
refresh();
graffitiFlag.value = !graffitiFlag.value;
if (graffitiFlag.value) {
document.body.style.cursor = 'crosshair';
} else {
document.body.style.cursor = 'pointer';
}
}
//
const isDragging = ref(false);
let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
// -
const dragState = {
isDragging: false,
initialMouseX: 0,
initialMouseY: 0,
initialElementX_1: 0,
initialElementY_1: 0,
initialElementX_2: 0,
initialElementY_2: 0,
};
//
const graffitis: any = ref([]);
const graffitisClone: any = ref([]);
let comparisoNnum = '';
const mouseCanvasRef_1 = ref();
const mouseCanvasRef_2 = ref();
//
function onMouseDown(event, num) {
if (
graffitiFlag.value &&
(graffitis.value.length == 0 || !graffitis.value?.some((item) => item.status != 'success'))
) {
//
if (nowGraffiti.value != -1) return;
if (graffitis.value.findIndex((item) => item.status == 'mouse') != -1) return;
comparisoNnum = num;
//
const rect =
comparisoNnum == '1'
? mouseCanvasRef_1.value.getBoundingClientRect()
: mouseCanvasRef_2.value.getBoundingClientRect();
startX = event.x - rect.x;
startY = event.y - rect.y;
isDragging.value = true;
//
graffitis.value.push({
x: startX,
y: startY,
width: 0,
height: 0,
color: graffitiColor.value,
text: '',
status: 'edit',
type: 'hand',
});
graffitisClone.value = cloneDeep(graffitis.value);
window.addEventListener('mousemove', onMouseMoveGraffit);
window.addEventListener('mouseup', onMouseUpGraffit);
}
if (!graffitiFlag.value) {
const dragElement1: any = document.querySelector('.dragModal_1');
const dragElement2: any = document.querySelector('.dragModal_2');
if (!dragElement1) return;
if (!dragElement2) return;
dragState.isDragging = true;
dragState.initialMouseX = event.clientX;
dragState.initialMouseY = event.clientY;
dragState.initialElementX_1 = dragElement1.offsetLeft;
dragState.initialElementY_1 = dragElement1.offsetTop;
dragState.initialElementX_2 = dragElement2.offsetLeft;
dragState.initialElementY_2 = dragElement2.offsetTop;
dragElement1.style.cursor = 'grabbing';
dragElement2.style.cursor = 'grabbing';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
}
// -
function onMouseMoveGraffit(event) {
//
const rect =
comparisoNnum == '1'
? mouseCanvasRef_1.value.getBoundingClientRect()
: mouseCanvasRef_2.value.getBoundingClientRect();
if (!isDragging.value) return;
endX = event.x - rect.x;
endY = event.y - rect.y;
if (endX < 0 && endY < 0) {
endX = startX;
endY = startY;
}
setMouseData();
}
// -
function onMouseUpGraffit(event) {
//
if (!isDragging.value) return;
const rect =
comparisoNnum == '1'
? mouseCanvasRef_1.value.getBoundingClientRect()
: mouseCanvasRef_2.value.getBoundingClientRect();
endX = event.x - rect.x;
endY = event.y - rect.y;
if (endX < 0 && endY < 0) {
endX = startX;
endY = startY;
}
isDragging.value = false;
if (event.x > rect.right || event.y > rect.bottom) {
graffitis.value.splice(graffitis.value.length - 1, 1);
nowGraffiti.value = -1;
} else {
setMouseData();
nowGraffiti.value = graffitis.value.length - 1;
}
window.removeEventListener('mousemove', onMouseMoveGraffit);
window.removeEventListener('mouseup', onMouseUpGraffit);
}
// -
const handleMouseMove = (event) => {
const dragElement1: any = document.querySelector('.dragModal_1');
const dragElement2: any = document.querySelector('.dragModal_2');
if (!dragElement1) return;
if (!dragElement2) return;
if (!dragState.isDragging) return;
const deltaX = event.clientX - dragState.initialMouseX;
const deltaY = event.clientY - dragState.initialMouseY;
dragElement1.style.left = `${dragState.initialElementX_1 + deltaX}px`;
dragElement1.style.top = `${dragState.initialElementY_1 + deltaY}px`;
dragElement2.style.left = `${dragState.initialElementX_2 + deltaX}px`;
dragElement2.style.top = `${dragState.initialElementY_2 + deltaY}px`;
};
// -
const handleMouseUp = () => {
const dragElement1: any = document.querySelector('.dragModal_1');
const dragElement2: any = document.querySelector('.dragModal_2');
if (!dragElement1) return;
if (!dragElement2) return;
dragState.isDragging = false;
dragElement1.style.cursor = 'default';
dragElement2.style.cursor = 'default';
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
//
function setMouseData() {
graffitis.value[graffitis.value.length - 1].x = Math.min(startX, endX);
graffitis.value[graffitis.value.length - 1].width = Math.abs(endX - startX);
graffitis.value[graffitis.value.length - 1].y = Math.min(startY, endY);
graffitis.value[graffitis.value.length - 1].height = Math.abs(endY - startY);
}
//
function deleteGraffiti(index = undefined, value = undefined) {
graffitis.value.splice(index, 1);
}
//
function onWheel(event) {
const delta = event.deltaY || event.detail || event.wheelDelta;
@ -287,56 +917,6 @@
scale.value -= 0.1;
}
}
//
const isDragging = ref(false);
let initialMouseX;
let initialMouseY;
let initialDocumentX_1;
let initialDocumentY_1;
let initialDocumentX_2;
let initialDocumentY_2;
//
function onMouseDown(event, num) {
const dragDocument1: any = document.querySelector('.dragModal_1');
const dragDocument2: any = document.querySelector('.dragModal_2');
isDragging.value = true;
initialMouseX = event.clientX;
initialMouseY = event.clientY;
initialDocumentX_1 = dragDocument1.offsetLeft;
initialDocumentY_1 = dragDocument1.offsetTop;
initialDocumentX_2 = dragDocument2.offsetLeft;
initialDocumentY_2 = dragDocument2.offsetTop;
dragDocument1.style.cursor = 'grabbing';
dragDocument2.style.cursor = 'grabbing';
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
// -
function onMouseMove(event) {
const dragDocument1: any = document.querySelector('.dragModal_1');
const dragDocument2: any = document.querySelector('.dragModal_2');
if (isDragging.value) {
const deltaX = event.clientX - initialMouseX;
const deltaY = event.clientY - initialMouseY;
dragDocument1.style.left = initialDocumentX_1 + deltaX + 'px';
dragDocument1.style.top = initialDocumentY_1 + deltaY + 'px';
dragDocument2.style.left = initialDocumentX_2 + deltaX + 'px';
dragDocument2.style.top = initialDocumentY_2 + deltaY + 'px';
}
}
// -
function onMouseUp(event) {
const dragDocument1: any = document.querySelector('.dragModal_1');
const dragDocument2: any = document.querySelector('.dragModal_2');
isDragging.value = false;
if (dragDocument1) {
dragDocument1.style.cursor = 'default';
}
if (dragDocument2) {
dragDocument2.style.cursor = 'default';
}
}
</script>
<style lang="less" scoped>
.imageDiv_ {
@ -427,4 +1007,13 @@
}
}
}
.popoverClass {
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
}
</style>

@ -22,13 +22,19 @@
<!-- 检测记录 -->
<div v-if="typeOpen" class="jiancejiluDiv">
<RecordList
v-if="!isNewBuildStatus"
v-if="status == 0"
:recordList="recordList"
:nowRecord="nowRecord"
@chooseNowRecord="chooseNowRecord"
@openIsNewBuildStatus="openIsNewBuildStatus"
@openBuildStatus="openBuildStatus"
@openEditStatus="openEditStatus"
/>
<RecordNewBuild v-if="status == 1" @restoreListStatus="restoreListStatus" />
<RecordListEditInfo
v-if="status == 2"
:nowRecord="nowRecord"
@restoreListStatus="restoreListStatus"
/>
<RecordNewBuild v-if="isNewBuildStatus" @closeIsNewBuildStatus="closeIsNewBuildStatus" />
</div>
<!-- 对比部分 -->
<div
@ -37,7 +43,7 @@
width: typeOpen ? 'calc(100% - 360px)' : '100%',
}"
>
<ImageContrast :recordList="recordList" :nowRecord="nowRecord" />
<ImageContrast :recordList="recordList" :nowRecord="nowRecord" :isEdit="isEdit" />
</div>
</div>
</div>
@ -45,7 +51,8 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { LeftOutlined, CaretUpOutlined } from '@ant-design/icons-vue';
import { RecordList, RecordNewBuild, ImageContrast } from './comparison';
import { RecordList, RecordNewBuild, RecordListEditInfo, ImageContrast } from './comparison';
import datajson from './data.json';
const props = defineProps(['']);
const emit = defineEmits(['closeComparisonModal']);
@ -56,186 +63,37 @@
typeOpen.value = !typeOpen.value;
}
const recordList = ref([
{
id: '0',
name: '变化检测_2024-05-15 17:13:27',
time: '2024-05-15 17:13:27',
createtime1: '2023-08-15 17:13:27',
createtime2: '2024-05-15 17:13:27',
changeRegion: '47',
imageGroup: [
{
id: '01',
show1: {
title: 'DJL_20230825170915_0003_W_广角1',
time: '2023-08-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
show2: {
title: 'DJL_20240925170915_0003_W_广角1',
time: '2024-09-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
},
{
id: '02',
show1: {
title: 'DJL_20230825170915_0003_W_广角2',
time: '2023-08-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
show2: {
title: 'DJL_20240925170915_0003_W_广角2',
time: '2024-09-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
},
{
id: '03',
show1: {
title: 'DJL_20230825170915_0003_W_广角3',
time: '2023-08-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
show2: {
title: 'DJL_20240925170915_0003_W_广角3',
time: '2024-09-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
},
],
},
{
id: '1',
name: '变化检测_2024-05-25 17:13:27',
time: '2024-05-25 17:13:27',
createtime1: '2024-05-25 17:13:27',
createtime2: '2024-05-25 17:13:27',
changeRegion: '47',
imageGroup: [
{
id: '11',
show1: {
title: 'DJL_20230825170915_0003_W_广角1',
time: '2023-08-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
show2: {
title: 'DJL_20240925170915_0003_W_广角1',
time: '2024-09-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
},
],
},
{
id: '2',
name: '变化检测_2024-06-05 17:13:27',
time: '2024-06-05 17:13:27',
createtime1: '2024-06-05 17:13:27',
createtime2: '2024-06-05 17:13:27',
changeRegion: '47',
imageGroup: [
{
id: '21',
show1: {
title: 'DJL_20230825170915_0003_W_广角1',
time: '2023-08-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
show2: {
title: 'DJL_20240925170915_0003_W_广角1',
time: '2024-09-25 17:22:56',
size: '8M',
width: 1024,
height: 576,
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imageJson: [],
},
},
],
},
{
id: '3',
name: '变化检测_2024-06-25 17:13:27',
time: '2024-06-25 17:13:27',
createtime1: '2024-06-25 17:13:27',
createtime2: '2024-06-25 17:13:27',
changeRegion: '47',
},
{
id: '4',
name: '变化检测_2024-06-25 17:13:27',
time: '2024-06-25 17:13:27',
createtime1: '2024-06-25 17:13:27',
createtime2: '2024-06-25 17:13:27',
changeRegion: '47',
},
{
id: '5',
name: '变化检测_2024-06-25 17:13:27',
time: '2024-06-25 17:13:27',
createtime1: '2024-06-25 17:13:27',
createtime2: '2024-06-25 17:13:27',
changeRegion: '47',
},
]);
//
function closeComparisonModal() {
emit('closeComparisonModal');
}
const recordList = ref(datajson);
const nowRecord = ref(recordList.value[0]);
// 012
const status = ref(0);
//
function chooseNowRecord(value) {
nowRecord.value = value;
}
//
function closeComparisonModal() {
emit('closeComparisonModal');
}
//
const isEdit: any = ref(false);
//
const isNewBuildStatus = ref(false);
//
function openIsNewBuildStatus() {
isNewBuildStatus.value = true;
// ------------------------------------------
//
function openBuildStatus() {
status.value = 1;
}
//
function openEditStatus(record) {
status.value = 2;
isEdit.value = true;
}
//
function closeIsNewBuildStatus() {
isNewBuildStatus.value = false;
//
function restoreListStatus() {
status.value = 0;
isEdit.value = false;
}
</script>
<style lang="less" scoped>

@ -36,6 +36,13 @@
</div>
<div class="modalChooseObject_choose_table">
<a-table
:rowKey="'id'"
:hideSelectAll="'true'"
:row-selection="{
selectedRowKeys: state.selectedRowKey1,
type: 'radio',
onChange: onSelectChange1,
}"
:columns="columns"
:data-source="showTableData1"
:pagination="false"
@ -70,6 +77,12 @@
</div>
<div class="modalChooseObject_choose_table">
<a-table
:rowKey="'id'"
:row-selection="{
selectedRowKeys: state.selectedRowKey2,
type: 'radio',
onChange: onSelectChange2,
}"
:columns="columns"
:data-source="showTableData2"
:pagination="false"
@ -89,12 +102,14 @@
</div>
<div class="modalChooseObject_buttons">
<a-button @click="closeModalChooseObject"></a-button>
<a-button type="primary" :disabled="true">确定</a-button>
<a-button type="primary" :disabled="submitDisabled" @click="submitModalChooseObject">
确定
</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { onMounted, ref, reactive } from 'vue';
import { SearchOutlined, LaptopOutlined } from '@ant-design/icons-vue';
import { GetMediaFile } from '@/api/demo/mediaLibrary';
import { getAirLineList } from '@/api/sys/workplan';
@ -144,34 +159,73 @@
let res = await getAirLineList(query);
options.value = res.items;
};
// 1
let data1: any = [];
const inputSearchValue1 = ref('');
const showTableData1 = ref([]);
// 2
let data2: any = [];
const inputSearchValue2 = ref('');
const showTableData2 = ref([]);
// 1
// 线
function getMediaFileOptions1() {
GetMediaFile({
page: 1,
limit: 100,
objectKeyExist: 0,
// taskId: airLineValue.value
airId: airLineValue.value,
}).then((res) => {
data1 = res.items;
showTableData1.value = data1;
data2 = [];
showTableData2.value = data2;
submitDisabled.value = true;
});
}
// 1
//
let data1: any = [];
//
const inputSearchValue1 = ref('');
//
const showTableData1 = ref([]);
// 2
function getMediaFileOptions2() {}
//
let data2: any = [];
//
const inputSearchValue2 = ref('');
//
const showTableData2 = ref([]);
// 12
const state = reactive<{
selectedRowKey1: [];
selectedRowKey2: [];
}>({
selectedRowKey1: [],
selectedRowKey2: [],
});
const submitDisabled = ref(true);
//
function onSelectChange1(selectedRowKeys) {
state.selectedRowKey1 = selectedRowKeys;
data2 = data1.filter((item) => item.id != state.selectedRowKey1[0]);
showTableData2.value = data2;
state.selectedRowKey2 = [];
submitDisabled.value = true;
}
//
function onSelectChange2(selectedRowKeys) {
state.selectedRowKey2 = selectedRowKeys;
submitDisabled.value = false;
}
//
function searchFilter1() {
showTableData1.value = data1.filter((item) => item.name.includes(inputSearchValue1.value));
}
function searchFilter2() {
showTableData2.value = data2.filter((item) => item.name.includes(inputSearchValue2.value));
}
//
function submitModalChooseObject() {
console.log(state.selectedRowKey1[0]);
console.log(state.selectedRowKey2[0]);
}
</script>
<style lang="less" scoped>
.modalChooseObjectDiv {

@ -2,7 +2,7 @@
<div>
<div class="recordList_title">
<span style="margin-left: 15px; font-size: 16px; font-weight: bold">检测记录</span>
<a-button type="primary" style="margin-right: 15px" @click="openIsNewBuildStatus">
<a-button type="primary" style="margin-right: 15px" @click="openBuildStatus">
<PlusOutlined />新建检测
</a-button>
</div>
@ -73,7 +73,7 @@
<template #title>
<span>编辑</span>
</template>
<DashOutlined style="font-size: 16px" />
<DashOutlined style="font-size: 16px" @click="editThisRecord(record)" />
</a-tooltip>
</div>
</div>
@ -81,10 +81,10 @@
<a-tag color="gray">变化区域{{ record.changeRegion }}</a-tag>
</div>
<div class="record_duibi">
<div class="point" style="background: #2773c3" />{{ record.createtime1 }}
<div class="point" style="background: #2773c3" />{{ record.name1 }}
</div>
<div class="record_duibi">
<div class="point" style="background: #cb8824" />{{ record.createtime2 }}
<div class="point" style="background: #cb8824" />{{ record.name2 }}
</div>
<div class="record_time">
<ClockCircleOutlined style="margin-right: 5px" />{{ record.time }}
@ -103,9 +103,10 @@
DashOutlined,
ClockCircleOutlined,
} from '@ant-design/icons-vue';
import { RecordListEditInfo } from './comparison';
const props = defineProps(['recordList', 'nowRecord']);
const emit = defineEmits(['chooseNowRecord', 'openIsNewBuildStatus']);
const emit = defineEmits(['chooseNowRecord', 'openBuildStatus', 'openEditStatus']);
const showSearch = ref(false);
const dropdownSelect = ref('所有航点航线');
@ -123,8 +124,13 @@
emit('chooseNowRecord', value);
}
//
function openIsNewBuildStatus() {
emit('openIsNewBuildStatus');
function openBuildStatus() {
emit('openBuildStatus');
}
//
function editThisRecord(value) {
emit('chooseNowRecord', value);
emit('openEditStatus');
}
</script>
<style lang="less" scoped>

@ -0,0 +1,405 @@
<template>
<div class="recordListShowInfo_div">
<div class="recordListShowInfo_title">
<span class="title">
{{ props.nowRecord.name }}
</span>
<span class="closeButton">
<CloseOutlined @click="restoreListStatus" style="" />
</span>
</div>
<div class="recordListShowInfo_info">
<a-row>
<a-col :span="24">
<div class="info_between">
<span>检测对象</span>
<span class="info_button">评价本次检测 <RightOutlined /></span>
</div>
</a-col>
<a-col :span="24">
<div class="info_point">
<div class="point" style="background: #2773c3" />{{ props.nowRecord.name1 }}
</div>
</a-col>
<a-col :span="24">
<div class="info_point">
<div class="point" style="background: #cb8824" />{{ props.nowRecord.name2 }}
</div>
</a-col>
<a-col :span="24">
<div class="info_"> 航线名称{{ props.nowRecord.airLineName }} </div>
</a-col>
<a-col :span="24">
<div class="info_"> 检测时间{{ props.nowRecord.time }} </div>
</a-col>
<a-col :span="24">
<div class="info_"> 动态物过滤{{ props.nowRecord.filterCar ? '车' : '无' }} </div>
</a-col>
<a-col :span="24">
<div class="info_"> 最小变化区域过滤{{ props.nowRecord.num }} </div>
</a-col>
<a-col :span="24">
<div style="height: 12px"></div>
</a-col>
<a-col :span="6">
<div class="choose_1"> 照片组 &nbsp;&nbsp;{{ props.nowRecord.imageGroup.length }} </div>
</a-col>
<a-col :span="9">
<div class="choose_1">
<span style="color: #ffffff33; margin-left: 5px; margin-right: 5px">
&nbsp;| &nbsp;
</span>
变化区域 &nbsp;&nbsp;{{ props.nowRecord.num }}
</div>
</a-col>
<a-col :span="8">
<div class="choose_1">
<span style="color: #ffffff33; margin-left: 5px; margin-right: 5px">
&nbsp;| &nbsp;
</span>
<a-popover placement="topLeft">
<template #content>
<div style="display: flex; gap: 5px">
<div
class="popoverClass"
v-for="color in [
'#2D8CF0',
'#19BE6B',
'#FFBB00',
'#E23C39',
'#B620E0',
'#ffffff',
]"
:key="color"
:style="{
background: color,
border: `1px solid #000000`,
}"
@click="graffitiColor = color"
>
<CheckOutlined v-if="graffitiColor == color" style="color: white" />
</div>
</div>
</template>
<BgColorsOutlined
:style="{
color: graffitiColor,
fontSize: '20px',
}"
/>
</a-popover>
</div>
</a-col>
<a-col :span="8">
<div class="choose_2">
<div>成功&nbsp;{{ props.nowRecord.num }} </div>
</div>
</a-col>
<a-col :span="8">
<div class="choose_2">
<div>失败&nbsp;{{ props.nowRecord.num }} </div>
</div>
</a-col>
<a-col :span="8">
<div class="choose_2">
<div>偏差&nbsp;{{ props.nowRecord.num }} </div>
</div>
</a-col>
<a-col :span="24">
<div class="table">
<a-table
:columns="columns"
:data-source="tableDataShow"
size="small"
:pagination="false"
:scroll="{ y: 400 }"
>
<template #headerCell="{ column }">
<template v-if="column.key === 'handNum'">
<span> {{ column.title }} <InfoCircleOutlined /> </span>
</template>
</template>
<template #bodyCell="{ column, record }">
<div @click="chooseRecord(record)">
<template v-if="column.key === 'index'">
<span> {{ record.index }}</span>
</template>
<template v-if="column.key === 'aiNum'">
<span v-if="record.type == 'error'">
<div class="point" style="background: #ff0000ff" />失败
</span>
<span v-else-if="record.type == 'warn'">
{{ record.aiNum }} <InfoCircleOutlined style="color: #cb8824" />
</span>
<span v-else> {{ record.aiNum ? record.aiNum : '--' }}</span>
</template>
<template v-if="column.key === 'handNum'">
<span> {{ record.handNum ? record.handNum : '--' }}</span>
</template>
<template v-if="column.key === 'total'">
<span> {{ record.total ? record.total : '--' }}</span>
</template>
</div>
</template>
</a-table>
</div>
</a-col>
<a-col :span="24">
<div class="buttons">
<div class="button_1">重新检测</div>
<div class="button_2">查看报告</div>
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import {
CloseOutlined,
RightOutlined,
BgColorsOutlined,
InfoCircleOutlined,
} from '@ant-design/icons-vue';
const props = defineProps(['nowRecord']);
const emits = defineEmits(['restoreListStatus', 'chooseGraffitiColor', 'chooseRecord']);
const graffitiColor = ref('#ffffff');
function chooseGraffitiColor() {
emits('chooseGraffitiColor', graffitiColor.value);
}
function chooseRecord(record) {
emits('chooseRecord', record);
}
//
function restoreListStatus() {
emits('restoreListStatus');
}
//
const columns = [
{
title: '照片组',
dataIndex: 'index',
key: 'index',
align: 'center',
},
{
title: 'AI',
dataIndex: 'aiNum',
key: 'aiNum',
align: 'center',
},
{
title: '手动',
dataIndex: 'handNum',
key: 'handNum',
align: 'center',
},
{
title: '总计',
dataIndex: 'total',
key: 'total',
align: 'center',
},
];
// -
const tableDataShow = ref([]);
let tableDataTotal: any = [];
onMounted(() => {
tableDataTotal = [];
props.nowRecord.imageGroup.forEach((item, index) => {
let aiNum = item.imageJson.filter((i) => i.type == 'ai')?.length;
let handNum = item.imageJson.filter((i) => i.type == 'hand')?.length;
tableDataTotal.push({
index: index + 1,
aiNum: aiNum,
handNum: handNum,
total: aiNum + handNum,
type: '',
});
});
tableDataShow.value = tableDataTotal;
// .concat(tableDataTotal)
// .concat(tableDataTotal)
// .concat(tableDataTotal);
});
</script>
<style lang="less" scoped>
.recordListShowInfo_div {
position: relative;
width: 100%;
height: 100%;
}
//
.recordListShowInfo_title {
position: relative;
top: 0px;
left: 0px;
color: #ffffff;
height: 60px;
width: 100%;
border-bottom: 1px solid #ffffff55;
display: inline-flex;
align-items: center;
justify-content: space-between;
.title {
display: flex;
align-items: center;
margin-left: 15px;
font-size: 18px;
font-weight: 500;
height: 60px;
}
.closeButton {
font-size: 25px;
color: white;
margin-right: 15px;
}
}
.recordListShowInfo_info {
width: auto;
height: auto;
margin: 10px 20px 10px 20px;
.info_between {
width: 100%;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: space-between;
.info_button {
cursor: pointer;
color: #2d8cf0;
}
}
.info_point {
width: 100%;
height: 25px;
display: inline-flex;
align-items: center;
justify-content: flex-start;
}
.info_ {
width: 100%;
height: 25px;
display: inline-flex;
align-items: center;
justify-content: flex-start;
gap: 20px;
}
.choose_1 {
height: 40px;
}
.choose_2 {
height: 50px;
div {
background: #3c3c3c;
border-radius: 20px;
margin: 0px 5px 0px 5px;
width: auto;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 13px;
cursor: pointer;
}
}
.choose_2_choose {
background: #3c3c3c;
border-radius: 20px;
margin: 0px 5px 0px 5px;
width: auto;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
color: #0f5ea1;
font-size: 13px;
cursor: pointer;
}
.table {
height: auto;
height: 450px;
width: 100%;
}
.buttons {
height: 50px;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
.button_1 {
display: flex;
align-items: center;
justify-content: center;
width: 45%;
height: 30px;
background: #3c3c3c;
border-radius: 5px;
cursor: pointer;
}
.button_2 {
display: flex;
align-items: center;
justify-content: center;
width: 45%;
height: 30px;
background: #2d8cf0;
border-radius: 5px;
cursor: pointer;
}
}
}
.popoverClass {
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
}
.point {
width: 12px;
height: 12px;
border-radius: 12px;
margin-right: 15px;
}
::v-deep .ant-table {
background: #3c3c3c !important;
color: #ffffff !important;
}
::v-deep .ant-table-cell {
background: #3c3c3c !important;
color: #ffffff !important;
}
</style>

@ -52,8 +52,8 @@
</template>
<InfoCircleOutlined style="font-size: 14px; margin-left: 5px" />
</a-tooltip>
0.01-50%</div
>
0.01-50%
</div>
</a-col>
<a-col :span="24" style="margin-top: 10px">
<div class="recordNewBuild_minChange">
@ -90,14 +90,10 @@
</a-row>
</div>
<div class="recordNewBuild_buttons">
<div class="cancelDiv" @click="closeIsNewBuildStatus"></div>
<div class="cancelDiv" @click="restoreListStatus"></div>
<div class="startDiv" :class="{ disabled: !newBuildRecord.data }">开始检测</div>
</div>
<!-- width="100%"
wrap-class-name="full-modal"
-->
<a-modal
v-model:open="modalChooseObjectOpen"
width="80%"
@ -121,7 +117,7 @@
import dayjs from 'dayjs';
const props = defineProps(['']);
const emit = defineEmits(['closeIsNewBuildStatus']);
const emit = defineEmits(['restoreListStatus']);
//
const modalChooseObjectOpen = ref(false);
@ -202,8 +198,8 @@
});
//
function closeIsNewBuildStatus() {
emit('closeIsNewBuildStatus');
function restoreListStatus() {
emit('restoreListStatus');
}
</script>
<style lang="less" scoped>

@ -17,12 +17,24 @@
<a-button :icon="h(BorderHorizontalOutlined)" @click="openComparisonModal">
变化检测
</a-button>
<a-button :icon="h(PlusOutlined)" type="primary" @click="addFolder">
新建文件夹
</a-button>
<a-button :icon="h(PlusOutlined)" @click="addFolder"> </a-button>
<a-button :icon="h(ColumnHeightOutlined)" @click="moveFolderOrFile"></a-button>
<a-button :icon="h(DeleteOutlined)" @click="deleteFolderOrFile"> </a-button>
<!-- <a-button :icon="h(DownloadOutlined)" @click="compressFolderOrFile"></a-button> -->
<a-popover trigger="hover">
<template #content>
<div v-if="!downloadLoading"> /</div>
<div v-if="downloadLoading">
<a-progress :percent="downPercent" type="circle" />
</div>
</template>
<a-button
:icon="h(DownloadOutlined)"
:loading="downloadLoading"
@click="downloadOpenMoadl"
>
批量下载
</a-button>
</a-popover>
<a-radio-group v-model:value="tableType">
<a-radio-button value="table"><BarsOutlined /></a-radio-button>
<a-radio-button value="store"><AppstoreOutlined /></a-radio-button>
@ -97,7 +109,15 @@
<span
style="width: 100%; display: flex; align-items: center; justify-content: center"
>
{{ record.size ? parseFloat((record.size / (1024 * 1024)).toFixed(2)) + 'M' : '-' }}
{{
record.size
? record.size > 1024 * 1024
? parseFloat((record.size / (1024 * 1024)).toFixed(2)) + 'MB'
: record.size > 1024
? parseFloat((record.size / 1024).toFixed(2)) + 'KB'
: parseFloat(record.size) + 'B'
: '-'
}}
</span>
</template>
<template v-if="column.key === 'airLineName'">
@ -284,6 +304,41 @@
<Comparison @closeComparisonModal="closeComparisonModal" />
</a-modal>
<!-- 下载文件命名窗口 -->
<a-modal
v-model:open="downloadOpen"
title="压缩"
width="30%"
:centered="true"
:closable="false"
:destroyOnClose="true"
:keyboard="false"
:mask="false"
:maskClosable="false"
@ok="downloadFile"
@cancel="((zipFileName = null), (downloadRows = []))"
>
<div style="width: 100%; margin: 10px; padding: 10px">
<a-row>
<a-col :span="24">
<div style="width: 100%; height: 40px">批量下载会先进行压缩成一个文件后续再下载</div>
</a-col>
<a-col :span="4">
<div style="width: 100%"> 压缩包名称: </div>
</a-col>
<a-col :span="20">
<div style="width: 80%">
<a-input
v-model:value="zipFileName"
placeholder="请输入压缩文件的名称"
allowClear
></a-input>
</div>
</a-col>
</a-row>
</div>
</a-modal>
<Path
v-if="pathDivShow"
:pathDivShow="pathDivShow"
@ -335,6 +390,8 @@
import { columns, searchFormSchema } from './modal.data';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash-es';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
const { VITE_GLOB_MEDIALIBRARY_IMAGE_URL } = getAppEnvConfig();
const { createConfirm, createMessage } = useMessage();
@ -348,88 +405,98 @@
const tableHeight: any = ref(0);
watch(
() => tableType.value,
(newval) => {
//
if (newval === 'store') {
//
let query = { ...searchParams.value, page: 1, limit: 1000 };
if (routeFlag.value) {
query = {
...query,
taskId: route.query.flightId,
objectKeyExist: 1,
};
} else {
query = {
...query,
parentKey: nowParentKey.value,
};
() => {
//
getShowTableData();
},
);
function getShowTableData() {
//
if (tableType.value === 'store') {
//
let query = { ...searchParams.value, page: 1, limit: 1000 };
if (routeFlag.value) {
query = {
...query,
taskId: route.query.flightId,
objectKeyExist: 1,
};
} else {
query = {
...query,
//
parentKey: searchInfo.type
? nowParentKey.value == '0'
? ''
: nowParentKey.value
: nowParentKey.value,
type: searchInfo.type,
};
}
GetMediaFile(query).then((res) => {
showTableData.value = res.items;
tableTypeAfterShow.value = tableType.value;
// -
const containers = document.querySelectorAll('.ant-table-container');
tableHeight.value = containers[0] ? containers[0]?.scrollHeight : 0;
if (containers) {
containers.forEach((container) => {
container.style.display = 'none';
});
}
GetMediaFile(query).then((res) => {
showTableData.value = res.items;
tableTypeAfterShow.value = newval;
// -
const containers = document.querySelectorAll('.ant-table-container');
tableHeight.value = containers[0] ? containers[0]?.scrollHeight : 0;
if (containers) {
containers.forEach((container) => {
container.style.display = 'none';
});
}
// -
const paginations = document.querySelectorAll('.ant-pagination');
if (paginations) {
paginations.forEach((pagination) => {
pagination.style.display = 'none';
});
}
//
let selectRowsIdArray = getSelectRows().map((item) => item.id);
showTableData.value.forEach((item) => {
if (selectRowsIdArray.includes(item.id)) {
item.checked = true;
changeStore(
{
target: {
checked: true,
},
},
item,
);
}
// -
const paginations = document.querySelectorAll('.ant-pagination');
if (paginations) {
paginations.forEach((pagination) => {
pagination.style.display = 'none';
});
});
}
//
if (newval === 'table') {
reload().then((res) => {
tableTypeAfterShow.value = newval;
// -
const containers = document.querySelectorAll('.ant-table-container');
tableHeight.value = containers[0] ? containers[0]?.scrollHeight : 0;
if (containers) {
containers.forEach((container) => {
container.style.display = 'block';
});
}
// -
const paginations = document.querySelectorAll('.ant-pagination');
if (paginations) {
paginations.forEach((pagination) => {
pagination.style.display = 'block';
});
}
//
let selectRowsIdArray = getSelectRows().map((item) => item.id);
showTableData.value.forEach((item) => {
if (selectRowsIdArray.includes(item.id)) {
item.checked = true;
changeStore(
{
target: {
checked: true,
},
},
item,
);
}
//
let selectRowsIdArray = showTableData.value.filter((item) => item.checked);
setSelectedRows(selectRowsIdArray);
});
}
},
);
});
}
//
if (tableType.value === 'table') {
reload().then((res) => {
tableTypeAfterShow.value = tableType.value;
// -
const containers = document.querySelectorAll('.ant-table-container');
tableHeight.value = containers[0] ? containers[0]?.scrollHeight : 0;
if (containers) {
containers.forEach((container) => {
container.style.display = 'block';
});
}
// -
const paginations = document.querySelectorAll('.ant-pagination');
if (paginations) {
paginations.forEach((pagination) => {
pagination.style.display = 'block';
});
}
//
let selectRowsIdArray = showTableData.value.filter((item) => item.checked);
setSelectedRows(selectRowsIdArray);
});
}
}
//
const checkNameChecked = ref(false);
// or
// or-
function changeStore(e, record) {
if (typeof record == 'string') {
if (e.target.checked) {
@ -545,7 +612,12 @@
...data,
page: tableTypeAfterShow.value == 'table' ? data.page : 1,
limit: tableTypeAfterShow.value == 'table' ? data.limit : 1000,
parentKey: nowParentKey.value,
//
parentKey: searchInfo.type
? nowParentKey.value == '0'
? ''
: nowParentKey.value
: nowParentKey.value,
};
return temp;
}
@ -593,7 +665,7 @@
nowParentKey.value = f.id;
floders.value = floders.value.splice(0, index + 1);
clearSelectedRowKeys();
if (current) {
if (current && f.name == '全部文件') {
setPagination({
current: current,
});
@ -606,9 +678,15 @@
// ----------------------------------------------------------------------
//
function handleSelect(orgId = '') {
searchInfo.orgId = orgId;
reload();
function handleSelect(type = '') {
searchInfo.type = type;
//
if (tableType.value == 'table') {
reload();
} else {
//
getShowTableData();
}
}
//
const childRef = ref<any>();
@ -641,7 +719,13 @@
} else {
query = {
...query,
parentKey: nowParentKey.value,
//
parentKey: searchInfo.type
? nowParentKey.value == '0'
? ''
: nowParentKey.value
: nowParentKey.value,
type: searchInfo.type,
};
}
GetMediaFile(query).then((res) => {
@ -725,6 +809,105 @@
});
}
//
const downloadOpen = ref(false);
const downloadLoading = ref(false);
const downPercent = ref(0);
//
let downloadRows: any = [];
const zipFileName = ref('');
// -
function downloadOpenMoadl() {
//
let rows = getSelectRows();
if (tableType.value == 'store') {
rows = showTableData.value.filter((item) => item.checked);
}
if (rows.length == 0) {
return createMessage.warn('请选择一个或者多个文件/文件夹进行删除');
}
zipFileName.value = '压缩文件' + dayjs().format('YYYY-MM-DD HH:mm:ss');
if (floders.value.length > 1) {
zipFileName.value = floders.value[floders.value.length - 1].name;
}
if (rows.length == 1 && !rows[0].objectKey) {
zipFileName.value = rows[0].name;
}
downloadRows = rows;
downloadOpen.value = true;
}
// -
async function downloadFile() {
downloadOpen.value = false;
downloadLoading.value = true;
getFileList(downloadRows).then(async (res) => {
let filelist: any = res;
const token = localStorage.getItem('X-Token');
const zip = new JSZip();
// ZIP
let index = 0;
for (const file of filelist) {
try {
const response = await fetch(file.url + '?t=' + Date.now(), {
headers: {
Authorization: `Bearer ${token}`,
},
mode: 'cors',
});
if (!response.ok) {
throw new Error(`HTTP 错误: ${response.status} - ${response.statusText}`);
}
const blob = await response.blob();
zip.file(`${file.path}/${file.name}`, blob, { binary: true });
} catch (error) {
console.error('请求失败:', error);
} finally {
index++;
downPercent.value = parseFloat(((index / filelist.length) * 100).toFixed(2));
}
}
// ZIP
try {
const blob = await zip.generateAsync({ type: 'blob' });
saveAs(blob, zipFileName.value + '.zip'); // ZIP
} catch (error) {
console.error('生成ZIP文件时出错:', error);
} finally {
downloadLoading.value = false;
}
});
}
// -
async function getFileList(rows) {
const filelist: any = [];
// 使 for...of await
for (const item of rows) {
if (item.objectKey) {
//
filelist.push({
name: item.name,
url: VITE_GLOB_MEDIALIBRARY_IMAGE_URL + item.objectKey,
path: '',
});
} else {
//
const res = await GetMediaFile({
parentKey: item.id,
page: 1,
limit: 1000,
});
res.items.forEach((r) => {
filelist.push({
name: r.name,
url: VITE_GLOB_MEDIALIBRARY_IMAGE_URL + r.objectKey,
path: item.name, //
});
});
}
}
return filelist;
}
// ----------------------------------------------------------------------------
const openPreview = ref(false);
//
@ -746,7 +929,13 @@
} else {
query = {
...query,
parentKey: nowParentKey.value,
//
parentKey: searchInfo.type
? nowParentKey.value == '0'
? ''
: nowParentKey.value
: nowParentKey.value,
type: searchInfo.type,
};
}
GetMediaFile(query).then((res) => {
@ -761,16 +950,21 @@
//
if (!record.objectKey) {
nowParentKey.value = record.id;
if (floders.value.length == 1) {
current = getPaginationRef().current;
}
floders.value.push({
id: nowParentKey.value,
name: record.name,
});
current = getPaginationRef().current;
setPagination({ current: 1 });
reload().then((res) => {
showTableData.value = res;
});
}
setTimeout(() => {
clearSelectedRowKeys();
}, 500);
}
function uniqueByKey(arrlist, record) {
let resultList: any = [];
@ -849,7 +1043,13 @@
} else {
query = {
...query,
parentKey: nowParentKey.value,
//
parentKey: searchInfo.type
? nowParentKey.value == '0'
? ''
: nowParentKey.value
: nowParentKey.value,
type: searchInfo.type,
};
}
GetMediaFile(query).then((res) => {

@ -24,34 +24,40 @@
</div>
<div class="titleTime">
<ClockCircleOutlined />
&nbsp;&nbsp;
<span>
{{
'&nbsp;&nbsp;' +
dayjs(props.nowShowImageData.createTime).format('YYYY-MM-DD HH:mm:ss (UTCZ)') +
'&nbsp;&nbsp;&nbsp;'
}}
{{ dayjs(props.nowShowImageData.createTime).format('YYYY-MM-DD HH:mm:ss (UTCZ)') }}
</span>
&nbsp;&nbsp;&nbsp;
<span>
{{
props.nowShowImageData.siz
? (props.nowShowImageData.size / 1024 / 1024).toFixed(2) + 'M' + '&nbsp;&nbsp;&nbsp;'
: imageSize + '&nbsp;&nbsp;&nbsp;'
props.nowShowImageData.size
? props.nowShowImageData.size > 1024 * 1024
? parseFloat((props.nowShowImageData.size / (1024 * 1024)).toFixed(2)) + 'MB'
: props.nowShowImageData.size > 1024
? parseFloat((props.nowShowImageData.size / 1024).toFixed(2)) + 'KB'
: parseFloat(props.nowShowImageData.size) + 'B'
: imageSize
}}
</span>
&nbsp;&nbsp;&nbsp;
<span>
{{ props.nowShowImageData.width + ' x ' + props.nowShowImageData.height }}
</span>
</div>
<div class="titleCoordinate">
<span>
{{ props.nowShowImageData.lat + '° E' + '&nbsp;&nbsp;&nbsp;&nbsp;' }}
{{ props.nowShowImageData.lat + '° E' }}
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
<span>
{{ props.nowShowImageData.lng + '° N' + '&nbsp;&nbsp;&nbsp;&nbsp;' }}
{{ props.nowShowImageData.lng + '° N' }}
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
<span>
{{ '拍摄高度' + props.nowShowImageData.relativeAltitude + 'm &nbsp;&nbsp;&nbsp;&nbsp;' }}
{{ '拍摄高度' + props.nowShowImageData.relativeAltitude + 'm' }}
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
</div>
</div>
<!-- 标签 -->
@ -865,8 +871,15 @@
const contentLength = response.headers.get('Content-Length');
if (contentLength) {
const sizeInBytes = parseInt(contentLength, 10);
const sizeInMB = (sizeInBytes / (1024 * 1024)).toFixed(2);
imageSize.value = parseFloat(sizeInMB) + 'M';
if (sizeInBytes > 1024 * 1024) {
imageSize.value = (sizeInBytes / (1024 * 1024)).toFixed(2) + 'MB';
} else if (sizeInBytes > 1024) {
imageSize.value = (sizeInBytes / 1024).toFixed(2) + 'KB';
} else if (sizeInBytes > 0) {
imageSize.value = sizeInBytes + 'B';
} else {
imageSize.value = '--';
}
} else {
imageSize.value = '--';
}
@ -981,45 +994,6 @@
initialElementX: 0,
initialElementY: 0,
};
onMounted(() => {
setupDragListeners();
});
function setupDragListeners() {
const dragElement = document.querySelector('.dragModal');
if (!dragElement) return;
if (graffitiFlag.value) return;
// 使便
const handleMouseDown = (event) => {
dragState.isDragging = true;
dragState.initialMouseX = event.clientX;
dragState.initialMouseY = event.clientY;
dragState.initialElementX = dragElement.offsetLeft;
dragState.initialElementY = dragElement.offsetTop;
dragElement.style.cursor = 'grabbing';
};
const handleMouseMove = (event) => {
if (!dragState.isDragging) return;
const deltaX = event.clientX - dragState.initialMouseX;
const deltaY = event.clientY - dragState.initialMouseY;
dragElement.style.left = `${dragState.initialElementX + deltaX}px`;
dragElement.style.top = `${dragState.initialElementY + deltaY}px`;
};
const handleMouseUp = () => {
dragState.isDragging = false;
dragElement.style.cursor = 'default';
};
//
dragElement.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
// 便
return () => {
dragElement.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
//
const isDragging = ref(false);
//
@ -1055,6 +1029,18 @@
window.addEventListener('mousemove', onMouseMoveGraffit);
window.addEventListener('mouseup', onMouseUpGraffit);
}
if (!graffitiFlag.value) {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
dragState.isDragging = true;
dragState.initialMouseX = event.clientX;
dragState.initialMouseY = event.clientY;
dragState.initialElementX = dragElement.offsetLeft;
dragState.initialElementY = dragElement.offsetTop;
dragElement.style.cursor = 'grabbing';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
}
// -
function onMouseMoveGraffit(event) {
@ -1091,6 +1077,26 @@
window.removeEventListener('mousemove', onMouseMoveGraffit);
window.removeEventListener('mouseup', onMouseUpGraffit);
}
// -
const handleMouseMove = (event) => {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
if (!dragState.isDragging) return;
const deltaX = event.clientX - dragState.initialMouseX;
const deltaY = event.clientY - dragState.initialMouseY;
dragElement.style.left = `${dragState.initialElementX + deltaX}px`;
dragElement.style.top = `${dragState.initialElementY + deltaY}px`;
};
// -
const handleMouseUp = () => {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
dragState.isDragging = false;
dragElement.style.cursor = 'default';
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
//
function onWheel(event) {
if (!graffitiFlag.value) {

@ -192,24 +192,20 @@
const imageTypeSelect = ref([]);
const imageOptions: any = ref([
{
value: '广角照片',
label: '广角照片',
},
{
value: '变焦照片',
label: '变焦照片',
value: 1,
label: '可见光照片',
},
{
value: '红外照片',
value: 2,
label: '红外照片',
},
{
value: '全景图',
label: '全景图',
value: 3,
label: '变焦照片',
},
{
value: '码流截图',
label: '码流截图',
value: 4,
label: '广角照片',
},
]);
//
@ -223,11 +219,9 @@
});
}
//
// if (tagSelect.value.length > 0) {
// imageTypeSelect.value.forEach((imagetype) => {
// filterImageData = filterImageData.filter((item) => item.fileTags.includes(imagetype));
// });
// }
if (imageTypeSelect.value.length > 0) {
filterImageData = filterImageData.filter((item) => imageTypeSelect.value.includes(item.type));
}
//
if (searchValue.value) {
filterImageData = filterImageData.filter((item) => item.name.includes(searchValue.value));

@ -716,7 +716,7 @@
}
}
//
// -
const dragState = {
isDragging: false,
initialMouseX: 0,
@ -724,45 +724,6 @@
initialElementX: 0,
initialElementY: 0,
};
onMounted(() => {
setupDragListeners();
});
function setupDragListeners() {
const dragElement = document.querySelector('.dragModal');
if (!dragElement) return;
if (graffitiFlag.value) return;
// 使便
const handleMouseDown = (event) => {
dragState.isDragging = true;
dragState.initialMouseX = event.clientX;
dragState.initialMouseY = event.clientY;
dragState.initialElementX = dragElement.offsetLeft;
dragState.initialElementY = dragElement.offsetTop;
dragElement.style.cursor = 'grabbing';
};
const handleMouseMove = (event) => {
if (!dragState.isDragging) return;
const deltaX = event.clientX - dragState.initialMouseX;
const deltaY = event.clientY - dragState.initialMouseY;
dragElement.style.left = `${dragState.initialElementX + deltaX}px`;
dragElement.style.top = `${dragState.initialElementY + deltaY}px`;
};
const handleMouseUp = () => {
dragState.isDragging = false;
dragElement.style.cursor = 'default';
};
//
dragElement.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
// 便
return () => {
dragElement.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
//
const isDragging = ref(false);
let startX = 0;
@ -876,6 +837,18 @@
window.addEventListener('mousemove', onMouseMoveGraffit);
window.addEventListener('mouseup', onMouseUpGraffit);
}
if (!graffitiFlag.value) {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
dragState.isDragging = true;
dragState.initialMouseX = event.clientX;
dragState.initialMouseY = event.clientY;
dragState.initialElementX = dragElement.offsetLeft;
dragState.initialElementY = dragElement.offsetTop;
dragElement.style.cursor = 'grabbing';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
}
// -
function onMouseMoveGraffit(event) {
@ -912,6 +885,25 @@
window.removeEventListener('mousemove', onMouseMoveGraffit);
window.removeEventListener('mouseup', onMouseUpGraffit);
}
// -
const handleMouseMove = (event) => {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
if (!dragState.isDragging) return;
const deltaX = event.clientX - dragState.initialMouseX;
const deltaY = event.clientY - dragState.initialMouseY;
dragElement.style.left = `${dragState.initialElementX + deltaX}px`;
dragElement.style.top = `${dragState.initialElementY + deltaY}px`;
};
// -
const handleMouseUp = () => {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
dragState.isDragging = false;
dragElement.style.cursor = 'default';
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
//
function onWheel(event) {

@ -21,7 +21,7 @@
<span class="infotitle">照片类型</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.imgtype || '--' }} </span>
<span class="infovalue">{{ getImageType(props.nowPreviewRecord.type) || '--' }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">任务名称</span>
@ -50,7 +50,11 @@
<span class="infovalue">
{{
props.nowPreviewRecord.size
? (props.nowPreviewRecord.size / 1024 / 1024).toFixed(2) + 'M'
? props.nowPreviewRecord.size > 1024 * 1024
? parseFloat((props.nowPreviewRecord.size / (1024 * 1024)).toFixed(2)) + 'MB'
: props.nowPreviewRecord.size > 1024
? parseFloat((props.nowPreviewRecord.size / 1024).toFixed(2)) + 'KB'
: parseFloat(props.nowPreviewRecord.size) + 'B'
: imageSize
}}
</span>
@ -266,6 +270,26 @@
editName.value = props.nowPreviewRecord.name.split('.').slice(0, -1).join('.');
}
//
function getImageType(type) {
let imageType = '';
switch (type) {
case 1:
imageType = '可见光照片';
break;
case 2:
imageType = '红外照片';
break;
case 3:
imageType = '变焦照片';
break;
case 4:
imageType = '广角照片';
break;
}
return imageType;
}
// --------------------------------
const addFileTagsFlag = ref(false);
const newFileTagsName = ref('');
@ -382,8 +406,15 @@
const contentLength = response.headers.get('Content-Length');
if (contentLength) {
const sizeInBytes = parseInt(contentLength, 10);
const sizeInMB = (sizeInBytes / (1024 * 1024)).toFixed(2);
imageSize.value = parseFloat(sizeInMB) + 'M';
if (sizeInBytes > 1024 * 1024) {
imageSize.value = (sizeInBytes / (1024 * 1024)).toFixed(2) + 'MB';
} else if (sizeInBytes > 1024) {
imageSize.value = (sizeInBytes / 1024).toFixed(2) + 'KB';
} else if (sizeInBytes > 0) {
imageSize.value = sizeInBytes + 'B';
} else {
imageSize.value = '--';
}
} else {
imageSize.value = '--';
}

@ -8,4 +8,6 @@ export { default as LoadControl } from './src/LoadControl.vue';
export { default as FlightControl } from './src/FlightControl.vue';
export { default as TakeOffForm } from './src/TakeOffForm.vue';
export { default as FlyToForm } from './src/FlyToForm.vue';
export { default as Patrol } from './src/Patrol.vue';
export { default as Report } from './src/Report.vue';
export { default as Map } from '../workplan/components/map.vue';

@ -2,14 +2,10 @@
<div>
<div class="map-container">
<div class="map-container-content">
<Map
:airRoute="airRoute"
@flyToThere="flyToThere"
:airPort="airPort"
:uavTrack="uavTrack"
/>
<Map @flyToThere="flyToThere" :airPort="airPort" :uavTrack="uavTrack" />
</div>
</div>
<SelectComponent @selectChange="changeSelect" />
<AirportInformation
@changeLive="changeAirportLive"
@ -76,6 +72,18 @@
@loadLiveStreaming="livePreviewVisible = false"
@changeCameraType="changeCameraType"
/>
<div class="intelligent-patrol">
<div @click="patrolVisible = true">
<span> <RadarChartOutlined /> </span>
<span>智能巡检</span>
</div>
<div @click="reportVisible = true">
<span> <AlertOutlined /></span>
<span>上报事件</span>
</div>
</div>
<Patrol v-if="patrolVisible" @changePatrol="patrolVisible = false" />
<Report @changeReport="reportVisible = false" v-if="reportVisible" />
</div>
</template>
<script setup lang="ts">
@ -92,6 +100,8 @@
FlightControl,
TakeOffForm,
FlyToForm,
Patrol,
Report,
} from './index';
import { useMessage } from '@/hooks/web/useMessage';
import { getClient, createConnection, clientSubscribe, destroyConnection } from '@/utils/mqtt';
@ -100,18 +110,13 @@
import { EventBus } from '@/utils/eventBus';
import { drcUpTopic, setTopic } from '@/utils/debugging/remote';
import { airPortStore } from '@/store/modules/airport';
import { AlertOutlined, RadarChartOutlined } from '@ant-design/icons-vue';
const zIndex = ref(0);
const airPortStoreVal = airPortStore();
const airPortInfo = airPortStoreVal.getAirport;
const UAVinfo = airPortStoreVal.getUAV;
const { createMessage } = useMessage();
const airRoute = ref({
airLineType: null,
airType: null,
airModel: null,
name: null,
});
const locationVal: any = ref({});
const flyToThere = (e) => {
locationVal.value.lat = e._lat;
@ -193,6 +198,10 @@
const flyToFormVisible = ref(false);
//
const uavLive = ref(false);
//
const patrolVisible = ref(false);
//
const reportVisible = ref(false);
const changeAirportLive = () => {
airportLiveVisible.value = !airportLiveVisible.value;
zIndex.value++;
@ -331,4 +340,38 @@
right: 10px;
bottom: 20px;
}
.intelligent-patrol {
position: absolute;
right: 40px;
top: 100px;
display: flex;
flex-direction: column;
div {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
cursor: pointer;
}
span {
color: #fff;
}
span:first-child {
margin-bottom: 6px;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
color: #fff;
background: linear-gradient(180deg, rgba(13, 25, 45, 0.87) 0%, #25436c 100%);
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 10px;
border-image: linear-gradient(180deg, rgba(54, 97, 164, 1), rgba(61, 109, 171, 0.68)) 1 1;
}
::v-deep .anticon svg {
width: 20px;
height: 20px;
}
}
</style>

@ -0,0 +1,362 @@
<template>
<div>
<div class="shade-container" v-if="drawarea"></div>
<div class="airport-information">
<div class="title">选择AI算法实例<span> 航飞要求 </span></div>
<div class="content">
<div class="content-edit instantiate" @click="instantiateVisible = !instantiateVisible">
<div class="input-result">
<span :style="{ backgroundColor: instantiateItem.bgColor }">
{{ instantiateItem.type }}</span
>
{{ instantiateItem.label }}
</div>
<UpOutlined v-if="instantiateVisible" />
<DownOutlined v-else />
<div class="select-result" v-if="instantiateVisible">
<div
v-for="(item, index) in instantiateOptions"
:key="index"
@click="instantiateSelect(item)"
>
<span :style="{ backgroundColor: item.bgColor }">{{ item.type }}</span>
{{ item.label }}
</div>
</div>
</div>
<div class="content-edit space">
<span>空间约束范围</span>
<div class="space-content">
<span>导入KML</span>
<span @click="drawarea = true"><PlusOutlined />新增</span>
</div>
</div>
<div class="content-title">
<span>识别时间范围</span>
</div>
<div class="content-edit">
<a-select v-model:value="data.time" :options="timeOptions" />
</div>
<div class="content-title">
<span>触发动作</span>
</div>
<div class="content-edit" style="border: none">
<a-radio-group v-model:value="data.action" button-style="solid">
<a-radio-button value="1">关闭</a-radio-button>
<a-radio-button value="2">等待接管</a-radio-button>
</a-radio-group>
</div>
<div class="content-title">
<span>警告提示</span>
</div>
<div class="content-edit">
<a-input v-model:value="data.code" placeholder="异常提示" />
</div>
<div class="content-edit">
<a-textarea v-model:value="data.desc" placeholder="识别到异常目标" />
</div>
<div class="content-button">
<a-button type="primary" style="background: #3a57e8" @click="emits('changePatrol')"
>取消</a-button
>
<a-button
type="primary"
style="background: #0a99eb; margin-left: 20px"
@click="emits('changeLoadControl')"
>确定</a-button
>
</div>
</div>
</div>
<div class="patrol-map" v-if="drawarea">
<div class="title">绘制范围</div>
<div class="map-container">
<div class="map-container-content">
<Map :drawArea="drawarea" @areaData="getAreaData" />
</div>
</div>
<div class="content-button">
<a-button type="primary" style="background: #3a57e8" @click="drawarea = false"
>取消</a-button
>
<a-button
type="primary"
style="background: #0a99eb; margin-left: 20px"
@click="patrolMapConfirm"
>确定</a-button
>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import {
InfoCircleOutlined,
DownOutlined,
UpOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import { Map } from '../index';
const { createMessage } = useMessage();
const emits = defineEmits(['changePatrol']);
const props = defineProps({});
const data = reactive({
instantiate: null,
code: '',
desc: '',
action: '1',
time: null,
area: null,
});
const bgOptions = ref(['#6909B2', '#09B284', '#B2AA09', '#E240BD', '#E24040']);
const instantiateVisible = ref(false);
const instantiateOptions = ref([
{
label: '疑似违停车辆识别',
type: '违停车辆',
value: '1',
bgColor: bgOptions.value[0],
},
{
label: '疑似违停车辆识别',
type: '违停车辆',
value: '1',
bgColor: bgOptions.value[2],
},
]);
const timeOptions = ref([
{
label: '全天',
value: 'day',
},
]);
const instantiateItem = ref({});
const instantiateSelect = (val) => {
data.instantiate = val.value;
instantiateItem.value = val;
};
const drawarea = ref(false);
const getAreaData = (val) => {
console.log(val);
data.area = val;
};
const patrolMapConfirm = () => {
if (!data.area) {
createMessage.warning('请绘制区域');
return;
}
drawarea.value = false;
};
onMounted(() => {});
</script>
<style lang="less" scoped>
.shade-container {
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.3);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
.title {
width: 100%;
padding: 10px 0;
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-bottom: 1px solid #4e5778;
display: flex;
justify-content: space-between;
align-items: center;
span {
color: #f2762d;
font-size: 12px;
border-radius: 10px;
border: 1px solid #f2762d;
display: inline-block;
padding: 4px 6px;
}
}
.content-button {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.airport-information {
position: absolute;
top: 100px;
right: 140px;
width: 290px;
padding: 10px;
background: #0d0e15;
margin: 10px 0 0 10px;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px 0px 36px 0px rgba(58, 87, 232, 0.73);
border-radius: 6px;
backdrop-filter: blur(3px);
color: #fff;
.content-item {
display: flex;
align-items: center;
border-bottom: 1px solid #4e5778;
padding: 10px 0;
.item-div {
width: 49%;
cursor: pointer;
img {
width: 20px;
}
}
}
.content-edit {
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #4e5778;
margin-top: 10px;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
input {
background: none;
border: none;
color: #fff;
font-size: 12px;
}
textarea {
background: none;
color: #fff;
border: none;
}
::v-deep .ant-select-selector {
background: none;
width: 220px;
border: none;
text-align: center;
color: #fff;
font-size: 12px;
}
}
.instantiate {
padding: 4px 6px;
cursor: pointer;
position: relative;
div {
span {
margin-right: 6px;
}
}
.input-result {
span {
padding: 6px;
border-radius: 6px;
display: inline-block;
}
}
.select-result {
position: absolute;
top: 40px;
left: -2%;
width: 104%;
background: #0d0e15;
box-shadow:
0px 5px 15px 0px rgba(0, 0, 6, 0.15),
inset 0px 0px 18px 0px rgba(58, 87, 232, 0.73);
height: 300px;
overflow: auto;
z-index: 2;
border: 1px solid #4e5778;
div {
padding: 10px 6px;
border-bottom: 1px solid #1c1c1c;
span {
padding: 6px;
border-radius: 6px;
display: inline-block;
}
}
}
}
.space {
padding: 10px 4px;
font-size: 14px;
.space-content {
> span {
color: #f2762d;
border: 1px solid #f2762d;
padding: 4px 8px;
border-radius: 18px;
font-size: 12px;
cursor: pointer;
}
> span:last-child {
margin-left: 10px;
color: #0377f6;
border: 1px solid #0377f6;
}
}
}
.content-title {
padding-top: 10px;
.anticon {
margin-left: 4px;
cursor: pointer;
}
}
::v-deep .ant-radio-group {
width: 100%;
margin: 2px;
}
::v-deep .ant-radio-button-wrapper {
border: none;
width: 50%;
text-align: center;
background: #0b234d;
color: #fff;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px -1px 5px 0px rgba(213, 220, 255, 0.73);
border-radius: 4px;
}
::v-deep .ant-radio-button-wrapper-checked {
background: #0377f6;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px -1px 5px 0px rgba(213, 220, 255, 0.73);
}
}
.patrol-map {
position: absolute;
top: 80px;
right: calc(50% - 400px);
width: 800px;
height: 600px;
padding: 10px;
background: #0d0e15;
margin: 10px 0 0 10px;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px 0px 36px 0px rgba(58, 87, 232, 0.73);
border-radius: 6px;
backdrop-filter: blur(3px);
color: #fff;
z-index: 1001;
.map-container {
width: 100%;
height: calc(100% - 80px);
}
.map-container-content {
height: calc(100% - 10px);
}
}
</style>

@ -0,0 +1,400 @@
<template>
<div>
<div class="shade-container"></div>
<div class="patrol-map" v-if="step == 1">
<div class="title">绘制范围</div>
<div class="map-container">
<div class="map-container-content">
<Map :drawArea="drawarea" @areaData="getAreaData" />
</div>
</div>
<div class="content-button">
<a-button type="primary" style="background: #3a57e8" @click="emits('changeReport')"
>取消</a-button
>
<a-button type="primary" style="background: #0a99eb; margin-left: 20px" @click="nextStep"
>下一步</a-button
>
</div>
</div>
<div class="airport-information" v-else>
<div class="title">关联事件</div>
<div class="content">
<div class="content-edit">
<span class="edit-title">发生时间</span>
<a-input v-model:value="data.time" placeholder="异常提示" readonly />
</div>
<div class="content-edit instantiate">
<span class="edit-title">关联事件</span>
<div class="input-container" @click="instantiateVisible = !instantiateVisible">
<div class="input-result">
<span :style="{ backgroundColor: instantiateItem.bgColor }">
{{ instantiateItem.type }}</span
>
{{ instantiateItem.label }}
</div>
<UpOutlined v-if="instantiateVisible" />
<DownOutlined v-else />
</div>
<div class="select-result" v-if="instantiateVisible">
<div
v-for="(item, index) in instantiateOptions"
:key="index"
@click="instantiateSelect(item)"
>
<span :style="{ backgroundColor: item.bgColor }">{{ item.type }}</span>
{{ item.label }}
</div>
</div>
</div>
<div class="content-edit">
<span class="edit-title">描述</span>
<a-input v-model:value="data.desc" />
</div>
<div class="content-edit">
<span class="edit-title">发生位置</span>
<a-input v-model:value="data.location" readonly />
</div>
<div class="clearfix"></div>
<div class="map-view">
<div class="map-view-container">
<div class="map-view-container-content">
<Map :polygonArea="data.area" />
</div>
</div>
</div>
<div class="content-button">
<a-button type="primary" style="background: #3a57e8" @click="lastStep"></a-button>
<a-button
type="primary"
style="background: #0a99eb; margin-left: 20px"
@click="reportConfirm"
>提交关联</a-button
>
<a-button
type="primary"
style="background: #0a99eb; margin-left: 20px"
@click="reportConfirm"
>上报新事件</a-button
>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { InfoCircleOutlined, DownOutlined, UpOutlined } from '@ant-design/icons-vue';
import { Map } from '../index';
const step = ref(1);
const { createMessage } = useMessage();
const emits = defineEmits(['changeReport']);
const props = defineProps({});
const data = reactive({
location: null,
instantiate: '',
desc: '',
time: null,
area: null,
});
const bgOptions = ref(['#6909B2', '#09B284', '#B2AA09', '#E240BD', '#E24040']);
const instantiateVisible = ref(false);
const instantiateOptions = ref([
{
label: '疑似违停车辆识别',
type: '违停车辆',
value: '1',
bgColor: bgOptions.value[0],
},
{
label: '疑似违停车辆识别',
type: '违停车辆',
value: '1',
bgColor: bgOptions.value[2],
},
]);
const instantiateItem = ref({});
const instantiateSelect = (val) => {
data.instantiate = val.value;
instantiateItem.value = val;
instantiateVisible.value = false;
};
const drawarea = ref(true);
const getAreaData = (val) => {
console.log(val);
data.area = val;
const center = getPointsCalculateCenter(val);
data.location = center[1] + '°E,' + center[0] + '°N';
};
const nextStep = () => {
if (!data.area) {
createMessage.warning('请绘制区域');
return;
}
step.value = 2;
};
const lastStep = () => {
step.value = 1;
};
const reportConfirm = () => {};
const getPointsCalculateCenter = (points) => {
let point_num = points.length; //
let X = 0,
Y = 0,
Z = 0;
for (let i = 0; i < points.length; i++) {
if (points[i] == '') {
continue;
}
let point = points[i];
let lat, lng, x, y, z;
lat = (parseFloat(point[1]) * Math.PI) / 180;
lng = (parseFloat(point[0]) * Math.PI) / 180;
x = Math.cos(lat) * Math.cos(lng);
y = Math.cos(lat) * Math.sin(lng);
z = Math.sin(lat);
X += x;
Y += y;
Z += z;
}
X = X / point_num;
Y = Y / point_num;
Z = Z / point_num;
let tmp_lng = Math.atan2(Y, X);
let tmp_lat = Math.atan2(Z, Math.sqrt(X * X + Y * Y));
return [(tmp_lat * 180) / Math.PI, (tmp_lng * 180) / Math.PI];
};
onMounted(() => {});
</script>
<style lang="less" scoped>
.shade-container {
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.3);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
.title {
width: 100%;
padding: 10px 0;
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-bottom: 1px solid #4e5778;
display: flex;
justify-content: space-between;
span {
color: #f2762d;
font-size: 12px;
}
}
.content-button {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.airport-information {
position: absolute;
top: 100px;
right: calc(50% - 360px);
width: 700px;
padding: 10px;
background: #0d0e15;
margin: 10px 0 0 10px;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px 0px 36px 0px rgba(58, 87, 232, 0.73);
border-radius: 6px;
backdrop-filter: blur(3px);
color: #fff;
z-index: 1001;
.content-item {
display: flex;
align-items: center;
border-bottom: 1px solid #4e5778;
padding: 10px 0;
.item-div {
width: 49%;
cursor: pointer;
img {
width: 20px;
}
}
}
.content-edit {
display: flex;
flex-direction: column;
margin-top: 14px;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
width: 49%;
float: left;
.edit-title {
display: block;
width: 100px;
margin: 10px 0;
}
input {
background: none;
border: 1px solid #4e5778;
color: #fff;
font-size: 12px;
}
textarea {
background: none;
color: #fff;
border: none;
}
::v-deep .ant-select-selector {
background: none;
width: 220px;
border: 1px solid #4e5778;
text-align: center;
color: #fff;
font-size: 12px;
}
}
.instantiate {
cursor: pointer;
position: relative;
div {
span {
margin-right: 6px;
}
}
.input-container {
width: 100%;
border: 1px solid #4e5778;
display: flex;
border-radius: 6px;
padding: 2px 6px;
}
.input-result {
width: 92%;
height: 26px;
span {
padding: 6px;
border-radius: 6px;
display: inline-block;
}
}
.select-result {
position: absolute;
top: 70px;
left: 0;
width: 100%;
background: #0d0e15;
box-shadow:
0px 5px 15px 0px rgba(0, 0, 6, 0.15),
inset 0px 0px 18px 0px rgba(58, 87, 232, 0.73);
height: 300px;
overflow: auto;
z-index: 2;
border: 1px solid #4e5778;
div {
padding: 10px 6px;
border-bottom: 1px solid #1c1c1c;
span {
padding: 6px;
border-radius: 6px;
display: inline-block;
}
}
}
}
.space {
padding: 10px 4px;
font-size: 14px;
.space-content {
span {
color: #f2762d;
border: 1px solid #f2762d;
padding: 4px 6px;
border-radius: 18px;
font-size: 12px;
cursor: pointer;
}
span:last-child {
margin-left: 10px;
color: #0377f6;
border: 1px solid #0377f6;
}
}
}
.content-title {
padding-top: 10px;
.anticon {
margin-left: 4px;
cursor: pointer;
}
}
::v-deep .ant-radio-group {
width: 100%;
margin: 2px;
}
::v-deep .ant-radio-button-wrapper {
border: none;
width: 50%;
text-align: center;
background: #0b234d;
color: #fff;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px -1px 5px 0px rgba(213, 220, 255, 0.73);
border-radius: 4px;
}
::v-deep .ant-radio-button-wrapper-checked {
background: #0377f6;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px -1px 5px 0px rgba(213, 220, 255, 0.73);
}
}
.patrol-map {
position: absolute;
top: 80px;
right: calc(50% - 400px);
width: 900px;
height: 600px;
padding: 10px;
background: #0d0e15;
margin: 10px 0 0 10px;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
inset 0px 0px 36px 0px rgba(58, 87, 232, 0.73);
border-radius: 6px;
backdrop-filter: blur(3px);
color: #fff;
z-index: 1001;
.map-container {
width: 100%;
height: calc(100% - 80px);
}
.map-container-content {
height: calc(100% - 10px);
}
}
.map-view {
margin-top: 20px;
.map-view-container {
width: 100%;
height: calc(100% - 80px);
}
.map-view-container-content {
height: calc(100% - 10px);
}
}
.clearfix {
clear: both;
}
</style>

@ -157,6 +157,7 @@
<a-switch v-model:checked="submitForm.aiInspection"
checked-color="#3A57E8"
un-checked-color="#cccccc"
@change="changeAiInspection"
/>
</div>
</div>
@ -489,7 +490,7 @@ watch(
}
)
const emit = defineEmits(['selectAriLine','cancleCraete',"selectAircraft","successCreatePlan"]);
const emit = defineEmits(['selectAriLine','cancleCraete',"selectAircraft","successCreatePlan","intelligentPatrol"]);
const removeAirLine = ()=>{
props.checkedAriLine.value = {};
@ -719,7 +720,11 @@ const handleRepeatTypeChange = ()=>{
submitForm.value.periodicFormula = cronTime;
}
const changeAiInspection = (val)=>{
if(val){
emit('intelligentPatrol')
}
}
</script>
<style scoped>

File diff suppressed because it is too large Load Diff

@ -16,7 +16,11 @@
<!-- 创建计划 -->
<div v-if="workPlanFormShow" style="width:360px;height: calc( 100vh - 144px);flex:1;">
<createWorkPlan :workPlanStatus="workPlanStatus" :formData="formData" @successCreatePlan="successCreatePlan" @cancleCraete="cancleCraete" @selectAircraft="selectAircraft" @selectAriLine="selectAriLine" :checkedAriLine="checkedAriLine" :checkedDronePort="checkedDronePort"></createWorkPlan>
<createWorkPlan :workPlanStatus="workPlanStatus" :formData="formData" @successCreatePlan="successCreatePlan" @cancleCraete="cancleCraete" @selectAircraft="selectAircraft" @selectAriLine="selectAriLine" :checkedAriLine="checkedAriLine" :checkedDronePort="checkedDronePort" @intelligentPatrol="changePatrolShow"></createWorkPlan>
</div>
<!-- 智能巡检 -->
<div v-if="patrolShow" class="patrol-box">
<Patrol @changePatrol="patrolShow = false"/>
</div>
<!-- 航线库 -->
@ -64,7 +68,7 @@ import createAirLine from './components/createAirLine.vue'
import Map from './components/map.vue'
import AirPolygon from './components/airPolygon.vue';
import {getAirLine} from '@/api/sys/workplan';
import { Patrol } from '../flightoperation/index';
import JSZip from 'jszip';
import axios from 'axios';
@ -114,7 +118,7 @@ const ariLineShow = ref(false);
const aircraftShow = ref(false);
const createAirLineShow = ref(false);
const importAirLineShow = ref(true);
const patrolShow = ref(false);
if(paramValue){
planListShow.value = false;
workPlanFormShow.value = true;
@ -123,6 +127,9 @@ const selectAriLine = ()=> {
ariLineShow.value = true;
aircraftShow.value = false;
}
const changePatrolShow = ()=>{
patrolShow.value = true;
}
// 线
const closeAirLine = ()=>{
@ -311,4 +318,16 @@ onMounted(()=>{
top:-40px;
left:-40px;
}
.patrol-box{
width:360px;
height: calc( 100vh - 144px);
flex:1;
position: relative;
}
.patrol-box ::v-deep .airport-information{
right: 0;
left: 0;
top: 200px;
}
</style>

Loading…
Cancel
Save