addInspectionTask.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <div class="add-inspection-task-container" v-if="visible">
  3. <div class="add-inspection-task-title">
  4. <span class="title-text">新增巡查任务</span>
  5. <img src="@/assets/image/common/close.png" style="cursor: pointer" alt="" @click="closeModal" />
  6. </div>
  7. <div class="add-inspection-task-content">
  8. <el-form ref="addTaskFormRef" :rules="rules" :model="addTaskFrom" label-width="0.8rem" size="mini">
  9. <el-form-item label="任务名称" prop="securityPatrolName">
  10. <el-input v-model="addTaskFrom.securityPatrolName" placeholder="请输入"></el-input>
  11. </el-form-item>
  12. <el-form-item label="任务内容">
  13. <el-input type="textarea" rows="4" v-model="addTaskFrom.securityPatrolContext" placeholder="请输入"></el-input>
  14. </el-form-item>
  15. <el-form-item label="巡查点" prop="inspectionPoint">
  16. <div v-for="(item, index) in addTaskFrom.inspectionPoint" :key="index" class="ins-point">
  17. <el-select v-model="item.value" placeholder="请选择巡查点" :popper-append-to-body="false" popper-class="u-popper-select" @change="onPointSelect">
  18. <el-option :label="opt.devName" :value="opt.deviceCode" :disabled="opt.disabled" v-for="opt in inspectionOptions" :key="opt.deviceCode"></el-option>
  19. </el-select>
  20. <img src="@/assets/image/safety-inspection/del.png" alt="" @click="removeOption(index)" v-if="isFirstItem()" />
  21. <img src="@/assets/image/safety-inspection/add.png" alt="" v-if="isLastItem(index)" @click="addOption" />
  22. </div>
  23. </el-form-item>
  24. <el-form-item label="责任人" prop="responsiblePerson">
  25. <el-select v-model="addTaskFrom.responsiblePerson" placeholder="请选择责任人" style="width: 100%" :popper-append-to-body="false" popper-class="u-popper-select">
  26. <el-option :label="p.name" :value="p.name" v-for="(p, i) in personList" :key="i"></el-option>
  27. </el-select>
  28. </el-form-item>
  29. <el-form-item>
  30. <div style="display: flex; justify-content: flex-end; margin-top: 0.22rem">
  31. <el-button size="mini" plain @click="closeModal" class="cancelBtn">取消</el-button>
  32. <el-button size="mini" type="primary" @click="toSave">保存</el-button>
  33. <el-button size="mini" type="primary" @click="startInspection">开始巡查</el-button>
  34. </div>
  35. </el-form-item>
  36. </el-form>
  37. </div>
  38. </div>
  39. </template>
  40. <script>
  41. import "mars3d/dist/mars3d.css"
  42. import * as mars3d from 'mars3d'
  43. import Vue from 'vue'
  44. import { toAddSecurityPatrol, endPatrol, startPatrol } from '@/api/securityPatrolApi'
  45. import moment from 'moment'
  46. // 为了方便使用,绑定到原型链,在其他vue文件,直接this.mars3d 来使用
  47. Vue.prototype.mars3d = mars3d
  48. Vue.prototype.Cesium = mars3d.Cesium
  49. export default {
  50. name: 'addInspectionTask',
  51. props: {
  52. visible: Boolean,
  53. inspectionOptions: Array
  54. },
  55. data() {
  56. return {
  57. addTaskFrom: {
  58. securityPatrolName: '',
  59. securityPatrolContext: '',
  60. inspectionPoint: [{ value: '' }],
  61. responsiblePerson: ''
  62. },
  63. rules: {
  64. securityPatrolName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
  65. inspectionPoint: [{ required: true, validator: this.validateInspectionPoints, trigger: 'change' }],
  66. responsiblePerson: [{ required: true, message: '请选择责任人', trigger: 'change' }]
  67. },
  68. personList: [
  69. {
  70. name: '杨英'
  71. },
  72. {
  73. name: '马刚'
  74. },
  75. {
  76. name: '谢智宇'
  77. }
  78. ],
  79. fieldErrors: {},
  80. isPatrolling: false,
  81. currentPatrolIndex: 0,
  82. currentTime: '',
  83. patrolInterval: null,
  84. xcId: ''
  85. }
  86. },
  87. watch: {
  88. visible(val) {
  89. if (val) {
  90. this.addTaskFrom = {
  91. securityPatrolName: '',
  92. securityPatrolContext: '',
  93. inspectionPoint: [{ value: '' }],
  94. responsiblePerson: ''
  95. }
  96. }
  97. }
  98. },
  99. mounted() {
  100. this.$globalEventBus.$on('closeVideoPlayAdd', () => {
  101. this.stopPatrol()
  102. })
  103. this.$globalEventBus.$on('toPlayNextVideoAdd', () => {
  104. this.goToNextPatrolPoint()
  105. })
  106. },
  107. methods: {
  108. // 验证巡查点
  109. validateInspectionPoints(rule, value, callback) {
  110. this.fieldErrors = {}
  111. let isValid = true
  112. let hasDuplicate = false
  113. // 检查是否所有巡查点都已选择
  114. this.addTaskFrom.inspectionPoint.forEach((item, index) => {
  115. if (!item.value) {
  116. isValid = false
  117. this.$set(this.fieldErrors, index, '请选择巡查点')
  118. }
  119. })
  120. // 检查是否有重复选择
  121. const selectedValues = this.addTaskFrom.inspectionPoint.map((item) => item.value).filter((v) => v)
  122. const uniqueValues = new Set(selectedValues)
  123. if (selectedValues.length !== uniqueValues.size) {
  124. isValid = false
  125. hasDuplicate = true
  126. }
  127. if (!isValid) {
  128. if (hasDuplicate) {
  129. callback(new Error('不能重复选择相同的巡查点'))
  130. } else {
  131. callback(new Error('请确保所有巡查点都已选择'))
  132. }
  133. } else {
  134. callback()
  135. }
  136. },
  137. closeModal() {
  138. this.$emit('closeAddTask')
  139. },
  140. toSave() {
  141. this.$refs.addTaskFormRef.validate((valid) => {
  142. if (valid) {
  143. toAddSecurityPatrol(this.addTaskFrom).then((res) => {
  144. if (res.data) {
  145. this.$message.success('添加成功')
  146. this.closeModal()
  147. } else {
  148. this.$message.error('添加失败')
  149. }
  150. })
  151. }
  152. })
  153. },
  154. onPointSelect(a) {
  155. const obj = this.inspectionOptions.filter((item) => item.deviceCode == a)
  156. this.addTaskFrom.inspectionPoint.map((point) => {
  157. if (point.value == a) {
  158. Object.assign(point, obj[0])
  159. }
  160. })
  161. },
  162. startInspection() {
  163. this.$refs.addTaskFormRef.validate((valid) => {
  164. if (valid) {
  165. startPatrol(this.addTaskFrom).then((res) => {
  166. this.xcId = res.data.newSercurity.id
  167. })
  168. this.$emit('closeAddTask')
  169. this.currentTime = moment().format('YYYY-MM-DD HH:mm:ss')
  170. if (this.addTaskFrom.inspectionPoint === 0) {
  171. this.$message.warning('请先添加至少一个巡查点')
  172. return
  173. }
  174. this.addTaskFrom.inspectionPoint.forEach((item, index) => {
  175. this.fetchUrl(item).then((res) => {
  176. let url = ''
  177. if (res.code == 200) {
  178. url = res.data.streamUrl
  179. } else if (res.code == 400) {
  180. this.$message.error(res.msg)
  181. } else if (res.code == 4001) {
  182. this.$message.warning('设备离线')
  183. }
  184. this.$set(this.addTaskFrom.inspectionPoint[index], 'url', url)
  185. })
  186. })
  187. this.isPatrolling = true
  188. this.currentPatrolIndex = 0
  189. this.goToNextPatrolPoint()
  190. // 设置定时器,每20秒切换到下一个点
  191. this.patrolInterval = setInterval(() => {
  192. this.goToNextPatrolPoint()
  193. }, 20000)
  194. }
  195. })
  196. },
  197. fetchUrl(item) {
  198. return new Promise((resolve, reject) => {
  199. window
  200. .requestSDK(
  201. '/ttvideo/video/player/getVideoRealtimeUrl',
  202. {
  203. deviceCode: item.deviceCode,
  204. channelCode: item.channelCode,
  205. netType: '1',
  206. protocolType: 5,
  207. streamType: 1
  208. },
  209. {},
  210. 'post'
  211. )
  212. .then(async (res) => {
  213. resolve(res)
  214. })
  215. })
  216. },
  217. // 切换到下一个巡查点
  218. goToNextPatrolPoint() {
  219. if (this.currentPatrolIndex >= this.addTaskFrom.inspectionPoint.length) {
  220. const endTime = moment().format('YYYY-MM-DD HH:mm:ss')
  221. this.stopPatrol()
  222. this.$globalEventBus.$emit('clickVideoPlay', { visible: false, type: 'add' })
  223. endPatrol({ id: this.xcId, endTime: endTime, startTime: this.currentTime }).then((res) => {
  224. this.$emit('refreshData')
  225. })
  226. return
  227. }
  228. const point = this.addTaskFrom.inspectionPoint[this.currentPatrolIndex]
  229. this.flyToPoint(point)
  230. // 播放视频
  231. this.playVideo(point)
  232. this.currentPatrolIndex++
  233. },
  234. playVideo(point) {
  235. console.info(point)
  236. this.$globalEventBus.$emit('clickVideoPlay', { point: point, visible: true, type: 'add' })
  237. },
  238. stopPatrol() {
  239. this.isPatrolling = false
  240. clearInterval(this.patrolInterval)
  241. },
  242. // 定位到点
  243. flyToPoint(point) {
  244. window.map.flyToPoint([point.longitude, point.latitude], {
  245. radius: 5000, // 飞行距离目标点的距离
  246. duration: 2 // 飞行持续时间(秒)
  247. })
  248. },
  249. isLastItem(index) {
  250. return index === this.addTaskFrom.inspectionPoint.length - 1
  251. },
  252. isFirstItem() {
  253. return this.addTaskFrom.inspectionPoint.length > 1
  254. },
  255. addOption() {
  256. this.addTaskFrom.inspectionPoint.push({ value: '' })
  257. },
  258. removeOption(index) {
  259. if (this.addTaskFrom.inspectionPoint.length > 1) {
  260. this.addTaskFrom.inspectionPoint.splice(index, 1)
  261. this.$refs.addTaskFormRef.validateField('inspectionPoint');
  262. }
  263. }
  264. },
  265. destroyed() {
  266. this.stopPatrol()
  267. this.$globalEventBus.$off('closeVideoPlayAdd')
  268. }
  269. }
  270. </script>
  271. <style scoped lang="scss">
  272. .add-inspection-task-container {
  273. position: absolute;
  274. top: 0.8rem;
  275. right: 4.85rem;
  276. width: px-to-rem(422);
  277. z-index: 9999;
  278. .add-inspection-task-title {
  279. background: url('@/assets/image/common/popup_title_bg.png') no-repeat;
  280. background-size: 100% 100%;
  281. height: px-to-rem(39);
  282. display: flex;
  283. justify-content: space-between;
  284. align-items: center;
  285. padding: 0 px-to-rem(20);
  286. font-size: px-to-rem(16);
  287. color: #fff;
  288. .title-text {
  289. font-weight: bold;
  290. margin-left: px-to-rem(20);
  291. }
  292. }
  293. .add-inspection-task-content {
  294. padding: px-to-rem(20);
  295. min-height: px-to-rem(407);
  296. background: rgb(35, 61, 108, 0.8);
  297. :deep(.el-form) {
  298. .el-button {
  299. font-size: px-to-rem(14);
  300. }
  301. .el-form-item__label {
  302. color: #fff;
  303. font-size: px-to-rem(14);
  304. }
  305. .el-input__inner,
  306. .el-textarea__inner {
  307. background: rgba(79, 159, 255, 0.12);
  308. border-radius: 4px;
  309. border: 1px solid #4f9fff;
  310. background: transparent;
  311. color: #fff;
  312. }
  313. }
  314. .ins-point {
  315. display: flex;
  316. align-items: center;
  317. gap: px-to-rem(5);
  318. margin-bottom: px-to-rem(20);
  319. }
  320. .ins-point:last-child {
  321. margin-bottom: 0;
  322. }
  323. :deep(.el-button) {
  324. line-height: initial;
  325. background: rgba(79, 159, 255, 0.8);
  326. padding: px-to-rem(3) px-to-rem(7);
  327. }
  328. .cancelBtn {
  329. border: 1px solid #4f9fff;
  330. color: #fff;
  331. background-color: transparent;
  332. }
  333. }
  334. }
  335. </style>