Detail.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <template>
  2. <div
  3. v-loading="videoLoading"
  4. class="detail"
  5. >
  6. <i
  7. class="el-icon-close closeDetail"
  8. @click="close"
  9. />
  10. <div
  11. v-if="detailobj.cameraType===2"
  12. class="detail-top"
  13. >
  14. <div class="detail_text">
  15. {{ detailobj.name }}人流量监测
  16. </div>
  17. <div class="detail_border" />
  18. </div>
  19. <video
  20. ref="player"
  21. style="width: 100%"
  22. class="video"
  23. muted
  24. autoplay
  25. :poster="require('@/assets/video-post.png')"
  26. />
  27. <div
  28. v-if="detailobj.cameraType===2"
  29. class="detail-buttom"
  30. >
  31. <el-row :gutter="16">
  32. <el-col :span="24">
  33. <div class="video-controls l-flex--row">
  34. <div
  35. v-show="settingBshow"
  36. class="settingB"
  37. >
  38. <div v-show="settingTab">
  39. <div
  40. v-for="(item, index) in setData"
  41. :key="index"
  42. class="settingT"
  43. @click="setClick(index)"
  44. >
  45. {{ item }} <i class="el-icon-arrow-right" />
  46. </div>
  47. </div>
  48. <div v-show="!settingTab">
  49. <div
  50. class="settingT settingsub"
  51. @click="setBack"
  52. >
  53. <i class="el-icon-arrow-left" />{{ setData[settingActive] }}
  54. </div>
  55. <div class="settingHeight">
  56. <div
  57. v-for="(item, index) in settingData[settingActive]"
  58. :key="index"
  59. class="settingT settingsub"
  60. @click="settingClick(index)"
  61. >
  62. <i :class="{ 'el-icon-check': item.active }" />
  63. {{ item.value }}
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. <div class="l-flex__auto">
  69. <img
  70. :src="imgUrl.stop"
  71. class="stop"
  72. @click="stopbtn"
  73. >
  74. </div>
  75. <img
  76. :src="imgUrl.setting"
  77. class="setting"
  78. @click="settingShow"
  79. >
  80. <img
  81. :src="imgUrl.refresh"
  82. class="refresh"
  83. @click="refresh"
  84. >
  85. </div>
  86. </el-col>
  87. <el-col :span="8">
  88. <div class="o-detail">
  89. <div>
  90. <span class="o-detail_title">设备名称:</span><span>{{ detailobj.name }}</span>
  91. </div>
  92. <div>
  93. <span class="o-detail_title">ID:</span><span>{{ detailobj.identifier }}</span>
  94. </div>
  95. <div>
  96. <span class="o-detail_title">用户名:</span><span>{{ detailobj.username }}</span>
  97. </div>
  98. <div>
  99. <span class="o-detail_title">备注:</span><span>{{ detailobj.remark }}</span>
  100. </div>
  101. </div>
  102. </el-col>
  103. <el-col :span="16">
  104. <div class="o-detail">
  105. <div
  106. id="main"
  107. style="width: 100%; height: 200px"
  108. />
  109. <div class="choosedate">
  110. <div class="timeBtn">
  111. <span
  112. :class="{ active: active === 'hour' }"
  113. @click="timeType('hour')"
  114. >1小时</span>
  115. <span
  116. :class="{ active: active === 'day' }"
  117. @click="timeType('day')"
  118. >1天</span>
  119. </div>
  120. <el-date-picker
  121. v-model="datevalue"
  122. type="datetime"
  123. placeholder="选择日期时间"
  124. prefix-icon="el-icon-date"
  125. format="yyyy-MM-dd HH:mm"
  126. value-format="yyyy-MM-dd HH:mm:ss"
  127. @change="onDateTimeChange()"
  128. />
  129. </div>
  130. </div>
  131. </el-col>
  132. </el-row>
  133. </div>
  134. </div>
  135. </template>
  136. <script>
  137. import flvjs from 'flv.js'
  138. import * as echarts from 'echarts'
  139. import {
  140. getStatistic,
  141. getVideoinfo,
  142. getAvailableParam,
  143. setCamera
  144. } from '@/api/camera'
  145. const CAMERA_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_CAMERA_PROXY}`
  146. export default {
  147. name: 'CameraDetail',
  148. props: {
  149. detailobj: {
  150. type: Object,
  151. default () {
  152. return {}
  153. }
  154. }
  155. // ['']
  156. },
  157. data () {
  158. return {
  159. datevalue: new Date(),
  160. active: 'hour', // hour是小时,day是天
  161. imgUrl: {
  162. setting: require('@/assets/icon_setting.png'),
  163. stop: require('@/assets/icon_stop.png'),
  164. start: require('@/assets/icon_start.png'),
  165. refresh: require('@/assets/icon_refresh.png')
  166. },
  167. settingActive: 0,
  168. setData: ['分辨率', '帧率', '码流'],
  169. settingData: [[], [], []],
  170. settingTab: true,
  171. settingBshow: false,
  172. echartsData: '',
  173. player: null,
  174. availableParam: {},
  175. infoData: {},
  176. videoLoading: false,
  177. settingDatacopy: []
  178. }
  179. },
  180. created () {
  181. this.getAvailableParam()
  182. },
  183. mounted () {
  184. this.getflv()
  185. this.getStatistic(
  186. this.getStarttime(new Date(), 'now'),
  187. this.getStarttime(new Date())
  188. )
  189. },
  190. methods: {
  191. close () {
  192. this.destroyPlayer()
  193. this.$emit('closeDetail')
  194. },
  195. getflv () {
  196. if (flvjs.isSupported()) {
  197. // 创建一个flvjs实例
  198. this.player = flvjs.createPlayer({
  199. type: 'flv',
  200. isLive: true,
  201. // hasAudio: false,
  202. url: `${CAMERA_URL}/${this.detailobj.identifier}?authorization=${this.$keycloak.token}`
  203. })
  204. this.player.on('error', e => {
  205. console.log(e)
  206. })
  207. // 将实例挂载到video元素上面
  208. this.player.attachMediaElement(this.$refs.player)
  209. try {
  210. // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
  211. this.player.load()
  212. this.player.play()
  213. this.videoLoading = false
  214. } catch (error) {
  215. console.log('连接websocker异常', error)
  216. }
  217. }
  218. },
  219. setCamera () {
  220. setCamera({
  221. deviceId: this.detailobj.identifier,
  222. width: this.infoData.withHight.split('*')[0],
  223. hight: this.infoData.withHight.split('*')[1],
  224. bitRate: this.infoData.bitRate.slice(0, -4),
  225. frameRate: this.infoData.frameRate.slice(0, -3)
  226. }).then(() => {
  227. this.destroyPlayer()
  228. this.getflv()
  229. })
  230. },
  231. getAvailableParam () {
  232. getAvailableParam().then(({ data }) => {
  233. data.itemList.forEach(item => {
  234. this.settingData[0].push({
  235. value: `${item.snWidth}*${item.snHight}`,
  236. active: false
  237. })
  238. })
  239. for (let i = 1; i < 26; i++) {
  240. this.settingData[1].push({ value: `${i}fps`, active: false })
  241. this.settingDatacopy.push({ value: `${i}fps`, active: false })
  242. }
  243. data.streamRateTypeList.forEach(item => {
  244. this.settingData[2].push({ value: `${item}kb/s`, active: false })
  245. })
  246. this.availableParam = data
  247. this.getVideoinfo(data)
  248. })
  249. },
  250. getVideoinfo (param) {
  251. getVideoinfo(this.detailobj.identifier).then(({ data }) => {
  252. this.infoData = {
  253. withHight: `${data.width}*${data.hight}`,
  254. frameRate: `${data.frameRate}fps`,
  255. bitRate: `${data.bitRate}kb/s`
  256. }
  257. this.dataInit(param, this.infoData)
  258. })
  259. },
  260. dataInit (param, data) {
  261. for (let i = 0; i < this.settingData[0].length; i++) {
  262. if (this.settingData[0][i].value === data.withHight) {
  263. this.settingData[0][i].active = true
  264. }
  265. }
  266. this.settingData[1] = this.settingDatacopy
  267. for (let i = 0; i < this.settingData[1].length; i++) {
  268. if (this.settingData[1][i].value === data.frameRate) {
  269. this.settingData[1][i].active = true
  270. } else {
  271. this.settingData[1][i].active = false
  272. }
  273. }
  274. for (let i = 0; i < this.settingData[2].length; i++) {
  275. if (this.settingData[2][i].value === data.bitRate) {
  276. this.settingData[2][i].active = true
  277. }
  278. }
  279. const findobj = param.itemList.find(item => `${item.snWidth}*${item.snHight}` === data.withHight)
  280. this.settingData[1] = this.settingData[1].filter(item => {
  281. const splceitem = item.value.slice(0, -3)
  282. return splceitem <= findobj.resolutionFpsMax
  283. })
  284. const findData = this.settingData[1].find(item => item.active === true)
  285. if (!findData) {
  286. this.infoData.frameRate = findData ? this.infoData.frameRate : `${this.settingData[1].length}fps`
  287. this.settingData[1][this.settingData[1].length].active = true
  288. }
  289. // this.settingData[1][this.settingData[1].length].value = this.infoData.frameRate
  290. this.settingData[2] = this.settingData[2].filter(item => {
  291. const splceitem = item.value.slice(0, -4)
  292. return (
  293. splceitem >= findobj.minBitRateOptions
  294. && splceitem <= findobj.maxBitRateOptions
  295. )
  296. })
  297. },
  298. destroyPlayer () {
  299. // this.player.pause();
  300. this.player.unload()
  301. this.player.detachMediaElement()
  302. this.player.destroy()
  303. this.player = null
  304. },
  305. stopbtn () {
  306. if (this.$refs.player.paused) {
  307. this.imgUrl.stop = require('@/assets/icon_stop.png')
  308. this.player.play()
  309. } else {
  310. this.imgUrl.stop = this.imgUrl.start
  311. this.player.pause()
  312. }
  313. },
  314. setClick (index) {
  315. this.settingActive = index
  316. this.settingTab = !this.settingTab
  317. },
  318. setBack () {
  319. this.settingTab = !this.settingTab
  320. },
  321. settingClick (index) {
  322. this.settingData[this.settingActive].forEach(element => {
  323. element.active = false
  324. })
  325. this.settingData[this.settingActive][index].active = !this.settingData[this.settingActive][index].active
  326. const arr = JSON.stringify(this.infoData)
  327. // console.log(this.settingData)
  328. if (this.settingActive === 0) {
  329. if (
  330. this.infoData.withHight !==
  331. this.settingData[this.settingActive][index].value
  332. ) {
  333. this.infoData.withHight =
  334. this.settingData[this.settingActive][index].value
  335. this.dataInit(this.availableParam, this.infoData)
  336. }
  337. } else if (this.settingActive === 1) {
  338. this.infoData.frameRate =
  339. this.settingData[this.settingActive][index].value
  340. } else if (this.settingActive === 2) {
  341. this.infoData.bitRate =
  342. this.settingData[this.settingActive][index].value
  343. }
  344. if (!(arr === JSON.stringify(this.infoData))) {
  345. this.setCamera()
  346. }
  347. // console.log(this.infoData)
  348. },
  349. settingShow () {
  350. this.settingBshow = !this.settingBshow
  351. },
  352. refresh () {
  353. this.destroyPlayer()
  354. this.getflv()
  355. },
  356. timeType (type) {
  357. this.active = type
  358. let startTime
  359. if (this.datevalue.length) {
  360. startTime = this.datevalue
  361. } else {
  362. this.datevalue = new Date()
  363. startTime = this.getStarttime(new Date(), 'now')
  364. }
  365. this.getStatistic(startTime, this.getStarttime(startTime, type))
  366. },
  367. onDateTimeChange () {
  368. if (this.datevalue) {
  369. this.getStatistic(this.datevalue, this.getStarttime(this.datevalue, this.active))
  370. }
  371. },
  372. getStarttime (time, type) {
  373. let onehour = 60 * 60 * 1000
  374. if (type === 'day') {
  375. onehour = 60 * 60 * 1000 * 24
  376. } else if (type === 'now') {
  377. onehour = 0
  378. }
  379. const startTime = new Date(new Date(time).getTime() - onehour)
  380. .toLocaleString('zh', { hour12: false })
  381. .split('/')
  382. .join('-')
  383. const arr = startTime.split(' ')[0].split('-')
  384. for (let i = 0; i < arr.length; i++) {
  385. if (arr[i].length < 2) {
  386. arr[i] = `0${arr[i]}`
  387. }
  388. }
  389. return `${arr.join('-')} ${startTime.split(' ')[1]}`
  390. },
  391. getStatistic (endTime, startTime) {
  392. getStatistic({
  393. deviceId: this.detailobj.identifier,
  394. startTime,
  395. endTime,
  396. pageIndex: 1,
  397. pageSize: 10000
  398. }).then(({ data }) => {
  399. this.echartsData = data
  400. this.initEchart()
  401. })
  402. },
  403. getxdata (data) {
  404. const arr = []
  405. data.forEach(item => {
  406. const time = item.eventTime.slice(11, 16)
  407. arr.push(time)
  408. })
  409. return arr
  410. },
  411. getydata (data) {
  412. const arr = []
  413. data.forEach(item => {
  414. arr.push(item.insidePeopleNum)
  415. })
  416. return arr
  417. },
  418. initEchart () {
  419. const data = this.echartsData.filter(item => item.insidePeopleNum !== 0)
  420. const xdata = this.getxdata(data)
  421. const ydata = this.getydata(data)
  422. const chartDom = document.getElementById('main')
  423. const myChart = echarts.init(chartDom)
  424. myChart.setOption({
  425. title: {
  426. text: '区域内人数',
  427. textStyle: {
  428. color: '#fff',
  429. fontWeight: 'bold'
  430. }
  431. // padding: [0, 30],
  432. },
  433. xAxis: {
  434. type: 'category',
  435. data: xdata,
  436. splitLine: {
  437. show: false
  438. },
  439. axisLine: {
  440. lineStyle: {
  441. color: '#4779BC'
  442. }
  443. },
  444. axisLabel: {
  445. color: '#A9CEFF'
  446. }
  447. },
  448. yAxis: {
  449. type: 'value',
  450. minInterval: 1,
  451. splitLine: {
  452. lineStyle: {
  453. color: '#4779BC',
  454. type: 'dashed'
  455. }
  456. },
  457. axisLine: {
  458. show: false,
  459. lineStyle: {
  460. color: '#4779BC'
  461. }
  462. },
  463. axisLabel: {
  464. color: '#A9CEFF'
  465. }
  466. },
  467. grid: {
  468. left: '30',
  469. right: '20',
  470. top: '40',
  471. bottom: '20'
  472. },
  473. series: [
  474. {
  475. data: ydata,
  476. type: 'bar',
  477. showBackground: true,
  478. backgroundStyle: {
  479. color: 'transparent'
  480. },
  481. itemStyle: {
  482. color: 'rgba(0, 191, 208, 0.5)'
  483. },
  484. select: {
  485. itemStyle: {
  486. color: 'rgb(0, 234, 255)'
  487. }
  488. }
  489. }
  490. ],
  491. tooltip: {
  492. formatter: '时间:{b}<br />人流量:{c}'
  493. }
  494. })
  495. }
  496. }
  497. }
  498. </script>
  499. <style lang="scss" scoped>
  500. .detail {
  501. width: 100%;
  502. height: 100%;
  503. position: relative;
  504. overflow: hidden;
  505. .closeDetail {
  506. position: absolute;
  507. right: 20px;
  508. top: 5px;
  509. cursor: pointer;
  510. z-index: 3;
  511. font-size: 42px;
  512. color: #000;
  513. }
  514. .video {
  515. width: 100%;
  516. height: 100vh;
  517. object-fit: contain;
  518. }
  519. .detail-buttom {
  520. position: absolute;
  521. bottom: 0;
  522. width: 100%;
  523. left: 0;
  524. padding: 16px;
  525. .o-detail {
  526. padding: 24px 24px 70px;
  527. background-color: rgba(0, 62, 144, 0.8);
  528. color: #fff;
  529. line-height: 36px;
  530. height: 238px;
  531. position: relative;
  532. border-radius: 4px;
  533. &_title {
  534. width: 72px;
  535. display: inline-block;
  536. border-radius: 4px;
  537. }
  538. .choosedate {
  539. position: absolute;
  540. right: 20px;
  541. top: 10px;
  542. .timeBtn {
  543. display: inline-block;
  544. border-radius: 4px;
  545. background: #4478bc;
  546. height: 24px;
  547. line-height: 24px;
  548. margin-right: 16px;
  549. span {
  550. font-size: 14px;
  551. background: #4478bc;
  552. border-radius: 4px;
  553. padding: 0 7px;
  554. display: inline-block;
  555. cursor: pointer;
  556. width: 50px;
  557. text-align: center;
  558. }
  559. .active {
  560. background: #0096ff;
  561. }
  562. }
  563. }
  564. }
  565. .video-controls {
  566. background-color: rgba(0, 6, 13, 0.75);
  567. height: 48px;
  568. border-radius: 4px;
  569. margin-bottom: 16px;
  570. position: relative;
  571. img {
  572. cursor: pointer;
  573. }
  574. .stop {
  575. margin-left: 12px;
  576. }
  577. .setting {
  578. margin-right: 50px;
  579. }
  580. .refresh {
  581. margin-right: 38px;
  582. }
  583. .settingB {
  584. position: absolute;
  585. padding: 10px 0px;
  586. background-color: rgba($color: #000000, $alpha: 0.85);
  587. right: 134px;
  588. transform: translateX(50%);
  589. bottom: 48px;
  590. color: #fff;
  591. .settingT {
  592. padding: 0px 16px;
  593. height: 40px;
  594. line-height: 40px;
  595. width: 180px;
  596. cursor: pointer;
  597. position: relative;
  598. &:hover {
  599. background-color: rgba(69, 69, 69, 0.85);
  600. }
  601. i {
  602. position: absolute;
  603. right: 16px;
  604. top: 13px;
  605. }
  606. }
  607. .settingHeight {
  608. max-height: 250px;
  609. overflow: auto;
  610. }
  611. .settingsub {
  612. width: 140px;
  613. padding-left: 42px;
  614. i {
  615. position: absolute;
  616. left: 16px;
  617. }
  618. }
  619. }
  620. }
  621. }
  622. .detail-top {
  623. position: absolute;
  624. top: 0;
  625. width: 100%;
  626. left: 0;
  627. padding-top: 16px;
  628. font-size: 24px;
  629. text-align: center;
  630. background-color: rgba(0, 62, 144, 0.8);
  631. color: #fff;
  632. z-index: 2;
  633. .detail {
  634. &_border {
  635. position: absolute;
  636. left: 50%;
  637. transform: translateX(-50%);
  638. width: 700px;
  639. height: 0;
  640. border-top: 16px solid rgba(0, 62, 144, 0.8);
  641. border-right: 16px solid transparent;
  642. border-bottom: 16px solid transparent;
  643. border-left: 16px solid transparent;
  644. }
  645. &_text {
  646. width: 600px;
  647. margin: auto;
  648. overflow: hidden;
  649. text-overflow: ellipsis;
  650. white-space: nowrap;
  651. height: 32px;
  652. }
  653. }
  654. }
  655. }
  656. video::-webkit-media-controls-fullscreen-button {
  657. display: none;
  658. }
  659. //所有控件
  660. video::-webkit-media-controls-enclosure {
  661. display: none;
  662. }
  663. </style>
  664. <style lang="scss">
  665. .choosedate {
  666. input {
  667. background-color: transparent;
  668. height: 24px;
  669. line-height: 24px;
  670. font-size: 14px;
  671. color: #fff;
  672. // padding: 0 10px !important;
  673. }
  674. .el-input__prefix {
  675. top: -2px;
  676. }
  677. }
  678. </style>