Skip to content

Commit

Permalink
refactor: 完善艾特功能 (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
oljc authored Jul 7, 2023
1 parent de9a57b commit 2336abc
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 59 deletions.
21 changes: 18 additions & 3 deletions src/views/Home/components/ChatBox/MsgInput/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
// 艾特功能参考自 https://github.com/MrHGJ/at-mentions
import { ref, reactive, toRefs, watch, watchEffect, type StyleValue } from 'vue'
import { ref, reactive, toRefs, watch, watchEffect, type StyleValue, inject, provide } from 'vue'
import type { IMention, INode } from './types'
import type { CacheUserItem } from '@/services/types'
import { NodeType } from './types'
Expand Down Expand Up @@ -53,6 +53,7 @@ const emit = defineEmits([
'send',
])
const focusMsgInput = inject<() => void>('focusMsgInput')
const { modelValue: value, mentions, maxLength, disabled } = toRefs(props)
const editorRef = ref<HTMLElement | null>()
const scrollRef = ref()
Expand Down Expand Up @@ -292,7 +293,7 @@ const insertHtmlAtCaret = (
}
// 选择@的人。替换原来的检索文案,并插入新的@标签<button/>
const onSelectPerson = (personItem: CacheUserItem, ignore = false) => {
const selectPerson = (personItem: CacheUserItem, ignore = false) => {
// 选择人员后关闭并重置选人框,重置搜索词
showDialog.value = false
// 滚动到候选框顶部
Expand Down Expand Up @@ -396,7 +397,7 @@ const onInputKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault()
// 选择当前人
onSelectPerson(personList.value[activeIndex.value])
selectPerson(personList.value[activeIndex.value])
// 更新输入框同步值
onInputText()
}
Expand All @@ -411,6 +412,10 @@ const onInputKeyDown = (e: KeyboardEvent) => {
onWrap()
return
}
// 处理输入法状态下的回车事件
if ((e as KeyboardEvent).isComposing) {
return e.preventDefault()
}
// 禁止默认换行
if (e.key === 'Enter') {
e.preventDefault()
Expand Down Expand Up @@ -501,6 +506,16 @@ const onPaste = (e: ClipboardEvent) => {
return false
}
//
const onSelectPerson = (uid: number, ignore = false) => {
if (!uid) return
focusMsgInput?.()
setTimeout(() => {
const userItem = cachedStore.userCachedList[uid]
userItem && selectPerson?.(userItem as CacheUserItem, ignore)
}, 10)
}
// 暴露 ref 属性
defineExpose({ input: editorRef, range: editorRange, onSelectPerson })
</script>
Expand Down
38 changes: 20 additions & 18 deletions src/views/Home/components/ChatBox/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { judgeClient } from '@/utils/detectDevice'
import { emojis } from './constant'
import type { IMention } from './MsgInput/types'
import type { CacheUserItem } from '@/services/types'
import { useFileDialog } from '@vueuse/core'
import { useUpload } from '@/hooks/useUpload'
import { useRecording } from '@/hooks/useRecording'
Expand All @@ -30,7 +29,7 @@ const chatStore = useChatStore()
const isSelect = ref(false)
const isSending = ref(false)
const inputMsg = ref('')
const msg_input_ref = ref<typeof ElInput>()
const mentionRef = ref<typeof ElInput>()
const mentionList = ref<IMention[]>([])
const isAudio = ref(false)
const isHovered = ref(false)
Expand All @@ -40,16 +39,17 @@ const nowMsgType = ref<MsgEnum>(MsgEnum.FILE)
const focusMsgInput = () => {
setTimeout(() => {
if (!msg_input_ref.value) return
msg_input_ref.value?.focus?.()
const selection = msg_input_ref.value?.range?.selection as Selection
selection?.selectAllChildren(msg_input_ref.value.input)
if (!mentionRef.value) return
mentionRef.value?.focus?.()
const selection = mentionRef.value?.range?.selection as Selection
selection?.selectAllChildren(mentionRef.value.input)
selection?.collapseToEnd()
})
}
const onSelectPerson = (personItem: CacheUserItem, ignoreContentCheck?: boolean) => {
msg_input_ref.value?.onSelectPerson?.(personItem, ignoreContentCheck)
// 艾特
const onSelectPerson = (uid: number, ignoreCheck?: boolean) => {
mentionRef.value?.onSelectPerson?.(uid, ignoreCheck)
isAudio.value = false
}
provide('focusMsgInput', focusMsgInput)
Expand All @@ -76,11 +76,7 @@ const send = (msgType: MsgEnum, body: any, roomId = 1) => {
})
}
const sendMsgHandler = (e: Event) => {
// 处理输入法状态下的回车事件
if ((e as KeyboardEvent).isComposing) {
return e.preventDefault()
}
const sendMsgHandler = () => {
// 空消息或正在发送时禁止发送
if (!inputMsg.value?.trim().length || isSending.value) {
return
Expand Down Expand Up @@ -127,8 +123,8 @@ const showReplyContent = () => {
const onClearReply = () => (chatStore.currentMsgReply = {})
// 插入表情
const insertEmoji = (emoji: string) => {
const input = msg_input_ref.value?.input
const editRange = msg_input_ref.value?.range as {
const input = mentionRef.value?.input
const editRange = mentionRef.value?.range as {
range: Range
selection: Selection
}
Expand Down Expand Up @@ -241,7 +237,7 @@ const onStartRecord = () => {
class="m-input"
v-show="!isAudio"
v-model="inputMsg"
ref="msg_input_ref"
ref="mentionRef"
autofocus
:tabindex="!isSign || isSending"
:disabled="!isSign || isSending"
Expand Down Expand Up @@ -275,7 +271,13 @@ const onStartRecord = () => {
</li>
</ul>
</el-popover>
<Icon class="action" icon="at" :size="20" colorful />
<Icon
class="action"
icon="at"
:size="20"
colorful
@click="insertInputText({ content: '@', ...mentionRef?.range })"
/>
<Icon
:class="['action', { disabled: isUploading }]"
icon="tupian"
Expand Down
3 changes: 2 additions & 1 deletion src/views/Home/components/ChatBox/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
word-break: break-all;

.m-input {
margin: 0 2px;
padding: 0 4px;
margin: 0 4px;
background-color: transparent;
}

Expand Down
20 changes: 2 additions & 18 deletions src/views/Home/components/ChatList/ContextMenu/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ import {
type MenuOptions,
} from '@imengyu/vue3-context-menu'
import { useUserStore } from '@/stores/user'
import { useCachedStore } from '@/stores/cached'
import { copyToClip } from '@/utils/copy'
import { useChatStore } from '@/stores/chat'
import type { MessageType } from '@/services/types'
import { MsgEnum, PowerEnum } from '@/enums'
import type { CacheUserItem } from '@/services/types'
const focusMsgInput = inject<() => void>('focusMsgInput')
const onSelectPerson =
inject<(personItem: CacheUserItem, ignoreContentCheck?: boolean) => void>('onSelectPerson')
const onAtUser = inject<(uid: number, ignore: boolean) => void>('onSelectPerson')
const props = defineProps({
// 消息体
Expand All @@ -33,7 +29,6 @@ const props = defineProps({
const userInfo = useUserStore()?.userInfo
const chatStore = useChatStore()
const cachedStore = useCachedStore()
// FIXME 未登录到登录这些监听没有变化。需处理
const isCurrentUser = computed(() => props.msg?.fromUser.uid === userInfo.uid)
const isAdmin = computed(() => userInfo?.power === PowerEnum.ADMIN)
Expand Down Expand Up @@ -74,17 +69,6 @@ const download = () => {
}
const onDelete = () => chatStore.deleteMsg(props.msg.message.id)
// @ 用户
const onAtUser = () => {
// 输入框获取焦点
focusMsgInput?.()
// 插入内容
setTimeout(() => {
const userItem = cachedStore.userCachedList[props.msg.fromUser.uid]
userItem && onSelectPerson?.(userItem as CacheUserItem, true)
}, 10)
}
</script>

<template>
Expand All @@ -96,7 +80,7 @@ const onAtUser = () => {
...props.options,
}"
>
<ContextMenuItem label="at" @click="onAtUser" v-login-show>
<ContextMenuItem label="艾特Ta" @click="onAtUser?.(msg.fromUser.uid, true)" v-login-show>
<template #icon> <span class="icon">@</span> </template>
</ContextMenuItem>
<ContextMenuItem v-if="msg.message.type === MsgEnum.TEXT" label="复制" @click="copyContent">
Expand Down
5 changes: 4 additions & 1 deletion src/views/Home/components/ChatList/MsgItem/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const chatCls = computed(() => ({
'right': (isCurrentUser.value && props.bubbleMode === 'spread') || props.bubbleMode === 'right',
}))
const onAtUser = inject<(uid: number, ignore: boolean) => void>('onSelectPerson')
const renderMsgRef = ref<HTMLElement | null>(null)
const boxRef = ref<HTMLElement | null>(null)
const tooltipPlacement = ref()
Expand Down Expand Up @@ -138,7 +139,9 @@ onMounted(() => {
>
<img v-show="badgeInfo?.img" class="user-badge" :src="badgeInfo?.img" />
</el-tooltip>
<span class="user-name">{{ userInfo.name }}</span>
<span class="user-name" @click="onAtUser?.(userInfo.uid!, true)">{{
userInfo.name
}}</span>
<span class="user-ip">({{ userInfo.locPlace || '未知' }})</span>
<span class="send-time" v-if="isShowTime">
{{ formatTimestamp(msg.message.sendTime) }}
Expand Down
7 changes: 7 additions & 0 deletions src/views/Home/components/ChatList/MsgItem/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
cursor: pointer;
}

.user-name:hover::after {
position: absolute;
top: 0;
right: 100%;
content: '@';
}

.user-badge {
width: 18px;
height: 18px;
Expand Down
1 change: 1 addition & 0 deletions src/views/Home/components/ChatList/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const goToBottom = () => {
const goToNewMessage = () => {
// 未读消息数 = 总数 - 新消息数
virtualListRef.value.scrollToIndex(chatStore.chatMessageList.length - chatStore.newMsgCount)
chatStore.clearNewMsgCount()
}
// 提供虚拟列表 ref 给子组件使用
Expand Down
20 changes: 2 additions & 18 deletions src/views/Home/components/UserList/ContextMenu/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import { computed, type PropType, inject } from 'vue'
import apis from '@/services/apis'
import { ContextMenu, ContextMenuItem, type MenuOptions } from '@imengyu/vue3-context-menu'
import { useUserStore } from '@/stores/user'
import { useCachedStore } from '@/stores/cached'
import { PowerEnum } from '@/enums'
import type { CacheUserItem } from '@/services/types'
const focusMsgInput = inject<() => void>('focusMsgInput')
const onSelectPerson =
inject<(personItem: CacheUserItem, ignoreContentCheck?: boolean) => void>('onSelectPerson')
const onAtUser = inject<(uid: number, ignore: boolean) => void>('onSelectPerson')
const props = defineProps({
// 消息体
Expand All @@ -24,7 +20,6 @@ const props = defineProps({
})
const userInfo = useUserStore()?.userInfo
const cachedStore = useCachedStore()
const isAdmin = computed(() => userInfo?.power === PowerEnum.ADMIN)
// 拉黑用户
Expand All @@ -34,17 +29,6 @@ const onBlockUser = async () => {
await apis.blockUser({ uid }).send()
}
}
// @ 用户
const onAtUser = () => {
// 输入框获取焦点
focusMsgInput?.()
// 插入内容
setTimeout(() => {
const userItem = cachedStore.userCachedList[props.uid]
userItem && onSelectPerson?.(userItem as CacheUserItem, true)
}, 10)
}
</script>

<template>
Expand All @@ -56,7 +40,7 @@ const onAtUser = () => {
...props.options,
}"
>
<ContextMenuItem label="at" @click="onAtUser" v-login-show>
<ContextMenuItem label="艾特Ta" @click="onAtUser?.(props.uid, true)" v-login-show>
<template #icon> <span class="icon">@</span> </template>
</ContextMenuItem>
<ContextMenuItem v-if="isAdmin" label="拉黑(管理)" @click="onBlockUser">
Expand Down

0 comments on commit 2336abc

Please sign in to comment.