conductInspections.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  1. <!-- 开展检查 -->
  2. <template>
  3. <view class="inspectAdd">
  4. <scroll-view scroll-y @scrolltolower="scrollGet" class="info-max-box">
  5. <view class="list">
  6. <view class="list-li srearch">
  7. <view class="srearch-l">{{newData.subName?newData.subName:'实验室名称'}}</view>
  8. </view>
  9. <view class="list-li" style="margin-bottom: 20rpx;border:none;">
  10. <view>
  11. {{newData.roomNum?newData.roomNum:''}}房间&{{newData.buildName?newData.buildName:'楼栋'}}&{{newData.deptName?newData.deptName:'学院单位'}}
  12. </view>
  13. </view>
  14. <view class="list-li">
  15. <view>现场照片</view>
  16. </view>
  17. <view v-if="form.photoList[0]" class="check-for-img-max-box">
  18. <view class="left-title-p"></view>
  19. <view class="right-img-box">
  20. <view class="img-box" v-for="(imgUrl,imgIndex) in form.photoList" :key="imgIndex">
  21. <img class="img-data" :src="baseUrl+imgUrl.fileUrl">
  22. <img class="position-img" :src="imagesUrl('commonality/icon_ssp_closure.png')"
  23. @click="delImg(imgIndex)">
  24. </view>
  25. <!-- <img class="add-button" :src="imagesUrl('safetyCheck/img_ssp_pz@1x.png')"
  26. @click="selectImage()" v-if="form.photoList.length<6"> -->
  27. </view>
  28. </view>
  29. <view class="list-li" @click="checkItemModuleButton('open')" style="border-bottom: none;">
  30. <view> {{newData.hazardCheckCode?newData.hazardCheckCode:''}}
  31. {{newData.hazardCheckName?newData.hazardCheckName:'检查项'}}
  32. </view>
  33. <view style="color: #0183FA;">
  34. <img :src="imagesUrl('commonality/icon_wd_gd@1x.png')">
  35. </view>
  36. </view>
  37. <view class="hazardCheckNum" @click="hiddenDangerClick()">
  38. <img class="hazardCheckNum-l" :src="imagesUrl('safetyCheck/icon_aqjc_yinhuan.png')">
  39. <view class="hazardCheckNum-r">{{'已连续出现'+hazardCheckNum+'次隐患'}} </view>
  40. </view>
  41. <view class="list-li" v-if="!form.checkFlag">
  42. <view>隐患描述</view>
  43. <view></view>
  44. </view>
  45. <view class="describe">
  46. <textarea v-if="!form.checkFlag && !checkItemModuleType" cursor-spacing="40" class="describe-n" type="text"
  47. v-model="form.hazardDescribe" maxlength="200" placeholder="请填写隐患描述"
  48. placeholder-style="font-size:24rpx;color:#999;"></textarea>
  49. </view>
  50. </view>
  51. <view class="voice">
  52. <view class="voice_t">
  53. <view class="voice_t_t" v-if="!form.voiceList[0]"><text>语音备注:</text><text>按住话筒录音</text></view>
  54. <view class="voice_t_b" v-if="form.voiceList[0]">语音备注:({{form.voiceList.length}})</view>
  55. </view>
  56. <view class="voice_b" v-if="form.voiceList[0]" v-for="(voiceItem,voiceIndex) in form.voiceList"
  57. :key="voiceIndex">
  58. <view class="voice_b_l" @tap="voicePlay(voiceItem.fileUrl)">
  59. <img class="add-button" :src="imagesUrl('safetyCheck/icon_yybz_yy.png')" />
  60. {{voiceItem.fileLength}}″
  61. </view>
  62. <img class="voice_b_r" :src="imagesUrl('safetyCheck/icon_yybz_sc.png')"
  63. @tap="voiceDele('',voiceIndex)" />
  64. </view>
  65. <!-- <view class="voice-btn">
  66. <img class="voice_t_r" :src="imagesUrl('commonality/icon_sskz_azsh.png')"
  67. @longpress.stop="recordButton(item,$event)" @touchmove.stop="cancelButton"
  68. @touchend.stop="sendButton(item,$event)" />
  69. <text>按住说话</text>
  70. </view> -->
  71. </view>
  72. </scroll-view>
  73. <checkItemModule :propsData="propsData" v-if="checkItemModuleType"></checkItemModule>
  74. <!-- <view class="sub-btn">
  75. <view @click="submitForm(1)">保存草稿</view>
  76. <view @click="submitForm(2)">提交</view>
  77. </view> -->
  78. <view class="sub-btn" v-if="!checkItemModuleType">
  79. <!-- #ifdef MP-WEIXIN -->
  80. <img
  81. @longpress.stop="recordButton(item,$event)"
  82. @touchmove.stop="cancelButton"
  83. @touchend.stop="sendButton(item,$event)"
  84. :src="imagesUrl('safetyCheck/icon_aqjc_luyin.png')" />
  85. <!-- #endif -->
  86. <!-- #ifdef WEB -->
  87. <img
  88. @touchstart="handleTouchStart"
  89. @touchmove="handleTouchMove"
  90. @touchend="handleTouchEnd"
  91. @touchcancel="handleTouchEnd"
  92. @contextmenu.prevent="handleContextMenu"
  93. :src="imagesUrl('safetyCheck/icon_aqjc_luyin.png')" />
  94. <!-- #endif -->
  95. <img @click="selectImage()" :src="imagesUrl('safetyCheck/icon_aqjc_paizhao.png')" />
  96. <img @click="submitForm(2)" :src="imagesUrl('safetyCheck/icon_aqjc_tijiao.png')" />
  97. </view>
  98. <view v-if="!checkItemModuleType" class="inspectRecord" @click="inspectRecordClick()">检查记录</view>
  99. </view>
  100. </template>
  101. <script>
  102. // #ifdef WEB
  103. import Recorder from 'recorder-core';
  104. import 'recorder-core/src/engine/mp3';
  105. import 'recorder-core/src/engine/mp3-engine';
  106. // #endif
  107. import {
  108. config
  109. } from '@/api/request/config.js'
  110. import {
  111. securityAppCheckPlanCheckInfo,
  112. securityAppCheckSetOptionCheckCommit,
  113. securityAppCheckPhotoGetCheckNumBySub,
  114. } from '@/pages_safetyCheck/api/index.js'
  115. import checkItemModule from '@/pages_safetyCheck/component/checkItemModule.vue'
  116. export default {
  117. name: "inspectAdd",
  118. components: {
  119. checkItemModule
  120. },
  121. data() {
  122. return {
  123. baseUrl: config.base_url,
  124. form: {
  125. checkFlag: false,
  126. photoList: [],
  127. voiceList: [],
  128. },
  129. newData: {},
  130. radioList: [{
  131. id: 1,
  132. name: '符合',
  133. checked: false,
  134. },
  135. {
  136. id: 0,
  137. name: '不符合',
  138. checked: true,
  139. },
  140. ],
  141. pageType: '',
  142. sendLock: true, //发送锁,当为true时上锁,false时解锁发送
  143. recorderManager: wx.getRecorderManager(),
  144. optionData: null,
  145. //检查项组件数据
  146. checkItemModuleType: false,
  147. propsData: {},
  148. hazardCheckNum:0,
  149. //广播相关
  150. liveType: false,
  151. sendLock: true, //发送锁,当为true时上锁,false时解锁发送
  152. //H5
  153. recording: false,
  154. recorder: null,
  155. audioBlob: null,
  156. audioPath: null,
  157. }
  158. },
  159. onLoad(option) {
  160. let optionData = JSON.parse(decodeURIComponent(option.infoData));
  161. uni.setNavigationBarTitle({
  162. title: optionData.subName + '(' + (optionData.pageType == 0 ? (optionData.roomNum ? optionData
  163. .roomNum : '-') : (optionData
  164. .subRoom ? optionData
  165. .subRoom : '-')) + ')'
  166. })
  167. this.$set(this, 'pageType', optionData.pageType);
  168. this.$set(this, 'optionData', optionData);
  169. },
  170. onShow() {
  171. // #ifdef MP-WEIXIN
  172. if (this.recorderManager) {
  173. this.recorderManager.stop();
  174. }
  175. // #endif
  176. },
  177. mounted() {
  178. if (this.pageType == 0) {
  179. this.securityAppCheckPlanCheckInfo();
  180. this.securityAppCheckPhotoGetCheckNumBySub();
  181. }
  182. },
  183. methods: {
  184. //折叠展开
  185. inspectDot() {
  186. this.newData.inspectDot = !this.newData.inspectDot
  187. },
  188. //检查记录
  189. inspectRecordClick(){
  190. let infoData = this.optionData;
  191. infoData.curTabTow=1;//已检查
  192. uni.navigateTo({
  193. url: '/pages_safetyCheck/views/itemsManage/hiddenDangerItems?infoData=' +
  194. encodeURIComponent(JSON
  195. .stringify(infoData))
  196. });
  197. },
  198. //获取提交详情
  199. async securityAppCheckPlanCheckInfo() {
  200. let obj = {
  201. setOptionId: this.optionData.setOptionId,
  202. manageId: this.optionData.manageId,
  203. }
  204. const {
  205. data
  206. } = await securityAppCheckPlanCheckInfo(obj);
  207. if (data.code == 200) {
  208. data.data.inspectDot = true;
  209. this.$set(this, 'newData', data.data);
  210. //编辑的时候
  211. this.$set(this.form, 'checkFlag', data.data.checkFlag ? data.data.checkFlag : false);
  212. if(data.data.hazardDescribe){
  213. this.$set(this.form, 'hazardDescribe', data.data.hazardDescribe);
  214. }else{
  215. this.$set(this.newData,'hazardCheckPro',this.optionData.hazardCheckPro);
  216. this.$set(this.newData,'hazardCheckCode',this.optionData.hazardCheckCode);
  217. this.$set(this.newData,'hazardCheckName',this.optionData.hazardCheckName);
  218. this.$set(this.newData,'hazardCheckPro1',this.optionData.hazardCheckPro1);
  219. this.$set(this.newData,'hazardCheckCode1',this.optionData.hazardCheckCode1);
  220. this.$set(this.newData,'hazardCheckName1',this.optionData.hazardCheckName1);
  221. this.$set(this.newData,'hazardCheckPro2',this.optionData.hazardCheckPro2);
  222. this.$set(this.newData,'hazardCheckCode2',this.optionData.hazardCheckCode2);
  223. this.$set(this.newData,'hazardCheckName2',this.optionData.hazardCheckName2);
  224. this.$set(this.newData,'hazardCheckPoint',this.optionData.hazardCheckPoint);
  225. this.$set(this.form,'hazardDescribe',this.optionData.hazardCheckPoint);
  226. // let list=[];
  227. // list=data.data.hazardCheckPoint.split('#');
  228. // this.$set(this.form, 'hazardDescribe', list[0]);
  229. }
  230. this.$set(this.form, 'photoList', data.data.photoList ? data.data.photoList : []);
  231. this.$set(this.form, 'voiceList', data.data.voiceList ? data.data.voiceList : []);
  232. }
  233. },
  234. hiddenDangerClick(row) {
  235. if(!this.hiddenDangerRecordStatus){
  236. let infoData = this.optionData;
  237. infoData.subName=this.newData.subName;
  238. infoData.roomNum=this.newData.roomNum;
  239. infoData.hiddenDangerRecordStatus = true;//判断是否跳转隐患列表
  240. uni.navigateTo({
  241. url: '/pages_safetyCheck/views/inspectManage/hiddenDangerRecord?infoData=' +
  242. encodeURIComponent(JSON.stringify(infoData))
  243. });
  244. }
  245. },
  246. checkItemModuleButton(type, item,keyPoint) {
  247. if (type == 'open') {
  248. let obj = {
  249. infoType: this.pageType,
  250. checkType: 0,
  251. editStatus:this.optionData.editStatus,
  252. editItem:{
  253. hazardCheckName:this.newData.hazardCheckName,
  254. hazardCheckName1:this.newData.hazardCheckName1,
  255. hazardCheckName2:this.newData.hazardCheckName2,
  256. hazardCheckCode:this.newData.hazardCheckCode,
  257. hazardCheckCode1:this.newData.hazardCheckCode1,
  258. hazardCheckCode2:this.newData.hazardCheckCode2,
  259. hazardCheckPoint:this.newData.hazardCheckPoint,
  260. hazardCheckPro:this.newData.hazardCheckPro,
  261. hazardCheckPro1:this.newData.hazardCheckPro1,
  262. hazardCheckPro2:this.newData.hazardCheckPro2,
  263. setOptionId:this.newData.setOptionId,
  264. }
  265. }
  266. if (this.pageType == 0) {
  267. //检查
  268. obj.manageId = this.optionData.manageId
  269. obj.planSetId = this.optionData.planSetId
  270. }
  271. this.$set(this, 'propsData', obj);
  272. this.$set(this, 'checkItemModuleType', true);
  273. } else if (type == 'out') {
  274. this.$set(this, 'checkItemModuleType', false);
  275. this.$set(this, 'propsData', {});
  276. } else if (type == 'submit') {
  277. this.$set(this.newData, 'hazardCheckPro', item.hazardCheckPro);
  278. this.$set(this.newData, 'hazardCheckCode', item.hazardCheckCode);
  279. this.$set(this.newData, 'hazardCheckName', item.hazardCheckName);
  280. this.$set(this.newData, 'hazardCheckPro1', item.hazardCheckPro1);
  281. this.$set(this.newData, 'hazardCheckCode1', item.hazardCheckCode1);
  282. this.$set(this.newData, 'hazardCheckName1', item.hazardCheckName1);
  283. this.$set(this.newData, 'hazardCheckPro2', item.hazardCheckPro2);
  284. this.$set(this.newData, 'hazardCheckCode2', item.hazardCheckCode2);
  285. this.$set(this.newData, 'hazardCheckName2', item.hazardCheckName2);
  286. this.$set(this.newData, 'hazardCheckPoint', item.hazardCheckPoint);
  287. this.$set(this.form, 'hazardDescribe', keyPoint);
  288. // this.$set(this.optionData, 'setOptionId', item.setOptionId);
  289. this.$set(this.optionData, 'hazardCheckPro', item.hazardCheckPro);
  290. this.$set(this, 'checkItemModuleType', false);
  291. this.securityAppCheckPhotoGetCheckNumBySub();
  292. //编辑的时候
  293. if(this.optionData.editStatus){
  294. if(this.optionData.setOptionId!=this.newData.setOptionId){
  295. this.$set(this.form, 'photoList', []);
  296. this.$set(this.form, 'voiceList', []);
  297. }
  298. }
  299. }
  300. },
  301. //提交
  302. async submitForm(status) {
  303. if (!this.form.checkFlag) {
  304. if (!this.form.hazardDescribe) {
  305. uni.showToast({
  306. title: '请填写隐患描述!',
  307. icon: "none",
  308. mask: true,
  309. duration: 2000
  310. });
  311. return
  312. }
  313. if (!this.form.photoList[0]) {
  314. uni.showToast({
  315. title: '请选择现场照片!',
  316. icon: "none",
  317. mask: true,
  318. duration: 2000
  319. });
  320. return
  321. }
  322. }
  323. let obj = {
  324. checkFlag: this.form.checkFlag,
  325. checkStatus: 1,
  326. hazardDescribe: this.form.hazardDescribe,
  327. photoList: this.form.photoList,
  328. voiceList: this.form.voiceList,
  329. manageId: this.optionData.manageId,
  330. hazardCheckPro: this.newData.hazardCheckPro,
  331. hazardCheckCode: this.newData.hazardCheckCode,
  332. hazardCheckName: this.newData.hazardCheckName,
  333. hazardCheckPro2: this.newData.hazardCheckPro2,
  334. hazardCheckCode2: this.newData.hazardCheckCode2,
  335. hazardCheckName2: this.newData.hazardCheckName2,
  336. hazardCheckPro1: this.newData.hazardCheckPro1,
  337. hazardCheckCode1: this.newData.hazardCheckCode1,
  338. hazardCheckName1: this.newData.hazardCheckName1,
  339. hazardCheckPoint: this.form.hazardDescribe,
  340. }
  341. if(this.optionData.setOptionId){
  342. obj.setOptionId = this.optionData.setOptionId;
  343. }
  344. const {
  345. data
  346. } = await securityAppCheckSetOptionCheckCommit(obj);
  347. if (data.code == 200) {
  348. uni.$emit('infoPageSubmitButton', { type:1,subId:this.newData.subId });
  349. uni.showToast({
  350. title: '提交成功!',
  351. icon: "none",
  352. mask: true,
  353. duration: 2000
  354. });
  355. let infoData = this.optionData;
  356. infoData.setOptionId = data.data;
  357. uni.redirectTo({
  358. url: '/pages_safetyCheck/views/itemsManage/hiddenDangerItemsDetail?infoData=' +
  359. encodeURIComponent(JSON
  360. .stringify(infoData))
  361. });
  362. }
  363. },
  364. //获取检查项在当前实验室出现的次数
  365. async securityAppCheckPhotoGetCheckNumBySub() {
  366. let obj = {
  367. subId: this.optionData.subId,
  368. hazardCheckPro: this.optionData.hazardCheckPro,
  369. }
  370. const {
  371. data
  372. } = await securityAppCheckPhotoGetCheckNumBySub(obj);
  373. if (data.code == 200) {
  374. this.$set(this, 'hazardCheckNum', data.data);
  375. }
  376. },
  377. //滚动事件
  378. scrollGet() {},
  379. //单选按钮
  380. radioClick(d) {
  381. let self = this;
  382. d.checked = !d.checked
  383. if (d.checked) {
  384. //如果有选中的,循环把其他选中的取消
  385. this.radioList.forEach(function(item) {
  386. if (item.id == d.id) {
  387. self.$set(self.form, 'checkFlag', item.id);
  388. } else {
  389. item.checked = false;
  390. }
  391. })
  392. } else {
  393. }
  394. },
  395. /******图片上传******/
  396. selectImage() {
  397. let self = this;
  398. let photoMaxNun = 0;
  399. if (this.form.photoList.length > 5) {
  400. uni.showToast({
  401. title: '最多上传6张图片',
  402. icon: "none",
  403. mask: true,
  404. duration: 2000
  405. });
  406. return
  407. }
  408. photoMaxNun = 6 - this.form.photoList.length;
  409. uni.chooseImage({
  410. count: photoMaxNun,
  411. sizeType: ["original", "compressed"],
  412. sourceType: ["album", "camera"],
  413. success: function(res) {
  414. if (res.tempFilePaths[0]) {
  415. res.tempFilePaths.forEach(function(item, index) {
  416. self.uploadImg(item);
  417. })
  418. }
  419. }
  420. });
  421. },
  422. async uploadImg(tempFilePaths) {
  423. var self = this;
  424. uni.showLoading({
  425. title: '上传中',
  426. mask: true
  427. });
  428. uni.uploadFile({
  429. url: config.base_url + '/system/file/upload', //仅为示例,非真实的接口地址
  430. header: {
  431. 'Authorization': uni.getStorageSync('token')
  432. },
  433. filePath: tempFilePaths,
  434. name: 'file',
  435. formData: {
  436. 'user': 'test'
  437. },
  438. success: (uploadFileRes) => {
  439. let res = JSON.parse(uploadFileRes.data);
  440. if (res.code == 200) {
  441. this.form.photoList.push({
  442. 'fileUrl': res.data.url,
  443. 'fileName': res.data.name
  444. });
  445. } else {
  446. uni.showToast({
  447. title: res.msg,
  448. icon: "none",
  449. mask: true,
  450. duration: 2000
  451. });
  452. }
  453. },
  454. fail: err => {},
  455. complete: () => {
  456. uni.hideLoading()
  457. }
  458. });
  459. },
  460. //删除图片
  461. delImg(minIndex) {
  462. this.form.photoList.splice(minIndex, 1);
  463. this.$forceUpdate();
  464. },
  465. /******语音备注 *******/
  466. //语音备忘播放
  467. async voicePlay(item) {
  468. const innerAudioContext = uni.createInnerAudioContext();
  469. // innerAudioContext.autoplay = true;
  470. innerAudioContext.src = uni.getStorageSync('fileBrowseEnvironment') + '/' + item;
  471. innerAudioContext.onPlay(() => {
  472. // console.log('开始播放');
  473. uni.showToast({
  474. title: '开始播放',
  475. icon: "none",
  476. mask: true,
  477. });
  478. });
  479. innerAudioContext.onError((res) => {
  480. // console.log('播放失败');
  481. uni.showToast({
  482. title: '播放失败',
  483. icon: "none",
  484. mask: true,
  485. });
  486. });
  487. },
  488. //语音备忘删除
  489. async voiceDele(item, voiceIndex) {
  490. let self = this;
  491. uni.showModal({
  492. title: '',
  493. cancelColor: '#999999',
  494. confirmColor: '#FF6E6E',
  495. content: '确定删除此语音吗?',
  496. success(res) {
  497. if (res.confirm) {
  498. console.log('用户点击确定')
  499. self.form.voiceList.splice(voiceIndex, 1);
  500. self.$forceUpdate();
  501. } else if (res.cancel) {
  502. console.log('用户点击取消')
  503. }
  504. }
  505. })
  506. },
  507. //录制
  508. recordButton(item, e) {
  509. console.log("按下")
  510. let self = this;
  511. if (this.form.voiceList.length >= 5) {
  512. uni.showToast({
  513. title: '语音备忘最多只能上传5条',
  514. icon: "none",
  515. mask: true,
  516. duration: 2000
  517. });
  518. return
  519. }
  520. this.liveType = true;
  521. console.log('录制', e)
  522. this.startPoint = e.touches[0]; //记录长按时开始点信息,后面用于计算上划取消时手指滑动的距离。
  523. const options = {
  524. duration: 60000,
  525. sampleRate: 16000,
  526. numberOfChannels: 1,
  527. encodeBitRate: 48000,
  528. format: 'mp3',
  529. frameSize: 50
  530. }
  531. this.recorderManager.start(options); //开始录音
  532. this.recorderManager.onStart(() => {
  533. console.log('recorder start')
  534. })
  535. this.recorderManager.onError((res) => {
  536. console.log(res);
  537. })
  538. uni.showToast({
  539. title: "正在录音,上划取消发送",
  540. icon: "none",
  541. duration: 60000 //先定义个60秒,后面可以手动调用wx.hideToast()隐藏
  542. });
  543. this.sendLock = false; //长按时是不上锁的。
  544. },
  545. //取消
  546. cancelButton(e) {
  547. console.log("移动")
  548. let self = this;
  549. this.liveType = false;
  550. console.log('取消', e)
  551. let moveLenght = e.touches[e.touches.length - 1].clientY - this.startPoint.clientY; //移动距离
  552. if (Math.abs(moveLenght) > 50) {
  553. uni.showToast({
  554. title: "松开手指,取消发送",
  555. icon: "none",
  556. duration: 60000
  557. });
  558. this.sendLock = true; //触发了上滑取消发送,上锁
  559. } else {
  560. uni.showToast({
  561. title: "正在录音,上划取消发送",
  562. icon: "none",
  563. duration: 60000
  564. });
  565. this.sendLock = false; //上划距离不足,依然可以发送,不上锁
  566. }
  567. },
  568. //发送
  569. sendButton(item, e) {
  570. console.log("松开")
  571. let self = this;
  572. this.liveType = false;
  573. console.log('发送', e)
  574. uni.hideToast(); //结束录音、隐藏Toast提示框
  575. this.recorderManager.stop(); //结束录音
  576. this.recorderManager.onStop((res) => {
  577. if (!this.sendLock) {
  578. this.uploadVoice(item, res.duration, res.tempFilePath);
  579. }
  580. console.log('停止录音', res.tempFilePath)
  581. console.log("sendLock", this.sendLock);
  582. })
  583. },
  584. //上传MP3
  585. async uploadVoice(item, times, tempFilePaths) {
  586. var self = this;
  587. uni.uploadFile({
  588. url: config.base_url + '/system/file/upload', //仅为示例,非真实的接口地址
  589. header: {
  590. 'Authorization': uni.getStorageSync('token')
  591. },
  592. filePath: tempFilePaths,
  593. name: 'file',
  594. formData: {
  595. 'user': 'test'
  596. },
  597. success: (uploadFileRes) => {
  598. let res = JSON.parse(uploadFileRes.data);
  599. if (res.code == 200) {
  600. console.log("上传成功", res)
  601. console.log(item)
  602. let seconds = Math.round((parseInt(times) % (1000 * 60)) / 1000);
  603. this.form.voiceList.push({
  604. 'fileLength': seconds,
  605. 'fileUrl': res.data.url,
  606. 'fileName': res.data.name
  607. })
  608. console.log(JSON.stringify(self.voice))
  609. //self.textParseUrlIps(res.data.url);
  610. // self.newData.imgList.push(res.data.url);
  611. } else {
  612. uni.showToast({
  613. title: res.msg,
  614. icon: "none",
  615. mask: true,
  616. duration: 2000
  617. });
  618. }
  619. },
  620. fail: err => {
  621. uni.hideLoading()
  622. },
  623. complete: () => {}
  624. });
  625. },// #ifdef WEB
  626. /* H5 */
  627. async initRecorder() {
  628. try {
  629. // 获取麦克风权限
  630. const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  631. this.recorder = new Recorder({
  632. type: "mp3", // 输出格式
  633. bitRate: 128, // 比特率
  634. sampleRate: 44100 // 采样率
  635. });
  636. this.recorder.open(() => {
  637. // console.log("录音器初始化成功");
  638. this.recorder.start(); // 初始化后立即开始录音
  639. this.startRecord();
  640. }, (error) => {
  641. console.error("录音器初始化失败:", error);
  642. uni.showToast({
  643. title: '麦克风权限获取失败,请允许麦克风权限',
  644. icon: "none",
  645. mask: true,
  646. duration: 2000
  647. });
  648. });
  649. } catch (err) {
  650. console.error("获取麦克风失败:", err);
  651. uni.showToast({
  652. title: '请允许麦克风权限',
  653. icon: "none",
  654. mask: true,
  655. duration: 2000
  656. });
  657. }
  658. },
  659. // 开始录音
  660. async startRecord() {
  661. let self = this;
  662. if (this.form.voiceList.length >= 5) {
  663. uni.showToast({
  664. title: '语音备忘最多只能上传5条',
  665. icon: "none",
  666. mask: true,
  667. duration: 2000
  668. });
  669. return
  670. }
  671. if (!this.recorder) {
  672. await this.initRecorder();
  673. // this.startRecord()
  674. return;
  675. }
  676. this.recorder.start();
  677. this.recording = true;
  678. this.liveType = true;
  679. // uni.showToast({ title: "录音开始", icon: "none" });
  680. uni.showToast({
  681. title: "正在录音,上划取消发送",
  682. icon: "none",
  683. duration: 60000 //先定义个60秒,后面可以手动调用wx.hideToast()隐藏
  684. });
  685. },
  686. // 停止录音
  687. stopRecord() {
  688. if (!this.recorder || !this.recording) return;
  689. this.recorder.stop((blob, duration) => {
  690. this.recording = false;
  691. this.liveType = false;
  692. this.audioBlob = blob;
  693. // 生成临时文件路径
  694. this.audioPath = URL.createObjectURL(blob);
  695. this.uploadAudio(duration);
  696. }, (error) => {
  697. console.error("录音失败:", error);
  698. uni.showToast({ title: "录音失败", icon: "none" });
  699. });
  700. },
  701. delRecord(){
  702. this.recorder.stop((blob, duration) => {
  703. this.recording = false;
  704. this.liveType = false;
  705. this.audioBlob = null;
  706. this.audioPath = null;
  707. })
  708. },
  709. // 上传录音文件
  710. uploadAudio(times) {
  711. let self = this;
  712. if (!this.audioBlob) return;
  713. // 1. 通过fetch获取Blob数据
  714. fetch(this.audioPath)
  715. .then(response => response.blob())
  716. .then(blob => {
  717. // 2. 将Blob转换为File对象
  718. const file = new File([blob], 'audio.mp3', { type: 'audio/mp3' });
  719. // console.log('file',file);
  720. // 3. 使用uni.uploadFile上传
  721. uni.uploadFile({
  722. url: config.base_url + '/system/file/upload',
  723. header: {
  724. 'Authorization': uni.getStorageSync('token')
  725. },
  726. file: file,
  727. name: 'file',
  728. formData: {
  729. 'user': 'test'
  730. },
  731. success: (uploadFileRes) => {
  732. let res = JSON.parse(uploadFileRes.data);
  733. if (res.code == 200) {
  734. console.log("上传成功", res)
  735. let seconds = Math.round((parseInt(times) % (1000 * 60)) / 1000);
  736. this.form.voiceList.push({
  737. 'fileLength': seconds,
  738. 'fileUrl': res.data.url,
  739. 'fileName': res.data.name
  740. })
  741. // console.log(JSON.stringify(self.voice))
  742. //self.textParseUrlIps(res.data.url);
  743. // self.newData.imgList.push(res.data.url);
  744. } else {
  745. uni.showToast({
  746. title: res.msg,
  747. icon: "none",
  748. mask: true,
  749. duration: 2000
  750. });
  751. }
  752. },
  753. fail: (err) => {
  754. console.error('上传失败', err);
  755. uni.showToast({
  756. title: res.msg,
  757. icon: "none",
  758. mask: true,
  759. duration: 2000
  760. });
  761. }
  762. });
  763. })
  764. .catch(error => {
  765. console.error('获取Blob数据失败', error);
  766. });
  767. },
  768. //H5 拖拽
  769. // 触摸开始
  770. handleTouchStart(event) {
  771. // 彻底阻止所有默认行为
  772. event.preventDefault();
  773. event.stopPropagation();
  774. this.touchStartY = event.touches[0].clientY;
  775. this.hasMoved = false;
  776. this.moveDirection = null;
  777. this.hasTriggeredMethod3 = false;
  778. // 设置长按定时器
  779. this.longPressTimer = setTimeout(() => {
  780. this.isLongPress = true;
  781. this.startRecord(); // 执行方法1
  782. }, 300); // 300ms触发长按
  783. },
  784. // 触摸移动
  785. handleTouchMove(event) {
  786. console.log("按下")
  787. let self = this;
  788. // 彻底阻止所有默认行为
  789. event.preventDefault();
  790. event.stopPropagation();
  791. if (!this.isLongPress) return;
  792. const currentY = event.touches[0].clientY;
  793. const deltaY = currentY - this.touchStartY;
  794. // 检测滑动方向(向上滑动为负值)
  795. if (Math.abs(deltaY) > 75) { // 增加阈值避免误触
  796. uni.showToast({
  797. title: "松开手指,取消发送",
  798. icon: "none",
  799. duration: 60000
  800. });
  801. this.hasMoved = true;
  802. this.moveDirection = deltaY < 0 ? 'up' : 'down';
  803. // 向上滑动执行方法3(确保只触发一次)
  804. if (this.moveDirection === 'up' && !this.hasTriggeredMethod3) {
  805. this.hasTriggeredMethod3 = true;
  806. this.delRecord();
  807. }
  808. }
  809. },
  810. // 触摸结束
  811. handleTouchEnd() {
  812. // 清除长按定时器
  813. clearTimeout(this.longPressTimer);
  814. uni.hideToast(); //结束录音、隐藏Toast提示框
  815. if (this.isLongPress) {
  816. this.stopRecord(); // 执行方法2
  817. }
  818. // 重置状态
  819. this.isLongPress = false;
  820. this.hasMoved = false;
  821. this.moveDirection = null;
  822. },
  823. // 关键:禁用上下文菜单(长按菜单)
  824. handleContextMenu(e) {
  825. e.preventDefault();
  826. return false;
  827. },
  828. // #endif
  829. }
  830. }
  831. </script>
  832. <style lang="stylus" scoped>
  833. .inspectAdd {
  834. height: 100%;
  835. display flex;
  836. box-sizing: border-box;
  837. padding-bottom: 230rpx;
  838. box-sizing: border-box;
  839. .tip {
  840. width: 750rpx;
  841. height: 80rpx;
  842. background: rgba(1, 131, 250, 0.2);
  843. font-weight: normal;
  844. font-size: 28rpx;
  845. color: #0183FA;
  846. line-height: 80rpx;
  847. text-align: center;
  848. }
  849. .list {
  850. width: 750rpx;
  851. margin: 20rpx 0rpx 0;
  852. border-radius: 20rpx 20rpx 20rpx 20rpx;
  853. overflow: hidden;
  854. .list-li {
  855. display: flex;
  856. justify-content: space-between;
  857. align-items: center;
  858. padding: 0 30rpx;
  859. box-sizing: border-box;
  860. border-bottom: 1rpx solid #E0E0E0;
  861. background: #fff;
  862. >view:nth-of-type(1) {
  863. font-size: 30rpx;
  864. color: #333333;
  865. line-height: 100rpx;
  866. text-align: left;
  867. overflow: hidden;
  868. text-overflow: ellipsis;
  869. white-space: nowrap;
  870. }
  871. >view:nth-of-type(2) {
  872. font-size: 28rpx;
  873. color: #666666;
  874. line-height: 100rpx;
  875. text-align: left;
  876. display: flex;
  877. justify-content: space-between;
  878. align-items: center;
  879. >img {
  880. width: 30rpx;
  881. height: 30rpx;
  882. margin-left: 10rpx;
  883. }
  884. }
  885. }
  886. .hidden-content {
  887. width: 630rpx;
  888. min-height: 200rpx;
  889. background: #F5F5F5;
  890. border-radius: 20rpx 20rpx 20rpx 20rpx;
  891. margin: 20rpx 30rpx 0;
  892. padding: 20rpx;
  893. box-sizing: border-box;
  894. font-size: 28rpx;
  895. color: #666666;
  896. line-height: 39rpx;
  897. text-align: left;
  898. }
  899. .photo {
  900. display: flex;
  901. justify-content: flex-start;
  902. flex-wrap: wrap;
  903. margin-top: 20rpx;
  904. padding: 0 30rpx;
  905. box-sizing: border-box;
  906. >img {
  907. width: 150rpx;
  908. height: 150rpx;
  909. border-radius: 10rpx 10rpx 10rpx 10rpx;
  910. margin: 0 10rpx 10rpx 0;
  911. }
  912. >img:nth-of-type(4) {
  913. margin-right: 0;
  914. }
  915. }
  916. .describe {
  917. width: 750rpx;
  918. background: #fff;
  919. overflow: hidden;
  920. .describe-n {
  921. width: 690rpx;
  922. min-height: 180rpx;
  923. border-radius: 10rpx 10rpx 10rpx 10rpx;
  924. border: 1rpx solid #E0E0E0;
  925. margin: 40rpx;
  926. padding: 22rpx;
  927. box-sizing: border-box;
  928. background: #F5F5F5;
  929. }
  930. }
  931. .check-for-img-max-box {
  932. padding: 20px 28rpx;
  933. box-sizing: border-box;
  934. background: #fff;
  935. border-bottom: 2rpx solid #E0E0E0;
  936. .left-title-p {
  937. width: 100%;
  938. text-align: left;
  939. font-size: 30rpx;
  940. font-family: PingFang SC-Medium, PingFang SC;
  941. font-weight: 400;
  942. color: #333333;
  943. line-height: 80rpx;
  944. }
  945. .right-img-box {
  946. .img-box {
  947. display inline-block;
  948. height: 150rpx;
  949. width: 150rpx;
  950. position relative;
  951. margin: 0 10rpx 20rpx 0;
  952. border-radius 10rpx;
  953. overflow hidden;
  954. .img-data {
  955. height: 150rpx;
  956. width: 150rpx;
  957. }
  958. .position-img {
  959. position absolute;
  960. right: 0;
  961. top: 0;
  962. width: 36rpx;
  963. height: 36rpx;
  964. }
  965. }
  966. .img-box:nth-of-type(4n+4) {
  967. margin-right: 0rpx;
  968. }
  969. .add-button {
  970. margin: 0 0rpx 20rpx 0;
  971. border-radius 10rpx;
  972. overflow hidden;
  973. display inline-block;
  974. height: 150rpx;
  975. width: 150rpx;
  976. }
  977. }
  978. }
  979. .list-li:last-of-type {
  980. border: none;
  981. }
  982. .hazardCheckNum{
  983. border-bottom: 2rpx solid #E0E0E0;
  984. padding: 30rpx 40rpx 34rpx 40rpx;
  985. box-sizing: border-box;
  986. background: #fff;
  987. display: flex;
  988. justify-content: flex-end;
  989. align-items: center;
  990. .hazardCheckNum-l{
  991. width: 30rpx;
  992. height: 30rpx;
  993. margin-right: 18rpx;
  994. }
  995. .hazardCheckNum-r{
  996. font-size: 28rpx;
  997. color: #FF5900;
  998. line-height: 40rpx;
  999. text-align: left;
  1000. }
  1001. }
  1002. }
  1003. .srearch {
  1004. display: flex;
  1005. justify-content: space-between;
  1006. align-items: center;
  1007. .srearch-l {
  1008. font-size: 30rpx;
  1009. color: #333333;
  1010. line-height: 100rpx;
  1011. text-align: left;
  1012. }
  1013. .srearch-r {
  1014. width: 30rpx;
  1015. height: 30rpx;
  1016. margin-right: 0rpx;
  1017. }
  1018. }
  1019. /* 语音备注 */
  1020. .voice {
  1021. width: 750rpx;
  1022. min-height: 100rpx;
  1023. background: #FFFFFF;
  1024. border-radius: 20rpx 20rpx 20rpx 20rpx;
  1025. overflow: hidden;
  1026. .voice_t {
  1027. height: 100rpx;
  1028. display: flex;
  1029. justify-content: space-between;
  1030. align-items: center;
  1031. padding: 0 30rpx;
  1032. box-sizing: border-box;
  1033. border-top: 2rpx solid #E0E0E0;
  1034. .voice_t_t {
  1035. width: 100%;
  1036. display: flex;
  1037. justify-content: space-between;
  1038. >text:nth-of-type(1) {
  1039. font-size: 30rpx;
  1040. color: #333333;
  1041. line-height: 100rpx;
  1042. }
  1043. >text:nth-of-type(2) {
  1044. font-size: 28rpx;
  1045. color: #999999;
  1046. line-height: 100rpx;
  1047. }
  1048. }
  1049. .voice_t_b {
  1050. width: 100%;
  1051. font-size: 30rpx;
  1052. font-family: PingFang SC-Medium, PingFang SC;
  1053. font-weight: 400;
  1054. color: #333333;
  1055. line-height: 100rpx;
  1056. text-align: left;
  1057. }
  1058. }
  1059. .voice_b {
  1060. display flex;
  1061. justify-content flex-start;
  1062. align-items center;
  1063. margin-bottom: 20rpx;
  1064. padding: 0 30rpx;
  1065. box-sizing: border-box;
  1066. .voice_b_l {
  1067. width: 400rpx;
  1068. height: 40rpx;
  1069. background: #0183FA;
  1070. border-radius: 10rpx;
  1071. display flex;
  1072. justify-content flex-start;
  1073. align-items center;
  1074. font-size: 24rpx;
  1075. color: #FFFFFF;
  1076. line-height: 30rpx;
  1077. text-align: left;
  1078. >img {
  1079. width: 20rpx;
  1080. height: 20rpx;
  1081. margin-left: 16rpx;
  1082. margin-right: 20rpx;
  1083. }
  1084. }
  1085. .voice_b_r {
  1086. width: 28rpx;
  1087. height: 28rpx;
  1088. margin-left: 20rpx;
  1089. }
  1090. }
  1091. .voice-btn {
  1092. width: 150rpx;
  1093. height: 150rpx;
  1094. position: fixed;
  1095. right: 24rpx;
  1096. bottom: 176rpx;
  1097. >img {
  1098. width: 150rpx;
  1099. height: 150rpx;
  1100. position: absolute;
  1101. }
  1102. >text {
  1103. font-size: 24rpx;
  1104. color: #999999;
  1105. line-height: 30rpx;
  1106. text-align: center;
  1107. position: absolute;
  1108. top: 84rpx;
  1109. left: 26rpx;
  1110. }
  1111. }
  1112. }
  1113. }
  1114. .sub-btn {
  1115. display: flex;
  1116. justify-content: space-between;
  1117. position: fixed;
  1118. bottom: 30rpx;
  1119. width: 750rpx;
  1120. padding: 0 46rpx;
  1121. box-sizing: border-box;
  1122. >img {
  1123. width: 160rpx;
  1124. height: 160rpx;
  1125. }
  1126. }
  1127. .inspectRecord{
  1128. position: fixed;
  1129. top: 118rpx;
  1130. right: 0;
  1131. z-index: 200;
  1132. width: 160rpx;
  1133. height: 60rpx;
  1134. background: #0183FA;
  1135. border-radius: 50rpx 0rpx 0rpx 50rpx;
  1136. font-size: 30rpx;
  1137. color: #FFFFFF;
  1138. line-height: 60rpx;
  1139. text-align: center;
  1140. }
  1141. </style>