|
|
@@ -1,436 +0,0 @@
|
|
|
-import { computed, h, onBeforeUnmount, onMounted, ref, render } from 'vue';
|
|
|
-import Konva from 'konva';
|
|
|
-import cameraImg from '@/assets/camera/cameraImg.png';
|
|
|
-import favoritesImg from '@/assets/camera/favorites.png';
|
|
|
-import OptBar from '../components/CameraOptBar.vue';
|
|
|
-import DefaultTip from '../components/DefaultTip.vue';
|
|
|
-import { TipPositionEnum } from '../type';
|
|
|
-import { ElMessage } from 'element-plus';
|
|
|
-import { useGlobSetting } from '@/hooks/setting';
|
|
|
-import urlJoin from 'url-join';
|
|
|
-import useMiniMap from '../use-mini-map';
|
|
|
-import { storeToRefs } from 'pinia';
|
|
|
-
|
|
|
-export function useMapEditor() {
|
|
|
- const miniMap = useMiniMap();
|
|
|
- const { shopCameraList } = storeToRefs(miniMap);
|
|
|
-
|
|
|
- // let initWidth; // 默认宽度
|
|
|
- // let initHeight; // 默认高度
|
|
|
- let stage: Konva.Stage | null = null;
|
|
|
- let layer: Konva.Layer | null = null;
|
|
|
- let copyLayer: Konva.Layer | null = null;
|
|
|
- let defaultIcon: Konva.Image | null = null; // 默认相机的图标shape
|
|
|
- const addedCameras = ref<string[]>([]); // 已添加相机列表
|
|
|
- const activeGroup = ref<Konva.Group | null>(null); // transformer激活的相机
|
|
|
- const defaultCameraId = ref(''); // 默认相机的ID
|
|
|
- let optBlock: HTMLDivElement | null = null; // 鼠标右击弹出的选项组
|
|
|
- let defaultTip: HTMLDivElement | null = null; // 默认相机悬浮tip
|
|
|
- let isTransform = false; // 是否再变换中
|
|
|
- const activeCameraId = computed(() => activeGroup.value?.id()); // 当前选中相机ID
|
|
|
- const bgImgUrl = ref<string>('');
|
|
|
-
|
|
|
- const globSetting = useGlobSetting();
|
|
|
-
|
|
|
- /** 容器初始化 */
|
|
|
- const initContainer = (opt: Konva.StageConfig) => {
|
|
|
- // initWidth = opt.width || 0;
|
|
|
- // initHeight = opt.height || 0;
|
|
|
- stage = new Konva.Stage(opt);
|
|
|
- stage.on('click tap', handleStageClick);
|
|
|
- window.stage = stage;
|
|
|
- layer = new Konva.Layer();
|
|
|
- copyLayer = new Konva.Layer();
|
|
|
- stage.add(layer);
|
|
|
- stage.add(copyLayer);
|
|
|
- addDefaultIcon();
|
|
|
- };
|
|
|
-
|
|
|
- /** 初始生成默认相机的图标shape,但不可见 */
|
|
|
- const addDefaultIcon = () => {
|
|
|
- const favImg = new Image();
|
|
|
- favImg.onload = () => {
|
|
|
- defaultIcon = new Konva.Image({
|
|
|
- x: 18,
|
|
|
- y: -16,
|
|
|
- width: 16,
|
|
|
- height: 16,
|
|
|
- image: favImg,
|
|
|
- id: 'defaultIcon',
|
|
|
- visible: false,
|
|
|
- rotation: 0,
|
|
|
- });
|
|
|
- bindBaseEvt(defaultIcon);
|
|
|
- layer?.add(defaultIcon);
|
|
|
- layer?.batchDraw();
|
|
|
- };
|
|
|
- favImg.src = favoritesImg;
|
|
|
- };
|
|
|
-
|
|
|
- /** 更换背景图时根据图片大小重置容器宽高 */
|
|
|
- const resizeContainer = (width, height) => {
|
|
|
- // const newWidth = width > initWidth ? width : initWidth;
|
|
|
- // const newHeight = height > initHeight ? height : initHeight;
|
|
|
- // stage?.width(newWidth);
|
|
|
- // stage?.height(newHeight);
|
|
|
- stage?.width(width);
|
|
|
- stage?.height(height);
|
|
|
- };
|
|
|
-
|
|
|
- /** 添加背景 */
|
|
|
- const addBg = () => {
|
|
|
- const imgUrl = urlJoin(globSetting.imgUrl!, bgImgUrl.value);
|
|
|
- const bgNode = layer?.find('#bgImg')[0] as Konva.Image;
|
|
|
- const bgImg = new Image();
|
|
|
- bgImg.onload = () => {
|
|
|
- // 判断是否已有背景
|
|
|
- if (!bgNode) {
|
|
|
- const mapBg = new Konva.Image({
|
|
|
- x: 0,
|
|
|
- y: 0,
|
|
|
- image: bgImg,
|
|
|
- width: bgImg.width,
|
|
|
- height: bgImg.height,
|
|
|
- id: 'bgImg',
|
|
|
- });
|
|
|
- layer?.add(mapBg);
|
|
|
- mapBg.moveToBottom();
|
|
|
- } else {
|
|
|
- bgNode.width(bgImg.width);
|
|
|
- bgNode.height(bgImg.height);
|
|
|
- bgNode.image(bgImg);
|
|
|
- }
|
|
|
- resizeContainer(bgImg.width, bgImg.height);
|
|
|
- layer?.batchDraw();
|
|
|
- };
|
|
|
- bgImg.src = imgUrl;
|
|
|
- };
|
|
|
-
|
|
|
- /** 变更需要激活transform的相机 */
|
|
|
- const attachTransformer = (group: Konva.Group): Konva.Transformer => {
|
|
|
- activeGroup.value?.draggable(false);
|
|
|
- activeGroup.value = group;
|
|
|
- group.draggable(true);
|
|
|
- stage?.find?.('Transformer')[0]?.destroy(); // 清除现有transformer
|
|
|
- const id = group.id();
|
|
|
- const tr = new Konva.Transformer({
|
|
|
- keepRatio: true,
|
|
|
- rotateAnchorOffset: 30,
|
|
|
- rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315],
|
|
|
- enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
|
- id: 'tr_' + id,
|
|
|
- });
|
|
|
- tr.nodes([group]);
|
|
|
- layer?.add(tr);
|
|
|
- layer?.draw();
|
|
|
-
|
|
|
- group.on('dragstart', handleDragStart);
|
|
|
- group.on('dragstart', handleDragEnd);
|
|
|
-
|
|
|
- return tr;
|
|
|
- };
|
|
|
-
|
|
|
- /** 添加相机 */
|
|
|
- const addCamera = (id: string) => {
|
|
|
- const group = new Konva.Group({
|
|
|
- x: 50,
|
|
|
- y: 50,
|
|
|
- id,
|
|
|
- draggable: true,
|
|
|
- name: 'group',
|
|
|
- });
|
|
|
- const camImg = new Image();
|
|
|
- camImg.onload = () => {
|
|
|
- const cameraIcon = new Konva.Image({
|
|
|
- width: 52,
|
|
|
- height: 37,
|
|
|
- image: camImg,
|
|
|
- name: 'image',
|
|
|
- });
|
|
|
- group.add(cameraIcon);
|
|
|
- layer?.add(group);
|
|
|
- bindBaseEvt(cameraIcon);
|
|
|
- const tr = attachTransformer(group); // 添加的相机默认激活transformer
|
|
|
-
|
|
|
- addedCameras.value.push(id);
|
|
|
- // 如果是唯一相机,设置为默认相机
|
|
|
- if (addedCameras.value.length === 1) {
|
|
|
- defaultIcon?.show();
|
|
|
- setDefaultCamera(group, tr);
|
|
|
- tr.forceUpdate();
|
|
|
- }
|
|
|
- };
|
|
|
- camImg.src = cameraImg;
|
|
|
- };
|
|
|
-
|
|
|
- /** 变更默认相机 */
|
|
|
- const setDefaultCamera = (node: Konva.Group, tr?: Konva.Transformer) => {
|
|
|
- defaultIcon?.moveTo(node);
|
|
|
- tr?.forceUpdate();
|
|
|
- defaultCameraId.value = node.id();
|
|
|
- };
|
|
|
-
|
|
|
- /** 创建右键选项组 */
|
|
|
- const createOptBlock = (node: Konva.Group, x: number, y: number) => {
|
|
|
- const id = node.id();
|
|
|
- optBlock = document.createElement('div') as HTMLDivElement;
|
|
|
- optBlock.setAttribute('style', `position: absolute; left: ${x}px; top: ${y}px;`);
|
|
|
- const optBar = h(OptBar, {
|
|
|
- disabled: id === defaultCameraId.value,
|
|
|
- onSetDefault: () => {
|
|
|
- const tr = layer?.find(`#tr_${id}`)[0] as Konva.Transformer;
|
|
|
- setDefaultCamera(node, tr);
|
|
|
- destoryOptBlock();
|
|
|
- },
|
|
|
- });
|
|
|
- render(optBar, optBlock);
|
|
|
- const parentEl = document.getElementById('drawContainer') as HTMLDivElement;
|
|
|
- parentEl.append(optBlock);
|
|
|
- };
|
|
|
-
|
|
|
- /** 删除右键选项组 */
|
|
|
- const destoryOptBlock = () => {
|
|
|
- optBlock?.remove();
|
|
|
- optBlock = null;
|
|
|
- };
|
|
|
-
|
|
|
- /** 创建默认tip */
|
|
|
- const createDefaultTip = (x: number, y: number, pos: TipPositionEnum) => {
|
|
|
- if (isTransform) {
|
|
|
- return;
|
|
|
- }
|
|
|
- defaultTip = document.createElement('div') as HTMLDivElement;
|
|
|
- defaultTip.setAttribute('style', `position: absolute; left: ${x}px; top: ${y}px;`);
|
|
|
- const tipInstance = h(DefaultTip, { position: pos });
|
|
|
- render(tipInstance, defaultTip);
|
|
|
- const parentEl = document.getElementById('drawContainer') as HTMLDivElement;
|
|
|
- parentEl.append(defaultTip);
|
|
|
- };
|
|
|
-
|
|
|
- /** 删除默认tip */
|
|
|
- const destoryDefaultTip = () => {
|
|
|
- defaultTip?.remove();
|
|
|
- defaultTip = null;
|
|
|
- };
|
|
|
-
|
|
|
- /** 删除相机 */
|
|
|
- const deleteCamera = () => {
|
|
|
- // 判断是否为默认相机,默认相机不允许删除
|
|
|
- if (activeGroup.value?.id() === defaultCameraId.value) {
|
|
|
- ElMessage.error({
|
|
|
- message: '无法删除默认相机',
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
- const index = addedCameras.value.findIndex((item) => item === activeGroup.value?.id());
|
|
|
- index >= 0 && addedCameras.value.splice(index, 1);
|
|
|
- activeGroup.value?.destroy();
|
|
|
- stage!.find('Transformer')[0]?.destroy();
|
|
|
- layer?.draw();
|
|
|
- };
|
|
|
-
|
|
|
- /** 鼠标悬浮事件 */
|
|
|
- const handleMouseOver = (e) => {
|
|
|
- // 禁用浏览器默认鼠标事件
|
|
|
- document.oncontextmenu = () => {
|
|
|
- return false;
|
|
|
- };
|
|
|
- const group = e.target.parent;
|
|
|
- // 如果悬浮的相机是默认相机,弹出默认tip
|
|
|
- if (group.id() === defaultCameraId.value) {
|
|
|
- let pos = TipPositionEnum.TOP;
|
|
|
- const tipPosition = defaultIcon?.absolutePosition();
|
|
|
- let x = Number(tipPosition?.x.toFixed(2)) || 0;
|
|
|
- let y = Number(tipPosition?.y.toFixed(2)) || 0;
|
|
|
- const angle = group.rotation() >= 0 ? group.rotation() : group.rotation() + 360;
|
|
|
- if (angle >= 30) {
|
|
|
- if (angle <= 150) {
|
|
|
- pos = TipPositionEnum.RIGHT;
|
|
|
- x += 26;
|
|
|
- y -= 17;
|
|
|
- } else if (angle <= 210) {
|
|
|
- pos = TipPositionEnum.BOTTOM;
|
|
|
- y += 26;
|
|
|
- x -= 50;
|
|
|
- } else {
|
|
|
- pos = TipPositionEnum.LEFT;
|
|
|
- x -= 121;
|
|
|
- y -= 25;
|
|
|
- }
|
|
|
- } else {
|
|
|
- y -= 61;
|
|
|
- x -= 43;
|
|
|
- }
|
|
|
- createDefaultTip(x, y, pos);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 鼠标离开事件 */
|
|
|
- const handleMouseLeave = () => {
|
|
|
- // 恢复浏览器默认事件
|
|
|
- document.oncontextmenu = () => {
|
|
|
- return true;
|
|
|
- };
|
|
|
- defaultTip && destoryDefaultTip();
|
|
|
- };
|
|
|
-
|
|
|
- /** 开始拖拽事件 */
|
|
|
- const handleDragStart = () => {
|
|
|
- isTransform = true;
|
|
|
- destoryDefaultTip();
|
|
|
- destoryOptBlock();
|
|
|
- };
|
|
|
-
|
|
|
- /** 结束拖拽事件 */
|
|
|
- const handleDragEnd = () => {
|
|
|
- isTransform = false;
|
|
|
- };
|
|
|
-
|
|
|
- /** 全局点击事件 */
|
|
|
- const handleStageClick = (e) => {
|
|
|
- // 点击舞台取消现有激活的transformer
|
|
|
- if (e.target === stage) {
|
|
|
- stage!.find('Transformer')[0].destroy();
|
|
|
- layer!.draw();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 判断点击对象是否为相机
|
|
|
- if (!e.target.hasName('image')) {
|
|
|
- return;
|
|
|
- }
|
|
|
- const parent = e.target.parent;
|
|
|
- if (!parent.hasName('group')) {
|
|
|
- return;
|
|
|
- }
|
|
|
- const group = e.target.parent;
|
|
|
- attachTransformer(group);
|
|
|
- // 判断是否为右键点击
|
|
|
- if (e.evt.button === 2) {
|
|
|
- createOptBlock(group, e.evt.offsetX + 20, e.evt.offsetY);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 键盘点击事件 */
|
|
|
- const handleKeyDown = (e) => {
|
|
|
- // 删除键
|
|
|
- if (e.keyCode === 46 || e.code === 'Delete') {
|
|
|
- deleteCamera();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 基础监听事件绑定
|
|
|
- const bindBaseEvt = (node: Konva.Node) => {
|
|
|
- // node.on('transform', handleDragStart);
|
|
|
- // node.on('transformend', handleDragEnd);
|
|
|
- node.on('mouseover', handleMouseOver);
|
|
|
- node.on('mouseleave', handleMouseLeave);
|
|
|
- };
|
|
|
-
|
|
|
- /** 输出布局json */
|
|
|
- const toJson = () => {
|
|
|
- const json = stage!.toJSON();
|
|
|
- const cameras = JSON.parse(json)
|
|
|
- .children[0].children.filter((node) => node.className === 'Group')
|
|
|
- .map((item) => {
|
|
|
- return {
|
|
|
- cameraId: item.attrs.id,
|
|
|
- rotation: Number((item.attrs.rotation | 0).toFixed(2)),
|
|
|
- x: Math.round(item.attrs.x | 0),
|
|
|
- y: Math.round(item.attrs.y | 0),
|
|
|
- scaleX: Number((item.attrs.scaleX | 1).toFixed(1)),
|
|
|
- scaleY: Number((item.attrs.scaleY | 1).toFixed(1)),
|
|
|
- url: shopCameraList.value.find((cam) => cam.code === item.attrs.id)?.pushstreamIp || '',
|
|
|
- };
|
|
|
- });
|
|
|
- const layout = {
|
|
|
- bgInfo: {
|
|
|
- bgImg: bgImgUrl.value,
|
|
|
- width: layer?.find('#bgImg')[0].width(),
|
|
|
- height: layer?.find('#bgImg')[0].height(),
|
|
|
- },
|
|
|
- defaultCameraId: defaultCameraId.value,
|
|
|
- cameraList: cameras,
|
|
|
- };
|
|
|
-
|
|
|
- return JSON.stringify(layout);
|
|
|
- };
|
|
|
-
|
|
|
- /** 导入布局json */
|
|
|
- const createMap = (layout) => {
|
|
|
- // const layout = JSON.parse(json);
|
|
|
- bgImgUrl.value = layout.bgInfo.bgImg;
|
|
|
- addBg();
|
|
|
- layout.cameraList.forEach((camera) => {
|
|
|
- const group = new Konva.Group({
|
|
|
- x: camera.x,
|
|
|
- y: camera.y,
|
|
|
- id: camera.cameraId,
|
|
|
- rotation: camera.rotation,
|
|
|
- scaleX: camera.scaleX,
|
|
|
- scaleY: camera.scaleY,
|
|
|
- draggable: false,
|
|
|
- name: 'group',
|
|
|
- });
|
|
|
- const camImg = new Image();
|
|
|
- camImg.onload = () => {
|
|
|
- const cameraIcon = new Konva.Image({
|
|
|
- width: 52,
|
|
|
- height: 37,
|
|
|
- image: camImg,
|
|
|
- name: 'image',
|
|
|
- });
|
|
|
- group.add(cameraIcon);
|
|
|
- layer?.add(group);
|
|
|
- bindBaseEvt(cameraIcon);
|
|
|
- addedCameras.value.push(camera.cameraId);
|
|
|
-
|
|
|
- if (camera.cameraId === layout.defaultCameraId) {
|
|
|
- setDefaultCamera(group);
|
|
|
- defaultIcon?.show();
|
|
|
- }
|
|
|
-
|
|
|
- if (addedCameras.value.length === layout.cameraList.length) {
|
|
|
- layer?.batchDraw();
|
|
|
- }
|
|
|
- };
|
|
|
- camImg.src = cameraImg;
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- const resetMap = () => {
|
|
|
- defaultIcon?.moveTo(copyLayer);
|
|
|
- layer?.clear();
|
|
|
- layer?.removeChildren();
|
|
|
- defaultIcon?.moveTo(layer);
|
|
|
- addedCameras.value = [];
|
|
|
- activeGroup.value = null;
|
|
|
- defaultCameraId.value = '';
|
|
|
- isTransform = false;
|
|
|
- bgImgUrl.value = '';
|
|
|
- };
|
|
|
-
|
|
|
- onMounted(() => {
|
|
|
- window.addEventListener('keydown', handleKeyDown);
|
|
|
- });
|
|
|
-
|
|
|
- onBeforeUnmount(() => {
|
|
|
- window.removeEventListener('keydown', handleKeyDown);
|
|
|
- });
|
|
|
-
|
|
|
- return {
|
|
|
- defaultCameraId,
|
|
|
- activeCameraId,
|
|
|
- addedCameras,
|
|
|
- bgImgUrl,
|
|
|
- initContainer,
|
|
|
- addBg,
|
|
|
- addCamera,
|
|
|
- destoryOptBlock,
|
|
|
- toJson,
|
|
|
- createMap,
|
|
|
- resetMap,
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-export default useMapEditor;
|