index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. <template>
  2. <wrapper
  3. fill
  4. margin
  5. background
  6. >
  7. <el-tabs
  8. v-model="activeName"
  9. v-loading="videoOption.loading"
  10. class="c-tabs video"
  11. @tab-click="handleClick"
  12. >
  13. <el-tab-pane
  14. label="人流量监测"
  15. name="first"
  16. >
  17. <div class="has-padding">
  18. <div class="l-flex--row c-table__header">
  19. <div class="l-flex__auto c-sibling-item">
  20. <button
  21. v-if="hasEditPermission"
  22. class="o-button"
  23. @click="addbtn"
  24. >
  25. <i class="o-button__icon el-icon-circle-plus-outline" />新增
  26. </button>
  27. </div>
  28. <search-input
  29. v-model.trim="searchname"
  30. class="l-flex__none c-sibling-item"
  31. placeholder="搜索"
  32. @search="search"
  33. />
  34. <button
  35. class="l-flex__none c-sibling-item o-button"
  36. @click="search"
  37. >
  38. 搜索
  39. </button>
  40. </div>
  41. <el-row
  42. v-if="videoOption.list.length"
  43. :gutter="16"
  44. class="rowheight"
  45. >
  46. <el-col
  47. v-for="(video, index) in videoOption.list"
  48. :key="index"
  49. :span="8"
  50. class="cameraRow"
  51. >
  52. <div
  53. ref="videoB"
  54. class="o-video bg-purple"
  55. :style="{ height: videoheight }"
  56. @click="toDetail(video)"
  57. >
  58. <video
  59. :ref="video.deviceId"
  60. style="width: 100%;"
  61. class="video"
  62. muted
  63. autoplay
  64. :poster="require('@/assets//video-post.png')"
  65. />
  66. <div
  67. v-show="!video.online"
  68. class="offLine"
  69. >
  70. 设备离线
  71. </div>
  72. <div class="o-video_buttom l-flex--row">
  73. <div class="l-flex__auto">{{ video.name }}</div>
  74. <template v-if="hasEditPermission">
  75. <img
  76. class="o-video_edit"
  77. :src="imgUrl.edit"
  78. @click.stop="editbtn(video)"
  79. >
  80. <img
  81. class="o-video_delete"
  82. :src="imgUrl.delete"
  83. @click.stop="deletebtn(video)"
  84. >
  85. </template>
  86. </div>
  87. </div>
  88. </el-col>
  89. </el-row>
  90. <div
  91. v-if="!videoOption.list.length"
  92. class="empty"
  93. >无摄像头数据</div>
  94. <pagination
  95. :total="videoOption.totalCount"
  96. :page-sizes="[6]"
  97. :page.sync="videoOption.params.pageNum"
  98. :limit.sync="videoOption.params.pageSize"
  99. @pagination="getCamera"
  100. />
  101. </div>
  102. </el-tab-pane>
  103. <!-- <el-tab-pane label="视频回传" name="second">
  104. <div class="has-padding">
  105. <div class="l-flex--row c-table__header">
  106. <div class="l-flex__auto c-sibling-item">
  107. <el-checkbox-group v-model="checkList" @change="chechChange">
  108. <el-checkbox label="视频回采"></el-checkbox>
  109. <el-checkbox label="模拟终端"></el-checkbox>
  110. </el-checkbox-group>
  111. </div>
  112. <div class="l-flex__none c-sibling-item">
  113. <img
  114. class="c-sibling-item"
  115. @click="rowChange(24)"
  116. :src="require('@/assets/icon_one_normal.png')"
  117. alt=""
  118. />
  119. <img
  120. class="c-sibling-item"
  121. @click="rowChange(12)"
  122. :src="require('@/assets/icon_four_normal.png')"
  123. alt=""
  124. />
  125. <img
  126. class="c-sibling-item"
  127. @click="rowChange(8)"
  128. :src="require('@/assets/icon_nine_focus.png')"
  129. alt=""
  130. />
  131. </div>
  132. <button
  133. class="l-flex__none c-sibling-item o-button"
  134. @click="search"
  135. >
  136. <i class="o-button__icon el-icon-full-screen" />
  137. 全屏
  138. </button>
  139. </div>
  140. <el-row :gutter="16" class="rowheight">
  141. <el-col
  142. :span="rowNum"
  143. v-for="(item, index) in returnList.list"
  144. :key="index"
  145. class="cameraRow"
  146. >
  147. <div class="return_item bg-purple"
  148. ref="returnB"
  149. :style="{ height: returnheight }">
  150. <div class="o-video_buttom l-flex--row">
  151. <div class="l-flex__auto">{{ item.name }}</div>
  152. <img
  153. :src="require('@/assets/icon_address.png')"
  154. class="o-video_edit"
  155. />
  156. <div class="return_local">{{ item.name }}</div>
  157. </div>
  158. </div>
  159. </el-col>
  160. </el-row>
  161. </div>
  162. </el-tab-pane> -->
  163. </el-tabs>
  164. <!-- 新增和编辑 -->
  165. <el-dialog
  166. :title="dialogTitle"
  167. :visible.sync="adding"
  168. custom-class="c-dialog"
  169. :close-on-click-modal="false"
  170. >
  171. <el-form
  172. ref="cameraForm"
  173. :model="camera"
  174. :rules="cameraRules"
  175. label-width="100px"
  176. class="c-form"
  177. >
  178. <el-form-item
  179. label="设备名称"
  180. prop="name"
  181. >
  182. <el-input
  183. v-model.number="camera.name"
  184. class="c-form__item"
  185. />
  186. </el-form-item>
  187. <el-form-item
  188. v-if="!isEdit"
  189. label="ID"
  190. prop="deviceId"
  191. >
  192. <el-input
  193. v-model.number="camera.deviceId"
  194. class="c-form__item"
  195. />
  196. </el-form-item>
  197. <el-form-item
  198. label="用户名"
  199. prop="username"
  200. >
  201. <el-input
  202. v-model.number="camera.username"
  203. class="c-form__item"
  204. />
  205. </el-form-item>
  206. <el-form-item
  207. label="密码"
  208. prop="password"
  209. >
  210. <el-input
  211. v-model.number="camera.password"
  212. class="c-form__item"
  213. />
  214. </el-form-item>
  215. <el-form-item
  216. label="备注"
  217. prop="remark"
  218. >
  219. <el-input
  220. v-model.number="camera.remark"
  221. type="textarea"
  222. maxlength="500"
  223. class="c-form__item"
  224. show-word-limit
  225. />
  226. </el-form-item>
  227. </el-form>
  228. <template #footer>
  229. <button
  230. class="o-button"
  231. @click="add('cameraForm')"
  232. >
  233. 确定
  234. </button>
  235. <button
  236. class="o-button cancel"
  237. @click="handleCloseAddDialog('cameraForm')"
  238. >
  239. 取消
  240. </button>
  241. </template>
  242. </el-dialog>
  243. <el-dialog
  244. title
  245. :fullscreen="true"
  246. :visible="detailing"
  247. :close-on-click-modal="false"
  248. class="fulldialog"
  249. >
  250. <detail
  251. v-if="detailing"
  252. :detailobj="editOption"
  253. @closeDetail="closeDetail"
  254. />
  255. </el-dialog>
  256. </wrapper>
  257. </template>
  258. <script>
  259. import flvjs from 'flv.js'
  260. import {
  261. getCamera,
  262. addCamera,
  263. updateCamera,
  264. deleteCamera,
  265. getOnline
  266. } from '@/api/camera'
  267. import { createListOptions } from '@/utils'
  268. import Detail from './components/Detail'
  269. const CAMERA_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_CAMERA_PROXY}`
  270. export default {
  271. name: 'Camera',
  272. components: {
  273. Detail
  274. },
  275. data () {
  276. return {
  277. activeName: 'first',
  278. searchname: '',
  279. imgUrl: {
  280. edit: require('@/assets/icon_edit.png'),
  281. delete: require('@/assets/icon_delete.png')
  282. },
  283. videoOption: createListOptions({
  284. pageSize: 6
  285. }),
  286. dialogTitle: '新增',
  287. adding: false,
  288. camera: {
  289. name: '',
  290. deviceId: '',
  291. username: '',
  292. password: '',
  293. remark: ''
  294. },
  295. cameraRules: {
  296. name: [{ required: true, message: '请填写设备名称', trigger: 'blur' }],
  297. deviceId: [{ required: true, message: '请填写ID', trigger: 'blur' }],
  298. username: [
  299. { required: true, message: '请填写用户名', trigger: 'blur' }
  300. ],
  301. password: [{ required: true, message: '请填写密码', trigger: 'blur' }]
  302. },
  303. isEdit: true,
  304. editOption: {},
  305. detailing: false,
  306. videoheight: '',
  307. playerList: {},
  308. checkList: [],
  309. returnList: createListOptions({
  310. pageSize: 9
  311. }),
  312. returnheight: 0,
  313. rowNum: 8
  314. }
  315. },
  316. created () {
  317. this.getCamera()
  318. },
  319. beforeDestroy () {
  320. this.destroyPlayer()
  321. },
  322. methods: {
  323. rowChange (num) {
  324. this.rowNum = num
  325. this.$nextTick(() => {
  326. this.returnheight = (this.$refs.returnB[0].clientWidth * 9) / 16 + 'px'
  327. })
  328. },
  329. chechChange (val) {
  330. console.log(val)
  331. },
  332. destroyPlayer () {
  333. for (const key in this.playerList) {
  334. if (Object.hasOwnProperty.call(this.playerList, key)) {
  335. // const element = object[key];
  336. if (this.playerList[key]) {
  337. this.playerList[key].pause()
  338. this.playerList[key].unload()
  339. this.playerList[key].detachMediaElement()
  340. this.playerList[key].destroy()
  341. this.playerList[key] = null
  342. }
  343. }
  344. }
  345. },
  346. handleClick () {},
  347. search () {
  348. this.getCamera()
  349. },
  350. getCamera () {
  351. const options = this.videoOption
  352. options.error = false
  353. options.loading = true
  354. options.params.name = this.searchname
  355. getCamera(options.params)
  356. .then(
  357. ({ data, totalCount }) => {
  358. options.totalCount = totalCount
  359. options.list = data
  360. // for (let i = 0; i < 34; i++) {
  361. // data.push({
  362. // deviceId: Math.random(),
  363. // })
  364. // }
  365. this.$nextTick(() => {
  366. this.videoheight =
  367. (this.$refs.videoB[0].clientWidth * 9) / 16 + 'px'
  368. this.destroyPlayer()
  369. for (let i = 0; i < data.length; i++) {
  370. getOnline(options.list[i].deviceId).then(({ data }) => {
  371. options.list[i].online = data
  372. if (data) {
  373. this.getflv(options.list[i].deviceId)
  374. }
  375. })
  376. }
  377. })
  378. },
  379. () => {
  380. options.error = true
  381. options.list = []
  382. }
  383. )
  384. .finally(() => {
  385. options.loading = false
  386. })
  387. },
  388. addCamera () {
  389. addCamera({
  390. name: this.camera.name,
  391. deviceId: this.camera.deviceId,
  392. username: this.camera.username,
  393. password: this.camera.password,
  394. remark: this.camera.remark
  395. }).then(() => {
  396. this.handleCloseAddDialog('cameraForm')
  397. this.getCamera()
  398. })
  399. },
  400. addbtn () {
  401. this.camera = {
  402. name: '',
  403. deviceId: '',
  404. username: '',
  405. password: '',
  406. remark: ''
  407. }
  408. this.dialogTitle = '新增'
  409. this.isEdit = false
  410. this.adding = true
  411. },
  412. add (formName) {
  413. this.$refs[formName].validate((valid) => {
  414. if (valid) {
  415. if (this.isEdit) {
  416. this.edit(this.editOption)
  417. } else {
  418. this.addCamera()
  419. }
  420. } else {
  421. console.log('error submit!!')
  422. return false
  423. }
  424. })
  425. },
  426. handleCloseAddDialog (formName) {
  427. this.adding = false
  428. this.$refs[formName].resetFields()
  429. },
  430. editbtn (item) {
  431. this.adding = true
  432. this.isEdit = true
  433. this.dialogTitle = '编辑'
  434. this.editOption = item
  435. this.camera = {
  436. name: item.name,
  437. username: item.username,
  438. remark: item.remark
  439. }
  440. },
  441. edit (item) {
  442. updateCamera({
  443. id: item.id,
  444. name: this.camera.name,
  445. deviceId: item.deviceId,
  446. username: this.camera.username,
  447. password: this.camera.password,
  448. remark: this.camera.remark
  449. }).then(() => {
  450. this.handleCloseAddDialog('cameraForm')
  451. this.getCamera()
  452. })
  453. },
  454. deletebtn (item) {
  455. const videoOption = this.videoOption
  456. deleteCamera({ id: item.id, name: item.name }).then(() => {
  457. if (videoOption.list.length === 1 && videoOption.params.pageNum > 1) {
  458. videoOption.params.pageNum -= 1
  459. }
  460. this.getCamera()
  461. })
  462. },
  463. getflv (deviceId) {
  464. if (flvjs.isSupported()) {
  465. // 创建一个flvjs实例
  466. this.playerList[deviceId] = flvjs.createPlayer({
  467. type: 'flv',
  468. isLive: true,
  469. hasAudio: false,
  470. url: `${CAMERA_URL}/${deviceId}?authorization=${this.$keycloak.token}`
  471. })
  472. this.playerList[deviceId].on('error', (e) => {
  473. console.log(e)
  474. })
  475. // 将实例挂载到video元素上面
  476. this.playerList[deviceId].attachMediaElement(this.$refs[deviceId][0])
  477. // player.currentTime = parseFloat(document.getElementsByName('seekpoint')[0].value);
  478. try {
  479. // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
  480. this.playerList[deviceId].load()
  481. this.playerList[deviceId].play()
  482. } catch (error) {
  483. console.log('连接websocker异常:' + error)
  484. console.log(error)
  485. }
  486. }
  487. },
  488. toDetail (item) {
  489. if (item.online) {
  490. this.detailing = true
  491. this.editOption = item
  492. this.destroyPlayer()
  493. }
  494. },
  495. closeDetail () {
  496. this.detailing = false
  497. this.getCamera()
  498. }
  499. }
  500. }
  501. </script>
  502. <style lang="scss" scoped>
  503. //所有控件
  504. video::-webkit-media-controls-enclosure {
  505. display: none;
  506. }
  507. .has-padding {
  508. height: 100%;
  509. }
  510. .rowheight {
  511. height: calc(100% - 40px);
  512. overflow: auto;
  513. }
  514. .c-tabs {
  515. height: 100%;
  516. }
  517. .cameraRow {
  518. margin-bottom: 16px;
  519. }
  520. .o-video {
  521. // height: 190px;
  522. background-color: #edf0f6;
  523. position: relative;
  524. overflow: hidden;
  525. &_buttom {
  526. position: absolute;
  527. bottom: 0px;
  528. width: 100%;
  529. height: 58px;
  530. background-image: linear-gradient(0deg, rgba(6, 11, 18, 0.9), transparent);
  531. border-radius: 0px 0px 4px 4px;
  532. color: #fff;
  533. padding: 0 16px;
  534. }
  535. img {
  536. cursor: pointer;
  537. }
  538. &_edit {
  539. margin-right: 16px;
  540. }
  541. .offLine {
  542. position: absolute;
  543. left: 50%;
  544. top: 50%;
  545. color: #ff0000;
  546. text-align: center;
  547. transform: translate(-50%, -50%);
  548. }
  549. }
  550. .return {
  551. &_item {
  552. background-color: #aaa;
  553. width: 100%;
  554. height: 150px;
  555. position: relative;
  556. }
  557. &_local {
  558. font-size: 12px;
  559. }
  560. }
  561. .empty {
  562. color: #aaa;
  563. text-align: center;
  564. }
  565. </style>
  566. <style lang="scss">
  567. .fulldialog {
  568. .el-dialog__header {
  569. display: none;
  570. }
  571. .el-dialog__body {
  572. padding: 0;
  573. height: 100%;
  574. }
  575. }
  576. .video {
  577. .el-tabs__content {
  578. height: calc(100% - 40px);
  579. }
  580. .el-tab-pane {
  581. height: 100%;
  582. }
  583. }
  584. </style>