MessageList.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <template>
  2. <div class="flex overflow-hidden message-list-modal">
  3. <slot></slot>
  4. <!-- <div class="message-list p-base scroll-y-auto">
  5. <template v-if="!!(messageList || []).length">
  6. <div
  7. v-for="message in messageList"
  8. :key="message.id"
  9. class="radius-base fill-blank p-base m-b-mini relative message-item"
  10. :class="{ active: currentMessage?.sendUserId === message.sendUserId, 'un-read': message.unReadCount > 0 }"
  11. @click="checkMessage(message)"
  12. >
  13. <div class="flex items-center m-b-base message-title">
  14. <div class="message-send-user">{{ message.sendUserName }}</div>
  15. <div class="m-l-auto message-send-time">{{ dayjs(message.sendTime).format('HH:mm') }}</div>
  16. </div>
  17. <pre class="message-content">{{ transHtmlContent(message.content) }}</pre>
  18. </div>
  19. </template>
  20. <div v-else class="none-msg-box">
  21. <div class="center-box">
  22. <svg-icon name="none_message" style="font-size: 120px"></svg-icon>
  23. <p class="tip">暂无消息</p>
  24. </div>
  25. </div>
  26. </div> -->
  27. <div class="message-info-container">
  28. <div class="flex direction-column message-info">
  29. <div class="flex items-center p-base message-info-header">
  30. <div class="flex items-center send-user">
  31. <!-- <span class="m-r-mini">发件人</span> -->
  32. <span class="radius-base user-name">{{
  33. mainStore?.myUserInfo?.id == currentMessage?.sendUserId
  34. ? currentMessage?.receiveUserName
  35. : currentMessage?.sendUserName
  36. }}</span>
  37. </div>
  38. <!-- <div class="grid pointer m-l-auto close-icon" @click="$emit('close')">
  39. <el-icon><close /></el-icon>
  40. </div> -->
  41. </div>
  42. <div class="p-base" style="height: calc(100% - 221px)">
  43. <!-- <pre
  44. class="full-h radius-base p-extra-base scroll-y-auto message-info-content"
  45. @click="onContentClick"
  46. v-html="currentMessage?.content"
  47. ></pre> -->
  48. <div ref="historyMsgWrap" class="history-box scroll-auto">
  49. <div v-for="(message, index) in historyReverse" :key="index" class="m-b-base">
  50. <div class="m-b-mini message-header">
  51. <span class="m-r-base user-name">{{ message.sendUserName }}</span>
  52. <span class="message-time">{{ message.sendTime }}</span>
  53. </div>
  54. <div class="history-message-info-content" @click="onContentClick" v-html="message.content"></div>
  55. </div>
  56. </div>
  57. </div>
  58. <div class="p-base" style="padding-top: 0">
  59. <div class="p-base radius-base overflow-hidden msg-content-box">
  60. <div style="height: calc(100% - 18px)" class="scroll-y-auto">
  61. <content-edit-able
  62. v-model="messageContent"
  63. class="full content-edit-able"
  64. @click="onContentClick"
  65. ></content-edit-able>
  66. </div>
  67. <div class="limit-tip">{{ messageContent.length }} / 2000</div>
  68. </div>
  69. </div>
  70. <div class="p-base flex items-center justify-between">
  71. <!-- <el-button size="small" type="primary" :disabled="!currentMessage" @click="onReply">回复</el-button> -->
  72. <!-- <el-button size="small" plain :disabled="!currentMessage" @click="toggleHistory">历史消息</el-button> -->
  73. <el-button v-if="showSendPaper" size="small" plain @click="sendCurrentPaper">发送当前试卷</el-button>
  74. <span v-else></span>
  75. <el-button size="small" type="primary" :disabled="!allowSend" :loading="sendLoading" @click="onSendMessage"
  76. >发送</el-button
  77. >
  78. </div>
  79. </div>
  80. <!-- <message-history v-if="showHistory" :send-user-id="currentMessage?.sendUserId"></message-history> -->
  81. </div>
  82. </div>
  83. <image-preview
  84. v-model="previewModalVisible"
  85. resize-key="can-resize22"
  86. :url="paperPath"
  87. :is-big="true"
  88. ></image-preview>
  89. </template>
  90. <script setup lang="ts" name="MessageList">
  91. /** 消息列表*/
  92. import { ref, watch, onUnmounted, computed, nextTick, unref } from 'vue'
  93. import { ElButton, ElIcon, ElMessage } from 'element-plus'
  94. import { Close } from '@element-plus/icons-vue'
  95. import useFetch from '@/hooks/useFetch'
  96. import MessageHistory from '@/components/shared/message/MessageHistory.vue'
  97. import ImagePreview from '../ImagePreview.vue'
  98. import useMainStore from '@/store/main'
  99. import { setLastMsgs } from '@/hooks/useMessageLoop'
  100. import bus from '@/utils/bus'
  101. import type { ExtractApiResponse } from '@/api/api'
  102. import ContentEditAble from '@/components/common/ContentEditAble.vue'
  103. type MessageType = ExtractArrayValue<ExtractApiResponse<'getMessageList'>>
  104. const historyMsgWrap = ref()
  105. const mainStore = useMainStore()
  106. const props = defineProps<{
  107. replyUserId?: number | null
  108. paperPath?: string | null
  109. replyUserName?: string | null
  110. }>()
  111. const emits = defineEmits(['close', 'change-type', 'reply'])
  112. const { fetch: getMessageHistory, result: history, loading } = useFetch('getMessageHistory')
  113. const historyReverse = computed(() => {
  114. return ((history.value as any) || []).reverse()
  115. })
  116. /** 图片预览 */
  117. const previewModalVisible = ref<boolean>(false)
  118. /** 是否已发送试卷 */
  119. const sendedPaper = ref<boolean>(false)
  120. /** 显示发送试卷按钮 */
  121. const showSendPaper = computed<boolean>(() => {
  122. // return !!props.paperPath && !sendedPaper.value
  123. return !!props.paperPath
  124. })
  125. /** 发送当前试卷 */
  126. const sendCurrentPaper = () => {
  127. if (props.paperPath) {
  128. messageContent.value += `<span class="pointer inline link-button" contenteditable="false" data-path="${props.paperPath}">查看试卷</span>`
  129. }
  130. }
  131. /** 消息内容 */
  132. const messageContent = ref<string>('')
  133. watch(messageContent, () => {
  134. // let reg = /<span class=\"pointer inline link-button\" contenteditable=\"false\" data-path=.*>查看试卷<\/span>/g
  135. // let arr = messageContent.value.match(reg)
  136. // let text = messageContent.value.replace(reg, '【查看试卷临时替换字符】')
  137. // text = transHtmlContent(text)
  138. // if (arr && arr.length) {
  139. // text = text.replace('【查看试卷临时替换字符】', arr[0])
  140. // }
  141. // messageContent.value = text
  142. nextTick(() => {
  143. const paperButton = document.querySelector(`.content-edit-able [data-path="${props.paperPath}"]`)
  144. sendedPaper.value = !!paperButton
  145. })
  146. })
  147. /** 图片路径 */
  148. const paperPath = ref<string>('')
  149. const showHistory = ref<boolean>(false)
  150. const { fetch: getMessageList, result: messageList } = useFetch('getMessageList')
  151. const currentMessage = ref<MessageType>()
  152. function scrollToBottom() {
  153. let scrollHeight = historyMsgWrap.value.scrollHeight
  154. if (scrollHeight > 200) {
  155. historyMsgWrap.value.scrollTop = scrollHeight - 200 + 100
  156. }
  157. }
  158. watch(historyReverse, (newVal: any, oldVal: any) => {
  159. let oldV = unref(oldVal)
  160. if ((oldV || []).length == 0) {
  161. nextTick(() => {
  162. scrollToBottom()
  163. })
  164. }
  165. })
  166. watch(currentMessage, (newVal: any, oldVal: any) => {
  167. if (currentMessage.value) {
  168. useFetch('handleReadMessage')
  169. .fetch({ id: currentMessage.value.id })
  170. .then(() => {
  171. if (currentMessage.value?.unReadCount) {
  172. // currentMessage.value.unReadCount--
  173. currentMessage.value.unReadCount = 0
  174. }
  175. if (currentMessage.value?.sendUserId) {
  176. getMessageHistory({
  177. sendUserId:
  178. currentMessage.value.sendUserId == mainStore?.myUserInfo?.id
  179. ? currentMessage.value.receiveUserId
  180. : currentMessage.value.sendUserId,
  181. }).then(() => {
  182. let newV = unref(newVal)
  183. let oldV = unref(oldVal)
  184. if (newV.id != oldV?.id) {
  185. setTimeout(scrollToBottom, 1)
  186. }
  187. })
  188. }
  189. })
  190. }
  191. })
  192. const allowSend = computed<boolean>(() => {
  193. return !!(messageContent.value && currentMessage.value?.sendUserId)
  194. })
  195. const { fetch: sendMessage, loading: sendLoading } = useFetch('sendMessage')
  196. /** 发送消息 */
  197. const onSendMessage = async () => {
  198. try {
  199. if (messageContent.value.length > 2000) {
  200. return ElMessage.error('输入内容过长')
  201. }
  202. await sendMessage({
  203. content: messageContent.value,
  204. // receiveUserIds: [currentMessage.value?.sendUserId as number],
  205. receiveUserIds: [
  206. (currentMessage.value?.sendUserId == mainStore?.myUserInfo?.id
  207. ? currentMessage.value?.receiveUserId
  208. : currentMessage.value?.sendUserId) as number,
  209. ],
  210. })
  211. ElMessage.success('发送成功')
  212. messageContent.value = ''
  213. if (currentMessage.value?.sendUserId) {
  214. getMessageHistory({
  215. sendUserId:
  216. currentMessage.value.sendUserId == mainStore?.myUserInfo?.id
  217. ? currentMessage.value.receiveUserId
  218. : currentMessage.value.sendUserId,
  219. }).then(() => {
  220. setTimeout(scrollToBottom, 1)
  221. })
  222. }
  223. // emit('close')
  224. } catch (error) {
  225. console.error(error)
  226. }
  227. }
  228. // const checkMessage = (message: MessageType) => {
  229. // currentMessage.value = message
  230. // }
  231. bus.on('clickChangeMsg', (msg: any) => {
  232. currentMessage.value = msg
  233. })
  234. const toggleHistory = () => {
  235. showHistory.value = !showHistory.value
  236. }
  237. const onReply = () => {
  238. if (currentMessage.value) {
  239. emits('change-type', 'send')
  240. emits('reply', currentMessage.value.sendUserId, currentMessage.value.sendUserName)
  241. }
  242. }
  243. const onContentClick = (e: Event) => {
  244. const target = e.target as HTMLButtonElement
  245. const path = target.getAttribute('data-path')
  246. if (path) {
  247. previewModalVisible.value = true
  248. paperPath.value = path
  249. }
  250. }
  251. // getMessageList().then((result) => {
  252. // currentMessage.value = result?.[0]
  253. // if (result[0]) {
  254. // bus.emit('getCurMsg', result[0])
  255. // }
  256. // })
  257. // watch(
  258. // () => mainStore.newMsgs,
  259. // () => {
  260. // getMessageList().then((res) => {
  261. // if (res && Array.isArray(res)) {
  262. // let find = res.find((item) => item.sendUserId == currentMessage.value?.sendUserId)
  263. // if (!!find) {
  264. // currentMessage.value = find
  265. // }
  266. // }
  267. // })
  268. // }
  269. // )
  270. onUnmounted(() => {
  271. useFetch('getUnReadMessage')
  272. .fetch()
  273. .then((res) => {
  274. setLastMsgs(res)
  275. })
  276. })
  277. </script>
  278. <style scoped lang="scss">
  279. .message-list-modal {
  280. width: 100%;
  281. height: 100%;
  282. background-color: transparent;
  283. border-top-left-radius: 0;
  284. border-bottom-left-radius: 0;
  285. border-bottom-right-radius: 6px;
  286. .message-info-container {
  287. // width: 600px;
  288. width: calc(100% - 260px);
  289. height: 100%;
  290. position: relative;
  291. .history-box {
  292. height: 100%;
  293. border: 1px solid #eee;
  294. border-radius: 4px;
  295. padding: 15px;
  296. .message-header {
  297. color: $color--primary;
  298. }
  299. .history-message-info-content {
  300. white-space: pre-wrap;
  301. }
  302. }
  303. .msg-content-box {
  304. height: 100px;
  305. font-size: 12px;
  306. border: 1px solid #0091ff;
  307. .limit-tip {
  308. text-align: right;
  309. height: 18px;
  310. padding-top: 5px;
  311. }
  312. }
  313. .message-info {
  314. // height: 446px;
  315. height: 100%;
  316. background-color: $color--white;
  317. .message-info-header {
  318. border-bottom: $OnePixelLine;
  319. .send-user {
  320. font-size: $SmallFont;
  321. color: $RegularFontColor;
  322. .user-name {
  323. display: inline-block;
  324. width: 160px;
  325. padding: 0 12px;
  326. border: $OnePixelLine;
  327. height: 38px;
  328. line-height: 36px;
  329. }
  330. }
  331. .close-icon {
  332. width: 20px;
  333. height: 20px;
  334. place-items: center;
  335. font-size: 18px;
  336. color: $RegularFontColor;
  337. &:hover {
  338. color: $NormalColor;
  339. }
  340. }
  341. }
  342. .message-info-content {
  343. border: $OnePixelLine;
  344. font-size: $SmallFont;
  345. white-space: pre-wrap;
  346. }
  347. }
  348. }
  349. }
  350. </style>