123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792 |
- <template>
- <div class="box box-primary">
- <div class="box-header">
- <h4 class="text-primary text-center">设备树</h4>
- </div>
- <div class="box-body">
- <form class="form-inline" autocomplete="off" spellcheck="false">
- <div class="form-group form-group-sm">
- <label>搜索</label>
- <input type="text" class="form-control" placeholder="关键字" v-model.trim="q" @keydown.enter.prevent ref="q">
- </div>
- <span class="hidden-xs"> </span>
- <div class="form-group pull-right">
- <router-link :to="`/devices/1`" class="btn btn-default btn-sm">
- <i class="fa fa-table"></i> 列表视图
- </router-link>
- </div>
- </form>
- <br>
- <div class="clearfix"></div>
- <div class="content">
- <div :class="[{'col-md-3': hasAnyRole(serverInfo, userInfo, '管理员', '操作员'), 'col-md-4': !hasAnyRole(serverInfo, userInfo, '管理员', '操作员')}]">
- <ul class="nav nav-tabs">
- <li class="active"><a href="#dev-tree-wrapper" data-toggle="tab">设备树</a></li>
- <li><a href="#group-tree-wrapper" data-toggle="tab">分 组</a></li>
- </ul>
- <div class="tab-content" style="margin: 10px 0;" id="tab-tree-wrapper">
- <div class="tab-pane active" ref="devTreeWrapper" id="dev-tree-wrapper">
- <el-tree ref="devTree" id="dev-tree" node-key="key" v-if="showTree" :style="`${isMobile() ? 'max-height:200px;' : ''};min-height:200px;overflow:auto;`"
- :props="treeProps" :load="treeLoad" :filter-node-method="treeFilter" lazy
- @node-click="treeNodeClick" @node-contextmenu="treeNodeRightClick" >
- <span class="custom-tree-node" slot-scope="{node, data}">
- <span :class="{'text-green': treeLeaf(data) && data.status === 'ON'}">
- <i :class="['fa', {
- 'fa-sitemap' : !treeLeaf(data),
- 'fa-camera': treeLeaf(data)
- }]"></i>
- <span class="ellipsis" :title="node.label">{{node.label}}</span>
- </span>
- </span>
- </el-tree>
- </div>
- <div class="tab-pane" ref="groupTreeWrapper" id="group-tree-wrapper">
- <el-tree ref="groupTree" id="group-tree" node-key="key" v-if="showGroupTree" :style="`${isMobile() ? 'max-height:200px;' : ''};min-height:200px;overflow:auto;`"
- :props="treeProps" :load="groupTreeLoad" :filter-node-method="treeFilter" lazy
- @node-click="treeNodeClick" @node-contextmenu="treeNodeRightClick" >
- <span class="custom-tree-node" slot-scope="{node, data}">
- <span :class="{'text-green': data.status === 'ON' && data.subCount === 0 && data.code && data.serial && !data.custom}">
- <i :class="['fa', {
- 'fa-home': !data.code,
- 'fa-sitemap' : data.code && !treeLeaf(data),
- 'fa-camera': treeLeaf(data)
- }]"></i>
- <span class="ellipsis" :title="node.label">{{node.label}}</span>
- </span>
- </span>
- </el-tree>
- </div>
- </div>
- </div>
- <VueContextMenu class="right-menu" :target="contextMenuTarget" :show="contextMenuVisible" @update:show="(show) => contextMenuVisible = show">
- <a href="javascript:;" @click="treeRefresh" v-show="contextMenuNodeData">
- <i class="fa fa-refresh"></i> 刷新节点
- </a>
- <a href="javascript:;" @click="showNodeAddDlg" v-show="contextMenuNodeData && contextMenuNodeData.custom">
- <i class="fa fa-plus"></i> 新建节点
- </a>
- <a href="javascript:;" @click="showCustomListDlg" v-show="contextMenuNodeData && contextMenuNodeData.custom && contextMenuNodeData.code">
- <i class="fa fa-check"></i> 选择通道
- </a>
- <a href="javascript:;" @click="showNodeEditDlg" v-show="contextMenuNodeData && !(contextMenuNodeData.custom && !contextMenuNodeData.code)">
- <i class="fa fa-edit"></i> 编辑节点
- </a>
- <a href="javascript:;" @click="removeCustomNode" v-show="contextMenuNodeData && contextMenuNodeData.custom && contextMenuNodeData.code">
- <i class="fa fa-remove"></i> 删除节点
- </a>
- </VueContextMenu>
- <div id="dev-tree-right" :class="['row', {'col-md-9': hasAnyRole(serverInfo, userInfo, '管理员', '操作员'), 'col-md-8': !hasAnyRole(serverInfo, userInfo, '管理员', '操作员')}]" data-sticky-wrap data-sticky-for="1000" data-sticky-class="sticky">
- <div class="col-md-9 col-lg-10" id="dev-tree-player">
- <div class="view-list row">
- <div class="video-show">
- <div>
- <div class="no-margin no-padding video col-sm-6" @click="clickPlayer(player, index, $event)" :class="{'active': index == playerIdx, 'col-sm-12': playersLength == 1,'col-sm-6': playersLength == 4,'col-sm-4': playersLength == 9,'col-sm-3': playersLength == 16}" v-for="(player,index) in players" :key="index" @mousemove="resetCloseTimer(player)" @touchstart="resetCloseTimer(player)">
- <LivePlayer :videoUrl="player.url" :poster="player.poster" live muted stretch v-loading="player.bLoading" element-loading-text="加载中..." element-loading-background="#000" :loading.sync="player.bLoading" @message="$message"></LivePlayer>
- <div class="video-close" v-show="player.url && player.bCloseShow" v-on:click="closeVideo(player)">关闭</div>
- </div>
- </div>
- </div>
- </div>
- <!-- <div class="no-margin no-padding video" @mousemove="resetCloseTimer()" @touchstart="resetCloseTimer()">
- <LivePlayer :videoUrl="player.url" :poster="player.poster" live muted stretch v-loading="player.bLoading" element-loading-text="加载中..." element-loading-background="#000" @message="$message"></LivePlayer>
- <div class="video-close" v-show="player.url && player.bCloseShow" v-on:click="closeVideo()">关闭</div>
- </div> -->
- <br>
- <br>
- </div>
- <div class="col-md-3 col-lg-2" id="dev-tree-ptz" v-if="hasAnyRole(serverInfo, userInfo, '管理员', '操作员')">
- <div class="ptz-block">
- <div class="ptz-cell ptz-up" :class="{'readonly': !playing }" command="up" title="上" @mousedown.prevent="ptzControl">
- <i class="fa fa-chevron-up"></i>
- </div>
- <div class="ptz-cell ptz-left" :class="{'readonly': !playing }" command="left" title="左" @mousedown.prevent="ptzControl">
- <i class="fa fa-chevron-left"></i>
- </div>
- <div class="ptz-cell ptz-center" title="云台控制">
- <i class="fa fa-microphone" :class="{'readonly': !playing }" title="按住喊话" @mousedown.prevent="talkStart" v-if="canTalk() && serverInfo.VersionType == '旗舰版'"></i>
- <i class="fa fa-microphone-slash" :class="{'readonly': !playing }" title="由于浏览器安全策略, 非 HTTPS 或 localhost 访问, 对讲不可用" v-if="!canTalk() && serverInfo.VersionType == '旗舰版'"></i>
- </div>
- <div class="ptz-cell ptz-right" :class="{'readonly': !playing }" command="right" title="右" @mousedown.prevent="ptzControl">
- <i class="fa fa-chevron-right"></i>
- </div>
- <div class="ptz-cell ptz-down" :class="{'readonly': !playing }" command="down" title="下" @mousedown.prevent="ptzControl">
- <i class="fa fa-chevron-down"></i>
- </div>
- <div class="ptz-cell ptz-zoomin" :class="{'readonly': !playing }" command="zoomin" title="放大" @mousedown.prevent="ptzControl">
- <i class="fa fa-plus"></i>
- </div>
- <div class="ptz-cell ptz-zoomout" :class="{'readonly': !playing }" command="zoomout" title="缩小" @mousedown.prevent="ptzControl">
- <i class="fa fa-minus"></i>
- </div>
- </div>
- </div>
- </div>
- <div class="clearfix"></div>
- <div class="text-center text-gray" v-if="isDemoUser(serverInfo, userInfo)">
- <br>
- 提示: 演示系统限制匿名登录播放时间, 若需测试长时间播放, 请<a target="_blank" href="//www.liveqing.com/docs/download/LiveGBS.html">下载使用</a>
- </div>
- <br>
- </div>
- </div>
- <!-- <div class="box-footer">
- </div> -->
- <DeviceTreeNodeEditDlg ref="nodeEditDlg" @submit="treeRefresh" style="z-index:2001;"></DeviceTreeNodeEditDlg>
- <ChannelCustomListDlg ref="customListDlg" @hide="treeRefresh" style="z-index:2001;" size="modal-lg" :title="customListDlgTitle"></ChannelCustomListDlg>
- <!-- <resize-observer @notify="resetTreeMaxHeight"/> -->
- </div>
- </template>
- <script>
- import Sticky from "sticky-js";
- import _ from "lodash";
- import url from "url";
- import { mapState } from "vuex"
- import LivePlayer from '@liveqing/liveplayer'
- import { component as VueContextMenu } from '@xunlei/vue-context-menu'
- import DeviceTreeNodeEditDlg from 'components/DeviceTreeNodeEditDlg.vue'
- import ChannelCustomListDlg from 'components/ChannelCustomListDlg.vue'
- export default {
- props: {},
- data() {
- return {
- q: "",
- loading: false,
- showTree: true,
- showGroupTree: true,
- playerIdx: 0,
- playersLength: 4,
- players: [],
- pnode: null,
- contextMenuTarget: null,
- contextMenuVisible: false,
- contextMenuNodeData: null,
- treeProps: {
- label: (data, node) => {
- node.serial = data.serial;
- node.code = data.code;
- var label = (data.customName || data.name || data.id);
- if(!this.treeLeaf(data) && data.subCount > 0) {
- label += ` [${data.onlineSubCount}/${data.subCount}]`;
- }
- return label;
- },
- isLeaf: (data, node) => {
- return this.treeLeaf(data);
- },
- disabled: (data, node) => {
- return this.treeLeaf(data) && data.status != "ON";
- }
- },
- customListDlgTitle: "选择通道",
- sticky: null,
- };
- },
- components: {
- LivePlayer, VueContextMenu, DeviceTreeNodeEditDlg, ChannelCustomListDlg
- },
- computed: {
- ...mapState(['userInfo', 'serverInfo']),
- playing() {
- var player = this.players[this.playerIdx]||{};
- return !!player.url;
- },
- canPTZ() {
- var player = this.players[this.playerIdx]||{};
- return !!player.url && (player.ptzType === 0 || player.ptzType === 1);
- }
- },
- mounted() {
- this.setPlayersLength(this.playersLength);
- this.contextMenuTarget = document.querySelector('#tab-tree-wrapper');
- $(document).on("mouseup touchend", this.ctrlStop);
- $(document).on('expanded.pushMenu collapsed.pushMenu', this.updateSticky);
- this.sticky = new Sticky("#dev-tree-right");
- },
- beforeDestroy() {
- this.ctrlStop();
- $(document).off("mouseup touchend", this.ctrlStop);
- $(document).off('expanded.pushMenu collapsed.pushMenu', this.updateSticky);
- this.clearVideos();
- if (this.sticky) {
- this.sticky.destroy();
- this.sticky = null;
- }
- },
- beforeRouteLeave(to, from, next) {
- this.ctrlStop();
- this.clearVideos();
- next();
- },
- beforeRouteUpdate(to, from, next) {
- this.ctrlStop();
- this.clearVideos();
- next();
- },
- watch: {
- q: function(newVal, oldVal) {
- this.$refs['devTree'].filter(newVal);
- this.$refs['groupTree'].filter(newVal);
- },
- },
- methods: {
- treeLoad(data, resolve) {
- var serial = data.serial||"";
- var pcode = data.code||"";
- $.get("/api/v1/device/channeltree", {
- serial: serial,
- pcode: pcode
- }).then(ret => {
- resolve((ret||[]).map(v => {
- return Object.assign(v, {
- key: v.id,
- })
- }));
- this.$refs['devTree'].filter(this.q);
- })
- },
- groupTreeLoad(data, resolve) {
- var serial = data.serial||"";
- var pcode = data.code||"";
- $.get("/api/v1/device/grouptree", {
- serial: serial,
- pcode: pcode
- }).then(ret => {
- resolve((ret||[]).map(v => {
- return Object.assign(v, {
- key: v.id,
- })
- }));
- this.$refs['groupTree'].filter(this.q);
- })
- },
- treeFilter(value, data) {
- if (!value) return true;
- return data.name.indexOf(value) !== -1 || data.customName.indexOf(value) !== -1 || data.id.indexOf(value) !== -1;
- },
- treeNodeRefresh(key) {
- let node = this.$refs['devTree'].getNode(key);
- if(!node) return;
- node.loaded = false;
- node.expand();
- },
- treeNodeRightClick(event, data, node, c) {
- if(!this.hasAnyRole(this.serverInfo, this.userInfo, '管理员')) return;
- this.contextMenuNodeData = data;
- this.pnode = node.parent;
- var new_event;
- if(typeof(MouseEvent) === 'function') {
- new_event = new MouseEvent(event.type, event);
- } else {
- new_event = document.createEvent('MouseEvents');
- new_event.initMouseEvent(event.type, true, true, event.view, event.detail,
- event.screenX, event.screenY, event.clientX, event.clientY,
- event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, event.button, event);
- }
- this.contextMenuTarget.dispatchEvent(new_event);
- },
- treeNodeClick(data, node, c) {
- this.contextMenuNodeData = null;
- if(this.treeLeaf(data) && data.status === "ON") {
- var player = this.players[this.playerIdx]||{};
- if(player.bLoading) return;
- this.closeVideo(player);
- player.serial = data.serial;
- player.code = data.code;
- player.ptzType = data.ptzType;
- player.bLoading = true;
- $.get("/api/v1/stream/start", {
- serial: data.serial,
- code: data.code,
- }).then(streamInfo => {
- streamInfo = streamInfo || {};
- var videoUrl = this.isMobile() ? streamInfo.HLS : streamInfo.RTMP;
- var protocol = this.isMobile() ? "HLS" : "RTMP";
- if(this.flvSupported()) {
- if(streamInfo.WS_FLV) {
- videoUrl = streamInfo.WS_FLV;
- protocol = "WS_FLV";
- } else if(streamInfo.FLV) {
- videoUrl = streamInfo.FLV;
- protocol = "FLV";
- }
- }
- player.protocol = protocol;
- player.poster = protocol == "RTMP" ? "" : streamInfo.SnapURL;
- this.$nextTick(() => {
- player.url = videoUrl || "";
- });
- }).always(() => {
- player.bLoading = false;
- })
- this.setPlayerIdx(this.playerIdx + 1);
- }
- },
- showNodeEditDlg() {
- this.contextMenuVisible = false;
- var data = Object.assign({}, this.contextMenuNodeData, { parent: this.getParentData(), add: false});
- this.$refs['nodeEditDlg'].show(data);
- },
- showNodeAddDlg() {
- this.contextMenuVisible = false;
- var data = Object.assign({
- serial: this.contextMenuNodeData.serial||'',
- code: '',
- name: '',
- customName: '',
- }, { parent: this.contextMenuNodeData, add: true});
- this.$refs['nodeEditDlg'].show(data);
- },
- showCustomListDlg() {
- this.contextMenuVisible = false;
- this.customListDlgTitle = `选择通道(${this.contextMenuNodeData.customName || this.contextMenuNodeData.name || this.contextMenuNodeData.code})`;
- this.$refs['customListDlg'].show(this.contextMenuNodeData.code);
- },
- removeCustomNode() {
- this.contextMenuVisible = false;
- if(!this.contextMenuNodeData) return;
- var name = this.contextMenuNodeData.customName || this.contextMenuNodeData.name || this.contextMenuNodeData.id;
- this.$confirm(`确认删除 ${name}`, "提示").then(() => {
- $.get("/api/v1/channel/remove", {
- serial: this.contextMenuNodeData.serial,
- code: this.contextMenuNodeData.code,
- }).always(() => {
- this.treeNodeRefresh(this.pnode);
- });
- }).catch(() => {});
- },
- treeRefresh() {
- this.contextMenuVisible = false;
- if(this.pnode) {
- this.treeNodeRefresh(this.pnode);
- return;
- }
- this.showTree = false;
- this.$nextTick(() => {
- this.showTree = true;
- })
- },
- treeLeaf(data) {
- if (data && data.subCount == 0 && data.serial && data.code && !data.custom) {
- if (!data.parental || data.manufacturer != 'LiveQing') {
- if (data.code.length != 20 || data.code.substring(10, 13) != "216" || data.code.substring(10, 13) != "215") {
- return true;
- }
- }
- }
- return false;
- },
- getParentData() {
- if(!this.contextMenuNodeData) return null;
- if(!this.$refs["devTree"]) return null;
- var pNode = this.$refs["devTree"].getNode(this.contextMenuNodeData);
- if(!pNode) return null;
- if(!pNode.parent) return null;
- return pNode.parent.data;
- },
- resetCloseTimer(player) {
- if(!player) return;
- player.bCloseShow = true;
- if (player.closeTimer) {
- clearTimeout(player.closeTimer)
- }
- player.closeTimer = setTimeout(() => {
- player.bCloseShow = false;
- }, 2000);
- },
- closeVideo(player) {
- if(!player) return;
- player.bLoading = false;
- player.bCloseShow = false;
- player.poster = "";
- player.url = "";
- },
- clearVideos() {
- for(var idx in this.players) {
- this.closeVideo(this.players[idx]);
- }
- },
- setPlayersLength(len) {
- if(len == this.players.length) {
- return
- }
- this.clearVideos();
- this.players = [];
- this.playersLength = len;
- for(var i=0; i < len; i++) {
- this.players.push({
- serial: "",
- code: "",
- url: "",
- protocol: "",
- poster: "",
- bLoading: false,
- bCloseShow: false,
- })
- }
- },
- setPlayerIdx(idx) {
- this.ctrlStop();
- this.playerIdx = idx % this.players.length;
- },
- clickPlayer(player, idx, event) {
- if(!$(event.target).hasClass("video-close")) {
- this.setPlayerIdx(idx);
- }
- },
- ptzControl(e) {
- var player = this.players[this.playerIdx];
- if(!player || !player.serial || !player.code) return;
- var $target = $(e.currentTarget);
- $.get("/api/v1/control/ptz", {
- serial: player.serial,
- code: player.code,
- command: $target.attr("command")
- });
- $target.addClass("active");
- },
- ptzStop() {
- var player = this.players[this.playerIdx];
- if(!player || !player.serial || !player.code) return;
- if ($(this.$el).find(".ptz-cell.active").length > 0) {
- $.get("/api/v1/control/ptz", {
- serial: player.serial,
- code: player.code,
- command: "stop"
- });
- $(this.$el).find(".ptz-cell.active").removeClass("active");
- }
- },
- talkStart(e) {
- if(!this.ws) {
- var talkURL = this.wsTalkURL();
- if(!talkURL) return;
- var ws = new WebSocket(talkURL);
- ws.onopen = evt => {
- console.log("ws talk open");
- this.ws = ws;
- }
- ws.onclose = evt => {
- console.log("ws talk close");
- }
- ws.onerror = evt => {
- console.log("ws talk error", evt);
- }
- }
- var $target = $(e.currentTarget);
- if(this.recorder) {
- $target.addClass("active");
- this.recorder.start();
- return;
- }
- LiveRecorder.get((rec, err) => {
- if(err) {
- alert(err);
- return
- }
- // this.$refs["player"].setMuted(true);
- $target.addClass("active");
- this.recorder = rec;
- this.recorder.start();
- }, {
- sampleBits: 16,
- sampleRate: 8000,
- pcmCallback: pcm => {
- // binary to base64 string
- var reader = new window.FileReader();
- reader.onloadend = () => {
- var base64 = reader.result;
- var base64 = base64.split(',')[1];
- if(this.ws) {
- this.ws.send(base64);
- }
- }
- reader.readAsDataURL(pcm);
- }
- })
- },
- talkStop() {
- if(this.recorder) {
- this.recorder.stop();
- // this.recorder = null;
- $(this.$el).find(".fa-microphone.active").removeClass("active");
- // this.$refs["player"].setMuted(false);
- }
- if(this.ws) {
- this.ws.close();
- this.ws = null;
- }
- },
- ctrlStop() {
- this.talkStop();
- this.ptzStop();
- },
- wsTalkURL() {
- var player = this.players[this.playerIdx];
- if(!player || !player.serial || !player.code) return '';
- var protocal = "ws:";
- if(location.protocol.startsWith("https")) {
- protocal = "wss:";
- }
- //url query param "format=pcm|g711"
- return `${protocal}//${location.host}/api/v1/control/ws-talk/${player.serial}/${player.code}?format=pcm`;
- },
- resetTreeMaxHeight() {
- var $tree = $("#dev-tree, #group-tree");
- if(this.isMobile()) {
- $tree.css("max-height", 200);
- } else {
- this.$nextTick(() => {
- var ph = $("#dev-tree-player").height() - 90;
- var wh = $(window).height() - 450;
- var th = wh > ph ? wh : ph;
- $tree.css("max-height", th);
- })
- }
- },
- updateSticky() {
- this.sticky && this.sticky.update();
- },
- }
- };
- </script>
- <style lang="less" scoped>
- .right-menu {
- position: fixed;
- background: #fff;
- border: solid 1px rgba(0, 0, 0, .2);
- border-radius: 3px;
- z-index: 999;
- display: none;
- }
- .right-menu a {
- width: 100px;
- height: 28px;
- line-height: 28px;
- text-align: center;
- display: block;
- color: #1a1a1a;
- }
- .right-menu a:hover {
- background: #eee;
- color: #fff;
- }
- .right-menu {
- border: 1px solid #eee;
- box-shadow: 0 0.5em 1em 0 rgba(0,0,0,.1);
- border-radius: 1px;
- }
- a {
- text-decoration: none;
- }
- .right-menu a {
- padding: 2px;
- }
- .right-menu a:hover {
- background: #42b983;
- }
- .video {
- position: relative;
- .video-close {
- position: absolute;
- top: 5px;
- right: 5px;
- color: white;
- font-size: 12px;
- background-color: fade(gray, 50%);
- padding: 2px 5px;
- cursor: pointer;
- border-radius: 2px;
- max-width: 120px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- }
- .ptz-block {
- width: 150px;
- height: 180px;
- margin: 0 auto;
- text-align: center;
- position: relative;
- font-size: 24px;
- }
- .ptz-cell {
- width: 50px;
- height: 50px;
- line-height: 50px;
- position: absolute;
- }
- .ptz-cell.active {
- color: #ccc;
- font-size: 26px;
- }
- .ptz-cell.readonly {
- color: #ddd;
- }
- .fa-microphone.active {
- color: #FFF;
- }
- .fa-microphone.readonly {
- color: #ddd;
- }
- .fa-microphone-slash.readonly {
- color: #ddd;
- }
- .ptz-center {
- top: 50px;
- left: 50px;
- border-radius: 25px;
- background-color: #ccc;
- }
- .ptz-up {
- top: 0;
- left: 50px;
- }
- .ptz-left {
- top: 50px;
- left: 0;
- }
- .ptz-right {
- top: 50px;
- left: 100px;
- }
- .ptz-down {
- top: 100px;
- left: 50px;
- }
- .ptz-zoomin {
- top: 150px;
- left: 20px;
- }
- .ptz-zoomout {
- top: 150px;
- left: 80px;
- }
- .ptz-up,
- .ptz-left,
- .ptz-right,
- .ptz-down,
- .ptz-center > .fa-microphone,
- .ptz-zoomin,
- .ptz-zoomout {
- cursor: pointer;
- &.readonly {
- cursor: auto;
- }
- }
- </style>
- <style lang="less">
- #dev-tree-player {
- .view-list {
- .video-show {
- .video {
- border: 1px solid #fff;
- &.active .video-inner {
- border: 2px solid red;
- }
- }
- .col-sm-12 {
- &:nth-child(1) {
- border: 0;
- }
- }
- .col-sm-6 {
- &:nth-child(1),
- &:nth-child(2) {
- border-top-color: transparent;
- }
- &:nth-child(2),
- &:nth-child(4) {
- border-right-color: transparent;
- }
- &:nth-child(1),
- &:nth-child(3) {
- border-left-color: transparent;
- }
- &:nth-child(3),
- &:nth-child(4) {
- border-bottom-color: transparent;
- }
- }
- .col-sm-4 {
- &:nth-child(1),
- &:nth-child(2),
- &:nth-child(3) {
- border-top-color: transparent;
- }
- &:nth-child(3),
- &:nth-child(6),
- &:nth-child(9) {
- border-right-color: transparent;
- }
- &:nth-child(7),
- &:nth-child(8),
- &:nth-child(9) {
- border-bottom-color: transparent;
- }
- &:nth-child(1),
- &:nth-child(4),
- &:nth-child(7) {
- border-left-color: transparent;
- }
- }
- .col-sm-3 {
- &:nth-child(1),
- &:nth-child(2),
- &:nth-child(3),
- &:nth-child(4) {
- border-top-color: transparent;
- }
- &:nth-child(4),
- &:nth-child(8),
- &:nth-child(12),
- &:nth-child(16) {
- border-right-color: transparent;
- }
- &:nth-child(13),
- &:nth-child(14),
- &:nth-child(15),
- &:nth-child(16) {
- border-bottom-color: transparent;
- }
- &:nth-child(1),
- &:nth-child(5),
- &:nth-child(9),
- &:nth-child(13) {
- border-left-color: transparent;
- }
- }
- }
- }
- }
- </style>
|