diff --git a/src/api/mts.js b/src/api/mts.js
index dcd0922..29c832d 100644
--- a/src/api/mts.js
+++ b/src/api/mts.js
@@ -3,3 +3,7 @@ import request from '@/js/utils/request'
export const mtsUploadService = (obj) => {
return request.postForm('/mts/upload', obj)
}
+
+export const mtsImageService = (obj) => {
+ return request.get('/mts/image', { params: obj })
+}
diff --git a/src/js/utils/common.js b/src/js/utils/common.js
index 4802344..ca2d4ca 100644
--- a/src/js/utils/common.js
+++ b/src/js/utils/common.js
@@ -201,3 +201,31 @@ export const jsonParseSafe = (str) => {
}
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+
+export const base64ToFile = (base64Data, fileName) => {
+ let arr = base64Data.split(',') // 将 Base64 数据拆分成数据部分和前缀部分
+ let bstr = atob(arr[1])
+ let n = bstr.length
+ let u8arr = new Uint8Array(n)
+ while (n--) {
+ u8arr[n] = bstr.charCodeAt(n)
+ }
+ const mimeType = base64Data.match(/data:(.*?);/)[1]
+ switch (mimeType) {
+ case 'image/png':
+ fileName = fileName + '.png'
+ break
+ case 'image/jpeg':
+ fileName = fileName + '.jpg'
+ break
+ case 'image/gif':
+ fileName = fileName + '.gif'
+ break
+ case 'application/pdf':
+ fileName = fileName + '.pdf'
+ break
+ default:
+ fileName = fileName + '.dat'
+ }
+ return new File([u8arr], fileName, { type: mimeType })
+}
diff --git a/src/js/utils/emojis.js b/src/js/utils/emojis.js
index e712bbc..4301011 100644
--- a/src/js/utils/emojis.js
+++ b/src/js/utils/emojis.js
@@ -180,7 +180,7 @@ export const emojiTrans = (content) => {
new Set(matches).forEach((item) => {
const emoji = emojis[item]
- content = content.replaceAll(item, emoji)
+ content = emoji ? content.replaceAll(item, emoji) : content
})
return content
diff --git a/src/stores/image.js b/src/stores/image.js
new file mode 100644
index 0000000..1309257
--- /dev/null
+++ b/src/stores/image.js
@@ -0,0 +1,70 @@
+import { mtsImageService } from '@/api/mts'
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+const pattern = /\{[a-f0-9]+\}/g
+
+// image的缓存数据,不持久化存储
+export const imageStore = defineStore('anyim-image', () => {
+ /**
+ * {
+ * objectId_01: {objectId: objectId_01, originUrl: xxx, thumbUrl: xxx},
+ * objectId_02: {objectId: objectId_02, originUrl: xxx, thumbUrl: xxx},
+ * }
+ */
+ const image = ref({})
+
+ const setImage = (obj) => {
+ image.value[obj.objectId] = obj
+ }
+
+ const imageTrans = (content, maxWidth = 400, maxHeight = 300) => {
+ const matches = content.match(pattern)
+ if (!matches || matches.length === 0) {
+ return content
+ }
+
+ new Set(matches).forEach((item) => {
+ let startIndex = item.indexOf('{')
+ let endIndex = item.indexOf('}')
+ const objectId = item.slice(startIndex + 1, endIndex)
+ const thumbUrl = image.value[objectId]?.thumbUrl
+ if (thumbUrl) {
+ const imageHtml =
+ `
`
+ content = content.replaceAll(item, imageHtml)
+ }
+ })
+
+ return content
+ }
+
+ const getImageFromContent = async (content) => {
+ const imageIds = new Set()
+ const matches = content.match(pattern)
+ if (matches && matches.length > 0) {
+ matches.forEach((item) => {
+ let startIndex = item.indexOf('{')
+ let endIndex = item.indexOf('}')
+ const objectId = item.slice(startIndex + 1, endIndex)
+ if (!image.value[objectId]) {
+ imageIds.add(objectId)
+ }
+ })
+ }
+ if (imageIds.size > 0) {
+ const res = await mtsImageService({ objectIds: [...imageIds].join(',') })
+ res.data.data.forEach((item) => {
+ imageStore().setImage(item) // 缓存image数据
+ })
+ }
+ }
+
+ return {
+ image,
+ setImage,
+ imageTrans,
+ getImageFromContent
+ }
+})
diff --git a/src/stores/index.js b/src/stores/index.js
index 0f3d714..e905ddc 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -12,3 +12,4 @@ export * from './message'
export * from './search'
export * from './userCard'
export * from './groupCard'
+export * from './image'
diff --git a/src/stores/message.js b/src/stores/message.js
index 733d504..4b3ae0c 100644
--- a/src/stores/message.js
+++ b/src/stores/message.js
@@ -6,6 +6,9 @@ import {
msgQueryPartitionService
} from '@/api/message'
import { ElMessage } from 'element-plus'
+import { MsgType } from '@/proto/msg'
+import { mtsImageService } from '@/api/mts'
+import { imageStore } from './image'
// 消息功能相关需要缓存的数据,不持久化存储
export const messageStore = defineStore('anyim-message', () => {
@@ -123,16 +126,41 @@ export const messageStore = defineStore('anyim-message', () => {
const addMsgRecords = (sessionId, msgRecords) => {
if (!msgRecords?.length) return
+ const imageIds = new Set()
msgRecords.forEach((item) => {
if (!msgRecordsList.value[sessionId]) {
msgRecordsList.value[sessionId] = {}
}
msgRecordsList.value[sessionId][item.msgId] = item
+
+ // 如果消息内容中含有图片,则查询图片的url
+ if (item.msgType === MsgType.CHAT || item.msgType === MsgType.GROUP_CHAT) {
+ const pattern = /\{[a-f0-9]+\}/g
+ const matches = item.content.match(pattern)
+ if (matches && matches.length > 0) {
+ matches.forEach((item) => {
+ let startIndex = item.indexOf('{')
+ let endIndex = item.indexOf('}')
+ const objectId = item.slice(startIndex + 1, endIndex)
+ if (!imageStore().image[objectId]) {
+ imageIds.add(objectId)
+ }
+ })
+ }
+ }
})
// 更新排序
msgIdSortArray.value[sessionId] = Object.keys(msgRecordsList.value[sessionId]).sort(
(a, b) => a - b
)
+
+ if (imageIds.size > 0) {
+ mtsImageService({ objectIds: [...imageIds].join(',') }).then((res) => {
+ res.data.data.forEach((item) => {
+ imageStore().setImage(item) // 缓存image数据
+ })
+ })
+ }
}
/**
diff --git a/src/views/message/MessageLayout.vue b/src/views/message/MessageLayout.vue
index bbfe4fe..ab7911d 100644
--- a/src/views/message/MessageLayout.vue
+++ b/src/views/message/MessageLayout.vue
@@ -8,7 +8,6 @@ import {
CirclePlus,
LocationInformation,
Clock,
- Picture,
FolderAdd,
CreditCard,
ArrowDownBold,
@@ -1108,22 +1107,17 @@ const onSendEmoji = (key) => {
-
-
-
-
-
-
+
-
+
-
+
diff --git a/src/views/message/components/InputEditor.vue b/src/views/message/components/InputEditor.vue
index e1a0f8d..e3d68e8 100644
--- a/src/views/message/components/InputEditor.vue
+++ b/src/views/message/components/InputEditor.vue
@@ -2,13 +2,18 @@
import { QuillEditor, Delta, Quill } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { onMounted, onUnmounted, ref, watch } from 'vue'
-import { messageStore } from '@/stores'
-import { ElMessage } from 'element-plus'
-import { emojis } from '@/js/utils/emojis'
+import { v4 as uuidv4 } from 'uuid'
+import { messageStore, imageStore } from '@/stores'
+import { ElMessage, ElLoading } from 'element-plus'
+import { emojis, emojiTrans } from '@/js/utils/emojis'
+import { base64ToFile } from '@/js/utils/common'
+import { mtsUploadService } from '@/api/mts'
+import { el_loading_options } from '@/const/commonConst'
const props = defineProps(['sessionId', 'draft'])
const emit = defineEmits(['sendMessage'])
const messageData = messageStore()
+const imageData = imageStore()
const editorRef = ref()
@@ -16,11 +21,11 @@ const getQuill = () => {
return editorRef.value?.getQuill()
}
-onMounted(() => {
+onMounted(async () => {
// 给组件增加滚动条样式
document.querySelector('.ql-editor').classList.add('my-scrollbar')
- getQuill().setText(props.draft)
- getQuill().setSelection(getQuill().getLength(), 0, 'user')
+ await imageData.getImageFromContent(props.draft)
+ formatContent(props.draft)
getQuill().on('composition-start', () => {
// 当用户使用拼音输入法开始输入汉字时,这个事件就会被触发
getQuill().root.dataset.placeholder = ''
@@ -41,30 +46,43 @@ onUnmounted(() => {
}
})
-const getContent = () => {
+const getContent = async () => {
const delta = getQuill().getContents()
let content = ''
- delta.ops.forEach((op) => {
+ for (let index = 0; index < delta.ops.length; index++) {
+ const op = delta.ops[index]
const insert = op.insert
- // 文本
if (insert && typeof insert === 'string') {
+ // 文本
content = content + insert
} else if (insert && insert.image) {
const alt = op.attributes?.alt
- // 表情
if (alt && alt.startsWith('[') && alt.endsWith(']')) {
+ // 表情
content = content + alt
+ } else if (alt && alt.startsWith('{') && alt.endsWith('}')) {
+ // 图片
+ content = content + alt
+ } else if (insert.image.startsWith('data:') && insert.image.includes('base64')) {
+ // base64编码的图片
+ const file = base64ToFile(insert.image, uuidv4()) // base64转file
+ el_loading_options.text = '图片上传中...' //上传中加一个loading效果
+ const loadingInstance = ElLoading.service(el_loading_options)
+ const res = await mtsUploadService({ file: file }) //上传图片至服务端
+ loadingInstance.close()
+ imageData.setImage(res.data.data) // 缓存image数据
+ content = content + `{${res.data.data.objectId}}`
}
}
- })
+ }
return content.trim()
}
// 监控session发生了切换
watch(
() => props.sessionId,
- (newValue, oldValue) => {
- let content = getContent()
+ async (newValue, oldValue) => {
+ let content = await getContent()
// 草稿若没发生变动,则不触发存储
if (oldValue && content !== messageData.sessionList[oldValue].draft) {
messageData.updateSession({
@@ -72,14 +90,22 @@ watch(
draft: content
})
}
- getQuill().setText(messageData.sessionList[newValue].draft || '')
- getQuill().setSelection(getQuill().getLength(), 0, 'user')
+ formatContent(messageData.sessionList[newValue].draft || '')
},
{ deep: true }
)
-const handleEnter = () => {
- const content = getContent()
+const formatContent = (content) => {
+ let html = emojiTrans(content)
+ html = imageData.imageTrans(html)
+ html = html.replace(/\n/g, '
')
+ getQuill().setText('')
+ getQuill().clipboard.dangerouslyPasteHTML(0, html)
+ getQuill().setSelection(getQuill().getLength(), 0, 'user')
+}
+
+const handleEnter = async () => {
+ const content = await getContent()
if (!content) {
ElMessage.warning('请勿发送空内容')
getQuill().setText('')
diff --git a/src/views/message/components/MessageItem.vue b/src/views/message/components/MessageItem.vue
index 3f78f84..fac6b6d 100644
--- a/src/views/message/components/MessageItem.vue
+++ b/src/views/message/components/MessageItem.vue
@@ -2,7 +2,7 @@
import { computed } from 'vue'
import { WarningFilled } from '@element-plus/icons-vue'
import { MsgType } from '@/proto/msg'
-import { userStore, messageStore, groupStore, groupCardStore } from '@/stores'
+import { userStore, messageStore, groupStore, groupCardStore, imageStore } from '@/stores'
import { messageSysShowTime, messageBoxShowTime, jsonParseSafe } from '@/js/utils/common'
import UserAvatarIcon from '@/components/common/UserAvatarIcon.vue'
import { emojiTrans } from '@/js/utils/emojis'
@@ -24,6 +24,7 @@ const userData = userStore()
const messageData = messageStore()
const groupData = groupStore()
const groupCardData = groupCardStore()
+const imageData = imageStore()
const msg = computed(() => {
return messageData.getMsg(props.sessionId, props.msgId)
@@ -401,6 +402,12 @@ const onClickSystemMsg = (e) => {
const onResendMsg = () => {
emit('resendMsg', msg.value)
}
+
+const formatContent = (content) => {
+ let html = emojiTrans(content)
+ html = imageData.imageTrans(html)
+ return html
+}
@@ -453,7 +460,7 @@ const onResendMsg = () => {
-
+
@@ -485,7 +492,7 @@ const onResendMsg = () => {
{{ msgTime }}
-
+