addInspectionTask.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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,i) in inspectionOptions" :key="i"></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. this.$emit('refreshData')
  166. })
  167. this.$emit('closeAddTask')
  168. this.currentTime = moment().format('YYYY-MM-DD HH:mm:ss')
  169. if (this.addTaskFrom.inspectionPoint === 0) {
  170. this.$message.warning('请先添加至少一个巡查点')
  171. return
  172. }
  173. this.isPatrolling = true
  174. this.currentPatrolIndex = 0
  175. this.goToNextPatrolPoint()
  176. // 设置定时器,每20秒切换到下一个点
  177. this.patrolInterval = setInterval(() => {
  178. this.goToNextPatrolPoint()
  179. }, 20000)
  180. }
  181. })
  182. },
  183. fetchUrl(item) {
  184. return new Promise((resolve, reject) => {
  185. window
  186. .requestSDK(
  187. '/ttvideo/video/player/getVideoRealtimeUrl',
  188. {
  189. deviceCode: item.deviceCode,
  190. channelCode: item.channelCode,
  191. netType: 2,
  192. protocolType: 9,
  193. streamType: 1
  194. },
  195. {},
  196. 'post'
  197. )
  198. .then(async (res) => {
  199. resolve(res)
  200. })
  201. })
  202. },
  203. // 切换到下一个巡查点
  204. goToNextPatrolPoint() {
  205. if (this.currentPatrolIndex >= this.addTaskFrom.inspectionPoint.length) {
  206. const endTime = moment().format('YYYY-MM-DD HH:mm:ss')
  207. this.stopPatrol()
  208. this.$globalEventBus.$emit('clickVideoInspectPlay', { visible: false, type: 'add' })
  209. endPatrol({ id: this.xcId, endTime: endTime, startTime: this.currentTime }).then((res) => {
  210. this.$emit('refreshData')
  211. })
  212. return
  213. }
  214. const point = this.addTaskFrom.inspectionPoint[this.currentPatrolIndex]
  215. this.flyToPoint(point)
  216. // 播放视频
  217. this.playVideo(point)
  218. this.currentPatrolIndex++
  219. },
  220. playVideo(point) {
  221. this.fetchUrl(point).then((res) => {
  222. if (res.code == 200) {
  223. point.url = res.data.streamUrl
  224. this.addTaskFrom.countPatrolPoints = this.addTaskFrom.inspectionPoint.length
  225. this.$globalEventBus.$emit('clickVideoInspectPlay', {taskInfo:this.addTaskFrom, point: point, visible: true, type: 'add' })
  226. } else if (res.code == 4001) {
  227. this.$message.warning(JSON.parse(res.msg).resultMsg)
  228. }
  229. })
  230. },
  231. stopPatrol() {
  232. this.isPatrolling = false
  233. clearInterval(this.patrolInterval)
  234. },
  235. // 定位到点
  236. flyToPoint(point) {
  237. window.map.flyToPoint([point.longitude, point.latitude], {
  238. radius: 5000, // 飞行距离目标点的距离
  239. duration: 2 // 飞行持续时间(秒)
  240. })
  241. },
  242. isLastItem(index) {
  243. return index === this.addTaskFrom.inspectionPoint.length - 1
  244. },
  245. isFirstItem() {
  246. return this.addTaskFrom.inspectionPoint.length > 1
  247. },
  248. addOption() {
  249. this.addTaskFrom.inspectionPoint.push({ value: '' })
  250. },
  251. removeOption(index) {
  252. if (this.addTaskFrom.inspectionPoint.length > 1) {
  253. this.addTaskFrom.inspectionPoint.splice(index, 1)
  254. this.$refs.addTaskFormRef.validateField('inspectionPoint')
  255. }
  256. }
  257. },
  258. destroyed() {
  259. this.stopPatrol()
  260. this.$globalEventBus.$off('closeVideoPlayAdd')
  261. }
  262. }
  263. </script>
  264. <style scoped lang="scss">
  265. .add-inspection-task-container {
  266. position: absolute;
  267. top: 0.8rem;
  268. right: 4.85rem;
  269. width: px-to-rem(422);
  270. z-index: 9999;
  271. .add-inspection-task-title {
  272. background: url('@/assets/image/common/popup_title_bg.png') no-repeat;
  273. background-size: 100% 100%;
  274. height: px-to-rem(39);
  275. display: flex;
  276. justify-content: space-between;
  277. align-items: center;
  278. padding: 0 px-to-rem(20);
  279. font-size: px-to-rem(16);
  280. color: #fff;
  281. .title-text {
  282. font-weight: bold;
  283. margin-left: px-to-rem(20);
  284. }
  285. }
  286. .add-inspection-task-content {
  287. padding: px-to-rem(20);
  288. min-height: px-to-rem(407);
  289. background: rgb(35, 61, 108, 0.8);
  290. :deep(.el-form) {
  291. .el-button {
  292. font-size: px-to-rem(14);
  293. }
  294. .el-form-item__label {
  295. color: #fff;
  296. font-size: px-to-rem(14);
  297. }
  298. .el-input__inner,
  299. .el-textarea__inner {
  300. background: rgba(79, 159, 255, 0.12);
  301. border-radius: 4px;
  302. border: 1px solid #4f9fff;
  303. background: transparent;
  304. color: #fff;
  305. }
  306. }
  307. .ins-point {
  308. display: flex;
  309. align-items: center;
  310. gap: px-to-rem(5);
  311. margin-bottom: px-to-rem(20);
  312. }
  313. .ins-point:last-child {
  314. margin-bottom: 0;
  315. }
  316. :deep(.el-button) {
  317. line-height: initial;
  318. background: rgba(79, 159, 255, 0.8);
  319. padding: px-to-rem(3) px-to-rem(7);
  320. }
  321. .cancelBtn {
  322. border: 1px solid #4f9fff;
  323. color: #fff;
  324. background-color: transparent;
  325. }
  326. }
  327. }
  328. </style>