媒体库-第一版
parent
4583fe0c4e
commit
e1c29bd6f7
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div class="m-4 mr-0 overflow-hidden bg-white">
|
||||||
|
<BasicTree
|
||||||
|
ref="asyncExpandTreeRef"
|
||||||
|
treeWrapperClassName="h-[calc(100%-35px)] overflow-auto"
|
||||||
|
:clickRowToExpand="false"
|
||||||
|
:treeData="treeData"
|
||||||
|
:fieldNames="{ key: 'id', title: 'name' }"
|
||||||
|
@select="handleSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, nextTick, unref } from 'vue';
|
||||||
|
import { BasicTree, TreeItem, TreeActionType } from '@/components/Tree';
|
||||||
|
import { getChildrenTree } from '@/api/demo/system';
|
||||||
|
import { isArray } from '@/utils/is';
|
||||||
|
|
||||||
|
const emit = defineEmits(['select']);
|
||||||
|
|
||||||
|
const treeData = 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',
|
||||||
|
name: '媒体库',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 'allResource',
|
||||||
|
name: '全部资源',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'wideAngle',
|
||||||
|
name: '广角照片',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zoom',
|
||||||
|
name: '变焦照片',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'infrared',
|
||||||
|
name: '红外照片',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'video',
|
||||||
|
name: '视频资源',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'achievementModel',
|
||||||
|
name: '成果模型',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: '2d',
|
||||||
|
name: '二维正摄',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3d',
|
||||||
|
name: '三维模型',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gas',
|
||||||
|
name: '气体探测',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vr',
|
||||||
|
name: 'VR全景',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 显示到一级
|
||||||
|
nextTick(() => {
|
||||||
|
unref(asyncExpandTreeRef)?.filterByLevel(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(keys) {
|
||||||
|
emit('select', keys[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetch();
|
||||||
|
});
|
||||||
|
defineExpose({
|
||||||
|
fetch,
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,483 @@
|
|||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
<!-- 新建文件夹 -->
|
||||||
|
<AddFolderModal @register="addFolderModal" @success="handleSuccess" />
|
||||||
|
<!-- 移动 -->
|
||||||
|
<MoveFileModal @register="moveFileModal" @success="handleSuccess" />
|
||||||
|
<!-- 压缩 -->
|
||||||
|
<CompressFileModal @register="compressFileModal" @success="handleSuccess" />
|
||||||
|
<!-- 重命名 -->
|
||||||
|
<RenameModal @register="renameModal" @success="handleSuccess" />
|
||||||
|
<!-- 预览弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="open"
|
||||||
|
width="100%"
|
||||||
|
wrap-class-name="full-modal"
|
||||||
|
:centered="true"
|
||||||
|
:closable="false"
|
||||||
|
:footer="null"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
:mask="false"
|
||||||
|
:maskClosable="false"
|
||||||
|
@ok="handleOk"
|
||||||
|
>
|
||||||
|
<Preview
|
||||||
|
:nowPreviewRecord="nowPreviewRecord"
|
||||||
|
:previewRecordList="previewRecordList"
|
||||||
|
@chooseNowPreviewRecord="chooseNowPreviewRecord"
|
||||||
|
@closeModal="closeModal"
|
||||||
|
@reloadTable="reload"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</PageWrapper>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref, h } from 'vue';
|
||||||
|
import { BasicTable, useTable, TableAction } from '@/components/Table';
|
||||||
|
import { getOrgList, deleteDept } from '@/api/demo/system';
|
||||||
|
import { PageWrapper } from '@/components/Page';
|
||||||
|
import { useModal } from '@/components/Modal';
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
import {
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
ColumnHeightOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
AppstoreOutlined,
|
||||||
|
BarsOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import LeftTree from './LeftTree.vue';
|
||||||
|
import { AddFolderModal } from './modal/modal';
|
||||||
|
import { MoveFileModal } from './modal/modal';
|
||||||
|
import { CompressFileModal } from './modal/modal';
|
||||||
|
import { RenameModal } from './modal/modal';
|
||||||
|
import Preview from './preview/preview.vue';
|
||||||
|
import { PermissionBtn } from '@/components/PermissionBtn/index';
|
||||||
|
import { columns, searchFormSchema } from './modal.data';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1-2',
|
||||||
|
name: '南山风景照.jpg',
|
||||||
|
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',
|
||||||
|
imgtype: '原片',
|
||||||
|
taskname: '佛山大火救援项目',
|
||||||
|
airlineName: '火灾救援勘查航线',
|
||||||
|
width: 889,
|
||||||
|
height: 500,
|
||||||
|
size: '6.2M',
|
||||||
|
photographFeiji: '救援机1007',
|
||||||
|
photographNumber: 'GD610',
|
||||||
|
photographMan: 'zachzhou',
|
||||||
|
photographTime: '2020-10-22 00:00:00',
|
||||||
|
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',
|
||||||
|
type: 'img',
|
||||||
|
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
|
||||||
|
imgtype: '原片2',
|
||||||
|
taskname: '佛山大火救援项目2',
|
||||||
|
airlineName: '火灾救援勘查航线2',
|
||||||
|
width: 2000,
|
||||||
|
height: 1334,
|
||||||
|
size: '1.63M',
|
||||||
|
photographFeiji: '救援机1008',
|
||||||
|
photographNumber: 'GD610',
|
||||||
|
photographMan: 'zachzhou',
|
||||||
|
photographTime: '2020-10-22 00:00:00',
|
||||||
|
label: ['人', '车'],
|
||||||
|
lat: 35.362625,
|
||||||
|
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',
|
||||||
|
type: 'img',
|
||||||
|
url: 'https://pic.rmb.bdstatic.com/bjh/gallery/8c885a0e3cf0647b60548535e2e9ca39.jpeg',
|
||||||
|
imgtype: '红外照片',
|
||||||
|
taskname: '佛山大火救援项目-红外照片',
|
||||||
|
airlineName: '火灾救援勘查航线',
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
size: '6.2M',
|
||||||
|
photographFeiji: '救援机1007',
|
||||||
|
photographNumber: 'GD610',
|
||||||
|
photographMan: 'zachzhou',
|
||||||
|
photographTime: '2020-10-22 00:00:00',
|
||||||
|
label: ['人', '车'],
|
||||||
|
lat: 35.362625,
|
||||||
|
lng: 118.033886,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1-5',
|
||||||
|
name: '广角照片.jpg',
|
||||||
|
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: '广角照片',
|
||||||
|
taskname: '佛山大火救援项目-广角照片',
|
||||||
|
airlineName: '火灾救援勘查航线',
|
||||||
|
width: 749,
|
||||||
|
height: 500,
|
||||||
|
size: '6.2M',
|
||||||
|
photographFeiji: '救援机1007',
|
||||||
|
photographNumber: 'GD610',
|
||||||
|
photographMan: 'zachzhou',
|
||||||
|
photographTime: '2020-10-22 00:00:00',
|
||||||
|
label: ['人', '车'],
|
||||||
|
lat: 35.362625,
|
||||||
|
lng: 118.033886,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1-6',
|
||||||
|
name: '变焦照片.jpg',
|
||||||
|
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: '变焦照片',
|
||||||
|
taskname: '佛山大火救援项目-变焦照片',
|
||||||
|
airlineName: '火灾救援勘查航线',
|
||||||
|
width: 500,
|
||||||
|
height: 518,
|
||||||
|
size: '6.2M',
|
||||||
|
photographFeiji: '救援机1007',
|
||||||
|
photographNumber: 'GD610',
|
||||||
|
photographMan: 'zachzhou',
|
||||||
|
photographTime: '2020-10-22 00:00:00',
|
||||||
|
label: ['人', '车'],
|
||||||
|
lat: 35.362625,
|
||||||
|
lng: 118.033886,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '41',
|
||||||
|
name: '视频',
|
||||||
|
createtime: '2025-03-24 18:13:17',
|
||||||
|
type: 'folder',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: '4-4',
|
||||||
|
name: 'XZD153狼窝沟西南',
|
||||||
|
createTime: '',
|
||||||
|
type: 'video',
|
||||||
|
url: '74b95e6575d741489b9a9061bb646467',
|
||||||
|
manufacturer: '海康',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4-5',
|
||||||
|
name: '费县马庄镇陈家鱼后村南斜坡后村',
|
||||||
|
createTime: '',
|
||||||
|
type: 'video',
|
||||||
|
url: 'http://111.36.45.20:18000/flv/hls/H-dcb1ea7388588111.flv',
|
||||||
|
manufacturer: '腾讯',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4-6',
|
||||||
|
name: '可落',
|
||||||
|
createTime: '',
|
||||||
|
type: 'video',
|
||||||
|
url: '8H03AA1PAG8D9BF',
|
||||||
|
manufacturer: '乐橙',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4-7',
|
||||||
|
name: '费县薛庄镇东张林村村南可见光',
|
||||||
|
createTime: '',
|
||||||
|
type: 'video',
|
||||||
|
url: '37130100181328000392',
|
||||||
|
manufacturer: '青犀',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
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',
|
||||||
|
// onChange: (selectedRowKeys, selectedRows) => {
|
||||||
|
// console.log(selectedRowKeys, selectedRows);
|
||||||
|
// },
|
||||||
|
// onSelect: (record, selected, selectedRows) => {
|
||||||
|
// console.log(record, selected, selectedRows);
|
||||||
|
// },
|
||||||
|
// onSelectAll: (selected, selectedRows, changeRows) => {
|
||||||
|
// console.log(selected, selectedRows, changeRows);
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
const childRef = ref<any>();
|
||||||
|
function handleSuccess() {
|
||||||
|
clearSelectedRowKeys();
|
||||||
|
childRef.value.fetch();
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗----------------------------------------------------------------------
|
||||||
|
// 新建文件夹
|
||||||
|
const [addFolderModal, { openModal: openAddFolderModal }] = useModal();
|
||||||
|
// 移动
|
||||||
|
const [moveFileModal, { openModal: openMoveFileModal }] = useModal();
|
||||||
|
// 压缩
|
||||||
|
const [compressFileModal, { openModal: openCompressFileModal }] = useModal();
|
||||||
|
// 重命名
|
||||||
|
const [renameModal, { openModal: openRenameModal }] = useModal();
|
||||||
|
|
||||||
|
// 新建文件夹
|
||||||
|
function addFolder() {
|
||||||
|
let rows = getSelectRows();
|
||||||
|
let record: any = null;
|
||||||
|
if (rows.length == 1) {
|
||||||
|
if (rows[0].type == 'folder') {
|
||||||
|
record = rows[0];
|
||||||
|
} else {
|
||||||
|
record = findParentIdById(getDataSource(), rows[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openAddFolderModal(true, {
|
||||||
|
record,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取父元素
|
||||||
|
function findParentIdById(tree, targetId) {
|
||||||
|
function recurse(nodes) {
|
||||||
|
for (let node of nodes) {
|
||||||
|
if (node.children) {
|
||||||
|
for (let child of node.children) {
|
||||||
|
if (child.id === targetId) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
const found = recurse([child]);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return recurse(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动文件
|
||||||
|
function moveFolderOrFile() {
|
||||||
|
let rows = getSelectRows();
|
||||||
|
if (rows.length > 0) {
|
||||||
|
const record = rows;
|
||||||
|
openMoveFileModal(true, {
|
||||||
|
tableData: getDataSource(),
|
||||||
|
record,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return createMessage.warn('请选择一个或者多个文件/文件夹进行移动');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
async function deleteFolderOrFile() {
|
||||||
|
let rows = getSelectRows();
|
||||||
|
if (rows.length == 0) {
|
||||||
|
return createMessage.warn('请选择一个或者多个文件/文件夹进行删除');
|
||||||
|
}
|
||||||
|
const query = rows.map((item) => item.id);
|
||||||
|
createConfirm({
|
||||||
|
iconType: 'info',
|
||||||
|
title: '删除',
|
||||||
|
content: '确定要删除当前部门吗',
|
||||||
|
onOk: async () => {
|
||||||
|
// const data = await deleteDept(query);
|
||||||
|
const data = null;
|
||||||
|
if (data) {
|
||||||
|
handleSuccess();
|
||||||
|
createMessage.success('删除成功');
|
||||||
|
} else {
|
||||||
|
createMessage.error('删除失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压缩
|
||||||
|
function compressFolderOrFile() {
|
||||||
|
let rows = getSelectRows();
|
||||||
|
if (rows.length > 0) {
|
||||||
|
const record = rows;
|
||||||
|
openCompressFileModal(true, {
|
||||||
|
tableData: getDataSource(),
|
||||||
|
record,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return createMessage.warn('请选择一个或者多个文件/文件夹压缩');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重命名
|
||||||
|
function renameRecord(record) {
|
||||||
|
openRenameModal(true, {
|
||||||
|
record,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览----------------------------------------------------------------------
|
||||||
|
const open = ref(false);
|
||||||
|
// 预览
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片选择
|
||||||
|
function chooseNowPreviewRecord(value) {
|
||||||
|
nowPreviewRecord.value = value;
|
||||||
|
}
|
||||||
|
// 关闭
|
||||||
|
function closeModal() {
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,192 @@
|
|||||||
|
import { BasicColumn, FormSchema } from '@/components/Table';
|
||||||
|
import { h } from 'vue';
|
||||||
|
import { Tag } from 'ant-design-vue';
|
||||||
|
import { getPosGroupList } from '@/api/demo/system';
|
||||||
|
|
||||||
|
export const columns: BasicColumn[] = [
|
||||||
|
{
|
||||||
|
title: '文件名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createtime',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '大小',
|
||||||
|
dataIndex: 'size',
|
||||||
|
align: 'left',
|
||||||
|
width: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '标签',
|
||||||
|
dataIndex: 'label',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '设备名称',
|
||||||
|
dataIndex: 'equipmentName',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const searchFormSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'label',
|
||||||
|
label: '标签',
|
||||||
|
component: 'Select',
|
||||||
|
colProps: { span: 8 },
|
||||||
|
componentProps: {
|
||||||
|
mode: 'multiple',
|
||||||
|
options: [
|
||||||
|
{ label: '标签1', value: '标签1' },
|
||||||
|
{ label: '标签2', value: '标签2' },
|
||||||
|
{ label: '标签3', value: '标签3' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'equipmentName',
|
||||||
|
label: '设备名称',
|
||||||
|
component: 'Input',
|
||||||
|
colProps: { span: 5 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: '[startTime, endTime]',
|
||||||
|
label: '时间选择',
|
||||||
|
component: 'RangePicker',
|
||||||
|
colProps: { span: 6 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
label: '文件名称',
|
||||||
|
component: 'Input',
|
||||||
|
colProps: { span: 5 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const formGroupSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'posGroupId',
|
||||||
|
component: 'ApiSelect',
|
||||||
|
label: '职级组',
|
||||||
|
required: true,
|
||||||
|
componentProps: ({ formActionType, formModel }) => {
|
||||||
|
return {
|
||||||
|
api: getPosGroupList, // 接口
|
||||||
|
// 接口参数
|
||||||
|
resultField: 'result',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const formSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
label: '部门id',
|
||||||
|
component: 'Input',
|
||||||
|
ifShow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
label: '部门名称',
|
||||||
|
component: 'Input',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'parentId',
|
||||||
|
label: '上级部门',
|
||||||
|
component: 'TreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
fieldNames: {
|
||||||
|
label: 'name',
|
||||||
|
key: 'id',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
onChange:(value)=>{
|
||||||
|
console.log(value)
|
||||||
|
},
|
||||||
|
getPopupContainer: () => document.body,
|
||||||
|
},
|
||||||
|
// required: true,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// field: 'orderNo',
|
||||||
|
// label: '排序',
|
||||||
|
// component: 'InputNumber',
|
||||||
|
// required: true,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
label: '状态',
|
||||||
|
component: 'RadioButtonGroup',
|
||||||
|
defaultValue: 0,
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '启用', value: 0 },
|
||||||
|
{ label: '停用', value: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// label: '备注',
|
||||||
|
// field: 'remark',
|
||||||
|
// component: 'InputTextArea',
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 新建文件夹
|
||||||
|
export const addFolderSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
component: 'Input',
|
||||||
|
label: '上级目录',
|
||||||
|
required: false,
|
||||||
|
defaultValue: '/',
|
||||||
|
componentProps: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'newName',
|
||||||
|
component: 'Input',
|
||||||
|
label: '新文件夹名称',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 压缩
|
||||||
|
export const compressFileSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'compressName',
|
||||||
|
component: 'Input',
|
||||||
|
label: '压缩文件名称',
|
||||||
|
required: true,
|
||||||
|
defaultValue: '压缩文件',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 重命名
|
||||||
|
export const renameSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
component: 'Input',
|
||||||
|
label: '新名称',
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
];
|
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<BasicModal v-bind="$attrs" @register="registerModal" title="新建文件夹" @ok="handleSubmit">
|
||||||
|
<BasicForm @register="registerForm" />
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal';
|
||||||
|
import { BasicForm, useForm } from '@/components/Form';
|
||||||
|
import { addFolderSchema } from '../modal.data';
|
||||||
|
|
||||||
|
import { orgPosGroup } from '@/api/demo/system';
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||||
|
labelWidth: 100,
|
||||||
|
baseColProps: { span: 24 },
|
||||||
|
schemas: addFolderSchema,
|
||||||
|
showActionButtonGroup: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上级文件夹的id
|
||||||
|
let folderId = ref();
|
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||||
|
resetFields();
|
||||||
|
setModalProps({ confirmLoading: false });
|
||||||
|
folderId.value = data.record?.id;
|
||||||
|
setFieldsValue({
|
||||||
|
...data.record,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
try {
|
||||||
|
const values = await validate();
|
||||||
|
let query = {
|
||||||
|
folderId: folderId.value,
|
||||||
|
newName: values.newName,
|
||||||
|
};
|
||||||
|
// 调用接口
|
||||||
|
const data = await orgPosGroup(query);
|
||||||
|
if (data) {
|
||||||
|
setModalProps({ confirmLoading: true });
|
||||||
|
closeModal();
|
||||||
|
emit('success');
|
||||||
|
return createMessage.success('成功');
|
||||||
|
} else {
|
||||||
|
return createMessage.error('失败');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setModalProps({ confirmLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<BasicModal
|
||||||
|
v-bind="$attrs"
|
||||||
|
@register="registerModal"
|
||||||
|
title="重命名"
|
||||||
|
height="100"
|
||||||
|
@ok="handleSubmit"
|
||||||
|
>
|
||||||
|
<BasicForm @register="registerForm" />
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal';
|
||||||
|
import { BasicForm, useForm } from '@/components/Form';
|
||||||
|
import { renameSchema } from '../modal.data';
|
||||||
|
|
||||||
|
import { orgPosGroup } from '@/api/demo/system';
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||||
|
labelWidth: 100,
|
||||||
|
baseColProps: { span: 24 },
|
||||||
|
schemas: renameSchema,
|
||||||
|
showActionButtonGroup: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上级文件夹的id
|
||||||
|
let id = ref();
|
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||||
|
resetFields();
|
||||||
|
setModalProps({ confirmLoading: false });
|
||||||
|
id.value = data.record?.id;
|
||||||
|
setFieldsValue({
|
||||||
|
...data.record,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
try {
|
||||||
|
const values = await validate();
|
||||||
|
let query = {
|
||||||
|
id: id.value,
|
||||||
|
name: values.name,
|
||||||
|
};
|
||||||
|
// 调用接口
|
||||||
|
const data = await orgPosGroup(query);
|
||||||
|
if (data) {
|
||||||
|
setModalProps({ confirmLoading: true });
|
||||||
|
closeModal();
|
||||||
|
emit('success');
|
||||||
|
return createMessage.success('重命名成功');
|
||||||
|
} else {
|
||||||
|
return createMessage.error('重命名失败');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setModalProps({ confirmLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,4 @@
|
|||||||
|
export { default as AddFolderModal } from './AddFolderModal.vue';
|
||||||
|
export { default as MoveFileModal } from './MoveFileModal.vue';
|
||||||
|
export { default as CompressFileModal } from './CompressFileModal.vue';
|
||||||
|
export { default as RenameModal } from './RenameModal.vue';
|
@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
style="position: relative; height: 100%"
|
||||||
|
ref="vChartRef"
|
||||||
|
:id="'mars3d-container-' + name"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import * as mars3d from 'mars3d';
|
||||||
|
import { useModal } from '@/components/Modal';
|
||||||
|
import { EventBus } from '@/utils/eventBus';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const [editModal, { openModal: openEidtModal }] = useModal();
|
||||||
|
|
||||||
|
let map: mars3d.Map; // 地图对象
|
||||||
|
|
||||||
|
let mapIns: any = null;
|
||||||
|
let markers: any = [];
|
||||||
|
let AMapIns: any = null;
|
||||||
|
|
||||||
|
const vChartRef = ref<HTMLElement>();
|
||||||
|
const props = defineProps(['nowPreviewRecord']);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let options = {
|
||||||
|
scene: {
|
||||||
|
center: {
|
||||||
|
lat: props.nowPreviewRecord.lat,
|
||||||
|
lng: props.nowPreviewRecord.lng,
|
||||||
|
alt: 8306.3,
|
||||||
|
heading: 360,
|
||||||
|
pitch: -45,
|
||||||
|
},
|
||||||
|
scene3DOnly: false,
|
||||||
|
shadows: false,
|
||||||
|
removeDblClick: true,
|
||||||
|
sceneMode: 3,
|
||||||
|
showSun: true,
|
||||||
|
showMoon: true,
|
||||||
|
showSkyBox: true,
|
||||||
|
showSkyAtmosphere: true,
|
||||||
|
fog: true,
|
||||||
|
fxaa: true,
|
||||||
|
requestRenderMode: true,
|
||||||
|
contextOptions: {
|
||||||
|
requestWebgl1: false,
|
||||||
|
webgl: {
|
||||||
|
preserveDrawingBuffer: true,
|
||||||
|
alpha: false,
|
||||||
|
stencil: true,
|
||||||
|
powerPreference: 'high-performance',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
globe: {
|
||||||
|
depthTestAgainstTerrain: false,
|
||||||
|
baseColor: '#546a53',
|
||||||
|
showGroundAtmosphere: true,
|
||||||
|
enableLighting: false,
|
||||||
|
},
|
||||||
|
cameraController: {
|
||||||
|
zoomFactor: 3,
|
||||||
|
minimumZoomDistance: 1,
|
||||||
|
maximumZoomDistance: 50000000,
|
||||||
|
enableRotate: true,
|
||||||
|
enableTranslate: true,
|
||||||
|
enableTilt: true,
|
||||||
|
enableZoom: true,
|
||||||
|
enableCollisionDetection: true,
|
||||||
|
minimumCollisionTerrainHeight: 15000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
control: {
|
||||||
|
homeButton: false,
|
||||||
|
baseLayerPicker: false,
|
||||||
|
sceneModePicker: false,
|
||||||
|
vrButton: false,
|
||||||
|
fullscreenButton: false,
|
||||||
|
navigationHelpButton: false,
|
||||||
|
animation: false,
|
||||||
|
timeline: false,
|
||||||
|
infoBox: false,
|
||||||
|
geocoder: false,
|
||||||
|
selectionIndicator: false,
|
||||||
|
showRenderLoopErrors: true,
|
||||||
|
contextmenu: {
|
||||||
|
hasDefault: true,
|
||||||
|
},
|
||||||
|
mouseDownView: true,
|
||||||
|
zoom: {
|
||||||
|
insertIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
method: {
|
||||||
|
templateValues: {
|
||||||
|
dataServer: '//data.mars3d.cn',
|
||||||
|
gltfServerUrl: '//data.mars3d.cn/gltf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
terrain: {
|
||||||
|
url: '//data.mars3d.cn/terrain',
|
||||||
|
show: true,
|
||||||
|
clip: true,
|
||||||
|
},
|
||||||
|
basemaps: [
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: '地图底图',
|
||||||
|
type: 'group',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2021,
|
||||||
|
pid: 10,
|
||||||
|
name: '天地图影像',
|
||||||
|
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
|
||||||
|
type: 'group',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
name: '底图',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_d',
|
||||||
|
eventParent: {
|
||||||
|
id: 2021,
|
||||||
|
pid: 10,
|
||||||
|
name: '天地图影像',
|
||||||
|
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
|
||||||
|
type: 'group',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
name: '底图',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_d',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '注记',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_z',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
private: false,
|
||||||
|
id: 'm-a57ecb7d-ba05-47a3-b1be-2e28411a5954',
|
||||||
|
opacity: 1,
|
||||||
|
pid: 2021,
|
||||||
|
parent: {
|
||||||
|
id: 2021,
|
||||||
|
pid: 10,
|
||||||
|
name: '天地图影像',
|
||||||
|
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
|
||||||
|
type: 'group',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
name: '底图',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_d',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '注记',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_z',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '注记',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_z',
|
||||||
|
eventParent: {
|
||||||
|
id: 2021,
|
||||||
|
pid: 10,
|
||||||
|
name: '天地图影像',
|
||||||
|
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
|
||||||
|
type: 'group',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
name: '底图',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_d',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '注记',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_z',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
private: false,
|
||||||
|
id: 'm-671f9d42-dda7-45ec-9d0f-4259c915b2cb',
|
||||||
|
opacity: 1,
|
||||||
|
pid: 2021,
|
||||||
|
parent: {
|
||||||
|
id: 2021,
|
||||||
|
pid: 10,
|
||||||
|
name: '天地图影像',
|
||||||
|
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
|
||||||
|
type: 'group',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
name: '底图',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_d',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '注记',
|
||||||
|
type: 'tdt',
|
||||||
|
layer: 'img_z',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
show: true,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layers: [],
|
||||||
|
};
|
||||||
|
initMap(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const initMap = (newData: any) => {
|
||||||
|
// 第一次加载
|
||||||
|
if (isFirstLoad.value) {
|
||||||
|
map = new mars3d.Map(vChartRef.value, newData);
|
||||||
|
} else {
|
||||||
|
// 之后更新
|
||||||
|
map.setSceneOptions(newData.scene);
|
||||||
|
}
|
||||||
|
isFirstLoad.value = false;
|
||||||
|
};
|
||||||
|
function flyToPoint(data) {
|
||||||
|
map.flyToPoint(data, {
|
||||||
|
radius: 5000, // 距离目标点的距离
|
||||||
|
duration: 4,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
flyToPoint,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped></style>
|
@ -0,0 +1,10 @@
|
|||||||
|
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 MonitorHK } from './video/monitorHK.vue';
|
||||||
|
export { default as MonitorLC } from './video/monitorLC.vue';
|
||||||
|
export { default as MonitorQX } from './video/monitorQX.vue';
|
||||||
|
export { default as MonitorTX } from './video/monitorTX.vue';
|
||||||
|
export { default as PanoViewer } from './PanoViewer.vue';
|
||||||
|
export { default as Map } from './Map.vue';
|
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal">
|
||||||
|
<div class="title">
|
||||||
|
<div class="title-1">{{ props.nowPreviewRecord.name }}</div>
|
||||||
|
<div class="title-2">
|
||||||
|
{{
|
||||||
|
props.nowPreviewRecord.createTime +
|
||||||
|
' ' +
|
||||||
|
props.nowPreviewRecord.size +
|
||||||
|
' ' +
|
||||||
|
props.nowPreviewRecord.id
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="closeButton">
|
||||||
|
<CloseOutlined @click="closeModal" style="font-size: 25px; color: white" />
|
||||||
|
</div>
|
||||||
|
<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"
|
||||||
|
/> -->
|
||||||
|
<PreviewImage
|
||||||
|
:nowPreviewRecord="props.nowPreviewRecord"
|
||||||
|
:previewRecordList="props.previewRecordList"
|
||||||
|
@chooseNowPreviewRecord="chooseNowPreviewRecord"
|
||||||
|
@reloadTable="reloadTable"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- VR全景 -->
|
||||||
|
<!-- <div v-if="props.nowPreviewRecord.type == 'vr'">
|
||||||
|
<PanoViewer />
|
||||||
|
</div> -->
|
||||||
|
<!-- 视频 -->
|
||||||
|
<div class="video" v-if="props.nowPreviewRecord.type == 'video'">
|
||||||
|
<PreviewVideo :nowPreviewRecord="props.nowPreviewRecord" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="information">
|
||||||
|
<PreviewInformation :nowPreviewRecord="props.nowPreviewRecord" @reloadTable="reloadTable" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
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 { PanoViewer } from './preview';
|
||||||
|
|
||||||
|
const props = defineProps(['nowPreviewRecord', 'previewRecordList']);
|
||||||
|
const emit = defineEmits(['closeModal', 'chooseNowPreviewRecord', 'reloadTable']);
|
||||||
|
|
||||||
|
// 选择
|
||||||
|
function chooseNowPreviewRecord(value) {
|
||||||
|
emit('chooseNowPreviewRecord', value);
|
||||||
|
}
|
||||||
|
// 刷新表格
|
||||||
|
function reloadTable() {
|
||||||
|
emit('reloadTable');
|
||||||
|
}
|
||||||
|
// 关闭弹窗
|
||||||
|
function closeModal() {
|
||||||
|
emit('closeModal');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.modal {
|
||||||
|
width: 100%;
|
||||||
|
height: 920px;
|
||||||
|
|
||||||
|
// 页面不能被选中
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* IE/Edge */
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 15px;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
.title-1 {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.title-2 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #f0f3f3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 15px;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
.mainBody {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
|
||||||
|
.imgOrVideo {
|
||||||
|
width: 80%;
|
||||||
|
height: 920px;
|
||||||
|
background: #101010;
|
||||||
|
|
||||||
|
.image {
|
||||||
|
// display: flex;
|
||||||
|
// align-items: center;
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.information {
|
||||||
|
position: relative;
|
||||||
|
width: 20%;
|
||||||
|
height: 100%;
|
||||||
|
background: #1c1c1c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,583 @@
|
|||||||
|
<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>
|
@ -0,0 +1,893 @@
|
|||||||
|
<template>
|
||||||
|
<div class="image">
|
||||||
|
<div class="canvas">
|
||||||
|
<div
|
||||||
|
ref="mouseCanvasRef"
|
||||||
|
@mousedown="onMouseDown"
|
||||||
|
:style="{
|
||||||
|
position: 'relative',
|
||||||
|
transform: `scale(${scale}) rotate(${rotationAngle}deg)`,
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
width: `${getImageWidthAndHeight[0]}px`,
|
||||||
|
height: `${getImageWidthAndHeight[1]}px`,
|
||||||
|
background: `url(${props.nowPreviewRecord.url}) no-repeat`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- 创建的矩形 -->
|
||||||
|
<div
|
||||||
|
v-if="hideOrShowGraffitiFlag"
|
||||||
|
v-for="(rect, index) in graffitis"
|
||||||
|
:key="index"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: rect.x + 'px',
|
||||||
|
top: rect.y + 'px',
|
||||||
|
width: rect.width + 'px',
|
||||||
|
height: rect.height + 'px',
|
||||||
|
zIndex: rect.status == 'edit' ? 1000 : 201,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- 文字标签 -->
|
||||||
|
<div
|
||||||
|
v-if="nowGraffiti == index || rect.text"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0px',
|
||||||
|
top: rect.height + 'px',
|
||||||
|
width: '220px',
|
||||||
|
height: 30 + 'px',
|
||||||
|
background: nowGraffiti == index ? '#ffffff' : '#ffffff00',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div style="display: block" v-if="nowGraffiti == index">
|
||||||
|
<a-input
|
||||||
|
v-model:value="rect.text"
|
||||||
|
style="width: 110px; height: 30px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
<CheckOutlined
|
||||||
|
style="margin-right: 10px; color: green"
|
||||||
|
@click="
|
||||||
|
nowGraffiti = -1;
|
||||||
|
rect.status = 'success';
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<CloseOutlined
|
||||||
|
style="margin-right: 10px; color: red"
|
||||||
|
@click="
|
||||||
|
rect.text ? (rect.text = graffitisClone[index].text) : (rect.text = '');
|
||||||
|
nowGraffiti = -1;
|
||||||
|
rect.status = 'success';
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<DeleteOutlined
|
||||||
|
style="margin-right: 10px"
|
||||||
|
@click="
|
||||||
|
graffitis.splice(index, 1);
|
||||||
|
nowGraffiti = -1;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<a-popover placement="top">
|
||||||
|
<template #content>
|
||||||
|
<div style="display: flex; gap: 5px">
|
||||||
|
<div
|
||||||
|
class="popoverClass"
|
||||||
|
v-for="color in ['#2D8CF0', '#19BE6B', '#FFBB00', '#E23C39', '#B620E0']"
|
||||||
|
:key="color"
|
||||||
|
:style="{ background: color }"
|
||||||
|
@click="rect.color = color"
|
||||||
|
>
|
||||||
|
<CheckOutlined v-if="rect.color == color" style="color: white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<FontColorsOutlined style="" />
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="nowGraffiti != index"
|
||||||
|
style="
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-text-stroke: 1px white;
|
||||||
|
width: fit-content;
|
||||||
|
"
|
||||||
|
:style="{
|
||||||
|
textDecoration: rect.status == 'mouse' ? 'underline' : '',
|
||||||
|
}"
|
||||||
|
@mouseenter="rect.status != 'edit' ? (rect.status = 'mouse') : ''"
|
||||||
|
@mouseleave="rect.status == 'mouse' ? (rect.status = 'success') : ''"
|
||||||
|
@click="
|
||||||
|
rect.status = 'edit';
|
||||||
|
graffitisClone = cloneDeep(graffitis);
|
||||||
|
nowGraffiti = index;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ rect.text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 上 -->
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: rect.status == 'edit' ? '6px' : '0px',
|
||||||
|
top: '0px',
|
||||||
|
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
|
||||||
|
height: '4px',
|
||||||
|
background: `${rect.color}`,
|
||||||
|
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'top')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@click="
|
||||||
|
rect.status = 'edit';
|
||||||
|
nowGraffiti = index;
|
||||||
|
"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'top')"
|
||||||
|
/>
|
||||||
|
<!-- 下 -->
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: rect.status == 'edit' ? '6px' : '0px',
|
||||||
|
bottom: `0px`,
|
||||||
|
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
|
||||||
|
height: '4px',
|
||||||
|
background: `${rect.color}`,
|
||||||
|
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'bottom')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@click="
|
||||||
|
rect.status = 'edit';
|
||||||
|
nowGraffiti = index;
|
||||||
|
"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'bottom')"
|
||||||
|
/>
|
||||||
|
<!-- 右 -->
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '0px',
|
||||||
|
top: rect.status == 'edit' ? '6px' : '0px',
|
||||||
|
width: '4px',
|
||||||
|
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
|
||||||
|
background: `${rect.color}`,
|
||||||
|
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'right')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@click="
|
||||||
|
rect.status = 'edit';
|
||||||
|
nowGraffiti = index;
|
||||||
|
"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'right')"
|
||||||
|
/>
|
||||||
|
<!-- 左 -->
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0px',
|
||||||
|
top: rect.status == 'edit' ? '6px' : '0px',
|
||||||
|
width: '4px',
|
||||||
|
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
|
||||||
|
background: `${rect.color}`,
|
||||||
|
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'left')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@click="
|
||||||
|
rect.status = 'edit';
|
||||||
|
nowGraffiti = index;
|
||||||
|
"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'left')"
|
||||||
|
/>
|
||||||
|
<!-- 左上 -->
|
||||||
|
<div
|
||||||
|
v-if="rect.status == 'edit'"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '-4px',
|
||||||
|
top: '-4px',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
background: `#ffffff`,
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'leftTop')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'leftTop')"
|
||||||
|
/>
|
||||||
|
<!-- 右下 -->
|
||||||
|
<div
|
||||||
|
v-if="rect.status == 'edit'"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '-4px',
|
||||||
|
bottom: '-4px',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
background: `#ffffff`,
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'rightBottom')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'rightBottom')"
|
||||||
|
/>
|
||||||
|
<!-- 右上 -->
|
||||||
|
<div
|
||||||
|
v-if="rect.status == 'edit'"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '-4px',
|
||||||
|
top: '-4px',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
background: `#ffffff`,
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'rightTop')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'rightTop')"
|
||||||
|
/>
|
||||||
|
<!-- 左下 -->
|
||||||
|
<div
|
||||||
|
v-if="rect.status == 'edit'"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '-4px',
|
||||||
|
bottom: '-4px',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
background: `#ffffff`,
|
||||||
|
}"
|
||||||
|
@mouseenter="mouseenter(rect, 'leftBottom')"
|
||||||
|
@mouseleave="mouseleave(rect)"
|
||||||
|
@mousedown="funMouseDownEdit($event, index, 'leftBottom')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="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="refresh" /> </div>
|
||||||
|
|
|
||||||
|
<!-- 全屏 -->
|
||||||
|
<!-- <div class="button"> <RedoOutlined @click="refresh" /> </div> -->
|
||||||
|
|
|
||||||
|
<!-- 复制到剪贴板 -->
|
||||||
|
<div class="button">
|
||||||
|
<ExportOutlined @click="copyToClipboard(props.nowPreviewRecord.url)" />
|
||||||
|
</div>
|
||||||
|
<!-- 下载 -->
|
||||||
|
<div class="button">
|
||||||
|
<DownloadOutlined @click="fetchAndDownloadImage(props.nowPreviewRecord.url)" />
|
||||||
|
</div>
|
||||||
|
<!-- 删除 -->
|
||||||
|
<div class="button"> <DeleteOutlined @click="deleteCanvas" /> </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
|
||||||
|
class="graffitiButton"
|
||||||
|
@click="setGraffiti"
|
||||||
|
:style="graffitiFlag ? 'outline: 2px solid #2B85E4' : ''"
|
||||||
|
>
|
||||||
|
<a-popover placement="left">
|
||||||
|
<template #content>
|
||||||
|
<div style="display: flex; gap: 5px">
|
||||||
|
<div
|
||||||
|
class="popoverClass"
|
||||||
|
v-for="color in ['#2D8CF0', '#19BE6B', '#FFBB00', '#E23C39', '#B620E0']"
|
||||||
|
:key="color"
|
||||||
|
:style="{ background: color }"
|
||||||
|
@click="graffitiColor = color"
|
||||||
|
>
|
||||||
|
<CheckOutlined v-if="graffitiColor == color" style="color: white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<EditOutlined
|
||||||
|
:style="{
|
||||||
|
color: graffitiColor,
|
||||||
|
fontSize: '20px',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
<!-- 隐藏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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, watch, computed } from 'vue';
|
||||||
|
import {
|
||||||
|
CloseOutlined,
|
||||||
|
RightOutlined,
|
||||||
|
LeftOutlined,
|
||||||
|
ZoomOutOutlined,
|
||||||
|
ZoomInOutlined,
|
||||||
|
RotateLeftOutlined,
|
||||||
|
RotateRightOutlined,
|
||||||
|
ExportOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
CheckOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
EyeInvisibleOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
FontColorsOutlined,
|
||||||
|
RedoOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
const { createConfirm, createMessage } = useMessage();
|
||||||
|
|
||||||
|
const props = defineProps(['nowPreviewRecord', 'previewRecordList']);
|
||||||
|
const emit = defineEmits(['chooseNowPreviewRecord', 'reloadTable']);
|
||||||
|
|
||||||
|
// 宽高
|
||||||
|
const getImageWidthAndHeight = computed(() => {
|
||||||
|
let width = 1300;
|
||||||
|
let height = 800;
|
||||||
|
if (props.nowPreviewRecord.width > 1300 || props.nowPreviewRecord.height > 800) {
|
||||||
|
if (props.nowPreviewRecord.width / 1300 > props.nowPreviewRecord.height / 800) {
|
||||||
|
width = 1300;
|
||||||
|
height = (props.nowPreviewRecord.height / props.nowPreviewRecord.width) * 1300;
|
||||||
|
} else {
|
||||||
|
height = 800;
|
||||||
|
width = (props.nowPreviewRecord.width / props.nowPreviewRecord.height) * 800;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
width = props.nowPreviewRecord.width;
|
||||||
|
height = props.nowPreviewRecord.height;
|
||||||
|
}
|
||||||
|
return [width, height];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上一张、下一张图片
|
||||||
|
function clickLeftOrRightButton(direction) {
|
||||||
|
const list = props.previewRecordList.filter((item) => item.type == 'img');
|
||||||
|
graffitiFlag.value = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格
|
||||||
|
function reloadTable() {
|
||||||
|
emit('reloadTable');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缩放比例-----------------------------------
|
||||||
|
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 refresh() {
|
||||||
|
scale.value = 1;
|
||||||
|
rotationAngle.value = 0;
|
||||||
|
graffitiFlag.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function deleteCanvas() {}
|
||||||
|
|
||||||
|
// 复制到剪贴板-----------------------------------
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏标签
|
||||||
|
const hideOrShowTextboxFlag = ref(true);
|
||||||
|
function hideOrShowTextbox(value) {
|
||||||
|
hideOrShowTextboxFlag.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置画笔---------------------------------------------------------------
|
||||||
|
const graffitiFlag = ref(false);
|
||||||
|
const graffitiColor = ref('#E23C39');
|
||||||
|
const nowGraffiti = ref(-1);
|
||||||
|
const nowMouseGraffiti = ref(0);
|
||||||
|
|
||||||
|
function setGraffiti() {
|
||||||
|
graffitiFlag.value = !graffitiFlag.value;
|
||||||
|
if (graffitiFlag.value) {
|
||||||
|
document.body.style.cursor = 'crosshair';
|
||||||
|
} else {
|
||||||
|
document.body.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 临时选框状态
|
||||||
|
const isDrawing = ref(false);
|
||||||
|
let startX = 0;
|
||||||
|
let startY = 0;
|
||||||
|
let endX = 0;
|
||||||
|
let endY = 0;
|
||||||
|
|
||||||
|
// 所有已创建的矩形
|
||||||
|
const graffitis: any = ref([]);
|
||||||
|
const graffitisClone: any = ref([]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.nowPreviewRecord.id,
|
||||||
|
() => {
|
||||||
|
graffitis.value = props.nowPreviewRecord.graffitiJson
|
||||||
|
? JSON.parse(props.nowPreviewRecord.graffitiJson)
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => graffitis.value,
|
||||||
|
() => {
|
||||||
|
props.nowPreviewRecord.graffitiJson = JSON.stringify(graffitis.value);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const mouseCanvasRef = ref();
|
||||||
|
// 鼠标按下
|
||||||
|
function onMouseDown(e) {
|
||||||
|
if (!graffitiFlag.value) return;
|
||||||
|
if (nowGraffiti.value != -1) return;
|
||||||
|
if (graffitis.value.findIndex((item) => item.status == 'mouse') != -1) return;
|
||||||
|
// 获取相对于容器的坐标
|
||||||
|
const rect = mouseCanvasRef.value.getBoundingClientRect();
|
||||||
|
startX = e.x - rect.x;
|
||||||
|
startY = e.y - rect.y;
|
||||||
|
isDrawing.value = true;
|
||||||
|
|
||||||
|
// 添加矩形到数组中
|
||||||
|
graffitis.value.push({
|
||||||
|
x: startX,
|
||||||
|
y: startY,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
color: graffitiColor.value,
|
||||||
|
text: '',
|
||||||
|
status: 'edit',
|
||||||
|
});
|
||||||
|
graffitisClone.value = cloneDeep(graffitis.value);
|
||||||
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
window.addEventListener('mouseup', onMouseUp);
|
||||||
|
}
|
||||||
|
// 鼠标移动
|
||||||
|
function onMouseMove(e) {
|
||||||
|
const rect = mouseCanvasRef.value.getBoundingClientRect();
|
||||||
|
if (!isDrawing.value) return;
|
||||||
|
endX = e.x - rect.x;
|
||||||
|
endY = e.y - rect.y;
|
||||||
|
if (endX < 0 && endY < 0) {
|
||||||
|
endX = startX;
|
||||||
|
endY = startY;
|
||||||
|
}
|
||||||
|
setMouseData();
|
||||||
|
}
|
||||||
|
// 鼠标松开
|
||||||
|
function onMouseUp(e) {
|
||||||
|
if (!isDrawing.value) return;
|
||||||
|
const rect = mouseCanvasRef.value.getBoundingClientRect();
|
||||||
|
|
||||||
|
endX = e.x - rect.x;
|
||||||
|
endY = e.y - rect.y;
|
||||||
|
if (endX < 0 && endY < 0) {
|
||||||
|
endX = startX;
|
||||||
|
endY = startY;
|
||||||
|
}
|
||||||
|
isDrawing.value = false;
|
||||||
|
if (e.x > rect.right || e.y > rect.bottom) {
|
||||||
|
graffitis.value.splice(graffitis.value.length - 1, 1);
|
||||||
|
nowGraffiti.value = -1;
|
||||||
|
} else {
|
||||||
|
setMouseData();
|
||||||
|
nowGraffiti.value = graffitis.value.length - 1;
|
||||||
|
}
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置鼠标涂鸦绘画出的数据
|
||||||
|
function setMouseData() {
|
||||||
|
graffitis.value[graffitis.value.length - 1].x = Math.min(startX, endX);
|
||||||
|
graffitis.value[graffitis.value.length - 1].width = Math.abs(endX - startX);
|
||||||
|
graffitis.value[graffitis.value.length - 1].y = Math.min(startY, endY);
|
||||||
|
graffitis.value[graffitis.value.length - 1].height = Math.abs(endY - startY);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// 鼠标聚焦
|
||||||
|
function mouseenter(rect, type) {
|
||||||
|
if (rect.status != 'edit') {
|
||||||
|
rect.status = 'mouse';
|
||||||
|
document.body.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
if (rect.status == 'edit') {
|
||||||
|
if (type == 'top' || type == 'bottom') {
|
||||||
|
document.body.style.cursor = 'ns-resize';
|
||||||
|
}
|
||||||
|
if (type == 'left' || type == 'right') {
|
||||||
|
document.body.style.cursor = 'ew-resize';
|
||||||
|
}
|
||||||
|
if (type == 'leftTop' || type == 'rightBottom') {
|
||||||
|
document.body.style.cursor = 'nwse-resize';
|
||||||
|
}
|
||||||
|
if (type == 'leftBottom' || type == 'rightTop') {
|
||||||
|
document.body.style.cursor = 'nesw-resize';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 鼠标离开
|
||||||
|
function mouseleave(rect) {
|
||||||
|
if (rect.status == 'mouse' || rect.status == 'edit') {
|
||||||
|
if (rect.status == 'mouse') {
|
||||||
|
rect.status = 'success';
|
||||||
|
}
|
||||||
|
if (graffitiFlag.value) {
|
||||||
|
document.body.style.cursor = 'crosshair';
|
||||||
|
} else {
|
||||||
|
document.body.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 编辑状态下的鼠标按下
|
||||||
|
const mouseEditType = ref('');
|
||||||
|
function funMouseDownEdit(e, index, type) {
|
||||||
|
// 获取相对于容器的坐标
|
||||||
|
graffitisClone.value = cloneDeep(graffitis.value);
|
||||||
|
const rect = mouseCanvasRef.value.getBoundingClientRect();
|
||||||
|
startX = e.x - rect.x;
|
||||||
|
startY = e.y - rect.y;
|
||||||
|
isDrawing.value = true;
|
||||||
|
nowGraffiti.value = index;
|
||||||
|
mouseEditType.value = type;
|
||||||
|
window.addEventListener('mousemove', funMouseMoveEdit);
|
||||||
|
window.addEventListener('mouseup', funMouseUpEdit);
|
||||||
|
}
|
||||||
|
// 编辑状态下的鼠标移动
|
||||||
|
function funMouseMoveEdit(e) {
|
||||||
|
const rect = mouseCanvasRef.value.getBoundingClientRect();
|
||||||
|
if (!isDrawing.value) return;
|
||||||
|
endX = e.x - rect.x;
|
||||||
|
endY = e.y - rect.y;
|
||||||
|
if (endX < 0 && endY < 0) {
|
||||||
|
endX = startX;
|
||||||
|
endY = startY;
|
||||||
|
}
|
||||||
|
funSetMouseDataEdit();
|
||||||
|
}
|
||||||
|
// 编辑状态下的鼠标松开
|
||||||
|
function funMouseUpEdit(e) {
|
||||||
|
if (!isDrawing.value) return;
|
||||||
|
const rect = mouseCanvasRef.value.getBoundingClientRect();
|
||||||
|
endX = e.x - rect.x;
|
||||||
|
endY = e.y - rect.y;
|
||||||
|
if (endX < 0 && endY < 0) {
|
||||||
|
endX = startX;
|
||||||
|
endY = startY;
|
||||||
|
}
|
||||||
|
isDrawing.value = false;
|
||||||
|
if (e.x > rect.right || e.y > rect.bottom) {
|
||||||
|
graffitis.value[nowGraffiti.value] = graffitisClone.value[nowGraffiti.value];
|
||||||
|
} else {
|
||||||
|
funSetMouseDataEdit();
|
||||||
|
}
|
||||||
|
window.removeEventListener('mousemove', funMouseMoveEdit);
|
||||||
|
window.removeEventListener('mouseup', funMouseUpEdit);
|
||||||
|
}
|
||||||
|
// 编辑状态下的数据
|
||||||
|
function funSetMouseDataEdit() {
|
||||||
|
if (['top', 'leftTop', 'rightTop'].includes(mouseEditType.value)) {
|
||||||
|
graffitis.value[nowGraffiti.value].height =
|
||||||
|
graffitis.value[nowGraffiti.value].height + graffitis.value[nowGraffiti.value].y - endY;
|
||||||
|
graffitis.value[nowGraffiti.value].y = endY;
|
||||||
|
}
|
||||||
|
if (['bottom', 'leftBottom', 'rightBottom'].includes(mouseEditType.value)) {
|
||||||
|
graffitis.value[nowGraffiti.value].height = endY - graffitis.value[nowGraffiti.value].y;
|
||||||
|
}
|
||||||
|
if (['left', 'leftTop', 'leftBottom'].includes(mouseEditType.value)) {
|
||||||
|
graffitis.value[nowGraffiti.value].width =
|
||||||
|
graffitis.value[nowGraffiti.value].width + graffitis.value[nowGraffiti.value].x - endX;
|
||||||
|
graffitis.value[nowGraffiti.value].x = endX;
|
||||||
|
}
|
||||||
|
if (['right', 'rightTop', 'rightBottom'].includes(mouseEditType.value)) {
|
||||||
|
graffitis.value[nowGraffiti.value].width = endX - graffitis.value[nowGraffiti.value].x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
</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: 820px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 涂鸦颜色选择-提示
|
||||||
|
.graffitiButton {
|
||||||
|
background: #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
right: 40px;
|
||||||
|
top: 5%;
|
||||||
|
z-index: 200;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverClass {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签颜色选择
|
||||||
|
.showTextboxClass {
|
||||||
|
position: absolute;
|
||||||
|
right: 40px;
|
||||||
|
top: 25%;
|
||||||
|
width: 30px;
|
||||||
|
height: 70px;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
color: #2d8cf0;
|
||||||
|
background: #ffffff;
|
||||||
|
font-size: 22px;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,271 @@
|
|||||||
|
<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: 200px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MonitorHK
|
||||||
|
v-if="props.nowPreviewRecord.manufacturer == '海康'"
|
||||||
|
:serialNumberValue="props.nowPreviewRecord.url"
|
||||||
|
:width="1380"
|
||||||
|
:height="900"
|
||||||
|
/>
|
||||||
|
<MonitorLC
|
||||||
|
v-if="props.nowPreviewRecord.manufacturer == '乐橙'"
|
||||||
|
:deviceId="props.nowPreviewRecord.url"
|
||||||
|
:channelId="0"
|
||||||
|
:width="1396"
|
||||||
|
:height="900"
|
||||||
|
:videoMuted="true"
|
||||||
|
/>
|
||||||
|
<MonitorTX
|
||||||
|
v-if="props.nowPreviewRecord.manufacturer == '腾讯'"
|
||||||
|
:serialNumberValue="props.nowPreviewRecord.url"
|
||||||
|
:width="1396"
|
||||||
|
:height="900"
|
||||||
|
:videoLoop="false"
|
||||||
|
:videoMuted="true"
|
||||||
|
:videoFit="'contain'"
|
||||||
|
/>
|
||||||
|
<MonitorQX
|
||||||
|
v-if="props.nowPreviewRecord.manufacturer == '青犀'"
|
||||||
|
:serialNumberValue="props.nowPreviewRecord.url"
|
||||||
|
:width="1396"
|
||||||
|
:height="900"
|
||||||
|
:videoLoop="false"
|
||||||
|
:videoMuted="true"
|
||||||
|
:videoFit="'contain'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { MonitorHK } from './preview';
|
||||||
|
import { MonitorLC } from './preview';
|
||||||
|
import { MonitorTX } from './preview';
|
||||||
|
import { MonitorQX } from './preview';
|
||||||
|
|
||||||
|
const props = defineProps(['nowPreviewRecord']);
|
||||||
|
</script>
|
||||||
|
<style lang="less"></style>
|
@ -0,0 +1,207 @@
|
|||||||
|
<template>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-container">
|
||||||
|
<div :id="'root' + props.timestamp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="box-controls">
|
||||||
|
<div class="left-controls">
|
||||||
|
<div>
|
||||||
|
<n-button quaternary @click="playOrPauseClick">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<Pause
|
||||||
|
v-if="control_playOrPause"
|
||||||
|
:style="{
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<CaretForward
|
||||||
|
v-if="!control_playOrPause"
|
||||||
|
:style="{
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-button quaternary @click="volumeClick">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<VolumeHigh
|
||||||
|
v-if="!control_volume"
|
||||||
|
:style="{
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<VolumeMute
|
||||||
|
v-if="control_volume"
|
||||||
|
:style="{
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-controls">
|
||||||
|
<n-button quaternary @click="fullScreenClick">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<ExpandOutlined
|
||||||
|
:style="{
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted, watch, ref } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ExpandOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { Pause, CaretForward, VolumeHigh, VolumeMute } from '@vicons/ionicons5';
|
||||||
|
|
||||||
|
let BASE_URL = 'http://111.17.207.220:9001/api';
|
||||||
|
|
||||||
|
const props = defineProps([
|
||||||
|
'deviceId',
|
||||||
|
'channelId',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'timestamp',
|
||||||
|
'videoMuted',
|
||||||
|
]);
|
||||||
|
let clPlayer: any = null;
|
||||||
|
|
||||||
|
// 开始播放/暂停播放
|
||||||
|
const control_playOrPause = ref(true);
|
||||||
|
// 音量:true为静音
|
||||||
|
const control_volume = ref(true);
|
||||||
|
// 全屏
|
||||||
|
const control_fullScreen = ref(false);
|
||||||
|
|
||||||
|
// 开始播放/暂停播放方法
|
||||||
|
function playOrPauseClick() {
|
||||||
|
control_playOrPause.value = !control_playOrPause.value;
|
||||||
|
if (control_playOrPause.value) {
|
||||||
|
clPlayer.play();
|
||||||
|
} else {
|
||||||
|
clPlayer.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 音量
|
||||||
|
function volumeClick() {
|
||||||
|
control_volume.value = !control_volume.value;
|
||||||
|
if (control_volume.value) {
|
||||||
|
clPlayer.volume(0);
|
||||||
|
} else {
|
||||||
|
clPlayer.volume(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全屏
|
||||||
|
function fullScreenClick() {
|
||||||
|
clPlayer.fullScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取kitToken
|
||||||
|
function getKitToken(deviceId, channelId) {
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
url: BASE_URL + '/Camera/getKitToken?deviceId=' + deviceId + '&channelId=0' + '&type=0',
|
||||||
|
}).then((res) => {
|
||||||
|
let kitToken = res.data.result.data.kitToken;
|
||||||
|
loadMonitorVideo(deviceId, kitToken, channelId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始
|
||||||
|
function loadMonitorVideo(deviceId, kitToken, channelId) {
|
||||||
|
// 结束上一个
|
||||||
|
closeMonitorVideo();
|
||||||
|
|
||||||
|
clPlayer = new imouPlayer({
|
||||||
|
id: 'root' + props.timestamp,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height - 35,
|
||||||
|
deviceId: deviceId,
|
||||||
|
token: kitToken,
|
||||||
|
channelId: channelId,
|
||||||
|
type: 1,
|
||||||
|
streamId: 0,
|
||||||
|
recordType: 'cloud',
|
||||||
|
code: '',
|
||||||
|
controls: true,
|
||||||
|
});
|
||||||
|
clPlayer.volume(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 结束
|
||||||
|
function closeMonitorVideo() {
|
||||||
|
if (clPlayer != null) {
|
||||||
|
clPlayer.destroy();
|
||||||
|
clPlayer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.deviceId,
|
||||||
|
() => {
|
||||||
|
getKitToken(props.deviceId, props.channelId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getKitToken(props.deviceId, props.channelId);
|
||||||
|
control_volume.value = props.videoMuted;
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
closeMonitorVideo();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.box {
|
||||||
|
width: v-bind('`${props.width}px`');
|
||||||
|
height: v-bind('`${props.height}px`');
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
.box-container {
|
||||||
|
width: v-bind('`${props.width}px`');
|
||||||
|
height: v-bind('`${props.height-35}px`');
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
background: #000000;
|
||||||
|
height: 35px;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-controls {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="box">
|
||||||
|
<easy-player
|
||||||
|
:video-url="getUrl(props.serialNumberValue)"
|
||||||
|
live
|
||||||
|
autoplay
|
||||||
|
fluent="true"
|
||||||
|
:style="{ width: props.width + 'px', height: props.height + 'px' }"
|
||||||
|
:stretch="props.videoFit"
|
||||||
|
:muted="props.videoMuted"
|
||||||
|
:loop="props.videoLoop"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps([
|
||||||
|
'serialNumberValue',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'videoMuted',
|
||||||
|
'videoLoop',
|
||||||
|
'videoFit',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function getUrl(value) {
|
||||||
|
return 'http://221.2.83.254:7012/live/' + value + '.m3u8';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.box {
|
||||||
|
width: v-bind('`${props.width}px`');
|
||||||
|
height: v-bind('`${props.height}px`');
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
::v-deep .vjs-bitrate-control {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<video
|
||||||
|
:id="'ZhiGan_ModalVideo' + props.timestamp"
|
||||||
|
class="TCPlayer-video-container"
|
||||||
|
preload="auto"
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
playsinline
|
||||||
|
autoplay
|
||||||
|
:loop="props.videoLoop"
|
||||||
|
:muted="props.videoMuted"
|
||||||
|
:style="{
|
||||||
|
width: props.width + 'px',
|
||||||
|
height: props.height + 'px',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted, ref, watch, nextTick } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps([
|
||||||
|
'serialNumberValue',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'timestamp',
|
||||||
|
'videoLoop',
|
||||||
|
'videoMuted',
|
||||||
|
'videoFit',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 视频控件初始化
|
||||||
|
let player: any = null;
|
||||||
|
|
||||||
|
function handlerPlayVideo() {
|
||||||
|
nextTick(() => {
|
||||||
|
if (player) {
|
||||||
|
player.src(props.serialNumberValue);
|
||||||
|
} else {
|
||||||
|
player = TCPlayer('ZhiGan_ModalVideo' + props.timestamp, {});
|
||||||
|
player.src(props.serialNumberValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePlayerVideo() {
|
||||||
|
if (player) {
|
||||||
|
player.dispose();
|
||||||
|
player = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.serialNumberValue,
|
||||||
|
() => {
|
||||||
|
handlerPlayVideo();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
handlerPlayVideo();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
closePlayerVideo();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
handlerPlayVideo,
|
||||||
|
closePlayerVideo,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
video {
|
||||||
|
display: block;
|
||||||
|
object-fit: v-bind('props.videoFit');
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .vjs-live-control .vjs-live-display {
|
||||||
|
width: 100px !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,10 @@
|
|||||||
|
// 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 MonitorHK } from './video/monitorHK.vue';
|
||||||
|
// export { default as MonitorLC } from './video/monitorLC.vue';
|
||||||
|
// export { default as MonitorQX } from './video/monitorQX.vue';
|
||||||
|
// export { default as MonitorTX } from './video/monitorTX.vue';
|
||||||
|
// export { default as PanoViewer } from './PanoViewer.vue';
|
||||||
|
// export { default as Map } from './Map.vue';
|
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div> </div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
<style lang="less"></style>
|
Loading…
Reference in New Issue