addInspectionTask.vue 10 KB

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