index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <template>
  2. <view>
  3. <view class="TUI-message-input-container">
  4. <view class="TUI-commom-function">
  5. <view v-for="(item, index) in commonFunction" :key="index" class="TUI-commom-function-item" :data-function="item" @tap="handleCommonFunctions">
  6. {{ item.name }}
  7. </view>
  8. </view>
  9. <view class="TUI-message-input">
  10. <image class="TUI-icon" @tap="switchAudio" :src="isAudio ? '/static/assets/keyboard.svg' : '/static/assets/audio.svg'"></image>
  11. <view v-if="!isAudio" class="TUI-message-input-main">
  12. <input
  13. class="TUI-message-input-area"
  14. :adjust-position="true"
  15. cursor-spacing="20"
  16. v-model="inputText"
  17. @input="onInputValueChange"
  18. maxlength="140"
  19. type="text"
  20. placeholder-class="input-placeholder"
  21. placeholder="请输入内容"
  22. @focus="inputBindFocus"
  23. @blur="inputBindBlur"
  24. />
  25. </view>
  26. <view
  27. v-else
  28. class="TUI-message-input-main"
  29. @longpress="handleLongPress"
  30. @touchmove="handleTouchMove"
  31. @touchend="handleTouchEnd"
  32. style="display: flex; justify-content: center; font-size: 32rpx; font-family: PingFangSC-Regular;"
  33. >
  34. <text>{{ text }}</text>
  35. </view>
  36. <view class="TUI-message-input-functions" hover-class="none">
  37. <image class="TUI-icon" @tap="handleEmoji" src="/static/assets/face-emoji.svg"></image>
  38. <view v-if="!sendMessageBtn" @tap="handleExtensions"><image class="TUI-icon" src="/static/assets/more.svg"></image></view>
  39. <view v-else class="TUI-sendMessage-btn" @tap="sendTextMessage">发送</view>
  40. </view>
  41. </view>
  42. <view v-if="displayFlag === 'emoji'" class="TUI-Emoji-area"><TUI-Emoji @enterEmoji="appendMessage"></TUI-Emoji></view>
  43. <view v-if="displayFlag === 'extension'" class="TUI-Extensions">
  44. <!-- TODO: 这里功能还没实现 -->
  45. <!-- <camera device-position="back" flash="off" binderror="error" style="width: 100%; height: 300px;"></camera>-->
  46. <view class="TUI-Extension-slot" @tap="handleSendPicture">
  47. <image class="TUI-Extension-icon" src="/static/assets/take-photo.svg"></image>
  48. <view class="TUI-Extension-slot-name">拍摄照片</view>
  49. </view>
  50. <view class="TUI-Extension-slot" @tap="handleSendImage">
  51. <image class="TUI-Extension-icon" src="/static/assets/send-img.svg"></image>
  52. <view class="TUI-Extension-slot-name">发送图片</view>
  53. </view>
  54. <view class="TUI-Extension-slot" @tap="handleShootVideo">
  55. <image class="TUI-Extension-icon" src="/static/assets/take-video.svg"></image>
  56. <view class="TUI-Extension-slot-name">拍摄视频</view>
  57. </view>
  58. <view class="TUI-Extension-slot" @tap="handleSendVideo">
  59. <image class="TUI-Extension-icon" src="/static/assets/send-video.svg"></image>
  60. <view class="TUI-Extension-slot-name">发送视频</view>
  61. </view>
  62. <!-- <view class="TUI-Extension-slot" @tap="handleCalling(1)">
  63. <image class="TUI-Extension-icon" src="/static/assets/audio-calling.svg"></image>
  64. <view class="TUI-Extension-slot-name">语音通话</view>
  65. </view>
  66. <view class="TUI-Extension-slot" @tap="handleCalling(2)">
  67. <image class="TUI-Extension-icon" src="/static/assets/video-calling.svg"></image>
  68. <view class="TUI-Extension-slot-name">视频通话</view>
  69. </view> -->
  70. <view class="TUI-Extension-slot" @tap="handleServiceEvaluation">
  71. <image class="TUI-Extension-icon" src="/static/assets/service-assess.svg"></image>
  72. <view class="TUI-Extension-slot-name">服务评价</view>
  73. </view>
  74. <!-- <view class="TUI-Extension-slot" @tap="handleSendOrder">
  75. <image class="TUI-Extension-icon" src="/static/assets/send-order.svg"></image>
  76. <view class="TUI-Extension-slot-name">发送订单</view>
  77. </view> -->
  78. </view>
  79. <TUI-Common-Words class="tui-cards" :display="displayCommonWords" @sendMessage="$handleSendTextMessage" @close="$handleCloseCards"></TUI-Common-Words>
  80. <TUI-Order-List class="tui-cards" :display="displayOrderList" @sendCustomMessage="$handleSendCustomMessage" @close="$handleCloseCards"></TUI-Order-List>
  81. <TUI-Service-Evaluation
  82. class="tui-cards"
  83. :display="displayServiceEvaluation"
  84. @sendCustomMessage="$handleSendCustomMessage"
  85. @close="$handleCloseCards"
  86. ></TUI-Service-Evaluation>
  87. </view>
  88. <view class="record-modal" v-if="popupToggle" @longpress="handleLongPress" @touchmove="handleTouchMove" @touchend="handleTouchEnd">
  89. <view class="wrapper"><view class="modal-loading"></view></view>
  90. <view class="modal-title">{{ title }}</view>
  91. </view>
  92. </view>
  93. </template>
  94. <script>
  95. import TUIEmoji from '../message-elements/emoji/index';
  96. import TUICommonWords from '../message-private/common-words/index';
  97. import TUIOrderList from '../message-private/order-list/index';
  98. import TUIServiceEvaluation from '../message-private/service-evaluation/index';
  99. export default {
  100. data() {
  101. return {
  102. firstSendMessage: true,
  103. inputText: '',
  104. extensionArea: false,
  105. sendMessageBtn: false,
  106. displayFlag: '',
  107. isAudio: false,
  108. bottomVal: 0,
  109. startPoint: 0,
  110. popupToggle: false,
  111. isRecording: false,
  112. canSend: true,
  113. text: '按住说话',
  114. title: ' ',
  115. notShow: false,
  116. isShow: true,
  117. recordTime: 0,
  118. recordTimer: null,
  119. commonFunction: [
  120. {
  121. name: '常用语',
  122. key: '0'
  123. },
  124. // {
  125. // name: '发送订单',
  126. // key: '1'
  127. // },
  128. {
  129. name: '服务评价',
  130. key: '2'
  131. }
  132. ],
  133. displayServiceEvaluation: false,
  134. displayCommonWords: false,
  135. displayOrderList: false
  136. };
  137. },
  138. components: {
  139. TUIEmoji,
  140. TUICommonWords,
  141. TUIOrderList,
  142. TUIServiceEvaluation
  143. },
  144. props: {
  145. conversation: {
  146. type: Object,
  147. default: () => {}
  148. },
  149. toUser: {
  150. type: String,
  151. default: () => {}
  152. }
  153. },
  154. watch: {
  155. conversation: {
  156. handler: function(newVal) {
  157. // todo 值会被改变
  158. // this.setData({
  159. // conversation: newVal
  160. // });
  161. },
  162. immediate: true,
  163. deep: true
  164. }
  165. },
  166. beforeMount() {
  167. var that=this;
  168. // 加载声音录制管理器
  169. this.recorderManager = uni.getRecorderManager();
  170. this.recorderManager.onStop(res => {
  171. clearInterval(this.recordTimer);
  172. // 兼容 uniapp 打包app,duration 和 fileSize 需要用户自己补充
  173. // 文件大小 = (音频码率) x 时间长度(单位:秒) / 8
  174. let msg = {
  175. duration: res.duration ? res.duration : this.recordTime * 1000,
  176. tempFilePath: res.tempFilePath,
  177. fileSize: res.fileSize ? res.fileSize : ((48 * this.recordTime) / 8) * 1024
  178. };
  179. uni.hideLoading();
  180. // 兼容 uniapp 语音消息没有duration
  181. if (this.canSend) {
  182. if (msg.duration < 1000) {
  183. uni.showToast({
  184. title: '录音时间太短',
  185. icon: 'none'
  186. });
  187. } else {
  188. var orderId=uni.getStorageSync('orderId');
  189. // res.tempFilePath 存储录音文件的临时路径
  190. const message = uni.$TUIKit.createAudioMessage({
  191. to: this.getToAccount(),
  192. conversationType: this.conversation.type,
  193. payload: {
  194. file: msg
  195. },
  196. cloudCustomData: 'orderId='+orderId
  197. });
  198. this.$sendTIMMessage(message);
  199. }
  200. }
  201. that.setData({
  202. startPoint: 0,
  203. popupToggle: false,
  204. isRecording: false,
  205. canSend: true,
  206. title: ' ',
  207. text: '按住说话'
  208. });
  209. });
  210. },
  211. methods: {
  212. switchAudio() {
  213. this.isAudio= !this.isAudio;
  214. this.text='按住说话';
  215. },
  216. handleLongPress(e) {
  217. this.recorderManager.start({
  218. duration: 60000,
  219. // 录音的时长,单位 ms,最大值 600000(10 分钟)
  220. sampleRate: 44100,
  221. // 采样率
  222. numberOfChannels: 1,
  223. // 录音通道数
  224. encodeBitRate: 192000,
  225. // 编码码率
  226. format: 'aac' // 音频格式,选择此格式创建的音频消息,可以在即时通信 IM 全平台(Android、iOS、微信小程序和Web)互通
  227. });
  228. this.setData({
  229. startPoint: e.touches[0],
  230. title: '正在录音',
  231. // isRecording : true,
  232. // canSend: true,
  233. notShow: true,
  234. isShow: false,
  235. isRecording: true,
  236. popupToggle: true,
  237. recordTime: 0
  238. });
  239. this.recordTimer = setInterval(() => {
  240. this.recordTime++;
  241. }, 1000);
  242. },
  243. // 录音时的手势上划移动距离对应文案变化
  244. handleTouchMove(e) {
  245. if (this.isRecording) {
  246. if (this.startPoint.clientY - e.touches[e.touches.length - 1].clientY > 100) {
  247. this.setData({
  248. text: '抬起停止',
  249. title: '松开手指,取消发送',
  250. canSend: false
  251. });
  252. } else if (this.startPoint.clientY - e.touches[e.touches.length - 1].clientY > 20) {
  253. this.setData({
  254. text: '抬起停止',
  255. title: '上划可取消',
  256. canSend: true
  257. });
  258. } else {
  259. this.setData({
  260. text: '抬起停止',
  261. title: '正在录音',
  262. canSend: true
  263. });
  264. }
  265. }
  266. },
  267. // 手指离开页面滑动
  268. handleTouchEnd() {
  269. this.setData({
  270. isRecording: false,
  271. popupToggle: false
  272. });
  273. uni.hideLoading();
  274. this.recorderManager.stop();
  275. },
  276. handleEmoji() {
  277. let targetFlag = 'emoji';
  278. if (this.displayFlag === 'emoji') {
  279. targetFlag = '';
  280. }
  281. this.displayFlag=targetFlag;
  282. },
  283. handleExtensions() {
  284. let targetFlag = 'extension';
  285. if (this.displayFlag === 'extension') {
  286. targetFlag = '';
  287. }
  288. this.displayFlag=targetFlag;
  289. },
  290. error(e) {
  291. console.log(e.detail);
  292. },
  293. handleSendPicture() {
  294. this.sendImageMessage('camera');
  295. },
  296. handleSendImage() {
  297. this.sendImageMessage('album');
  298. },
  299. sendImageMessage(type) {
  300. uni.chooseImage({
  301. sourceType: [type],
  302. count: 1,
  303. success: res => {
  304. if (res) {
  305. var orderId=uni.getStorageSync('orderId');
  306. const message = uni.$TUIKit.createImageMessage({
  307. to: this.getToAccount(),
  308. conversationType: this.conversation.type,
  309. payload: {
  310. file: res
  311. },
  312. cloudCustomData: 'orderId='+orderId,
  313. onProgress: percent => {
  314. message.percent = percent;
  315. }
  316. });
  317. this.$sendTIMMessage(message);
  318. }
  319. }
  320. });
  321. },
  322. handleShootVideo() {
  323. this.sendVideoMessage('camera');
  324. },
  325. handleSendVideo() {
  326. this.sendVideoMessage('album');
  327. },
  328. sendVideoMessage(type) {
  329. uni.chooseVideo({
  330. sourceType: [type],
  331. // 来源相册或者拍摄
  332. maxDuration: 60,
  333. // 设置最长时间60s
  334. camera: 'back',
  335. // 后置摄像头
  336. success: res => {
  337. if (res) {
  338. var orderId=uni.getStorageSync('orderId');
  339. const message = uni.$TUIKit.createVideoMessage({
  340. to: this.getToAccount(),
  341. conversationType: this.conversation.type,
  342. payload: {
  343. file: res
  344. },
  345. cloudCustomData: 'orderId='+orderId,
  346. onProgress: percent => {
  347. message.percent = percent;
  348. }
  349. });
  350. this.$sendTIMMessage(message);
  351. }
  352. }
  353. });
  354. },
  355. handleCommonFunctions(e) {
  356. switch (e.target.dataset.function.key) {
  357. case '0':
  358. this.setData({
  359. displayCommonWords: true
  360. });
  361. break;
  362. // case '1':
  363. // this.setData({
  364. // displayOrderList: true
  365. // });
  366. // break;
  367. case '2':
  368. this.setData({
  369. displayServiceEvaluation: true
  370. });
  371. break;
  372. default:
  373. break;
  374. }
  375. },
  376. handleSendOrder() {
  377. this.setData({
  378. displayOrderList: true
  379. });
  380. },
  381. appendMessage(e) {
  382. this.setData({
  383. inputText: this.inputText + e.detail.message,
  384. sendMessageBtn: true
  385. });
  386. },
  387. getToAccount() {
  388. return this.toUser;
  389. },
  390. handleCalling(value) {
  391. // todo 目前支持单聊
  392. if (this.conversation.type === 'GROUP') {
  393. uni.showToast({
  394. title: '群聊暂不支持',
  395. icon: 'none'
  396. });
  397. return;
  398. }
  399. const { userID } = this.conversation.userProfile;
  400. // #ifdef APP-PLUS
  401. if(typeof(uni.$TUICalling) === 'undefined') {
  402. logger.error('请使用真机运行并且自定义基座调试,可能影响音视频功能~ 插件地址:https://ext.dcloud.net.cn/plugin?id=7097 , 调试地址:https://nativesupport.dcloud.net.cn/NativePlugin/use/use');
  403. uni.showToast({
  404. title: '请使用真机运行并且自定义基座调试,可能影响音视频功能~ ',
  405. icon: 'none',
  406. duration: 3000
  407. });
  408. } else {
  409. uni.$TUICalling.call(
  410. {
  411. userID: userID,
  412. type: value
  413. },
  414. res => {
  415. console.log(JSON.stringify(res));
  416. }
  417. );
  418. }
  419. // #endif
  420. // #ifdef MP-WEIXIN
  421. uni.showToast({
  422. title: '微信小程序暂不支持',
  423. icon: 'none'
  424. });
  425. // uni.$wxTUICalling.call({userID, type: value})
  426. // #endif
  427. },
  428. sendTextMessage(msg, flag) {
  429. var orderId=uni.getStorageSync('orderId');
  430. console.log(this.conversation.type)
  431. const to = this.getToAccount();
  432. const text = flag ? msg : this.inputText;
  433. const message = uni.$TUIKit.createTextMessage({
  434. to,
  435. conversationType: this.conversation.type,
  436. payload: {
  437. text:text
  438. },
  439. cloudCustomData: 'orderId='+orderId
  440. });
  441. this.setData({
  442. inputText: '',
  443. sendMessageBtn: false
  444. });
  445. this.$sendTIMMessage(message);
  446. },
  447. onInputValueChange(event) {
  448. if (event.detail.value) {
  449. this.setData({
  450. sendMessageBtn: true
  451. });
  452. } else {
  453. this.setData({
  454. sendMessageBtn: false
  455. });
  456. }
  457. },
  458. $handleSendTextMessage(event) {
  459. this.sendTextMessage(event.detail.message, true);
  460. this.setData({
  461. displayCommonWords: false
  462. });
  463. },
  464. $handleSendCustomMessage(e) {
  465. var orderId=uni.getStorageSync('orderId');
  466. const message = uni.$TUIKit.createCustomMessage({
  467. to: this.getToAccount(),
  468. conversationType: this.conversation.type,
  469. payload: e.detail.payload,
  470. cloudCustomData: 'orderId='+orderId
  471. });
  472. this.$sendTIMMessage(message);
  473. this.setData({
  474. displayOrderList: false
  475. });
  476. },
  477. $handleCloseCards(e) {
  478. switch (e.detail.key) {
  479. case '0':
  480. this.setData({
  481. displayCommonWords: false
  482. });
  483. break;
  484. case '1':
  485. this.setData({
  486. displayOrderList: false
  487. });
  488. break;
  489. case '2':
  490. this.setData({
  491. displayServiceEvaluation: false
  492. });
  493. break;
  494. default:
  495. break;
  496. }
  497. },
  498. $sendTIMMessage(message) {
  499. this.$emit('sendMessage', {
  500. detail: {
  501. message
  502. }
  503. });
  504. uni.$TUIKit.sendMessage(message).then((res) => {
  505. this.firstSendMessage = false
  506. }).catch((error) => {
  507. })
  508. this.setData({
  509. displayFlag: ''
  510. });
  511. },
  512. handleClose() {
  513. this.setData({
  514. displayFlag: ''
  515. });
  516. },
  517. handleServiceEvaluation() {
  518. this.setData({
  519. displayServiceEvaluation: true
  520. });
  521. },
  522. inputBindFocus() {
  523. console.log('占位:函数 inputBindFocus 未声明');
  524. },
  525. inputBindBlur() {
  526. console.log('占位:函数 inputBindBlur 未声明');
  527. }
  528. }
  529. };
  530. </script>
  531. <style>
  532. @import './index.css';
  533. </style>