feat(components): 完善右键功能的显示资料

剔除冗余代码
完善回复对超链接的支持
This commit is contained in:
nongyehong 2024-04-22 23:32:09 +08:00
parent fc753b5f05
commit cf4820bffb
14 changed files with 138 additions and 88 deletions

View File

@ -37,12 +37,13 @@
<n-popover
@update:show="handlePopoverUpdate(item.key)"
trigger="click"
placement="right-start"
placement="right"
:show-arrow="false"
v-model:show="infoPopover"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<ContextMenu
@select="$event.click(item)"
@select="$event.click(item, 'Main')"
:menu="activeItem.type === RoomTypeEnum.GROUP ? optionsList : []"
:special-menu="report">
<n-avatar
@ -62,7 +63,7 @@
</ContextMenu>
</template>
<!-- 用户个人信息框 -->
<InfoPopover :info="item.accountId !== userId ? activeItemRef : void 0" />
<InfoPopover v-if="selectKey === item.key" :info="item.accountId !== userId ? activeItemRef : void 0" />
</n-popover>
<n-flex
vertical
@ -220,7 +221,6 @@ import { EventEnum, MittEnum, MsgEnum, RoomTypeEnum } from '@/enums'
import { MockItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { invoke } from '@tauri-apps/api/tauri'
import { optionsList, report } from './config.ts'
import { usePopover } from '@/hooks/usePopover.ts'
import { useWindow } from '@/hooks/useWindow.ts'
import { listen } from '@tauri-apps/api/event'
@ -238,15 +238,14 @@ const activeItemRef = ref({ ...activeItem })
const settingStore = setting()
const { login } = storeToRefs(settingStore)
const { createWebviewWindow } = useWindow()
/** 当前点击的用户的key */
const selectKey = ref()
/** 跳转回复消息后选中效果 */
const activeReply = ref(-1)
/** item最小高度用于计算滚动大小和位置 */
const itemSize = computed(() => (activeItem.type === RoomTypeEnum.GROUP ? 98 : 70))
/** 虚拟列表 */
const virtualListInst = ref<VirtualListInst>()
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-main')
/** 手动触发Popover显示 */
const infoPopover = ref(false)
const { removeTag } = useCommon()
const {
handleScroll,
@ -262,8 +261,12 @@ const {
modalShow,
userId,
specialMenuList,
itemComputed
itemComputed,
optionsList,
report,
selectKey
} = useChatMain(activeItem)
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-main')
// // TextBody
// const textBody = {
// content: '123',
@ -403,6 +406,11 @@ onMounted(() => {
Mitt.on(MittEnum.SEND_MESSAGE, (event: any) => {
handleSendMessage(event)
})
Mitt.on(`${MittEnum.INFO_POPOVER}-Main`, (event: any) => {
selectKey.value = event
infoPopover.value = true
handlePopoverUpdate(event)
})
Mitt.on(MittEnum.MSG_BOX_SHOW, (event: any) => {
activeItemRef.value = event.item
})

View File

@ -35,11 +35,12 @@
<n-popover
@update:show="handlePopoverUpdate(item.key)"
trigger="click"
placement="left-start"
placement="left"
:show-arrow="false"
v-model:show="infoPopover"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<ContextMenu @select="$event.click(item)" :menu="optionsList" :special-menu="report">
<ContextMenu @select="$event.click(item, 'Sidebar')" :menu="optionsList" :special-menu="report">
<n-flex @click="selectKey = item.key" :key="item.key" :size="10" align="center" class="item">
<n-avatar
lazy
@ -57,30 +58,31 @@
</ContextMenu>
</template>
<!-- 用户个人信息框 -->
<InfoPopover :info="item" />
<InfoPopover v-if="selectKey === item.key" :info="item" />
</n-popover>
</template>
</n-virtual-list>
</main>
</template>
<script setup lang="ts">
import { RoomTypeEnum } from '@/enums'
import { MittEnum, RoomTypeEnum } from '@/enums'
import { MockItem } from '@/services/types.ts'
import { MockList } from '@/mock'
import { InputInst } from 'naive-ui'
import { optionsList, report } from './config.ts'
import { usePopover } from '@/hooks/usePopover.ts'
/** 当前点击的用户的key */
const selectKey = ref()
const isSearch = ref(false)
const searchRef = ref('')
const inputInstRef = ref<InputInst | null>(null)
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-sidebar')
import { useChatMain } from '@/hooks/useChatMain.ts'
import Mitt from '@/utils/Bus.ts'
const { activeItem } = defineProps<{
activeItem: MockItem
}>()
const isSearch = ref(false)
const searchRef = ref('')
/** 手动触发Popover显示 */
const infoPopover = ref(false)
const inputInstRef = ref<InputInst | null>(null)
const { optionsList, report, selectKey } = useChatMain(activeItem)
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-sidebar')
const handleSearch = () => {
isSearch.value = !isSearch.value
@ -88,6 +90,14 @@ const handleSearch = () => {
inputInstRef.value?.select()
})
}
onMounted(() => {
Mitt.on(`${MittEnum.INFO_POPOVER}-Sidebar`, (event: any) => {
selectKey.value = event
infoPopover.value = true
handlePopoverUpdate(event)
})
})
</script>
<style scoped lang="scss">

View File

@ -1,36 +0,0 @@
// TODO config文件做简单的操作配置如果需求复杂就封装成hooks (nyh -> 2024-03-23 03:35:05)
/** 右键用户信息菜单(单聊的时候显示) */
const optionsList = ref([
{
label: '发送信息',
icon: 'message-action',
click: (item: any) => {
console.log(item)
}
},
{
label: 'TA',
icon: 'aite',
click: () => {}
},
{
label: '查看资料',
icon: 'notes',
click: () => {}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
}
])
/** 举报选项 */
const report = ref([
{
label: '举报',
icon: 'caution',
click: () => {}
}
])
export { optionsList, report }

View File

@ -59,7 +59,9 @@ export enum MittEnum {
/** 消息列表被清空或者暂无消息 */
NOT_MSG = 'notMsg',
/** 回复消息 */
REPLY_MEG = 'replyMeg'
REPLY_MEG = 'replyMeg',
/** 手动触发InfoPopover */
INFO_POPOVER = 'infoPopover'
}
/** 主题类型 */

View File

@ -9,28 +9,30 @@ export const useChatMain = (activeItem: MockItem) => {
const { removeTag } = useCommon()
const settingStore = setting()
const { login } = storeToRefs(settingStore)
/* 选中的气泡消息 */
/** 选中的气泡消息 */
const activeBubble = ref(-1)
/* 当前登录的用户id */
/** 当前登录的用户id */
const userId = ref(login.value.accountInfo.uid)
/* 提醒框标题 */
/** 提醒框标题 */
const tips = ref()
/* 是否显示删除信息的弹窗 */
/** 是否显示删除信息的弹窗 */
const modalShow = ref(false)
/* 需要删除信息的下标 */
/** 需要删除信息的下标 */
const delIndex = ref(0)
/* 悬浮的页脚 */
/** 悬浮的页脚 */
const floatFooter = ref(false)
/* 记录历史消息下标 */
/** 记录历史消息下标 */
const historyIndex = ref(0)
/* 新消息数 */
/** 新消息数 */
const newMsgNum = ref(0)
/* 计算出触发页脚后的历史消息下标 */
/** 当前点击的用户的key */
const selectKey = ref()
/** 计算出触发页脚后的历史消息下标 */
const itemComputed = computed(() => {
return items.value.filter((item) => item.accountId !== userId.value).length
})
/*! 模拟信息列表 */
/**! 模拟信息列表 */
const items = ref(
Array.from({ length: 5 }, (_, i) => ({
value: `${i}安老师`,
@ -49,7 +51,7 @@ export const useChatMain = (activeItem: MockItem) => {
}))
)
/* 通用右键菜单 */
/** 通用右键菜单 */
const commonMenuList = ref<OPT.RightMenu[]>([
{
label: '转发',
@ -65,7 +67,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/* 右键消息菜单列表 */
/** 右键消息菜单列表 */
const menuList = ref<OPT.RightMenu[]>([
{
label: '复制',
@ -77,7 +79,7 @@ export const useChatMain = (activeItem: MockItem) => {
},
...commonMenuList.value
])
/* 右键菜单下划线后的列表 */
/** 右键菜单下划线后的列表 */
const specialMenuList = ref<OPT.RightMenu[]>([
{
label: '删除',
@ -89,7 +91,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/* 文件类型右键菜单 */
/** 文件类型右键菜单 */
const fileMenuList = ref<OPT.RightMenu[]>([
{
label: '预览',
@ -108,7 +110,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/* 图片类型右键菜单 */
/** 图片类型右键菜单 */
const imageMenuList = ref<OPT.RightMenu[]>([
{
label: '添加到表情',
@ -135,6 +137,41 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/** 右键用户信息菜单(单聊的时候显示) */
const optionsList = ref([
{
label: '发送信息',
icon: 'message-action',
click: (item: any) => {
console.log(item)
}
},
{
label: 'TA',
icon: 'aite',
click: () => {}
},
{
label: '查看资料',
icon: 'notes',
click: (item: any, type: string) => {
Mitt.emit(`${MittEnum.INFO_POPOVER}-${type}`, item.key)
}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
}
])
/** 举报选项 */
const report = ref([
{
label: '举报',
icon: 'caution',
click: () => {}
}
])
/**
*
@ -166,7 +203,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
/* 处理滚动事件(用于页脚显示功能) */
/** 处理滚动事件(用于页脚显示功能) */
const handleScroll = (e: Event) => {
const target = e.target as HTMLElement
// 获取已滚动的距离,即从顶部到当前滚动位置的距离
@ -197,7 +234,7 @@ export const useChatMain = (activeItem: MockItem) => {
return type === MsgEnum.IMAGE ? imageMenuList.value : type === MsgEnum.FILE ? fileMenuList.value : menuList.value
}
/* 删除信息事件 */
/** 删除信息事件 */
const handleConfirm = () => {
// 根据key找到items中对应的下标
const index = items.value.findIndex((item) => item.key === delIndex.value)
@ -205,7 +242,7 @@ export const useChatMain = (activeItem: MockItem) => {
modalShow.value = false
}
/* 点击气泡消息时候监听用户是否按下ctrl+c来复制内容 */
/** 点击气泡消息时候监听用户是否按下ctrl+c来复制内容 */
const handleMsgClick = (item: any) => {
activeBubble.value = item.key
// 启用键盘监听
@ -235,6 +272,9 @@ export const useChatMain = (activeItem: MockItem) => {
modalShow,
userId,
specialMenuList,
itemComputed
itemComputed,
optionsList,
report,
selectKey
}
}

View File

@ -1,6 +1,7 @@
import { LimitEnum, MsgEnum } from '@/enums'
import { Ref } from 'vue'
import { createFileOrVideoDom } from '@/utils/CreateDom.ts'
import { RegExp } from '@/utils/RegExp.ts'
/** 常用工具类 */
export const useCommon = () => {
@ -152,6 +153,15 @@ export const useCommon = () => {
min-width: 0;
`
let contentBox
const { hyperlinkRegex, foundHyperlinks } = RegExp.isHyperlink(content)
// 判断是否包含超链接
if (foundHyperlinks && foundHyperlinks.length > 0) {
content.replace(hyperlinkRegex, (match: string) => {
reply.value.content = match.startsWith('www.') ? 'https://' + match : match
})
// 去掉content中的标签
content = removeTag(content)
}
// 判断content内容是否是data:image/开头的数组
if (Array.isArray(content)) {
// 获取总共有多少张图片

View File

@ -7,6 +7,7 @@ import { useDebounceFn } from '@vueuse/core'
import Mitt from '@/utils/Bus.ts'
import { MockList } from '@/mock'
import { useCommon } from './useCommon.ts'
import { RegExp } from '@/utils/RegExp.ts'
export const useMsgInput = (messageInputDom: Ref) => {
const { triggerInputEvent, insertNode, getMessageContentType, getEditorRange, imgPaste, removeTag, reply } =
@ -120,9 +121,6 @@ export const useMsgInput = (messageInputDom: Ref) => {
content: msgInput.value,
reply: contentType === MsgEnum.REPLY ? reply.value : null
}
const hyperlinkRegex = /(\b(?:https?:\/\/|www)[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi
const foundHyperlinks = msg.content.match(hyperlinkRegex)
/** 如果是Reply消息需要将消息的样式修改 */
if (msg.type === MsgEnum.REPLY) {
// 先去掉原来的标签
@ -131,6 +129,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
// TODO 不允许用户删除回复消息中最前面的空格或者标志符号 (nyh -> 2024-04-17 06:39:22)
msg.content = msg.content.replace(/^[\S\s]*\u00A0/, '')
}
const { hyperlinkRegex, foundHyperlinks } = RegExp.isHyperlink(msg.content)
/** 判断是否有超链接 */
if (foundHyperlinks && foundHyperlinks.length > 0) {
msg.content = msg.content.replace(hyperlinkRegex, (match) => {

View File

@ -25,7 +25,7 @@ export const MockList = ref<MockItem[]>(
key: i,
avatar: `${avatars}?${i}`,
type: type,
accountId: i,
accountId: `${i}`,
accountName: generateRandomString(Math.floor(Math.random() * 10) + 1, type)
}
})

View File

@ -4,6 +4,7 @@ import Mitt from '@/utils/Bus.ts'
const { VITE_WEBSOCKET_URL } = import.meta.env
/** websocket连接对象 */
let ws: WebSocket
/** 初始化websocket连接 */
const initWebSocket = () => {
ws = new WebSocket(`${VITE_WEBSOCKET_URL}/`)
ws.onopen = () => {

View File

@ -24,7 +24,6 @@ declare module 'vue' {
NAvatar: typeof import('naive-ui')['NAvatar']
NAvatarGroup: typeof import('naive-ui')['NAvatarGroup']
NBadge: typeof import('naive-ui')['NBadge']
NBlockquote: typeof import('naive-ui')['NBlockquote']
NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCheckbox: typeof import('naive-ui')['NCheckbox']

View File

@ -31,11 +31,13 @@ export class RegExp {
}
/**
*
* @param val
*
* @param content
* @returns hyperlinkRegex foundHyperlinks
*/
public static isHyperlink(val: string): boolean {
const hyperlinkRegex = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?$/
return hyperlinkRegex.test(val)
public static isHyperlink(content: string) {
const hyperlinkRegex = /(\b(?:https?:\/\/|www)[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi
const foundHyperlinks = content.match(hyperlinkRegex)
return { hyperlinkRegex, foundHyperlinks }
}
}

15
src/utils/Worker.ts Normal file
View File

@ -0,0 +1,15 @@
// 在 Web Worker 中接收消息
self.onmessage = (event) => {
const number = event.data
// 执行一些耗时的操作
const result = calculateSquare(number)
// 将结果发送回主线程
self.postMessage(result)
}
// 一些耗时的操作
const calculateSquare = (number: any) => {
return number * number
}

View File

@ -56,6 +56,7 @@ onMounted(() => {
Mitt.on(WsResEnum.QRCODE_LOGIN, (e: any) => {
QRCode.value = e.data.loginUrl
loading.value = false
loadText.value = '请使用微信扫码登录'
})
Mitt.on(WsResEnum.LOGIN_SUCCESS, (e: any) => {
delay(async () => {
@ -69,7 +70,6 @@ onMounted(() => {
}, 1000)
})
delay(() => {
loadText.value = '请使用微信扫码登录'
sendToServer({ type: WsReqEnum.LOGIN })
}, 1000)
})

View File

@ -9,7 +9,7 @@ import unocss from '@unocss/vite'
import terser from '@rollup/plugin-terser'
// https://vitejs.dev/config/
/**! 暂时不需要优化前端打包(如开启gzip这些tauri可能解析不了) */
/**! 不需要优化前端打包(如开启gzip) */
export default defineConfig(({ mode }: ConfigEnv) => {
// 获取当前环境的配置,如何设置第三个参数则加载所有变量而不是以“VITE_”前缀的变量
const config = loadEnv(mode, '/')