rapidRep.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <template>
  2. <view class="handler">
  3. <view class="body view-body">
  4. <view class="form_item column">
  5. <view class="title"><text class="required newicon newicon-bitian"></text>故障描述:</view>
  6. <uni-easyinput class="value" type="textarea" v-model="dataInfo.deferralRemark" placeholder="请输入故障描述" :class="{formRed: isSubmit && !dataInfo.deferralRemark.trim()}" />
  7. </view>
  8. <view class="candidate">
  9. <view class="candidate-item" v-for="item in candidateData" :key="item" @click="itemCandidate(item)">{{item.name}}</view>
  10. </view>
  11. <view class="form_item" v-if="cmdbRepair.valueconfig==1">
  12. <view class="title select">关联资产:</view>
  13. <input class="item-input" focus placeholder="请扫描资产卡二维码" v-model="dataInfo.property"/>
  14. <text class="newicon newicon-saoma icon" @click="scanCodes"></text>
  15. </view>
  16. <view class="form_item">
  17. <view class="title">照片录像:</view>
  18. <view class="value">
  19. <uni-file-picker ref="handlerImgRef" v-model="dataInfo.handlerImgList"
  20. limit="4" @success="handlerImgSuccess"
  21. @fail="handlerImgFail" @select="handlerImgSelect" @delete="handlerImgDelete">
  22. </uni-file-picker>
  23. <view class="imgTips">(支持JPG/PNG格式图片,单张大小3M以内,录像支持30秒)</view>
  24. </view>
  25. </view>
  26. <view class="form_item">
  27. <view class="title">录音:</view>
  28. <view class="chunk" v-if="!dataInfo.recBlob" @click="examineRecord" @mouseup="recStop">按住录音</view>
  29. <view v-if="dataInfo.recBlob">
  30. <!-- <audio src="dataInfo.recBlob" controls></audio> -->
  31. <icon type="clear" size="26" @click="clearRec"/>
  32. </view>
  33. </view>
  34. </view>
  35. <view class="foot_common_btns">
  36. <button @click="goBackOrToList" type="default" class="cancelButton btn">返回</button>
  37. <button @click="submit" type="default" class="primaryButton btn">下一步</button>
  38. </view>
  39. </view>
  40. </template>
  41. <script setup>
  42. import { SM } from "@/http/http.js"
  43. import { ref, reactive, computed } from 'vue'
  44. import NumberModal from '@/components/NumberModal.vue';
  45. import { onLoad } from '@dcloudio/uni-app'
  46. import { generateNumberArray } from '@/utils/index.js'
  47. import { api_group, api_incidentDetail, api_getSolution, api_user, api_incidentTask, api_branch, api_dutyDepartment, api_getDictionary, api_querySummaryDoc, api_addSummaryDoc } from "@/http/api.js"
  48. import { defaultColor } from '@/static/js/theme.js'
  49. import { useSetTitle } from '@/share/useSetTitle.js'
  50. import { useMakePhoneCall } from '@/share/useMakePhoneCall.js'
  51. import { useUploadFile } from '@/share/useUploadFile.js'
  52. import { useGoBack } from '@/share/useGoBack.js'
  53. import { useLoginUserStore } from '@/stores/loginUser'
  54. import { useHandlerStore } from '@/stores/handler'
  55. import Recorder from 'recorder-core';
  56. import 'recorder-core/src/engine/mp3';
  57. import 'recorder-core/src/engine/mp3-engine';
  58. import 'recorder-core/src/engine/wav';
  59. import 'recorder-core/src/extensions/waveview';
  60. useSetTitle();
  61. const loginUserStore = useLoginUserStore();
  62. const handlerStore = useHandlerStore();
  63. const { makePhoneCall } = useMakePhoneCall();
  64. const { uploadFile } = useUploadFile();
  65. const { goBack } = useGoBack();
  66. // 主题颜色
  67. const primaryColor = ref(defaultColor)
  68. // 数据
  69. const dataInfo = reactive({
  70. tabs: [
  71. // {id: 5, name: '故障处理', value: 'doing', num: ''},
  72. // {id: 6, name: '延期处理', value: 'overtime', num: ''},
  73. ],
  74. tabActiveValue: 0,//当前选择的tab
  75. incidentId: undefined,//事件ID
  76. incidentData: {},//事件对象
  77. deferralRemark: '',//故障描述
  78. property:'', //资产
  79. handlerImgList: [],//处理图片列表
  80. recBlob:'' //录音
  81. })
  82. // 故障处理用是否提供备用机
  83. const newProvideBackupMachine = ref(0)
  84. // 知识库id
  85. const solutionId = ref(null)
  86. // 是否提交
  87. const isSubmit = ref(false)
  88. // 处理图片
  89. const handlerImgRef = ref(null)
  90. const candidateData = ref([
  91. {name:'马桶肃杀'},
  92. {name:'马桶肃杀'},
  93. {name:'马桶肃杀'},
  94. {name:'马桶肃杀'},
  95. {name:'马桶肃杀'},
  96. {name:'马桶肃杀'},
  97. {name:'马桶肃杀'},
  98. {name:'马桶肃杀'},
  99. {name:'马桶肃杀'},
  100. ])
  101. const cmdbRepair = ref(null)
  102. let rec = null;
  103. let wave = null;
  104. const recwave = ref(null);
  105. // 上一步或者返回列表
  106. function goBackOrToList(){
  107. uni.setStorageSync('repairData','')
  108. goBack();
  109. }
  110. function itemCandidate(item){
  111. dataInfo.deferralRemark = item.name
  112. }
  113. // 获取录音权限
  114. function recOpen() {
  115. rec = Recorder({
  116. type: 'wav', //录音格式,可以换成wav等其他格式
  117. sampleRate: 16000, //录音的采样率,越大细节越丰富越细腻
  118. bitRate: 16, //录音的比特率,越大音质越好
  119. onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate) => {
  120. if (wave) {
  121. wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
  122. }
  123. },
  124. });
  125. if (!rec) {
  126. alert('当前浏览器不支持录音功能!');
  127. return;
  128. }
  129. //打开录音,获得权限
  130. rec.open(
  131. () => {
  132. console.log('录音已打开');
  133. startRecord()
  134. if (recwave.value) {
  135. //创建音频可视化图形绘制对象
  136. wave = Recorder.WaveView({ elem: recwave.value });
  137. }
  138. },
  139. (msg, isUserNotAllow) => {
  140. //用户拒绝了录音权限,或者浏览器不支持录音
  141. console.log((isUserNotAllow ? 'UserNotAllow,' : '') + '无法录音:' + msg);
  142. },
  143. );
  144. // uni.getSetting({
  145. // success(res) {
  146. // // 判断是否开启了录音权限
  147. // if (res.authSetting['scope.record']) {
  148. // startRecord()
  149. // } else {
  150. // uni.showToast({
  151. // icon: 'none',
  152. // title: '录音权限未开启,无法录音',
  153. // mask: true,
  154. // });
  155. // }
  156. // }
  157. // })
  158. }
  159. // 检查录音
  160. function examineRecord() {
  161. recOpen()
  162. }
  163. function startRecord(){
  164. if (!rec) {
  165. console.error('未打开录音');
  166. return;
  167. }
  168. rec.start();
  169. }
  170. // 结束录音
  171. function recStop() {
  172. if (!rec) {
  173. console.error('未打开录音');
  174. return;
  175. }
  176. rec.stop(
  177. (blob, duration) => {
  178. //blob就是我们要的录音文件对象,可以上传,或者本地播放
  179. dataInfo.recBlob = blob;
  180. //简单利用URL生成本地文件地址,此地址只能本地使用,比如赋值给audio.src进行播放,赋值给a.href然后a.click()进行下载(a需提供download="xxx.mp3"属性)
  181. const localUrl = (window.URL || window.webkitURL).createObjectURL(blob);
  182. console.log('录音成功', blob, localUrl, '时长:' + duration + 'ms');
  183. // upload(blob); //把blob文件上传到服务器
  184. rec.close(); //关闭录音,释放录音资源,当然可以不释放,后面可以连续调用start
  185. rec = null;
  186. },
  187. (err) => {
  188. console.error('结束录音出错:' + err);
  189. rec.close(); //关闭录音,释放录音资源,当然可以不释放,后面可以连续调用start
  190. rec = null;
  191. },
  192. );
  193. }
  194. // 删除录音
  195. function clearRec(){
  196. dataInfo.recBlob = null
  197. }
  198. // 扫码资产码
  199. function scanCodes(){
  200. SM().then((res) => {
  201. let postData = {
  202. code: ress1,
  203. account: loginUserStore.loginUser.user.account,
  204. };
  205. // api_scanCode(postData).then((res) => {
  206. // uni.hideLoading();
  207. // if (res.status == 200) {
  208. // inspectionValueStore.setInspectionValueData(res.data);
  209. // uni.navigateTo({
  210. // url: `/pages/inspection/inspectionValue/inspectionValue?inspectionExecuteId=${data.id}`
  211. // })
  212. // } else {
  213. // uni.showToast({
  214. // icon: 'none',
  215. // title: res.msg || '请求数据失败!'
  216. // });
  217. // }
  218. // });
  219. })
  220. }
  221. // 上传处理图片成功
  222. function handlerImgSuccess(e){
  223. dataInfo.handlerImgList.forEach(v => {
  224. v.url = v.path;
  225. })
  226. console.log(dataInfo.handlerImgList);
  227. let handlerOrder$ = handlerOrder();
  228. let requestList = [handlerOrder$];
  229. dataInfo.handlerImgList.forEach(v => {
  230. let handlerOrderImg$ = handlerOrderImg(v);
  231. requestList.push(handlerOrderImg$);
  232. })
  233. Promise.all(requestList).then(resList => {
  234. uni.hideLoading();
  235. console.log(resList);
  236. if(resList[0].state == 200){
  237. uni.showToast({
  238. icon: 'none',
  239. title: '处理成功',
  240. mask: true,
  241. });
  242. setTimeout(() => {
  243. uni.reLaunch({
  244. url: '/pages/incidentList/incidentList',
  245. })
  246. }, 1500)
  247. }else{
  248. uni.showToast({
  249. icon: 'none',
  250. title: resList[0].msg || '请求数据失败!'
  251. });
  252. }
  253. })
  254. }
  255. // 上传处理图片失败
  256. function handlerImgFail(e){
  257. dataInfo.handlerImgList.forEach(v => {
  258. v.url = v.path;
  259. })
  260. console.log(dataInfo.handlerImgList);
  261. }
  262. // 选择上传图片
  263. function handlerImgSelect(e){
  264. dataInfo.handlerImgList = dataInfo.handlerImgList.concat(e.tempFiles);
  265. console.log(dataInfo.handlerImgList);
  266. }
  267. // 删除上传图片
  268. function handlerImgDelete(e){
  269. dataInfo.handlerImgList = dataInfo.handlerImgList.filter(v => e.tempFile.uuid != v.uuid);
  270. console.log(dataInfo.handlerImgList);
  271. }
  272. // 获取事件详情
  273. function getIncidentDetail(){
  274. if(uni.getStorageSync('repairData')){
  275. let data = JSON.parse(uni.getStorageSync('repairData'))
  276. if(data){
  277. dataInfo.deferralRemark = data.deferralRemark
  278. dataInfo.property = data.property
  279. dataInfo.handlerImgList = data.handlerImgList
  280. dataInfo.recBlob = data.recBlob
  281. }
  282. }
  283. }
  284. // 下一步
  285. function submit(){
  286. isSubmit.value = true;
  287. if(dataInfo.deferralRemark==''){
  288. uni.showToast({
  289. icon: 'none',
  290. title: '请选择输入故障描述'
  291. });
  292. return;
  293. }
  294. uni.setStorageSync('repairData',JSON.stringify(dataInfo))
  295. uni.navigateTo({
  296. url: `/pages/repair/rapidRepNext`,
  297. });
  298. }
  299. // 处理提交事件
  300. function handlerOrder(){
  301. dataInfo.incidentData.returnBackupMachine = dataInfo.returnBackupMachine
  302. let postData = {
  303. incident: dataInfo.incidentData,
  304. solutionId:solutionId.value
  305. }
  306. postData.incident.handleDescription = dataInfo.handleDescription;
  307. postData.incident.handleCategory = {id: dataInfo.handleCategory};
  308. postData.incident.closecode = {id: dataInfo.closecode};
  309. postData.incident.category = dataInfo.category;
  310. postData.incident.synergetic = dataInfo.synergetic;
  311. return api_incidentTask(dataInfo.tabActiveValue, postData);
  312. }
  313. // 处理图片
  314. function handlerOrderImg(imgObj){
  315. return uploadFile(imgObj, 'incident', dataInfo.incidentId)
  316. }
  317. onLoad((option) => {
  318. // let storeData = handlerStore.handler.data
  319. // if(storeData && storeData.type=='rep'){
  320. // solutionId.value = storeData.solutionId
  321. // dataInfo.isSummaryNext = storeData.isSummaryNext
  322. // dataInfo.incidentId = storeData.incidentId;
  323. // }else{
  324. // dataInfo.incidentId = option.incidentId;
  325. // dataInfo.isSummaryNext = option.isSummaryNext == 1;
  326. // }
  327. let data = JSON.parse(uni.getStorageSync('sysData'))
  328. cmdbRepair.value = data.find(i=>i.keyconfig=='cmdbRepair')
  329. getIncidentDetail();
  330. })
  331. </script>
  332. <style lang="scss" scoped>
  333. .handler{
  334. height: 100%;
  335. display: flex;
  336. flex-direction: column;
  337. justify-content: space-between;
  338. padding: 0 30rpx;
  339. .head{
  340. height: 88rpx;
  341. display: flex;
  342. position: fixed;
  343. z-index: 99;
  344. width: 100%;
  345. background-color: #fff;
  346. font-size: 30rpx;
  347. .tab{
  348. flex: 1;
  349. display: flex;
  350. justify-content: center;
  351. align-items: center;
  352. border-bottom: 4rpx solid transparent;
  353. &.active{
  354. color: $uni-primary;
  355. border-color: $uni-primary;
  356. }
  357. }
  358. }
  359. .body{
  360. box-sizing: border-box;
  361. flex: 1;
  362. min-height: 0;
  363. &.bg{
  364. background-color: #F7F7F7;
  365. }
  366. .summaryItem{
  367. &:first-of-type{
  368. .summaryItem_head{
  369. border-bottom: 1rpx solid #DDDDDD;
  370. }
  371. }
  372. .summary_total{
  373. padding: 20rpx 0;
  374. display: flex;
  375. justify-content: center;
  376. align-items: center;
  377. }
  378. .summaryItem_head{
  379. padding: 24rpx;
  380. font-size: 26rpx;
  381. color: #3A3A3A;
  382. }
  383. .summaryItem_body{
  384. font-size: 30rpx;
  385. background-color: #fff;
  386. .summaryItem_bodyItem{
  387. padding: 24rpx;
  388. border-bottom: 1rpx solid #DDDDDD;
  389. .summaryItem_bodyItem_top{
  390. display: flex;
  391. justify-content: space-between;
  392. align-items: center;
  393. .value{
  394. padding-left: 48rpx;
  395. flex-shrink: 0;
  396. }
  397. }
  398. .summaryItem_bodyItem_bottom{
  399. margin-top: 24rpx;
  400. display: flex;
  401. justify-content: space-between;
  402. align-items: center;
  403. .name{
  404. text-align: right;
  405. flex: 1;
  406. }
  407. .value{
  408. width: 240rpx;
  409. text-align: right;
  410. padding-left: 48rpx;
  411. flex-shrink: 0;
  412. }
  413. }
  414. }
  415. }
  416. .summaryItem_foot{
  417. font-size: 30rpx;
  418. background-color: #fff;
  419. &.total{
  420. margin-top: 24rpx;
  421. }
  422. .summaryItem_foot_total{
  423. padding: 24rpx 0;
  424. display: flex;
  425. justify-content: center;
  426. align-items: center;
  427. }
  428. .summaryItem_foot_add{
  429. border-top: 1rpx solid #DDDDDD;
  430. padding: 24rpx 0;
  431. display: flex;
  432. justify-content: center;
  433. align-items: center;
  434. .newicon-icon-test{
  435. font-size: 30rpx;
  436. font-weight: bold;
  437. }
  438. }
  439. }
  440. }
  441. .form_item_column{
  442. padding-top: 24rpx;
  443. min-height: 86rpx;
  444. .form_item{
  445. padding-top: 0;
  446. min-height: auto;
  447. }
  448. }
  449. .candidate{
  450. display: flex;
  451. flex-wrap: wrap;
  452. .candidate-item{
  453. padding: 6rpx 15rpx;
  454. font-size: 26rpx;
  455. color: #949494;
  456. background: #E9E9E9;
  457. border-radius: 50rpx;
  458. margin-right: 20rpx;
  459. margin-top: 15rpx;
  460. }
  461. }
  462. .form_item{
  463. display: flex;
  464. align-items: center;
  465. padding-top: 24rpx;
  466. min-height: 86rpx;
  467. position: relative;
  468. .chunk{
  469. width: 100%;
  470. height: 70rpx;
  471. line-height: 70rpx;
  472. text-align: center;
  473. background: #F7F8FA;
  474. box-shadow: 0px 3px 6px 1px rgba(0,0,0,0.16);
  475. border-radius: 10rpx;
  476. }
  477. &.column{
  478. height: auto;
  479. flex-direction: column;
  480. align-items: flex-start;
  481. .import-rep{
  482. padding: 5rpx 10rpx;
  483. border-radius: 50rpx;
  484. background: #d1fcd5;
  485. color: #49b856;
  486. font-size: 24rpx;
  487. }
  488. .title{
  489. margin-right: 0;
  490. }
  491. .title-width{
  492. width: 100%;
  493. }
  494. .title-fl-sb{
  495. display: flex;
  496. justify-content: space-between;
  497. width: 100%;
  498. }
  499. .value{
  500. margin-top: 10rpx;
  501. // padding-left: 20rpx;
  502. box-sizing: border-box;
  503. }
  504. .tips{
  505. padding: 24rpx;
  506. text-align: center;
  507. font-size: 22rpx;
  508. color: #909399;
  509. width: 100%;
  510. box-sizing: border-box;
  511. }
  512. }
  513. .title{
  514. font-size: 26rpx;
  515. display: flex;
  516. align-items: center;
  517. margin-right: 12rpx;
  518. flex-shrink: 0;
  519. &.select{
  520. width: calc(5em + 20rpx);
  521. }
  522. }
  523. .value{
  524. width: 100%;
  525. &.category{
  526. width: 100%;
  527. display: flex;
  528. justify-content: space-between;
  529. align-items: center;
  530. .categoryName{
  531. font-size: 26rpx;
  532. color: #555;
  533. flex: 1;
  534. }
  535. .newicon-weibiaoti2010104{
  536. color: $uni-primary;
  537. margin-left: 24rpx;
  538. }
  539. }
  540. .imgTips{
  541. color: #909399;
  542. font-size: 22rpx;
  543. margin-top: 10rpx;
  544. }
  545. }
  546. .item-input{
  547. border: 1px solid #DBDBDB;
  548. height: 70rpx;
  549. line-height: 70rpx;
  550. padding: 0 10rpx;
  551. border-radius: 4rpx;
  552. width: 100%;
  553. font-size: 14px;
  554. }
  555. .icon{
  556. position: absolute;
  557. right: 20rpx;
  558. color: #49b856;
  559. }
  560. .synergeticNames{
  561. font-size: 26rpx;
  562. margin-right: 24rpx;
  563. }
  564. .synergeticAdd{
  565. flex-shrink: 0;
  566. }
  567. }
  568. }
  569. }
  570. </style>