index.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <s-layout
  3. class="chat-wrap"
  4. :title="!isReconnecting ? '连接客服成功' : '会话重连中'"
  5. navbar="inner"
  6. >
  7. <!-- 覆盖头部导航栏背景颜色 -->
  8. <view class="page-bg" :style="{ height: sys_navBar + 'px' }"></view>
  9. <!-- 聊天区域 -->
  10. <MessageList ref="messageListRef">
  11. <template #bottom>
  12. <message-input
  13. v-model="chat.msg"
  14. @on-tools="onTools"
  15. @send-message="onSendMessage"
  16. :auto-focus="false"
  17. :show-char-count="true"
  18. :max-length="500"
  19. ></message-input>
  20. </template>
  21. </MessageList>
  22. <!-- 聊天工具 -->
  23. <tools-popup
  24. :show-tools="chat.showTools"
  25. :tools-mode="chat.toolsMode"
  26. @close="handleToolsClose"
  27. @on-emoji="onEmoji"
  28. @image-select="onSelect"
  29. @on-show-select="onShowSelect"
  30. >
  31. <message-input
  32. v-model="chat.msg"
  33. @on-tools="onTools"
  34. @send-message="onSendMessage"
  35. :auto-focus="false"
  36. :show-char-count="true"
  37. :max-length="500"
  38. ></message-input>
  39. </tools-popup>
  40. <!-- 商品订单选择 -->
  41. <SelectPopup
  42. :mode="chat.selectMode"
  43. :show="chat.showSelect"
  44. @select="onSelect"
  45. @close="chat.showSelect = false"
  46. />
  47. </s-layout>
  48. </template>
  49. <script setup>
  50. import MessageList from '@/pages/chat/components/messageList.vue';
  51. import { reactive, ref, toRefs } from 'vue';
  52. import sheep from '@/sheep';
  53. import ToolsPopup from '@/pages/chat/components/toolsPopup.vue';
  54. import MessageInput from '@/pages/chat/components/messageInput.vue';
  55. import SelectPopup from '@/pages/chat/components/select-popup.vue';
  56. import {
  57. KeFuMessageContentTypeEnum,
  58. WebSocketMessageTypeConstants,
  59. } from '@/pages/chat/util/constants';
  60. import FileApi from '@/sheep/api/infra/file';
  61. import KeFuApi from '@/sheep/api/promotion/kefu';
  62. import { useWebSocket } from './util/useWebSocket';
  63. import { jsonParse } from '@/sheep/helper/utils';
  64. const sys_navBar = sheep.$platform.navbar;
  65. const chat = reactive({
  66. msg: '',
  67. scrollInto: '',
  68. showTools: false,
  69. toolsMode: '',
  70. showSelect: false,
  71. selectMode: '',
  72. });
  73. // 发送消息
  74. async function onSendMessage() {
  75. if (!chat.msg) return;
  76. try {
  77. const data = {
  78. contentType: KeFuMessageContentTypeEnum.TEXT,
  79. content: JSON.stringify({ text: chat.msg }),
  80. };
  81. await KeFuApi.sendKefuMessage(data);
  82. chat.msg = '';
  83. } finally {
  84. chat.showTools = false;
  85. }
  86. }
  87. const messageListRef = ref();
  88. //======================= 聊天工具相关 start =======================
  89. function handleToolsClose() {
  90. chat.showTools = false;
  91. chat.toolsMode = '';
  92. }
  93. function onEmoji(item) {
  94. chat.msg += item.name;
  95. }
  96. // 点击工具栏开关
  97. function onTools(mode) {
  98. if (isReconnecting.value) {
  99. sheep.$helper.toast('您已掉线!请返回重试');
  100. return;
  101. }
  102. // 第二次点击关闭
  103. if (chat.showTools && chat.toolsMode === mode) {
  104. handleToolsClose();
  105. return;
  106. }
  107. // 切换工具栏
  108. if (chat.showTools && chat.toolsMode !== mode) {
  109. chat.showTools = false;
  110. chat.toolsMode = '';
  111. }
  112. // 延迟打开等一下过度效果
  113. setTimeout(() => {
  114. chat.toolsMode = mode;
  115. chat.showTools = true;
  116. }, 200);
  117. }
  118. function onShowSelect(mode) {
  119. chat.showTools = false;
  120. chat.showSelect = true;
  121. chat.selectMode = mode;
  122. }
  123. async function onSelect({ type, data }) {
  124. let msg;
  125. switch (type) {
  126. case 'image':
  127. const res = await FileApi.uploadFile(data.tempFiles[0].path);
  128. msg = {
  129. contentType: KeFuMessageContentTypeEnum.IMAGE,
  130. content: JSON.stringify({ picUrl: res.data }),
  131. };
  132. break;
  133. case 'goods':
  134. msg = {
  135. contentType: KeFuMessageContentTypeEnum.PRODUCT,
  136. content: JSON.stringify(data),
  137. };
  138. break;
  139. case 'order':
  140. msg = {
  141. contentType: KeFuMessageContentTypeEnum.ORDER,
  142. content: JSON.stringify(data),
  143. };
  144. break;
  145. }
  146. if (msg) {
  147. // 发送消息
  148. // scrollBottom();
  149. await KeFuApi.sendKefuMessage(msg);
  150. await messageListRef.value.refreshMessageList();
  151. chat.showTools = false;
  152. chat.showSelect = false;
  153. chat.selectMode = '';
  154. }
  155. }
  156. //======================= 聊天工具相关 end =======================
  157. const { options } = useWebSocket({
  158. // 连接成功
  159. onConnected: async () => {},
  160. // 收到消息
  161. onMessage: async (data) => {
  162. const type = data.type;
  163. if (!type) {
  164. console.error('未知的消息类型:' + data);
  165. return;
  166. }
  167. // 2.2 消息类型:KEFU_MESSAGE_TYPE
  168. if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
  169. // 刷新消息列表
  170. await messageListRef.value.refreshMessageList(jsonParse(data.content));
  171. return;
  172. }
  173. // 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
  174. if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
  175. console.log('管理员已读消息');
  176. // 更新消息已读状态
  177. sheep.$helper.toast('客服已读您的消息');
  178. }
  179. },
  180. });
  181. const isReconnecting = toRefs(options).isReconnecting; // 重连状态
  182. </script>
  183. <style scoped lang="scss">
  184. .chat-wrap {
  185. .page-bg {
  186. width: 100%;
  187. position: absolute;
  188. top: 0;
  189. left: 0;
  190. background-color: var(--ui-BG-Main);
  191. z-index: 1;
  192. }
  193. .status {
  194. position: relative;
  195. box-sizing: border-box;
  196. z-index: 3;
  197. height: 70rpx;
  198. padding: 0 30rpx;
  199. background: var(--ui-BG-Main-opacity-1);
  200. display: flex;
  201. align-items: center;
  202. font-size: 30rpx;
  203. font-weight: 400;
  204. color: var(--ui-BG-Main);
  205. }
  206. }
  207. </style>