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