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([]); // 已添加相机列表 const activeGroup = ref(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(''); 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;