mirror of
https://gitee.com/HuLaSpark/HuLa.git
synced 2024-11-29 18:28:30 +08:00
⚡ perf(system): 优化回复功能的实现
支持对图片和其他类型消息回复
This commit is contained in:
parent
e1eb827b58
commit
c5109d7441
@ -91,7 +91,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFileDialog } from '@vueuse/core'
|
||||
import { MsgEnum } from '@/enums'
|
||||
import { LimitEnum, MsgEnum } from '@/enums'
|
||||
import { useCommon } from '@/hooks/useCommon.ts'
|
||||
|
||||
const { open, onChange } = useFileDialog()
|
||||
@ -116,8 +116,8 @@ const emojiHandle = (item: string) => {
|
||||
|
||||
onChange((files) => {
|
||||
if (!files) return
|
||||
if (files.length > 5) {
|
||||
window.$message.warning('一次性只能上传5个文件')
|
||||
if (files.length > LimitEnum.COM_COUNT) {
|
||||
window.$message.warning(`一次性只能上传${LimitEnum.COM_COUNT}个文件或图片`)
|
||||
return
|
||||
}
|
||||
for (let file of files) {
|
||||
|
@ -88,9 +88,9 @@
|
||||
@click="handleMsgClick(item)">
|
||||
<!-- <!– 渲染消息内容体 –>-->
|
||||
<!-- <RenderMessage :message="message" />-->
|
||||
<!-- 消息为文本类型 -->
|
||||
<!-- 消息为文本类型或者回复消息 -->
|
||||
<div
|
||||
v-if="item.type === MsgEnum.TEXT"
|
||||
v-if="item.type === MsgEnum.TEXT || item.type === MsgEnum.REPLY"
|
||||
style="white-space: pre-wrap"
|
||||
:class="[
|
||||
{ active: activeBubble === item.key },
|
||||
@ -133,17 +133,6 @@
|
||||
preview-disabled
|
||||
style="border-radius: 8px"
|
||||
:src="item.content"></n-image>
|
||||
|
||||
<!-- 消息为回复消息 -->
|
||||
<div
|
||||
v-if="item.type === MsgEnum.REPLY"
|
||||
style="white-space: pre-wrap"
|
||||
:class="[
|
||||
{ active: activeBubble === item.key },
|
||||
item.accountId === userId ? 'bubble-oneself' : 'bubble'
|
||||
]">
|
||||
<span v-html="item.content"></span>
|
||||
</div>
|
||||
</ContextMenu>
|
||||
|
||||
<!-- 回复的内容 -->
|
||||
@ -152,12 +141,26 @@
|
||||
:size="6"
|
||||
v-if="item.reply && item.type === MsgEnum.REPLY"
|
||||
@click="jumpToReplyMsg(item.reply.key)"
|
||||
class="reply-bubble">
|
||||
class="reply-bubble relative">
|
||||
<svg class="size-14px"><use href="#to-top"></use></svg>
|
||||
<span>{{ `${item.reply.accountName}:` }}</span>
|
||||
<span class="content-span">
|
||||
{{ item.reply.content }}
|
||||
<!-- 当回复消息为图片时渲染 -->
|
||||
<n-image
|
||||
v-if="item.reply.content.startsWith('data:image/')"
|
||||
:img-props="{ style: { maxWidth: '50px', maxHeight: '50px' } }"
|
||||
show-toolbar-tooltip
|
||||
style="border-radius: 4px"
|
||||
@click.stop
|
||||
:fallback-src="'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'"
|
||||
:src="item.reply.content" />
|
||||
<!-- 当回复消息为文本时渲染(判断是否有aitSpan标签) -->
|
||||
<span v-else class="content-span">
|
||||
{{ handleReply(item.reply.content) }}
|
||||
</span>
|
||||
<!-- 多个图片时计数器样式 -->
|
||||
<div v-if="item.reply.imgCount" class="reply-img-sub">
|
||||
{{ item.reply.imgCount }}
|
||||
</div>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</div>
|
||||
@ -224,6 +227,7 @@ import { listen } from '@tauri-apps/api/event'
|
||||
import { useChatMain } from '@/hooks/useChatMain.ts'
|
||||
import { VirtualListInst } from 'naive-ui'
|
||||
import { delay } from 'lodash-es'
|
||||
import { useCommon } from '@/hooks/useCommon.ts'
|
||||
|
||||
const { activeItem } = defineProps<{
|
||||
activeItem: MockItem
|
||||
@ -239,6 +243,7 @@ const itemSize = computed(() => (activeItem.type === RoomTypeEnum.GROUP ? 98 : 7
|
||||
/* 虚拟列表 */
|
||||
const virtualListInst = ref<VirtualListInst>()
|
||||
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-main')
|
||||
const { removeTag } = useCommon()
|
||||
const {
|
||||
handleScroll,
|
||||
handleMsgClick,
|
||||
@ -284,6 +289,11 @@ watchEffect(() => {
|
||||
activeItemRef.value = { ...activeItem }
|
||||
})
|
||||
|
||||
/* 处理回复消息中的 AIT 标签 */
|
||||
const handleReply = (content: string) => {
|
||||
return content.includes('id="aitSpan"') ? removeTag(content) : content
|
||||
}
|
||||
|
||||
/* 发送信息 */
|
||||
const handleSendMessage = (msg: any) => {
|
||||
nextTick(() => {
|
||||
|
@ -123,3 +123,9 @@ export enum CloseBxEnum {
|
||||
/** 关闭 */
|
||||
CLOSE = 'close'
|
||||
}
|
||||
|
||||
/** 限制上传 */
|
||||
export enum LimitEnum {
|
||||
/** 通用限制数量 */
|
||||
COM_COUNT = 5
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { MsgEnum } from '@/enums'
|
||||
import { LimitEnum, MsgEnum } from '@/enums'
|
||||
import { Ref } from 'vue'
|
||||
import { createFileOrVideoDom } from '@/utils/CreateDom.ts'
|
||||
|
||||
@ -8,7 +8,8 @@ export const useCommon = () => {
|
||||
const reply = ref({
|
||||
accountName: '',
|
||||
content: '',
|
||||
key: ''
|
||||
key: '',
|
||||
imgCount: 0
|
||||
})
|
||||
/** 获取当前光标选取的信息(需要判断是否为空) */
|
||||
|
||||
@ -128,7 +129,7 @@ export const useCommon = () => {
|
||||
`
|
||||
// 把dom中的value值作为回复信息的作者,dom中的content作为回复信息的内容
|
||||
const author = dom.accountName + ':'
|
||||
const content = dom.content
|
||||
let content = dom.content
|
||||
// 创建一个div标签节点作为回复信息的头部
|
||||
const headerNode = document.createElement('div')
|
||||
headerNode.style.cssText = `
|
||||
@ -151,6 +152,14 @@ export const useCommon = () => {
|
||||
min-width: 0;
|
||||
`
|
||||
let contentBox
|
||||
// 判断content内容是否是data:image/开头的数组
|
||||
if (Array.isArray(content)) {
|
||||
// 获取总共有多少张图片
|
||||
const imageCount = content.length
|
||||
// 获取第一个data:image/开头的图片
|
||||
content = content.find((item: string) => item.startsWith('data:image/'))
|
||||
reply.value.imgCount = imageCount
|
||||
}
|
||||
// 判断content内容开头是否是data:image/的是图片
|
||||
if (content.startsWith('data:image/')) {
|
||||
// 再创建一个img标签节点,并设置src属性为base64编码的图片
|
||||
@ -165,7 +174,14 @@ export const useCommon = () => {
|
||||
`
|
||||
// 将img标签节点插入到div标签节点中
|
||||
divNode.appendChild(contentBox)
|
||||
// 把图片传入到reply的content属性中
|
||||
reply.value.content = content
|
||||
} else {
|
||||
// 判断是否有@标签
|
||||
if (content.includes('id="aitSpan"')) {
|
||||
// 去掉content中的标签
|
||||
content = removeTag(content)
|
||||
}
|
||||
// 把正文放到span标签中,并设置span标签的样式
|
||||
contentBox = document.createElement('span')
|
||||
contentBox.style.cssText = `
|
||||
@ -183,6 +199,8 @@ export const useCommon = () => {
|
||||
// 在回复信息的右边添加一个关闭信息的按钮
|
||||
const closeBtn = document.createElement('span')
|
||||
closeBtn.style.cssText = `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
@ -203,7 +221,7 @@ export const useCommon = () => {
|
||||
selection?.removeAllRanges()
|
||||
selection?.addRange(range)
|
||||
triggerInputEvent(messageInput)
|
||||
reply.value = { accountName: '', content: '', key: '' }
|
||||
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
|
||||
})
|
||||
// 将头部和正文节点插入到div标签节点中
|
||||
divNode.appendChild(headerNode)
|
||||
@ -281,8 +299,8 @@ export const useCommon = () => {
|
||||
const handlePaste = (e: any, dom: HTMLElement) => {
|
||||
e.preventDefault()
|
||||
if (e.clipboardData.files.length > 0) {
|
||||
if (e.clipboardData.files.length > 5) {
|
||||
window.$message.warning('一次性只能上传5个文件')
|
||||
if (e.clipboardData.files.length > LimitEnum.COM_COUNT) {
|
||||
window.$message.warning(`一次性只能上传${LimitEnum.COM_COUNT}个文件或图片`)
|
||||
return
|
||||
}
|
||||
for (const file of e.clipboardData.files) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { MittEnum, MsgEnum } from '@/enums'
|
||||
import { LimitEnum, MittEnum, MsgEnum } from '@/enums'
|
||||
import { Ref } from 'vue'
|
||||
import { MockItem } from '@/services/types.ts'
|
||||
import { setting } from '@/stores/setting.ts'
|
||||
@ -70,7 +70,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
|
||||
}
|
||||
// 如果输入框没有值就把回复内容清空
|
||||
if (msgInput.value === '') {
|
||||
reply.value = { accountName: '', content: '', key: '' }
|
||||
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
|
||||
}
|
||||
})
|
||||
|
||||
@ -94,7 +94,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
|
||||
// TODO 如果已经有就替换原来的内容 (nyh -> 2024-04-18 23:10:56)
|
||||
return
|
||||
}
|
||||
reply.value = { accountName: event.value, content: event.content, key: event.key }
|
||||
reply.value = { imgCount: 0, accountName: event.value, content: event.content, key: event.key }
|
||||
if (messageInputDom.value) {
|
||||
nextTick().then(() => {
|
||||
messageInputDom.value.focus()
|
||||
@ -108,6 +108,11 @@ export const useMsgInput = (messageInputDom: Ref) => {
|
||||
/* 处理发送信息事件 */
|
||||
// TODO 输入框中的内容当我切换消息的时候需要记录之前输入框的内容 (nyh -> 2024-03-01 07:03:43)
|
||||
const send = () => {
|
||||
// 判断输入框中的图片或者文件数量是否超过限制
|
||||
if (messageInputDom.value.querySelectorAll('img').length > LimitEnum.COM_COUNT) {
|
||||
window.$message.warning(`一次性只能上传${LimitEnum.COM_COUNT}个文件或图片`)
|
||||
return
|
||||
}
|
||||
ait.value = false
|
||||
const contentType = getMessageContentType(messageInputDom)
|
||||
const msg = {
|
||||
@ -146,7 +151,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
|
||||
Mitt.emit(MittEnum.SEND_MESSAGE, msg)
|
||||
msgInput.value = ''
|
||||
messageInputDom.value.innerHTML = ''
|
||||
reply.value = { accountName: '', content: '', key: '' }
|
||||
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
|
||||
}
|
||||
|
||||
/* 当输入框手动输入值的时候触发input事件(使用vueUse的防抖) */
|
||||
|
@ -91,19 +91,21 @@ export const useWindow = () => {
|
||||
const checkWinExist = async (L: string) => {
|
||||
const isExistsWinds = WebviewWindow.getByLabel(L)
|
||||
if (isExistsWinds) {
|
||||
// 如果窗口已存在,首先检查是否最小化了
|
||||
const minimized = await isExistsWinds.isMinimized()
|
||||
// 检查是否是隐藏
|
||||
const hidden = await isExistsWinds.isVisible()
|
||||
if (!hidden) {
|
||||
await isExistsWinds.show()
|
||||
}
|
||||
if (minimized) {
|
||||
// 如果已最小化,恢复窗口
|
||||
await isExistsWinds.unminimize()
|
||||
}
|
||||
// 如果窗口已存在,则给它焦点,使其在最前面显示
|
||||
await isExistsWinds.setFocus()
|
||||
nextTick().then(async () => {
|
||||
// 如果窗口已存在,首先检查是否最小化了
|
||||
const minimized = await isExistsWinds.isMinimized()
|
||||
// 检查是否是隐藏
|
||||
const hidden = await isExistsWinds.isVisible()
|
||||
if (!hidden) {
|
||||
await isExistsWinds.show()
|
||||
}
|
||||
if (minimized) {
|
||||
// 如果已最小化,恢复窗口
|
||||
await isExistsWinds.unminimize()
|
||||
}
|
||||
// 如果窗口已存在,则给它焦点,使其在最前面显示
|
||||
await isExistsWinds.setFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,10 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
/* 回复图片的计数器样式 */
|
||||
.reply-img-sub {
|
||||
@apply absolute bottom-8px right-6px color-#13987f bg-[--bg-reply-img-count] p-[2px_4px] rounded-6px text-10px;
|
||||
}
|
||||
}
|
||||
/* 跳转到回复内容时候显示的样式 */
|
||||
.active-reply {
|
||||
|
@ -58,6 +58,7 @@
|
||||
--bg-reply-bubble: #d3d3d3;
|
||||
--reply-color: #909090;
|
||||
--reply-hover: #505050;
|
||||
--bg-reply-img-count: #e3e3e3;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
@ -119,6 +120,7 @@ html[data-theme='dark'] {
|
||||
--bg-reply-bubble: #505050;
|
||||
--reply-color: #e3e3e3;
|
||||
--reply-hover: #b1b1b1;
|
||||
--bg-reply-img-count: #505050;
|
||||
}
|
||||
/*! end */
|
||||
// 线性动画
|
||||
|
Loading…
Reference in New Issue
Block a user