徐景良 3 months ago
commit 8e55946a32

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,23 @@
import { clientReizePublish, clientReizeSubscribe } from '@/utils/mqtt';
export const eventsTopic = (data) => {
// 发送消息
clientReizePublish('thing/product/8UUXN5400A079H/events', data);
};
export const events_replyTopic = () => {
// 订阅消息
clientReizeSubscribe('thing/product/8UUXN5400A079H/events_reply');
};
export const servicesTopic = (data) => {
// 发送消息
clientReizePublish('thing/product/8UUXN5400A079H/services', data);
};
export const services_replyTopic = () => {
// 订阅消息
clientReizeSubscribe('thing/product/8UUXN5400A079H/services_reply');
};
export const drcDownTopic = (data) => {
// 发送消息thing/product/{gateway_sn}/drc/down
clientReizePublish('thing/product/8UUXN5400A079H/drc/down', data);
};

@ -18,6 +18,7 @@ const connection = {
username: 'sdhc',
password: '',
};
let client: any = {
connected: false,
};
@ -105,6 +106,51 @@ const clientPublish = (topic: string, querys: any) => {
// subscribe 事件 订阅
// unsubscribe 事件 取消订阅
// 抢夺负载权、飞行控制权的时候使用
const client_seize: any = {
connected: false,
};
const createSeizeConnection = () => {
const seizeConnection = connection;
seizeConnection.clientId = 'mqtt_client_1581F8HGX254V00A0BUY_seize';
try {
const { protocol, host, port, endpoint, ...options } = seizeConnection;
const connectUrl = `${protocol}://${host}:${port}${endpoint}`;
client_seize = mqtt.connect(connectUrl, options);
if (client.on) {
}
} catch (error) {
console.log('mqtt.connect error', error);
}
};
const getReizeClient = () => {
if (!client_seize || !client_seize.connected) {
getReizeClient();
}
return client_seize;
};
// 订阅事件
const clientReizeSubscribe = (topic: string, options?: any) => {
console.log(client_seize);
if (!client_seize || !client_seize.connected) {
createConnection();
}
getReizeClient().subscribe(topic, { qos: 0 }, (error, res) => {
console.log('订阅');
console.log(error, res);
});
};
// 发送消息
const clientReizePublish = (topic: string, querys: any) => {
if (!client_seize || !client_seize.connected) {
createConnection();
}
getReizeClient().publish(topic, JSON.stringify(querys), { qos: 0 }, (err) => {
if (err) {
console.error('Publish error:', err);
}
});
};
export {
// 连接
createConnection,
@ -112,4 +158,8 @@ export {
getClient,
clientSubscribe,
clientPublish,
createSeizeConnection,
getReizeClient,
clientReizeSubscribe,
clientReizePublish,
};

@ -18,12 +18,10 @@
const emit = defineEmits(['select']);
const treeData = ref([]);
const treeData: any = ref([]);
const asyncExpandTreeRef = ref<Nullable<TreeActionType>>(null);
async function fetch() {
// treeData.value = (await getChildrenTree({ parentId: 0 })) as unknown as TreeItem[];
// console.log(treeData.value);
treeData.value = [
{
id: 'meitiku',
@ -65,14 +63,14 @@
},
],
},
{
id: 'gas',
name: '气体探测',
},
{
id: 'vr',
name: 'VR全景',
},
// {
// id: 'gas',
// name: '',
// },
// {
// id: 'vr',
// name: 'VR',
// },
];
//

@ -1,42 +1,186 @@
<template>
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<LeftTree ref="childRef" class="w-1/5 xl:w-1/6" @select="handleSelect" />
<BasicTable class="w-4/5 xl:w-5/6" @register="registerTable" :searchInfo="searchInfo">
<template #toolbar>
<!-- <PermissionBtn @btnEvent="onBtnClicked"></PermissionBtn> -->
<a-button :icon="h(PlusOutlined)" type="primary" @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-radio-group v-model:value="tableType">
<a-radio-button value="table"><BarsOutlined /></a-radio-button>
<a-radio-button value="store"><AppstoreOutlined /></a-radio-button>
</a-radio-group>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<FolderOpenOutlined v-if="record.type == 'folder'" style="font-size: 20px" />
<img v-if="record.type == 'img'" :src="record.url" :width="30" :height="20" />
{{ record.name }}
</template>
<template v-if="column.key === 'size'">
{{ record.size ? record.size : '-' }}
</template>
<template v-if="column.key === 'label'">
<div v-if="record.label && record.label.length > 0">
<a-tag color="success" v-for="la in record.label" :key="la">{{ la }}</a-tag>
<div class="w-4/5 xl:w-5/6">
<BasicTable @register="registerTable" :searchInfo="searchInfo">
<template #toolbar>
<span v-for="f in floders" :key="f" class="floderTitle">
<span v-if="f != '全部文件'" style="margin-right: 10px"> / </span>
<span @click="getChildrenByProp(tableData, f)"> {{ f }}</span>
</span>
<div class="floderOtherButton">
<!-- <PermissionBtn @btnEvent="onBtnClicked"></PermissionBtn> -->
<a-button :icon="h(PlusOutlined)" type="primary" @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-radio-group v-model:value="tableType">
<a-radio-button value="table"><BarsOutlined /></a-radio-button>
<a-radio-button value="store"><AppstoreOutlined /></a-radio-button>
</a-radio-group>
</div>
</template>
<template v-if="column.key === 'action'">
<a-button type="text">
<EditOutlined @click="renameRecord(record)" />
</a-button>
<a-button type="text" v-if="record.type != 'folder'">
<EyeOutlined @click="lookRecord(record)" />
</a-button>
<template #bodyCell="{ column, record }">
<div v-if="tableType == 'table'">
<template v-if="column.key === 'name'">
<FolderOpenOutlined v-if="record.type == 'folder'" style="font-size: 20px" />
<!-- <PlaySquareTwoTone v-if="record.type == 'video'" style="font-size: 20px" /> -->
<img v-if="record.type == 'img'" :src="record.url" :width="30" :height="20" />
<img
v-if="record.type == 'video'"
:src="record.gifZoomImage"
:width="30"
:height="20"
/>
<FileOutlined v-if="record.type.includes('model')" style="font-size: 20px" />
<div
v-if="record.type.includes('2D')"
style="
position: absolute;
top: 20px;
left: 11px;
font-size: 10px;
color: #ffffff;
background: #000000;
pointer-events: none;
"
>
2D
</div>
<div
v-if="record.type.includes('3D')"
style="
position: absolute;
top: 20px;
left: 11px;
font-size: 10px;
color: #ffffff;
pointer-events: none;
background: #000000;
"
>
3D
</div>
<span
@click="lookRecord(record)"
@mouseover="record.isHovered = true"
@mouseout="record.isHovered = false"
:style="{
textDecoration: record.isHovered ? 'underline' : 'none',
marginLeft: '5px',
}"
>
{{ record.name }}
</span>
</template>
<template v-if="column.key === 'size'">
{{ record.size ? record.size : '-' }}
</template>
<template v-if="column.key === 'label'">
<div v-if="record.label && record.label.length > 0">
<a-tag color="success" v-for="la in record.label" :key="la">{{ la }}</a-tag>
</div>
</template>
<template v-if="column.key === 'action'">
<a-button type="text">
<EditOutlined @click="renameRecord(record)" />
</a-button>
</template>
</div>
</template>
</template>
</BasicTable>
</BasicTable>
<div v-if="tableType == 'store'" class="storeDivsAllChoose">
<a-checkbox v-model:checked="checkNameChecked" @change="changeStore($event, 'allChoose')">
全选
</a-checkbox>
</div>
<div v-if="tableType == 'store'" class="storeDivs">
<div
v-for="record in showTableData"
:key="record.id"
class="storeDiv"
:style="{ background: record.isHovered ? '#D5E8FC' : '#ffffff' }"
@mouseover="record.isHovered = true"
@mouseout="record.isHovered = false"
>
<div style="position: absolute; top: 0px; left: 5px">
<a-checkbox v-model:checked="record.checked" @change="changeStore($event, record)" />
</div>
<FolderOpenOutlined
v-if="record.type == 'folder'"
style="font-size: 40px"
@click="lookRecord(record)"
/>
<PlaySquareTwoTone
v-if="record.type == 'video'"
style="font-size: 40px"
@click="lookRecord(record)"
/>
<img
v-if="record.type == 'img'"
:src="record.url"
:width="60"
:height="40"
@click="lookRecord(record)"
/>
<FileOutlined
v-if="record.type.includes('model')"
style="font-size: 40px"
@click="lookRecord(record)"
/>
<div
v-if="record.type.includes('2D')"
style="
position: absolute;
top: 52px;
left: 60px;
font-size: 20px;
color: #ffffff;
pointer-events: none;
background: #000000;
"
@click="lookRecord(record)"
>
2D
</div>
<div
v-if="record.type.includes('3D')"
style="
position: absolute;
top: 52px;
left: 60px;
font-size: 20px;
color: #ffffff;
pointer-events: none;
background: #000000;
"
@click="lookRecord(record)"
>
3D
</div>
<span
:style="{
textDecoration: record.isHovered ? 'underline' : 'none',
position: 'absolute',
bottom: '10px',
left: '0px',
width: '100%',
height: '30px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
}"
@click="lookRecord(record)"
>
{{ record.name }}
</span>
</div>
</div>
</div>
<!-- 新建文件夹 -->
<AddFolderModal @register="addFolderModal" @success="handleSuccess" />
<!-- 移动 -->
@ -54,6 +198,7 @@
:closable="false"
:footer="null"
:destroyOnClose="true"
:keyboard="false"
:mask="false"
:maskClosable="false"
@ok="handleOk"
@ -78,7 +223,7 @@
</PageWrapper>
</template>
<script lang="ts" setup>
import { reactive, ref, h } from 'vue';
import { reactive, ref, watch, h } from 'vue';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { getOrgList, deleteDept } from '@/api/demo/system';
import { PageWrapper } from '@/components/Page';
@ -95,6 +240,8 @@
FolderOutlined,
FolderOpenOutlined,
EyeOutlined,
PlaySquareTwoTone,
FileOutlined,
} from '@ant-design/icons-vue';
import LeftTree from './LeftTree.vue';
import { AddFolderModal } from './modal/modal';
@ -106,51 +253,11 @@
import { PermissionBtn } from '@/components/PermissionBtn/index';
import { columns, searchFormSchema } from './modal.data';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash-es';
const { createConfirm, createMessage } = useMessage();
const data = ref([
{
id: '1',
name: '普通飞行1',
createtime: '2025-03-24 18:13:17',
type: 'folder',
children: [
{
id: '21',
name: '普通飞行2-1',
createtime: '2025-03-24 18:13:17',
type: 'folder',
children: [
{
id: '31',
name: '普通飞行3-1',
createtime: '2025-03-24 18:13:17',
type: 'img',
},
],
},
{
id: '22',
name: '普通飞行2-2',
createtime: '2025-03-24 18:13:17',
type: 'folder',
children: [
{
id: '32',
name: '普通飞行3-2',
createtime: '2025-03-24 18:13:17',
type: 'img',
},
{
id: '33',
name: '普通飞行3-3',
createtime: '2025-03-24 18:13:17',
type: 'img',
},
],
},
],
},
//
const tableData = ref([
{
id: '2',
name: '图片',
@ -160,9 +267,9 @@
{
id: '1-2',
name: '南山风景照.jpg',
createTime: '2020-10-22 17:33:22',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://cdn.colorhub.me/QgpUMkZxNhU/rs:auto:0:500:0/g:ce/fn:colorhub/bG9jYWw6Ly8vZmIvNmYvMjlkMTE1NjRkNmI5ZmRhOTczYmU3ZmUyNmMyMDkwM2MwZjU5ZmI2Zi5qcGVn.webp',
url: 'https://img2.baidu.com/it/u=257681495,312745373&fm=253&fmt=auto&app=138&f=JPEG?w=750&h=500',
imgtype: '原片',
taskname: '佛山大火救援项目',
airlineName: '火灾救援勘查航线',
@ -176,13 +283,11 @@
label: ['人', '车'],
lat: 35.362625,
lng: 118.033886,
canvasJson:
'{"version":"4.6.0","objects":[{"type":"path","version":"4.6.0","originX":"left","originY":"top","left":520.5,"top":634,"width":0,"height":0,"fill":null,"stroke":"#ffffff","strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"round","strokeDashOffset":0,"strokeLineJoin":"round","strokeUniform":false,"strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"path":[["M",520.999,634.5],["L",521.001,634.5]]},{"type":"rect","version":"4.6.0","originX":"left","originY":"top","left":523,"top":352.5,"width":100,"height":100,"fill":"#ffffff00","stroke":"#ffffff","strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":0.73,"scaleY":0.73,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0},{"type":"textbox","version":"4.6.0","originX":"left","originY":"top","left":523,"top":426.5,"width":200,"height":22.6,"fill":"#ffffff","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"fontFamily":"Times New Roman","fontWeight":"normal","fontSize":20,"text":"太阳","underline":false,"overline":false,"linethrough":false,"textAlign":"left","fontStyle":"normal","lineHeight":1.16,"textBackgroundColor":"","charSpacing":0,"styles":{},"direction":"ltr","path":null,"pathStartOffset":0,"pathSide":"left","minWidth":20,"splitByGrapheme":false},{"type":"path","version":"4.6.0","originX":"left","originY":"top","left":784,"top":436.5,"width":190.01,"height":85.01,"fill":null,"stroke":"#ffffff","strokeWidth":4,"strokeDashArray":null,"strokeLineCap":"round","strokeDashOffset":0,"strokeLineJoin":"round","strokeUniform":false,"strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"path":[["M",785.996,438.496],["Q",786,438.5,786.5,438.5],["Q",787,438.5,790.5,440],["Q",794,441.5,798.5,443.5],["Q",803,445.5,808,448],["Q",813,450.5,820,453.5],["Q",827,456.5,880,480.5],["Q",933,504.5,940,508],["Q",947,511.5,952.5,514],["Q",958,516.5,962.5,518],["Q",967,519.5,970,521],["Q",973,522.5,974.5,523],["L",976.004,523.504]]},{"type":"path","version":"4.6.0","originX":"left","originY":"top","left":808,"top":417.5,"width":123.01,"height":124.01,"fill":null,"stroke":"#ffffff","strokeWidth":4,"strokeDashArray":null,"strokeLineCap":"round","strokeDashOffset":0,"strokeLineJoin":"round","strokeUniform":false,"strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"path":[["M",933.004,419.496],["Q",933,419.5,931.5,420.5],["Q",930,421.5,928,425],["Q",926,428.5,921.5,433],["Q",917,437.5,914,441.5],["Q",911,445.5,905.5,450.5],["Q",900,455.5,894,462],["Q",888,468.5,883,473.5],["Q",878,478.5,872,484.5],["Q",866,490.5,861.5,495],["Q",857,499.5,851,504.5],["Q",845,509.5,841.5,513],["Q",838,516.5,833.5,520],["Q",829,523.5,826,526.5],["Q",823,529.5,820,533],["Q",817,536.5,815,538],["Q",813,539.5,812.5,540.5],["Q",812,541.5,811,542.5],["L",809.996,543.504]]}],"backgroundImage":{"type":"image","version":"4.6.0","originX":"left","originY":"top","left":0,"top":0,"width":889,"height":500,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1.46,"scaleY":1.6,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"cropX":0,"cropY":0,"src":"https://cdn.colorhub.me/QgpUMkZxNhU/rs:auto:0:500:0/g:ce/fn:colorhub/bG9jYWw6Ly8vZmIvNmYvMjlkMTE1NjRkNmI5ZmRhOTczYmU3ZmUyNmMyMDkwM2MwZjU5ZmI2Zi5qcGVn.webp","crossOrigin":null,"filters":[]}}',
},
{
id: '1-3',
name: '风景图.jpg',
createTime: '2020-10-22 17:33:22',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imgtype: '原片2',
@ -196,15 +301,13 @@
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362625,
lat: 35.362825,
lng: 118.033886,
canvasJson:
'{"version":"4.6.0","objects":[],"backgroundImage":{"type":"image","version":"4.6.0","originX":"left","originY":"top","left":0,"top":0,"width":2000,"height":1334,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":0.65,"scaleY":0.6,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"cropX":0,"cropY":0,"src":"https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871","crossOrigin":null,"filters":[]}}',
},
{
id: '1-4',
name: '红外照片.jpg',
createTime: '2020-10-22 17:33:22',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://pic.rmb.bdstatic.com/bjh/gallery/8c885a0e3cf0647b60548535e2e9ca39.jpeg',
imgtype: '红外照片',
@ -218,13 +321,13 @@
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362625,
lat: 35.362925,
lng: 118.033886,
},
{
id: '1-5',
name: '广角照片.jpg',
createTime: '2020-10-22 17:33:22',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://img2.baidu.com/it/u=2490853491,3226002419&fm=253&fmt=auto&app=138&f=JPEG?w=749&h=500',
imgtype: '广角照片',
@ -239,12 +342,12 @@
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362625,
lng: 118.033886,
lng: 118.033286,
},
{
id: '1-6',
name: '变焦照片.jpg',
createTime: '2020-10-22 17:33:22',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://img2.baidu.com/it/u=3778652155,475195343&fm=253&fmt=auto&app=138&f=PNG?w=500&h=518',
imgtype: '变焦照片',
@ -258,8 +361,8 @@
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362625,
lng: 118.033886,
lat: 35.362725,
lng: 118.033086,
},
],
},
@ -269,38 +372,48 @@
createtime: '2025-03-24 18:13:17',
type: 'folder',
children: [
{
id: '3-4',
name: 'XZD153狼窝沟西南',
createTime: '',
type: 'video',
url: '74b95e6575d741489b9a9061bb646467',
manufacturer: '海康',
},
// {
// id: '3-4',
// name: 'XZD153西',
// createtime: '',
// type: 'video',
// url: '74b95e6575d741489b9a9061bb646467',
// manufacturer: '',
// },
{
id: '3-5',
name: '费县马庄镇陈家鱼后村南斜坡后村',
createTime: '',
createtime: '',
type: 'video',
url: 'http://111.36.45.20:18000/flv/hls/H-dcb1ea7388588111.flv',
gifZoomImage: 'https://img.soogif.com/mrGHcO3xjFJnJ986TeL9oAr2BYfPIaM7.gif',
manufacturer: '腾讯',
},
{
id: '3-6',
name: '可落',
createTime: '',
id: '3-8',
name: 'movie',
createtime: '',
type: 'video',
url: '8H03AA1PAG8D9BF',
manufacturer: '乐橙',
},
{
id: '3-7',
name: '费县薛庄镇东张林村村南可见光',
createTime: '',
type: 'video',
url: '37130100181328000392',
manufacturer: '青犀',
url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4',
gifZoomImage: 'https://img.soogif.com/mrGHcO3xjFJnJ986TeL9oAr2BYfPIaM7.gif',
manufacturer: '腾讯',
},
// {
// id: '3-6',
// name: '',
// createtime: '',
// type: 'video',
// url: '8H03AA1PAG8D9BF',
// manufacturer: '',
// },
// {
// id: '3-7',
// name: '',
// createtime: '',
// type: 'video',
// url: '37130100181328000392',
// manufacturer: '',
// },
],
},
{
@ -342,52 +455,83 @@
],
},
]);
//
const showTableData = ref(cloneDeep(tableData.value));
const floders = ref(['全部文件']);
//
const tableType = ref('table');
const searchInfo = reactive<Recordable>({});
const searchParams = ref();
const [registerTable, { reload, getSelectRows, getDataSource, clearSelectedRowKeys }] = useTable({
// api: getOrgList,
dataSource: data,
rowKey: 'id',
columns,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
},
rowSelection: {
type: 'checkbox',
},
striped: false,
bordered: false,
inset: false,
tableSetting: {
redo: false,
size: false,
setting: false,
const tableHeight: any = ref(0);
watch(
() => tableType.value,
(newval) => {
//
const containers = document.querySelectorAll('.ant-table-container');
if (newval) {
tableHeight.value = containers[0] ? containers[0]?.scrollHeight : 0;
} else {
tableHeight.value = 0;
}
if (containers) {
containers.forEach((container) => {
container.style.display = tableType.value === 'table' ? 'block' : 'none';
});
}
//
const paginations = document.querySelectorAll('.ant-pagination');
if (paginations) {
paginations.forEach((pagination) => {
pagination.style.display = tableType.value === 'table' ? 'block' : 'none';
});
}
},
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
handleSearchInfoFn(info) {
console.log(info);
console.log(searchInfo.value);
);
searchParams.value = info;
return info;
},
beforeFetch: (data) => {
//
var temp = {
startTime: dayjs(data.startTime).startOf('month').format('YYYY-MM-DD'),
endTime: dayjs(data.endTime).endOf('month').format('YYYY-MM-DD HH:mm:ss'),
};
return temp;
},
afterFetch: (res) => {
console.log(res);
},
});
const searchInfo = reactive<Recordable>({});
const searchParams = ref();
const [registerTable, { reload, getSelectRows, setSelectedRows, clearSelectedRowKeys }] =
useTable({
// api: getOrgList,
// title: '',
dataSource: showTableData,
rowKey: 'id',
columns,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
},
rowSelection: {
type: 'checkbox',
},
isTreeTable: false,
striped: false,
bordered: false,
inset: false,
tableSetting: {
redo: false,
size: false,
setting: false,
},
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
handleSearchInfoFn(info) {
console.log(info);
console.log(searchInfo.value);
searchParams.value = info;
return info;
},
beforeFetch: (data) => {
//
var temp = {
startTime: dayjs(data.startTime).startOf('month').format('YYYY-MM-DD'),
endTime: dayjs(data.endTime).endOf('month').format('YYYY-MM-DD HH:mm:ss'),
};
return temp;
},
afterFetch: (res) => {
console.log(res);
},
});
function handleSelect(orgId = '') {
searchInfo.orgId = orgId;
@ -419,7 +563,7 @@
if (rows[0].type == 'folder') {
record = rows[0];
} else {
record = findParentIdById(getDataSource(), rows[0].id);
record = findParentIdById(tableData.value, rows[0].id);
}
}
openAddFolderModal(true, {
@ -452,7 +596,7 @@
if (rows.length > 0) {
const record = rows;
openMoveFileModal(true, {
tableData: getDataSource(),
tableData: tableData.value,
record,
});
} else {
@ -490,7 +634,7 @@
if (rows.length > 0) {
const record = rows;
openCompressFileModal(true, {
tableData: getDataSource(),
tableData: tableData.value,
record,
});
} else {
@ -511,12 +655,45 @@
const nowPreviewRecord: any = ref();
const previewRecordList: any = ref([]);
function lookRecord(record) {
open.value = true;
nowPreviewRecord.value = record;
previewRecordList.value = findParentIdById(getDataSource(), record.id)?.children;
if (!previewRecordList.value) {
previewRecordList.value = getDataSource();
if (record.type == 'folder') {
showTableData.value = record.children;
floders.value.push(record.name);
} else {
open.value = true;
nowPreviewRecord.value = record;
previewRecordList.value = findParentIdById(tableData.value, record.id)?.children;
if (!previewRecordList.value) {
previewRecordList.value = tableData.value;
}
}
}
//
function getChildrenByProp(children, name) {
if (name == '全部文件') {
showTableData.value = cloneDeep(tableData.value);
floders.value = ['全部文件'];
clearSelectedRowKeys();
return;
}
for (const node of children) {
if (node.name === name) {
showTableData.value = cloneDeep(node.children);
const index = floders.value.indexOf(name);
floders.value = index === -1 ? [] : floders.value.slice(0, index + 1);
clearSelectedRowKeys();
return node.children || [];
}
if (node.children && node.children.length > 0) {
const result = getChildrenByProp(node.children, node.name);
if (result) {
const index = floders.value.indexOf(name);
floders.value = index === -1 ? [] : floders.value.slice(0, index + 1);
clearSelectedRowKeys();
return result;
}
}
}
return null;
}
//
@ -525,6 +702,108 @@
}
//
function closeModal() {
document.body.style.cursor = 'auto';
open.value = false;
}
const checkNameList: any = ref([]);
const checkNameChecked = ref(false);
function changeStore(e, record) {
if (typeof record == 'string') {
if (e.target.checked) {
checkNameChecked.value = true;
showTableData.value = showTableData.value.map((item) => ({
...item,
checked: true,
}));
} else {
checkNameChecked.value = false;
showTableData.value = showTableData.value.map((item) => ({
...item,
checked: false,
}));
}
} else {
if (showTableData.value.filter((item) => item.checked).length == showTableData.value.length) {
checkNameChecked.value = true;
} else {
checkNameChecked.value = false;
}
}
}
</script>
<style lang="less" scoped>
.floderTitle {
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
cursor: pointer;
}
::v-deep .vben-basic-table-header__toolbar {
display: flex !important;
justify-content: flex-start !important;
}
.floderOtherButton {
position: absolute;
right: 0px;
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
::v-deep .floderTitle .ant-btn {
padding-left: 0px !important;
padding-right: 0px !important;
}
::v-deep .floderTitle .ant-btn-text {
padding-left: 0px !important;
padding-right: 0px !important;
}
::v-deep .ant-table-row-expand-icon {
display: none !important;
}
::v-deep .ant-table-row-expand-icon-collapsed {
display: none !important;
}
.storeDivsAllChoose {
width: 100%;
height: 40px;
margin: 0px 0px 16px 16px;
padding-left: 16px;
background: #ffffff;
display: flex;
align-items: center;
justify-content: flex-start;
}
.storeDivs {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 566px;
background: #ffffff;
margin: 0px 16px 16px 16px;
// gap: 20px;
.storeDiv {
position: relative;
width: 150px;
height: 120px;
outline: 1px solid #000000;
margin: 16px;
display: flex;
align-items: center;
justify-content: center;
}
}
::v-deep .vben-basic-table {
height: fit-content !important;
}
</style>

@ -47,7 +47,7 @@ export const searchFormSchema: FormSchema[] = [
field: 'label',
label: '标签',
component: 'Select',
colProps: { span: 8 },
colProps: { span: 6 },
componentProps: {
mode: 'multiple',
options: [
@ -61,19 +61,19 @@ export const searchFormSchema: FormSchema[] = [
field: 'equipmentName',
label: '设备名称',
component: 'Input',
colProps: { span: 5 },
colProps: { span: 6 },
},
{
field: '[startTime, endTime]',
label: '时间选择',
component: 'RangePicker',
colProps: { span: 6 },
colProps: { span: 6},
},
{
field: 'name',
label: '文件名称',
component: 'Input',
colProps: { span: 5 },
colProps: { span: 6 },
},
];
export const formGroupSchema: FormSchema[] = [

@ -1,9 +1,14 @@
<template>
<div :id="mapId" class="map"> </div>
<div :id="mapId" class="map">
<div class="mapInfo">
<span> {{ props.nowPreviewRecord.lat }}°N</span>
<span> {{ props.nowPreviewRecord.lng }}°E</span>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted, onUnmounted, defineEmits, computed } from 'vue';
import { defineProps, ref, onMounted, onUnmounted, defineEmits, watch } from 'vue';
import { v4 as uuidv4 } from 'uuid';
import mapboxgl, { Map } from 'mapbox-gl';
import { MapboxConfig, MapboxDefaultStyle } from '@/components/MapboxMaps/src/config';
@ -15,9 +20,10 @@
MinusOutlined,
HeatMapOutlined,
} from '@ant-design/icons-vue';
import { cloneDeep } from 'lodash-es';
const props = defineProps(['nowPreviewRecord']);
const emits = defineEmits(['getMap']);
const props = defineProps(['nowPreviewRecord', 'previewRecordList', 'hideOrShowTextboxFlag']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
const mapId = `modal-map-${uuidv4()}`;
const networkType = ref('WAN');
@ -54,8 +60,7 @@
source: 'raster-tiles',
},
];
return new mapboxgl.Map({
map = new mapboxgl.Map({
container: mapId,
language: 'zh-cmn',
projection: 'equirectangular', // wgs84
@ -69,19 +74,133 @@
minZoom: 8,
pitch: 0,
zoom: 14,
center: [117.984425, 35.270654],
center: [parseFloat(props.nowPreviewRecord.lng), parseFloat(props.nowPreviewRecord.lat)],
});
setImage();
};
watch(
() => props.hideOrShowTextboxFlag,
(newValue) => {
if (newValue) {
props.previewRecordList.forEach((item) => {
if (!map.getLayer(item.id)) {
const coordinates = [parseFloat(item.lng), parseFloat(item.lat)];
//
map.addLayer({
id: item.id,
type: 'symbol',
source: {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: coordinates,
},
},
],
},
},
layout: {
'icon-image': item.id,
'icon-size': 40 / item.width,
//
'icon-allow-overlap': true, //
'icon-ignore-placement': true, //
visibility: 'visible', //
},
});
}
});
} else {
let filters = cloneDeep(props.previewRecordList).filter(
(item) => item.id != props.nowPreviewRecord.id,
);
filters.forEach((fl) => {
if (map.getLayer(fl.id)) {
map.removeLayer(fl.id);
}
if (map.getSource(fl.id)) {
map.removeSource(fl.id);
}
});
}
},
);
function setImage() {
props.previewRecordList.forEach((item) => {
map.on('style.load', () => {
map.loadImage(item.url, (error, image) => {
if (error) {
console.error('加载图片失败:', error.message);
return;
}
if (!map.hasImage(item.id)) {
map.addImage(item.id, image);
}
const coordinates = [parseFloat(item.lng), parseFloat(item.lat)];
if (!map.getLayer(item.id)) {
//
map.addLayer({
id: item.id,
type: 'symbol',
source: {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: coordinates,
},
},
],
},
},
layout: {
'icon-image': item.id,
'icon-size': 40 / item.width,
//
'icon-allow-overlap': true, //
'icon-ignore-placement': true, //
visibility: 'visible', //
},
});
map.on('click', item.id, function (e) {
clickHandler(item);
});
}
});
});
});
}
onMounted(() => {
mapboxgl.accessToken = MapboxConfig.ACCESS_TOKEN;
//
map = initMap();
initMap();
});
onUnmounted(() => {
map && map.remove();
props.previewRecordList.forEach((item) => {
map.off('click', item.id, function (e) {
clickHandler(item);
});
});
});
//
function clickHandler(item) {
emit('chooseNowPreviewRecord', item);
}
</script>
<style lang="scss" scoped>
@ -91,6 +210,30 @@
height: 100%;
}
.mapInfo {
position: absolute;
width: 100%;
height: 30px;
bottom: 0px;
left: 0px;
background: #3d3f3aaa;
z-index: 1000;
display: flex;
justify-content: flex-start;
span {
height: 100%;
width: 30%;
color: #ffffff;
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 15px;
margin-left: 10px;
}
}
::v-deep .mapboxgl-ctrl {
display: none !important;
}

@ -1,7 +1,7 @@
export { default as PreviewImage } from './previewImage.vue';
export { default as PreviewCanvas } from './previewCanvas.vue';
export { default as PreviewVideo } from './previewVideo.vue';
export { default as PreviewInformation } from './previewInformation.vue';
export { default as PreviewImageInformation } from './previewImageInformation.vue';
export { default as PreviewVideoInformation } from './previewVideoInformation.vue';
export { default as MonitorHK } from './video/monitorHK.vue';
export { default as MonitorLC } from './video/monitorLC.vue';
export { default as MonitorQX } from './video/monitorQX.vue';

@ -18,18 +18,13 @@
<div class="mainBody">
<div class="imgOrVideo">
<!-- 图片 -->
<div class="image" v-if="props.nowPreviewRecord.type == 'img'">
<!-- <PreviewCanvas
:nowPreviewRecord="props.nowPreviewRecord"
:previewRecordList="props.previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
@reloadTable="reloadTable"
/> -->
<div class="imageDiv" v-if="props.nowPreviewRecord.type == 'img'">
<PreviewImage
:nowPreviewRecord="props.nowPreviewRecord"
:previewRecordList="props.previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
@reloadTable="reloadTable"
@setHideOrShowTextboxFlag="setHideOrShowTextboxFlag"
/>
</div>
<!-- VR全景 -->
@ -37,12 +32,31 @@
<PanoViewer />
</div> -->
<!-- 视频 -->
<div class="video" v-if="props.nowPreviewRecord.type == 'video'">
<PreviewVideo :nowPreviewRecord="props.nowPreviewRecord" />
<div class="videoDiv" v-if="props.nowPreviewRecord.type == 'video'">
<PreviewVideo
:nowPreviewRecord="props.nowPreviewRecord"
:previewRecordList="props.previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
@reloadTable="reloadTable"
/>
</div>
</div>
<div class="information">
<PreviewInformation :nowPreviewRecord="props.nowPreviewRecord" @reloadTable="reloadTable" />
<PreviewImageInformation
v-if="props.nowPreviewRecord.type == 'img'"
:nowPreviewRecord="props.nowPreviewRecord"
:previewRecordList="props.previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
@reloadTable="reloadTable"
:hideOrShowTextboxFlag="hideOrShowTextboxFlag"
/>
<PreviewVideoInformation
v-if="props.nowPreviewRecord.type == 'video'"
:nowPreviewRecord="props.nowPreviewRecord"
:previewRecordList="props.previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
@reloadTable="reloadTable"
/>
</div>
</div>
</div>
@ -51,15 +65,20 @@
import { ref } from 'vue';
import { CloseOutlined } from '@ant-design/icons-vue';
import { PreviewImage } from './preview';
import { PreviewCanvas } from './preview';
import { PreviewVideo } from './preview';
import { PreviewInformation } from './preview';
import { PreviewImageInformation } from './preview';
import { PreviewVideoInformation } from './preview';
import { PanoViewer } from './preview';
const props = defineProps(['nowPreviewRecord', 'previewRecordList']);
const emit = defineEmits(['closeModal', 'chooseNowPreviewRecord', 'reloadTable']);
console.log(props.nowPreviewRecord);
console.log(props.previewRecordList);
const hideOrShowTextboxFlag = ref(true);
function setHideOrShowTextboxFlag(value) {
hideOrShowTextboxFlag.value = value;
}
//
function chooseNowPreviewRecord(value) {
@ -71,13 +90,14 @@
}
//
function closeModal() {
document.body.style.cursor = 'default';
emit('closeModal');
}
</script>
<style lang="less">
.modal {
width: 100%;
height: 1020px;
height: 920px;
//
-webkit-user-select: none; /* Safari */
@ -109,7 +129,7 @@
}
.mainBody {
width: 100%;
height: 1020px;
height: 920px;
display: flex;
position: absolute;
top: 0px;
@ -117,10 +137,10 @@
.imgOrVideo {
width: 80%;
height: 1020px;
height: 920px;
background: #101010;
.image {
.imageDiv {
// display: flex;
// align-items: center;
position: relative;
@ -128,6 +148,13 @@
width: 100%;
height: 100%;
}
.videoDiv {
position: relative;
justify-content: center;
width: 100%;
height: 100%;
}
}
.information {

@ -1,583 +0,0 @@
<template>
<div class="image">
<div class="canvas">
<canvas
width="1300"
height="800"
id="canvas"
:style="{
transform: `scale(${scale}) rotate(${rotationAngle}deg)`,
transition: 'transform 0.2s',
}"
></canvas>
</div>
<div class="bottom">
<div class="buttonList">
<!-- 放大 -->
<div class="button"> <ZoomInOutlined @click="zoomIn" /> </div>
<!-- 缩小 -->
<div class="button"> <ZoomOutOutlined @click="zoomOut" /> </div>
<!-- 顺时针旋转 -->
<div class="button"> <RotateRightOutlined @click="rotateClockwise" /> </div>
<!-- 逆时针旋转 -->
<div class="button"> <RotateLeftOutlined @click="rotateCounterClockwise" /> </div>
|
<!-- 刷新 -->
<div class="button"> <RedoOutlined @click="refreshCanvas" /> </div>
<!-- 涂鸦 -->
<div class="tipChoose" v-if="graffitiFlag">
<n-color-picker size="small" :modes="['rgb']" v-model:value="graffitiColor" />
<a-slider v-model:value="graffitiWidth" :min="1" :max="10" />
</div>
<div class="button" :style="{ background: graffitiFlag ? 'blue' : '' }">
<HighlightOutlined
:style="{
color: graffitiColor,
}"
@click="
graffitiFlag = !graffitiFlag;
rectFlag = false;
textboxFlag = false;
"
/>
</div>
<!-- 标注 -->
<div class="tipChoose" v-if="rectFlag">
<n-color-picker size="small" :modes="['rgb']" v-model:value="rectColor" />
</div>
<div class="button" :style="{ background: rectFlag ? 'blue' : '' }">
<BorderOutlined
@click="
graffitiFlag = false;
rectFlag = !rectFlag;
textboxFlag = false;
"
/>
</div>
<!-- 文字 -->
<div class="tipChoose" v-if="textboxFlag">
<n-color-picker size="small" :modes="['rgb']" v-model:value="textboxColor" />
<a-slider v-model:value="textboxFontSize" :min="10" :max="50" />
</div>
<div class="button" :style="{ background: textboxFlag ? 'blue' : '' }">
<FontSizeOutlined
@click="
graffitiFlag = false;
rectFlag = false;
textboxFlag = !textboxFlag;
"
/>
</div>
<!-- 保存 -->
<div class="button"> <CheckOutlined @click="checkCanvas" /> </div>
<!-- 删除 -->
<div class="button"> <CloseOutlined @click="deleteCanvas" /> </div>
<!-- 隐藏or显示涂鸦和标签 -->
<div class="button">
<EyeOutlined @click="hideOrShowGraffiti(false)" v-if="hideOrShowGraffitiFlag" />
<EyeInvisibleOutlined @click="hideOrShowGraffiti(true)" v-if="!hideOrShowGraffitiFlag" />
<div style="position: absolute; bottom: 0px; right: 0px; font-size: 10px">涂鸦</div>
</div>
<div class="button">
<EyeOutlined @click="hideOrShowTextbox(false)" v-if="hideOrShowTextboxFlag" />
<EyeInvisibleOutlined @click="hideOrShowTextbox(true)" v-if="!hideOrShowTextboxFlag" />
<div style="position: absolute; bottom: 0px; right: 0px; font-size: 10px">标签</div>
</div>
|
<!-- 复制到剪贴板 -->
<div class="button">
<ExportOutlined @click="copyToClipboard(props.nowPreviewRecord.url)" />
</div>
<!-- 下载 -->
<div class="button">
<DownloadOutlined @click="fetchAndDownloadImage(props.nowPreviewRecord.url)" />
</div>
</div>
<div class="imageList">
<div v-for="li in props.previewRecordList" :key="li.id" @click="chooseNowPreviewRecord(li)">
<div
:class="li.id == props.nowPreviewRecord.id ? 'bottom_div_choose' : 'bottom_div'"
v-if="li.type == 'img'"
>
<img :src="li.url" :width="60" :height="40" />
</div>
</div>
</div>
</div>
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch, computed } from 'vue';
import { fabric } from 'fabric';
import {
CloseOutlined,
RightOutlined,
LeftOutlined,
ZoomOutOutlined,
ZoomInOutlined,
RotateLeftOutlined,
RotateRightOutlined,
RedoOutlined,
FileImageOutlined,
ExportOutlined,
DownloadOutlined,
DeleteOutlined,
HighlightOutlined,
CheckOutlined,
FontSizeOutlined,
BorderOutlined,
EyeOutlined,
EyeInvisibleOutlined,
} from '@ant-design/icons-vue';
import { useMessage } from '@/hooks/web/useMessage';
import { json } from 'stream/consumers';
const { createConfirm, createMessage } = useMessage();
const props = defineProps(['nowPreviewRecord', 'previewRecordList']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
//
function clickLeftOrRightButton(direction) {
const list = props.previewRecordList.filter((item) => item.type == 'img');
for (let index = 0; index < list.length; index++) {
if (list[index].id == props.nowPreviewRecord.id) {
if (direction == 'left') {
if (index == 0) {
chooseNowPreviewRecord(list[list.length - 1]);
} else {
chooseNowPreviewRecord(list[index - 1]);
}
}
if (direction == 'right') {
if (index == list.length - 1) {
chooseNowPreviewRecord(list[0]);
} else {
chooseNowPreviewRecord(list[index + 1]);
}
}
}
}
}
//
function chooseNowPreviewRecord(value) {
emit('chooseNowPreviewRecord', value);
//
scale.value = 1;
rotationAngle.value = 0;
setBackgroudUrl(value);
}
//
function reloadTable() {
emit('reloadTable');
}
//
const nowCanvasJson = ref(props.nowPreviewRecord.canvasJson);
let canvas: any = null;
//
function setBackgroudUrl(value) {
nowCanvasJson.value = value.canvasJson;
canvas.loadFromJSON(value.canvasJson);
fabric.Image.fromURL(value.url, (img) => {
//
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
});
});
}
// ---------------------------------------------------------------
const graffitiFlag = ref(false);
const graffitiColor = ref('#ffffff');
const graffitiWidth = ref(1);
watch(
() => (graffitiFlag.value, graffitiColor.value, graffitiWidth.value),
() => {
setGraffiti();
},
{
deep: true,
},
);
//
function setGraffiti() {
scale.value = 1;
rotationAngle.value = 0;
//
canvas.isDrawingMode = graffitiFlag.value;
//
canvas.freeDrawingBrush.color = graffitiColor.value;
//
canvas.freeDrawingBrush.width = graffitiWidth.value;
}
// ---------------------------------------------------------------
const textboxFlag = ref(false);
const textboxColor = ref('#ffffff');
const textboxFontSize = ref(20);
watch(
() => textboxFlag.value,
() => {
if (textboxFlag.value) {
setTextbox();
}
},
{
deep: true,
},
);
watch(
() => (textboxColor.value, textboxFontSize.value),
() => {
setTextboxStyle();
},
{
deep: true,
},
);
//
function setTextbox() {
scale.value = 1;
rotationAngle.value = 0;
addClickEvent();
}
//
function setTextboxStyle() {
const activeObj = canvas.getActiveObject();
if (activeObj) {
console.log(activeObj);
activeObj.fill = textboxColor.value;
activeObj.fontSize = textboxFontSize.value;
canvas.setActiveObject(activeObj);
}
}
// ---------------------------------------------------------------
const rectFlag = ref(false);
const rectColor = ref('#ffffff');
watch(
() => rectFlag.value,
() => {
if (rectFlag.value) {
setRect();
}
},
{
deep: true,
},
);
watch(
() => rectColor.value,
() => {
setRectStyle();
},
{
deep: true,
},
);
//
function setRect() {
scale.value = 1;
rotationAngle.value = 0;
addClickEvent();
}
//
function setRectStyle() {
const activeObj = canvas.getActiveObject();
if (activeObj) {
activeObj.stroke = rectColor.value;
canvas.setActiveObject(activeObj);
}
}
//
function addClickEvent() {
canvas.on('mouse:down', (options) => {
if (textboxFlag.value) {
//
const textbox = new fabric.Textbox('标注', {
width: 200,
fill: textboxColor.value,
fontSize: textboxFontSize.value,
top: options.absolutePointer.y,
left: options.absolutePointer.x,
});
canvas.add(textbox);
removeClickEvent();
}
if (rectFlag.value) {
//
const rect = new fabric.Rect({
top: options.absolutePointer.y,
left: options.absolutePointer.x,
fill: '#ffffff00',
stroke: rectColor.value,
width: 100,
height: 100,
});
canvas.add(rect);
removeClickEvent();
}
});
}
//
function removeClickEvent() {
canvas.off('mouse:down');
}
// -----------------------------------
const scale = ref(1);
//
function zoomIn() {
if (scale.value < 3) {
// 3
scale.value += 0.1;
}
}
//
function zoomOut() {
if (scale.value > 0.5) {
// 0.5
scale.value -= 0.1;
}
}
// -----------------------------------
const rotationAngle = ref(0);
//
function rotateClockwise() {
rotationAngle.value += 90; // 90
}
//
function rotateCounterClockwise() {
rotationAngle.value -= 90; // -90
}
// -----------------------------------
function checkCanvas() {
nowCanvasJson.value = JSON.stringify(canvas);
props.nowPreviewRecord.canvasJson = JSON.stringify(canvas);
textboxFlag.value = false;
graffitiFlag.value = false;
rectFlag.value = false;
console.log(JSON.stringify(canvas));
createMessage.success('保存成功!');
}
//
function deleteCanvas() {
const activeObj = canvas.getActiveObject();
canvas.remove(activeObj);
textboxFlag.value = false;
graffitiFlag.value = false;
rectFlag.value = false;
}
//
function refreshCanvas() {
scale.value = 1;
rotationAngle.value = 0;
setBackgroudUrl(props.nowPreviewRecord);
}
// -----------------------------------
const copyToClipboard = async (url) => {
try {
await navigator.clipboard.writeText(url);
createMessage.success('图片链接已复制到剪贴板');
} catch (err) {
createMessage.error('无法复制图片链接');
}
};
//
async function fetchAndDownloadImage(url) {
try {
const response = await fetch(url, {
mode: 'cors',
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
const urlObject = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = urlObject;
link.download = props.nowPreviewRecord.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(urlObject);
} catch (error) {
console.error('Error downloading image:', error);
}
}
//
const hideOrShowGraffitiFlag = ref(true);
function hideOrShowGraffiti(value) {
hideOrShowGraffitiFlag.value = value;
let json = JSON.parse(nowCanvasJson.value);
json.objects.forEach((item) => {
if (item.type === 'path') {
item.visible = value;
}
});
nowCanvasJson.value = JSON.stringify(json);
canvas.loadFromJSON(nowCanvasJson.value);
}
//
const hideOrShowTextboxFlag = ref(true);
function hideOrShowTextbox(value) {
hideOrShowTextboxFlag.value = value;
let json = JSON.parse(nowCanvasJson.value);
json.objects.forEach((item) => {
if (item.type === 'textbox' || item.type === 'rect') {
item.visible = value;
}
});
nowCanvasJson.value = JSON.stringify(json);
canvas.loadFromJSON(nowCanvasJson.value);
}
onMounted(() => {
canvas = new fabric.Canvas('canvas');
//
setBackgroudUrl(props.nowPreviewRecord);
// json
if (!nowCanvasJson.value) {
nowCanvasJson.value = JSON.stringify(canvas);
}
//
setGraffiti();
});
</script>
<style lang="less" scoped>
.image {
position: relative;
width: 100%;
height: 900px;
display: block;
}
.canvas {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 800px;
}
.bottom {
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
height: 100px;
background: #1c1c1c;
display: block;
.buttonList {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 40px;
.button {
position: relative;
padding-left: 10px;
padding-right: 10px;
color: #ffffff;
font-size: 22px;
}
}
.imageList {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 60px;
.bottom_div {
padding: 5px;
margin-left: 5px;
margin-right: 5px;
}
.bottom_div_choose {
padding: 4px;
border: 1px solid yellow;
margin-left: 5px;
margin-right: 5px;
}
}
}
.leftButton {
position: absolute;
left: 40px;
top: 45%;
z-index: 1000;
background: #9c9c9c;
border-radius: 50px;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.rightButton {
position: absolute;
right: 40px;
top: 45%;
z-index: 1000;
background: #9c9c9c;
border-radius: 50px;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.eyeButton {
position: absolute;
right: 40px;
top: 45px;
background: #ffffff;
width: 50px;
height: 100px;
}
.tipChoose {
position: absolute;
background: #1c1c1c;
color: #ffffff;
padding: 5px;
top: 35px;
width: 100px;
}
</style>

@ -1,6 +1,6 @@
<template>
<div class="image">
<div class="canvas" @mouseleave="document.body.style.cursor = 'auto'">
<div class="previewImage">
<div id="imageDiv" class="imageDiv">
<div
ref="mouseCanvasRef"
@mousedown="onMouseDown"
@ -43,12 +43,18 @@
<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; color: green"
@click="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CloseOutlined
@ -62,7 +68,7 @@
<DeleteOutlined
style="margin-right: 10px"
@click="
graffitis.splice(index, 1);
deleteGraffiti(index, rect.text);
nowGraffiti = -1;
"
/>
@ -243,34 +249,135 @@
/>
</div>
</div>
<!-- 全屏 -->
<div class="expandButton" @click="clickExpandButton">
<ExpandOutlined v-if="!fullscreenFlag" style="color: #3c3c3c; font-size: 25px" />
<CompressOutlined v-if="fullscreenFlag" style="color: #3c3c3c; font-size: 25px" />
</div>
</div>
<div class="bottom">
<div class="buttonList">
<!-- 放大 -->
<div class="button"> <ZoomInOutlined @click="zoomIn" /> </div>
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>放大</span>
</template>
<ZoomInOutlined @click="zoomIn" />
</a-tooltip>
</div>
<!-- 缩小 -->
<div class="button"> <ZoomOutOutlined @click="zoomOut" /> </div>
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>缩小</span>
</template>
<ZoomOutOutlined @click="zoomOut" />
</a-tooltip>
</div>
<!-- 顺时针旋转 -->
<div class="button"> <RotateRightOutlined @click="rotateClockwise" /> </div>
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>顺时针旋转</span>
</template>
<RotateRightOutlined @click="rotateClockwise" />
</a-tooltip>
</div>
<!-- 逆时针旋转 -->
<div class="button"> <RotateLeftOutlined @click="rotateCounterClockwise" /> </div>
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>逆时针旋转</span>
</template>
<RotateLeftOutlined @click="rotateCounterClockwise" />
</a-tooltip>
</div>
<!-- 刷新 -->
<div class="button"> <RedoOutlined @click="refresh" /> </div>
|
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>刷新</span>
</template>
<RedoOutlined @click="refresh" />
</a-tooltip>
</div>
<span style="color: #ffffff; margin-left: 5px; margin-right: 5px">|</span>
<!-- 全屏 -->
<!-- <div class="button"> <RedoOutlined @click="refresh" /> </div> -->
|
<!-- 对比 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>对比</span>
</template>
<BorderHorizontalOutlined @click="c" />
</a-tooltip>
</div>
<!-- 加载到地图上 -->
<div class="button2" @click="funAddOrRemoveToMap">
<a-tooltip placement="top">
<template #title>
<span>
{{ props.nowPreviewRecord.addOrRemoveToMap ? '在地图上取消加载' : '在地图上加载' }}
</span>
</template>
<svg
v-if="props.nowPreviewRecord.addOrRemoveToMap"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
width="25"
height="25"
>
<path
d="M19 5v11.17l2 2V5c0-1.1-.9-2-2-2H5.83l2 2H19zM2.81 2.81L1.39 4.22L3 5.83V19c0 1.1.9 2 2 2h13.17l1.61 1.61l1.41-1.41L2.81 2.81zM5 19V7.83l7.07 7.07l-.82 1.1L9 13l-3 4h8.17l2 2H5z"
fill="#ffffff"
></path>
</svg>
<svg
v-if="!props.nowPreviewRecord.addOrRemoveToMap"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
width="25"
height="25"
>
<path
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14L6 17h12l-3.86-5.14z"
fill="#ffffff"
></path>
</svg>
</a-tooltip>
</div>
<!-- 复制到剪贴板 -->
<div class="button">
<ExportOutlined @click="copyToClipboard(props.nowPreviewRecord.url)" />
<a-tooltip placement="top">
<template #title>
<span>图片链接复制到剪贴板</span>
</template>
<ExportOutlined @click="copyToClipboard(props.nowPreviewRecord.url)" />
</a-tooltip>
</div>
<!-- 下载 -->
<div class="button">
<DownloadOutlined @click="fetchAndDownloadImage(props.nowPreviewRecord.url)" />
<a-tooltip placement="top">
<template #title>
<span>下载</span>
</template>
<DownloadOutlined @click="fetchAndDownloadImage(props.nowPreviewRecord.url)" />
</a-tooltip>
</div>
<!-- 删除 -->
<div class="button"> <DeleteOutlined @click="deleteCanvas" /> </div>
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>删除</span>
</template>
<DeleteOutlined @click="deleteImage" />
</a-tooltip>
</div>
</div>
<div class="imageList">
<div v-for="li in props.previewRecordList" :key="li.id" @click="chooseNowPreviewRecord(li)">
@ -293,11 +400,7 @@
</div>
<!-- 涂鸦颜色选择-提示 -->
<div
class="graffitiButton"
@click="setGraffiti"
:style="graffitiFlag ? 'outline: 2px solid #2B85E4' : ''"
>
<div @click="setGraffiti" :class="graffitiFlag ? 'graffitiButton_choose' : 'graffitiButton'">
<a-popover placement="left">
<template #content>
<div style="display: flex; gap: 5px">
@ -323,42 +426,64 @@
<!-- 隐藏or显示涂鸦和标签 -->
<div class="showTextboxClass">
<div class="button">
<EyeOutlined @click="hideOrShowGraffiti(false)" v-if="hideOrShowGraffitiFlag" />
<EyeInvisibleOutlined @click="hideOrShowGraffiti(true)" v-if="!hideOrShowGraffitiFlag" />
<div
style="
position: absolute;
bottom: 0px;
right: 0px;
font-size: 10px;
color: #000000;
pointer-events: none;
"
>
涂鸦
</div>
<a-tooltip placement="left">
<template #title>
<span>{{ hideOrShowGraffitiFlag ? '点击隐藏涂鸦信息' : '点击显示涂鸦信息' }}</span>
</template>
<EyeOutlined @click="hideOrShowGraffiti(false)" v-if="hideOrShowGraffitiFlag" />
<EyeInvisibleOutlined @click="hideOrShowGraffiti(true)" v-if="!hideOrShowGraffitiFlag" />
<div
style="
position: absolute;
bottom: 0px;
right: 0px;
font-size: 10px;
color: #000000;
pointer-events: none;
"
>
涂鸦
</div>
</a-tooltip>
</div>
<div class="button">
<EyeOutlined @click="hideOrShowTextbox(false)" v-if="hideOrShowTextboxFlag" />
<EyeInvisibleOutlined @click="hideOrShowTextbox(true)" v-if="!hideOrShowTextboxFlag" />
<div
style="
position: absolute;
bottom: 0px;
right: 0px;
font-size: 10px;
color: #000000;
pointer-events: none;
"
>
标注
</div>
<a-tooltip placement="left">
<template #title>
<span>
{{
hideOrShowTextboxFlag
? '点击隐藏地图上的其他标注信息'
: '点击显示地图上的其他标注信息'
}}
</span>
</template>
<EyeOutlined @click="hideOrShowTextbox(false)" v-if="hideOrShowTextboxFlag" />
<EyeInvisibleOutlined @click="hideOrShowTextbox(true)" v-if="!hideOrShowTextboxFlag" />
<div
style="
position: absolute;
bottom: 0px;
right: 0px;
font-size: 10px;
color: #000000;
pointer-events: none;
"
>
标注
</div>
</a-tooltip>
</div>
</div>
<!-- 退出涂鸦 -->
<div class="escTip" v-if="graffitiFlag">
<div class="whiteEsc">Esc</div>
<div class="blackTip">退出涂鸦</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch, computed } from 'vue';
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue';
import {
CloseOutlined,
RightOutlined,
@ -376,6 +501,10 @@
EditOutlined,
FontColorsOutlined,
RedoOutlined,
FileImageOutlined,
BorderHorizontalOutlined,
ExpandOutlined,
CompressOutlined,
} from '@ant-design/icons-vue';
import { useMessage } from '@/hooks/web/useMessage';
import { cloneDeep } from 'lodash-es';
@ -383,7 +512,7 @@
const { createConfirm, createMessage } = useMessage();
const props = defineProps(['nowPreviewRecord', 'previewRecordList']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable', 'setHideOrShowTextboxFlag']);
//
const getImageWidthAndHeight = computed(() => {
@ -477,7 +606,7 @@
}
//
function deleteCanvas() {}
function deleteImage() {}
// -----------------------------------
const copyToClipboard = async (url) => {
@ -489,6 +618,16 @@
}
};
//
function funAddOrRemoveToMap() {
props.nowPreviewRecord.addOrRemoveToMap = !props.nowPreviewRecord.addOrRemoveToMap;
if (props.nowPreviewRecord.addOrRemoveToMap) {
createMessage.success('在地图上加载成功');
} else {
createMessage.success('在地图上取消加载成功');
}
}
//
async function fetchAndDownloadImage(url) {
try {
@ -522,6 +661,7 @@
const hideOrShowTextboxFlag = ref(true);
function hideOrShowTextbox(value) {
hideOrShowTextboxFlag.value = value;
emit('setHideOrShowTextboxFlag', value);
}
// ---------------------------------------------------------------
@ -529,7 +669,7 @@
const graffitiColor = ref('#E23C39');
const nowGraffiti = ref(-1);
const nowMouseGraffiti = ref(0);
//
function setGraffiti() {
graffitiFlag.value = !graffitiFlag.value;
if (graffitiFlag.value) {
@ -564,14 +704,33 @@
);
watch(
() => graffitis.value,
() => {
props.nowPreviewRecord.graffitiJson = JSON.stringify(graffitis.value);
},
() => {},
{
deep: true,
},
);
//
function addGraffiti() {
props.nowPreviewRecord.graffitiJson = JSON.stringify(graffitis.value);
graffitis.value.forEach((item) => {
if (!props.nowPreviewRecord.label.includes(item.text)) {
if (item.text) {
props.nowPreviewRecord.label.push(item.text);
}
}
});
}
//
function deleteGraffiti(index = undefined, value = undefined) {
graffitis.value.splice(index, 1);
// or
props.nowPreviewRecord.graffitiJson = JSON.stringify(graffitis.value);
if (!graffitis.value.some((item) => item.text == value)) {
props.nowPreviewRecord.label = props.nowPreviewRecord.label.filter((la) => la != value);
}
}
const mouseCanvasRef = ref();
//
function onMouseDown(e) {
@ -741,23 +900,61 @@
}
}
onMounted(() => {});
//
const addEventListener = () => {
window.addEventListener('keydown', handleKeydown);
};
//
const removeEventListener = () => {
window.removeEventListener('keydown', handleKeydown);
};
//
const handleKeydown = (event: KeyboardEvent) => {
if (event.code === 'Escape' && graffitiFlag.value) {
setGraffiti();
}
};
//
const fullscreenFlag = ref(false);
function clickExpandButton() {
const mapContainer = document.getElementById('imageDiv');
if (mapContainer) {
if (!document.fullscreenElement) {
mapContainer.requestFullscreen();
fullscreenFlag.value = true;
} else {
document.exitFullscreen();
fullscreenFlag.value = false;
}
}
}
//
onMounted(() => {
addEventListener();
});
//
onBeforeUnmount(() => {
removeEventListener();
});
</script>
<style lang="less" scoped>
.image {
.previewImage {
position: relative;
width: 100%;
height: 900px;
height: 100%;
display: block;
}
.canvas {
.imageDiv {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 920px;
height: 820px;
background: #101010;
}
.bottom {
@ -781,9 +978,22 @@
position: relative;
padding-left: 10px;
padding-right: 10px;
margin-left: 6px;
margin-right: 6px;
color: #ffffff;
font-size: 22px;
}
.button2 {
padding-top: 3px;
padding-left: 10px;
padding-right: 10px;
margin-left: 6px;
margin-right: 6px;
display: flex;
align-items: center;
justify-content: center;
}
}
.imageList {
@ -854,6 +1064,26 @@
justify-content: center;
}
.graffitiButton_choose {
background: linear-gradient(to bottom left, #2b85e4 10px, transparent 1px), #ffffff;
background-size:
100% 100%,
auto;
position: absolute;
right: 40px;
top: 5%;
z-index: 200;
width: 30px;
height: 30px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
outline: 2px solid #2b85e4;
}
.popoverClass {
width: 18px;
height: 18px;
@ -883,11 +1113,58 @@
font-size: 22px;
width: 30px;
height: 30px;
border: 1px solid #000000;
display: flex;
align-items: center;
justify-content: center;
}
}
//
.expandButton {
position: absolute;
right: 30px;
bottom: 15%;
z-index: 500;
background: #ffffff;
border-radius: 5px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.escTip {
position: absolute;
left: 0%;
bottom: 100px;
width: 120px;
height: 40px;
background: #9c9c9c77;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: flex-start;
.whiteEsc {
height: 30px;
background: #ffffff;
font-size: 16px;
padding: 10px;
border-radius: 5px;
display: flex;
margin-left: 10px;
align-items: center;
}
.blackTip {
display: flex;
margin-left: 5px;
align-items: center;
color: #ffffff;
}
}
</style>

@ -0,0 +1,459 @@
<template>
<div>
<div class="title"> 详细信息 </div>
<div class="infoDiv">
<a-row>
<a-col :span="7">
<span class="infotitle">图片名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue_name" v-if="editNameFlag">
{{ props.nowPreviewRecord.name }}
<EditOutlined style="font-size: 20px; color: #07aaed" @click="editNameChange" />
</span>
<span class="infovalue_name" v-if="!editNameFlag">
<a-input v-model:value="editName" size="small" />
<CheckOutlined
style="margin-left: 10px; color: green"
@click="pressEnterNameFunction"
/>
<CloseOutlined style="margin-left: 10px; color: red" @click="editNameBlur" />
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片类型</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.imgtype }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">任务名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.taskname }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">航线名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.airlineName }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片分辨率</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.width + '*' + props.nowPreviewRecord.height }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片大小</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.size }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄飞机</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographFeiji }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄负载</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographNumber }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄人员</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographMan }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄时间</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographTime }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">标签</span>
</a-col>
<a-col :span="17">
<span class="infovalue_graffitiNum">
<a-tag color="success" v-for="la in props.nowPreviewRecord.label" :key="la">
{{ la }}
</a-tag>
<PlusSquareOutlined style="font-size: 20px; color: #07aaed" @click="addLabelChange" />
</span>
<a-modal
title="标签设置"
:open="addLabelFlag"
:mask="false"
:maskClosable="false"
:closable="false"
:footer="null"
>
<div style="display: block">
<div
style="display: flex; flex-wrap: wrap; gap: 5px; width: 96%; margin: 10px"
v-if="props.nowPreviewRecord.label.length > 0"
>
已添加的标签
<div
v-for="la in props.nowPreviewRecord.label"
:key="la"
style="border: 1px solid #595959; border-radius: 3px"
>
<span style="margin-left: 5px">{{ la }}</span>
<CloseOutlined
style="margin-left: 5px; margin-right: 5px"
@click="deleteLabel(la)"
/>
</div>
</div>
<div style="display: inline-flex; gap: 5px; width: 96%; margin: 10px">
<a-input v-model:value="newLabelName" size="small" placeholder="请输入标签" />
<a-button type="primary" @click="pressEnterLabelFunction"></a-button>
</div>
<div
style="
display: inline-flex;
gap: 5px;
width: 96%;
margin: 5px 10px 20px 10px;
justify-content: flex-end;
"
>
<a-button
@click="
addLabelFlag = false;
newLabelName = '';
"
>
关闭
</a-button>
</div>
</div>
</a-modal>
</a-col>
<a-col :span="7">
<span class="infotitle">涂鸦总数</span>
</a-col>
<a-col
:span="17"
style="
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
"
@mouseenter="showGraffitiNum = true"
@mouseleave="showGraffitiNum = false"
>
<div class="graffitiNum">
<EditOutlined style="color: #ffffff" />
<span>
{{
props.nowPreviewRecord.graffitiJson
? JSON.parse(props.nowPreviewRecord.graffitiJson).length
: 0
}}
</span>
</div>
<div
v-if="showGraffitiNum"
style="position: absolute; top: 36px; left: 5px; width: 60%; z-index: 1000"
:style="{
height: `${options.length * 30}px`,
}"
>
<div v-for="op in options" :key="op.text" style="width: 100%; height: 30px">
<div class="graffitiNum2">
<span>
{{ op.text }}
</span>
<span>
{{ op.num }}
</span>
</div>
</div>
</div>
</a-col>
<a-col :span="7">
<span class="infotitle">照片位置</span>
</a-col>
<a-col :span="17">
<span class="infobutton">
<EnvironmentOutlined
v-if="props.nowPreviewRecord.addOrRemoveToMap"
style="font-size: 20px; color: #07aaed"
@click="flyPoint"
/>
</span>
</a-col>
<a-col :span="24">
<div class="map">
<Map
ref="mapRef"
:nowPreviewRecord="props.nowPreviewRecord"
:previewRecordList="props.previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
:hideOrShowTextboxFlag="props.hideOrShowTextboxFlag"
/>
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import {
EditOutlined,
PlusSquareOutlined,
EnvironmentOutlined,
CheckOutlined,
CloseOutlined,
} from '@ant-design/icons-vue';
import { orgPosGroup } from '@/api/demo/system';
import { Map } from './preview';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage, createConfirm } = useMessage();
const props = defineProps(['nowPreviewRecord', 'previewRecordList', 'hideOrShowTextboxFlag']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
// --------------------------------
const editNameFlag = ref(true);
const editName = ref(props.nowPreviewRecord.name.split('.').slice(0, -1).join('.'));
function editNameChange() {
if (props.nowPreviewRecord.name.split('.').length <= 1) {
editName.value = props.nowPreviewRecord.name;
}
editNameFlag.value = false;
}
//
async function pressEnterNameFunction() {
let newName: any = null;
if (props.nowPreviewRecord.name.split('.').length <= 1) {
newName = editName.value;
} else {
newName = editName.value + '.' + props.nowPreviewRecord.name.split('.').pop();
}
let query = {
id: props.nowPreviewRecord.id,
newName: newName,
};
props.nowPreviewRecord.name = newName;
editNameFlag.value = true;
emit('reloadTable');
return;
//
const data = await orgPosGroup(query);
if (data) {
editNameFlag.value = true;
emit('reloadTable');
return createMessage.success('修改名称成功');
} else {
return createMessage.error('修改名称失败');
}
}
function editNameBlur() {
editNameFlag.value = true;
editName.value = props.nowPreviewRecord.name.split('.').slice(0, -1).join('.');
}
// --------------------------------
const addLabelFlag = ref(false);
const newLabelName = ref('');
function addLabelChange() {
addLabelFlag.value = true;
}
//
async function pressEnterLabelFunction() {
if (!newLabelName.value) {
return;
}
if (!props.nowPreviewRecord.label.includes(newLabelName.value)) {
props.nowPreviewRecord.label.push(newLabelName.value);
let query = {
id: props.nowPreviewRecord.id,
newLabel: props.nowPreviewRecord.label,
};
addLabelFlag.value = true;
chooseNowPreviewRecord({
...props.nowPreviewRecord,
label: props.nowPreviewRecord.label,
});
emit('reloadTable');
newLabelName.value = '';
return;
//
const data = await orgPosGroup(query);
if (data) {
addLabelFlag.value = true;
emit('reloadTable');
return createMessage.success('修改名称成功');
} else {
return createMessage.error('修改名称失败');
}
} else {
return createMessage.error('此标签已存在!');
}
}
//
function deleteLabel(value) {
createConfirm({
iconType: 'info',
title: '提醒',
content: '删除标签【' + value + '】,同标签名的涂鸦标记都会被删除!',
onOk: () => {
props.nowPreviewRecord.label = props.nowPreviewRecord.label.filter(
(item) => item !== value,
);
let json = JSON.parse(props.nowPreviewRecord.graffitiJson);
if (json.some((item) => item.text == value)) {
json = json.filter((item) => item.text !== value);
props.nowPreviewRecord.graffitiJson = JSON.stringify(json);
}
chooseNowPreviewRecord({
...props.nowPreviewRecord,
label: props.nowPreviewRecord.label,
graffitiJson: JSON.stringify(json),
});
emit('reloadTable');
},
onCancel: () => {},
});
}
const showGraffitiNum = ref(false);
const options = computed(() => {
if (props.nowPreviewRecord.graffitiJson) {
const map = {};
// text
JSON.parse(props.nowPreviewRecord.graffitiJson).forEach((item) => {
const key = item.text;
if (map[key]) {
map[key].num += 1;
} else {
map[key] = { text: key, num: 1 };
}
});
// map
return Object.values(map);
} else {
return [];
}
});
//
function chooseNowPreviewRecord(value) {
emit('chooseNowPreviewRecord', value);
}
// --------------------------------
const mapRef = ref();
function flyPoint() {
mapRef.value?.flyToPoint([props.nowPreviewRecord.lng, props.nowPreviewRecord.lat]);
}
</script>
<style lang="less" scoped>
.title {
position: relative;
width: 50%;
height: 60px;
font-size: 15px;
color: white;
display: flex;
align-items: center;
}
.infoDiv {
position: relative;
width: 90%;
height: 480px;
.infotitle {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
margin-left: 10px;
}
.infovalue {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
max-width: 250px;
flex-wrap: wrap;
}
.infovalue_name {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
max-width: 250px;
}
.infovalue_graffitiNum {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: auto;
min-height: 40px;
max-width: 250px;
flex-wrap: wrap;
}
.infobutton {
font-size: 10px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: right;
height: 40px;
}
}
.map {
width: 100%;
height: 240px;
margin-left: 5px;
border-radius: 5px;
}
.graffitiNum {
display: flex;
align-items: center;
justify-content: space-between;
color: #ffffff;
width: 60%;
background: #3c3c3c;
height: 30px;
border-radius: 5px;
padding-left: 10px;
padding-right: 10px;
}
.graffitiNum2 {
display: flex;
align-items: center;
justify-content: space-between;
color: #ffffff;
width: 95%;
background: #1c1c1c;
height: 40px;
padding-left: 10px;
padding-right: 10px;
}
</style>

@ -1,271 +0,0 @@
<template>
<div>
<div class="title"> 详细信息 </div>
<div class="info">
<a-row>
<a-col :span="7">
<span class="infotitle">图片名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue" v-if="editNameFlag">
{{ props.nowPreviewRecord.name }}
<EditOutlined style="font-size: 20px; color: #07aaed" @click="editNameChange" />
</span>
<span class="infovalue" v-if="!editNameFlag">
<a-input
v-model:value="editName"
size="small"
@keypress.enter="pressEnterNameFunction"
@blur="editNameBlur"
/>
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片类型</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.imgtype }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">任务名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.taskname }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">航线名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.airlineName }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片分辨率</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.width + '*' + props.nowPreviewRecord.height }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片大小</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.size }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄飞机</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographFeiji }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄负载</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographNumber }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄人员</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographMan }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄时间</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.photographTime }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">标签</span>
</a-col>
<a-col :span="17">
<span class="infovalue" v-if="addLabelFlag">
<a-tag color="success" v-for="la in props.nowPreviewRecord.label" :key="la">
{{ la }}
</a-tag>
<PlusSquareOutlined style="font-size: 20px; color: #07aaed" @click="addLabelChange" />
</span>
<span class="infovalue" v-if="!addLabelFlag">
<a-tag
color="success"
v-for="la in props.nowPreviewRecord.label"
:key="la"
closable
@close="deleteLabel(la)"
>
{{ la }}
</a-tag>
<a-input
v-model:value="newLabelName"
size="small"
placeholder=""
@keypress.enter="pressEnterLabelFunction"
@blur="addLabelBlur"
/>
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">涂鸦总数</span>
</a-col>
<a-col :span="17">
<span class="infovalue">{{ props.nowPreviewRecord.label }} </span>
</a-col>
<a-col :span="7">
<span class="infotitle">照片位置</span>
</a-col>
<a-col :span="17">
<span class="infobutton">
<EnvironmentOutlined style="font-size: 20px; color: #07aaed" @click="flyPoint" />
</span>
</a-col>
<a-col :span="24">
<div class="map">
<Map ref="mapRef" :nowPreviewRecord="props.nowPreviewRecord" />
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { EditOutlined, PlusSquareOutlined, EnvironmentOutlined } from '@ant-design/icons-vue';
import { orgPosGroup } from '@/api/demo/system';
import { Map } from './preview';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage } = useMessage();
const props = defineProps(['nowPreviewRecord']);
const emit = defineEmits(['reloadTable']);
// --------------------------------
const editNameFlag = ref(true);
const editName = ref(props.nowPreviewRecord.name.split('.').slice(0, -1).join('.'));
function editNameChange() {
editNameFlag.value = false;
}
//
async function pressEnterNameFunction() {
let query = {
id: props.nowPreviewRecord.id,
newName: editName.value + '.' + props.nowPreviewRecord.name.split('.').pop(),
};
props.nowPreviewRecord.name =
editName.value + '.' + props.nowPreviewRecord.name.split('.').pop();
editNameFlag.value = true;
emit('reloadTable');
return;
//
const data = await orgPosGroup(query);
if (data) {
editNameFlag.value = true;
emit('reloadTable');
return createMessage.success('修改名称成功');
} else {
return createMessage.error('修改名称失败');
}
}
function editNameBlur() {
editNameFlag.value = true;
editName.value = props.nowPreviewRecord.name.split('.').slice(0, -1).join('.');
}
// --------------------------------
const addLabelFlag = ref(true);
const newLabelName = ref('');
function addLabelChange() {
addLabelFlag.value = false;
}
//
async function pressEnterLabelFunction() {
if (!newLabelName.value) {
return;
}
if (!props.nowPreviewRecord.label.includes(newLabelName.value)) {
props.nowPreviewRecord.label.push(newLabelName.value);
let query = {
id: props.nowPreviewRecord.id,
newLabel: props.nowPreviewRecord.label,
};
addLabelFlag.value = true;
emit('reloadTable');
newLabelName.value = '';
return;
//
const data = await orgPosGroup(query);
if (data) {
addLabelFlag.value = true;
emit('reloadTable');
return createMessage.success('修改名称成功');
} else {
return createMessage.error('修改名称失败');
}
} else {
return createMessage.error('此标签已存在!');
}
}
function deleteLabel(value) {
props.nowPreviewRecord.label = props.nowPreviewRecord.label.filter((item) => item !== value);
emit('reloadTable');
}
function addLabelBlur() {
addLabelFlag.value = true;
newLabelName.value = '';
}
// --------------------------------
const mapRef = ref();
function flyPoint() {
mapRef.value?.flyToPoint([props.nowPreviewRecord.lng, props.nowPreviewRecord.lat]);
}
</script>
<style lang="less" scoped>
.title {
position: relative;
width: 50%;
height: 60px;
font-size: 15px;
color: white;
display: flex;
align-items: center;
}
.info {
position: relative;
width: 90%;
height: 480px;
.infotitle {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
margin-left: 10px;
}
.infovalue {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
}
.infobutton {
font-size: 10px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: right;
height: 40px;
}
}
.map {
width: 100%;
height: 300px;
margin-left: 10px;
}
</style>

@ -1,46 +1,245 @@
<template>
<div>
<MonitorHK
<div class="videoDiv">
<div class="showVideo">
<!-- <MonitorHK
v-if="props.nowPreviewRecord.manufacturer == '海康'"
:serialNumberValue="props.nowPreviewRecord.url"
:width="1380"
:height="900"
:width="1300"
:height="800"
/>
<MonitorLC
v-if="props.nowPreviewRecord.manufacturer == '乐橙'"
:deviceId="props.nowPreviewRecord.url"
:channelId="0"
:width="1396"
:height="900"
:width="1300"
:height="800"
:videoMuted="true"
/>
<MonitorTX
v-if="props.nowPreviewRecord.manufacturer == '腾讯'"
:serialNumberValue="props.nowPreviewRecord.url"
:width="1396"
:height="900"
:videoLoop="false"
:videoMuted="true"
:videoFit="'contain'"
/>
<MonitorQX
/> -->
<!-- <MonitorTX
v-if="props.nowPreviewRecord.manufacturer == '腾讯'"
:serialNumberValue="props.nowPreviewRecord.url"
:width="1300"
:height="820"
/> -->
<!-- <MonitorQX
v-if="props.nowPreviewRecord.manufacturer == '青犀'"
:serialNumberValue="props.nowPreviewRecord.url"
:width="1396"
:height="900"
:width="1300"
:height="800"
:videoLoop="false"
:videoMuted="true"
:videoFit="'contain'"
/>
/> -->
<video :src="props.nowPreviewRecord.url" class="video-player" controls muted autoplay></video>
</div>
<div class="bottomDiv">
<div class="buttonList">
<!-- 下载 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>下载</span>
</template>
<DownloadOutlined @click="fetchAndDownloadVideo(props.nowPreviewRecord.url)" />
</a-tooltip>
</div>
<!-- 删除 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>删除</span>
</template>
<DeleteOutlined @click="deleteVideo" />
</a-tooltip>
</div>
</div>
<div class="imageList">
<div v-for="li in props.previewRecordList" :key="li.id" @click="chooseNowPreviewRecord(li)">
<div
:class="li.id == props.nowPreviewRecord.id ? 'bottom_div_choose' : 'bottom_div'"
v-if="li.type == 'video'"
>
<img :src="li.gifZoomImage" :width="60" :height="40" />
</div>
</div>
</div>
</div>
<!-- 上一张下一张图片 -->
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
</div>
</template>
<script lang="ts" setup>
import {
DownloadOutlined,
DeleteOutlined,
LeftOutlined,
RightOutlined,
} from '@ant-design/icons-vue';
import { MonitorHK } from './preview';
import { MonitorLC } from './preview';
import { MonitorTX } from './preview';
import { MonitorQX } from './preview';
const props = defineProps(['nowPreviewRecord']);
console.log(props.nowPreviewRecord);
const props = defineProps(['nowPreviewRecord', 'previewRecordList']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
//
function clickLeftOrRightButton(direction) {
const list = props.previewRecordList.filter((item) => item.type == 'video');
for (let index = 0; index < list.length; index++) {
if (list[index].id == props.nowPreviewRecord.id) {
if (direction == 'left') {
if (index == 0) {
chooseNowPreviewRecord(list[list.length - 1]);
} else {
chooseNowPreviewRecord(list[index - 1]);
}
}
if (direction == 'right') {
if (index == list.length - 1) {
chooseNowPreviewRecord(list[0]);
} else {
chooseNowPreviewRecord(list[index + 1]);
}
}
}
}
}
//
async function fetchAndDownloadVideo(url) {
try {
const response = await fetch(url, {
mode: 'cors',
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
const urlObject = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = urlObject;
link.download = props.nowPreviewRecord.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(urlObject);
} catch (error) {
console.error('Error downloading image:', error);
}
}
//
function deleteVideo() {}
//
function chooseNowPreviewRecord(value) {
emit('chooseNowPreviewRecord', value);
}
</script>
<style lang="less"></style>
<style lang="less">
.videoDiv {
position: relative;
width: 100%;
height: 100%;
display: block;
}
.showVideo {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 820px;
}
.bottomDiv {
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
height: 100px;
background: #1c1c1c;
display: block;
.buttonList {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 40px;
.button {
position: relative;
padding-left: 10px;
padding-right: 10px;
margin-left: 6px;
margin-right: 6px;
color: #ffffff;
font-size: 22px;
}
}
.imageList {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 60px;
.bottom_div {
padding: 5px;
margin-left: 5px;
margin-right: 5px;
}
.bottom_div_choose {
padding: 4px;
border: 1px solid yellow;
margin-left: 5px;
margin-right: 5px;
}
}
}
//
.leftButton {
position: absolute;
left: 40px;
top: 45%;
z-index: 200;
background: #9c9c9c;
border-radius: 50px;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.rightButton {
position: absolute;
right: 40px;
top: 45%;
z-index: 200;
background: #9c9c9c;
border-radius: 50px;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.video-player {
height: 820px;
margin: 10px;
}
</style>

@ -0,0 +1,376 @@
<template>
<div>
<div class="title"> 详细信息 </div>
<div class="infoDiv">
<a-row>
<a-col :span="7">
<span class="infotitle">视频名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue_name" v-if="editNameFlag">
{{ props.nowPreviewRecord.name }}
<EditOutlined style="font-size: 20px; color: #07aaed" @click="editNameChange" />
</span>
<span class="infovalue_name" v-if="!editNameFlag">
<a-input v-model:value="editName" size="small" style="width: 80%" />
<CheckOutlined
style="margin-left: 10px; color: green"
@click="pressEnterNameFunction"
/>
<CloseOutlined style="margin-left: 10px; color: red" @click="editNameBlur" />
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">任务名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.taskname ? props.nowPreviewRecord.taskname : '--' }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">航线名称</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.airlineName ? props.nowPreviewRecord.airlineName : '--' }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">视频时长</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.airlineName ? props.nowPreviewRecord.airlineName : '--' }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">视频分辨率</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{
props.nowPreviewRecord.width && props.nowPreviewRecord.height
? props.nowPreviewRecord.width + '*' + props.nowPreviewRecord.height
: '--'
}}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">视频大小</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.size ? props.nowPreviewRecord.size : '--' }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄负载</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{
props.nowPreviewRecord.photographNumber
? props.nowPreviewRecord.photographNumber
: '--'
}}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">文件来源</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{ props.nowPreviewRecord.photographMan ? props.nowPreviewRecord.photographMan : '--' }}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">拍摄时间</span>
</a-col>
<a-col :span="17">
<span class="infovalue">
{{
props.nowPreviewRecord.photographTime ? props.nowPreviewRecord.photographTime : '--'
}}
</span>
</a-col>
<a-col :span="7">
<span class="infotitle">标签</span>
</a-col>
<a-col :span="17">
<span class="infovalue_graffitiNum">
<a-tag color="success" v-for="la in props.nowPreviewRecord.label" :key="la">
{{ la }}
</a-tag>
<PlusSquareOutlined style="font-size: 20px; color: #07aaed" @click="addLabelChange" />
</span>
<a-modal
title="标签设置"
:open="addLabelFlag"
:mask="false"
:maskClosable="false"
:closable="false"
:footer="null"
>
<div style="display: block">
<div
style="display: flex; flex-wrap: wrap; gap: 5px; width: 96%; margin: 10px"
v-if="props.nowPreviewRecord.label && props.nowPreviewRecord.label.length > 0"
>
已添加的标签
<div
v-for="la in props.nowPreviewRecord.label"
:key="la"
style="border: 1px solid #595959; border-radius: 3px"
>
<span style="margin-left: 5px">{{ la }}</span>
<CloseOutlined
style="margin-left: 5px; margin-right: 5px"
@click="deleteLabel(la)"
/>
</div>
</div>
<div style="display: inline-flex; gap: 5px; width: 96%; margin: 10px">
<a-input v-model:value="newLabelName" size="small" placeholder="请输入标签" />
<a-button type="primary" @click="pressEnterLabelFunction"></a-button>
</div>
<div
style="
display: inline-flex;
gap: 5px;
width: 96%;
margin: 5px 10px 20px 10px;
justify-content: flex-end;
"
>
<a-button
@click="
addLabelFlag = false;
newLabelName = '';
"
>
关闭
</a-button>
</div>
</div>
</a-modal>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import {
EditOutlined,
PlusSquareOutlined,
EnvironmentOutlined,
CheckOutlined,
CloseOutlined,
} from '@ant-design/icons-vue';
import { orgPosGroup } from '@/api/demo/system';
import { Map } from './preview';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage, createConfirm } = useMessage();
const props = defineProps(['nowPreviewRecord', 'previewRecordList', 'hideOrShowTextboxFlag']);
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
// --------------------------------
const editNameFlag = ref(true);
const editName = ref(props.nowPreviewRecord.name.split('.').slice(0, -1).join('.'));
function editNameChange() {
if (props.nowPreviewRecord.name.split('.').length <= 1) {
editName.value = props.nowPreviewRecord.name;
}
editNameFlag.value = false;
}
//
async function pressEnterNameFunction() {
let newName: any = null;
if (props.nowPreviewRecord.name.split('.').length <= 1) {
newName = editName.value;
} else {
newName = editName.value + '.' + props.nowPreviewRecord.name.split('.').pop();
}
let query = {
id: props.nowPreviewRecord.id,
newName: newName,
};
props.nowPreviewRecord.name = newName;
editNameFlag.value = true;
emit('reloadTable');
return;
//
const data = await orgPosGroup(query);
if (data) {
editNameFlag.value = true;
emit('reloadTable');
return createMessage.success('修改名称成功');
} else {
return createMessage.error('修改名称失败');
}
}
function editNameBlur() {
editNameFlag.value = true;
editName.value = props.nowPreviewRecord.name.split('.').slice(0, -1).join('.');
}
// --------------------------------
const addLabelFlag = ref(false);
const newLabelName = ref('');
function addLabelChange() {
addLabelFlag.value = true;
}
//
async function pressEnterLabelFunction() {
if (!newLabelName.value) {
return;
}
if (
!props.nowPreviewRecord.label ||
!props.nowPreviewRecord.label.includes(newLabelName.value)
) {
if (!props.nowPreviewRecord.label) {
props.nowPreviewRecord.label = [];
}
props.nowPreviewRecord.label.push(newLabelName.value);
let query = {
id: props.nowPreviewRecord.id,
newLabel: props.nowPreviewRecord.label,
};
addLabelFlag.value = true;
chooseNowPreviewRecord({
...props.nowPreviewRecord,
label: props.nowPreviewRecord.label,
});
emit('reloadTable');
newLabelName.value = '';
return;
//
const data = await orgPosGroup(query);
if (data) {
addLabelFlag.value = true;
emit('reloadTable');
return createMessage.success('修改名称成功');
} else {
return createMessage.error('修改名称失败');
}
} else {
return createMessage.error('此标签已存在!');
}
}
//
function deleteLabel(value) {
props.nowPreviewRecord.label = props.nowPreviewRecord.label.filter((item) => item !== value);
chooseNowPreviewRecord({
...props.nowPreviewRecord,
label: props.nowPreviewRecord.label,
});
emit('reloadTable');
}
//
function chooseNowPreviewRecord(value) {
emit('chooseNowPreviewRecord', value);
}
</script>
<style lang="less" scoped>
.title {
position: relative;
width: 50%;
height: 60px;
font-size: 15px;
color: white;
display: flex;
align-items: center;
}
.infoDiv {
position: relative;
width: 90%;
height: 480px;
.infotitle {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
margin-left: 10px;
}
.infovalue {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: 40px;
max-width: 250px;
flex-wrap: wrap;
}
.infovalue_name {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: auto;
min-height: 40px;
max-width: 250px;
}
.infovalue_graffitiNum {
font-size: 15px;
color: #ffffff;
display: flex;
align-items: center;
height: auto;
min-height: 40px;
max-width: 250px;
flex-wrap: wrap;
}
.infobutton {
font-size: 10px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: right;
height: 40px;
}
}
.map {
width: 100%;
height: 240px;
margin-left: 5px;
border-radius: 5px;
}
.graffitiNum {
display: flex;
align-items: center;
justify-content: space-between;
color: #ffffff;
width: 60%;
background: #3c3c3c;
height: 30px;
border-radius: 5px;
padding-left: 10px;
padding-right: 10px;
}
.graffitiNum2 {
display: flex;
align-items: center;
justify-content: space-between;
color: #ffffff;
width: 95%;
background: #1c1c1c;
height: 40px;
padding-left: 10px;
padding-right: 10px;
}
</style>

@ -218,7 +218,7 @@
//
initPlugin();
//
const elements = document.querySelectorAll('.ZhiGan_ModalVideo');
const elements = document.querySelectorAll('.ModalVideo');
if (elements.length > 0) {
// scroll
elements.forEach((element) => {
@ -230,7 +230,7 @@
//
closeHkVideo();
//
const elements = document.querySelectorAll('.ZhiGan_ModalVideo');
const elements = document.querySelectorAll('.ModalVideo');
if (elements.length > 0) {
// scroll
elements.forEach((element) => {

@ -1,14 +1,14 @@
<template>
<div>
<video
:id="'ZhiGan_ModalVideo' + props.timestamp"
:id="'TCPlayerVideo'"
class="TCPlayer-video-container"
preload="auto"
crossOrigin="anonymous"
playsinline
autoplay
:loop="props.videoLoop"
:muted="props.videoMuted"
:loop="false"
:muted="true"
:style="{
width: props.width + 'px',
height: props.height + 'px',
@ -20,15 +20,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, nextTick } from 'vue';
const props = defineProps([
'serialNumberValue',
'width',
'height',
'timestamp',
'videoLoop',
'videoMuted',
'videoFit',
]);
const props = defineProps(['serialNumberValue', 'width', 'height']);
//
let player: any = null;
@ -38,7 +30,7 @@
if (player) {
player.src(props.serialNumberValue);
} else {
player = TCPlayer('ZhiGan_ModalVideo' + props.timestamp, {});
player = TCPlayer('TCPlayerVideo', {});
player.src(props.serialNumberValue);
}
});
@ -75,7 +67,7 @@
<style lang="scss" scoped>
video {
display: block;
object-fit: v-bind('props.videoFit');
object-fit: contain;
}
::v-deep .vjs-live-control .vjs-live-display {

@ -45,8 +45,8 @@
// viewer.scene.skyBox.show = false;
//
viewer.scene.skyAtmosphere.show = false;
// viewer.scene.sun.show = false;
// viewer.scene.moon.show = false;
viewer.scene.sun.show = false;
viewer.scene.moon.show = false;
//
// viewer.scene.backgroundColor = Cesium.Color.BLACK;
//
@ -66,7 +66,7 @@
},
});
//
viewer.scene.globe.depthTestAgainstTerrain = true;
// viewer.scene.globe.depthTestAgainstTerrain = true;
// //
const terrainProvider = await Cesium.CesiumTerrainProvider.fromIonAssetId(3956);
viewer.terrainProvider = terrainProvider;
@ -102,7 +102,7 @@
originalModelMatrix = Cesium.Matrix4.clone(tileset.modelMatrix);
//
//
tileset.clampToGround = true;
// tileset.clampToGround = true;
viewer.scene.primitives.add(tileset);
viewer.zoomTo(tileset);
} catch (error) {

@ -130,7 +130,7 @@
.modelDiv {
position: relative;
width: 100%;
height: 1020px;
height: 920px;
display: block;
}
@ -163,7 +163,7 @@
align-items: center;
justify-content: center;
width: 100%;
height: 890px;
height: 800px;
background: #000000;
}
.modelDiv_bottom {

@ -4,23 +4,23 @@
<Map :airRoute="airRoute" />
</div>
<SelectComponent @selectChange="changeSelect" />
<!-- <AirportInformation
<AirportInformation
@changeLive="changeAirportLive"
@changeRemote="changeRemote"
:msgData="msgData"
/> -->
/>
<UAVInformation
:msgData="msgData"
@changeLoadControl="changeLoadControl"
@changeFlightControl="changeFlightControl"
/>
<!-- 远程调试 -->
<div v-if="remoteVisible">
<!-- <div v-if="remoteVisible">
<RemoteDebugging @changeRemote="changeRemote" :msgData="msgData" />
</div>
</div> -->
<!-- 负载控制 -->
<div v-if="loadControlVisible">
<LoadControl @changeLoadControl="changeLoadControl" />
<LoadControl @changeLoadControl="changeLoadControl" :msgData="msgData" />
</div>
<!-- 飞行控制 -->
<div v-if="flightControlVisible">
@ -76,7 +76,7 @@
//
const livePreviewVisible = ref(true);
//
const remoteVisible = ref(true);
const remoteVisible = ref(false);
//
const loadControlVisible = ref(false);
//
@ -103,10 +103,12 @@
//
getClient().on('message', (topic, message) => {
const rs = JSON.parse(message);
msgData.value = {
topic: topic,
message: rs,
};
if (rs) {
msgData.value = {
topic: topic,
message: rs,
};
}
});
};
</script>

@ -1,7 +1,7 @@
<template>
<div class="airport-information" v-if="airportVal">
<div class="title"
>机场信息<span>
>机场信息<span v-if="airportVal.mode_code">
<template v-if="airportVal.mode_code == 0"></template>
<template v-else-if="airportVal.mode_code == 1">飞行作业中</template>
<template v-else-if="airportVal.mode_code == 2">作业后状态恢复</template>
@ -10,8 +10,9 @@
<template v-else-if="airportVal.mode_code == 5">任务空闲</template>
<template v-else-if="airportVal.mode_code == 255">飞行器异常</template>
<template v-else-if="airportVal.mode_code == 256">未知状态</template>
</span></div
>
</span>
<span v-else></span>
</div>
<div class="content">
<div class="content-title">
{{ time }}
@ -46,7 +47,7 @@
<div class="content-item">
<div class="item-div">
<img src="@/assets/images/flightoperation/rate.png" alt="" />
{{ airportVal.network_state.rate }}KB/s
{{ airportVal.network_state ? airportVal.network_state.rate : '0' }}KB/s
</div>
<a-divider type="vertical" style="border-color: #4e5778" />
<div class="item-div">
@ -57,17 +58,17 @@
<div class="content-item">
<div class="item-div">
<img src="@/assets/images/flightoperation/voltage.png" alt="" />
{{ airportVal.drone_charge_state.capacity_percent }} %
{{ airportVal.drone_charge_state ? airportVal.drone_charge_state.capacity_percent : 0 }} %
</div>
</div>
<div class="content-button">
<!-- <div class="content-button">
<a-button type="primary" style="background: #3a57e8" @click="emits('changeLive')"
>机场直播</a-button
>
<a-button type="primary" style="background: #0a99eb" @click="emits('changeRemote')"
>远程调试</a-button
>
</div>
</div> -->
</div>
</div>
</template>
@ -99,7 +100,8 @@
watch(
() => props.msgData,
(val) => {
if (val.topic == 'thing/product/8UUXN5400A079H/osd') {
// network_state
if (val.topic == 'thing/product/8UUXN5400A079H/osd' && val.message.data.network_state) {
// console.log(val);
airportVal.value = val.message.data;
time.value = timestampToFormattedDate(val.message.timestamp);

@ -8,11 +8,10 @@
</div>
<div class="content">
<div class="content-button">
<a-button>抢夺负载控制</a-button>
<a-button>进入指令飞行</a-button>
<a-button>退出指令飞行</a-button>
<a-button>获取飞行器控制器</a-button>
<a-button>一键起飞</a-button>
<a-button>飞向目标点</a-button>
<a-button>指点飞行</a-button>
<a-button>智能环绕</a-button>
<a-button>一键返航</a-button>
</div>
<div class="content-info">

@ -1,46 +1,54 @@
<template>
<div class="remote-debugging" v-if="airportVal" v-drag>
<div class="title">
<span> 负载控制 </span>
<span> 云台相机控制 </span>
<div @click="emits('changeLoadControl')">
<CloseOutlined />
</div>
</div>
<div class="content">
<div class="content-item">
<span>负载控制</span>
<a-button>抢夺负载控制</a-button>
<span>相机控制权</span>
<a-button @click="obtain"></a-button>
</div>
<div class="content-item">
<!-- <div class="content-item">
<span>切换相机模式</span>
<a-button>抢夺负载控制</a-button>
</div>
</div> -->
<div class="content-item">
<span>拍照</span>
<a-button>单拍</a-button>
<a-button @click="singleShot"></a-button>
</div>
<div class="direction-controller">
<img src="@/assets/images/flightoperation/direction_controller.png" alt="" />
<div class="direction-controller-top"></div>
<div class="direction-controller-right"></div>
<div class="direction-controller-bottom"></div>
<div class="direction-controller-left"></div>
<div class="direction-controller-top" @click="changeDrc('top')"></div>
<div class="direction-controller-right" @click="changeDrc('right')"></div>
<div class="direction-controller-bottom" @click="changeDrc('bottom')"></div>
<div class="direction-controller-left" @click="changeDrc('left')"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { getClient, createConnection } from '@/utils/mqtt';
import { onMounted, ref, watch, reactive } from 'vue';
import { getReizeClient, createSeizeConnection, clientReizePublish } from '@/utils/mqtt';
import { CloseOutlined } from '@ant-design/icons-vue';
import { vDrag } from '@/utils/drag';
import { buildGUID } from '@/utils/uuid';
import { eventsTopic, events_replyTopic, drcDownTopic } from '@/utils/debugging/events';
const emits = defineEmits(['changeLoadControl']);
const props = defineProps({
airportAllVal: Object,
msgData: Object,
});
console.log(props);
const checked = ref(false);
const drcVal = reactive({
roll: 1024,
pitch: 1024,
throttle: 1024,
yaw: 1024,
gimbal_pitch: 1024,
});
const airportVal: any = ref({
mode_code: 0,
wind_speed: 0,
@ -56,14 +64,74 @@
},
});
watch(
() => props.airportAllVal,
() => props.msgData,
(val) => {
console.log(val);
airportVal.value = val.data;
if (val.topic == 'thing/product/8UUXN5400A079H/events_reply') {
console.log(val);
}
},
);
onMounted(() => {});
const obtain = () => {
//
eventsTopic({
bid: buildGUID(),
method: 'flight_authority_grab',
tid: buildGUID(),
timestamp: new Date().getTime(),
data: {},
});
//
eventsTopic({
bid: buildGUID(),
method: 'payload_authority_grab',
tid: buildGUID(),
timestamp: new Date().getTime(),
data: {
payload_index: '99-0-0',
},
});
events_replyTopic();
};
//
const singleShot = () => {
//
eventsTopic({
bid: buildGUID(),
method: 'camera_photo_take',
tid: buildGUID(),
timestamp: new Date().getTime(),
data: {
payload_index: '99-0-0',
},
});
};
//
const changeDrc = (val) => {
if (val == 'top') {
drcVal.pitch = drcVal.pitch + 20;
} else if (val == 'bottom') {
drcVal.pitch = drcVal.pitch - 20;
} else if (val == 'left') {
drcVal.roll = drcVal.roll - 20;
} else if (val == 'right') {
drcVal.roll = drcVal.roll + 20;
}
const querys = {
seq: 1,
method: 'stick_control',
data: drcVal,
};
drcDownTopic(querys);
};
onMounted(() => {
// createSeizeConnection();
// //
// getReizeClient().on('message', (topic, message) => {
// const rs = JSON.parse(message);
// console.log(rs);
// });
});
</script>
<style lang="less" scoped>
.remote-debugging {

@ -1,6 +1,14 @@
<template>
<div class="airport-information">
<div class="title">无人机信息<span>未连接</span></div>
<div class="title"
>无人机信息<span>
<template
v-if="uavInformation.sub_device && uavInformation.sub_device.device_online_status == 1"
>开机</template
>
<template v-else></template>
</span></div
>
<div class="content">
<div class="content-title">
{{ time }}
@ -9,45 +17,65 @@
<div class="content-item">
<div class="item-div">
<img src="@/assets/images/flightoperation/project.png" alt="" />
1
{{ uavInformation.wireless_link ? uavInformation.wireless_link.dongle_number : 0 }}
</div>
<a-divider type="vertical" style="border-color: #4e5778" />
<div class="item-div">
<!-- <div class="item-div">
<img src="@/assets/images/flightoperation/arrow.png" alt="" />
0%
</div> -->
<div class="item-div">
<img src="@/assets/images/flightoperation/electricity.png" alt="" />
{{
uavInformation.drone_battery_maintenance_info
? uavInformation.drone_battery_maintenance_info.capacity_percent
: 0
}}
</div>
</div>
<div class="content-item">
<div class="item-div">
<img src="@/assets/images/flightoperation/rtk.png" alt="" />
0
<img src="@/assets/images/flightoperation/sdr.png" alt="" />
<!-- sdr_link_state -->
<template
v-if="uavInformation.wireless_link && uavInformation.wireless_link.sdr_link_state == 1"
>连接</template
>
<template v-else></template>
</div>
<a-divider type="vertical" style="border-color: #4e5778" />
<div class="item-div">
<img src="@/assets/images/flightoperation/electricity.png" alt="" />
89%(空闲中)
<img src="@/assets/images/flightoperation/4g.png" alt="" />
<template
v-if="
uavInformation.wireless_link && uavInformation.wireless_link['4g_link_state'] == 1
"
>连接</template
>
<template v-else></template>
</div>
</div>
<!-- sdr_freq_band sdr_quality 4g_freq_band 4g_quality -->
<div class="content-item">
<div class="item-div">
<img src="@/assets/images/flightoperation/agl.png" alt="" />
0M
<img src="@/assets/images/flightoperation/sdr.png" alt="" />
{{ uavInformation.wireless_link ? uavInformation.wireless_link.sdr_quality : 0 }}
</div>
<a-divider type="vertical" style="border-color: #4e5778" />
<div class="item-div">
<img src="@/assets/images/flightoperation/agl.png" alt="" />
0M
<img src="@/assets/images/flightoperation/4g.png" alt="" />
{{ uavInformation.wireless_link ? uavInformation.wireless_link['4g_quality'] : 0 }}
</div>
</div>
<div class="content-item">
<div class="item-div">
<img src="@/assets/images/flightoperation/hs.png" alt="" />
0m/s
<img src="@/assets/images/flightoperation/sdr.png" alt="" />
{{ uavInformation.wireless_link ? uavInformation.wireless_link.sdr_freq_band : 0 }}
</div>
<a-divider type="vertical" style="border-color: #4e5778" />
<div class="item-div">
<img src="@/assets/images/flightoperation/h.png" alt="" />
0M
<img src="@/assets/images/flightoperation/4g.png" alt="" />
{{ uavInformation.wireless_link ? uavInformation.wireless_link['4g_freq_band'] : 0 }}
</div>
</div>
<div class="content-edit">
@ -89,7 +117,7 @@
>飞行控制</a-button
>
<a-button type="primary" style="background: #0a99eb" @click="emits('changeLoadControl')"
>负载控制</a-button
>云台相机控制</a-button
>
</div>
<div class="content-button">
@ -104,7 +132,6 @@
import { reactive, ref, watch } from 'vue';
import { CopyOutlined, EditOutlined } from '@ant-design/icons-vue';
import { timestampToFormattedDate } from '@/utils/index';
import { getClient, createConnection, clientPublish, clientSubscribe } from '@/utils/mqtt';
import { buildGUID } from '@/utils/uuid';
import { servicesTopic, services_replyTopic } from '@/utils/debugging/remote';
@ -138,13 +165,41 @@
limitedRange: '500m',
obstacleAvoidance: '500m',
});
const uavInformation = ref({
sub_device: {
//
device_online_status: 0,
},
drone_battery_maintenance_info: {
//
capacity_percent: 0,
},
wireless_link: {
// Dongle
dongle_number: 0,
sdr_quality: 0,
'4g_quality': 0,
'4g_uav_quality': 0,
'4g_gnd_quality': 0,
remain_upload: 0,
sdr_link_state: 0,
sdr_freq_band: 0,
},
});
const time = ref(timestampToFormattedDate(new Date().getTime()));
watch(
() => props.msgData,
(val) => {
if (val.topic == 'thing/product/8UUXN5400A079H/osd') {
// console.log(val);
time.value = timestampToFormattedDate(val.message.timestamp);
if (
val.message.data.sub_device ||
val.message.data.drone_battery_maintenance_info ||
val.message.data.wireless_link
) {
// console.log(val);
uavInformation.value = val.message.data;
time.value = timestampToFormattedDate(val.message.timestamp);
}
}
},
);
@ -152,7 +207,7 @@
<style lang="less" scoped>
.airport-information {
position: absolute;
top: 360px;
top: 320px;
left: 0;
z-index: 999;
width: 260px;

Loading…
Cancel
Save