index1.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <template>
  2. <page-meta :root-font-size="$store.getters.storeRootFontSize"></page-meta>
  3. <view :style="{ backgroundColor: '#F5F7FA' }" class="chating_container">
  4. <chating-header @click="pageClick" ref="chatingHeaderRef" :mutipleCheckVisible="mutipleCheckVisible" @mutipleCheckUpdate="mutipleCheckUpdate" />
  5. <chating-list
  6. @click="pageClick"
  7. :mutipleCheckVisible="mutipleCheckVisible"
  8. ref="chatingListRef"
  9. @initSuccess="initSuccess"
  10. :menuOutsideFlag="menuOutsideFlag"
  11. @closeMenu="closeMenu" />
  12. <chating-footer v-if="imType == 1 || imType == 2 || $companyUserIsLogin()" v-show="!mutipleCheckVisible" ref="chatingFooterRef" :footerOutsideFlag="footerOutsideFlag" @scrollToBottom="scrollToBottom" />
  13. <view v-show="mutipleCheckVisible" class="mutiple_action_container">
  14. <!-- <view @click="forward('BatchForWard')" class="action_item">
  15. <image src="@/static/images/mutiple_batch.png" />
  16. <text style="margin-top: 12rpx;"> 逐条转发 </text>
  17. </view> -->
  18. <view @click="forward('MergeForWard')" class="action_item">
  19. <image src="../../../static/images/mutiple_merge.png" />
  20. <text style="margin-top: 12rpx">合并转发</text>
  21. </view>
  22. <view @click="mutipleRemove" class="action_item">
  23. <image src="../../../static/images/mutiple_delete.png" />
  24. <text style="margin-top: 12rpx; color: #ff381f">删除</text>
  25. </view>
  26. </view>
  27. <u-loading-page :loading="initLoading"></u-loading-page>
  28. </view>
  29. </template>
  30. <script>
  31. import { mapGetters, mapActions } from 'vuex';
  32. import IMSDK from 'openim-uniapp-polyfill';
  33. import { ContactChooseTypes, GroupMemberListTypes, PageEvents } from '../../../constant';
  34. import ChatingHeader from './components/ChatingHeader.vue';
  35. import ChatingFooter from './components/ChatingFooter/index.vue';
  36. import ChatingList from './components/ChatingList.vue';
  37. import { markConversationAsRead, parseMessageByType, callingModule } from '../../../util/imCommon';
  38. export default {
  39. components: {
  40. ChatingHeader,
  41. ChatingFooter,
  42. ChatingList
  43. },
  44. data() {
  45. return {
  46. listHeight: 0,
  47. footerOutsideFlag: 0,
  48. menuOutsideFlag: 0,
  49. initLoading: false,
  50. mutipleCheckVisible: false,
  51. back2Tab: false,
  52. timer: null,
  53. fontSize:"40px"
  54. };
  55. },
  56. computed: {
  57. ...mapGetters(["timStore"]),
  58. // 响应式派生数据
  59. imType() {
  60. return this.timStore?.imType || '';
  61. },
  62. orderId() {
  63. return this.timStore?.orderId || '';
  64. },
  65. },
  66. onLoad(options) {
  67. this.setPageListener();
  68. this.checkCalling();
  69. if (options?.back2Tab) {
  70. this.back2Tab = JSON.parse(options.back2Tab);
  71. }
  72. },
  73. onUnload() {
  74. this.disposePageListener();
  75. markConversationAsRead(
  76. {...this.$store.getters.storeCurrentConversation},
  77. true
  78. );
  79. this.resetConversationState();
  80. this.resetMessageState();
  81. if (this.timer) {
  82. clearInterval(this.timer);
  83. }
  84. },
  85. methods: {
  86. ...mapActions('message', ['resetMessageState', 'deleteMessages']),
  87. ...mapActions('conversation', ['resetConversationState']),
  88. scrollToBottom(isRecv = false) {
  89. //// this.$refs.chatingListRef.scrollToAnchor(`auchor${clientMsgID}`, isRecv);
  90. //this.$refs.chatingListRef.scrollToBottom(false, isRecv);
  91. this.$refs.chatingListRef.scrollToBottom();
  92. },
  93. nativeCallEndHandler() {
  94. if (!this.$store.getters.storeCurrentConversation.groupID) {
  95. return;
  96. }
  97. IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters.storeCurrentConversation.groupID).then(({ data }) => {
  98. if (data.invitation) {
  99. this.$refs.chatingHeaderRef.updateCallingData(data);
  100. } else {
  101. this.$refs.chatingHeaderRef.updateCallingData(null);
  102. }
  103. });
  104. },
  105. checkCalling() {
  106. if (!this.$store.getters.storeCurrentConversation.groupID) {
  107. return;
  108. }
  109. if (this.timer) {
  110. clearInterval(this.timer);
  111. }
  112. this.nativeCallEndHandler();
  113. this.timer = setInterval(this.nativeCallEndHandler, 10000);
  114. },
  115. mutipleRemove() {
  116. const checkedMessages = this.$store.getters.storeHistoryMessageList.filter((message) => message.checked);
  117. if (checkedMessages.length === 0) {
  118. uni.$u.toast('请先选择要操作的消息');
  119. return;
  120. }
  121. let count = 0;
  122. checkedMessages.map((message) => {
  123. IMSDK.asyncApi(IMSDK.IMMethods.DeleteMessage, IMSDK.uuid(), {
  124. conversationID: this.$store.getters.storeCurrentConversation.conversationID,
  125. clientMsgID: message.clientMsgID
  126. }).then(() => {
  127. count++;
  128. if (count === checkedMessages.length) {
  129. this.deleteMessages(checkedMessages);
  130. this.mutipleCheckUpdateHandler(false);
  131. uni.$u.toast('删除成功');
  132. }
  133. });
  134. });
  135. },
  136. forward(type) {
  137. const checkedMessages = this.$store.getters.storeHistoryMessageList.filter((message) => message.checked);
  138. if (checkedMessages.length === 0) {
  139. uni.$u.toast('请先选择要操作的消息');
  140. return;
  141. }
  142. const currentConversation = this.$store.getters.storeCurrentConversation;
  143. const title = currentConversation.groupID ? `群聊${currentConversation.showName}的聊天记录` : `和${currentConversation.showName}的聊天记录`;
  144. const mergeInfo = {
  145. messageList: [...checkedMessages],
  146. summaryList: checkedMessages.map((message) => `${message.senderNickname}:${parseMessageByType(message)}`),
  147. title
  148. };
  149. uni.navigateTo({
  150. url: `/pages_im/pages/common/contactChoose/index?type=${type}&mergeInfo=${encodeURIComponent(JSON.stringify(mergeInfo))}`
  151. });
  152. },
  153. closeMenu() {
  154. this.getEl('.message_menu_container').then((res) => {
  155. if (res) {
  156. this.menuOutsideFlag += 1;
  157. }
  158. });
  159. },
  160. async pageClick(e) {
  161. this.getEl('.message_menu_container').then((res) => {
  162. if (res) {
  163. this.menuOutsideFlag += 1;
  164. }
  165. });
  166. this.footerOutsideFlag += 1;
  167. },
  168. getEl(el) {
  169. return new Promise((resolve) => {
  170. const query = uni.createSelectorQuery().in(this);
  171. query.select(el)
  172. .boundingClientRect((data) => {
  173. resolve(data);
  174. }).exec();
  175. });
  176. },
  177. initSuccess() {
  178. this.initLoading = false;
  179. },
  180. async getCheckedUsers(data) {
  181. this.$refs.chatingFooterRef.customEditorCtx.undo();
  182. for (const item of data) {
  183. this.$refs.chatingFooterRef.insertAt(item.userID, item.nickname);
  184. await uni.$u.sleep(200);
  185. }
  186. },
  187. sendRtcInvite(mediaType, userIDList) {
  188. const isVideo = mediaType === 'video';
  189. const inviteeUserIDList = userIDList ?? [this.$store.getters.storeCurrentConversation.userID];
  190. callingModule.startLiveChat(isVideo, inviteeUserIDList, this.$store.getters.storeCurrentConversation.groupID, this.$store.getters.storeCurrentUserID);
  191. console.log("qxj inviteeUserIDList",inviteeUserIDList);
  192. console.log("qxj groupID",this.$store.getters.storeCurrentConversation.groupID);
  193. console.log("qxj groupID",this.$store.getters.storeCurrentUserID);
  194. },
  195. chooseCallMediaType(mediaType) {
  196. return new Promise((resolve, reject) => {
  197. resolve(mediaType);
  198. if (mediaType) {
  199. resolve(mediaType);
  200. return;
  201. }
  202. // uni.showActionSheet({
  203. // itemList: ['语音通话', '视频通话'],
  204. // success: ({ tapIndex }) => {
  205. // resolve(tapIndex ? 'video' : 'audio');
  206. // },
  207. // fail: () => reject()
  208. // });
  209. });
  210. },
  211. // page event
  212. typingHandler() {
  213. this.$refs.chatingHeaderRef.updateTyping();
  214. },
  215. onlineCheckHandler() {
  216. this.$refs.chatingHeaderRef.checkOnline();
  217. },
  218. atSomeOneHandler({ userID, nickname }) {
  219. if (plus.os.name == 'iOS') {
  220. var UIImpactFeedbackGenerator = plus.ios.importClass('UIImpactFeedbackGenerator');
  221. var impact = new UIImpactFeedbackGenerator();
  222. impact.prepare();
  223. impact.init(1);
  224. impact.impactOccurred();
  225. } else {
  226. uni.vibrateLong();
  227. }
  228. this.$refs.chatingFooterRef.insertAt(userID, nickname);
  229. },
  230. mutipleCheckUpdateHandler({ flag, message }) {
  231. this.mutipleCheckVisible = flag;
  232. if (message) {
  233. this.$store.dispatch('message/updateOneMessage', {
  234. message: {
  235. ...message,
  236. checked: true
  237. }
  238. });
  239. }
  240. if (!flag) {
  241. const tmpMessageList = [...this.$store.getters.storeHistoryMessageList];
  242. tmpMessageList.map((message) => (message.checked = false));
  243. this.$store.commit('message/SET_HISTORY_MESSAGE_LIST', tmpMessageList);
  244. }
  245. },
  246. rtcInvitePrepare(mediaType) {
  247. this.chooseCallMediaType(mediaType).then((callMediaType) => {
  248. if (this.$store.getters.storeCurrentConversation.userID) {
  249. this.sendRtcInvite(callMediaType);
  250. } else {
  251. uni.$u.route('/pages_im/pages/conversation/groupMemberList/index', {
  252. type: GroupMemberListTypes.CallInvite,
  253. groupID: this.$store.getters.storeCurrentConversation.groupID,
  254. callMediaType: callMediaType
  255. });
  256. }
  257. });
  258. },
  259. setPageListener() {
  260. IMSDK.subscribe('onRecvC2CReadReceipt', ({ data: receiptList }) => {
  261. console.log('onRecvC2CReadReceipt', receiptList);
  262. });
  263. uni.$on(PageEvents.TypingUpdate, this.typingHandler);
  264. uni.$on(PageEvents.OnlineStateCheck, this.onlineCheckHandler);
  265. uni.$on(PageEvents.ScrollToBottom, this.scrollToBottom);
  266. uni.$on(PageEvents.AtSomeOne, this.atSomeOneHandler);
  267. uni.$on(PageEvents.MutipleCheckUpdate, this.mutipleCheckUpdateHandler);
  268. uni.$on(PageEvents.RtcCall, this.rtcInvitePrepare);
  269. uni.$on(PageEvents.NativeCallEnd, this.nativeCallEndHandler);
  270. },
  271. disposePageListener() {
  272. uni.$off(PageEvents.TypingUpdate, this.typingHandler);
  273. uni.$off(PageEvents.OnlineStateCheck, this.onlineCheckHandler);
  274. uni.$off(PageEvents.ScrollToBottom, this.scrollToBottom);
  275. uni.$off(PageEvents.AtSomeOne, this.atSomeOneHandler);
  276. uni.$off(PageEvents.MutipleCheckUpdate, this.mutipleCheckUpdateHandler);
  277. uni.$off(PageEvents.RtcCall, this.rtcInvitePrepare);
  278. uni.$off(PageEvents.NativeCallEnd, this.nativeCallEndHandler);
  279. },
  280. mutipleCheckUpdate() {
  281. this.mutipleCheckUpdateHandler({ flag: false });
  282. }
  283. },
  284. onBackPress() {
  285. if (!this.mutipleCheckVisible) {
  286. // uni.switchTab({
  287. // url: '/pages_im/pages/conversation/conversationList/index'
  288. // });
  289. //uni.navigateBack();
  290. return false;
  291. } else {
  292. this.mutipleCheckUpdateHandler({ flag: false });
  293. return true;
  294. }
  295. },
  296. // beforeDestroy() {
  297. // uni.switchTab({
  298. // url: '/pages_im/pages/conversation/conversationList/index'
  299. // });
  300. // }
  301. };
  302. </script>
  303. <style lang="scss" scoped>
  304. .chating_container {
  305. @include colBox(false);
  306. height: 100vh;
  307. overflow: hidden;
  308. background-color: #F5F7FA !important;
  309. position: relative;
  310. .watermark {
  311. font-size: 16px; /* 水印文字大小 */
  312. color: rgba(0, 0, 0, 0.2); /* 水印文字颜色,使用透明度控制可见度 */
  313. position: absolute; /* 水印相对定位 */
  314. transform: rotate(-45deg);
  315. pointer-events: none; /* 防止水印文字干扰交互 */
  316. /* 为不同的水印元素设置不同的偏移,以避免重叠 */
  317. // transform-origin: top right;
  318. // margin-top: 20px;
  319. // margin-right: 20px;
  320. }
  321. // ::before {
  322. // content: "Your Watermark Text"; /* 替换为你想要的水印文字 */
  323. // font-size: 16px; /* 水印文字大小 */
  324. // color: rgba(0, 0, 0, 0.2); /* 水印文字颜色,使用透明度控制可见度 */
  325. // position: absolute; /* 水印相对定位 */
  326. // top: 20px; /* 距离顶部的距离 */
  327. // right: 20px; /* 距离右侧的距离 */
  328. // transform: rotate(-45deg); /* 将水印旋转为倾斜状态 */
  329. // pointer-events: none; /* 防止水印文字干扰交互 */
  330. // z-index: -1; /* 将水印置于底层 */
  331. // }
  332. .mutiple_action_container {
  333. display: flex;
  334. border-top: 1px solid #eaebed;
  335. background: #f0f2f6;
  336. justify-content: space-evenly;
  337. padding: 12px 24px;
  338. .action_item {
  339. @include centerBox();
  340. flex-direction: column;
  341. image {
  342. width: 48px;
  343. height: 48px;
  344. }
  345. }
  346. }
  347. }
  348. </style>