mirror of
https://gitee.com/HuLaSpark/HuLa.git
synced 2024-11-29 18:28:30 +08:00
feat(components): ✨ 完善右键功能的显示资料
剔除冗余代码 完善回复对超链接的支持
This commit is contained in:
parent
fc753b5f05
commit
cf4820bffb
@ -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
|
||||
})
|
||||
|
@ -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">
|
||||
|
@ -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 }
|
@ -59,7 +59,9 @@ export enum MittEnum {
|
||||
/** 消息列表被清空或者暂无消息 */
|
||||
NOT_MSG = 'notMsg',
|
||||
/** 回复消息 */
|
||||
REPLY_MEG = 'replyMeg'
|
||||
REPLY_MEG = 'replyMeg',
|
||||
/** 手动触发InfoPopover */
|
||||
INFO_POPOVER = 'infoPopover'
|
||||
}
|
||||
|
||||
/** 主题类型 */
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
// 获取总共有多少张图片
|
||||
|
@ -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) => {
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -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 = () => {
|
||||
|
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@ -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']
|
||||
|
@ -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
15
src/utils/Worker.ts
Normal 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
|
||||
}
|
@ -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)
|
||||
})
|
||||
|
@ -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, '/')
|
||||
|
Loading…
Reference in New Issue
Block a user