From 37a84e66917d6e22e03e31b0a115e2c16d23ed21 Mon Sep 17 00:00:00 2001 From: 朱俊杰 <502612493@qq.com> Date: 星期四, 10 二月 2022 14:45:33 +0800 Subject: [PATCH] 新增实时监控功能 --- web_src/src/api/deviceApi.js | 19 src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.xml | 37 + src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java | 33 src/main/java/com/genersoft/iot/vmp/utils/node/INode.java | 42 + src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeMerger.java | 51 ++ src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java | 9 web_src/src/components/live.vue | 357 +++++++++++++++ src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTreeNode.java | 20 pom.xml | 11 src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java | 7 src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java | 8 src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java | 21 src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeManager.java | 68 ++ web_src/src/components/channelTreeItem.vue | 74 +++ src/main/java/com/genersoft/iot/vmp/utils/node/ForestNode.java | 28 + src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java | 41 + web_src/src/router/index.js | 5 src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTree.java | 50 ++ web_src/src/components/UiHeader.vue | 1 src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java | 54 ++ web_src/src/components/channelTree.vue | 70 +++ src/main/java/com/genersoft/iot/vmp/utils/CollectionUtil.java | 12 web_src/src/components/jessibuca.vue | 317 +++++++++++++ src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java | 3 24 files changed, 1,322 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index abf4e9c..9331de3 100644 --- a/pom.xml +++ b/pom.xml @@ -277,5 +277,16 @@ </plugin> </plugins> + <resources> + <resource> + <directory>src/main/resources</directory> + </resource> + <resource> + <directory>src/main/java</directory> + <includes> + <include>**/*.xml</include> + </includes> + </resource> + </resources> </build> </project> diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java index 5e2745a..a188571 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree; import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; import com.github.pagehelper.PageInfo; @@ -94,6 +95,13 @@ public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit); /** + * 鑾峰彇鏌愪釜璁惧鐨勯�氶亾鏍� + * @param deviceId 璁惧ID + * @return + */ + List<DeviceChannelTree> tree(String deviceId); + + /** * 鑾峰彇鏌愪釜璁惧鐨勯�氶亾鍒楄〃 * * @param deviceId 璁惧ID diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java index 7f52f79..896d730 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree; import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; @@ -201,4 +202,6 @@ @Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND status=1") List<DeviceChannel> queryOnlineChannelsByDeviceId(String deviceId); + + List<DeviceChannelTree> tree(String deviceId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.xml b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.xml new file mode 100644 index 0000000..ce69d22 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper"> + + <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 --> + <resultMap id="treeNodeResultMap" type="com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTreeNode"> + <id column="id" property="id"/> + <result column="parentId" property="parentId"/> + <result column="status" property="status"/> + <result column="title" property="title"/> + <result column="value" property="value"/> + <result column="key" property="key"/> + <result column="deviceId" property="deviceId"/> + <result column="channelId" property="channelId"/> + <result column="longitude" property="lng"/> + <result column="latitude" property="lat"/> + </resultMap> + + + <select id="tree" resultMap="treeNodeResultMap"> + SELECT + channelId, + channelId as id, + deviceId, + parentId, + status, + name as title, + channelId as "value", + channelId as "key", + channelId, + longitude, + latitude + from device_channel + where deviceId = #{deviceId} + </select> + +</mapper> diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java index f43f92f..57b30f1 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java @@ -13,6 +13,8 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.dao.*; +import com.genersoft.iot.vmp.utils.node.ForestNodeMerger; +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree; import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; @@ -329,6 +331,11 @@ } @Override + public List<DeviceChannelTree> tree(String deviceId) { + return ForestNodeMerger.merge(deviceChannelMapper.tree(deviceId)); + } + + @Override public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) { return deviceChannelMapper.queryChannels(deviceId, null,null, null, null); } diff --git a/src/main/java/com/genersoft/iot/vmp/utils/CollectionUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/CollectionUtil.java new file mode 100644 index 0000000..4f7ca1f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/CollectionUtil.java @@ -0,0 +1,12 @@ +package com.genersoft.iot.vmp.utils; + +import java.util.Arrays; + +public class CollectionUtil { + + public static <T> boolean contains(T[] array, final T element) { + return array != null && Arrays.stream(array).anyMatch((x) -> { + return ObjectUtils.nullSafeEquals(x, element); + }); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java b/src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java new file mode 100644 index 0000000..1f429bc --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.utils; + +import java.util.Arrays; + +public class ObjectUtils { + public static boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } else if (o1 != null && o2 != null) { + if (o1.equals(o2)) { + return true; + } else { + return o1.getClass().isArray() && o2.getClass().isArray() && arrayEquals(o1, o2); + } + } else { + return false; + } + } + + private static boolean arrayEquals(Object o1, Object o2) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.equals((Object[])((Object[])o1), (Object[])((Object[])o2)); + } else if (o1 instanceof boolean[] && o2 instanceof boolean[]) { + return Arrays.equals((boolean[])((boolean[])o1), (boolean[])((boolean[])o2)); + } else if (o1 instanceof byte[] && o2 instanceof byte[]) { + return Arrays.equals((byte[])((byte[])o1), (byte[])((byte[])o2)); + } else if (o1 instanceof char[] && o2 instanceof char[]) { + return Arrays.equals((char[])((char[])o1), (char[])((char[])o2)); + } else if (o1 instanceof double[] && o2 instanceof double[]) { + return Arrays.equals((double[])((double[])o1), (double[])((double[])o2)); + } else if (o1 instanceof float[] && o2 instanceof float[]) { + return Arrays.equals((float[])((float[])o1), (float[])((float[])o2)); + } else if (o1 instanceof int[] && o2 instanceof int[]) { + return Arrays.equals((int[])((int[])o1), (int[])((int[])o2)); + } else if (o1 instanceof long[] && o2 instanceof long[]) { + return Arrays.equals((long[])((long[])o1), (long[])((long[])o2)); + } else { + return o1 instanceof short[] && o2 instanceof short[] && Arrays.equals((short[]) ((short[]) o1), (short[]) ((short[]) o2)); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java b/src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java new file mode 100644 index 0000000..0de2160 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java @@ -0,0 +1,54 @@ +package com.genersoft.iot.vmp.utils.node; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 鑺傜偣鍩虹被 + * + */ +@Data +public class BaseNode<T> implements INode<T> { + + private static final long serialVersionUID = 1L; + + /** + * 涓婚敭ID + */ + protected String id; + + /** + * 鐖惰妭鐐笽D + */ + protected String parentId; + + /** + * 瀛愬瓩鑺傜偣 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + protected List<T> children = new ArrayList<T>(); + + /** + * 鏄惁鏈夊瓙瀛欒妭鐐� + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Boolean hasChildren; + + /** + * 鏄惁鏈夊瓙瀛欒妭鐐� + * + * @return Boolean + */ + @Override + public Boolean getHasChildren() { + if (children.size() > 0) { + return true; + } else { + return this.hasChildren; + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNode.java b/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNode.java new file mode 100644 index 0000000..0ba7207 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNode.java @@ -0,0 +1,28 @@ +package com.genersoft.iot.vmp.utils.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; + + +/** + * 妫灄鑺傜偣绫� + * + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ForestNode extends BaseNode<ForestNode> { + + private static final long serialVersionUID = 1L; + + /** + * 鑺傜偣鍐呭 + */ + private Object content; + + public ForestNode(String id, String parentId, Object content) { + this.id = id; + this.parentId = parentId; + this.content = content; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeManager.java b/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeManager.java new file mode 100644 index 0000000..de98fdc --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeManager.java @@ -0,0 +1,68 @@ +package com.genersoft.iot.vmp.utils.node; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 妫灄绠$悊绫� + * + * @author smallchill + */ +public class ForestNodeManager<T extends INode<T>> { + + /** + * 妫灄鐨勬墍鏈夎妭鐐� + */ + private final ImmutableMap<String, T> nodeMap; + + /** + * 妫灄鐨勭埗鑺傜偣ID + */ + private final Map<String, Object> parentIdMap = Maps.newHashMap(); + + public ForestNodeManager(List<T> nodes) { + nodeMap = Maps.uniqueIndex(nodes, INode::getId); + } + + /** + * 鏍规嵁鑺傜偣ID鑾峰彇涓�涓妭鐐� + * + * @param id 鑺傜偣ID + * @return 瀵瑰簲鐨勮妭鐐瑰璞� + */ + public INode<T> getTreeNodeAt(String id) { + if (nodeMap.containsKey(id)) { + return nodeMap.get(id); + } + return null; + } + + /** + * 澧炲姞鐖惰妭鐐笽D + * + * @param parentId 鐖惰妭鐐笽D + */ + public void addParentId(String parentId) { + parentIdMap.put(parentId, ""); + } + + /** + * 鑾峰彇鏍戠殑鏍硅妭鐐�(涓�涓.鏋楀搴斿棰楁爲) + * + * @return 鏍戠殑鏍硅妭鐐归泦鍚� + */ + public List<T> getRoot() { + List<T> roots = new ArrayList<>(); + nodeMap.forEach((key, node) -> { + if (node.getParentId() == null || parentIdMap.containsKey(node.getId())) { + roots.add(node); + } + }); + return roots; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeMerger.java b/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeMerger.java new file mode 100644 index 0000000..062d4cd --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeMerger.java @@ -0,0 +1,51 @@ +package com.genersoft.iot.vmp.utils.node; + +import com.genersoft.iot.vmp.utils.CollectionUtil; + +import java.util.List; + +/** + * 妫灄鑺傜偣褰掑苟绫� + * + */ +public class ForestNodeMerger { + + /** + * 灏嗚妭鐐规暟缁勫綊骞朵负涓�涓.鏋楋紙澶氭5鏍戯級锛堝~鍏呰妭鐐圭殑children鍩燂級 + * 鏃堕棿澶嶆潅搴︿负O(n^2) + * + * @param items 鑺傜偣鍩� + * @return 澶氭5鏍戠殑鏍硅妭鐐归泦鍚� + */ + public static <T extends INode<T>> List<T> merge(List<T> items) { + ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items); + items.forEach(forestNode -> { + if (forestNode.getParentId() != null) { + INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId()); + if (node != null) { + node.getChildren().add(forestNode); + } else { + forestNodeManager.addParentId(forestNode.getId()); + } + } + }); + return forestNodeManager.getRoot(); + } + + public static <T extends INode<T>> List<T> merge(List<T> items, String[] parentIds) { + ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items); + items.forEach(forestNode -> { + if (forestNode.getParentId() != null) { + INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId()); + if (CollectionUtil.contains(parentIds, forestNode.getId())){ + forestNodeManager.addParentId(forestNode.getId()); + } else { + if (node != null){ + node.getChildren().add(forestNode); + } + } + } + }); + return forestNodeManager.getRoot(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/node/INode.java b/src/main/java/com/genersoft/iot/vmp/utils/node/INode.java new file mode 100644 index 0000000..4d6ebfc --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/node/INode.java @@ -0,0 +1,42 @@ +package com.genersoft.iot.vmp.utils.node; + +import java.io.Serializable; +import java.util.List; + +/** + * + * 鑺傜偣 + */ +public interface INode<T> extends Serializable { + + /** + * 涓婚敭 + * + * @return String + */ + String getId(); + + /** + * 鐖朵富閿� + * + * @return String + */ + String getParentId(); + + /** + * 瀛愬瓩鑺傜偣 + * + * @return List<T> + */ + List<T> getChildren(); + + /** + * 鏄惁鏈夊瓙瀛欒妭鐐� + * + * @return Boolean + */ + default Boolean getHasChildren() { + return false; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java b/src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java new file mode 100644 index 0000000..9df6f11 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java @@ -0,0 +1,21 @@ +package com.genersoft.iot.vmp.utils.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 鏍戝瀷鑺傜偣绫� + * + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class TreeNode extends BaseNode<TreeNode> { + + private static final long serialVersionUID = 1L; + + private String title; + + private String key; + + private String value; +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTree.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTree.java new file mode 100644 index 0000000..b147a9e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTree.java @@ -0,0 +1,50 @@ +package com.genersoft.iot.vmp.vmanager.bean; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.utils.node.INode; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "DeviceChannelTree瀵硅薄", description = "DeviceChannelTree瀵硅薄") +public class DeviceChannelTree extends DeviceChannel implements INode<DeviceChannelTree> { + private static final long serialVersionUID = 1L; + + /** + * 涓婚敭ID + */ + private String id; + + /** + * 鐖惰妭鐐笽D + */ + private String parentId; + + private String parentName; + + /** + * 瀛愬瓩鑺傜偣 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List<DeviceChannelTree> children; + + /** + * 鏄惁鏈夊瓙瀛欒妭鐐� + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Boolean hasChildren; + + @Override + public List<DeviceChannelTree> getChildren() { + if (this.children == null) { + this.children = new ArrayList<>(); + } + return this.children; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTreeNode.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTreeNode.java new file mode 100644 index 0000000..29d82be --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTreeNode.java @@ -0,0 +1,20 @@ +package com.genersoft.iot.vmp.vmanager.bean; + +import com.genersoft.iot.vmp.utils.node.TreeNode; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class DeviceChannelTreeNode extends TreeNode { + + private Integer status; + + private String deviceId; + + private String channelId; + + private Double lng; + + private Double lat; +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java index f8e2c1c..b4e3eb4 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java @@ -1,32 +1,35 @@ package com.genersoft.iot.vmp.vmanager.bean; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor public class WVPResult<T> { private int code; private String msg; private T data; - public int getCode() { - return code; + private static final Integer SUCCESS = 200; + private static final Integer FAILED = 400; + + public static <T> WVPResult<T> Data(T t, String msg) { + return new WVPResult<>(SUCCESS, msg, t); } - public void setCode(int code) { - this.code = code; + public static <T> WVPResult<T> Data(T t) { + return Data(t, "鎴愬姛"); } - public String getMsg() { - return msg; + public static <T> WVPResult<T> fail(int code, String msg) { + return new WVPResult<>(code, msg, null); } - public void setMsg(String msg) { - this.msg = msg; + public static <T> WVPResult<T> fail(String msg) { + return fail(FAILED, msg); } - public T getData() { - return data; - } - - public void setData(T data) { - this.data = data; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java index d83094e..d9357d2 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java @@ -10,8 +10,10 @@ import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -25,6 +27,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; +import java.util.List; import java.util.UUID; @Api(tags = "鍥芥爣璁惧鏌ヨ", value = "鍥芥爣璁惧鏌ヨ") @@ -431,5 +434,9 @@ return result; } - + @GetMapping("/{deviceId}/tree") + @ApiOperation(value = "閫氶亾鏍戝舰缁撴瀯", notes = "閫氶亾鏍戝舰缁撴瀯") + public WVPResult<List<DeviceChannelTree>> tree(@PathVariable String deviceId) { + return WVPResult.Data(storager.tree(deviceId)); + } } diff --git a/web_src/src/api/deviceApi.js b/web_src/src/api/deviceApi.js new file mode 100644 index 0000000..830164f --- /dev/null +++ b/web_src/src/api/deviceApi.js @@ -0,0 +1,19 @@ +import axios from 'axios'; + +export const tree = (deviceId) => { + return axios({ + url: `/api/device/query/${deviceId}/tree`, + method: 'get' + }) +} + +export const deviceList = (page, count) => { + return axios({ + method: 'get', + url:`/api/device/query/devices`, + params: { + page, + count + } + }) +} \ No newline at end of file diff --git a/web_src/src/components/UiHeader.vue b/web_src/src/components/UiHeader.vue index 6391fe8..4bbf639 100644 --- a/web_src/src/components/UiHeader.vue +++ b/web_src/src/components/UiHeader.vue @@ -2,6 +2,7 @@ <div id="UiHeader"> <el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal"> <el-menu-item index="/">鎺у埗鍙�</el-menu-item> + <el-menu-item index="/live">瀹炴椂鐩戞帶</el-menu-item> <el-menu-item index="/deviceList">璁惧鍒楄〃</el-menu-item> <el-menu-item index="/pushVideoList">鎺ㄦ祦鍒楄〃</el-menu-item> <el-menu-item index="/streamProxyList">鎷夋祦浠g悊</el-menu-item> diff --git a/web_src/src/components/channelTree.vue b/web_src/src/components/channelTree.vue new file mode 100644 index 0000000..ae9eac7 --- /dev/null +++ b/web_src/src/components/channelTree.vue @@ -0,0 +1,70 @@ +<template> + <div> + <el-tree :data="channelList" :props="props" @node-click="sendDevicePush"> + <span slot-scope="{ node }"> + <span v-if="node.isLeaf"> + <i class="el-icon-video-camera" :style="{color:node.disabled==1?'#67C23A':'#F56C6C'}"></i> + </span> + <span v-else> + <i class="el-icon-folder"></i> + </span> + <span> + {{ node.label }} + </span> + </span> + </el-tree> + </div> +</template> +<script> +import ChannelTreeItem from "@/components/channelTreeItem" +import {tree} from '@/api/deviceApi' + +export default { + components: { + ChannelTreeItem, + }, + props:{ + device: { + type: Object, + required: true + } + }, + data() { + return { + loading: false, + channelList: [], + props: { + label: 'title', + children: 'children', + isLeaf: 'hasChildren', + disabled: 'status' + }, + } + }, + computed: { + + }, + mounted() { + this.leafs = [] + this.getTree() + }, + methods: { + getTree() { + this.loading = true + var that = this + tree(this.device.deviceId).then(function (res) { + console.log(res.data.data); + that.channelList = res.data.data; + that.loading = false; + }).catch(function (error) { + console.log(error); + that.loading = false; + }); + }, + sendDevicePush(c) { + if(c.hasChildren) return + this.$emit('sendDevicePush',c) + } + } +} +</script> \ No newline at end of file diff --git a/web_src/src/components/channelTreeItem.vue b/web_src/src/components/channelTreeItem.vue new file mode 100644 index 0000000..7f2a2a5 --- /dev/null +++ b/web_src/src/components/channelTreeItem.vue @@ -0,0 +1,74 @@ +<template> + <div> + <!-- <div :index="item.key" v-for="(item,i) in list" :key="i+'-'"> + <el-submenu v-if="item.hasChildren"> + <template slot="title"> + <i class="el-icon-video-camera"></i> + <span slot="title">{{item.title || item.deviceId}}</span> + </template> + <channel-list :list="item.children" @sendDevicePush="sendDevicePush"></channel-list> + </el-submenu> + <el-menu-item v-else :index="item.key" @click="sendDevicePush(item)"> + <template slot="title" > + <i class="el-icon-switch-button" :style="{color:item.status==1?'#67C23A':'#F56C6C'}"></i> + <span slot="title">{{item.title}}</span> + </template> + </el-menu-item> + </div> --> + <div > + <template v-if="!item.hasChildren"> + <el-menu-item :index="item.key" @click="sendDevicePush(item)"> + <i class="el-icon-video-camera" :style="{color:item.status==1?'#67C23A':'#F56C6C'}"></i> + {{item.title}} + </el-menu-item> + </template> + + <el-submenu v-else :index="item.key"> + <template slot="title" > + <i class="el-icon-location-outline"></i> + {{item.title}} + </template> + + <template v-for="child in item.children"> + <channel-item + v-if="child.hasChildren" + :item="child" + :key="child.key" + @sendDevicePush="sendDevicePush"/> + <el-menu-item v-else :key="child.key" :index="child.key" @click="sendDevicePush(child)"> + <i class="el-icon-video-camera" :style="{color:child.status==1?'#67C23A':'#F56C6C'}"></i> + {{child.title}} + </el-menu-item> + </template> + </el-submenu> + </div> + </div> +</template> +<script> +export default { + name:'ChannelItem', + props:{ + list:Array, + channelId: String, + item: { + type: Object, + required: true + } + }, + data () { + return { + + } + }, + watch: { + channelId(val) { + console.log(val); + } + }, + methods: { + sendDevicePush(c) { + this.$emit('sendDevicePush',c) + } + } +} +</script> diff --git a/web_src/src/components/jessibuca.vue b/web_src/src/components/jessibuca.vue new file mode 100644 index 0000000..b66a9c6 --- /dev/null +++ b/web_src/src/components/jessibuca.vue @@ -0,0 +1,317 @@ +<template> + <div :id="'jessibuca'+idx" style="width: 100%; height: 100%"> + <div :id="'container'+idx" ref="container" style="width: 100%; height: 100%; background-color: #000" @dblclick="fullscreenSwich"> + <div class="buttons-box" :id="'buttonsBox'+idx"> + <div class="buttons-box-left"> + <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i> + <i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause"></i> + <i class="iconfont icon-stop jessibuca-btn" @click="destroyButton"></i> + <i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="jessibuca.mute()"></i> + <i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="jessibuca.cancelMute()"></i> + </div> + <div class="buttons-box-right"> + <span class="jessibuca-btn">{{kBps}} kb/s</span> +<!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>--> +<!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>--> + <i class="iconfont icon-camera1196054easyiconnet jessibuca-btn" @click="screenshot" style="font-size: 1rem !important"></i> + <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick"></i> + <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich"></i> + <i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich"></i> + </div> + </div> + + </div> + </div> +</template> + +<script> +export default { + name: 'jessibuca', + data() { + return { + jessibuca: null, + playing: false, + isNotMute: false, + quieting: false, + fullscreen: false, + loaded: false, // mute + speed: 0, + performance: "", // 宸ヤ綔鎯呭喌 + kBps: 0, + btnDom: null, + videoInfo: null, + volume: 1, + rotate: 0, + vod: true, // 鐐规挱 + forceNoOffscreen: false, + }; + }, + props: ['videoUrl', 'error', 'hasAudio', 'height','idx'], + mounted () { + window.onerror = (msg) => { + // console.error(msg) + }; + let paramUrl = decodeURIComponent(this.$route.params.url) + this.$nextTick(() =>{ + let dom = document.getElementById("container"+this.idx); + // dom.style.height = (9/16 ) * dom.clientWidth + "px" + if (typeof (this.videoUrl) == "undefined") { + this.videoUrl = paramUrl; + } + this.btnDom = document.getElementById("buttonsBox"+this.idx); + console.log("鍒濆鍖栨椂鐨勫湴鍧�涓�: " + this.videoUrl) + this.play(this.videoUrl) + }) + }, + watch:{ + videoUrl(newData, oldData){ + this.play(newData) + }, + immediate:true + }, + methods: { + create(){ + let options = {}; + console.log(this.$refs.container) + console.log("hasAudio " + !!this.hasAudio) + + this.jessibuca = new window.Jessibuca(Object.assign( + { + container: this.$refs.container, + videoBuffer: 0.2, // 鏈�澶х紦鍐叉椂闀匡紝鍗曚綅绉� + isResize: true, + decoder: "./static/js/jessibuca/index.js", + // text: "WVP-PRO", + // background: "bg.jpg", + loadingText: "鍔犺浇涓�", + hasAudio: !!this.hasAudio, + debug: false, + timeout:5, + supportDblclickFullscreen: false, // 鏄惁鏀寔灞忓箷鐨勫弻鍑讳簨浠讹紝瑙﹀彂鍏ㄥ睆锛屽彇娑堝叏灞忎簨浠躲�� + operateBtns: { + fullscreen: false, + screenshot: false, + play: false, + audio: false, + }, + record: "record", + vod: this.vod, + forceNoOffscreen: this.forceNoOffscreen, + isNotMute: this.isNotMute, + }, + options + )); + + let _this = this; + this.jessibuca.on("load", function () { + console.log("on load init"); + }); + + this.jessibuca.on("log", function (msg) { + console.log("on log", msg); + }); + this.jessibuca.on("record", function (msg) { + console.log("on record:", msg); + }); + this.jessibuca.on("pause", function () { + _this.playing = false; + }); + this.jessibuca.on("play", function () { + _this.playing = true; + }); + this.jessibuca.on("fullscreen", function (msg) { + console.log("on fullscreen", msg); + _this.fullscreen = msg + }); + + this.jessibuca.on("mute", function (msg) { + console.log("on mute", msg); + _this.isNotMute = !msg; + }); + this.jessibuca.on("audioInfo", function (msg) { + // console.log("audioInfo", msg); + }); + + this.jessibuca.on("videoInfo", function (msg) { + this.videoInfo = msg; + // console.log("videoInfo", msg); + + }); + + this.jessibuca.on("bps", function (bps) { + // console.log('bps', bps); + + }); + let _ts = 0; + this.jessibuca.on("timeUpdate", function (ts) { + // console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts); + _ts = ts; + }); + + this.jessibuca.on("videoInfo", function (info) { + console.log("videoInfo", info); + }); + + this.jessibuca.on("error", (error) =>{ + console.log("error", error); + this.pause() + }); + + this.jessibuca.on("timeout", ()=> { + console.log("timeout"); + // this.pause() + this.play(this.videoUrl) + }); + + this.jessibuca.on('start', function () { + console.log('start'); + }) + + this.jessibuca.on("performance", function (performance) { + let show = "鍗¢】"; + if (performance === 2) { + show = "闈炲父娴佺晠"; + } else if (performance === 1) { + show = "娴佺晠"; + } + _this.performance = show; + }); + this.jessibuca.on('buffer', function (buffer) { + // console.log('buffer', buffer); + }) + + this.jessibuca.on('stats', function (stats) { + // console.log('stats', stats); + }) + + this.jessibuca.on('kBps', function (kBps) { + _this.kBps = Math.round(kBps); + }); + + // 鏄剧ず鏃堕棿鎴� PTS + this.jessibuca.on('videoFrame', function () { + + }) + + // + this.jessibuca.on('metadata', function () { + + }); + }, + playBtnClick: function (event){ + this.play(this.videoUrl) + }, + play: function (url) { + console.log(url) + + if (this.jessibuca) { + this.destroy(); + } + if(!url){ + return + } + this.create(); + this.jessibuca.on("play", () => { + this.playing = true; + this.loaded = true; + this.quieting = this.jessibuca.quieting; + }); + if (this.jessibuca.hasLoaded()) { + this.jessibuca.play(url); + } else { + this.jessibuca.on("load", () => { + console.log("load 鎾斁") + this.jessibuca.play(url); + }); + } + }, + pause: function () { + if (this.jessibuca) { + this.jessibuca.pause(); + } + this.playing = false; + this.err = ""; + this.performance = ""; + }, + destroy: function () { + if (this.jessibuca) { + this.jessibuca.destroy(); + } + if (document.getElementById("buttonsBox"+this.idx) == null) { + document.getElementById("container"+this.idx).appendChild(this.btnDom) + } + this.jessibuca = null; + this.playing = false; + this.err = ""; + this.performance = ""; + + }, + eventcallbacK: function(type, message) { + // console.log("player 浜嬩欢鍥炶皟") + // console.log(type) + // console.log(message) + }, + fullscreenSwich: function (){ + let isFull = this.isFullscreen() + this.jessibuca.setFullscreen(!isFull) + this.fullscreen = !isFull; + }, + isFullscreen: function (){ + return document.fullscreenElement || + document.msFullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || false; + }, + resize(){ + this.jessibuca.resize() + }, + screenshot(){ + this.jessibuca.screenshot('鎴浘','png',0.5) + // let base64 = this.jessibuca.screenshot("shot","jpeg",0.5,'base64') + // this.$emit('screenshot',base64) + }, + destroyButton() { + this.$emit('destroy', this.idx) + this.destroy() + } + }, + destroyed() { + if (this.jessibuca) { + this.jessibuca.destroy(); + } + this.playing = false; + this.loaded = false; + this.performance = ""; + }, +} +</script> + +<style> + .buttons-box{ + width: 100%; + height: 28px; + background-color: rgba(43, 51, 63, 0.7); + position: absolute; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + left: 0; + bottom: 0; + user-select: none; + z-index: 10; + } + .jessibuca-btn{ + width: 20px; + color: rgb(255, 255, 255); + line-height: 27px; + margin: 0px 10px; + padding: 0px 2px; + cursor: pointer; + text-align: center; + font-size: 0.8rem !important; + } + .buttons-box-right { + position: absolute; + right: 0; + } +</style> diff --git a/web_src/src/components/live.vue b/web_src/src/components/live.vue new file mode 100644 index 0000000..beab9e4 --- /dev/null +++ b/web_src/src/components/live.vue @@ -0,0 +1,357 @@ +<template> + <div id="devicePosition" style="height: 100%"> + <el-container style="height: 100%"> + <el-header> + <uiHeader></uiHeader> + </el-header> + <el-container v-loading="loading" element-loading-text="鎷煎懡鍔犺浇涓�"> + <el-aside width="300px" style="background-color: #ffffff"> + <div style="text-align: center;padding-top: 20px;">璁惧鍒楄〃</div> + <el-menu v-loading="loading"> + <el-submenu v-for="device in deviceList" :key="device.deviceId" :index="device.deviceId" @click="sendDevicePush(item)"> + <template slot="title" > + <i class="el-icon-location-outline"></i> + {{device.name}} + </template> + <ChannelTree :device="device" @sendDevicePush="sendDevicePush"></ChannelTree> + </el-submenu> + </el-menu> + </el-aside> + <el-container> + <!-- <LivePlay></LivePlay> --> + <el-header height="40px" style="text-align: left;font-size: 17px;line-height: 40px;"> + 鍒嗗睆: + <i class="el-icon-full-screen btn" :class="{active:spilt==1}" @click="spilt=1"/> + <i class="el-icon-menu btn" :class="{active:spilt==4}" @click="spilt=4"/> + <i class="el-icon-s-grid btn" :class="{active:spilt==9}" @click="spilt=9"/> + </el-header> + <el-main> + <div style="width: 100%;height: calc( 100vh - 110px );display: flex;flex-wrap: wrap;background-color: #000;"> + <div v-for="i in spilt" :key="i" class="play-box" + :style="liveStyle" :class="{redborder:playerIdx == (i-1)}" + @click="playerIdx = (i-1)" + > + <div v-if="!videoUrl[i-1]" style="color: #ffffff;font-size: 30px;font-weight: bold;">{{i}}</div> + <player v-else :ref="'player'+i" :videoUrl="videoUrl[i-1]" fluent autoplay :height="true" + :idx="'player'+i" @screenshot="shot" @destroy="destroy"></player> + <!-- <player v-else ref="'player'+i" :idx="'player'+i" :visible.sync="showVideoDialog" :videoUrl="videoUrl[i-1]" :height="true" :hasAudio="hasAudio" fluent autoplay live ></player> --> + </div> + </div> + </el-main> + </el-container> + </el-container> + </el-container> + </div> +</template> + +<script> + import uiHeader from "./UiHeader.vue"; + import player from './jessibuca.vue' + import ChannelTree from './channelTree.vue' + + export default { + name: "live", + components: { + uiHeader, player, ChannelTree + }, + data() { + return { + showVideoDialog: true, + hasAudio: false, + videoUrl:[''], + spilt:1,//鍒嗗睆 + playerIdx:0,//婵�娲绘挱鏀惧櫒 + + deviceList: [], //璁惧鍒楄〃 + currentDevice: {}, //褰撳墠鎿嶄綔璁惧瀵硅薄 + + videoComponentList: [], + updateLooper: 0, //鏁版嵁鍒锋柊杞鏍囧織 + currentDeviceChannelsLenth:0, + winHeight: window.innerHeight - 200, + currentPage:1, + count:15, + total:0, + getDeviceListLoading: false, + + //channel + searchSrt: "", + channelType: "", + online: "", + channelTotal:0, + deviceChannelList:[], + loading:false + }; + }, + mounted() { + this.initData(); + + }, + created(){ + this.checkPlayByParam() + }, + + computed:{ + liveStyle(){ + if(this.spilt==1){ + return {width:'100%',height:'100%'} + }else if(this.spilt==4){ + return {width:'49%',height:'49%'} + }else if(this.spilt==9){ + return {width:'32%',height:'32%'} + } + } + }, + watch:{ + spilt(newValue){ + console.log("鍒囨崲鐢诲箙;"+newValue) + let that = this + for (let i = 1; i <= newValue; i++) { + if(!that.$refs['player'+i]){ + continue + } + this.$nextTick(()=>{ + if(that.$refs['player'+i] instanceof Array){ + that.$refs['player'+i][0].resize() + }else { + that.$refs['player'+i].resize() + } + }) + + } + window.localStorage.setItem('split',newValue) + }, + '$route.fullPath':'checkPlayByParam' + }, + destroyed() { + clearTimeout(this.updateLooper); + }, + methods: { + initData: function () { + this.getDeviceList(); + + }, + destroy(idx) { + console.log(idx); + this.clear(idx.substring(idx.length-1)) + }, + getDeviceList: function() { + let that = this; + this.$axios({ + method: 'get', + url:`/api/device/query/devices`, + params: { + page: that.currentPage, + count: that.count + } + }).then(function (res) { + console.log(res.data.list); + that.total = res.data.total; + + that.deviceList = res.data.list.map(item=>{return {deviceChannelList:[],...item}}); + that.getDeviceListLoading = false; + }).catch(function (error) { + console.log(error); + that.getDeviceListLoading = false; + }); + }, + //閫氱煡璁惧涓婁紶濯掍綋娴� + sendDevicePush: function (itemData) { + if(itemData.status===0){ + this.$message.error('璁惧绂荤嚎!'); + return + } + this.save(itemData) + let deviceId = itemData.deviceId; + // this.isLoging = true; + let channelId = itemData.channelId; + console.log("閫氱煡璁惧鎺ㄦ祦1锛�" + deviceId + " : " + channelId ); + let idxTmp = this.playerIdx + let that = this; + this.loading = true + this.$axios({ + method: 'get', + url: '/api/play/start/' + deviceId + '/' + channelId + }).then(function (res) { + // that.isLoging = false; + console.log('=====----=====') + console.log(res) + if (res.data.code == 0 && res.data.data) { + itemData.playUrl = res.data.data.httpsFlv + that.setPlayUrl(res.data.data.ws_flv,idxTmp) + }else { + that.$message.error(res.data.msg); + } + }).catch(function (e) { + }).finally(()=>{ + that.loading = false + }); + }, + setPlayUrl(url,idx){ + this.$set(this.videoUrl,idx,url) + let _this = this + setTimeout(()=>{ + window.localStorage.setItem('videoUrl',JSON.stringify(_this.videoUrl)) + },100) + + }, + checkPlayByParam(){ + let {deviceId,channelId} = this.$route.query + if(deviceId && channelId){ + this.sendDevicePush({deviceId,channelId}) + } + }, + convertImageToCanvas(image) { + var canvas = document.createElement("canvas"); + canvas.width = image.width; + canvas.height = image.height; + canvas.getContext("2d").drawImage(image, 0, 0); + return canvas; + }, + shot(e){ + // console.log(e) + // send({code:'image',data:e}) + var base64ToBlob = function(code) { + let parts = code.split(';base64,'); + let contentType = parts[0].split(':')[1]; + let raw = window.atob(parts[1]); + let rawLength = raw.length; + let uInt8Array = new Uint8Array(rawLength); + for(let i = 0; i < rawLength; ++i) { + uInt8Array[i] = raw.charCodeAt(i); + } + return new Blob([uInt8Array], { + type: contentType + }); + }; + let aLink = document.createElement('a'); + let blob = base64ToBlob(e); //new Blob([content]); + let evt = document.createEvent("HTMLEvents"); + evt.initEvent("click", true, true); //initEvent 涓嶅姞鍚庝袱涓弬鏁板湪FF涓嬩細鎶ラ敊 浜嬩欢绫诲瀷锛屾槸鍚﹀啋娉★紝鏄惁闃绘娴忚鍣ㄧ殑榛樿琛屼负 + aLink.download = '鎴浘'; + aLink.href = URL.createObjectURL(blob); + aLink.click(); + }, + save(item){ + let dataStr = window.localStorage.getItem('playData') || '[]' + let data = JSON.parse(dataStr); + data[this.playerIdx] = item + window.localStorage.setItem('playData',JSON.stringify(data)) + }, + clear(idx) { + let dataStr = window.localStorage.getItem('playData') || '[]' + let data = JSON.parse(dataStr); + data[idx-1] = null; + console.log(data); + window.localStorage.setItem('playData',JSON.stringify(data)) + }, + loadAndPlay(){ + let dataStr = window.localStorage.getItem('playData') || '[]' + let data = JSON.parse(dataStr); + + data.forEach((item,i)=>{ + if(item){ + this.playerIdx = i + this.sendDevicePush(item) + } + }) + } + } + }; +</script> +<style> + .btn{ + margin: 0 10px; + + } + .btn:hover{ + color: #409EFF; + } + .btn.active{ + color: #409EFF; + + } + .redborder{ + border: 2px solid red !important; + } + .play-box{ + background-color: #000000; + border: 2px solid #505050; + display: flex; + align-items: center; + justify-content: center; + } +</style> +<style> + .videoList { + display: flex; + flex-wrap: wrap; + align-content: flex-start; + } + + .video-item { + position: relative; + width: 15rem; + height: 10rem; + margin-right: 1rem; + background-color: #000000; + } + + .video-item-img { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 100%; + height: 100%; + } + + .video-item-img:after { + content: ""; + display: inline-block; + position: absolute; + z-index: 2; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 3rem; + height: 3rem; + background-image: url("../assets/loading.png"); + background-size: cover; + background-color: #000000; + } + + .video-item-title { + position: absolute; + bottom: 0; + color: #000000; + background-color: #ffffff; + line-height: 1.5rem; + padding: 0.3rem; + width: 14.4rem; + } + + .baidumap { + width: 100%; + height: 100%; + border: none; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + margin: auto; + } + + /* 鍘婚櫎鐧惧害鍦板浘鐗堟潈閭h瀛� 鍜� 鐧惧害logo */ + .baidumap > .BMap_cpyCtrl { + display: none !important; + } + .baidumap > .anchorBL { + display: none !important; + } +</style> diff --git a/web_src/src/router/index.js b/web_src/src/router/index.js index 59bbb23..ad573cf 100644 --- a/web_src/src/router/index.js +++ b/web_src/src/router/index.js @@ -15,6 +15,7 @@ import web from '../components/setting/Web.vue' import sip from '../components/setting/Sip.vue' import media from '../components/setting/Media.vue' +import live from '../components/live.vue' import wasmPlayer from '../components/dialog/jessibuca.vue' import rtcPlayer from '../components/dialog/rtcPlayer.vue' @@ -35,6 +36,10 @@ component: control, }, { + path: '/live', + component: live, + }, + { path: '/deviceList', component: deviceList, }, -- Gitblit v1.8.0