index.vue 15 KB

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