Sfoglia il codice sorgente

feat: 用户系统更新
fix: 设备绑定优化

kindring 1 anno fa
parent
commit
659c850340
52 ha cambiato i file con 1653 aggiunte e 120 eliminazioni
  1. 4 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipUserConfig.java
  2. 25 0
      src/main/java/com/genersoft/iot/vmp/service/IAccountService.java
  3. 22 2
      src/main/java/com/genersoft/iot/vmp/service/impl/AccountServiceImpl.java
  4. 1 0
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java
  5. 6 1
      src/main/java/com/genersoft/iot/vmp/storager/dao/AccountMapper.java
  6. 115 0
      src/main/java/com/genersoft/iot/vmp/vmanager/account/AccountDeviceControl.java
  7. 0 17
      src/main/java/com/genersoft/iot/vmp/vmanager/account/DeviceManager.java
  8. 17 5
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java
  9. 5 3
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  10. 21 31
      src/main/java/com/genersoft/iot/vmp/vmanager/user/AccountController.java
  11. 1 1
      src/main/resources/application.yml
  12. 103 0
      web_src/src/components/account_com/bindDevice.vue
  13. 12 0
      web_src/src/components/account_com/channelList.vue
  14. 276 0
      web_src/src/components/account_com/deviceList.vue
  15. 2 4
      web_src/src/components/dialog/SyncChannelProgress.vue
  16. 10 10
      web_src/src/components/dialog/changePushKey.vue
  17. 14 14
      web_src/src/components/layoutCom/user_header.vue
  18. 75 0
      web_src/src/components/modal/waitJump.js
  19. 2 2
      web_src/src/components/u_page/u_info.vue
  20. 43 0
      web_src/src/icons/iconSvg.vue
  21. 9 0
      web_src/src/icons/registerSvg.js
  22. 5 0
      web_src/src/icons/svg/addLive.svg
  23. 9 0
      web_src/src/icons/svg/addPhoto.svg
  24. 9 0
      web_src/src/icons/svg/addVideo.svg
  25. 5 0
      web_src/src/icons/svg/arrow-down.svg
  26. 5 0
      web_src/src/icons/svg/arrow-up.svg
  27. 5 0
      web_src/src/icons/svg/bell.svg
  28. 20 0
      web_src/src/icons/svg/camera.svg
  29. 5 0
      web_src/src/icons/svg/close.svg
  30. 13 0
      web_src/src/icons/svg/copy.svg
  31. 5 0
      web_src/src/icons/svg/falling.svg
  32. 11 0
      web_src/src/icons/svg/full.svg
  33. 9 0
      web_src/src/icons/svg/info.svg
  34. 8 0
      web_src/src/icons/svg/logs.svg
  35. 5 0
      web_src/src/icons/svg/next.svg
  36. 5 0
      web_src/src/icons/svg/prev.svg
  37. 9 0
      web_src/src/icons/svg/statistics.svg
  38. 5 0
      web_src/src/icons/svg/system.svg
  39. 5 0
      web_src/src/icons/svg/up.svg
  40. 7 0
      web_src/src/icons/svg/warning.svg
  41. 7 0
      web_src/src/icons/svg/ybp.svg
  42. 4 6
      web_src/src/layout/UiHeader.vue
  43. 476 0
      web_src/src/layout/adminLayout.vue
  44. 25 0
      web_src/src/map/userMenus.js
  45. 63 16
      web_src/src/pages/account/AccountRegister.vue
  46. 2 0
      web_src/src/pages/account/main.js
  47. 58 0
      web_src/src/pages/device/UserDevice.vue
  48. 20 0
      web_src/src/pages/device/device.html
  49. 42 0
      web_src/src/pages/device/main.js
  50. 19 0
      web_src/src/pages/device/router/deviceRouter.js
  51. 16 5
      web_src/src/until/FieldCheck.js
  52. 13 3
      web_src/vue.config.js

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipUserConfig.java

@@ -114,6 +114,10 @@ public class SipUserConfig {
     }
 
     public Boolean isEnableBind() {
+        // 为空判断
+        if (enableBind == null) {
+            return false;
+        }
         return enableBind.equals("1");
     }
 }

+ 25 - 0
src/main/java/com/genersoft/iot/vmp/service/IAccountService.java

@@ -1,6 +1,10 @@
 package com.genersoft.iot.vmp.service;
 
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
+import com.github.pagehelper.PageInfo;
+
+import java.util.List;
 
 public interface IAccountService {
 
@@ -47,11 +51,32 @@ public interface IAccountService {
 
     /**
      * 检查绑定码是否能够被绑定, 能够绑定则 允许 true
+     *
      * @param bindCode
      * @return
      */
     Boolean checkIsAllowBind(String bindCode);
 
+    /**
+     * 使用绑定码检索设备
+     */
 
+    /**
+     * 获取用户设备
+     *
+     * @param accountId 用户id
+     * @param page      页码
+     * @param limit     账户
+     * @return
+     */
+    PageInfo<Device> getAccountDevices(int accountId, int page, int limit);
 
+    /**
+     * 获取指定用户的指定设备
+     *
+     * @param accountId 用户id
+     * @param deviceId  设备id
+     * @return Device
+     */
+    Device getAccountDevice(int accountId, int deviceId);
 }

+ 22 - 2
src/main/java/com/genersoft/iot/vmp/service/impl/AccountServiceImpl.java

@@ -5,15 +5,24 @@ import com.genersoft.iot.vmp.service.IAccountService;
 import com.genersoft.iot.vmp.storager.dao.AccountMapper;
 import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
 import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 @Service
 public class AccountServiceImpl implements IAccountService {
 
+    private Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
+
     @Autowired
     private AccountMapper accountMapper;
 
+    @Autowired
     private DeviceMapper deviceMapper;
 
     /**
@@ -62,15 +71,26 @@ public class AccountServiceImpl implements IAccountService {
     }
 
 
-    public Boolean checkIsAllowBind(String bindCode)
-    {
+    public Boolean checkIsAllowBind(String bindCode) {
         if (deviceMapper.getDeviceByBindCode(bindCode) == null) {
+            logger.warn("not find device");
             return false;
         }
         Device device = accountMapper.findDeviceByDevCode(bindCode);
+        logger.info("device: {}", device);
         return device == null;
     }
 
 
+    public PageInfo<Device> getAccountDevices(int accountId, int page, int count) {
+        PageHelper.startPage(page, count);
+        List<Device> devices = accountMapper.getAccountDevices(accountId);
+        return new PageInfo<>(devices);
+    }
+
+    public Device getAccountDevice(int accountId, int deviceId) {
+        return accountMapper.getAccountDevice(accountId, deviceId);
+    }
+
 
 }

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java

@@ -209,4 +209,5 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
     public List<Device> getDeviceByChannelId(String channelId) {
         return channelMapper.getDeviceByChannelId(channelId);
     }
+
 }

+ 6 - 1
src/main/java/com/genersoft/iot/vmp/storager/dao/AccountMapper.java

@@ -25,7 +25,7 @@ public interface AccountMapper {
 
 
     @Insert("INSERT INTO device_bind " +
-            "(accountId, deviceId, devCode) VALUES" +
+            "(userId, devId, devCode) VALUES" +
             "(#{id}, #{deviceId}, #{devCode})")
     int bindDevice(int id, int deviceId, String devCode);
 
@@ -40,6 +40,9 @@ public interface AccountMapper {
     @Select("SELECT * FROM `device_bind` as b LEFT JOIN `device` as d ON b.devId = d.id where b.userId = #{id}")
     List<Device> getAccountDevices(int id);
 
+    @Select("SELECT * FROM `device_bind` as b LEFT JOIN `device` as d ON b.devId = d.id where b.userId = #{userId} AND b.devId = #{devId}")
+    Device getAccountDevice(int userId, int devId);
+
     @Select("SELECT * FROM account WHERE id = #{id}")
     UserAccount selectById(int id);
 
@@ -48,4 +51,6 @@ public interface AccountMapper {
 
     @Select("SELECT * FROM account WHERE account = #{account} AND password = #{password}")
     UserAccount login(String account, String password);
+
+
 }

+ 115 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/account/AccountDeviceControl.java

@@ -0,0 +1,115 @@
+package com.genersoft.iot.vmp.vmanager.account;
+
+import com.genersoft.iot.vmp.conf.security.saToken.SaUserCheckRole;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.service.IAccountService;
+import com.genersoft.iot.vmp.service.IDeviceChannelService;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
+import com.github.pagehelper.PageInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "普通用户设备管理")
+@CrossOrigin(origins = "*")
+@RestController
+@RequestMapping("/account/device")
+@SaUserCheckRole("user")
+public class AccountDeviceControl {
+    private Logger logger = LoggerFactory.getLogger(AccountDeviceControl.class);
+
+    @Autowired
+    private IAccountService accountService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private IDeviceChannelService deviceChannelService;
+
+    @GetMapping("/bind")
+    @Operation(summary = "绑定设备")
+    @Parameter(name = "bindCode", description = "设备码", required = true)
+    public WVPResult<String> bindDevice(@RequestParam String bindCode) {
+        logger.info("[绑定设备] bind account {}", bindCode);
+        String userId = StpUserUtil.getLoginId().toString();
+        Device device = deviceService.getBindDevice(bindCode);
+        if (device == null) {
+            return WVPResult.fail(ErrorCode.ERROR404, "无法找到对应设备");
+        }
+        // 判断设备是否已经绑定
+        if (!accountService.checkIsAllowBind(bindCode)) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR403,
+                    "设备已经绑定, 无法重新绑定");
+        }
+        if (accountService.bindDevice(
+                Integer.parseInt(userId),
+                device.getId(),
+                bindCode) < 1) {
+            logger.warn("[绑定设备] 绑定失败");
+            return WVPResult.fail(ErrorCode.ERROR500, "绑定失败");
+        }
+        ;
+
+        return WVPResult.success();
+    }
+
+    @GetMapping("/unbind")
+    @Operation(summary = "解绑设备")
+    @Parameter(name = "deviceId", description = "设备id", required = true)
+    public WVPResult<String> unbindDevice(
+            @RequestParam String deviceId) {
+        String userId = StpUserUtil.getLoginId().toString();
+        accountService.unBindDevice(Integer.parseInt(userId), Integer.parseInt(deviceId));
+        return WVPResult.success("ok");
+    }
+
+    // 获取当前登录的用户设备
+    @GetMapping("/search")
+    @Operation(summary = "获取设备列表")
+    @Parameter(name = "p", description = "当前页", required = true)
+    @Parameter(name = "l", description = "每页查询数量", required = true)
+    public WVPResult<List<Device>> getDevices(
+            @RequestParam(value = "p", required = false, defaultValue = "1") int page,
+            @RequestParam(value = "l", required = false, defaultValue = "20") int limit) {
+        String userId = StpUserUtil.getLoginId().toString();
+        PageInfo<Device> pageResult = accountService.getAccountDevices(
+                Integer.parseInt(userId),
+                page,
+                limit);
+        return WVPResult.success(
+                pageResult.getList(),
+                page,
+                pageResult.getPageSize(),
+                pageResult.getTotal());
+    }
+
+    // 获取设备通道列表
+    @GetMapping("/channels")
+    @Operation(summary = "获取设备通道列表")
+    @Parameter(name = "deviceId", description = "设备id", required = true)
+    public WVPResult<List<ChannelReduce>> getChannels(
+            @RequestParam(value = "deviceId", required = true) int deviceId) {
+        int userId = Integer.parseInt(StpUserUtil.getLoginId().toString());
+        // 判断当前用户是否有该设备
+        Device device = accountService.getAccountDevice(userId, deviceId);
+        if (device == null) {
+            return WVPResult.fail(ErrorCode.ERROR403, "当前用户无权限访问此设备");
+        }
+        List<ChannelReduce> channels = deviceChannelService.queryAllChannelList(device.getDeviceId());
+        return WVPResult.success(channels);
+    }
+
+    //
+}

+ 0 - 17
src/main/java/com/genersoft/iot/vmp/vmanager/account/DeviceManager.java

@@ -1,17 +0,0 @@
-package com.genersoft.iot.vmp.vmanager.account;
-
-import com.genersoft.iot.vmp.conf.security.saToken.SaUserCheckRole;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@Tag(name = "普通用户设备管理")
-@CrossOrigin(origins = "*")
-@RestController
-@RequestMapping("/account")
-@SaUserCheckRole("user")
-public class DeviceManager {
-    // 根据绑定码
-
-}

+ 17 - 5
src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java

@@ -20,11 +20,11 @@ public class WVPResult<T> implements Cloneable{
 
 
     // todo 返回结果携带搜索结果等页数
-    public WVPResult(int code, String msg, T data, int page, int limit, int total) {
+    public WVPResult(int code, String msg, T data, int page, int limit, long total) {
         this.code = code;
         this.msg = msg;
         this.data = data;
-        this.page =  page;
+        this.page = page;
         this.limit = limit;
         this.total = total;
     }
@@ -51,7 +51,7 @@ public class WVPResult<T> implements Cloneable{
 
     @JsonInclude(JsonInclude.Include.NON_NULL)
     @Schema(description = "总条数")
-    private int total;
+    private long total;
 
     public static <T> WVPResult<T> success(T t, String msg) {
         return new WVPResult<>(ErrorCode.SUCCESS.getCode(), msg, t);
@@ -61,6 +61,17 @@ public class WVPResult<T> implements Cloneable{
         return new WVPResult<>(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null);
     }
 
+    // 返回成功带数据和页码的返回
+    public static <T> WVPResult<T> success(T data, int page, int limit, long total) {
+        return new WVPResult<>(
+                ErrorCode.SUCCESS.getCode(),
+                ErrorCode.SUCCESS.getMsg(), data,
+                page,
+                limit,
+                total
+        );
+    }
+
     public static <T> WVPResult<T> success(T t) {
         return success(t, ErrorCode.SUCCESS.getMsg());
     }
@@ -114,9 +125,10 @@ public class WVPResult<T> implements Cloneable{
     }
 
     public void setPage(int page) {
-        this.page =  page;
+        this.page = page;
     }
-    public int getTotal() {
+
+    public long getTotal() {
         return total;
     }
 

+ 5 - 3
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java

@@ -22,6 +22,7 @@ import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
 import com.genersoft.iot.vmp.vmanager.bean.AiLib;
 import com.genersoft.iot.vmp.vmanager.bean.BaseTree;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
@@ -58,7 +59,6 @@ import java.util.*;
 @CrossOrigin
 @RestController
 @RequestMapping("/api/device/query")
-@SaAdminCheckRole("admin")
 public class DeviceQuery {
 
     private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
@@ -97,6 +97,7 @@ public class DeviceQuery {
     @Parameter(name = "deviceId", description = "设备国标编号", required = true)
     @GetMapping("/devices/{deviceId}")
     public Device devices(@PathVariable String deviceId) {
+
         return storager.queryVideoDevice(deviceId);
     }
 
@@ -290,8 +291,9 @@ public class DeviceQuery {
     public PageInfo<Device> devices(int page,
                                     int count,
                                     @RequestParam(value = "online", defaultValue = "false", required = false) boolean online) {
-
-        logger.info("[设备查询] 查询所有设备 {}", 11);
+        String adminId = StpAdminUtil.getLoginId().toString();
+        String userId = StpUserUtil.getLoginId().toString();
+        logger.info("[设备查询] 查询所有设备 {}", adminId);
         if (online) {
             return storager.queryVideoDeviceList(page, count, online);
         }

+ 21 - 31
src/main/java/com/genersoft/iot/vmp/vmanager/user/AccountController.java

@@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
 import com.genersoft.iot.vmp.utils.StpUserUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import com.github.pagehelper.PageInfo;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -20,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.List;
 
 @Tag(name = "普通用户管理")
 @CrossOrigin(origins = "*")
@@ -45,14 +47,24 @@ public class AccountController {
                                       @Parameter String name,
                                       @Parameter String account,
                                       @Parameter String password) {
-        logger.info("[注册账户] name:{} account:{} password: {}",
-                name, account, password);
+        logger.info("[注册账户] name:{} account:{} ",
+                name, account);
+        if (name == null || account == null || password == null) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR400,
+                    "缺少参数");
+        }
         if (accountService.checkAccount(account) != null) {
             return WVPResult.fail(
                     ErrorCode.ERROR100,
                     "账号已存在");
         }
-//        accountService.registerAccount("u", account, password);
+        if (!accountService.registerAccount(name, account, password)) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR100,
+                    "注册失败");
+        }
+        logger.info("[注册账户] 用户: {} 注册:{}成功", name, account);
         return WVPResult.success();
     }
 
@@ -74,40 +86,18 @@ public class AccountController {
         return WVPResult.success(null, "ok");
     }
 
-    @GetMapping("/bind/device")
-    @Operation(summary = "绑定设备")
-    @Parameter(name = "devCode", description = "设备码", required = true)
-    public WVPResult<String> bindDevice(@RequestParam String devCode) {
-        logger.info("[绑定设备] bind account ");
-        WVPResult wvpResult = new WVPResult();
-        String userId = StpUserUtil.getLoginId().toString();
+    @GetMapping("/load/bind")
+    @Operation(summary = "获取绑定码对应的设备")
+    @Parameter(name = "bindCode", description = "绑定码", required = true)
+    public WVPResult<Device> getBindDevice(@RequestParam String devCode) {
         Device device = deviceService.getBindDevice(devCode);
         if (device == null) {
             return WVPResult.fail(ErrorCode.ERROR404, "无法找到对应设备");
         }
-        // 判断设备是否已经绑定
-        if (!accountService.checkIsAllowBind(devCode)) {
-            return WVPResult.fail(
-                    ErrorCode.ERROR403,
-                    "设备已经绑定, 无法重新绑定");
-        }
-        accountService.bindDevice(
-                Integer.parseInt(userId),
-                device.getId(),
-                devCode);
-
-        return wvpResult;
+        return WVPResult.success(device);
     }
 
-    @GetMapping("/unbind/device")
-    @Operation(summary = "解绑设备")
-    @Parameter(name = "deviceId", description = "设备id", required = true)
-    public WVPResult<String> unbindDevice(
-            @RequestParam String deviceId) {
-        String userId = StpUserUtil.getLoginId().toString();
-        accountService.unBindDevice(Integer.parseInt(userId), Integer.parseInt(deviceId));
-        return WVPResult.success("ok");
-    }
+
 
 
 

+ 1 - 1
src/main/resources/application.yml

@@ -180,4 +180,4 @@ springdoc:
     api-docs:
         enabled: true
     swagger-ui:
-        enabled: false
+        enabled: true

+ 103 - 0
web_src/src/components/account_com/bindDevice.vue

@@ -0,0 +1,103 @@
+<script>
+import {FormVerify} from "kind-form-verify";
+import fieldCheck from "@/until/FieldCheck";
+import handle from "@/until/handle";
+
+
+let fieldVerify = null;
+export default {
+  name: "bindDevice",
+  data() {
+    return {
+      loading: false,
+      form: {
+        bindCode: {
+          val: "",
+          oldVal: "",
+          init: "",
+          msg: "",
+          showText: ""
+        }
+      }
+    }
+  },
+  beforeMount() {
+    fieldVerify = new FormVerify(this.form, fieldCheck.fieldCheck)
+  },
+  methods: {
+
+    formBlurHandle(key) {
+      console.log("formBlurHandle")
+      if (!this.form[key]) {
+        return console.warn("error field")
+      }
+      fieldVerify.checkItem(key)
+    },
+    formFocusHandle(type, key) {
+      let formObject = this.form;
+      if (!formObject[key]) {
+        return console.warn("error field")
+      }
+      formObject[key].msg = ""
+    },
+    bindHandle() {
+      console.log("bindHandle")
+      if (!fieldVerify.check()) {
+        return this.$message.warning("绑定码不合法")
+      }
+      let formData = fieldVerify.getFormData()
+      this.executeBindDevice(formData)
+
+    },
+    async executeBindDevice(formData) {
+      this.loading = true
+      let url = `/account/device/bind`
+      url += `?bindCode=${formData.bindCode}`
+      let [err, res] = await handle(this.$axios.get(
+          url,
+      ));
+      this.loading = false
+      if (err) {
+        console.log(err);
+        this.$message.error(`设备绑定失败 ${err.message}`);
+        return
+      }
+      let response = res.data
+      if (response.code !== 0) {
+        this.$message.error(`设备绑定失败 ${response.msg}`)
+        return
+      }
+      console.log(response);
+      this.$emit('exitBind')
+
+    }
+  }
+}
+</script>
+
+<template>
+  <div class="full-box">
+    <!--  输入绑定码 -->
+    <div class="bind-box" :loading="loading">
+      <el-form>
+        <el-form-item class="form-item" prop="bindCode" :error="form.bindCode.msg">
+          <el-input v-model="form.bindCode.val"
+                    placeholder="设备绑定码"
+                    auto-complete="new-password"
+                    @blur="formBlurHandle('bindCode')"
+                    @focus="formFocusHandle('register', 'bindCode')"
+          />
+        </el-form-item>
+      </el-form>
+      <el-button @click="bindHandle">
+        绑定
+      </el-button>
+
+    </div>
+    <div class="result-box"></div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 12 - 0
web_src/src/components/account_com/channelList.vue

@@ -0,0 +1,12 @@
+<script>
+
+
+</script>
+
+<template>
+
+</template>
+
+<style scoped>
+
+</style>

+ 276 - 0
web_src/src/components/account_com/deviceList.vue

@@ -0,0 +1,276 @@
+<script>
+import handle from "@/until/handle";
+import querystring from "querystring";
+import {waitJump} from "@components/modal/waitJump";
+import BindDevice from "@components/account_com/bindDevice.vue";
+import syncChannelProgress from "@components/dialog/SyncChannelProgress.vue";
+
+export default {
+  name: "deviceList",
+  components: {syncChannelProgress, BindDevice},
+  props: {},
+  data() {
+    return {
+      page: 1,
+      limit: 10,
+      total: 0,
+      devices: [],
+      getDeviceListLoading: false,
+
+      bindDevice: {
+        show: false,
+      }
+
+    }
+  },
+  beforeMount() {
+    this.executeSearchDevices()
+  },
+  methods: {
+
+    refreshDevice() {
+      this.executeSearchDevices();
+    },
+    async executeSearchDevices() {
+      let url = `/account/device/search`
+      url += `?p=${this.page}&l=${this.limit}`
+      this.getDeviceListLoading = true
+      let [err, res] = await handle(this.$axios.get(
+          url,
+      ));
+      this.getDeviceListLoading = false
+      if (err) {
+        console.log(err);
+        this.$message.error(`获取设备列表失败 ${err.message}`);
+        return
+      }
+      let response = res.data
+      if (response.code !== 0) {
+        this.$message.error(`获取设备列表失败 ${response.msg}`)
+      }
+      console.log(response);
+      this.devices = response.data;
+      this.total = response.total;
+    },
+    async executeRefreshChannel(deviceId) {
+      console.log("刷新对应设备:" + deviceId);
+      let url = `/api/device/query/devices/${deviceId}/sync`
+      let [err, res] = await handle(this.$axios.get(
+          url
+      ))
+      if (err || res.data.code !== 0) {
+        console.error(err ? err : res)
+        this.$message({
+          showClose: true,
+          message: err ? err.message : res.msg,
+          type: 'error'
+        });
+        return
+      }
+      console.log("刷新设备结果:" + JSON.stringify(res.data));
+      this.$refs.syncChannelProgress.openDialog(deviceId)
+    },
+    async executeDeleteDevice(deviceId) {
+      let url = `/api/device/query/devices/${deviceId}/delete`
+      let [err, res] = await handle(this.$axios.get(url))
+      if (err || res.data.code != 0) {
+        return this.$message.error("移除设备失败");
+      }
+      this.$message.success("移除设备成功");
+    },
+    handleCurrentChange: function (val) {
+      this.currentPage = val;
+      this.getDeviceList();
+    },
+    handleSizeChange: function (val) {
+      this.count = val;
+      this.getDeviceList();
+    },
+
+    handleOpenBindDevice() {
+      this.bindDevice.show = true;
+    },
+    handleCloseBindDevice() {
+      this.bindDevice.show = false;
+    },
+    handleRefreshChannel(item) {
+      this.executeRefreshChannel(item.deviceId)
+    },
+    handleDeleteDevice(row) {
+      let msg = "确定删除此设备?"
+      if (row.online !== 0) {
+        msg = "在线设备删除后仍可通过注册再次上线。<br/>如需彻底删除请先将设备离线。<br/><strong>确定删除此设备?</strong>"
+      }
+      this.$confirm(msg, '提示', {
+        dangerouslyUseHTMLString: true,
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        center: true,
+        type: 'warning'
+      }).then(() => {
+        this.executeDeleteDevice(row.deviceId)
+      }).catch(() => {
+
+      });
+    },
+    handleOpenChannel(item) {
+
+    },
+    handleOpenEdit(item) {
+
+    }
+
+  }
+}
+</script>
+
+<template>
+  <div class="content device-full">
+    <!--    设备表格 -->
+    <!--设备列表-->
+    <div class="round-box search-header">
+      <div class="search-info">
+        <div class="page-title">我的设备</div>
+        <div class="page-header-btn">
+
+          <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary"
+                     @click="handleOpenBindDevice">绑定新设备
+          </el-button>
+          <!--        <el-button icon="el-icon-refresh-right" circle size="mini" :loading="getDeviceListLoading"-->
+          <!--                   @click="getDeviceList()"></el-button>-->
+
+          <el-button icon="el-icon-refresh-right" circle size="mini" :loading="getDeviceListLoading"
+                     @click="refreshDevice()"></el-button>
+        </div>
+      </div>
+    </div>
+    <div class="round-box search-ctx mt-2" v-if="total > 0 || getDeviceListLoading">
+      <el-table class="search-list" :loading="getDeviceListLoading" :data="devices" :height="'100%'"
+                header-row-class-name="table-header">
+        <el-table-column prop="name" label="名称" min-width="150">
+        </el-table-column>
+        <el-table-column prop="channelCount" label="通道数" min-width="80">
+        </el-table-column>
+        <el-table-column label="状态" min-width="80">
+          <template slot-scope="scope">
+            <div slot="reference" class="name-wrapper">
+              <el-tag size="medium" v-if="scope.row.online == 1">在线</el-tag>
+              <el-tag size="medium" type="info" v-if="scope.row.online == 0">离线</el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="keepaliveTime" label="最近心跳" min-width="160">
+        </el-table-column>
+
+        <el-table-column label="操作" min-width="210" fixed="right">
+          <template slot-scope="scope">
+            <el-button type="text" size="medium"
+                       v-bind:disabled="scope.row.online==0"
+                       icon="el-icon-refresh"
+                       @click="handleRefreshChannel(scope.row)"
+            >刷新
+            </el-button>
+            <el-divider direction="vertical"></el-divider>
+            <el-button type="text" size="medium" icon="el-icon-video-camera"
+                       @click="handleOpenChannel(scope.row)">通道
+            </el-button>
+            <el-divider direction="vertical"></el-divider>
+            <el-button size="medium" icon="el-icon-edit" type="text" @click="handleOpenEdit(scope.row)">编辑</el-button>
+
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+          style="float: right"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          :current-page="page"
+          :page-size="limit"
+          :page-sizes="[10, 15, 20, 30, 50]"
+          layout="total, sizes, prev, pager, next"
+          :total="total">
+      </el-pagination>
+    </div>
+    <!--    没有设备提示 -->
+    <div class="round-box search-ctx mt-2 " v-else>
+      <div class="search-tips">
+        <h1>暂时无法找到设备</h1>
+        <div class="btn-group">
+          <el-button @click="refreshDevice">重新加载</el-button>
+          <el-button @click="handleOpenBindDevice">绑定设备</el-button>
+        </div>
+
+      </div>
+    </div>
+
+    <!--    绑定新设备-->
+    <el-dialog
+        title="绑定新设备"
+        :close-on-click-modal="false"
+        :visible.sync="bindDevice.show"
+        :destroy-on-close="true"
+        @close="handleCloseBindDevice"
+    >
+      <bind-device @exitBind="handleCloseBindDevice"></bind-device>
+    </el-dialog>
+
+    <syncChannelProgress ref="syncChannelProgress"></syncChannelProgress>
+  </div>
+</template>
+
+<style scoped>
+.content {
+  width: 100%;
+  min-height: 100%;
+  position: relative;
+  /*top: 110px;*/
+  box-sizing: border-box;
+  padding: 5px;
+  background-color: #a4a4a4;
+}
+
+.round-box {
+  box-sizing: border-box;
+  padding: 5px;
+  background-color: #fff;
+  border-radius: 3px;
+}
+
+.mt-2 {
+  margin-top: 5px;
+}
+
+.device-full {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.search-info {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 35px;
+}
+
+.search-ctx {
+  width: 100%;
+  height: calc(100% - 50px);
+  display: flex;
+  flex-direction: column;
+}
+
+.search-list {
+  width: 100%;
+  height: calc(100% - 50px);
+}
+
+.search-tips {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+</style>

+ 2 - 4
web_src/src/components/dialog/SyncChannelProgress.vue

@@ -53,10 +53,8 @@ export default {
       this.getProgress()
     },
     getProgress(){
-      this.$axios.axios({
-        method: 'get',
-        url:`/api/device/query/${this.deviceId}/sync_status/`,
-      }).then((res) => {
+      let url = `/api/device/query/${this.deviceId}/sync_status/`
+      this.$axios.get(url).then((res) => {
         if (res.data.code === 0) {
           if (!this.syncFlag) {
             this.syncFlag = true;

+ 10 - 10
web_src/src/components/dialog/changePushKey.vue

@@ -11,16 +11,16 @@
     >
       <div id="shared" style="margin-right: 18px;">
         <el-form ref="pushKeyForm" :rules="rules" status-icon label-width="86px">
-              <el-form-item label="新pushKey" prop="newPushKey" >
-                <el-input v-model="newPushKey" autocomplete="off"></el-input>
-              </el-form-item>
-              <el-form-item>
-                <div style="float: right;">
-                  <el-button type="primary" @click="onSubmit">保存</el-button>
-                  <el-button @click="close">取消</el-button>
-                </div>
-              </el-form-item>
-            </el-form>
+          <el-form-item label="新pushKey" prop="newPushKey">
+            <el-input v-model="newPushKey" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item>
+            <div style="float: right;">
+              <el-button type="primary" @click="onSubmit">保存</el-button>
+              <el-button @click="close">取消</el-button>
+            </div>
+          </el-form-item>
+        </el-form>
       </div>
     </el-dialog>
   </div>

+ 14 - 14
web_src/src/components/layoutCom/user_header.vue

@@ -4,7 +4,7 @@
              active-text-color="#1890ff" mode="horizontal">
       <el-menu-item index="/">主控台</el-menu-item>
       <el-menu-item index="/userInfo">个人信息</el-menu-item>
-      <el-menu-item v-if="editUser" index="/userManager">用户管理</el-menu-item>
+      <el-menu-item index="/userManager">用户管理</el-menu-item>
     </el-menu>
   </div>
 </template>
@@ -24,20 +24,20 @@ export default {
     this.initHeader();
   },
   methods: {
-    initHeader(){
+    initHeader() {
       this.activeIndex = this.$route.path;
-      let user = userService.getUser();
-      console.log(user);
-      if(user && user.role){
-        let roleId = user.role.id;
-        console.log(roleId)
-        console.log(typeof roleId)
-        this.editUser = roleId === 1;
-        console.log(this.editUser)
-      }else{
-        this.editUser = false;
-        // todo: 重新获取用户信息,或者重新登录
-      }
+      // let user = userService.getUser();
+      // console.log(user);
+      // if(user && user.role){
+      //   let roleId = user.role.id;
+      //   console.log(roleId)
+      //   console.log(typeof roleId)
+      //   this.editUser = roleId === 1;
+      //   console.log(this.editUser)
+      // }else{
+      //   this.editUser = false;
+      //   // todo: 重新获取用户信息,或者重新登录
+      // }
       this.$nextTick()
 
     }

+ 75 - 0
web_src/src/components/modal/waitJump.js

@@ -0,0 +1,75 @@
+import {Modal} from "ant-design-vue";
+// 导入样式
+import 'ant-design-vue/dist/antd.css';
+import Vue from 'vue'
+
+const Type_Info = "info";
+const Type_Succeed = "succeed";
+const Type_Error = "error";
+
+function executeJump(
+    type = Type_Info,
+    title,
+    msg,
+    secondsPlaceHolder,
+    jumpUrl = "/",
+    secondsToGo = 5) {
+
+    // 提示初始化
+    Modal.install(Vue)
+    title = title || "操作成功";
+    secondsPlaceHolder = secondsPlaceHolder || '_';
+    msg = msg || `将在${secondsPlaceHolder} 秒后自动跳转页面.`;
+    let modalHandle = Modal.info;
+    switch (type) {
+        case Type_Succeed:
+            modalHandle = Modal.success
+            break
+        case Type_Error:
+            modalHandle = Modal.error
+            break
+        case Type_Info:
+        default:
+            modalHandle = Modal.info;
+    }
+    const modal = modalHandle({
+        title: title,
+        zIndex: 9999,
+        content: msg.replace(
+            secondsPlaceHolder,
+            secondsToGo.toString()),
+        okText: '立即跳转',
+        onOk() {
+            window.location.href = jumpUrl;
+        },
+    });
+    const interval = setInterval(() => {
+        secondsToGo -= 1;
+        modal.update({
+            content: msg.replace(
+                secondsPlaceHolder,
+                secondsToGo.toString()),
+        });
+    }, 1000);
+    setTimeout(() => {
+        window.location.href = jumpUrl;
+    }, 1 + (secondsToGo * 1000));
+}
+
+export function waitJump(
+    title,
+    msg,
+    secondsPlaceHolder,
+    jumpUrl = "/",
+    secondsToGo = 5,) {
+    executeJump(Type_Succeed, ...arguments)
+}
+
+export function errorWaitJump(title,
+                              msg,
+                              secondsPlaceHolder,
+                              jumpUrl = "/",
+                              secondsToGo = 5) {
+    console.log(errorWaitJump)
+    executeJump(Type_Error, ...arguments)
+}

+ 2 - 2
web_src/src/components/u_page/u_info.vue

@@ -41,8 +41,8 @@ export default {
     }
   },
   beforeMount() {
-    let u = userService.getUser();
-    this.username = u.username;
+    // let u = userService.getUser();
+    // this.username = u.username;
   },
   methods:{
     toBack(){

+ 43 - 0
web_src/src/icons/iconSvg.vue

@@ -0,0 +1,43 @@
+<template>
+  <svg :class="svgClass" aria-hidden="true">
+    <use :xlink:href="iconName"/>
+  </svg>
+</template>
+
+<script>
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true,
+    },
+    className: {
+      type: String,
+      default: '',
+    },
+  },
+  computed: {
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    },
+  },
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>

+ 9 - 0
web_src/src/icons/registerSvg.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from './iconSvg' // svg组件
+
+// 注册到全局
+Vue.component('svg-icon', SvgIcon)
+
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+const req = require.context('./svg', false, /\.svg$/)
+requireAll(req)

+ 5 - 0
web_src/src/icons/svg/addLive.svg

@@ -0,0 +1,5 @@
+<svg t="1637922957340" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9636"
+     width="200" height="200">
+    <path d="M438.449152 301.1072h144.243712L733.24544 150.5536c11.108352-11.108352 29.118464-11.108352 40.226816 0 11.108352 11.108352 11.108352 29.118464 0 40.226816l-110.326784 110.32576h190.18752c31.419392 0 56.889344 25.470976 56.889344 56.889344V813.1072c0 31.418368-25.469952 56.88832-56.889344 56.88832H170.667008c-31.419392 0-56.889344-25.469952-56.889344-56.88832V357.99552c0-31.418368 25.469952-56.88832 56.889344-56.88832h187.328512l-110.32576-110.326784c-11.109376-11.108352-11.109376-29.118464 0-40.226816 11.107328-11.108352 29.11744-11.108352 40.225792 0L438.449152 301.1072z m150.10816 310.444032a28.444672 28.444672 0 0 0 4.443136-4.442112c9.814016-12.26752 7.824384-30.16704-4.442112-39.980032l-115.678208-92.542976a28.444672 28.444672 0 0 0-17.769472-6.233088c-15.70816 0-28.443648 12.735488-28.443648 28.444672v185.084928a28.444672 28.444672 0 0 0 6.233088 17.769472c9.812992 12.26752 27.713536 14.255104 39.980032 4.442112l115.678208-92.542976z"
+          fill="#323233" p-id="9637"></path>
+</svg>

+ 9 - 0
web_src/src/icons/svg/addPhoto.svg

@@ -0,0 +1,9 @@
+<svg t="1637922671770" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8665"
+     width="200" height="200">
+    <path d="M771.378957 223.517703C726.576565 159.168116 650.402675 116.692722 563.885206 116.692722c-86.504166 0-162.68215 42.475394-207.418027 106.824981L35.400766 223.517703 35.400766 897.074216l961.38287 0L996.783637 223.517703 771.378957 223.517703zM590.925049 789.465382c-129.770576 0-235.05855-105.291045-235.05855-235.060597 0-129.817648 105.287975-235.100506 235.05855-235.100506 129.821741 0 235.110739 105.282858 235.110739 235.100506C826.035788 684.174338 720.74679 789.465382 590.925049 789.465382z"
+          p-id="8666"></path>
+    <path d="M590.925049 404.285766c-82.85608 0-150.07911 67.218938-150.07911 150.117996 0 82.842777 67.223031 150.08218 150.07911 150.08218 82.891895 0 150.117996-67.239404 150.117996-150.08218C741.043045 471.504704 673.816944 404.285766 590.925049 404.285766z"
+          p-id="8667"></path>
+    <path d="M278.720468 368.714619c0 27.04189-21.929452 48.959062-48.959062 48.959062-27.025517 0-48.959062-21.917172-48.959062-48.959062 0-27.02654 21.933545-48.944735 48.959062-48.944735C256.791016 319.769884 278.720468 341.688079 278.720468 368.714619z"
+          p-id="8668"></path>
+</svg>

+ 9 - 0
web_src/src/icons/svg/addVideo.svg

@@ -0,0 +1,9 @@
+<svg t="1637922565029" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7571"
+     width="200" height="200">
+    <path d="M368 724H252V608c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v116H72c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h116v116c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V788h116c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"
+          p-id="7572" fill="#8fa67c"></path>
+    <path d="M912 302.3L784 376V224c0-35.3-28.7-64-64-64H128c-35.3 0-64 28.7-64 64v352h72V232h576v560H448v72h272c35.3 0 64-28.7 64-64V648l128 73.7c21.3 12.3 48-3.1 48-27.6V330c0-24.6-26.7-40-48-27.7zM888 625l-104-59.8V458.9L888 399v226z"
+          p-id="7573" fill="#8fa67c"></path>
+    <path d="M320 360c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H208c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h112z" p-id="7574"
+          fill="#8fa67c"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/arrow-down.svg

@@ -0,0 +1,5 @@
+<svg t="1688635660812" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5400"
+     width="200" height="200">
+    <path d="M888.2 286.1L512 662.3 135.8 286.1c-16.4-16.4-43.1-16.4-59.5 0s-16.4 43.1 0 59.5l382.6 382.6c14.2 14.2 33 22 53.1 22s38.9-7.8 53.1-22l382.6-382.6c16.4-16.4 16.4-43.1 0-59.5-16.5-16.4-43.1-16.4-59.5 0z"
+          fill="#272636" p-id="5401"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/arrow-up.svg

@@ -0,0 +1,5 @@
+<svg t="1688635748482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6494"
+     width="200" height="200">
+    <path d="M904.533333 674.133333l-362.666666-362.666666c-17.066667-17.066667-42.666667-17.066667-59.733334 0l-362.666666 362.666666c-17.066667 17.066667-17.066667 42.666667 0 59.733334 17.066667 17.066667 42.666667 17.066667 59.733333 0L512 401.066667l332.8 332.8c8.533333 8.533333 19.2 12.8 29.866667 12.8s21.333333-4.266667 29.866666-12.8c17.066667-17.066667 17.066667-42.666667 0-59.733334z"
+          p-id="6495"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/bell.svg

@@ -0,0 +1,5 @@
+<svg t="1684304461383" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="888"
+     width="200" height="200">
+    <path d="M512.62 890A128.56 128.56 0 0 0 638.9 785.5H386.34A128.57 128.57 0 0 0 512.62 890zM793.86 656.24l-65.37-130.55a9.7 9.7 0 0 1-1.09-4.56l0.17-117.75a177.41 177.41 0 0 0-119.33-167.8 20.4 20.4 0 0 1-13.52-16.2A85.05 85.05 0 0 0 512 147h-0.29A85 85 0 0 0 429 219.38a20.43 20.43 0 0 1-13.53 16.2 177.41 177.41 0 0 0-119.33 167.8l0.17 117.75a9.58 9.58 0 0 1-1.09 4.56l-65.37 130.55c-19.08 38.18 8.62 83.1 51.35 83.1h461.31c42.73 0 70.43-44.92 51.35-83.1z"
+          fill="#001708" p-id="889"></path>
+</svg>

+ 20 - 0
web_src/src/icons/svg/camera.svg

@@ -0,0 +1,20 @@
+<svg t="1637928227040" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="12148" width="200" height="200">
+    <path d="M162.9 827.9L82 901.8V530.2l80.9 20.6z" fill="#303030" p-id="12149"></path>
+    <path d="M82 919.2c-2.4 0-4.7-0.5-7-1.5-6.3-2.8-10.4-9-10.4-15.9V530.2c0-5.4 2.5-10.4 6.7-13.7 4.2-3.3 9.7-4.4 14.9-3.1l80.9 20.7c7.7 2 13.1 8.9 13.1 16.8V828c0 4.9-2 9.5-5.6 12.8l-80.9 73.9c-3.2 2.9-7.4 4.5-11.7 4.5z m17.4-366.7v309.9l46.2-42.2v-256l-46.2-11.7z"
+          fill="#232323" p-id="12150"></path>
+    <path d="M741.5 694.5L207 395.2c-20.7-11.6-27.6-38.8-15.4-60.6L297.2 146c12.2-21.9 39-30.2 59.7-18.6l579.7 329.1-195.1 238z"
+          fill="#EDF4FC" p-id="12151"></path>
+    <path d="M741.5 711.8c-2.9 0-5.8-0.7-8.5-2.2L198.5 410.3c-14.2-8-24.3-21.2-28.4-37.3-4-15.8-1.8-32.4 6.4-46.9l105.6-188.6c8.1-14.5 21.1-25.1 36.7-29.9 15.9-4.9 32.5-3.3 46.7 4.7l579.8 329.2c4.4 2.5 7.6 6.9 8.5 11.9 0.9 5-0.4 10.2-3.6 14.2L754.9 705.5c-3.4 4.1-8.3 6.3-13.4 6.3zM336.8 139.5c-2.6 0-5.2 0.4-7.7 1.2-7 2.2-12.9 7.1-16.7 13.7L206.8 343c-3.7 6.7-4.8 14.3-3 21.4 1.7 6.8 5.9 12.3 11.7 15.6l521.8 292.2 172.8-210.8-561.7-318.9c-3.6-1.9-7.5-3-11.6-3z"
+          fill="#232323" p-id="12152"></path>
+    <path d="M686.1 726.8l-460.7-258c-9.1-5.1-12.3-16.6-7.2-25.6l18.1-32.3 505.2 282.9-11.6 20.7c-8.7 15.5-28.3 21-43.8 12.3z"
+          fill="#83CAFF" p-id="12153"></path>
+    <path d="M692.6 739.8c-12.4 0-24.6-3.2-35.7-9.4L229 490.8c-11.7-6.5-20.1-17.2-23.7-30.1-3.6-12.9-2-26.4 4.5-38l11.3-20.2c4.7-8.4 15.3-11.3 23.6-6.7l505.2 282.9c4 2.2 7 6 8.2 10.4 1.3 4.4 0.7 9.2-1.6 13.2-9.6 17.1-25.2 29.4-44 34.7-6.5 1.8-13.2 2.8-19.9 2.8zM242.9 434.5l-2.9 5.1c-2 3.6-2.5 7.7-1.4 11.7 1.1 3.9 3.7 7.2 7.3 9.2l427.9 239.6c13.1 7.3 28.7 6.2 40.4-1.7L242.9 434.5z"
+          fill="#232323" p-id="12154"></path>
+    <path d="M412.6 576.4l-53.7-31.1L289 666.4H168.7v62h156.1z" fill="#303030" p-id="12155"></path>
+    <path d="M324.8 745.8H168.7c-9.6 0-17.4-7.8-17.4-17.4v-62c0-9.6 7.8-17.4 17.4-17.4H279l64.9-112.4c4.8-8.3 15.4-11.1 23.7-6.4l53.7 31c8.3 4.8 11.1 15.4 6.4 23.7l-87.5 151.5c-3 5.6-8.7 9.4-15.4 9.4z m-138.7-34.7h128.7l74.1-128.4-23.7-13.7L304 675c-3.1 5.4-8.8 8.7-15 8.7H186.1v27.4z"
+          fill="#232323" p-id="12156"></path>
+    <path d="M358.3 241m-21.8 0a21.8 21.8 0 1 0 43.6 0 21.8 21.8 0 1 0-43.6 0Z" fill="#EA3934" p-id="12157"></path>
+    <path d="M786.8 733.1c-3.9 0-7.9-1.3-11.1-4-7.4-6.1-8.3-17.1-2.2-24.4L928.7 519c6.1-7.4 17.1-8.3 24.4-2.2 7.4 6.1 8.3 17.1 2.2 24.4L800.1 726.8c-3.4 4.2-8.4 6.3-13.3 6.3z"
+          fill="#232323" p-id="12158"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/close.svg

@@ -0,0 +1,5 @@
+<svg t="1638340156508" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2294"
+     width="200" height="200">
+    <path d="M544.448 499.2l284.576-284.576a32 32 0 0 0-45.248-45.248L499.2 453.952 214.624 169.376a32 32 0 0 0-45.248 45.248l284.576 284.576-284.576 284.576a32 32 0 0 0 45.248 45.248l284.576-284.576 284.576 284.576a31.904 31.904 0 0 0 45.248 0 32 32 0 0 0 0-45.248L544.448 499.2z"
+          p-id="2295"></path>
+</svg>

+ 13 - 0
web_src/src/icons/svg/copy.svg

@@ -0,0 +1,13 @@
+<svg t="1655371415335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2134"
+     width="200" height="200">
+    <path d="M672 832 224 832c-52.928 0-96-43.072-96-96L128 160c0-52.928 43.072-96 96-96l448 0c52.928 0 96 43.072 96 96l0 576C768 788.928 724.928 832 672 832zM224 128C206.368 128 192 142.368 192 160l0 576c0 17.664 14.368 32 32 32l448 0c17.664 0 32-14.336 32-32L704 160c0-17.632-14.336-32-32-32L224 128z"
+          p-id="2135"></path>
+    <path d="M800 960 320 960c-17.664 0-32-14.304-32-32s14.336-32 32-32l480 0c17.664 0 32-14.336 32-32L832 256c0-17.664 14.304-32 32-32s32 14.336 32 32l0 608C896 916.928 852.928 960 800 960z"
+          p-id="2136"></path>
+    <path d="M544 320 288 320c-17.664 0-32-14.336-32-32s14.336-32 32-32l256 0c17.696 0 32 14.336 32 32S561.696 320 544 320z"
+          p-id="2137"></path>
+    <path d="M608 480 288.032 480c-17.664 0-32-14.336-32-32s14.336-32 32-32L608 416c17.696 0 32 14.336 32 32S625.696 480 608 480z"
+          p-id="2138"></path>
+    <path d="M608 640 288 640c-17.664 0-32-14.304-32-32s14.336-32 32-32l320 0c17.696 0 32 14.304 32 32S625.696 640 608 640z"
+          p-id="2139"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/falling.svg

@@ -0,0 +1,5 @@
+<svg t="1637916367551" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6642"
+     width="200" height="200">
+    <path d="M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z"
+          p-id="6643"></path>
+</svg>

+ 11 - 0
web_src/src/icons/svg/full.svg

@@ -0,0 +1,11 @@
+<svg t="1637931157771" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="17835" width="200" height="200">
+    <path d="M639.328 416c8.032 0 16.096-3.008 22.304-9.056l202.624-197.184-0.8 143.808c-0.096 17.696 14.144 32.096 31.808 32.192 0.064 0 0.128 0 0.192 0 17.6 0 31.904-14.208 32-31.808l1.248-222.208c0-0.672-0.352-1.248-0.384-1.92 0.032-0.512 0.288-0.896 0.288-1.408 0.032-17.664-14.272-32-31.968-32.032L671.552 96l-0.032 0c-17.664 0-31.968 14.304-32 31.968C639.488 145.632 653.824 160 671.488 160l151.872 0.224-206.368 200.8c-12.672 12.32-12.928 32.608-0.64 45.248C622.656 412.736 630.976 416 639.328 416z"
+          p-id="17836"></path>
+    <path d="M896.032 639.552 896.032 639.552c-17.696 0-32 14.304-32.032 31.968l-0.224 151.872-200.832-206.4c-12.32-12.64-32.576-12.96-45.248-0.64-12.672 12.352-12.928 32.608-0.64 45.248l197.184 202.624-143.808-0.8c-0.064 0-0.128 0-0.192 0-17.6 0-31.904 14.208-32 31.808-0.096 17.696 14.144 32.096 31.808 32.192l222.24 1.248c0.064 0 0.128 0 0.192 0 0.64 0 1.12-0.32 1.76-0.352 0.512 0.032 0.896 0.288 1.408 0.288l0.032 0c17.664 0 31.968-14.304 32-31.968L928 671.584C928.032 653.952 913.728 639.584 896.032 639.552z"
+          p-id="17837"></path>
+    <path d="M209.76 159.744l143.808 0.8c0.064 0 0.128 0 0.192 0 17.6 0 31.904-14.208 32-31.808 0.096-17.696-14.144-32.096-31.808-32.192L131.68 95.328c-0.064 0-0.128 0-0.192 0-0.672 0-1.248 0.352-1.888 0.384-0.448 0-0.8-0.256-1.248-0.256 0 0-0.032 0-0.032 0-17.664 0-31.968 14.304-32 31.968L96 352.448c-0.032 17.664 14.272 32 31.968 32.032 0 0 0.032 0 0.032 0 17.664 0 31.968-14.304 32-31.968l0.224-151.936 200.832 206.4c6.272 6.464 14.624 9.696 22.944 9.696 8.032 0 16.096-3.008 22.304-9.056 12.672-12.32 12.96-32.608 0.64-45.248L209.76 159.744z"
+          p-id="17838"></path>
+    <path d="M362.368 617.056l-202.624 197.184 0.8-143.808c0.096-17.696-14.144-32.096-31.808-32.192-0.064 0-0.128 0-0.192 0-17.6 0-31.904 14.208-32 31.808l-1.248 222.24c0 0.704 0.352 1.312 0.384 2.016 0 0.448-0.256 0.832-0.256 1.312-0.032 17.664 14.272 32 31.968 32.032L352.448 928c0 0 0.032 0 0.032 0 17.664 0 31.968-14.304 32-31.968s-14.272-32-31.968-32.032l-151.936-0.224 206.4-200.832c12.672-12.352 12.96-32.608 0.64-45.248S375.008 604.704 362.368 617.056z"
+          p-id="17839"></path>
+</svg>

+ 9 - 0
web_src/src/icons/svg/info.svg

@@ -0,0 +1,9 @@
+<svg t="1684405184329" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2377"
+     width="200" height="200">
+    <path d="M512 960c-119.466667 0-232.533333-46.933333-315.733333-132.266667C110.933333 744.533333 64 631.466667 64 512c0-119.466667 46.933333-232.533333 132.266667-315.733333S392.533333 64 512 64c119.466667 0 232.533333 46.933333 315.733333 132.266667 85.333333 85.333333 132.266667 196.266667 132.266667 315.733333 0 119.466667-46.933333 232.533333-132.266667 315.733333-83.2 85.333333-196.266667 132.266667-315.733333 132.266667z m0-853.333333c-108.8 0-209.066667 42.666667-285.866667 119.466666C149.333333 302.933333 106.666667 403.2 106.666667 512s42.666667 209.066667 119.466666 285.866667C302.933333 874.666667 403.2 917.333333 512 917.333333s209.066667-42.666667 285.866667-119.466666C874.666667 721.066667 917.333333 620.8 917.333333 512s-42.666667-209.066667-119.466666-285.866667C721.066667 149.333333 620.8 106.666667 512 106.666667z"
+          p-id="2378"></path>
+    <path d="M512 245.333333c23.466667 0 42.666667 19.2 42.666667 42.666667s-19.2 42.666667-42.666667 42.666667-42.666667-19.2-42.666667-42.666667 19.2-42.666667 42.666667-42.666667zM522.666667 746.666667c-12.8 0-21.333333-8.533333-21.333334-21.333334V448h-21.333333c-12.8 0-21.333333-8.533333-21.333333-21.333333s8.533333-21.333333 21.333333-21.333334h42.666667c12.8 0 21.333333 8.533333 21.333333 21.333334v298.666666c0 12.8-8.533333 21.333333-21.333333 21.333334z"
+          p-id="2379"></path>
+    <path d="M597.333333 746.666667h-149.333333c-12.8 0-21.333333-8.533333-21.333333-21.333334s8.533333-21.333333 21.333333-21.333333h149.333333c12.8 0 21.333333 8.533333 21.333334 21.333333s-8.533333 21.333333-21.333334 21.333334z"
+          p-id="2380"></path>
+</svg>

+ 8 - 0
web_src/src/icons/svg/logs.svg

@@ -0,0 +1,8 @@
+<svg t="1637928354365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="14662" width="200" height="200">
+    <path d="M448.93 890.09H174.61a42.51 42.51 0 0 1-42.46-42.46V154.18a42.51 42.51 0 0 1 42.46-42.46h551.28a42.51 42.51 0 0 1 42.46 42.46v307.61H712V168.1H188.53v665.62h260.4z"
+          p-id="14663"></path>
+    <path d="M253.01 241.57h410.51v56.38H253.01zM253.01 372.82h410.51v56.38H253.01zM706.86 915.29c-107.54 0-195-87.49-195-195s87.49-195 195-195 195 87.49 195 195-87.46 195-195 195z m0-333.69c-76.46 0-138.66 62.2-138.66 138.66s62.2 138.66 138.66 138.66 138.66-62.2 138.66-138.66S783.31 581.6 706.86 581.6z"
+          p-id="14664"></path>
+    <path d="M785.26 742.45h-96.55V617.36h28.19v96.9h68.36v28.19z" p-id="14665"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/next.svg

@@ -0,0 +1,5 @@
+<svg t="1681641647302" class="icon" viewBox="0 0 1257 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2608"
+     width="200" height="200">
+    <path d="M745.47264 1024c11.264 0 23.552-4.096 31.744-13.312L1244.16064 543.744a45.12 45.12 0 0 0 0-63.488L777.21664 13.312a45.12 45.12 0 0 0-63.488 0c-18.432 17.408-18.432 46.08 0 64.512L1147.90464 512 713.72864 946.176c-18.432 18.432-18.432 47.104 0 64.512 8.192 9.216 20.48 13.312 31.744 13.312zM45.05664 557.056h1102.848A45.44 45.44 0 0 0 1192.96064 512a44.8 44.8 0 0 0-45.056-45.056H45.05664A44.8 44.8 0 0 0 0.00064 512c0 24.576 20.48 45.056 45.056 45.056z"
+          p-id="2609"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/prev.svg

@@ -0,0 +1,5 @@
+<svg t="1681641683494" class="icon" viewBox="0 0 1257 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3591"
+     width="200" height="200">
+    <path d="M512 1024c-11.264 0-23.552-4.096-31.744-13.312L13.312 543.744a45.12 45.12 0 0 1 0-63.488L480.256 13.312a45.12 45.12 0 0 1 63.488 0c18.432 17.408 18.432 46.08 0 64.512L109.568 512l434.176 434.176c18.432 18.432 18.432 47.104 0 64.512-8.192 9.216-20.48 13.312-31.744 13.312z m700.416-466.944H109.568A45.44 45.44 0 0 1 64.512 512c0-25.6 20.48-45.056 45.056-45.056h1102.848A44.8 44.8 0 0 1 1257.472 512a45.44 45.44 0 0 1-45.056 45.056z"
+          p-id="3592"></path>
+</svg>

+ 9 - 0
web_src/src/icons/svg/statistics.svg

@@ -0,0 +1,9 @@
+<svg t="1637928260937" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="12979" width="200" height="200">
+    <path d="M496 851.91H100V169.04h824V359h-48V217.04H148v586.87h348v48z" p-id="12980"></path>
+    <path d="M924 383H100V169h824z m-776-48h728V217H148zM214 504.98h249.71v48H214zM214 628.43h199.71v48H214z"
+          p-id="12981"></path>
+    <path d="M622.26 560.28h48v204.3h-48zM722.34 612.56h48v152.01h-48zM822.42 687.83h48v76.75h-48z" p-id="12982"></path>
+    <path d="M746.34 893a230.77 230.77 0 1 1 163.17-67.6A229.21 229.21 0 0 1 746.34 893z m0-413.51c-100.77 0-182.76 82-182.76 182.75S645.57 845 746.34 845 929.1 763 929.1 662.23s-81.99-182.75-182.76-182.75z"
+          p-id="12983"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/system.svg

@@ -0,0 +1,5 @@
+<svg t="1637928171784" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="11261" width="200" height="200">
+    <path d="M947.188 835.927l-50.079-35.055c-18.559-12.992-56.418-22.725-85.694-22.725H585.19v-79.844h288.761c2.2 0 4-1.8 4-4V156.702c0-2.2-1.8-4-4-4H176.663c-2.2 0-4 1.8-4 4v537.601c0 2.2 1.8 4 4 4h302.069v79.844H212.585c-29.276 0-67.134 9.734-85.694 22.725l-50.079 35.055c-26.411 18.488-17.538 35.371 22.786 35.371h824.803c40.324 0 49.198-16.883 22.787-35.371zM427.175 491.008a16.001 16.001 0 0 1-27.018 4.917l-66.792-76.29-30.074 34.372a16.002 16.002 0 0 1-12.042 5.464h-38.742c-8.836 0-16-7.164-16-16s7.164-16 16-16h31.481l37.331-42.666a16 16 0 0 1 24.08-0.004l61.215 69.92 60.521-161.261a16 16 0 0 1 30.347 1.168l41.855 144.388c0.045 0.156 0.088 0.313 0.128 0.47l17.727 68.934 53.023-135.33a16.001 16.001 0 0 1 14.896-10.163h0.1a16 16 0 0 1 14.87 10.347l35.146 93.072 36.371-46.095a16 16 0 0 1 12.561-6.089H784.8c8.837 0 16 7.164 16 16s-7.163 16-16 16h-52.885l-49.098 62.224a16 16 0 1 1-27.529-4.259l-30.451-80.64-55.913 142.705a16 16 0 0 1-30.393-1.852l-29.996-116.649-28.371-97.873-52.989 141.19z"
+          p-id="11262"></path>
+</svg>

+ 5 - 0
web_src/src/icons/svg/up.svg

@@ -0,0 +1,5 @@
+<svg t="1637924320195" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="10438" width="200" height="200">
+    <path d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3c-3.8 5.3-0.1 12.7 6.5 12.7h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
+          p-id="10439"></path>
+</svg>

+ 7 - 0
web_src/src/icons/svg/warning.svg

@@ -0,0 +1,7 @@
+<svg t="1637929771909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="16958" width="200" height="200">
+    <path d="M827.5 229.3l-298.6-97c-10.8-4.6-22.9-4.6-33.6 0l-298.7 97c-15.7 6.7-25.9 22.1-25.9 39.2v207.1c0 151.2 73.5 260 196.5 347.9l120 63.7c7.4 5.3 16.1 8 24.8 8s17.5-2.7 24.9-8l120-63.7C780 735.6 853.4 626.8 853.4 475.6V268.5c-0.1-17.1-10.2-32.5-25.9-39.2z m-40.2 242.8c0 133-64.6 225.7-172.9 303l-102.3 50.1-102.3-50.1c-108.3-77.3-172.9-170-172.9-303V279.7l275.2-86.9 275.2 86.9v192.4z"
+          fill="#e6e6e6" p-id="16959"></path>
+    <path d="M619.7 453.4c-1.1-2.2-3.3-3.6-5.8-3.6h-75.5l74.5-114.9c1.2-1.9 1.3-4.4 0.2-6.3-1.1-2-3.3-3.2-5.6-3.2H505.6c-2.4 0-4.6 1.3-5.7 3.4l-95.6 186.7c-1 1.9-0.9 4.2 0.3 6 1.2 1.8 3.2 3 5.4 3h65.5L404.2 690c-1.2 2.8-0.1 6.1 2.6 7.7 2.7 1.6 6.1 1.1 8.2-1.3L618.8 460c1.6-1.8 1.9-4.4 0.9-6.6z"
+          fill="#e6e6e6" p-id="16960"></path>
+</svg>

+ 7 - 0
web_src/src/icons/svg/ybp.svg

@@ -0,0 +1,7 @@
+<svg t="1638172085633" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2403"
+     width="200" height="200">
+    <path d="M886.7 449.2c-20.5-48.4-49.8-91.9-87.1-129.3-37.3-37.3-80.8-66.7-129.3-87.1-50.1-21.2-103.4-32-158.3-32s-108.1 10.8-158.3 32c-48.4 20.5-91.9 49.8-129.3 87.1s-66.7 80.8-87.1 129.3c-21.2 50.1-32 103.4-32 158.3h46c0-198.9 161.8-360.7 360.7-360.7s360.7 161.8 360.7 360.7h46c0-54.8-10.8-108.1-32-158.3zM205.1 777.1H817v46H205.1z"
+          p-id="2404"></path>
+    <path d="M493.7 712.6l88.2 0.6-0.6-88.2-229.2-141.5 141.6 229.1z m41.9-45.7l-16.1-0.1-25.8-41.8 41.8 25.8 0.1 16.1zM191.7 649.3h64v46h-64zM248.333 442.22l32.526-32.527 45.255 45.255-32.527 32.526zM696.93 454.961l45.255-45.254 32.527 32.526-45.255 45.255zM488.8 288.4h46v63.8h-46zM767.8 649.3h64v46h-64z"
+          p-id="2405"></path>
+</svg>

+ 4 - 6
web_src/src/layout/UiHeader.vue

@@ -61,18 +61,16 @@ export default {
     return {
       alarmNotify: false,
       sseSource: null,
-      username: userService.getUser().username,
+      username: '',
       activeIndex: this.$route.path,
       unreadAlarmCount: 0,
       // 刷新时间
-      refreshTime: 30 * 1000,
+      refreshTime: 30 * 1000 * 10,
       requestInProgress: false,
-      editUser: userService.getUser() ? userService.getUser().role.id === 1 : false
+      editUser: false
     };
   },
   created() {
-    console.log(4444)
-    console.log(JSON.stringify(userService.getUser()))
     if (this.$route.path.startsWith("/channelList")) {
       this.activeIndex = "/deviceList"
 
@@ -169,7 +167,7 @@ export default {
       this.requestInProgress = true;
       let [err,res] = await handle(this.$axios.get('ai/unread'));
       this.requestInProgress = false;
-      setTimeout(() => { this.fetchAlarmCount(); }, this.refreshTime);
+      // setTimeout(() => { this.fetchAlarmCount(); }, this.refreshTime);
       if(err){
         return console.error(err);
       }

+ 476 - 0
web_src/src/layout/adminLayout.vue

@@ -0,0 +1,476 @@
+<script>
+
+export default {
+  name: "adminLayout",
+  props: {
+    indexPath: {
+      default: '/'
+    },
+    headerHeight: {
+      default: '35px'
+    },
+    sideBarWidth: {
+      default: '270px'
+    },
+    sideBarHeight: {
+      default: '35px'
+    },
+    bgColor: {default: '#fff'},
+    sideBarColor: {default: '#e8e8e8'},
+    sideBarItemBgColor: {default: '#e8e8e8'},
+    sideBarItemHoverBgColor: {default: '#f0f0f0'},
+    headerBgColor: {default: '#086fb6'},
+    // 文本颜色, 主要文本颜色 次要文本颜色
+    textColor: {default: '#333'},
+    mainTextColor: {default: '#333'},
+    textColorHover: {default: '#fff'},
+    mainTextColorHover: {default: '#fff'},
+
+    btnColor: {default: '#086fb6'},
+    btnColorHover: {default: '#d2851a'},
+    btnTextColor: {default: '#ffffff'},
+    btnTextColorHover: {default: '#0ccdef'},
+
+    sidebarMenus: {
+      default() {
+        return []
+      }
+    },
+    interactSideBar: {
+      default: true
+    },
+    logoTitle: {
+      default: ''
+    },
+    headerMenus: {
+      default() {
+        return []
+      }
+    },
+    headerMenuKey: {
+      default: ''
+    },
+    userName: {
+      default: ''
+    },
+    btnLink: {
+      type: Boolean,
+      default: false
+    },
+    activeKey: {
+      default: ''
+    },
+  },
+  data() {
+    return {
+      isShowSideBar: true,
+      cssText: '',
+      sideMenus: [],
+    }
+  },
+  beforeMount() {
+    this.init_computedStyle();
+  },
+  watch: {
+    sidebarMenus() {
+      this.init_comSideBar();
+    },
+  },
+  mounted() {
+    this.init_comSideBar();
+  },
+  methods: {
+    init_comSideBar() {
+      // 循环遍历菜单
+      this.sideMenus = this.sidebarMenus.map((item) => {
+        let newItem = {...item};
+        newItem.isShow = false;
+        return newItem;
+      });
+    },
+    switchHandle() {
+      if (!this.interactSideBar) {
+        return console.log('不允许交互sidebar');
+      }
+      this.isShowSideBar = !this.isShowSideBar;
+      this.init_computedStyle();
+    },
+
+    switchSideBarMenuHandle(item) {
+      item.isShow = !item.isShow;
+    },
+
+    /**
+     * 计算dom元素样式
+     */
+    init_computedStyle() {
+      let cssText = `
+      --headerHeight: ${this.headerHeight};
+      --sideBarWidth: ${this.isShowSideBar ? this.sideBarWidth : this.sideBarHeight};
+      --baseSideBarWidth: ${this.sideBarWidth};
+      --sideBarHeight: ${this.sideBarHeight};
+      --bg-color: ${this.bgColor};
+      --sideBarBgColor: ${this.sideBarColor};
+      --header-color: ${this.headerBgColor};
+      --text-color: ${this.textColor};
+      --main-text-color: ${this.mainTextColor};
+      --text-color-hover: ${this.textColorHover};
+      --main-text-color-hover: ${this.mainTextColorHover};
+      --btn-color: ${this.btnColor};
+      --btn-color-hover: ${this.btnColorHover};
+      --btn-text-color: ${this.btnTextColor};
+      --btn-text-color-hover: ${this.btnTextColorHover};
+      `;
+      this.cssText = cssText;
+    },
+  }
+}
+</script>
+
+<template>
+  <!-- 管理员控制台布局 永久性头部,可伸缩侧边栏-->
+
+  <div class="lay-con" :style="cssText">
+    <!--  头部横条 -->
+    <div class="lay-header">
+      <div class="lay-logo">
+        <slot name="logo">
+          <h1>
+            <a :href="indexPath">
+              {{ logoTitle }}
+            </a>
+          </h1>
+
+
+        </slot>
+      </div>
+      <div class="lay-menus">
+        <div class="menus">
+        <span
+            v-for="item in headerMenus"
+            :key="item.key"
+            :class="`menu ${headerMenuKey===item.key?'':''}`"
+        >
+          <a :href="item.href">{{ item.text }}</a>
+        </span>
+        </div>
+        <div class="bell">
+          <slot name="bell"></slot>
+        </div>
+        <div class="user">
+          <slot name="user">
+            <span>{{ userName }}</span>
+          </slot>
+        </div>
+
+      </div>
+    </div>
+    <!--  内容部分-->
+    <div class="lay-box">
+      <div :class="`lay-sideBar ${!isShowSideBar?'lay-sideBar-hide':''}`">
+        <!--      侧边按钮 -->
+        <div class="lay-sideBar-control" @click="switchHandle">
+          <svg-icon :icon-class="isShowSideBar?'prev':'next'"/>
+        </div>
+        <!--          侧边栏,带标题,icon,下拉菜单-->
+        <div v-for="(item,i) in sideMenus"
+             :key="'sideBar-'+i"
+             :class="`bar-menu-item ${item.isShow?'bar-menu-item-show':''}`"
+        >
+          <div class="menu-title">
+            <span class="icon"> <svg-icon :icon-class="item.icon"/> </span>
+            <span class="text">{{ item.title }}</span>
+            <span class="icon option" @click="switchSideBarMenuHandle(item)">
+            <svg-icon :icon-class="item.isShow?'arrow-up':'arrow-down'"/>
+          </span>
+          </div>
+          <div class="sub-menu-box">
+            <div v-for="sub in item.child"
+                 :key="sub.key"
+                 :class="`menu-item ${activeKey===sub.key?'menu-items-show':''}`"
+            >
+              <router-link :to="sub.path" v-if="sub.type == 'router'">
+                {{ sub.title }}
+              </router-link>
+              <a :href="sub.path" v-else>{{ sub.title }}</a>
+            </div>
+          </div>
+
+        </div>
+      </div>
+      <div class="lay-content">
+        <slot></slot>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+.lay-con {
+  --headerHeight: v-bind(headerHeight);
+  --sideBarWidth: v-bind(sideBarWidth);
+  --sideBarHeight: v-bind(sideBarHeight);
+  --baseSideBarWidth: v-bind(sideBarWidth);
+  --bg-color: v-bind(bgColor);
+  --sideBarBgColor: v-bind(sideBarColor);
+  --header-color: v-bind(headerBgColor);
+  --text-color: v-bind(textColor);
+  --main-text-color: v-bind(mainTextColor);
+  --text-color-hover: v-bind(textColorHover);
+  --main-text-color-hover: v-bind(mainTextColorHover);
+
+  --btn-color: v-bind(btnColor);
+  --btn-color-hover: v-bind(btnColorHover);
+  --btn-text-color: v-bind(btnTextColor);
+  --btn-text-color-hover: v-bind(btnTextColorHover);
+
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+  background-color: var(--sideBarBgColor);
+  position: relative;
+
+}
+
+a {
+  text-decoration: none;
+  color: var(--btn-text-color)
+}
+
+.lay-header {
+  background-color: var(--header-color);
+  display: flex;
+  width: 100%;
+  height: var(--headerHeight);
+  flex-direction: row;
+}
+
+.lay-header > * {
+  width: 50%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+}
+
+.lay-logo {
+  margin-left: 15px;
+  margin-right: 15px;
+  box-sizing: border-box;
+}
+
+.lay-logo a {
+  color: var(--btn-text-color);
+  text-decoration: none;
+  font-weight: bold;
+  transition: color .7s;
+}
+
+.lay-logo a:hover {
+  color: var(--btn-text-color-hover);
+}
+
+.lay-menus {
+  justify-content: end;
+}
+
+.lay-menus .menus {
+  display: flex;
+  padding: 0 5px;
+}
+
+.lay-menus .menus .menu {
+  padding: 5px 15px;
+}
+
+.lay-menus .menus .menu:hover {
+  color: var(--text-color-hover);
+}
+
+.lay-box {
+  width: 100%;
+  height: calc(100% - var(--headerHeight));
+  display: flex;
+}
+
+.lay-box > * {
+  transition: width .7s;
+  height: 100%;
+  position: relative;
+}
+
+.lay-sideBar {
+  width: var(--sideBarWidth);
+}
+
+.lay-content {
+  width: calc(100% - var(--sideBarWidth));
+  height: 100%;
+  overflow: auto;
+  background-color: var(--bg-color);
+}
+
+.lay-sideBar-control {
+  position: relative;
+  width: 25px;
+  height: 25px;
+  top: 0px;
+  margin-top: 5px;
+  left: 100%;
+  transform: translate(-150%, 0);
+  border-radius: 3px;
+  border: 1px solid black;
+  z-index: 999;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: var(--sideBarBgColor);
+}
+
+.lay-sideBar-hide .lay-sideBar-control {
+  left: 50%;
+  transform: translate(-50%, 0);
+}
+
+.lay-sideBar-control:hover {
+  border-color: #f0ad4e;
+  background-color: #f0ad4e;
+  color: #fff;
+}
+
+.bar-menu-item {
+  width: 100%;
+  height: auto;
+  position: relative;
+  margin-top: 5px;
+}
+
+.bar-menu-item .menu-title {
+  display: flex;
+  width: 100%;
+  height: var(--sideBarHeight);
+  align-items: center;
+  box-sizing: border-box;
+  padding-left: 5px;
+  padding-right: 5px;
+  cursor: default;
+  overflow: hidden;
+}
+
+.bar-menu-item .menu-title:hover {
+  color: var(--btn-text-color-hover);
+}
+
+.bar-menu-item .menu-title .icon {
+  width: var(--sideBarHeight);
+  height: var(--sideBarHeight);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: var(--text-color);
+  flex-shrink: 0;
+}
+
+.bar-menu-item .menu-title .icon * {
+  color: inherit;
+}
+
+.bar-menu-item .menu-title .option {
+  cursor: pointer;
+  color: var(--text-color);
+}
+
+.bar-menu-item .menu-title .option:hover {
+  color: var(--btn-text-color-hover);
+}
+
+.bar-menu-item .menu-title .text {
+  width: calc(100% - var(--sideBarHeight) - var(--sideBarHeight));
+  display: flex;
+  align-items: center;
+}
+
+.bar-menu-item .sub-menu-box {
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  transition: height .7s;
+  box-sizing: border-box;
+  padding: 0 5px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.bar-menu-item .sub-menu-box .menu-item {
+  width: 100%;
+  height: calc(var(--sideBarHeight) - 5px);
+  box-sizing: border-box;
+  padding-left: calc(var(--sideBarHeight) / 2 + 5px);
+  cursor: pointer;
+  background-color: var(--btn-color);
+  color: var(--btn-text-color);
+  margin-top: 2px;
+}
+
+.bar-menu-item .sub-menu-box .menu-item > a {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.bar-menu-item .sub-menu-box .menu-item:hover {
+  color: var(--btn-text-color-hover);
+  background-color: var(--btn-color-hover);
+}
+
+.bar-menu-item-show {
+  height: auto;
+}
+
+.bar-menu-item-show .sub-menu-box {
+  height: auto;
+}
+
+
+.lay-sideBar-hide .lay-sideBar {
+  padding-top: 50px;
+}
+
+.lay-sideBar-hide .bar-menu-item {
+  box-sizing: border-box;
+  margin: 10px 0;
+  border-top: 1px solid var(--sideBarBgColor);
+}
+
+.lay-sideBar-hide .bar-menu-item .menu-title {
+  padding-left: 0;
+  padding-right: 0;
+}
+
+.lay-sideBar-hide .bar-menu-item .sub-menu-box {
+  position: absolute;
+  display: none;
+  top: 0;
+  left: 100%;
+  height: auto;
+  width: calc(var(--baseSideBarWidth) - var(--sideBarWidth) * 2);
+  padding: 5px;
+  box-shadow: 0px 0px 2px 3px var(--sideBarBgColor);
+  z-index: 9999;
+  background-color: var(--bg-color);
+  border-top: 1px solid var(--bg-color);
+}
+
+.lay-sideBar-hide .bar-menu-item:hover {
+  background-color: var(--bg-color);
+}
+
+.lay-sideBar-hide .bar-menu-item:hover .sub-menu-box {
+  display: block;
+}
+
+</style>

+ 25 - 0
web_src/src/map/userMenus.js

@@ -0,0 +1,25 @@
+export const userMenus = [
+    {
+        title: '摄像头',
+        icon: 'camera',
+        child: [
+            {
+                title: '我的设备',
+                key: 'devices',
+                type: 'router',
+                path: '/devices'
+            },
+            {
+                title: '设备1',
+                key: 'devices2',
+                type: 'router',
+                path: '/devices2'
+            },
+            {
+                title: '普通设备',
+                key: 'devices3',
+                path: '/account'
+            }
+        ]
+    }
+]

+ 63 - 16
web_src/src/pages/account/AccountRegister.vue

@@ -1,16 +1,19 @@
 <script>
 
-import { FieldCheck, FormVerify } from 'kind-form-verify';
+import {FieldCheck, FormVerify} from 'kind-form-verify';
 import fieldCheck from '@/until/FieldCheck'
 import handle from "@/until/handle";
 import crypto from "crypto";
+import querystring from "querystring";
 
 
+import {waitJump} from "@components/modal/waitJump";
+
 let fieldVerify = null;
 let registerFieldVerify = null;
 export default {
   name: 'app',
-  data(){
+  data() {
     return {
       form: {
         account: {
@@ -50,7 +53,6 @@ export default {
           msg: "",
           showText: ""
         },
-
       },
       loginTitle: "用户登录",
       rememberPassword: false,
@@ -102,7 +104,7 @@ export default {
       let url = `/account/login`
       let [err,res] = await handle(this.$axios.post(
           url,
-          formData
+          querystring.stringify(formData)
       ));
       this.isLoging = false;
       if (err) {
@@ -115,6 +117,13 @@ export default {
         this.$message.error(`登录失败 ${res.data.msg}`);
         return;
       }
+      this.$message.success(`登录成功`);
+      // 页面跳转
+      waitJump('登录成功',
+          '将在_ 秒后自动跳转页面.',
+          '_',
+          "/device",
+          5)
     },
 
     async sendRegister(){
@@ -127,9 +136,10 @@ export default {
       formData.password = crypto.createHash('md5').update(formData.password, "utf8").digest('hex')
       console.log(formData)
       let url = `/account/register`
+      // java 后台的解析方式为参数解析, 所以将其转为queryString
       let [err,res] = await handle(this.$axios.post(
           url,
-          formData
+          querystring.stringify(formData)
       ));
       this.isLoging = false;
       if (err) {
@@ -142,19 +152,42 @@ export default {
         this.$message.error(`账号注册失败 ${res.data.msg}`);
         return;
       }
+      this.$message.success(`账号注册成功`);
+      this.toLogin();
     },
 
-    loginBlurHandle(){
+
+    loginBlurHandle(key) {
       console.log("loginBlurHandle")
-      fieldVerify.check()
+      if (!this.form[key]) {
+        return console.warn("error field")
+      }
+      fieldVerify.checkItem(key)
     },
 
-    registerBlurHandle(){
+    registerBlurHandle(key) {
       console.log("registerBlurHandle")
-      registerFieldVerify.check()
+      if (!this.registerForm[key]) {
+        return console.warn("error field")
+      }
+      registerFieldVerify.checkItem(key)
+    },
+    /**
+     * 聚焦元素时移除错误提示
+     * @param type
+     * @param key
+     */
+    focusHandle(type, key) {
+      let formObject = this.form;
+      if (type == 'register') {
+        formObject = this.registerForm;
+      }
+      if (!formObject[key]) {
+        return console.warn("error field")
+      }
+      formObject[key].msg = ""
     }
 
-
   }
 }
 </script>
@@ -168,12 +201,18 @@ export default {
         <div class="title">{{loginTitle}}</div>
 
         <el-form-item class="form-item" prop="account" :error="form.account.msg">
-          <el-input v-model="form.account.val" placeholder="请输入内容" @blur="loginBlurHandle">
+          <el-input v-model="form.account.val"
+                    placeholder="请输入内容"
+                    @blur="loginBlurHandle('account')"
+                    @focus="focusHandle('login', 'account')">
           </el-input>
         </el-form-item>
 
-        <el-form-item class="form-item"  prop="password" :error="form.password.msg">
-          <el-input v-model="form.password.val" show-password placeholder="用户密码" @blur="loginBlurHandle">
+        <el-form-item class="form-item" prop="password" :error="form.password.msg">
+          <el-input v-model="form.password.val"
+                    show-password placeholder="用户密码"
+                    @blur="loginBlurHandle('password')"
+                    @focus="focusHandle('login', 'password')">
           </el-input>
         </el-form-item>
 
@@ -195,14 +234,19 @@ export default {
         <el-form-item class="form-item" prop="name" :error="registerForm.name.msg">
           <el-input v-model="registerForm.name.val"
                     placeholder="名称"
-                    @blur="registerBlurHandle">
+                    auto-complete="new-password"
+                    @blur="registerBlurHandle('name')"
+                    @focus="focusHandle('register', 'name')"
+          >
           </el-input>
         </el-form-item>
 
         <el-form-item class="form-item" prop="account" :error="registerForm.account.msg">
           <el-input v-model="registerForm.account.val"
                     placeholder="登录账号"
-                    @blur="registerBlurHandle"
+                    auto-complete="new-password"
+                    @blur="registerBlurHandle('account')"
+                    @focus="focusHandle('register', 'account')"
           >
           </el-input>
         </el-form-item>
@@ -210,7 +254,10 @@ export default {
         <el-form-item class="form-item"  prop="password" :error="registerForm.password.msg">
           <el-input v-model="registerForm.password.val"
                     show-password placeholder="用户密码"
-                    @blur="registerBlurHandle">
+                    auto-complete="new-password"
+                    @blur="registerBlurHandle('password')"
+                    @focus="focusHandle('register', 'password')"
+          >
           </el-input>
         </el-form-item>
 

+ 2 - 0
web_src/src/pages/account/main.js

@@ -1,5 +1,7 @@
 import Vue from 'vue';
 import App from './AccountRegister.vue';
+// 注册svg组件
+import '@/icons/registerSvg';
 import '@/assets/base.css'
 
 import ElementUI from 'element-ui';

+ 58 - 0
web_src/src/pages/device/UserDevice.vue

@@ -0,0 +1,58 @@
+<script>
+
+import {FieldCheck, FormVerify} from 'kind-form-verify';
+import fieldCheck from '@/until/FieldCheck'
+import handle from "@/until/handle";
+import crypto from "crypto";
+import querystring from "querystring";
+import AdminLayout from "@/layout/adminLayout.vue";
+import {userMenus} from "@/map/userMenus";
+
+
+export default {
+  name: 'app',
+  components: {AdminLayout},
+  data() {
+    return {
+      userMenus: userMenus,
+      isLoading: false
+    }
+  },
+  beforeMount() {
+  },
+
+  mounted() {
+
+  },
+
+  methods: {}
+}
+</script>
+
+<template>
+  <div id="app">
+    <admin-layout
+        :logoTitle="'合方圆监控中心'"
+        :sidebar-menus="userMenus"
+        :header-height="'45px'"
+    >
+      <router-view>
+
+      </router-view>
+    </admin-layout>
+
+  </div>
+</template>
+
+<style scoped>
+#app {
+  margin: 0 0;
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #fff;
+}
+
+</style>

+ 20 - 0
web_src/src/pages/device/device.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <title>合方圆-国标28181</title>
+    <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon">
+    <link rel="stylesheet" type="text/css" href="/static/css/iconfont.css">
+    <link rel="stylesheet" type="text/css" href="/static/css/login.css">
+</head>
+<body>
+<script type="text/javascript" src="/static/dist/missile.js"></script>
+<script type="text/javascript" src="/static/dist/h265webjs-v20221106.js"></script>
+<script type="text/javascript" src="/static/js/jessibuca/jessibuca.js"></script>
+<script type="text/javascript" src="/static/js/EasyWasmPlayer.js"></script>
+<!--    <script type="text/javascript" src="static/js/liveplayer-lib.min.js"></script>-->
+<script type="text/javascript" src="/static/js/config.js"></script>
+<div id="app"></div>
+</body>
+</html>

+ 42 - 0
web_src/src/pages/device/main.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import App from './UserDevice.vue';
+import deviceRouter from './router/deviceRouter';
+import '@/icons/registerSvg';
+import '@/assets/base.css'
+
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+
+import axios from '@/apiStore/axios';
+import userService from "@components/service/UserService";
+import router from "@/router";
+import {errorWaitJump, waitJump} from "@components/modal/waitJump";
+
+let _axios = axios.axios;
+_axios.interceptors.response.use((response) => {
+    // 对响应数据做点什么
+    let token = response.headers["access-token"];
+    if (token) {
+        console.log("更新token");
+        userService.setToken(token)
+    }
+    return response;
+}, (error) => {
+    // 对响应错误做点什么
+    if (error.response.status === 401) {
+        console.log("Received 401 Response");
+        errorWaitJump("登录失效",
+            "将在_ 秒后自动跳转页面.",
+            "_",
+            "/account",
+            3)
+    }
+    return Promise.reject(error);
+});
+
+Vue.prototype.$axios = _axios;
+Vue.use(ElementUI);
+new Vue({
+    router: deviceRouter,
+    render: h => h(App),
+}).$mount("#app")

+ 19 - 0
web_src/src/pages/device/router/deviceRouter.js

@@ -0,0 +1,19 @@
+import Vue from "vue";
+import VueRouter from "vue-router";
+
+import deviceList from "@components/account_com/deviceList.vue";
+
+
+Vue.use(VueRouter)
+
+export default new VueRouter({
+        mode: 'hash',
+        routes: [
+            {
+                path: '/',
+                name: 'home',
+                component: deviceList,
+            }
+        ]
+    }
+)

+ 16 - 5
web_src/src/until/FieldCheck.js

@@ -18,22 +18,33 @@ fieldCheck.addRuleItem('account',['account'],[
     requiredRuleItem,
     {
         type: 'string',
-        minLength: 5,
+        minLength: 3,
         maxLength: 30,
-        message: '账号长度限制为 5-30 个字符'
+        message: '账号长度限制为 3-30 个字符'
     }
 ]);
 
-fieldCheck.addRuleItem('password',['password'],[
+fieldCheck.addRuleItem('password', ['password'], [
     requiredRuleItem,
     {
         type: 'string',
-        minLength: 8,
+        minLength: 6,
         maxLength: 30,
-        message: "登录密码为 8-30 个字符"
+        message: "登录密码为 6-30 个字符"
     }
 ]);
 
+fieldCheck.addRuleItem('bindCode', ['bindCode'], [
+    requiredRuleItem,
+    {
+        type: 'string',
+        minLength: 15,
+        maxLength: 20,
+        message: "绑定码应该为 20 位"
+    }
+]);
+
+
 export default {
     fieldCheck
 }

+ 13 - 3
web_src/vue.config.js

@@ -6,8 +6,8 @@ function resolve(dir) {
     return path.join(__dirname, dir);
 }
 
-// const baseUrl = "https://127.0.0.1:19200"
-const baseUrl = "https://hofuniot.cn:29072"
+const baseUrl = "https://127.0.0.1:19200"
+// const baseUrl = "https://hofuniot.cn:29072"
 // const c =
 module.exports = {
     devServer: {
@@ -47,6 +47,11 @@ module.exports = {
                 secure: false,
                 changeOrigin: true,
             },
+            '/device/': {
+                target: baseUrl,
+                secure: false,
+                changeOrigin: true,
+            },
             '/static/snap': {
                 target: baseUrl,
                 secure: false,
@@ -71,7 +76,12 @@ module.exports = {
             entry: 'src/pages/account/main.js',
             template: 'src/pages/account/index.html',
             filename: 'account.html'
-        }
+        },
+        device: {
+            entry: 'src/pages/device/main.js',
+            template: 'src/pages/device/index.html',
+            filename: 'device.html'
+        },
     },
     configureWebpack: webPackConfig,
     css: {