message-tool.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <template>
  2. <div
  3. class="dialog-item"
  4. :class="env?.isH5 ? 'dialog-item-h5' : 'dialog-item-web'"
  5. >
  6. <MessageEmojiReact
  7. v-if="env?.isH5 && needEmojiReact"
  8. :message="message"
  9. type="dropdown"
  10. @handleCollapse="handleCollapse"
  11. />
  12. <ul
  13. class="dialog-item-list"
  14. :class="env?.isH5 ? 'dialog-item-list-h5' : 'dialog-item-list-web'"
  15. v-show="showToolList"
  16. >
  17. <li
  18. v-if="
  19. (message.type === types.MSG_FILE ||
  20. message.type === types.MSG_VIDEO ||
  21. message.type === types.MSG_IMAGE) &&
  22. !env.isH5
  23. "
  24. @click="openMessage(message)"
  25. >
  26. <i class="icon icon-msg-copy"></i>
  27. <span>{{ $t('TUIChat.打开') }}</span>
  28. </li>
  29. <li
  30. v-if="message.type === types.MSG_TEXT"
  31. @click="handleMessage(message, constant.handleMessage.copy)"
  32. >
  33. <i class="icon icon-msg-copy"></i>
  34. <span>{{ $t('TUIChat.复制') }}</span>
  35. </li>
  36. <li
  37. v-if="message.status === 'success'"
  38. @click="handleMessage(message, constant.handleMessage.forward)"
  39. >
  40. <i class="icon icon-msg-forward"></i>
  41. <span>{{ $t('TUIChat.转发') }}</span>
  42. </li>
  43. <li
  44. v-if="message.status === 'success'"
  45. @click="handleMessage(message, constant.handleMessage.reference)"
  46. >
  47. <i class="icon icon-msg-quote"></i>
  48. <span>{{ $t('TUIChat.引用') }}</span>
  49. </li>
  50. <li
  51. v-if="message.status === 'success'"
  52. @click="handleMessage(message, constant.handleMessage.reply)"
  53. >
  54. <i class="icon icon-msg-reply"></i>
  55. <span>{{ $t('TUIChat.回复') }}</span>
  56. </li>
  57. <li
  58. v-if="
  59. message.flow === 'out' &&
  60. message.status === 'success' &&
  61. message.type !== types.MSG_CUSTOM
  62. "
  63. @click="handleMessage(message, constant.handleMessage.revoke)"
  64. >
  65. <i class="icon icon-msg-revoke"></i>
  66. <span>{{ $t('TUIChat.撤回') }}</span>
  67. </li>
  68. <li
  69. v-if="message.status === 'success'"
  70. @click="handleMessage(message, constant.handleMessage.delete)"
  71. >
  72. <i class="icon icon-msg-del"></i>
  73. <span>{{ $t('TUIChat.删除') }}</span>
  74. </li>
  75. </ul>
  76. </div>
  77. </template>
  78. <script lang="ts">
  79. import {
  80. defineComponent,
  81. watch,
  82. reactive,
  83. toRefs,
  84. ref,
  85. watchEffect
  86. } from 'vue';
  87. import { Message } from '../interface';
  88. import TIM from '../../../../TUICore/tim';
  89. import { handleErrorPrompts } from '../../utils';
  90. import constant from '../../constant';
  91. import useClipboard from 'vue-clipboard3';
  92. import { useStore } from 'vuex';
  93. import MessageEmojiReact from './message-emoji-react.vue';
  94. export default defineComponent({
  95. props: {
  96. message: {
  97. type: Object,
  98. default: () => ({})
  99. },
  100. needEmojiReact: {
  101. type: Boolean,
  102. default: false
  103. }
  104. },
  105. components: {
  106. MessageEmojiReact
  107. },
  108. setup(props: any, ctx: any) {
  109. const TUIServer = (window as any)?.TUIKitTUICore?.TUIServer?.TUIChat;
  110. const VuexStore =
  111. ((window as any)?.TUIKitTUICore?.isOfficial && useStore && useStore()) ||
  112. {};
  113. const data = reactive({
  114. message: {} as Message,
  115. show: false,
  116. types: TIM.TYPES,
  117. env: TUIServer.TUICore.TUIEnv,
  118. showToolList: true,
  119. needEmojiReact: false
  120. });
  121. watchEffect(() => {
  122. data.needEmojiReact = props.needEmojiReact;
  123. });
  124. watch(
  125. () => props.message,
  126. () => {
  127. data.message = props.message;
  128. },
  129. { deep: true, immediate: true }
  130. );
  131. const openMessage = (item: any) => {
  132. let url = '';
  133. switch (item.type) {
  134. case data.types.MSG_FILE:
  135. url = item.payload.fileUrl;
  136. break;
  137. case data.types.MSG_VIDEO:
  138. url = item.payload.remoteVideoUrl;
  139. break;
  140. case data.types.MSG_IMAGE:
  141. url = item.payload.imageInfoArray[0].url;
  142. break;
  143. }
  144. window.open(url, '_blank');
  145. };
  146. const handleMessage = async (message: Message, type: string) => {
  147. switch (type) {
  148. case constant.handleMessage.revoke:
  149. try {
  150. await TUIServer.revokeMessage(message);
  151. (window as any)?.TUIKitTUICore?.isOfficial &&
  152. VuexStore?.commit &&
  153. VuexStore?.commit('handleTask', 1);
  154. } catch (error) {
  155. handleErrorPrompts(error, data.env);
  156. }
  157. break;
  158. case constant.handleMessage.copy:
  159. try {
  160. if (
  161. message?.type === data.types.MSG_TEXT &&
  162. message?.payload?.text
  163. ) {
  164. const { toClipboard } = useClipboard();
  165. await toClipboard(message?.payload?.text);
  166. }
  167. } catch (error) {
  168. handleErrorPrompts(error, data.env);
  169. }
  170. break;
  171. case constant.handleMessage.delete:
  172. await TUIServer.deleteMessage([message]);
  173. break;
  174. case constant.handleMessage.forward:
  175. ctx.emit('handleMessage', message, constant.handleMessage.forward);
  176. break;
  177. case constant.handleMessage.reference:
  178. ctx.emit('handleMessage', message, constant.handleMessage.reference);
  179. break;
  180. case constant.handleMessage.reply:
  181. ctx.emit('handleMessage', message, constant.handleMessage.reply);
  182. break;
  183. }
  184. };
  185. const handleCollapse = (isCollapse: boolean) => {
  186. if (!data?.env?.isH5) return;
  187. data.showToolList = isCollapse;
  188. };
  189. return {
  190. ...toRefs(data),
  191. openMessage,
  192. handleMessage,
  193. constant,
  194. handleCollapse
  195. };
  196. }
  197. });
  198. </script>
  199. <style lang="scss" scoped>
  200. @import url('../../../styles/common.scss');
  201. @import url('../../../styles/icon.scss');
  202. .dialog-item {
  203. background: #ffffff;
  204. min-width: min-content;
  205. max-width: 220Px;
  206. width: 72Px;
  207. height: fit-content;
  208. word-break: keep-all;
  209. top: 30Px;
  210. border-radius: 8Px;
  211. display: flex;
  212. flex-wrap: wrap;
  213. align-items: baseline;
  214. white-space: nowrap;
  215. border: 1Px solid #e0e0e0;
  216. &-web {
  217. padding: 12Px 0;
  218. }
  219. &-list {
  220. display: flex;
  221. flex-wrap: wrap;
  222. align-items: baseline;
  223. white-space: nowrap;
  224. justify-content: space-around;
  225. width: 100%;
  226. &-h5 {
  227. flex-wrap: nowrap;
  228. margin: 10Px;
  229. li {
  230. padding: 0 5Px;
  231. display: flex;
  232. flex-direction: column;
  233. align-items: center;
  234. font-size: 8Px;
  235. color: #4f4f4f;
  236. }
  237. }
  238. &-web {
  239. li:first-child {
  240. margin-top: 0;
  241. }
  242. li {
  243. padding: 4Px 12Px;
  244. font-size: 12Px;
  245. line-height: 17Px;
  246. display: flex;
  247. flex-direction: row;
  248. align-items: center;
  249. span {
  250. padding-left: 4Px;
  251. }
  252. }
  253. }
  254. }
  255. }
  256. </style>