Commit c6e8b341f3e266c857622e59d67edd1457be144e

Authored by 648540858
1 parent d881c982

web页面集成

web_src/src/assets/loading.png 0 → 100644

2.7 KB

web_src/src/assets/login-bg.jpg 0 → 100644

3.97 KB

web_src/src/assets/login-cloud.png 0 → 100644

3.31 KB

web_src/src/assets/logo.png 0 → 100644

6.69 KB

web_src/src/assets/play.png 0 → 100644

546 Bytes

web_src/src/components/Loading.vue 0 → 100644
  1 +//loading效果组件
  2 +
  3 +<template>
  4 +<div class="loadEffect" :style="{marginTop: marginTop? marginTop : '50%'}">
  5 + <span class="ld-span"></span>
  6 + <span class="ld-span"></span>
  7 + <span class="ld-span"></span>
  8 + <span class="ld-span"></span>
  9 + <span class="ld-span"></span>
  10 + <span class="ld-span"></span>
  11 + <span class="ld-span"></span>
  12 + <span class="ld-span"></span>
  13 +</div>
  14 +</template>
  15 +
  16 +<script>
  17 +export default {
  18 + name: 'Loading',
  19 + props: ["marginTop"]
  20 +
  21 +}
  22 +</script>
  23 +
  24 +<style scoped>
  25 +.loadEffect{
  26 + width: 100px;
  27 + height: 100px;
  28 + position: relative;
  29 + margin: 0 auto;
  30 + position: relative;
  31 + top:-50px;
  32 + margin-top:50%;
  33 + transform: scale(.5)
  34 +}
  35 +.loadEffect .ld-span{
  36 + display: inline-block;
  37 + width: 20px;
  38 + height: 20px;
  39 + border-radius: 50%;
  40 + background: #67e7d5;
  41 + position: absolute;
  42 + -webkit-animation: load 1.04s ease infinite;
  43 +}
  44 +@-webkit-keyframes load{
  45 + 0%{
  46 + -webkit-transform: scale(1.2);
  47 + opacity: 1;
  48 + }
  49 + 100%{
  50 + -webkit-transform: scale(.3);
  51 + opacity: 0.5;
  52 + }
  53 +}
  54 +.loadEffect .ld-span:nth-child(1){
  55 + left: 0;
  56 + top: 50%;
  57 + margin-top:-10px;
  58 + -webkit-animation-delay:0.13s;
  59 +}
  60 +.loadEffect .ld-span:nth-child(2){
  61 + left: 14px;
  62 + top: 14px;
  63 + -webkit-animation-delay:0.26s;
  64 +}
  65 +.loadEffect .ld-span:nth-child(3){
  66 + left: 50%;
  67 + top: 0;
  68 + margin-left: -10px;
  69 + -webkit-animation-delay:0.39s;
  70 +}
  71 +.loadEffect .ld-span:nth-child(4){
  72 + top: 14px;
  73 + right:14px;
  74 + -webkit-animation-delay:0.52s;
  75 +}
  76 +.loadEffect .ld-span:nth-child(5){
  77 + right: 0;
  78 + top: 50%;
  79 + margin-top:-10px;
  80 + -webkit-animation-delay:0.65s;
  81 +}
  82 +.loadEffect .ld-span:nth-child(6){
  83 + right: 14px;
  84 + bottom:14px;
  85 + -webkit-animation-delay:0.78s;
  86 +}
  87 +.loadEffect .ld-span:nth-child(7){
  88 + bottom: 0;
  89 + left: 50%;
  90 + margin-left: -10px;
  91 + -webkit-animation-delay:0.91s;
  92 +}
  93 +.loadEffect .ld-span:nth-child(8){
  94 + bottom: 14px;
  95 + left: 14px;
  96 + -webkit-animation-delay:1.04s;
  97 +}
  98 +</style>
... ...
web_src/src/components/Login.vue 0 → 100644
  1 +<template>
  2 +<div class="login" id="login">
  3 + <a href="javascript:;" class="log-close"><i class="icons close"></i></a>
  4 + <div class="log-bg">
  5 + <div class="log-cloud cloud1"></div>
  6 + <div class="log-cloud cloud2"></div>
  7 + <div class="log-cloud cloud3"></div>
  8 + <div class="log-cloud cloud4"></div>
  9 +
  10 + <div class="log-logo">Welcome!</div>
  11 + <div class="log-text"></div>
  12 + </div>
  13 + <div class="log-email">
  14 + <input type="text" placeholder="用户名" :class="'log-input' + (username==''?' log-input-empty':'')" v-model="username"><input type="password" placeholder="密码" :class="'log-input' + (password==''?' log-input-empty':'')" v-model="password">
  15 + <a href="javascript:;" class="log-btn" @click="login" >登录</a>
  16 + </div>
  17 + <Loading v-if="isLoging" marginTop="-30%"></Loading>
  18 +</div>
  19 +</template>
  20 +
  21 +<script>
  22 +import Loading from './Loading.vue'
  23 +import crypto from 'crypto'
  24 +export default {
  25 + name: 'Login',
  26 + data(){
  27 + return {
  28 + isLoging: false,
  29 + username: '',
  30 + password: ''
  31 + }
  32 + },
  33 + components:{
  34 + Loading
  35 + },
  36 + created(){
  37 + var that = this;
  38 + document.onkeydown = function(e) {
  39 + var key = window.event.keyCode;
  40 + if (key == 13) {
  41 + that.login();
  42 + }
  43 + }
  44 +
  45 + },
  46 + methods:{
  47 +
  48 + //登录逻辑
  49 + login(){
  50 + if(this.username!='' && this.password!=''){
  51 + this.toLogin();
  52 + }
  53 + },
  54 +
  55 + //登录请求
  56 + toLogin(){
  57 +
  58 + //一般要跟后端了解密码的加密规则
  59 + //这里例子用的哈希算法来自./js/sha1.min.js
  60 +
  61 + //需要想后端发送的登录参数
  62 + let loginParam = {
  63 + username: this.username,
  64 + password: crypto.createHash('md5').update(this.password, "utf8").digest('hex')
  65 + }
  66 + var that = this;
  67 + //设置在登录状态
  68 + this.isLoging = true;
  69 +
  70 + this.$axios.get("/auth/login",{
  71 + params: loginParam
  72 + } )
  73 + .then(function (res) {
  74 + console.log(JSON.stringify(res));
  75 + if (res.data == "success") {
  76 + that.$cookies.set("session", {"username": that.username}) ;
  77 + //登录成功后
  78 + that.$router.push('/');
  79 + }
  80 + })
  81 + .catch(function (error) {
  82 + console.log(error);
  83 + });
  84 +
  85 +
  86 +
  87 + },
  88 + setCookie: function (cname, cvalue, exdays) {
  89 + var d = new Date();
  90 + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
  91 + var expires = "expires=" + d.toUTCString();
  92 + console.info(cname + "=" + cvalue + "; " + expires);
  93 + document.cookie = cname + "=" + cvalue + "; " + expires;
  94 + console.info(document.cookie);
  95 + },
  96 + }
  97 +}
  98 +</script>
  99 +
  100 +<style scoped>
  101 +.login{position: fixed; overflow: hidden;left: 50%; margin-left: -250px; top:50%; margin-top: -350px; width: 500px; min-height: 555px; z-index: 10; right: 140px; background: #fff;-webkit-border-radius: 5px;
  102 +-moz-border-radius: 5px;
  103 +-ms-border-radius: 5px;
  104 +-o-border-radius: 5px;
  105 +border-radius: 5px; -webkit-box-shadow: 0px 3px 16px -5px #070707; box-shadow: 0px 3px 16px -5px #070707}
  106 +.log-close{display: block; position: absolute; top:12px; right: 12px; opacity: 1;}
  107 +.log-close:hover .icons{transform: rotate(180deg);}
  108 +.log-close .icons{opacity: 1; transition: all .3s}
  109 +.log-cloud{background-image: url(../assets/login-cloud.png); width: 63px ;height: 40px; position: absolute; z-index: 1}
  110 +.login .cloud1{top:21px; left: -30px; transform: scale(.6); animation: cloud1 20s linear infinite;}
  111 +.login .cloud2{top:87px; right: 20px; animation: cloud2 19s linear infinite;}
  112 +.login .cloud3{top:160px; left: 5px;transform: scale(.8);animation: cloud3 21s linear infinite;}
  113 +.login .cloud4{top:150px; left: -40px;transform: scale(.4);animation: cloud4 19s linear infinite;}
  114 +.log-bg{background: url(../assets/login-bg.jpg); width: 100%; height: 312px; overflow: hidden;}
  115 +.log-logo{height: 80px; margin: 120px auto 25px; text-align: center; color: #1fcab3; font-weight: bold; font-size: 40px;}
  116 +.log-text{color: #57d4c3; font-size: 13px; text-align: center; margin: 0 auto;}
  117 +.log-logo,.log-text{z-index: 2}
  118 +.icons{background:url(../assets/icons.png) no-repeat; display: inline-block;}
  119 +.close{height:16px;width:16px;background-position:-13px 0;}
  120 +.login-email{height:17px;width:29px;background-position:-117px 0;}
  121 +
  122 +.log-btns{padding: 15px 0; margin: 0 auto;}
  123 +.log-btn{width:402px; display: block; text-align: left; line-height: 50px;margin:0 auto 15px; height:50px; color:#fff; font-size:13px;-webkit-border-radius: 5px; background-color: #3B5999;
  124 +-moz-border-radius: 5px;
  125 +-ms-border-radius: 5px;
  126 +-o-border-radius: 5px;
  127 +border-radius: 5px;
  128 +position: relative;}
  129 +.log-btn.tw{background-color: #13B4E9}
  130 +.log-btn.email{background-color: #50E3CE}
  131 +.log-btn:hover,.log-btn:focus{color: #fff; opacity: .8;}
  132 +
  133 +.log-email{text-align: center; margin-top: 20px;}
  134 +.log-email .log-btn{background-color: #50E3CE;text-align: center;}
  135 +.log-input-empty{border: 1px solid #f37474 !important;}
  136 +.isloading{background: #d6d6d6}
  137 +.log-btn .icons{margin-left: 30px; vertical-align: middle;}
  138 +.log-btn .text{left: 95px; line-height: 50px; text-align: left; position: absolute;}
  139 +.log-input{width: 370px;overflow: hidden; padding: 0 15px;font-size: 13px; border: 1px solid #EBEBEB; margin:0 auto 15px; height: 48px; line-height: 48px; -webkit-border-radius: 5px;
  140 +-moz-border-radius: 5px;
  141 +-ms-border-radius: 5px;
  142 +-o-border-radius: 5px;
  143 +border-radius: 5px;}
  144 +.log-input.warn{border: 1px solid #f88787}
  145 +
  146 + @-webkit-keyframes cloud1 {
  147 + 0%{left: 200px}
  148 + 100%{left:-130px;}
  149 +}
  150 +@keyframes cloud1{
  151 + 0%{left: 200px}
  152 + 100%{left:-130px;}
  153 +}
  154 +
  155 + @-webkit-keyframes cloud2 {
  156 + 0%{left:500px;}
  157 + 100%{left:-90px;}
  158 +}
  159 +@keyframes cloud2{
  160 + 0%{left:500px;}
  161 + 100%{left:-90px;}
  162 +}
  163 +
  164 +@-webkit-keyframes cloud3 {
  165 + 0%{left:620px;}
  166 + 100%{left:-70px;}
  167 +}
  168 +@keyframes cloud3{
  169 + 0%{left:620px;}
  170 + 100%{left:-70px;}
  171 +}@-webkit-keyframes cloud4 {
  172 + 0%{left:100px;}
  173 + 100%{left:-70px;}
  174 +}
  175 +@keyframes cloud4{
  176 + 0%{left:100px;}
  177 + 100%{left:-70px;}
  178 +}
  179 +
  180 +</style>
... ...
web_src/src/components/UiHeader.vue 0 → 100644
  1 +<template>
  2 + <div id="UiHeader">
  3 + <el-menu router :default-active="this.$route.path" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
  4 + <el-menu-item index="/">控制台</el-menu-item>
  5 + <el-menu-item index="/videoList">设备列表</el-menu-item>
  6 + <!-- <el-menu-item index="/videoReplay">录像回看</el-menu-item> -->
  7 + <!-- <el-menu-item index="4">级联设置</el-menu-item> -->
  8 + <el-menu-item style="float: right;" @click="loginout">退出</el-menu-item>
  9 + </el-menu>
  10 + </div>
  11 +</template>
  12 +
  13 +<script>
  14 +export default {
  15 + name: "UiHeader",
  16 + methods:{
  17 +
  18 + loginout(){
  19 + // 删除cookie,回到登录页面
  20 + this.$cookies.remove("session");
  21 + this.$router.push('/login');
  22 + },
  23 + }
  24 +}
  25 +
  26 +</script>
0 27 \ No newline at end of file
... ...
web_src/src/components/channelList.vue 0 → 100644
  1 +<template>
  2 + <div id="channelList">
  3 + <el-container>
  4 +
  5 + <el-header>
  6 + <uiHeader></uiHeader>
  7 + </el-header>
  8 + <el-main>
  9 + <div style="background-color: #FFFFFF; position: relative; padding: 1rem 0.5rem 0.5rem 0.5rem; text-align: center;">
  10 + <span style="font-size: 1rem; font-weight: 500; ">通道列表({{parentChannelId ==0 ? deviceId:parentChannelId}})</span>
  11 +
  12 + </div>
  13 + <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;font-size: 14px;">
  14 + <el-button icon="el-icon-arrow-left" size="mini" style="margin-right: 1rem;" @click="showDevice">返回</el-button>
  15 + 搜索: <el-input @input="search" style="margin-right: 1rem; width: auto;" size="mini" placeholder="关键字" prefix-icon="el-icon-search" v-model="searchSrt" clearable> </el-input>
  16 +
  17 + 通道类型: <el-select size="mini" @change="search" style="margin-right: 1rem;" v-model="channelType" placeholder="请选择" default-first-option>
  18 + <el-option label="全部" value="" ></el-option>
  19 + <el-option label="设备" value="false"></el-option>
  20 + <el-option label="子目录" value="true" ></el-option>
  21 + </el-select>
  22 + 在线状态: <el-select size="mini" @change="search" v-model="online" placeholder="请选择" default-first-option>
  23 + <el-option label="全部" value=""></el-option>
  24 + <el-option label="在线" value="on"></el-option>
  25 + <el-option label="离线" value="off"></el-option>
  26 + </el-select>
  27 +
  28 + </div>
  29 + <devicePlayer ref="devicePlayer"></devicePlayer>
  30 + <!--设备列表-->
  31 + <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%">
  32 + <el-table-column prop="channelId" label="通道编号" width="210">
  33 + </el-table-column>
  34 + <el-table-column prop="name" label="通道名称" width="500">
  35 + </el-table-column>
  36 + <el-table-column prop="subCount" label="子节点数">
  37 + </el-table-column>
  38 + <el-table-column prop="ptztypeText" label="云台类型">
  39 + </el-table-column>
  40 + <el-table-column label="操作" width="240" align="center" fixed="right">
  41 + <template slot-scope="scope">
  42 + <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">预览视频</el-button>
  43 + <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看子目录</el-button>
  44 + <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
  45 + </template>
  46 + </el-table-column>
  47 + </el-table>
  48 + <el-pagination
  49 + style="float: right"
  50 + @size-change="handleSizeChange"
  51 + @current-change="currentChange"
  52 + :current-page="currentPage"
  53 + :page-size="count"
  54 + :page-sizes="[15, 20, 30, 50]"
  55 + layout="total, sizes, prev, pager, next"
  56 + :total="total">
  57 + </el-pagination>
  58 +
  59 + </el-main>
  60 + </el-container>
  61 + </div>
  62 +</template>
  63 +
  64 +<script>
  65 + import devicePlayer from './gb28181/devicePlayer.vue'
  66 + import uiHeader from './UiHeader.vue'
  67 + export default {
  68 + name: 'channelList',
  69 + components: {
  70 + devicePlayer,
  71 + uiHeader
  72 + },
  73 + data() {
  74 + return {
  75 + deviceId: this.$route.params.deviceId,
  76 + parentChannelId: this.$route.params.parentChannelId,
  77 + deviceChannelList: [],
  78 + videoComponentList: [],
  79 + currentPlayerInfo: {}, //当前播放对象
  80 + updateLooper: 0, //数据刷新轮训标志
  81 + searchSrt: "",
  82 + channelType: "",
  83 + online: "",
  84 + winHeight: window.innerHeight - 250,
  85 + currentPage: parseInt(this.$route.params.page),
  86 + count: parseInt(this.$route.params.count),
  87 + total:0,
  88 + beforeUrl:"/videoList"
  89 + };
  90 + },
  91 +
  92 + mounted() {
  93 + this.initData();
  94 + // this.updateLooper = setInterval(this.initData, 10000);
  95 + },
  96 + destroyed() {
  97 + this.$destroy('videojs');
  98 + clearTimeout(this.updateLooper);
  99 + },
  100 + methods: {
  101 + initData: function() {
  102 + if (this.parentChannelId == "" || this.parentChannelId == 0 ) {
  103 + this.getDeviceChannelList();
  104 + }else{
  105 + this.showSubchannels();
  106 + }
  107 +
  108 + },
  109 + initParam: function(){
  110 + this.deviceId= this.$route.params.deviceId;
  111 + this.parentChannelId= this.$route.params.parentChannelId;
  112 + this.currentPage= parseInt(this.$route.params.page);
  113 + this.count= parseInt(this.$route.params.count);
  114 + if (this.parentChannelId == "" || this.parentChannelId == 0 ) {
  115 + this.beforeUrl = "/videoList"
  116 + }
  117 +
  118 + },
  119 + currentChange: function(val){
  120 + var url = `/${this.$router.currentRoute.name}/${this.deviceId}/${this.parentChannelId}/${this.count}/${val}`
  121 + console.log(url)
  122 + this.$router.push(url).then(()=>{
  123 + this.initParam();
  124 + this.initData();
  125 + })
  126 + },
  127 + handleSizeChange: function(val){
  128 + var url = `/${this.$router.currentRoute.name}/${this.$router.params.deviceId}/${this.$router.params.parentChannelId}/${val}/1`
  129 + this.$router.push(url).then(()=>{
  130 + this.initParam();
  131 + this.initData();
  132 + })
  133 +
  134 + },
  135 + getDeviceChannelList: function() {
  136 + let that = this;
  137 + console.log(this.currentPage - 1)
  138 +
  139 + this.$axios.get(`/api/devices/${this.$route.params.deviceId}/channels`,{
  140 + params: {
  141 + page: that.currentPage - 1,
  142 + count: that.count,
  143 + query: that.searchSrt,
  144 + online: that.online,
  145 + channelType: that.channelType
  146 + }
  147 + } )
  148 + .then(function (res) {
  149 + console.log(res);
  150 + that.total = res.data.total;
  151 + that.deviceChannelList = res.data.data;
  152 + // 防止出现表格错位
  153 + that.$nextTick(()=>{
  154 + that.$refs.channelListTable.doLayout();
  155 + })
  156 + })
  157 + .catch(function (error) {
  158 + console.log(error);
  159 + });
  160 +
  161 + },
  162 +
  163 +
  164 + //gb28181平台对接
  165 + //刷新设备信息
  166 + refDevice: function(itemData) {
  167 + ///api/devices/{deviceId}/sync
  168 + console.log("刷新对应设备:" + itemData.deviceId);
  169 + this.$axios({
  170 + method: 'post',
  171 + url: '/api/devices/' + itemData.deviceId + '/sync'
  172 + }).then(function(res) {
  173 + // console.log("刷新设备结果:"+JSON.stringify(res));
  174 + }).catch(function(e) {
  175 + that.$message({
  176 + showClose: true,
  177 + message: '请求成功',
  178 + type: 'success'
  179 + });
  180 + });;
  181 + },
  182 + //通知设备上传媒体流
  183 + sendDevicePush: function(itemData) {
  184 + let deviceId = this.deviceId;
  185 +
  186 + let channelId = itemData.channelId;
  187 + console.log("通知设备推流1:" + deviceId + " : " + channelId);
  188 + let that = this;
  189 + this.$axios({
  190 + method: 'get',
  191 + url: '/api/play/' + deviceId + '/' + channelId
  192 + }).then(function(res) {
  193 + let ssrc = res.data.ssrc;
  194 + that.$refs.devicePlayer.play(res.data,deviceId,channelId);
  195 + }).catch(function(e) {
  196 + });
  197 + },
  198 + showDevice: function(){
  199 + this.$router.push(this.beforeUrl).then(()=>{
  200 + this.initParam();
  201 + this.initData();
  202 + })
  203 + },
  204 + changeSubchannel(itemData) {
  205 + console.log(this.$router.currentRoute)
  206 + this.beforeUrl = this.$router.currentRoute.path;
  207 +
  208 + var url = `/${this.$router.currentRoute.name}/${this.$router.currentRoute.params.deviceId}/${itemData.channelId}/${this.$router.currentRoute.params.count}/1`
  209 + this.$router.push(url).then(()=>{
  210 + this.searchSrt= "";
  211 + this.channelType= "";
  212 + this.online= "";
  213 + this.initParam();
  214 + this.initData();
  215 + })
  216 + },
  217 + showSubchannels: function(channelId){
  218 + let that = this;
  219 +
  220 + this.$axios.get(`/api/subChannels/${this.deviceId}/${this.parentChannelId}/channels`,{
  221 + params: {
  222 + page: that.currentPage - 1,
  223 + count: that.count,
  224 + query: that.searchSrt,
  225 + online: that.online,
  226 + channelType: that.channelType
  227 + }
  228 + } )
  229 + .then(function (res) {
  230 + that.total = res.data.total;
  231 + that.deviceChannelList = res.data.data;
  232 + // 防止出现表格错位
  233 + that.$nextTick(()=>{
  234 + that.$refs.channelListTable.doLayout();
  235 + })
  236 + })
  237 + .catch(function (error) {
  238 + console.log(error);
  239 + });
  240 + },
  241 + search: function() {
  242 + console.log(this.searchSrt)
  243 + this.currentPage = 1;
  244 + this.total = 0;
  245 + this.initData();
  246 + }
  247 +
  248 + }
  249 + };
  250 +</script>
  251 +
  252 +<style>
  253 + .videoList {
  254 + display: flex;
  255 + flex-wrap: wrap;
  256 + align-content: flex-start;
  257 + }
  258 +
  259 + .video-item {
  260 + position: relative;
  261 + width: 15rem;
  262 + height: 10rem;
  263 + margin-right: 1rem;
  264 + background-color: #000000;
  265 + }
  266 +
  267 + .video-item-img {
  268 + position: absolute;
  269 + top: 0;
  270 + bottom: 0;
  271 + left: 0;
  272 + right: 0;
  273 + margin: auto;
  274 + width: 100%;
  275 + height: 100%;
  276 + }
  277 +
  278 + .video-item-img:after {
  279 + content: "";
  280 + display: inline-block;
  281 + position: absolute;
  282 + z-index: 2;
  283 + top: 0;
  284 + bottom: 0;
  285 + left: 0;
  286 + right: 0;
  287 + margin: auto;
  288 + width: 3rem;
  289 + height: 3rem;
  290 + background-image: url("../assets/loading.png");
  291 + background-size: cover;
  292 + background-color: #000000;
  293 + }
  294 +
  295 + .video-item-title {
  296 + position: absolute;
  297 + bottom: 0;
  298 + color: #000000;
  299 + background-color: #ffffff;
  300 + line-height: 1.5rem;
  301 + padding: 0.3rem;
  302 + width: 14.4rem;
  303 + }
  304 +</style>
... ...
web_src/src/components/control.vue 0 → 100644
  1 +<template>
  2 + <div id="app">
  3 + <el-container>
  4 + <el-header>
  5 + <uiHeader></uiHeader>
  6 + </el-header>
  7 + <el-main>
  8 + <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
  9 + <span style="font-size: 1rem; font-weight: bold;">控制台</span>
  10 + <div style="position: absolute; right: 1rem; top: 0.3rem;">
  11 + <el-popover placement="bottom" width="750" height="300" trigger="click">
  12 + <div style="height: 600px;overflow:auto;">
  13 + <table class="table-c" cellspacing="0">
  14 + <tr v-for="(value, key, index) in serverConfig">
  15 + <td style="width: 18rem; text-align: right;">{{ key }}</td>
  16 + <td style="width: 33rem; text-align:left">{{ value }}</td>
  17 + </tr>
  18 + </table>
  19 + </div>
  20 + <el-button type="primary" slot="reference" size="mini" @click="getServerConfig()">查看服务器配置</el-button>
  21 + </el-popover>
  22 + <el-button style="margin-left: 1rem;" type="danger" size="mini" @click="reStartServer()">重启服务器</el-button>
  23 + </div>
  24 + </div>
  25 + <el-row :gutter="30">
  26 + <el-col :span="12"><div class="control-table" id="ThreadsLoad">table1</div></el-col>
  27 + <el-col :span="12"><div class="control-table" id="WorkThreadsLoad">table2</div></el-col>
  28 + </el-row>
  29 + <el-table :data="allSessionData" style="margin-top: 1rem;">
  30 + <el-table-column prop="peer_ip" label="远端"></el-table-column>
  31 + <el-table-column prop="local_ip" label="本地"></el-table-column>
  32 + <el-table-column prop="typeid" label="类型"></el-table-column>
  33 + <el-table-column align="right">
  34 + <template slot="header" slot-scope="scope">
  35 + <el-button icon="el-icon-refresh-right" circle @click="getAllSession()"></el-button>
  36 + </template>
  37 + <template slot-scope="scope">
  38 + <el-button @click.native.prevent="deleteRow(scope.$index, allSessionData)" type="text" size="small">移除</el-button>
  39 + </template>
  40 + </el-table-column>
  41 + </el-table>
  42 +
  43 + </el-main>
  44 + <!-- <el-footer style="position: absolute; bottom: 0; width: 100%;">ZLMediaKit-VUE_UI v1</el-footer> -->
  45 + </el-container>
  46 +
  47 + </div>
  48 +</template>
  49 +
  50 +<script>
  51 +
  52 +import uiHeader from './UiHeader.vue'
  53 +
  54 +import echarts from 'echarts';
  55 +export default {
  56 + name: 'app',
  57 + components: {
  58 + echarts,
  59 + uiHeader
  60 + },
  61 + data() {
  62 + return {
  63 + tableOption: {
  64 + // legend: {},
  65 + xAxis: {},
  66 + yAxis: {},
  67 + label: {},
  68 + tooltip: {},
  69 + dataZoom: [],
  70 + series: []
  71 + },
  72 + table1Option: {
  73 + // legend: {},
  74 + xAxis: {},
  75 + yAxis: {},
  76 + label: {},
  77 + tooltip: {},
  78 + series: []
  79 + },
  80 + mChart: null,
  81 + mChart1: null,
  82 + charZoomStart: 0,
  83 + charZoomEnd: 100,
  84 + chartInterval: 0, //更新图表统计图定时任务标识
  85 + allSessionData: [],
  86 + visible: false,
  87 + serverConfig: {}
  88 + };
  89 + },
  90 + mounted() {
  91 + this.getAllSession();
  92 + this.initTable();
  93 + this.updateData();
  94 + this.chartInterval = setInterval(this.updateData, 3000);
  95 + },
  96 + destroyed() {
  97 + clearInterval(this.chartInterval); //释放定时任务
  98 + },
  99 + methods: {
  100 + updateData: function() {
  101 + this.getThreadsLoad();
  102 + },
  103 + /**
  104 + * 获取线程状态
  105 + */
  106 + getThreadsLoad: function() {
  107 + let that = this;
  108 + this.$axios({
  109 + method: 'get',
  110 + url: '/zlm/index/api/getThreadsLoad'
  111 + }).then(function(res) {
  112 + if (res.data.code == 0) {
  113 + that.tableOption.xAxis.data.push(new Date().toLocaleTimeString());
  114 + that.table1Option.xAxis.data.push(new Date().toLocaleTimeString());
  115 +
  116 + for (var i = 0; i < res.data.data.length; i++) {
  117 + if (that.tableOption.series[i] === undefined) {
  118 + let data = {
  119 + data: [],
  120 + type: 'line'
  121 + };
  122 + let data1 = {
  123 + data: [],
  124 + type: 'line'
  125 + };
  126 + data.data.push(res.data.data[i].delay);
  127 + data1.data.push(res.data.data[i].load);
  128 + that.tableOption.series.push(data);
  129 + that.table1Option.series.push(data1);
  130 + } else {
  131 + that.tableOption.series[i].data.push(res.data.data[i].delay);
  132 + that.table1Option.series[i].data.push(res.data.data[i].load);
  133 + }
  134 + }
  135 + that.tableOption.dataZoom[0].start = that.charZoomStart;
  136 + that.tableOption.dataZoom[0].end = that.charZoomEnd;
  137 + that.table1Option.dataZoom[0].start = that.charZoomStart;
  138 + that.table1Option.dataZoom[0].end = that.charZoomEnd;
  139 + //that.myChart = echarts.init(document.getElementById('ThreadsLoad'));
  140 + that.myChart.setOption(that.tableOption, true);
  141 + // that.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
  142 + that.myChart1.setOption(that.table1Option, true);
  143 + }
  144 + });
  145 + },
  146 + initTable: function() {
  147 + let that = this;
  148 + this.tableOption.xAxis = {
  149 + type: 'category',
  150 + data: [], // x轴数据
  151 + name: '时间', // x轴名称
  152 + // x轴名称样式
  153 + nameTextStyle: {
  154 + fontWeight: 300,
  155 + fontSize: 15
  156 + }
  157 + };
  158 + this.tableOption.yAxis = {
  159 + type: 'value',
  160 + name: '延迟率', // y轴名称
  161 + boundaryGap: [0, '100%'],
  162 + max: 100,
  163 + axisLabel: {
  164 + show: true,
  165 + interval: 'auto',
  166 + formatter: '{value} %'
  167 + },
  168 + // y轴名称样式
  169 + nameTextStyle: {
  170 + fontWeight: 300,
  171 + fontSize: 15
  172 + }
  173 + };
  174 + this.tableOption.dataZoom = [
  175 + {
  176 + show: true,
  177 + start: this.charZoomStart,
  178 + end: this.charZoomEnd
  179 + }
  180 + ];
  181 + this.myChart = echarts.init(document.getElementById('ThreadsLoad'));
  182 + this.myChart.setOption(this.tableOption);
  183 + this.myChart.on('dataZoom', function(event) {
  184 + if (event.batch) {
  185 + that.charZoomStart = event.batch[0].start;
  186 + that.charZoomEnd = event.batch[0].end;
  187 + } else {
  188 + that.charZoomStart = event.start;
  189 + that.charZoomEnd = event.end;
  190 + }
  191 + });
  192 +
  193 + this.table1Option.xAxis = {
  194 + type: 'category',
  195 + data: [], // x轴数据
  196 + name: '时间', // x轴名称
  197 + // x轴名称样式
  198 + nameTextStyle: {
  199 + fontWeight: 300,
  200 + fontSize: 15
  201 + }
  202 + };
  203 + this.table1Option.yAxis = {
  204 + type: 'value',
  205 + name: '负载率', // y轴名称
  206 + boundaryGap: [0, '100%'],
  207 + max: 100,
  208 + axisLabel: {
  209 + show: true,
  210 + interval: 'auto',
  211 + formatter: '{value} %'
  212 + },
  213 + // y轴名称样式
  214 + nameTextStyle: {
  215 + fontWeight: 300,
  216 + fontSize: 15
  217 + }
  218 + };
  219 + this.table1Option.dataZoom = [
  220 + {
  221 + show: true,
  222 + start: this.charZoomStart,
  223 + end: this.charZoomEnd
  224 + }
  225 + ];
  226 + this.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
  227 + this.myChart1.setOption(this.table1Option);
  228 + this.myChart1.on('dataZoom', function(event) {
  229 + if (event.batch) {
  230 + that.charZoomStart = event.batch[0].start;
  231 + that.charZoomEnd = event.batch[0].end;
  232 + } else {
  233 + that.charZoomStart = event.start;
  234 + that.charZoomEnd = event.end;
  235 + }
  236 + });
  237 + },
  238 +
  239 + getAllSession: function() {
  240 + let that = this;
  241 + that.allSessionData = [];
  242 + console.log("地址:"+'/zlm/index/api/getAllSession');
  243 + this.$axios({
  244 + method: 'get',
  245 + url: '/zlm/index/api/getAllSession'
  246 + }).then(function(res) {
  247 + res.data.data.forEach(item => {
  248 + let data = {
  249 + peer_ip: item.peer_ip,
  250 + local_ip: item.local_ip,
  251 + typeid: item.typeid,
  252 + id: item.id
  253 + };
  254 + that.allSessionData.push(data);
  255 + });
  256 + });
  257 + },
  258 + getServerConfig: function() {
  259 + let that = this;
  260 + this.$axios({
  261 + method: 'get',
  262 + url: '/zlm/index/api/getServerConfig'
  263 + }).then(function(res) {
  264 + that.serverConfig = res.data.data[0];
  265 + that.visible = true;
  266 + });
  267 + },
  268 + reStartServer: function() {
  269 + let that = this;
  270 + this.$confirm('此操作将重启媒体服务器, 是否继续?', '提示', {
  271 + confirmButtonText: '确定',
  272 + cancelButtonText: '取消',
  273 + type: 'warning'
  274 + }).then(() => {
  275 + let that = this;
  276 + this.$axios({
  277 + method: 'get',
  278 + url: '/zlm/index/api/restartServer'
  279 + }).then(function(res) {
  280 + that.getAllSession();
  281 + if (res.data.code == 0) {
  282 + that.$message({
  283 + type: 'success',
  284 + message: '操作完成'
  285 + });
  286 + }
  287 + });
  288 + });
  289 + },
  290 + deleteRow: function(index, tabledata) {
  291 + let that = this;
  292 + this.$confirm('此操作将断开该通信链路, 是否继续?', '提示', {
  293 + confirmButtonText: '确定',
  294 + cancelButtonText: '取消',
  295 + type: 'warning'
  296 + })
  297 + .then(() => {
  298 + that.deleteSession(tabledata[index].id);
  299 + })
  300 + .catch(() => {
  301 + console.log('id:' + JSON.stringify(tabledata[index]));
  302 + this.$message({
  303 + type: 'info',
  304 + message: '已取消删除'
  305 + });
  306 + });
  307 + console.log(JSON.stringify(tabledata[index]));
  308 + },
  309 + deleteSession: function(id) {
  310 + let that = this;
  311 + this.$axios({
  312 + method: 'get',
  313 + url: '/zlm/index/api/kick_session&id=' + id
  314 + }).then(function(res) {
  315 + that.getAllSession();
  316 + that.$message({
  317 + type: 'success',
  318 + message: '删除成功!'
  319 + });
  320 + });
  321 + }
  322 + }
  323 +};
  324 +</script>
  325 +
  326 +<style>
  327 +#app {
  328 + height: 100%;
  329 +}
  330 +.control-table {
  331 + background-color: #ffffff;
  332 + height: 25rem;
  333 +}
  334 +.table-c {
  335 + border-right: 1px solid #dcdcdc;
  336 + border-bottom: 1px solid #dcdcdc;
  337 +}
  338 +.table-c td {
  339 + border-left: 1px solid #dcdcdc;
  340 + border-top: 1px solid #dcdcdc;
  341 + padding: 0.2rem;
  342 +}
  343 +.el-table {
  344 + width: 99.9% !important;
  345 +}
  346 +</style>
... ...
web_src/src/components/gb28181/devicePlayer.vue 0 → 100644
  1 +<template>
  2 + <div id="devicePlayer">
  3 + <el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="stop()">
  4 + <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" fluent autoplay live stretch></LivePlayer>
  5 + <div id="shared" style="text-align: right; margin-top: 1rem;">
  6 + <el-tabs v-model="tabActiveName">
  7 + <el-tab-pane label="媒体流信息" name="media">
  8 + <div style="margin-bottom: 0.5rem;">
  9 + <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>
  10 + <el-button type="primary" size="small" @click="startRecord()">录制</el-button>
  11 + <el-button type="primary" size="small" @click="stopRecord()">停止录制</el-button>
  12 + </div>
  13 + <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
  14 + <span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
  15 + <el-input v-model="getPlayerShared.sharedUrl" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedUrl)"></el-input>
  16 + </div>
  17 + <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
  18 + <span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
  19 + <el-input v-model="getPlayerShared.sharedIframe" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedIframe)"></el-input>
  20 + </div>
  21 + <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
  22 + <span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
  23 + <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedRtmp)"></el-input>
  24 + </div>
  25 + </el-tab-pane>
  26 + <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
  27 + <el-tab-pane label="录像查询" name="second">
  28 + <el-date-picker v-model="videoHistory.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间"
  29 + @change="recordList()"></el-date-picker>
  30 + <el-date-picker v-model="videoHistory.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间"
  31 + @change="recordList()"></el-date-picker>
  32 + <el-table :data="videoHistory.searchHistoryResult" style="width: 100%">
  33 + <el-table-column label="名称" prop="name" width="150"></el-table-column>
  34 + <el-table-column label="文件" prop="filePath" width="300"></el-table-column>
  35 + <el-table-column label="开始时间" prop="startTime" width="160"></el-table-column>
  36 + <el-table-column label="结束时间" prop="endTime" width="160"></el-table-column>
  37 +
  38 + <el-table-column label="操作">
  39 + <template slot-scope="scope">
  40 + <el-button type="primary" size="mini" @click="playRecord(false, scope.row)">播放</el-button>
  41 + </template>
  42 + </el-table-column>
  43 + </el-table>
  44 + </el-tab-pane>
  45 + <!--遥控界面-->
  46 + <el-tab-pane label="云台控制" name="third">
  47 + <div style="display: flex; justify-content: center;">
  48 + <div class="control-wrapper">
  49 + <div class="control-btn control-top" @mousedown="ptzCamera(0, 1, 0)" @mouseup="ptzCamera(0, 0, 0)">
  50 + <i class="el-icon-caret-top"></i>
  51 + <div class="control-inner-btn control-inner"></div>
  52 + </div>
  53 + <div class="control-btn control-left" @mousedown="ptzCamera(1, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
  54 + <i class="el-icon-caret-left"></i>
  55 + <div class="control-inner-btn control-inner"></div>
  56 + </div>
  57 + <div class="control-btn control-bottom" @mousedown="ptzCamera(0, 2, 0)" @mouseup="ptzCamera(0, 0, 0)">
  58 + <i class="el-icon-caret-bottom"></i>
  59 + <div class="control-inner-btn control-inner"></div>
  60 + </div>
  61 + <div class="control-btn control-right" @mousedown="ptzCamera(2, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
  62 + <i class="el-icon-caret-right"></i>
  63 + <div class="control-inner-btn control-inner"></div>
  64 + </div>
  65 + <div class="control-round">
  66 + <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
  67 + </div>
  68 +
  69 + <div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera(0, 0, 2)" @mouseup="ptzCamera(0, 0, 0)"><i
  70 + class="el-icon-zoom-in" style="font-size: 1.875rem;"></i></div>
  71 + <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera(0, 0, 1)"
  72 + @mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-out"></i></div>
  73 + </div>
  74 + </div>
  75 +
  76 + </el-tab-pane>
  77 + </el-tabs>
  78 + </div>
  79 + </el-dialog>
  80 + </div>
  81 +</template>
  82 +
  83 +<script>
  84 + import LivePlayer from '@liveqing/liveplayer'
  85 + export default {
  86 + name: 'devicePlayer',
  87 + props: {},
  88 + components: {
  89 + LivePlayer
  90 + },
  91 + computed: {
  92 + getPlayerShared: function() {
  93 + return {
  94 + sharedUrl: window.location.host + '/' + this.videoUrl,
  95 + sharedIframe: '<iframe src="' + window.location.host + '/' + this.videoUrl + '"></iframe>',
  96 + sharedRtmp: this.videoUrl
  97 + };
  98 + }
  99 + },
  100 + created() {
  101 + // this.videoHistory.searchHistoryResult = falsificationData.recordData.recordList;
  102 + },
  103 + data() {
  104 + return {
  105 + video:'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
  106 + videoUrl: '',
  107 + videoHistory: {
  108 + startTime: '',
  109 + endTime: '',
  110 + searchHistoryResult: [] //媒体流历史记录搜索结果
  111 + },
  112 + showVideoDialog: false,
  113 + normalssrc: '',
  114 + ssrc: '',
  115 + deviceId: '',
  116 + channelId: '',
  117 + tabActiveName: 'media'
  118 + };
  119 + },
  120 + methods: {
  121 +
  122 + play: function(streamInfo, deviceId, channelId) {
  123 + this.ssrc = streamInfo.ssrc;
  124 + this.deviceId = deviceId;
  125 + this.channelId = channelId;
  126 + this.videoUrl = streamInfo.flv + "?" + new Date().getTime();
  127 + this.showVideoDialog = true;
  128 + console.log(this.ssrc);
  129 + },
  130 + stop: function() {
  131 + console.log('关闭视频');
  132 + this.$refs.videoPlayer.pause();
  133 + this.videoUrl = '';
  134 + this.showVideoDialog = false;
  135 + this.$axios({
  136 + method: 'post',
  137 + url: '/api/play/' + this.ssrc + '/stop'
  138 + }).then(function(res) {
  139 + console.log(JSON.stringify(res));
  140 + });
  141 +
  142 + this.$axios({
  143 + method: 'post',
  144 + url: '/api/playback/' + this.ssrc + '/stop'
  145 + }).then(function(res) {
  146 + console.log(JSON.stringify(res));
  147 + });
  148 + },
  149 + copySharedInfo: function(data) {
  150 + console.log('复制内容:' + data);
  151 + let _this = this;
  152 + this.$copyText(data).then(
  153 + function(e) {
  154 + _this.$message({
  155 + showClose: true,
  156 + message: '复制成功',
  157 + type: 'success'
  158 + });
  159 + },
  160 + function(e) {
  161 + _this.$message({
  162 + showClose: true,
  163 + message: '复制失败,请手动复制',
  164 + type: 'error'
  165 + });
  166 + }
  167 + );
  168 + },
  169 +
  170 + recordList: function() {
  171 + if (!this.videoHistory.startTime || !this.videoHistory.endTime) {
  172 + return;
  173 + }
  174 + let that = this;
  175 + this.$axios({
  176 + method: 'get',
  177 + url: '/api/record/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.videoHistory
  178 + .startTime + '&endTime=' + this.videoHistory.endTime
  179 + }).then(function(res) {
  180 + console.log(JSON.stringify(res));
  181 + }).catch(function(e) {
  182 + // that.videoHistory.searchHistoryResult = falsificationData.recordData;
  183 + });
  184 +
  185 + },
  186 + playRecord: function(isBackLive, rowData) {
  187 + let that = this;
  188 + if(isBackLive){
  189 + this.videoUrl=this.getVideoUrlBySsrc(this.normalssrc);
  190 + return;
  191 + }
  192 + this.$axios({
  193 + method: 'get',
  194 + url: '/api/playback/' + this.deviceId + '/' + this.channelId + '?startTime=' + rowData.startTime + '&endTime=' +
  195 + rowData.endTime
  196 + }).then(function(res) {
  197 + let ssrc = res.data.ssrc;
  198 + that.videoUrl = that.getVideoUrlBySsrc(ssrc);
  199 + //that.videoUrl='http://hls.cntv.kcdnvip.com/asp/hls/main/0303000a/3/default/f466089412c04a759c5515dbfcc3ac3d/main.m3u8?maxbr=2048';
  200 + });
  201 +
  202 + },
  203 + ptzCamera: function(leftRight, upDown, zoom) {
  204 + console.log('云台控制:' + leftRight + ' : ' + upDown + " : " + zoom);
  205 + let that = this;
  206 + this.$axios({
  207 + method: 'post',
  208 + url: '/api/ptz/' + this.deviceId + '/' + this.channelId + '?leftRight=' + leftRight + '&upDown=' + upDown +
  209 + '&inOut=' + zoom + '&moveSpeed=50&zoomSpeed=50'
  210 + }).then(function(res) {});
  211 + },
  212 + //////////////////////播放器事件处理//////////////////////////
  213 + videoError:function(e){
  214 + console.log("播放器错误:"+JSON.stringify(e));
  215 + }
  216 + }
  217 + };
  218 +</script>
  219 +
  220 +<style>
  221 + .control-wrapper {
  222 + position: relative;
  223 + width: 6.25rem;
  224 + height: 6.25rem;
  225 + max-width: 6.25rem;
  226 + max-height: 6.25rem;
  227 + margin: 0 auto;
  228 + border-radius: 100%;
  229 + float: left;
  230 + }
  231 +
  232 + .control-btn {
  233 + display: flex;
  234 + justify-content: center;
  235 + position: absolute;
  236 + width: 44%;
  237 + height: 44%;
  238 + border-radius: 5px;
  239 + border: 1px solid #78aee4;
  240 + box-sizing: border-box;
  241 + transition: all 0.3s linear;
  242 + }
  243 +
  244 + .control-btn i {
  245 + font-size: 20px;
  246 + color: #78aee4;
  247 + display: flex;
  248 + justify-content: center;
  249 + align-items: center;
  250 + }
  251 +
  252 + .control-round {
  253 + position: absolute;
  254 + top: 21%;
  255 + left: 21%;
  256 + width: 58%;
  257 + height: 58%;
  258 + background: #fff;
  259 + border-radius: 100%;
  260 + }
  261 +
  262 + .control-round-inner {
  263 + position: absolute;
  264 + left: 15%;
  265 + top: 15%;
  266 + display: flex;
  267 + justify-content: center;
  268 + align-items: center;
  269 + width: 70%;
  270 + height: 70%;
  271 + font-size: 40px;
  272 + color: #78aee4;
  273 + border: 1px solid #78aee4;
  274 + border-radius: 100%;
  275 + transition: all 0.3s linear;
  276 + }
  277 +
  278 + .control-inner-btn {
  279 + position: absolute;
  280 + width: 60%;
  281 + height: 60%;
  282 + background: #fafafa;
  283 + }
  284 +
  285 + .control-top {
  286 + top: -8%;
  287 + left: 27%;
  288 + transform: rotate(-45deg);
  289 + border-radius: 5px 100% 5px 0;
  290 + }
  291 +
  292 + .control-top i {
  293 + transform: rotate(45deg);
  294 + border-radius: 5px 100% 5px 0;
  295 + }
  296 +
  297 + .control-top .control-inner {
  298 + left: -1px;
  299 + bottom: 0;
  300 + border-top: 1px solid #78aee4;
  301 + border-right: 1px solid #78aee4;
  302 + border-radius: 0 100% 0 0;
  303 + }
  304 +
  305 + .control-top .fa {
  306 + transform: rotate(45deg) translateY(-7px);
  307 + }
  308 +
  309 + .control-left {
  310 + top: 27%;
  311 + left: -8%;
  312 + transform: rotate(45deg);
  313 + border-radius: 5px 0 5px 100%;
  314 + }
  315 +
  316 + .control-left i {
  317 + transform: rotate(-45deg);
  318 + }
  319 +
  320 + .control-left .control-inner {
  321 + right: -1px;
  322 + top: -1px;
  323 + border-bottom: 1px solid #78aee4;
  324 + border-left: 1px solid #78aee4;
  325 + border-radius: 0 0 0 100%;
  326 + }
  327 +
  328 + .control-left .fa {
  329 + transform: rotate(-45deg) translateX(-7px);
  330 + }
  331 +
  332 + .control-right {
  333 + top: 27%;
  334 + right: -8%;
  335 + transform: rotate(45deg);
  336 + border-radius: 5px 100% 5px 0;
  337 + }
  338 +
  339 + .control-right i {
  340 + transform: rotate(-45deg);
  341 + }
  342 +
  343 + .control-right .control-inner {
  344 + left: -1px;
  345 + bottom: -1px;
  346 + border-top: 1px solid #78aee4;
  347 + border-right: 1px solid #78aee4;
  348 + border-radius: 0 100% 0 0;
  349 + }
  350 +
  351 + .control-right .fa {
  352 + transform: rotate(-45deg) translateX(7px);
  353 + }
  354 +
  355 + .control-bottom {
  356 + left: 27%;
  357 + bottom: -8%;
  358 + transform: rotate(45deg);
  359 + border-radius: 0 5px 100% 5px;
  360 + }
  361 +
  362 + .control-bottom i {
  363 + transform: rotate(-45deg);
  364 + }
  365 +
  366 + .control-bottom .control-inner {
  367 + top: -1px;
  368 + left: -1px;
  369 + border-bottom: 1px solid #78aee4;
  370 + border-right: 1px solid #78aee4;
  371 + border-radius: 0 0 100% 0;
  372 + }
  373 +
  374 + .control-bottom .fa {
  375 + transform: rotate(-45deg) translateY(7px);
  376 + }
  377 +</style>
... ...
web_src/src/components/videoList.vue 0 → 100644
  1 +<template>
  2 + <div id="app">
  3 + <el-container>
  4 +
  5 + <el-header>
  6 + <uiHeader></uiHeader>
  7 + </el-header>
  8 + <el-main>
  9 + <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
  10 + <span style="font-size: 1rem; font-weight: bold;">设备列表</span>
  11 + <div style="position: absolute; right: 1rem; top: 0.3rem;">
  12 + <el-button icon="el-icon-refresh-right" circle size="mini" @click="getDeviceList()"></el-button>
  13 + </div>
  14 + </div>
  15 + <devicePlayer ref="devicePlayer"></devicePlayer>
  16 + <!--设备列表-->
  17 + <el-table :data="deviceList" border style="width: 100%" :height="winHeight">
  18 + <el-table-column prop="name" label="名称" width="180" align="center">
  19 + </el-table-column>
  20 + <el-table-column prop="deviceId" label="设备编号" width="240" align="center">
  21 + </el-table-column>
  22 + <el-table-column label="地址" width="180" align="center">
  23 + <template slot-scope="scope">
  24 + <div slot="reference" class="name-wrapper">
  25 + <el-tag size="medium">{{ scope.row.host.address }}</el-tag>
  26 + </div>
  27 + </template>
  28 + </el-table-column>
  29 + <el-table-column prop="manufacturer" label="厂家" align="center">
  30 + </el-table-column>
  31 + <el-table-column prop="model" label="固件版本" align="center">
  32 + </el-table-column>
  33 + <el-table-column prop="transport" label="通讯方式" align="center">
  34 + </el-table-column>
  35 + <el-table-column prop="channelCount" label="通道数" align="center">
  36 + </el-table-column>
  37 + <el-table-column label="状态" width="180" align="center">
  38 + <template slot-scope="scope">
  39 + <div slot="reference" class="name-wrapper">
  40 + <el-tag size="medium">{{ scope.row.online==1?'在线' :'离线'}}</el-tag>
  41 + </div>
  42 + </template>
  43 + </el-table-column>
  44 +
  45 + <el-table-column label="操作" width="240" align="center" fixed="right">
  46 + <template slot-scope="scope">
  47 + <el-button size="mini" icon="el-icon-refresh" @click="refDevice(scope.row)">刷新</el-button>
  48 + <el-button size="mini" icon="el-icon-s-open" type="primary" @click="showChannelList(scope.row)">查看通道</el-button>
  49 + </template>
  50 + </el-table-column>
  51 + </el-table>
  52 + <el-pagination
  53 + style="float: right"
  54 + @size-change="handleSizeChange"
  55 + @current-change="currentChange"
  56 + :current-page="currentPage"
  57 + :page-size="count"
  58 + :page-sizes="[15, 25, 35, 50]"
  59 + layout="total, sizes, prev, pager, next"
  60 + :total="total">
  61 + </el-pagination>
  62 +
  63 + </el-main>
  64 + </el-container>
  65 + </div>
  66 +</template>
  67 +
  68 +<script>
  69 + import devicePlayer from './gb28181/devicePlayer.vue'
  70 + import uiHeader from './UiHeader.vue'
  71 + export default {
  72 + name: 'app',
  73 + components: {
  74 + devicePlayer,
  75 + uiHeader
  76 + },
  77 + data() {
  78 + return {
  79 + deviceList: [], //设备列表
  80 + currentDevice: {}, //当前操作设备对象
  81 +
  82 + videoComponentList: [],
  83 + updateLooper: 0, //数据刷新轮训标志
  84 + currentDeviceChannelsLenth:0,
  85 + winHeight: window.innerHeight - 200,
  86 + currentPage:1,
  87 + count:15,
  88 + total:0
  89 + };
  90 + },
  91 + computed: {
  92 + getcurrentDeviceChannels: function() {
  93 + let data = this.currentDevice['channelMap'];
  94 + let channels = null;
  95 + if (data) {
  96 + channels = Object.keys(data).map(key => {
  97 + return data[key];
  98 + });
  99 + this.currentDeviceChannelsLenth = channels.length;
  100 + }
  101 +
  102 + console.log("数据:" + JSON.stringify(channels));
  103 + return channels;
  104 + }
  105 + },
  106 + mounted() {
  107 + this.initData();
  108 + this.updateLooper = setInterval(this.initData, 10000);
  109 + },
  110 + destroyed() {
  111 + this.$destroy('videojs');
  112 + clearTimeout(this.updateLooper);
  113 + },
  114 + methods: {
  115 + initData: function() {
  116 + this.getDeviceList();
  117 + },
  118 + currentChange: function(val){
  119 + this.currentPage = val;
  120 + this.getDeviceList();
  121 + },
  122 + handleSizeChange: function(val){
  123 + this.count = val;
  124 + this.getDeviceList();
  125 + },
  126 + getDeviceList: function() {
  127 + let that = this;
  128 +
  129 + this.$axios.get(`/api/devices`,{
  130 + params: {
  131 + page: that.currentPage - 1,
  132 + count: that.count
  133 + }
  134 + } )
  135 + .then(function (res) {
  136 + console.log(res);
  137 + that.total = res.data.total;
  138 + that.deviceList = res.data.data;
  139 + })
  140 + .catch(function (error) {
  141 + console.log(error);
  142 + });
  143 +
  144 + },
  145 + showChannelList: function(row) {
  146 + console.log(JSON.stringify(row))
  147 + this.$router.push(`/channelList/${row.deviceId}/0/15/1`);
  148 + },
  149 +
  150 +
  151 + //gb28181平台对接
  152 + //刷新设备信息
  153 + refDevice: function(itemData) {
  154 + ///api/devices/{deviceId}/sync
  155 + console.log("刷新对应设备:" + itemData.deviceId);
  156 + this.$axios({
  157 + method: 'post',
  158 + url: '/api/devices/' + itemData.deviceId + '/sync'
  159 + }).then(function(res) {
  160 + // console.log("刷新设备结果:"+JSON.stringify(res));
  161 + }).catch(function(e) {
  162 + that.$message({
  163 + showClose: true,
  164 + message: '请求成功',
  165 + type: 'success'
  166 + });
  167 + });;
  168 + },
  169 + //通知设备上传媒体流
  170 + sendDevicePush: function(itemData) {
  171 + let deviceId = this.currentDevice.deviceId;
  172 + let channelId = itemData.channelId;
  173 + console.log("通知设备推流1:" + deviceId + " : " + channelId);
  174 + let that = this;
  175 + this.$axios({
  176 + method: 'get',
  177 + url: '/api/play/' + deviceId + '/' + channelId
  178 + }).then(function(res) {
  179 + let ssrc = res.data.ssrc;
  180 + that.$refs.devicePlayer.play(ssrc,deviceId,channelId);
  181 + }).catch(function(e) {
  182 + });
  183 + }
  184 +
  185 + }
  186 + };
  187 +</script>
  188 +
  189 +<style>
  190 + .videoList {
  191 + display: flex;
  192 + flex-wrap: wrap;
  193 + align-content: flex-start;
  194 + }
  195 +
  196 + .video-item {
  197 + position: relative;
  198 + width: 15rem;
  199 + height: 10rem;
  200 + margin-right: 1rem;
  201 + background-color: #000000;
  202 + }
  203 +
  204 + .video-item-img {
  205 + position: absolute;
  206 + top: 0;
  207 + bottom: 0;
  208 + left: 0;
  209 + right: 0;
  210 + margin: auto;
  211 + width: 100%;
  212 + height: 100%;
  213 + }
  214 +
  215 + .video-item-img:after {
  216 + content: "";
  217 + display: inline-block;
  218 + position: absolute;
  219 + z-index: 2;
  220 + top: 0;
  221 + bottom: 0;
  222 + left: 0;
  223 + right: 0;
  224 + margin: auto;
  225 + width: 3rem;
  226 + height: 3rem;
  227 + background-image: url("../assets/loading.png");
  228 + background-size: cover;
  229 + background-color: #000000;
  230 + }
  231 +
  232 + .video-item-title {
  233 + position: absolute;
  234 + bottom: 0;
  235 + color: #000000;
  236 + background-color: #ffffff;
  237 + line-height: 1.5rem;
  238 + padding: 0.3rem;
  239 + width: 14.4rem;
  240 + }
  241 +</style>
... ...
web_src/src/main.js 0 → 100644
  1 +import Vue from 'vue';
  2 +import App from './App.vue';
  3 +Vue.config.productionTip = false;
  4 +import ElementUI from 'element-ui';
  5 +import 'element-ui/lib/theme-chalk/index.css';
  6 +import router from './router/index.js';
  7 +import axios from 'axios';
  8 +import VueCookies from 'vue-cookies';
  9 +
  10 +import echarts from 'echarts';
  11 +import VueClipboard from 'vue-clipboard2'
  12 +Vue.use(VueClipboard)
  13 +Vue.use(ElementUI);
  14 +Vue.use(VueCookies);
  15 +Vue.prototype.$axios = axios;
  16 +
  17 +axios.defaults.baseURL = (process.env.NODE_ENV === 'development') ? process.env.BASE_API : "";
  18 +
  19 +Vue.prototype.$cookies.config(60*30);
  20 +
  21 +
  22 +new Vue({
  23 + router: router,
  24 + render: h => h(App),
  25 +}).$mount('#app')
... ...