浏览代码

cv 结束

East 3 年之前
当前提交
4135acd4be
共有 100 个文件被更改,包括 35711 次插入0 次删除
  1. 23 0
      .gitignore
  2. 24 0
      README.md
  3. 14 0
      babel.config.js
  4. 28988 0
      package-lock.json
  5. 60 0
      package.json
  6. 二进制
      public/favicon.ico
  7. 17 0
      public/index.html
  8. 42 0
      public/static/dahua/index.html
  9. 26 0
      public/static/dahua/src/WSPlayer/CONSTANT.js
  10. 112 0
      public/static/dahua/src/WSPlayer/PlayerItem.js
  11. 186 0
      public/static/dahua/src/WSPlayer/RealPlayer.js
  12. 294 0
      public/static/dahua/src/WSPlayer/RecordPlayer.js
  13. 253 0
      public/static/dahua/src/WSPlayer/WSPlayer.js
  14. 190 0
      public/static/dahua/src/WSPlayer/spin.js
  15. 1 0
      public/static/dahua/static/WSPlayer/PlayerControl.js
  16. 1 0
      public/static/dahua/static/WSPlayer/WSPlayer.js
  17. 1 0
      public/static/dahua/static/WSPlayer/audioTalkWorker.worker.js
  18. 1 0
      public/static/dahua/static/WSPlayer/audioWorker.worker.js
  19. 76 0
      public/static/dahua/static/WSPlayer/ffmpegasm.js
  20. 二进制
      public/static/dahua/static/WSPlayer/ffmpegasm.js.mem
  21. 二进制
      public/static/dahua/static/WSPlayer/icon/4screen-d.png
  22. 19 0
      public/static/dahua/static/WSPlayer/icon/4screen-d.svg
  23. 二进制
      public/static/dahua/static/WSPlayer/icon/4screen-h.png
  24. 19 0
      public/static/dahua/static/WSPlayer/icon/4screen-h.svg
  25. 二进制
      public/static/dahua/static/WSPlayer/icon/4screen-n.png
  26. 19 0
      public/static/dahua/static/WSPlayer/icon/4screen-n.svg
  27. 二进制
      public/static/dahua/static/WSPlayer/icon/4screen-p.png
  28. 19 0
      public/static/dahua/static/WSPlayer/icon/4screen-p.svg
  29. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOffDisable.png
  30. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOffHover.png
  31. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOffNormal.png
  32. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOffPress.png
  33. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOnDisable.png
  34. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOnHover.png
  35. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOnNormal.png
  36. 二进制
      public/static/dahua/static/WSPlayer/icon/AudioOnPress.png
  37. 二进制
      public/static/dahua/static/WSPlayer/icon/CloseDisable.png
  38. 二进制
      public/static/dahua/static/WSPlayer/icon/CloseHover.png
  39. 二进制
      public/static/dahua/static/WSPlayer/icon/CloseNormal.png
  40. 二进制
      public/static/dahua/static/WSPlayer/icon/ClosePress.png
  41. 二进制
      public/static/dahua/static/WSPlayer/icon/SnapHover.png
  42. 二进制
      public/static/dahua/static/WSPlayer/icon/SnapNormal.png
  43. 二进制
      public/static/dahua/static/WSPlayer/icon/SnapPress.png
  44. 二进制
      public/static/dahua/static/WSPlayer/icon/black-h.png
  45. 二进制
      public/static/dahua/static/WSPlayer/icon/black-n.png
  46. 二进制
      public/static/dahua/static/WSPlayer/icon/black-p.png
  47. 二进制
      public/static/dahua/static/WSPlayer/icon/default.png
  48. 二进制
      public/static/dahua/static/WSPlayer/icon/pause-d.png
  49. 二进制
      public/static/dahua/static/WSPlayer/icon/pause-h.png
  50. 二进制
      public/static/dahua/static/WSPlayer/icon/pause-n.png
  51. 二进制
      public/static/dahua/static/WSPlayer/icon/pause-p.png
  52. 二进制
      public/static/dahua/static/WSPlayer/icon/play-h.png
  53. 二进制
      public/static/dahua/static/WSPlayer/icon/play-n.png
  54. 二进制
      public/static/dahua/static/WSPlayer/icon/play-p.png
  55. 二进制
      public/static/dahua/static/WSPlayer/icon/start-d.png
  56. 二进制
      public/static/dahua/static/WSPlayer/icon/start-h.png
  57. 二进制
      public/static/dahua/static/WSPlayer/icon/start-n.png
  58. 二进制
      public/static/dahua/static/WSPlayer/icon/start-p.png
  59. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_1-d.png
  60. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_1-h.png
  61. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_1-n.png
  62. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_1-p.png
  63. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_4-d.png
  64. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_4-h.png
  65. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_4-n.png
  66. 二进制
      public/static/dahua/static/WSPlayer/icon/tip_4-p.png
  67. 284 0
      public/static/dahua/static/WSPlayer/player.css
  68. 1 0
      public/static/dahua/static/WSPlayer/videoWorker.worker.js
  69. 1 0
      public/static/dahua/static/WSPlayer/videoWorkerTrain.worker.js
  70. 10 0
      public/static/dahua/static/WSPlayer2.0.5/PlayerControl.js
  71. 1 0
      public/static/dahua/static/WSPlayer2.0.5/audioWorker.worker.js
  72. 135 0
      public/static/dahua/static/WSPlayer2.0.5/ffmpegasm.js
  73. 二进制
      public/static/dahua/static/WSPlayer2.0.5/ffmpegasm.js.mem
  74. 1 0
      public/static/dahua/static/WSPlayer2.0.5/videoWorker.worker.js
  75. 192 0
      public/static/dahua/static/datepicker.css
  76. 853 0
      public/static/dahua/static/datepicker.js
  77. 二进制
      public/static/dahua/static/images/custom_b.png
  78. 二进制
      public/static/dahua/static/images/custom_bl.png
  79. 二进制
      public/static/dahua/static/images/custom_br.png
  80. 二进制
      public/static/dahua/static/images/custom_l.png
  81. 二进制
      public/static/dahua/static/images/custom_r.png
  82. 二进制
      public/static/dahua/static/images/custom_t.png
  83. 二进制
      public/static/dahua/static/images/custom_tl.png
  84. 二进制
      public/static/dahua/static/images/custom_tr.png
  85. 二进制
      public/static/dahua/static/images/datepicker_b.png
  86. 二进制
      public/static/dahua/static/images/datepicker_bl.png
  87. 二进制
      public/static/dahua/static/images/datepicker_br.png
  88. 二进制
      public/static/dahua/static/images/datepicker_l.png
  89. 二进制
      public/static/dahua/static/images/datepicker_r.png
  90. 二进制
      public/static/dahua/static/images/datepicker_t.png
  91. 二进制
      public/static/dahua/static/images/datepicker_tl.png
  92. 二进制
      public/static/dahua/static/images/datepicker_tr.png
  93. 二进制
      public/static/dahua/static/images/field.png
  94. 2 0
      public/static/dahua/static/jquery-3.6.0.min.js
  95. 3843 0
      public/static/dahua/static/jquery.ztree.all.js
  96. 3 0
      public/static/dahua/static/jquery.ztree.all.min.js
  97. 二进制
      public/static/dahua/static/zTreeStyle/img/diy/1_close.png
  98. 二进制
      public/static/dahua/static/zTreeStyle/img/diy/1_open.png
  99. 二进制
      public/static/dahua/static/zTreeStyle/img/diy/2.png
  100. 0 0
      public/static/dahua/static/zTreeStyle/img/diy/3.png

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 24 - 0
README.md

@@ -0,0 +1,24 @@
+# hyyf_client
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 14 - 0
babel.config.js

@@ -0,0 +1,14 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ],
+  plugins: [
+    [
+
+      "import",
+
+      { libraryName: "ant-design-vue", libraryDirectory: "es", "style":'css' }
+
+    ]
+  ]
+}

文件差异内容过多而无法显示
+ 28988 - 0
package-lock.json


+ 60 - 0
package.json

@@ -0,0 +1,60 @@
+{
+  "name": "hyyf_client",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "ant-design-vue": "^1.7.8",
+    "axios": "^0.21.4",
+    "babel-plugin-import": "^1.13.3",
+    "core-js": "^3.6.5",
+    "echarts": "^4.9.0",
+    "element-china-area-data": "^5.0.2",
+    "element-ui": "^2.15.6",
+    "jquery": "^3.6.0",
+    "swiper": "^5.4.5",
+    "vue": "^2.6.11",
+    "vue-awesome-swiper": "^3.1.3",
+    "vue-color": "^2.8.1",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "babel-eslint": "^10.1.0",
+    "compression-webpack-plugin": "^5.0.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-vue": "^6.2.2",
+    "less": "^4.1.1",
+    "less-loader": "^10.0.1",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true,
+      "jquery": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

二进制
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body style="margin: 0; padding: 0;">
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 42 - 0
public/static/dahua/index.html

@@ -0,0 +1,42 @@
+<!--
+ * @Author: your name
+ * @Date: 2021-12-07 09:45:10
+ * @LastEditTime: 2021-12-23 08:51:21
+ * @LastEditors: your name
+ * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ * @FilePath: \hyyfClient\public\static\dahua\index.html
+-->
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+    <link rel="stylesheet" href="./static/WSPlayer/player.css" />
+    <script src="./static/jquery-3.6.0.min.js"></script>
+    <script src="./static/WSPlayer/PlayerControl.js"></script>
+  </head>
+  <body style="margin: 0; width: 100%; height: 100%">
+    <div id="ws-record-player" style="width: 100%; height: 600px;"></div>
+  </body>
+  <script type="module">
+    import WSPlayer from "./src/WSPlayer/WSPlayer.js";
+    let recordPlayer = new WSPlayer({
+      el: "ws-record-player", // 必传
+      type: "record", // real | record
+      serverIp: "36.26.62.70",
+      num: 1,
+      showControl: true,
+    });
+    let url = location.href;
+    let rtsps = url.split("?")[1];
+    let token = url.split("?")[2];
+    let rtsp = rtsps + "?" + token;
+    recordPlayer.playRecord({
+      rtspURL: rtsp, // String | Array[String] 可以传入多个rtsp地址,将会按照顺序在播放器中播放,最多播放四个
+      decodeMode: "canvas", // 解码方式, video|canvas 可以不传,自动识别,h264使用video播放,265使用canvas播放
+      // wsURL: '' // 选择传递,默认播放器是连接服务器对应的websocket,但是也可以指定连接websocket地址
+    });
+  </script>
+</html>

+ 26 - 0
public/static/dahua/src/WSPlayer/CONSTANT.js

@@ -0,0 +1,26 @@
+
+const CONSTANT = {
+    websocketPorts: {
+        realmonitor: {
+            ws: 9100,
+            wss: 9102
+        },
+        playback: {
+            ws: 9320,
+            wss: 9322
+        }
+    },
+    errorInfo: {
+        101: "播放延时大于8s",
+        201: "当前音频无法播放",
+        202: "websocket连接错误",
+        203: "文件播放完成",
+        404: "RTSP文件未找到",
+        457: "时间设置错误",
+        503: "SETUP服务不可用",
+        504: "对讲服务不可用",
+        defaultErrorMsg: "播放失败,请检查配置"
+    }
+}
+
+export default CONSTANT

+ 112 - 0
public/static/dahua/src/WSPlayer/PlayerItem.js

@@ -0,0 +1,112 @@
+/**
+ * PlayerItem
+ */
+class PlayerItem {
+    /**
+     * @param {*} opt.wrapperDomId 父级id
+     * @param {*} opt.index 索引
+     */
+    constructor(opt) {
+        // dom
+        this.$el = null
+        // 播放用元素
+        this.canvasElem = null
+        this.videoElem = null
+        // 每个组件的dom唯一 id
+        this.domId = opt.wrapperDomId + '-' + opt.index
+        // 所属的wsplayer
+        this.wsPlayer = opt.wsPlayer
+        // index序号
+        this.index = opt.index
+        // 第一帧事件
+        this.firstTime = 0
+        // audioOn
+        this.isAudioPlay = false
+    }
+
+    /**
+     * @param {*} domId 此播放器的id
+     */
+    initDom() {
+        let template = this.getTemplate()
+        let player = $(template)
+        this.wsPlayer.$wrapper.append(player[0])
+        this.$el = $('#' + this.domId)
+        this.canvasElem = document.getElementById(this.canvasId)
+        this.videoElem = document.getElementById(this.videoId)
+    }
+    // 添加监听
+    initMouseEvent() {
+        this.$el.click((evt) => {
+            this.wsPlayer.setSelectIndex(this.index)
+            this.$el.siblings().removeClass('selected').addClass('unselected')
+            this.$el.removeClass('unselected').addClass('selected')
+        })
+        this.$el.dblclick((evt) => {
+            if (this.wsPlayer.$wrapper.hasClass('fullplayer')) {
+                this.wsPlayer.setPlayerNum(4)
+            } else {
+                this.wsPlayer.setPlayerNum(1)
+            }
+            // this.wsPlayer.$wrapper.toggleClass('fullplayer')
+            this.wsPlayer.setSelectIndex(this.index)
+            this.$el.siblings().removeClass('selected').addClass('unselected')
+            this.$el.removeClass('unselected').addClass('selected')
+        })
+        $('.end-icon', this.$el).on('click', () => {
+            this.close()
+        })
+        $('.audio-icon', this.$el).click((evt) => {
+            if (this.isAudioPlay) {
+                // 正在播放,关闭声音
+                this.player.setAudioVolume(0);
+                $(evt.target).removeClass('on').addClass('off')
+            } else {
+                // 未播放,打开声音
+                this.player.setAudioVolume(1);
+                $(evt.target).removeClass('off').addClass('on')
+            }
+            this.isAudioPlay = !this.isAudioPlay
+        })
+        $('.capture-icon', this.$el).click((evt) => {
+            this.player.capture(`抓图-${Date.now()}`);
+        })
+        $('.close-icon', this.$el).click((evt) => {
+            this.player.stop()
+            this.player.close()
+            this.setStatus('closed')
+        })
+    }
+    // 设置状态
+    setStatus() {}
+    /**
+     * 关闭视频
+     */
+    play() {
+        this.player.play()
+        this.setStatus('playing')
+    }
+    /**
+     * 关闭视频
+     */
+    pause() {
+        this.player.pause()
+        this.setStatus('pause')
+    }
+    /**
+     * 关闭视频
+     */
+    close() {
+        this.player.stop()
+        this.player.close()
+        this.setStatus('closed')
+    }
+
+    // 设置元素是否可见
+    setDomVisible(dom, visible) {
+        dom.css({
+            visibility: visible ? 'visible' : 'hidden'
+        })
+    }
+}
+export default PlayerItem

+ 186 - 0
public/static/dahua/src/WSPlayer/RealPlayer.js

@@ -0,0 +1,186 @@
+import PlayerItem from './PlayerItem.js'
+import {Spinner} from './spin.js'
+import CONSTANT from './CONSTANT.js'
+const PlayerControl = window.PlayerControl;
+/* ---------------- PlayerItem ---------------- */
+class RealPlayerItem extends PlayerItem {
+    /**
+     * @param {*} opt.wrapperDomId 父级id
+     * @param {*} opt.index 索引
+     */
+    constructor(opt) {
+        super(opt)
+        this.canvasId = `${this.domId}-livecanvas`
+        this.videoId = `${this.domId}-liveVideo`
+        this.initDom()
+        this.defaultStatus = $('.default-status', this.$el)
+        this.error = $('.error', this.$el)
+        this.controller = $('.player-control', this.$el)
+        this.initMouseEvent()
+        /**
+         * this.state 当前Player状态
+         * created, ready, playing, pause, stop, closed, error
+         */
+        this.setStatus('created')
+    }
+    /**
+     * 播放器模板
+     */
+    getTemplate() {
+        let template = `
+        <div id="${this.domId}" class="wsplayer-item wsplayer-item-${this.index} ${this.index === 0 ? 'selected' : 'unselected'}">
+            <div class="full-content flex">
+                <canvas id="${this.canvasId}" class="kind-stream-canvas" kind-channel-id="0" width="800" height="600"></canvas>
+                <video id="${this.videoId}" class="kind-stream-canvas" kind-channel-id="0" muted style="display:none" width="800" height="600"></video>
+            </div>
+            <div class="default-status">
+                <img src="./static/WSPlayer/icon/default.png" alt="">
+            </div>
+            <div class="player-control top-control-bar">
+                <span class="stream-info"></span>
+                <div class="opt-icons">
+                    <div class="opt-icon audio-icon off"></div>
+                    <div class="opt-icon capture-icon"></div>
+                    <div class="opt-icon close-icon"></div>
+                </div>
+            </div>
+            <div class="error">
+                <div class="error-message"></div>
+            </div>
+        </div>
+        `
+        return template
+    }
+    /**
+     * 事件监听
+     */
+    initMouseEvent() {
+        super.initMouseEvent()
+        this.hideTimer = null
+        this.$el.on('mouseenter mousemove', (evt) => {
+            if (this.status === 'playing') {
+                this.hideTimer && clearTimeout(this.hideTimer)
+                this.setDomVisible($('.player-control', $(`#${this.domId}`)), true)
+            }
+        })
+        this.$el.on('mouseleave', (evt) => {
+            this.hideTimer = setTimeout(() => {
+                this.setDomVisible($('.player-control', $(`#${this.domId}`)), false)
+            }, 300)
+        })
+    }
+    /**
+     * 设置状态,同时控制组件显示
+     * created, playing, pause, stop, closed, error
+     */
+    setStatus(status, msg) {
+        this.status = status
+        switch (this.status) {
+            case 'created':
+            case 'closed':
+                this.setDomVisible(this.defaultStatus, true)
+                this.setDomVisible(this.error, false)
+                this.setDomVisible(this.controller, false)
+                $('.audio-icon', this.$el).removeClass('on').addClass('off')
+                break;
+            case 'ready':
+            case 'playing':
+            case 'pause':
+                this.setDomVisible(this.defaultStatus, false)
+                this.setDomVisible(this.error, false)
+                break;
+            case 'error':
+                this.setDomVisible(this.defaultStatus, false)
+                $('.error-message', this.$el).text(CONSTANT.errorInfo[msg.errorCode] ? CONSTANT.errorInfo[msg.errorCode] : CONSTANT.errorInfo['defaultErrorMsg'])
+                this.setDomVisible(this.error, true)
+                break;
+            default:
+                break;
+        }
+    }
+    /**
+     * 初始化播放器
+     * @param {*} options.rtspURL
+     * @param {*} options.decodeMode 可选参数
+     * @param {*} options.wsURL 可选参数
+     */
+    init(options) {
+        // testCode
+        // options.rtspURL = "rtsp://sdfasdf/"
+
+        if (this.player) {
+            this.player.close()
+        }
+        if (this.spinner) {
+            this.spinner.stop()
+        }
+        this.spinner = new Spinner({
+            color: '#ffffff'
+        }).spin(this.$el[0])
+        let self = this
+        this.player = new PlayerControl(Object.assign({
+            wsURL: this.wsPlayer.wsURL
+        }, options))
+        this.setStatus('ready')
+        this.player.on('ResolutionChanged', function (e) {
+            console.log(e)
+        });
+        this.player.on('PlayStart', function (e) {
+            console.log(e)
+            self.setStatus('playing')
+        });
+        this.player.on('DecodeStart', function (e) {
+            console.log('DecodeStart', e)
+            self.spinner.stop()
+            if (e.decodeMode === 'video') {
+                self.videoElem.style.display = '';
+                self.canvasElem.style.display = 'none';
+            } else {
+                self.videoElem.style.display = 'none';
+                self.canvasElem.style.display = '';
+            }
+            $('.stream-info', $(`#${self.domId}`)).text(`${e.encodeMode}, ${e.width}*${e.height}`)
+        });
+        this.player.on('UpdateCanvas', function (e) {
+            if (self.firstTime === 0) {
+                self.firstTime = e.timestamp;//获取录像文件的第一帧的时间戳
+            }
+        });
+        this.player.on('GetFrameRate', function (e) {
+            console.log('GetFrameRate: ', e)
+        });
+        this.player.on('FrameTypeChange', function (e) {
+            console.log('FrameTypeChange: ', e)
+        });
+        this.player.on('Error', function (e) {
+            self.spinner.stop()
+            console.log('Error: ' + JSON.stringify(e))
+            self.setStatus('error', e)
+        });
+        this.player.on('MSEResolutionChanged', function (e) {
+            console.log('MSEResolutionChanged: ', e)
+        });
+        this.player.on('audioChange', function (e) {
+            console.log('audioChange: ', e)
+        });
+        this.player.on('IvsDraw', function (e) {
+            console.log('IvsDraw: ', e)
+        });
+        this.player.on('WorkerReady', function () {
+            console.log('WorkerReady')
+            self.player.connect();
+        })
+        // this.player.on('FileOver', function (e) {
+        //     console.log('FileOver: ', e)
+        // });
+        this.player.on('Waiting', function (e) {
+            console.log('Waiting: ', e)
+        });
+        this.player.on('UpdateTime', function (e) {
+            console.log('UpdateTime: ', e)
+        });
+
+        this.player.init(this.canvasElem, this.videoElem);
+    }
+}
+export default RealPlayerItem

+ 294 - 0
public/static/dahua/src/WSPlayer/RecordPlayer.js

@@ -0,0 +1,294 @@
+import PlayerItem from './PlayerItem.js'
+import {Spinner} from './spin.js'
+import CONSTANT from './CONSTANT.js'
+
+const PlayerControl = window.PlayerControl;
+console.log(PlayerControl)
+
+
+/* ---------------- RecordPlayerItem ---------------- */
+class RecordPlayerItem extends PlayerItem {
+    /**
+     * @param {*} opt.wrapperDomId 父级id
+     * @param {*} opt.index 索引
+     */
+    constructor(opt) {
+        super(opt)
+        this.canvasId = `${this.domId}-recordcanvas`
+        this.videoId = `${this.domId}-recordVideo`
+        this.curTimestamp = 0
+        this.initDom()
+        this.defaultStatus = $('.default-status', this.$el)
+        this.error = $('.error', this.$el)
+        this.controller = $('.player-control', this.$el)
+        this.progressBar = $('.record-control-bar', this.$el)
+        this.timeInfo = $('.time-info', this.$el)
+        this.initMouseEvent()
+        /**
+         * this.state 当前Player状态
+         * created, ready, playing, pause, stop, closed, error
+         */
+        this.setStatus('created')
+    }
+    /**
+     * 播放器模板
+     */
+    getTemplate() {
+        let template = `
+        <div id="${this.domId}" class="wsplayer-item wsplayer-item-${this.index} ${this.index === 0 ? 'selected' : 'unselected'}">
+            <canvas id="${this.canvasId}" class="kind-stream-canvas" kind-channel-id="0" width="800" height="600"></canvas>
+            <video id="${this.videoId}" class="kind-stream-canvas" kind-channel-id="0" muted style="display:none" width="800" height="600"></video>
+            <div class="default-status">
+                <img src="./static/WSPlayer/icon/default.png" alt="">
+            </div>
+            <div class="player-control top-control-bar">
+                <span class="stream-info"></span>
+                <div class="opt-icons">
+                    <div class="opt-icon audio-icon off"></div>
+                    <div class="opt-icon capture-icon"></div>
+                    <div class="opt-icon close-icon"></div>
+                </div>
+            </div>
+            <div class="player-control record-control-bar">
+                <div class="wsplayer-progress-bar">
+                    <div class="progress-bar_background"></div>
+                    <div class="progress-bar_hover_light"></div>
+                    <div class="progress-bar_light"></div>
+                </div>
+                <div class="record-control-left">
+                    <div class="opt-icon play-ctrl-btn play-icon play"></div>
+                    <div class="time-info"></div>/<div class="time-long"></div>
+                </div>
+                <div class="record-control-right">
+                    <div class="opt-icon close-icon"></div>
+                </div>
+            </div>
+            <div class="error">
+                <div class="error-message"></div>
+            </div>
+            <div class="play-pause-wrapper">
+                <div class="play-ctrl-btn center-play-icon"></div>
+            </div>
+        </div>
+        `
+        return template
+    }
+    /**
+     * 事件监听
+     */
+    initMouseEvent() {
+        super.initMouseEvent()
+        this.hideTimer = null
+        this.$el.on('mouseenter mousemove', (evt) => {
+            if (this.status === 'playing') {
+                this.hideTimer && clearTimeout(this.hideTimer)
+                this.setDomVisible($('.player-control', $(`#${this.domId}`)), true)
+            } else if (this.status === 'ready') {
+                this.setDomVisible(this.progressBar, true)
+            }
+        })
+        this.$el.on('mouseleave', (evt) => {
+            if (this.status === 'pause') {
+                return
+            }
+            this.hideTimer = setTimeout(() => {
+                this.setDomVisible($('.player-control', $(`#${this.domId}`)), false)
+            }, 300)
+        })
+        $('.wsplayer-progress-bar', this.$el).on('mousemove', (evt) => {
+            $('.progress-bar_hover_light', this.$el).css({
+                width: evt.offsetX + 'px'
+            })
+        })
+        $('.wsplayer-progress-bar', this.$el).on('mouseleave', (evt) => {
+            $('.progress-bar_hover_light', this.$el).css({
+                width: 0
+            })
+        })
+        $('.play-ctrl-btn', this.$el).click((evt) => {
+            if (this.status === 'playing') {
+                // 正在播放,暂停播放
+                this.pause()
+                $('.play-icon', this.$el).removeClass('play').addClass('pause')
+            } else {
+                // 暂停播放状态,打开
+                this.play()
+                $('.play-icon', this.$el).removeClass('pause').addClass('play')
+            }
+        })
+    }
+    /**
+     * 设置状态,同时控制组件显示
+     * created, ready, playing, pause, stop, closed, error
+     */
+    setStatus(status, msg) {
+        this.status = status
+        switch (this.status) {
+            case 'created':
+            case 'closed':
+                this.setDomVisible(this.defaultStatus, true)
+                this.setDomVisible(this.error, false)
+                this.setDomVisible(this.controller, false)
+                $('.audio-icon', this.$el).removeClass('on').addClass('off')
+                break;
+            case 'ready':
+                this.setDomVisible(this.defaultStatus, false)
+                this.setDomVisible(this.error, false)
+                break;
+            case 'playing':
+                this.setDomVisible(this.defaultStatus, false)
+                this.setDomVisible(this.error, false)
+                this.setDomVisible($('.play-pause-wrapper', this.$el), false)
+                break;
+            case 'pause':
+                this.setDomVisible(this.defaultStatus, false)
+                this.setDomVisible(this.error, false)
+                this.setDomVisible(this.controller, false)
+                this.setDomVisible($('.play-pause-wrapper', this.$el), true)
+                break;
+            case 'error':
+                this.setDomVisible(this.defaultStatus, false)
+                $('.error-message', this.$el).text(CONSTANT.errorInfo[msg.errorCode] ? CONSTANT.errorInfo[msg.errorCode] : CONSTANT.errorInfo['defaultErrorMsg'])
+                this.setDomVisible(this.error, true)
+                break;
+            default:
+                break;
+        }
+    }
+    /**
+     * 播放录像
+     * @param {String} options.decodeMode 可选参数 video | canvas
+     * @param {String} options.wsURL 可选参数
+     * @param {Function} options.recordSource 2=设备,3=中心
+     * recordSource == 2 设备录像,按照时间方式播放
+     * @param {String} options.rtspURL String
+     * @param {Number | String} options.startTime 开始时间 时间戳或者'2021-09-18 15:40:00'格式的时间字符串
+     * @param {Number | String} options.endTime 结束时间 时间戳或者'2021-09-18 15:40:00'格式的时间字符串
+     * @param {Function} options.reload 重新拉流的回调函数,用于时间回放,返回promise
+     * reload(newStarTime, endTime).then(newRtspUrl => { play continue})
+     * recordSource == 3 中心录像,按照文件方式播放
+     * @param {Function} options.RecordFiles 文件列表
+     * @param {Function} options.getRtsp 文件列表
+     * getRtsp(file).then(newRtspUrl => { play continue})
+     */
+    init(options) {
+        if (this.player) {
+            this.player.close()
+        }
+        if (this.spinner) {
+            this.spinner.stop()
+        }
+        this.spinner = new Spinner({
+            color: '#ffffff'
+        }).spin(this.$el[0])
+        let self = this
+        this.player = new PlayerControl(Object.assign({
+            wsURL: this.wsPlayer.wsURL,
+        }, options))
+        this.options = options
+        this.timeLong = options.endTime - options.startTime
+        let seconds = this.timeLong % 60
+        let minutes = (parseInt(this.timeLong / 60)) % 60
+        let hours = (parseInt(this.timeLong / 3600))  % 60
+        this.timeLongStr = `${hours > 0 ? hours + ':' : ''}${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`
+        $('.time-long', this.$el).text(this.timeLongStr)
+        this.setStatus('ready')
+        this.player.on('ResolutionChanged', function (e) {
+            console.log(e)
+        });
+        this.player.on('PlayStart', function (e) {
+            console.log(e)
+            self.setStatus('playing')
+        });
+        this.player.on('DecodeStart', function (e) {
+            console.log('DecodeStart', e)
+            self.spinner.stop()
+            if (e.decodeMode === 'video') {
+                self.videoElem.style.display = '';
+                self.canvasElem.style.display = 'none';
+            } else {
+                self.videoElem.style.display = 'none';
+                self.canvasElem.style.display = '';
+            }
+            $('.stream-info', $(`#${self.domId}`)).text(`${e.encodeMode}, ${e.width}*${e.height}`)
+
+        });
+        this.player.on('UpdateCanvas', function (e) {
+            if (self.firstTime === 0) {
+                // 使用请求时间段的时间作为
+                self.firstTime = self.options.startTime
+                // self.firstTime = e.timestamp;//获取录像文件的第一帧的时间戳
+            }
+            // 一秒数据帧timestamp相同,此判断可以减少计算
+            if (e.timestamp > self.curTimestamp) {
+                self.curTimestamp = e.timestamp
+                let playtime = e.timestamp - self.firstTime
+                playtime = playtime < 0 ? 0 : playtime;
+                let seconds = playtime % 60
+                let minutes = (parseInt(playtime / 60)) % 60
+                let hours = (parseInt(playtime / 3600))  % 60
+                let timeString = `${hours > 0 ? hours + ':' : ''}${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`
+                self.timeInfo.text(timeString)
+                $('.progress-bar_light', self.$el).css({
+                    width: `${playtime * 100 / self.timeLong}%`
+                })
+                // ctrlBar.updateCanvas(e)
+                // console.log('UpdateCanvas: ' + JSON.stringify(e))
+            }
+        });
+        this.player.on('GetFrameRate', function (e) {
+            console.log('GetFrameRate: ', e)
+        });
+        this.player.on('FrameTypeChange', function (e) {
+            console.log('编码模式改变 FrameTypeChange: ', e)
+        });
+        this.player.on('Error', function (e) {
+            self.spinner.stop()
+            console.log('Error: ' + JSON.stringify(e))
+            self.setStatus('error', e)
+        });
+        this.player.on('MSEResolutionChanged', function (e) {
+            console.log('分辨率改变 MSEResolutionChanged: ', e)
+        });
+        this.player.on('audioChange', function (e) {
+            console.log('音频编码改变 audioChange: ', e)
+        });
+        this.player.on('IvsDraw', function (e) {
+            console.log('IvsDraw: ', e)
+        });
+        this.player.on('WorkerReady', function () {
+            console.log('WorkerReady')
+            self.player.connect();
+        })
+        this.player.on('FileOver', function (e) {
+            console.log('回放播放完成 FileOver: ', e)
+        });
+        this.player.on('Waiting', function (e) {
+            console.log('Waiting: ', e)
+        });
+        this.player.on('UpdateTime', function (e) {
+            console.log('UpdateTime: ', e)
+        });
+        this.player.on('GetFirstFrame', function(){
+            console.log('收到第一帧');
+        });
+
+        this.player.init(this.canvasElem, this.videoElem);
+    }
+    /**
+     * 倍速播放
+     * @param {Number} speed 倍速
+     */
+    playSpeed(speed) {
+        this.player.playSpeed(speed)
+    }
+    /**
+     * 时间跳转
+     * @param {*} time
+     */
+    playByTime(time) {
+        this.player.playByTime(time)
+    }
+}
+
+export default RecordPlayerItem

+ 253 - 0
public/static/dahua/src/WSPlayer/WSPlayer.js

@@ -0,0 +1,253 @@
+import CONSTANT from './CONSTANT.js'
+import RealPlayerItem from './RealPlayer.js'
+import RecordPlayerItem from './RecordPlayer.js'
+
+/* ---------------- WSPlayer ---------------- */
+const defaultConfig = {
+    num: 4,
+    showControl: true,
+    type: 'real'
+}
+class WSPlayer {
+    /**
+     * 构造函数
+     * @param {String} options.el 必传 播放器domId
+     * @param {String} options.type  必传 类型 real | record
+     * @param {Sting} options.serverIp 必传 服务器IP
+     * @param {number} options.num 播放器数量 default 1
+     * @param {boolean} options.showControl 显示播放器控制栏 default false
+     */
+    constructor(options) {
+        if (!options.el || !options.type || !options.serverIp) {
+            console.error(`el, type, serverIp 为必传参数,请校验入参`)
+            return false
+        }
+        let isHttps = location.protocol == 'https:'
+        // 协议判断
+        this.protocol = isHttps ? 'wss' : 'ws'
+        // 服务器IP
+        this.serverIp = options.serverIp ? options.serverIp : location.hostname
+        this.options = Object.assign({}, defaultConfig, options)
+        this.$el = $('#' + options.el)
+        this.width = this.$el.attr('width')
+        this.height = this.$el.attr('height')
+        this.$el.height(`${this.height}px`)
+        this.$el.width(`${this.width}px`)
+        console.log(this.$el.width())
+        this.$el.addClass(`ws-player`)
+        // 添加player-wrapper
+        this.$el.append(`<div class="player-wrapper"></div>`)
+        this.$wrapper = $('.player-wrapper', this.$el)
+        this.playerList = []
+        $(this.$el).attr('inited', true)
+        switch (options.type) {
+            case 'real':
+                this.wsPort = CONSTANT.websocketPorts.realmonitor[this.protocol]
+                if (this.options.showControl) {
+                    this.__addRealControl()
+                } else {
+                    this.$wrapper.addClass('nocontrol')
+                }
+                for (let i of [0, 1, 2, 3]) {
+                    this.playerList.push(new RealPlayerItem({
+                        wrapperDomId: options.el,
+                        index: i,
+                        wsPlayer: this
+                    }))
+                }
+                break;
+            case 'record':
+                for (let i of [0, 1, 2, 3]) {
+                    this.playerList.push(new RecordPlayerItem({
+                        wrapperDomId: options.el,
+                        index: i,
+                        wsPlayer: this
+                    }))
+                }
+                this.wsPort = CONSTANT.websocketPorts.playback[this.protocol]
+                if (this.options.showControl) {
+                    this.__addRecordControl()
+                } else {
+                    this.$wrapper.addClass('nocontrol')
+                }
+                break;
+            default:
+                break;
+        }
+        // 设置服务器websocektUrl地址
+        this.wsURL = `${this.protocol}://${this.serverIp}:${this.wsPort}`
+        this.setSelectIndex(0)
+        this.setPlayerNum(options.num)
+    }
+    /**
+     * 播放实时视频
+     * @param {*} options.rtspURL String | array
+     * @param {*} options.decodeMode 可选参数 video | canvas
+     * @param {*} options.wsURL 可选参数
+     */
+    playReal(opt) {
+        if (!opt.rtspURL) {
+            console.error("播放实时视频需要传入rtspURL")
+            return
+        }
+        // 多个rtsp地址时,按照播放器顺序,选择播放器进行播放
+        if (opt.rtspURL instanceof Array) {
+            opt.rtspURL.forEach((item, i) => {
+                if (i < 4) {
+                    this.playReal(Object.assign({}, opt, {
+                        rtspURL: item
+                    }))
+                }
+            })
+        } else {
+            let player = this.playerList[this.selectIndex]
+            if (this.showNum > 1) {
+                this.setSelectIndex((this.selectIndex + 1) % this.showNum)
+            }
+            player.init(opt)
+        }
+    }
+    /**
+     * 播放录像
+     * @param {String} options.decodeMode 可选参数 video | canvas
+     * @param {String} options.wsURL 可选参数
+     * @param {Function} options.recordSource 2=设备,3=中心
+     * recordSource == 2 设备录像,按照时间方式播放
+     * @param {String} options.rtspURL String
+     * @param {Number | String} options.startTime 开始时间 时间戳或者'2021-09-18 15:40:00'格式的时间字符串
+     * @param {Number | String} options.endTime 结束时间 时间戳或者'2021-09-18 15:40:00'格式的时间字符串
+     * @param {Function} options.reload 重新拉流的回调函数,用于时间回放,返回promise
+     * reload(newStarTime, endTime).then(newRtspUrl => { play continue})
+     * recordSource == 3 中心录像,按照文件方式播放
+     * @param {Function} options.RecordFiles 文件列表
+     * @param {Function} options.getRtsp 文件列表
+     * getRtsp(file).then(newRtspUrl => { play continue})
+     */
+    playRecord(opt) {
+        let player = this.playerList[this.selectIndex]
+        if (this.showNum > 1) {
+            this.setSelectIndex((this.selectIndex + 1) % this.showNum)
+        }
+        player.init(opt)
+    }
+
+    /**
+     * 播放
+     */
+    play() {
+        let player = this.playerList[this.selectIndex]
+        player.play()
+    }
+    /**
+     * 暂停播放
+     */
+    pause() {
+        let player = this.playerList[this.selectIndex]
+        player.pause()
+    }
+    /**
+     * 倍速播放
+     * @param {Number} speed 倍速
+     */
+    playSpeed(speed) {
+        if (this.options.type === 'real') {
+            console.warn('实时预览不支持倍速播放')
+            return
+        }
+        let player = this.playerList[this.selectIndex]
+        player.playSpeed(speed)
+    }
+    /**
+     * 时间跳转
+     * @param {*} time
+     */
+    playByTime(time) {
+        if (this.options.type === 'real') {
+            console.warn('实时预览不支持时间设置')
+            return
+        }
+        let player = this.playerList[this.selectIndex]
+        player.playByTime(time)
+    }
+    /**
+     * 设置选中的播放器
+     * @param {*} index
+     */
+    setSelectIndex(index) {
+        this.selectIndex = index
+        this.playerList.forEach((item, i) => {
+            if (i === index) {
+                item.$el.removeClass('unselected').addClass('selected')
+            } else {
+                item.$el.removeClass('selected').addClass('unselected')
+            }
+        })
+    }
+    /**
+     * 控制视频播放器数量
+     * @param {*} number
+     * @param {*} index
+     */
+    setPlayerNum(number, index) {
+        console.log("WSPlayer setPlayerNum", number, index)
+        this.showNum = number
+        switch (number) {
+            case 1:
+                this.$wrapper.addClass('fullplayer')
+                break;
+            default:
+                this.$wrapper.removeClass('fullplayer')
+                break;
+        }
+    }
+    // 关闭所有播放器
+    close() {
+        this.playerList.forEach(item => {
+            item.close()
+        })
+    }
+
+    /* ----------------------- 内部方法 -----------------------*/
+    /**
+     * 添加实时播放控制栏
+     */
+    __addRealControl() {
+        this.$el.append(`
+            <div class="ws-control">
+                <div class="flex">
+                    <div class="ws-ctrl-icon one-screen-icon"></div>
+                    <div class="ws-ctrl-icon four-screen-icon"></div>
+                </div>
+            </div>
+        `)
+
+        $('.one-screen-icon', this.$el).click(() => {
+            this.setPlayerNum(1, this.selectIndex)
+        })
+        $('.four-screen-icon', this.$el).click(() => {
+            this.setPlayerNum(4)
+        })
+    }
+    /**
+     * 添加录像回放控制栏
+     */
+    __addRecordControl() {
+        this.$el.append(`
+            <div class="ws-control">
+                <div class="flex">
+                    <div class="ws-ctrl-icon one-screen-icon"></div>
+                    <div class="ws-ctrl-icon four-screen-icon"></div>
+                </div>
+            </div>
+        `)
+
+        $('.one-screen-icon', this.$el).click(() => {
+            this.setPlayerNum(1, this.selectIndex)
+        })
+        $('.four-screen-icon', this.$el).click(() => {
+            this.setPlayerNum(4)
+        })
+    }
+}
+
+export default WSPlayer

+ 190 - 0
public/static/dahua/src/WSPlayer/spin.js

@@ -0,0 +1,190 @@
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var defaults = {
+    lines: 12,
+    length: 7,
+    width: 5,
+    radius: 10,
+    scale: 1.0,
+    corners: 1,
+    color: '#000',
+    fadeColor: 'transparent',
+    animation: 'spinner-line-fade-default',
+    rotate: 0,
+    direction: 1,
+    speed: 1,
+    zIndex: 2e9,
+    className: 'spinner',
+    top: '50%',
+    left: '50%',
+    shadow: '0 0 1px transparent',
+    position: 'absolute',
+};
+var Spinner = /** @class */ (function () {
+    function Spinner(opts) {
+        if (opts === void 0) { opts = {}; }
+        this.opts = __assign(__assign({}, defaults), opts);
+    }
+    /**
+     * Adds the spinner to the given target element. If this instance is already
+     * spinning, it is automatically removed from its previous target by calling
+     * stop() internally.
+     */
+    Spinner.prototype.spin = function (target) {
+        this.stop();
+        this.el = document.createElement('div');
+        this.el.className = this.opts.className;
+        this.el.setAttribute('role', 'progressbar');
+        css(this.el, {
+            position: this.opts.position,
+            width: 0,
+            zIndex: this.opts.zIndex,
+            left: this.opts.left,
+            top: this.opts.top,
+            transform: "scale(" + this.opts.scale + ")",
+        });
+        if (target) {
+            target.insertBefore(this.el, target.firstChild || null);
+        }
+        drawLines(this.el, this.opts);
+        return this;
+    };
+    /**
+     * Stops and removes the Spinner.
+     * Stopped spinners may be reused by calling spin() again.
+     */
+    Spinner.prototype.stop = function () {
+        if (this.el) {
+            if (typeof requestAnimationFrame !== 'undefined') {
+                cancelAnimationFrame(this.animateId);
+            }
+            else {
+                clearTimeout(this.animateId);
+            }
+            if (this.el.parentNode) {
+                this.el.parentNode.removeChild(this.el);
+            }
+            this.el = undefined;
+        }
+        return this;
+    };
+    return Spinner;
+}());
+export { Spinner };
+/**
+ * Sets multiple style properties at once.
+ */
+function css(el, props) {
+    for (var prop in props) {
+        el.style[prop] = props[prop];
+    }
+    return el;
+}
+/**
+ * Returns the line color from the given string or array.
+ */
+function getColor(color, idx) {
+    return typeof color == 'string' ? color : color[idx % color.length];
+}
+/**
+ * Internal method that draws the individual lines.
+ */
+function drawLines(el, opts) {
+    var borderRadius = (Math.round(opts.corners * opts.width * 500) / 1000) + 'px';
+    var shadow = 'none';
+    if (opts.shadow === true) {
+        shadow = '0 2px 4px #000'; // default shadow
+    }
+    else if (typeof opts.shadow === 'string') {
+        shadow = opts.shadow;
+    }
+    var shadows = parseBoxShadow(shadow);
+    for (var i = 0; i < opts.lines; i++) {
+        var degrees = ~~(360 / opts.lines * i + opts.rotate);
+        var backgroundLine = css(document.createElement('div'), {
+            position: 'absolute',
+            top: -opts.width / 2 + "px",
+            width: (opts.length + opts.width) + 'px',
+            height: opts.width + 'px',
+            background: getColor(opts.fadeColor, i),
+            borderRadius: borderRadius,
+            transformOrigin: 'left',
+            transform: "rotate(" + degrees + "deg) translateX(" + opts.radius + "px)",
+        });
+        var delay = i * opts.direction / opts.lines / opts.speed;
+        delay -= 1 / opts.speed; // so initial animation state will include trail
+        var line = css(document.createElement('div'), {
+            width: '100%',
+            height: '100%',
+            background: getColor(opts.color, i),
+            borderRadius: borderRadius,
+            boxShadow: normalizeShadow(shadows, degrees),
+            animation: 1 / opts.speed + "s linear " + delay + "s infinite " + opts.animation,
+        });
+        backgroundLine.appendChild(line);
+        el.appendChild(backgroundLine);
+    }
+}
+function parseBoxShadow(boxShadow) {
+    var regex = /^\s*([a-zA-Z]+\s+)?(-?\d+(\.\d+)?)([a-zA-Z]*)\s+(-?\d+(\.\d+)?)([a-zA-Z]*)(.*)$/;
+    var shadows = [];
+    for (var _i = 0, _a = boxShadow.split(','); _i < _a.length; _i++) {
+        var shadow = _a[_i];
+        var matches = shadow.match(regex);
+        if (matches === null) {
+            continue; // invalid syntax
+        }
+        var x = +matches[2];
+        var y = +matches[5];
+        var xUnits = matches[4];
+        var yUnits = matches[7];
+        if (x === 0 && !xUnits) {
+            xUnits = yUnits;
+        }
+        if (y === 0 && !yUnits) {
+            yUnits = xUnits;
+        }
+        if (xUnits !== yUnits) {
+            continue; // units must match to use as coordinates
+        }
+        shadows.push({
+            prefix: matches[1] || '',
+            x: x,
+            y: y,
+            xUnits: xUnits,
+            yUnits: yUnits,
+            end: matches[8],
+        });
+    }
+    return shadows;
+}
+/**
+ * Modify box-shadow x/y offsets to counteract rotation
+ */
+function normalizeShadow(shadows, degrees) {
+    var normalized = [];
+    for (var _i = 0, shadows_1 = shadows; _i < shadows_1.length; _i++) {
+        var shadow = shadows_1[_i];
+        var xy = convertOffset(shadow.x, shadow.y, degrees);
+        normalized.push(shadow.prefix + xy[0] + shadow.xUnits + ' ' + xy[1] + shadow.yUnits + shadow.end);
+    }
+    return normalized.join(', ');
+}
+function convertOffset(x, y, degrees) {
+    var radians = degrees * Math.PI / 180;
+    var sin = Math.sin(radians);
+    var cos = Math.cos(radians);
+    return [
+        Math.round((x * cos + y * sin) * 1000) / 1000,
+        Math.round((-x * sin + y * cos) * 1000) / 1000,
+    ];
+}

文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer/PlayerControl.js


文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer/WSPlayer.js


文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer/audioTalkWorker.worker.js


文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer/audioWorker.worker.js


文件差异内容过多而无法显示
+ 76 - 0
public/static/dahua/static/WSPlayer/ffmpegasm.js


二进制
public/static/dahua/static/WSPlayer/ffmpegasm.js.mem


二进制
public/static/dahua/static/WSPlayer/icon/4screen-d.png


+ 19 - 0
public/static/dahua/static/WSPlayer/icon/4screen-d.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
+	<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+	<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+	<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+]>
+<svg version="1.1" id="图层_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+	 x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
+<g>
+	<path fill="#4A515A" d="M8,2H2C1.448,2,1,2.448,1,3v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C9,2.448,8.552,2,8,2z"/>
+	<path fill="#4A515A" d="M18,2h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C19,2.448,18.552,2,18,2z"/>
+	<path fill="#4A515A" d="M8,11H2c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C9,11.448,8.552,11,8,11z"/>
+	<path fill="#4A515A" d="M18,11h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C19,11.448,18.552,11,18,11
+		z"/>
+</g>
+</svg>

二进制
public/static/dahua/static/WSPlayer/icon/4screen-h.png


+ 19 - 0
public/static/dahua/static/WSPlayer/icon/4screen-h.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
+	<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+	<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+	<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+]>
+<svg version="1.1" id="图层_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+	 x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
+<g>
+	<path fill="#FFFFFF" d="M8,2H2C1.448,2,1,2.448,1,3v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C9,2.448,8.552,2,8,2z"/>
+	<path fill="#FFFFFF" d="M18,2h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C19,2.448,18.552,2,18,2z"/>
+	<path fill="#FFFFFF" d="M8,11H2c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C9,11.448,8.552,11,8,11z"/>
+	<path fill="#FFFFFF" d="M18,11h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C19,11.448,18.552,11,18,11
+		z"/>
+</g>
+</svg>

二进制
public/static/dahua/static/WSPlayer/icon/4screen-n.png


+ 19 - 0
public/static/dahua/static/WSPlayer/icon/4screen-n.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
+	<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+	<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+	<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+]>
+<svg version="1.1" id="图层_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+	 x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
+<g>
+	<path fill="#C2CAD7" d="M8,2H2C1.448,2,1,2.448,1,3v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C9,2.448,8.552,2,8,2z"/>
+	<path fill="#C2CAD7" d="M18,2h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C19,2.448,18.552,2,18,2z"/>
+	<path fill="#C2CAD7" d="M8,11H2c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C9,11.448,8.552,11,8,11z"/>
+	<path fill="#C2CAD7" d="M18,11h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C19,11.448,18.552,11,18,11
+		z"/>
+</g>
+</svg>

二进制
public/static/dahua/static/WSPlayer/icon/4screen-p.png


+ 19 - 0
public/static/dahua/static/WSPlayer/icon/4screen-p.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
+	<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+	<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+	<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+]>
+<svg version="1.1" id="图层_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+	 x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
+<g>
+	<path fill="#40C4FF" d="M8,2H2C1.448,2,1,2.448,1,3v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C9,2.448,8.552,2,8,2z"/>
+	<path fill="#40C4FF" d="M18,2h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1V3C19,2.448,18.552,2,18,2z"/>
+	<path fill="#40C4FF" d="M8,11H2c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C9,11.448,8.552,11,8,11z"/>
+	<path fill="#40C4FF" d="M18,11h-6c-0.552,0-1,0.448-1,1v5c0,0.552,0.448,1,1,1h6c0.552,0,1-0.448,1-1v-5C19,11.448,18.552,11,18,11
+		z"/>
+</g>
+</svg>

二进制
public/static/dahua/static/WSPlayer/icon/AudioOffDisable.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOffHover.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOffNormal.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOffPress.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOnDisable.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOnHover.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOnNormal.png


二进制
public/static/dahua/static/WSPlayer/icon/AudioOnPress.png


二进制
public/static/dahua/static/WSPlayer/icon/CloseDisable.png


二进制
public/static/dahua/static/WSPlayer/icon/CloseHover.png


二进制
public/static/dahua/static/WSPlayer/icon/CloseNormal.png


二进制
public/static/dahua/static/WSPlayer/icon/ClosePress.png


二进制
public/static/dahua/static/WSPlayer/icon/SnapHover.png


二进制
public/static/dahua/static/WSPlayer/icon/SnapNormal.png


二进制
public/static/dahua/static/WSPlayer/icon/SnapPress.png


二进制
public/static/dahua/static/WSPlayer/icon/black-h.png


二进制
public/static/dahua/static/WSPlayer/icon/black-n.png


二进制
public/static/dahua/static/WSPlayer/icon/black-p.png


二进制
public/static/dahua/static/WSPlayer/icon/default.png


二进制
public/static/dahua/static/WSPlayer/icon/pause-d.png


二进制
public/static/dahua/static/WSPlayer/icon/pause-h.png


二进制
public/static/dahua/static/WSPlayer/icon/pause-n.png


二进制
public/static/dahua/static/WSPlayer/icon/pause-p.png


二进制
public/static/dahua/static/WSPlayer/icon/play-h.png


二进制
public/static/dahua/static/WSPlayer/icon/play-n.png


二进制
public/static/dahua/static/WSPlayer/icon/play-p.png


二进制
public/static/dahua/static/WSPlayer/icon/start-d.png


二进制
public/static/dahua/static/WSPlayer/icon/start-h.png


二进制
public/static/dahua/static/WSPlayer/icon/start-n.png


二进制
public/static/dahua/static/WSPlayer/icon/start-p.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_1-d.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_1-h.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_1-n.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_1-p.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_4-d.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_4-h.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_4-n.png


二进制
public/static/dahua/static/WSPlayer/icon/tip_4-p.png


+ 284 - 0
public/static/dahua/static/WSPlayer/player.css

@@ -0,0 +1,284 @@
+.ws-player {
+  position: relative;
+  background-color: #000000;
+  display: flex;
+  justify-content: center;
+}
+.ws-player .player-wrapper {
+  position: relative;
+  width: 100%;
+  height: calc(100% - 30px);
+  overflow: hidden;
+}
+.ws-player .player-wrapper.nocontrol {
+  height: 100%;
+}
+.ws-player .wsplayer-item {
+  position: absolute;
+  width: calc(50% - 2px);
+  height: calc(50% - 2px);
+}
+.ws-player .wsplayer-item-0 {
+  top: 0;
+  left: 0;
+}
+.ws-player .wsplayer-item-1 {
+  top: 0;
+  right: 0;
+}
+.ws-player .wsplayer-item-2 {
+  bottom: 0;
+  left: 0;
+}
+.ws-player .wsplayer-item-3 {
+  bottom: 0;
+  right: 0;
+}
+.ws-player .wsplayer-item.selected {
+  border: 1px solid #009cff;
+  transition: all cubic-bezier(0.19, 1, 0.22, 1) .3s;
+}
+.ws-player .wsplayer-item.unselected {
+  border: 1px solid #161A1E;
+}
+.ws-player .player-wrapper.fullplayer .wsplayer-item.selected {
+  width: calc(100% - 2px);
+  height: calc(100% - 2px);
+}
+.ws-player .player-wrapper.fullplayer .wsplayer-item.unselected {
+  display: none;
+}
+.ws-player .kind-stream-canvas {
+  width: 100%;
+  height: 100%;
+}
+.ws-player .full-content {
+  width: 100%;
+  height: 100%;
+}
+.ws-player .default-status {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  z-index: 10;
+  left: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  user-select: none;
+}
+.ws-player .player-control {
+  position: absolute;
+  width: 100%;
+  background: rgba(0,0,0, .8);
+  visibility: hidden;
+  user-select: none;
+}
+.ws-player .top-control-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 20;
+  top: 0;
+  height: 30px;
+  color: #fff;
+}
+.ws-player .record-control-bar {
+  z-index: 20;
+  bottom: 0;
+  height: 39px;
+  visibility: visible;
+  color: #fff;
+}
+.ws-player .record-control-right,
+.ws-player .record-control-left {
+  height: 34px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.ws-player .record-control-left {
+  float: left;
+}
+.ws-player .record-control-right {
+  float: right;
+}
+.ws-player .wsplayer-progress-bar {
+  position: relative;
+  height: 5px;
+  margin: 0 10px;
+  cursor: pointer;
+}
+.ws-player .progress-bar_background {
+  position: absolute;
+  background: rgba(231, 231, 231, 0.3);
+  height: 100%;
+  width: 100%;
+}
+.ws-player .progress-bar_light {
+  position: absolute;
+  height: 100%;
+  background: #459DF5;
+  z-index: 2;
+}
+.ws-player .progress-bar_hover_light {
+  position: absolute;
+  cursor: pointer;
+  background: rgba(231, 231, 231, 0.7);
+  height: 100%;
+}
+.ws-player .error {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 30;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  visibility: hidden;
+  color: rgba(255,255,255, .8);
+  font-size: 30px;
+}
+.ws-player .play-pause-wrapper {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 30;
+  width: 100%;
+  height: 100%;
+  visibility: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: rgba(0, 0, 0, .3);
+}
+.ws-player .center-play-icon {
+  width: 60px;
+  height: 60px;
+  background-image: url(./icon/play-n.png);
+}
+.ws-player .center-play-icon:hover {
+  background-image: url(./icon/play-h.png);
+}
+.ws-player .opt-icons {
+  display: flex;
+}
+.ws-player .opt-icon {
+  width: 20px;
+  height: 20px;
+  margin: 0 10px;
+}
+.ws-player .audio-icon.off {
+  background-image: url(./icon/AudioOffNormal.png);
+}
+.ws-player .audio-icon.off:hover {
+  background-image: url(./icon/AudioOffHover.png);
+}
+.ws-player .audio-icon.on {
+  background-image: url(./icon/AudioOnPress.png);
+}
+.ws-player .capture-icon {
+  background-image: url(./icon/SnapNormal.png);
+}
+.ws-player .capture-icon:hover {
+  background-image: url(./icon/SnapHover.png);
+}
+.ws-player .close-icon {
+  background-image: url(./icon/CloseNormal.png);
+}
+.ws-player .close-icon:hover {
+  background-image: url(./icon/CloseHover.png);
+}
+.ws-player .play-icon.play {
+  background-image: url(./icon/pause-n.png);
+}
+.ws-player .play-icon.play:hover {
+  background-image: url(./icon/pause-h.png);
+}
+.ws-player .play-icon.pause {
+  background-image: url(./icon/start-n.png);
+}
+.ws-player .play-icon.pause:hover {
+  background-image: url(./icon/start-h.png);
+}
+
+.ws-player .ws-control {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 30px;
+  width: 100%;
+  background: #4C5054;
+}
+.ws-player .ws-ctrl-icon {
+  width: 21px;
+  height: 20px;
+  margin: 0 10px;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+.ws-player .one-screen-icon {
+  background-image: url(./icon/tip_1-n.png);
+}
+.ws-player .one-screen-icon:hover {
+  background-image: url(./icon/tip_1-h.png);
+}
+.ws-player .four-screen-icon {
+  background-image: url(./icon/tip_4-n.png);
+}
+.ws-player .four-screen-icon.active {
+  background-image: url(./icon/tip_4-p.png);
+}
+.ws-player .four-screen-icon:hover {
+  background-image: url(./icon/tip_4-h.png);
+}
+
+.ws-player .flex {
+  display: flex;
+}
+
+/* spinner 动画 */
+@keyframes spinner-line-fade-more {
+  0%, 100% {
+    opacity: 0; /* minimum opacity */
+  }
+  1% {
+    opacity: 1;
+  }
+}
+
+@keyframes spinner-line-fade-quick {
+  0%, 39%, 100% {
+    opacity: 0.25; /* minimum opacity */
+  }
+  40% {
+    opacity: 1;
+  }
+}
+
+@keyframes spinner-line-fade-default {
+  0%, 100% {
+    opacity: 0.22; /* minimum opacity */
+  }
+  1% {
+    opacity: 1;
+  }
+}
+
+@keyframes spinner-line-shrink {
+  0%, 25%, 100% {
+    /* minimum scale and opacity */
+    transform: scale(0.5);
+    opacity: 0.25;
+  }
+  26% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}

文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer/videoWorker.worker.js


文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer/videoWorkerTrain.worker.js


文件差异内容过多而无法显示
+ 10 - 0
public/static/dahua/static/WSPlayer2.0.5/PlayerControl.js


文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer2.0.5/audioWorker.worker.js


文件差异内容过多而无法显示
+ 135 - 0
public/static/dahua/static/WSPlayer2.0.5/ffmpegasm.js


二进制
public/static/dahua/static/WSPlayer2.0.5/ffmpegasm.js.mem


文件差异内容过多而无法显示
+ 1 - 0
public/static/dahua/static/WSPlayer2.0.5/videoWorker.worker.js


+ 192 - 0
public/static/dahua/static/datepicker.css

@@ -0,0 +1,192 @@
+div.datepicker {
+	position: relative;
+	font-family: Arial, Helvetica, sans-serif;
+	font-size: 12px;
+	width: 196px;
+	height: 147px;
+	position: absolute;
+	cursor: default;
+	top: 0;
+	left: 0;
+	display: none;
+}
+.datepickerContainer {
+	background: #121212;
+	position: absolute;
+	top: 10px;
+	left: 10px;
+}
+.datepickerBorderT {
+	position: absolute;
+	left: 10px;
+	top: 0;
+	right: 10px;
+	height: 10px;
+	background: url(./images/datepicker_t.png);
+}
+.datepickerBorderB {
+	position: absolute;
+	left: 10px;
+	bottom: 0;
+	right: 10px;
+	height: 10px;
+	background: url(./images/datepicker_b.png);
+}
+.datepickerBorderL {
+	position: absolute;
+	left: 0;
+	bottom: 10px;
+	top: 10px;
+	width: 10px;
+	background: url(./images/datepicker_l.png);
+}
+.datepickerBorderR {
+	position: absolute;
+	right: 0;
+	bottom: 10px;
+	top: 10px;
+	width: 10px;
+	background: url(./images/datepicker_r.png);
+}
+.datepickerBorderTL {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 10px;
+	height: 10px;
+	background: url(./images/datepicker_tl.png);
+}
+.datepickerBorderTR {
+	position: absolute;
+	top: 0;
+	right: 0;
+	width: 10px;
+	height: 10px;
+	background: url(./images/datepicker_tr.png);
+}
+.datepickerBorderBL {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	width: 10px;
+	height: 10px;
+	background: url(./images/datepicker_bl.png);
+}
+.datepickerBorderBR {
+	position: absolute;
+	bottom: 0;
+	right: 0;
+	width: 10px;
+	height: 10px;
+	background: url(./images/datepicker_br.png);
+}
+.datepickerHidden {
+	display: none;
+}
+div.datepicker table {
+	border-collapse:collapse;
+}
+div.datepicker a {
+	color: #eee;
+	text-decoration: none;
+	cursor: default;
+	outline: none;
+}
+div.datepicker table td {
+	text-align: right;
+	padding: 0;
+	margin: 0;
+}
+div.datepicker th {
+	text-align: center;
+	color: #999;
+	font-weight: normal;
+}
+div.datepicker tbody th {
+	text-align: left;
+}
+div.datepicker tbody a {
+	display: block;
+}
+.datepickerDays a {
+	width: 20px;
+	line-height: 16px;
+	height: 16px;
+	padding-right: 2px;
+}
+.datepickerYears a,
+.datepickerMonths a{
+	width: 44px;
+	line-height: 36px;
+	height: 36px;
+	text-align: center;
+}
+td.datepickerNotInMonth a {
+	color: #666;
+}
+tbody.datepickerDays td.datepickerSelected{
+	background: #136A9F;
+}
+tbody.datepickerDays td.datepickerNotInMonth.datepickerSelected {
+	background: #17384d;
+}
+tbody.datepickerYears td.datepickerSelected,
+tbody.datepickerMonths td.datepickerSelected{
+	background: #17384d;
+}
+div.datepicker a:hover,
+div.datepicker a:hover {
+	color: #88c5eb;
+}
+div.datepicker td.datepickerNotInMonth a:hover {
+	color: #999;
+}
+div.datepicker tbody th {
+	text-align: left;
+}
+.datepickerSpace div {
+	width: 20px;
+}
+.datepickerGoNext a,
+.datepickerGoPrev a,
+.datepickerMonth a {
+	text-align: center;
+	height: 20px;
+	line-height: 20px;
+}
+.datepickerGoNext a {
+	float: right;
+	width: 20px;
+}
+.datepickerGoPrev a {
+	float: left;
+	width: 20px;
+}
+table.datepickerViewDays tbody.datepickerMonths,
+table.datepickerViewDays tbody.datepickerYears {
+	display: none;
+}
+table.datepickerViewMonths tbody.datepickerDays,
+table.datepickerViewMonths tbody.datepickerYears,
+table.datepickerViewMonths tr.datepickerDoW {
+	display: none;
+}
+table.datepickerViewYears tbody.datepickerDays,
+table.datepickerViewYears tbody.datepickerMonths,
+table.datepickerViewYears tr.datepickerDoW {
+	display: none;
+}
+td.datepickerDisabled a,
+td.datepickerDisabled.datepickerNotInMonth a{
+	color: #333;
+}
+td.datepickerDisabled a:hover {
+	color: #333;
+}
+td.datepickerSpecial a {
+	background: #700;
+}
+/* www.codefans.net */
+td.datepickerSpecial.datepickerSelected a {
+	background: #a00;
+}

+ 853 - 0
public/static/dahua/static/datepicker.js

@@ -0,0 +1,853 @@
+/**
+ *
+ * Date picker
+ * Author: Stefan Petre www.eyecon.ro
+ * Download: http://www.jb51.net
+ */
+(function ($) {
+	var DatePicker = function () {
+		var	ids = {},
+			tpl = {
+				wrapper: '<div class="datepicker"><div class="datepickerBorderT"></div><div class="datepickerBorderB"></div><div class="datepickerBorderL"></div><div class="datepickerBorderR"></div><div class="datepickerBorderTL"></div><div class="datepickerBorderTR"></div><div class="datepickerBorderBL"></div><div class="datepickerBorderBR"></div><div class="datepickerContainer"><table cellspacing="0" cellpadding="0"><tbody><tr></tr></tbody></table></div></div>',
+				head: [
+					'<td>',
+					'<table cellspacing="0" cellpadding="0">',
+						'<thead>',
+							'<tr>',
+								'<th class="datepickerGoPrev"><a href="#"><span><%=prev%></span></a></th>',
+								'<th colspan="6" class="datepickerMonth"><a href="#"><span></span></a></th>',
+								'<th class="datepickerGoNext"><a href="#"><span><%=next%></span></a></th>',
+							'</tr>',
+							'<tr class="datepickerDoW">',
+								'<th><span><%=week%></span></th>',
+								'<th><span><%=day1%></span></th>',
+								'<th><span><%=day2%></span></th>',
+								'<th><span><%=day3%></span></th>',
+								'<th><span><%=day4%></span></th>',
+								'<th><span><%=day5%></span></th>',
+								'<th><span><%=day6%></span></th>',
+								'<th><span><%=day7%></span></th>',
+							'</tr>',
+						'</thead>',
+					'</table></td>'
+				],
+				space : '<td class="datepickerSpace"><div></div></td>',
+				days: [
+					'<tbody class="datepickerDays">',
+						'<tr>',
+							'<th class="datepickerWeek"><a href="#"><span><%=weeks[0].week%></span></a></th>',
+							'<td class="<%=weeks[0].days[0].classname%>"><a href="#"><span><%=weeks[0].days[0].text%></span></a></td>',
+							'<td class="<%=weeks[0].days[1].classname%>"><a href="#"><span><%=weeks[0].days[1].text%></span></a></td>',
+							'<td class="<%=weeks[0].days[2].classname%>"><a href="#"><span><%=weeks[0].days[2].text%></span></a></td>',
+							'<td class="<%=weeks[0].days[3].classname%>"><a href="#"><span><%=weeks[0].days[3].text%></span></a></td>',
+							'<td class="<%=weeks[0].days[4].classname%>"><a href="#"><span><%=weeks[0].days[4].text%></span></a></td>',
+							'<td class="<%=weeks[0].days[5].classname%>"><a href="#"><span><%=weeks[0].days[5].text%></span></a></td>',
+							'<td class="<%=weeks[0].days[6].classname%>"><a href="#"><span><%=weeks[0].days[6].text%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<th class="datepickerWeek"><a href="#"><span><%=weeks[1].week%></span></a></th>',
+							'<td class="<%=weeks[1].days[0].classname%>"><a href="#"><span><%=weeks[1].days[0].text%></span></a></td>',
+							'<td class="<%=weeks[1].days[1].classname%>"><a href="#"><span><%=weeks[1].days[1].text%></span></a></td>',
+							'<td class="<%=weeks[1].days[2].classname%>"><a href="#"><span><%=weeks[1].days[2].text%></span></a></td>',
+							'<td class="<%=weeks[1].days[3].classname%>"><a href="#"><span><%=weeks[1].days[3].text%></span></a></td>',
+							'<td class="<%=weeks[1].days[4].classname%>"><a href="#"><span><%=weeks[1].days[4].text%></span></a></td>',
+							'<td class="<%=weeks[1].days[5].classname%>"><a href="#"><span><%=weeks[1].days[5].text%></span></a></td>',
+							'<td class="<%=weeks[1].days[6].classname%>"><a href="#"><span><%=weeks[1].days[6].text%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<th class="datepickerWeek"><a href="#"><span><%=weeks[2].week%></span></a></th>',
+							'<td class="<%=weeks[2].days[0].classname%>"><a href="#"><span><%=weeks[2].days[0].text%></span></a></td>',
+							'<td class="<%=weeks[2].days[1].classname%>"><a href="#"><span><%=weeks[2].days[1].text%></span></a></td>',
+							'<td class="<%=weeks[2].days[2].classname%>"><a href="#"><span><%=weeks[2].days[2].text%></span></a></td>',
+							'<td class="<%=weeks[2].days[3].classname%>"><a href="#"><span><%=weeks[2].days[3].text%></span></a></td>',
+							'<td class="<%=weeks[2].days[4].classname%>"><a href="#"><span><%=weeks[2].days[4].text%></span></a></td>',
+							'<td class="<%=weeks[2].days[5].classname%>"><a href="#"><span><%=weeks[2].days[5].text%></span></a></td>',
+							'<td class="<%=weeks[2].days[6].classname%>"><a href="#"><span><%=weeks[2].days[6].text%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<th class="datepickerWeek"><a href="#"><span><%=weeks[3].week%></span></a></th>',
+							'<td class="<%=weeks[3].days[0].classname%>"><a href="#"><span><%=weeks[3].days[0].text%></span></a></td>',
+							'<td class="<%=weeks[3].days[1].classname%>"><a href="#"><span><%=weeks[3].days[1].text%></span></a></td>',
+							'<td class="<%=weeks[3].days[2].classname%>"><a href="#"><span><%=weeks[3].days[2].text%></span></a></td>',
+							'<td class="<%=weeks[3].days[3].classname%>"><a href="#"><span><%=weeks[3].days[3].text%></span></a></td>',
+							'<td class="<%=weeks[3].days[4].classname%>"><a href="#"><span><%=weeks[3].days[4].text%></span></a></td>',
+							'<td class="<%=weeks[3].days[5].classname%>"><a href="#"><span><%=weeks[3].days[5].text%></span></a></td>',
+							'<td class="<%=weeks[3].days[6].classname%>"><a href="#"><span><%=weeks[3].days[6].text%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<th class="datepickerWeek"><a href="#"><span><%=weeks[4].week%></span></a></th>',
+							'<td class="<%=weeks[4].days[0].classname%>"><a href="#"><span><%=weeks[4].days[0].text%></span></a></td>',
+							'<td class="<%=weeks[4].days[1].classname%>"><a href="#"><span><%=weeks[4].days[1].text%></span></a></td>',
+							'<td class="<%=weeks[4].days[2].classname%>"><a href="#"><span><%=weeks[4].days[2].text%></span></a></td>',
+							'<td class="<%=weeks[4].days[3].classname%>"><a href="#"><span><%=weeks[4].days[3].text%></span></a></td>',
+							'<td class="<%=weeks[4].days[4].classname%>"><a href="#"><span><%=weeks[4].days[4].text%></span></a></td>',
+							'<td class="<%=weeks[4].days[5].classname%>"><a href="#"><span><%=weeks[4].days[5].text%></span></a></td>',
+							'<td class="<%=weeks[4].days[6].classname%>"><a href="#"><span><%=weeks[4].days[6].text%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<th class="datepickerWeek"><a href="#"><span><%=weeks[5].week%></span></a></th>',
+							'<td class="<%=weeks[5].days[0].classname%>"><a href="#"><span><%=weeks[5].days[0].text%></span></a></td>',
+							'<td class="<%=weeks[5].days[1].classname%>"><a href="#"><span><%=weeks[5].days[1].text%></span></a></td>',
+							'<td class="<%=weeks[5].days[2].classname%>"><a href="#"><span><%=weeks[5].days[2].text%></span></a></td>',
+							'<td class="<%=weeks[5].days[3].classname%>"><a href="#"><span><%=weeks[5].days[3].text%></span></a></td>',
+							'<td class="<%=weeks[5].days[4].classname%>"><a href="#"><span><%=weeks[5].days[4].text%></span></a></td>',
+							'<td class="<%=weeks[5].days[5].classname%>"><a href="#"><span><%=weeks[5].days[5].text%></span></a></td>',
+							'<td class="<%=weeks[5].days[6].classname%>"><a href="#"><span><%=weeks[5].days[6].text%></span></a></td>',
+						'</tr>',
+					'</tbody>'
+				],
+				months: [
+					'<tbody class="<%=className%>">',
+						'<tr>',
+							'<td colspan="2"><a href="#"><span><%=data[0]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[1]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[2]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[3]%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<td colspan="2"><a href="#"><span><%=data[4]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[5]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[6]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[7]%></span></a></td>',
+						'</tr>',
+						'<tr>',
+							'<td colspan="2"><a href="#"><span><%=data[8]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[9]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[10]%></span></a></td>',
+							'<td colspan="2"><a href="#"><span><%=data[11]%></span></a></td>',
+						'</tr>',
+					'</tbody>'
+				]
+			},
+			defaults = {
+				flat: false,
+				starts: 1,
+				prev: '&#9664;',
+				next: '&#9654;',
+				lastSel: false,
+				mode: 'single',
+				calendars: 1,
+				format: 'Y-m-d',
+				position: 'bottom',
+				eventName: 'click',
+				onRender: function(){return {};},
+				onChange: function(){return true;},
+				onShow: function(){return true;},
+				onBeforeShow: function(){return true;},
+				onHide: function(){return true;},
+				locale: {
+					days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+					daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+					daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
+					months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+					monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+					weekMin: 'wk'
+				}
+			},
+			fill = function(el) {
+				var options = $(el).data('datepicker');
+				var cal = $(el);
+				var currentCal = Math.floor(options.calendars/2), date, data, dow, month, cnt = 0, week, days, indic, indic2, html, tblCal;
+				cal.find('td>table tbody').remove();
+				for (var i = 0; i < options.calendars; i++) {
+					date = new Date(options.current);
+					date.addMonths(-currentCal + i);
+					tblCal = cal.find('table').eq(i+1);
+					switch (tblCal[0].className) {
+						case 'datepickerViewDays':
+							dow = formatDate(date, 'B, Y');
+							break;
+						case 'datepickerViewMonths':
+							dow = date.getFullYear();
+							break;
+						case 'datepickerViewYears':
+							dow = (date.getFullYear()-6) + ' - ' + (date.getFullYear()+5);
+							break;
+					} 
+					tblCal.find('thead tr:first th:eq(1) span').text(dow);
+					dow = date.getFullYear()-6;
+					data = {
+						data: [],
+						className: 'datepickerYears'
+					}
+					for ( var j = 0; j < 12; j++) {
+						data.data.push(dow + j);
+					}
+					html = tmpl(tpl.months.join(''), data);
+					date.setDate(1);
+					data = {weeks:[], test: 10};
+					month = date.getMonth();
+					var dow = (date.getDay() - options.starts) % 7;
+					date.addDays(-(dow + (dow < 0 ? 7 : 0)));
+					week = -1;
+					cnt = 0;
+					while (cnt < 42) {
+						indic = parseInt(cnt/7,10);
+						indic2 = cnt%7;
+						if (!data.weeks[indic]) {
+							week = date.getWeekNumber();
+							data.weeks[indic] = {
+								week: week,
+								days: []
+							};
+						}
+						data.weeks[indic].days[indic2] = {
+							text: date.getDate(),
+							classname: []
+						};
+						if (month != date.getMonth()) {
+							data.weeks[indic].days[indic2].classname.push('datepickerNotInMonth');
+						}
+						if (date.getDay() == 0) {
+							data.weeks[indic].days[indic2].classname.push('datepickerSunday');
+						}
+						if (date.getDay() == 6) {
+							data.weeks[indic].days[indic2].classname.push('datepickerSaturday');
+						}
+						var fromUser = options.onRender(date);
+						var val = date.valueOf();
+						if (fromUser.selected || options.date == val || $.inArray(val, options.date) > -1 || (options.mode == 'range' && val >= options.date[0] && val <= options.date[1])) {
+							data.weeks[indic].days[indic2].classname.push('datepickerSelected');
+						}
+						if (fromUser.disabled) {
+							data.weeks[indic].days[indic2].classname.push('datepickerDisabled');
+						}
+						if (fromUser.className) {
+							data.weeks[indic].days[indic2].classname.push(fromUser.className);
+						}
+						data.weeks[indic].days[indic2].classname = data.weeks[indic].days[indic2].classname.join(' ');
+						cnt++;
+						date.addDays(1);
+					}
+					html = tmpl(tpl.days.join(''), data) + html;
+					data = {
+						data: options.locale.monthsShort,
+						className: 'datepickerMonths'
+					};
+					html = tmpl(tpl.months.join(''), data) + html;
+					tblCal.append(html);
+				}
+			},
+			parseDate = function (date, format) {
+				if (date.constructor == Date) {
+					return new Date(date);
+				}
+				var parts = date.split(/\W+/);
+				var against = format.split(/\W+/), d, m, y, h, min, now = new Date();
+				for (var i = 0; i < parts.length; i++) {
+					switch (against[i]) {
+						case 'd':
+						case 'e':
+							d = parseInt(parts[i],10);
+							break;
+						case 'm':
+							m = parseInt(parts[i], 10) - 1;
+							break;
+						case 'Y':
+						case 'y':
+							y = parseInt(parts[i], 10);
+							y += y > 100 ? 0 : (y < 29 ? 2000 : 1900);
+							break;
+						case 'H':
+						case 'I':
+						case 'k':
+						case 'l':
+							h = parseInt(parts[i], 10);
+							break;
+						case 'P':
+						case 'p':
+							if (/pm/i.test(parts[i]) && h < 12) {
+								h += 12;
+							} else if (/am/i.test(parts[i]) && h >= 12) {
+								h -= 12;
+							}
+							break;
+						case 'M':
+							min = parseInt(parts[i], 10);
+							break;
+					}
+				}
+				return new Date(
+					y||now.getFullYear(),
+					m||now.getMonth(),
+					d||now.getDate(),
+					h||now.getHours(),
+					min||now.getMinutes(),
+					0
+				);
+			},
+			formatDate = function(date, format) {
+				var m = date.getMonth();
+				var d = date.getDate();
+				var y = date.getFullYear();
+				var wn = date.getWeekNumber();
+				var w = date.getDay();
+				var s = {};
+				var hr = date.getHours();
+				var pm = (hr >= 12);
+				var ir = (pm) ? (hr - 12) : hr;
+				var dy = date.getDayOfYear();
+				if (ir == 0) {
+					ir = 12;
+				}
+				var min = date.getMinutes();
+				var sec = date.getSeconds();
+				var parts = format.split(''), part;
+				for ( var i = 0; i < parts.length; i++ ) {
+					part = parts[i];
+					switch (parts[i]) {
+						case 'a':
+							part = date.getDayName();
+							break;
+						case 'A':
+							part = date.getDayName(true);
+							break;
+						case 'b':
+							part = date.getMonthName();
+							break;
+						case 'B':
+							part = date.getMonthName(true);
+							break;
+						case 'C':
+							part = 1 + Math.floor(y / 100);
+							break;
+						case 'd':
+							part = (d < 10) ? ("0" + d) : d;
+							break;
+						case 'e':
+							part = d;
+							break;
+						case 'H':
+							part = (hr < 10) ? ("0" + hr) : hr;
+							break;
+						case 'I':
+							part = (ir < 10) ? ("0" + ir) : ir;
+							break;
+						case 'j':
+							part = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy;
+							break;
+						case 'k':
+							part = hr;
+							break;
+						case 'l':
+							part = ir;
+							break;
+						case 'm':
+							part = (m < 9) ? ("0" + (1+m)) : (1+m);
+							break;
+						case 'M':
+							part = (min < 10) ? ("0" + min) : min;
+							break;
+						case 'p':
+						case 'P':
+							part = pm ? "PM" : "AM";
+							break;
+						case 's':
+							part = Math.floor(date.getTime() / 1000);
+							break;
+						case 'S':
+							part = (sec < 10) ? ("0" + sec) : sec;
+							break;
+						case 'u':
+							part = w + 1;
+							break;
+						case 'w':
+							part = w;
+							break;
+						case 'y':
+							part = ('' + y).substr(2, 2);
+							break;
+						case 'Y':
+							part = y;
+							break;
+					}
+					parts[i] = part;
+				}
+				return parts.join('');
+			},
+			extendDate = function(options) {
+				if (Date.prototype.tempDate) {
+					return;
+				}
+				Date.prototype.tempDate = null;
+				Date.prototype.months = options.months;
+				Date.prototype.monthsShort = options.monthsShort;
+				Date.prototype.days = options.days;
+				Date.prototype.daysShort = options.daysShort;
+				Date.prototype.getMonthName = function(fullName) {
+					return this[fullName ? 'months' : 'monthsShort'][this.getMonth()];
+				};
+				Date.prototype.getDayName = function(fullName) {
+					return this[fullName ? 'days' : 'daysShort'][this.getDay()];
+				};
+				Date.prototype.addDays = function (n) {
+					this.setDate(this.getDate() + n);
+					this.tempDate = this.getDate();
+				};
+				Date.prototype.addMonths = function (n) {
+					if (this.tempDate == null) {
+						this.tempDate = this.getDate();
+					}
+					this.setDate(1);
+					this.setMonth(this.getMonth() + n);
+					this.setDate(Math.min(this.tempDate, this.getMaxDays()));
+				};
+				Date.prototype.addYears = function (n) {
+					if (this.tempDate == null) {
+						this.tempDate = this.getDate();
+					}
+					this.setDate(1);
+					this.setFullYear(this.getFullYear() + n);
+					this.setDate(Math.min(this.tempDate, this.getMaxDays()));
+				};
+				Date.prototype.getMaxDays = function() {
+					var tmpDate = new Date(Date.parse(this)),
+						d = 28, m;
+					m = tmpDate.getMonth();
+					d = 28;
+					while (tmpDate.getMonth() == m) {
+						d ++;
+						tmpDate.setDate(d);
+					}
+					return d - 1;
+				};
+				Date.prototype.getFirstDay = function() {
+					var tmpDate = new Date(Date.parse(this));
+					tmpDate.setDate(1);
+					return tmpDate.getDay();
+				};
+				Date.prototype.getWeekNumber = function() {
+					var tempDate = new Date(this);
+					tempDate.setDate(tempDate.getDate() - (tempDate.getDay() + 6) % 7 + 3);
+					var dms = tempDate.valueOf();
+					tempDate.setMonth(0);
+					tempDate.setDate(4);
+					return Math.round((dms - tempDate.valueOf()) / (604800000)) + 1;
+				};
+				Date.prototype.getDayOfYear = function() {
+					var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+					var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
+					var time = now - then;
+					return Math.floor(time / 24*60*60*1000);
+				};
+			},
+			layout = function (el) {
+				var options = $(el).data('datepicker');
+				var cal = $('#' + options.id);
+				if (!options.extraHeight) {
+					var divs = $(el).find('div');
+					options.extraHeight = divs.get(0).offsetHeight + divs.get(1).offsetHeight;
+					options.extraWidth = divs.get(2).offsetWidth + divs.get(3).offsetWidth;
+				}
+				var tbl = cal.find('table:first').get(0);
+				var width = tbl.offsetWidth;
+				var height = tbl.offsetHeight;
+				cal.css({
+					width: width + options.extraWidth + 'px',
+					height: height + options.extraHeight + 'px'
+				}).find('div.datepickerContainer').css({
+					width: width + 'px',
+					height: height + 'px'
+				});
+			},
+			click = function(ev) {
+				if ($(ev.target).is('span')) {
+					ev.target = ev.target.parentNode;
+				}
+				var el = $(ev.target);
+				if (el.is('a')) {
+					ev.target.blur();
+					if (el.hasClass('datepickerDisabled')) {
+						return false;
+					}
+					var options = $(this).data('datepicker');
+					var parentEl = el.parent();
+					var tblEl = parentEl.parent().parent().parent();
+					var tblIndex = $('table', this).index(tblEl.get(0)) - 1;
+					var tmp = new Date(options.current);
+					var changed = false;
+					var fillIt = false;
+					if (parentEl.is('th')) {
+						if (parentEl.hasClass('datepickerWeek') && options.mode == 'range' && !parentEl.next().hasClass('datepickerDisabled')) {
+							var val = parseInt(parentEl.next().text(), 10);
+							tmp.addMonths(tblIndex - Math.floor(options.calendars/2));
+							if (parentEl.next().hasClass('datepickerNotInMonth')) {
+								tmp.addMonths(val > 15 ? -1 : 1);
+							}
+							tmp.setDate(val);
+							options.date[0] = (tmp.setHours(0,0,0,0)).valueOf();
+							tmp.setHours(23,59,59,0);
+							tmp.addDays(6);
+							options.date[1] = tmp.valueOf();
+							fillIt = true;
+							changed = true;
+							options.lastSel = false;
+						} else if (parentEl.hasClass('datepickerMonth')) {
+							tmp.addMonths(tblIndex - Math.floor(options.calendars/2));
+							switch (tblEl.get(0).className) {
+								case 'datepickerViewDays':
+									tblEl.get(0).className = 'datepickerViewMonths';
+									el.find('span').text(tmp.getFullYear());
+									break;
+								case 'datepickerViewMonths':
+									tblEl.get(0).className = 'datepickerViewYears';
+									el.find('span').text((tmp.getFullYear()-6) + ' - ' + (tmp.getFullYear()+5));
+									break;
+								case 'datepickerViewYears':
+									tblEl.get(0).className = 'datepickerViewDays';
+									el.find('span').text(formatDate(tmp, 'B, Y'));
+									break;
+							}
+						} else if (parentEl.parent().parent().is('thead')) {
+							switch (tblEl.get(0).className) {
+								case 'datepickerViewDays':
+									options.current.addMonths(parentEl.hasClass('datepickerGoPrev') ? -1 : 1);
+									break;
+								case 'datepickerViewMonths':
+									options.current.addYears(parentEl.hasClass('datepickerGoPrev') ? -1 : 1);
+									break;
+								case 'datepickerViewYears':
+									options.current.addYears(parentEl.hasClass('datepickerGoPrev') ? -12 : 12);
+									break;
+							}
+							fillIt = true;
+						}
+					} else if (parentEl.is('td') && !parentEl.hasClass('datepickerDisabled')) {
+						switch (tblEl.get(0).className) {
+							case 'datepickerViewMonths':
+								options.current.setMonth(tblEl.find('tbody.datepickerMonths td').index(parentEl));
+								options.current.setFullYear(parseInt(tblEl.find('thead th.datepickerMonth span').text(), 10));
+								options.current.addMonths(Math.floor(options.calendars/2) - tblIndex);
+								tblEl.get(0).className = 'datepickerViewDays';
+								break;
+							case 'datepickerViewYears':
+								options.current.setFullYear(parseInt(el.text(), 10));
+								tblEl.get(0).className = 'datepickerViewMonths';
+								break;
+							default:
+								var val = parseInt(el.text(), 10);
+								tmp.addMonths(tblIndex - Math.floor(options.calendars/2));
+								if (parentEl.hasClass('datepickerNotInMonth')) {
+									tmp.addMonths(val > 15 ? -1 : 1);
+								}
+								tmp.setDate(val);
+								switch (options.mode) {
+									case 'multiple':
+										val = (tmp.setHours(0,0,0,0)).valueOf();
+										if ($.inArray(val, options.date) > -1) {
+											$.each(options.date, function(nr, dat){
+												if (dat == val) {
+													delete options.date[nr];
+													return false;
+												}
+											});
+										} else {
+											options.date.push(val);
+										}
+										break;
+									case 'range':
+										if (!options.lastSel) {
+											options.date[0] = (tmp.setHours(0,0,0,0)).valueOf();
+										}
+										val = (tmp.setHours(23,59,59,0)).valueOf();
+										if (val < options.date[0]) {
+											options.date[1] = options.date[0] + 86399000;
+											options.date[0] = val - 86399000;
+										} else {
+											options.date[1] = val;
+										}
+										options.lastSel = !options.lastSel;
+										break;
+									default:
+										options.date = tmp.valueOf();
+										break;
+								}
+								break;
+						}
+						fillIt = true;
+						changed = true;
+					}
+					if (fillIt) {
+						fill(this);
+					}
+					if (changed) {
+						options.onChange.apply(this, prepareDate(options));
+					}
+				}
+				return false;
+			},
+			prepareDate = function (options) {
+				var tmp;
+				if (options.mode == 'single') {
+					tmp = new Date(options.date);
+					return [formatDate(tmp, options.format), tmp];
+				} else {
+					tmp = [[],[]];
+					$.each(options.date, function(nr, val){
+						var date = new Date(val);
+						tmp[0].push(formatDate(date, options.format));
+						tmp[1].push(date);
+					});
+					return tmp;
+				}
+			},
+			getViewport = function () {
+				var m = document.compatMode == 'CSS1Compat';
+				return {
+					l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
+					t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop),
+					w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth),
+					h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight)
+				};
+			},
+			isChildOf = function(parentEl, el, container) {
+				if (parentEl == el) {
+					return true;
+				}
+				if (parentEl.contains) {
+					return parentEl.contains(el);
+				}
+				if ( parentEl.compareDocumentPosition ) {
+					return !!(parentEl.compareDocumentPosition(el) & 16);
+				}
+				var prEl = el.parentNode;
+				while(prEl && prEl != container) {
+					if (prEl == parentEl)
+						return true;
+					prEl = prEl.parentNode;
+				}
+				return false;
+			},
+			show = function (ev) {
+				var cal = $('#' + $(this).data('datepickerId'));
+				if (!cal.is(':visible')) {
+					var calEl = cal.get(0);
+					var options = cal.data('datepicker');
+					options.onBeforeShow.apply(this, [cal.get(0)]);
+					var pos = $(this).offset();
+					var viewPort = getViewport();
+					var top = pos.top;
+					var left = pos.left;
+					// var oldDisplay = $.curCSS(calEl, 'display');
+					cal.css({
+						visibility: 'hidden',
+						display: 'block'
+					});
+					layout(calEl);
+					switch (options.position){
+						case 'top':
+							top -= calEl.offsetHeight;
+							break;
+						case 'left':
+							left -= calEl.offsetWidth;
+							break;
+						case 'right':
+							left += this.offsetWidth;
+							break;
+						case 'bottom':
+							top += this.offsetHeight;
+							break;
+					}
+					if (top + calEl.offsetHeight > viewPort.t + viewPort.h) {
+						top = pos.top  - calEl.offsetHeight;
+					}
+					if (top < viewPort.t) {
+						top = pos.top + this.offsetHeight + calEl.offsetHeight;
+					}
+					if (left + calEl.offsetWidth > viewPort.l + viewPort.w) {
+						left = pos.left - calEl.offsetWidth;
+					}
+					if (left < viewPort.l) {
+						left = pos.left + this.offsetWidth
+					}
+					cal.css({
+						visibility: 'visible',
+						display: 'block',
+						top: top + 'px',
+						left: left + 'px'
+					});
+					if (options.onShow.apply(this, [cal.get(0)]) != false) {
+						cal.show();
+					}
+					$(document).bind('mousedown', {cal: cal, trigger: this}, hide);
+				}
+				return false;
+			},
+			hide = function (ev) {
+				if (ev.target != ev.data.trigger && !isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
+					if (ev.data.cal.data('datepicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
+						ev.data.cal.hide();
+					}
+					$(document).unbind('mousedown', hide);
+				}
+			};
+		return {
+			init: function(options){
+				options = $.extend({}, defaults, options||{});
+				extendDate(options.locale);
+				options.calendars = Math.max(1, parseInt(options.calendars,10)||1);
+				options.mode = /single|multiple|range/.test(options.mode) ? options.mode : 'single';
+				return this.each(function(){
+					if (!$(this).data('datepicker')) {
+						if (options.date.constructor == String) {
+							options.date = parseDate(options.date, options.format);
+							options.date.setHours(0,0,0,0);
+						}
+						if (options.mode != 'single') {
+							if (options.date.constructor != Array) {
+								options.date = [options.date.valueOf()];
+								if (options.mode == 'range') {
+									options.date.push(((new Date(options.date[0])).setHours(23,59,59,0)).valueOf());
+								}
+							} else {
+								for (var i = 0; i < options.date.length; i++) {
+									options.date[i] = (parseDate(options.date[i], options.format).setHours(0,0,0,0)).valueOf();
+								}
+								if (options.mode == 'range') {
+									options.date[1] = ((new Date(options.date[1])).setHours(23,59,59,0)).valueOf();
+								}
+							}
+						} else {
+							options.date = options.date.valueOf();
+						}
+						if (!options.current) {
+							options.current = new Date();
+						} else {
+							options.current = parseDate(options.current, options.format);
+						} 
+						options.current.setDate(1);
+						options.current.setHours(0,0,0,0);
+						var id = 'datepicker_' + parseInt(Math.random() * 1000), cnt;
+						options.id = id;
+						$(this).data('datepickerId', options.id);
+						var cal = $(tpl.wrapper).attr('id', id).bind('click', click).data('datepicker', options);
+						if (options.className) {
+							cal.addClass(options.className);
+						}
+						for (var i = 0; i < options.calendars; i++) {
+							cnt = options.starts;
+							cal.find('tr:first').append(
+								i > 0 ?  tpl.space: '',
+								tmpl(tpl.head.join(''), {
+									week: options.locale.weekMin,
+									prev: options.prev,
+									next: options.next,
+									day1: options.locale.daysMin[(cnt++)%7],
+									day2: options.locale.daysMin[(cnt++)%7],
+									day3: options.locale.daysMin[(cnt++)%7],
+									day4: options.locale.daysMin[(cnt++)%7],
+									day5: options.locale.daysMin[(cnt++)%7],
+									day6: options.locale.daysMin[(cnt++)%7],
+									day7: options.locale.daysMin[(cnt++)%7]
+								})
+							);
+						}
+						cal.find('tr:first table').addClass('datepickerViewDays');
+						fill(cal.get(0));
+						if (options.flat) {
+							cal.appendTo(this).show().css('position', 'relative');
+							layout(cal.get(0));
+						} else {
+							cal.appendTo(document.body);
+							$(this).bind(options.eventName, show);
+						}
+					}
+				});
+			},
+			showPicker: function() {
+				return this.each( function () {
+					if ($(this).data('datepickerId')) {
+						show.apply(this);
+					}
+				});
+			},
+			hidePicker: function() {
+				return this.each( function () {
+					if ($(this).data('datepickerId')) {
+						$('#' + $(this).data('datepickerId')).hide();
+					}
+				});
+			},
+			setDate: function(date, shiftTo){
+				return this.each(function(){
+					if ($(this).data('datepickerId')) {
+						var cal = $('#' + $(this).data('datepickerId'));
+						var options = cal.data('datepicker');
+						options.date = date;
+						if (options.date.constructor == String) {
+							options.date = parseDate(options.date, options.format);
+							options.date.setHours(0,0,0,0);
+						}
+						if (options.mode != 'single') {
+							if (options.date.constructor != Array) {
+								options.date = [options.date.valueOf()];
+								if (options.mode == 'range') {
+									options.date.push(((new Date(options.date[0])).setHours(23,59,59,0)).valueOf());
+								}
+							} else {
+								for (var i = 0; i < options.date.length; i++) {
+									options.date[i] = (parseDate(options.date[i], options.format).setHours(0,0,0,0)).valueOf();
+								}
+								if (options.mode == 'range') {
+									options.date[1] = ((new Date(options.date[1])).setHours(23,59,59,0)).valueOf();
+								}
+							}
+						} else {
+							options.date = options.date.valueOf();
+						}
+						if (shiftTo) {
+							options.current = new Date (options.mode != 'single' ? options.date[0] : options.date);
+						}
+						fill(cal.get(0));
+					}
+				});
+			},
+			getDate: function(formated) {
+				if (this.size() > 0) {
+					return prepareDate($('#' + $(this).data('datepickerId')).data('datepicker'))[formated ? 0 : 1];
+				}
+			}
+		};
+	}();
+	$.fn.extend({
+		DatePicker: DatePicker.init,
+		DatePickerHide: DatePicker.hide,
+		DatePickerShow: DatePicker.show,
+		DatePickerSetDate: DatePicker.setDate,
+		DatePickerGetDate: DatePicker.getDate
+	});
+})(jQuery);
+
+(function(){
+  var cache = {};
+ 
+  this.tmpl = function tmpl(str, data){
+    // Figure out if we're getting a template, or if we need to
+    // load the template - and be sure to cache the result.
+    var fn = !/\W/.test(str) ?
+      cache[str] = cache[str] ||
+        tmpl(document.getElementById(str).innerHTML) :
+     
+      // Generate a reusable function that will serve as a template
+      // generator (and which will be cached).
+      new Function("obj",
+        "var p=[],print=function(){p.push.apply(p,arguments);};" +
+       
+        // Introduce the data as local variables using with(){}
+        "with(obj){p.push('" +
+       
+        // Convert the template into pure JavaScript
+        str
+          .replace(/[\r\t\n]/g, " ")
+          .split("<%").join("\t")
+          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+          .replace(/\t=(.*?)%>/g, "',$1,'")
+          .split("\t").join("');")
+          .split("%>").join("p.push('")
+          .split("\r").join("\\'")
+      + "');}return p.join('');");
+   
+    // Provide some basic currying to the user
+    return data ? fn( data ) : fn;
+  };
+})();

二进制
public/static/dahua/static/images/custom_b.png


二进制
public/static/dahua/static/images/custom_bl.png


二进制
public/static/dahua/static/images/custom_br.png


二进制
public/static/dahua/static/images/custom_l.png


二进制
public/static/dahua/static/images/custom_r.png


二进制
public/static/dahua/static/images/custom_t.png


二进制
public/static/dahua/static/images/custom_tl.png


二进制
public/static/dahua/static/images/custom_tr.png


二进制
public/static/dahua/static/images/datepicker_b.png


二进制
public/static/dahua/static/images/datepicker_bl.png


二进制
public/static/dahua/static/images/datepicker_br.png


二进制
public/static/dahua/static/images/datepicker_l.png


二进制
public/static/dahua/static/images/datepicker_r.png


二进制
public/static/dahua/static/images/datepicker_t.png


二进制
public/static/dahua/static/images/datepicker_tl.png


二进制
public/static/dahua/static/images/datepicker_tr.png


二进制
public/static/dahua/static/images/field.png


文件差异内容过多而无法显示
+ 2 - 0
public/static/dahua/static/jquery-3.6.0.min.js


文件差异内容过多而无法显示
+ 3843 - 0
public/static/dahua/static/jquery.ztree.all.js


文件差异内容过多而无法显示
+ 3 - 0
public/static/dahua/static/jquery.ztree.all.min.js


二进制
public/static/dahua/static/zTreeStyle/img/diy/1_close.png


二进制
public/static/dahua/static/zTreeStyle/img/diy/1_open.png


二进制
public/static/dahua/static/zTreeStyle/img/diy/2.png


+ 0 - 0
public/static/dahua/static/zTreeStyle/img/diy/3.png


部分文件因为文件数量过多而无法显示