✨ feat(im): MessageInput 工具栏挪到底部 + 4 图标统一 Iconify + 聊天历史挪到右上角
对齐微信 PC:输入区在上、操作图标在下;会话级操作(如聊天历史)统一放 header 右上角
【MessageInput.vue】
- 模板顺序对调:editor 在上 / 工具栏在下(justify-between:左 4 图标 gap-1 / 右"发 送"按钮)
- editor min-height 80 → 100、padding 8/12 → 10/14,输入区视觉权重接近微信
- 4 个图标统一走 Iconify ant-design outlined 同源,避免 ep / antd 混用视觉割裂:
- 表情:Sunny → ant-design:smile-outlined(Element Plus 没有 smile,必须走 Iconify)
- 图片:Picture → ant-design:picture-outlined
- 文件夹:Paperclip → ant-design:folder-outlined(附件 → 文件夹更贴近微信观感)
- 语音:Microphone → ant-design:audio-outlined
- 整条 @element-plus/icons-vue import 删除,全部改 <span class="message-input__tool inline-flex …">
+ <Icon icon="…" :size="18" /> 的统一外壳;scoped CSS 的 :deep(svg) 继续命中,padding / hover
样式不动;DOM 实测 4 图标全部 30×30、top:761、间距 34px 完全对齐
- EmojiPicker class:bottom-9 left-3 → bottom-full left-3 mb-2,picker 从工具栏顶部向上弹出
(旧值在新布局下会浮在工具栏内部,盖住图标)
- 删 defineEmits<{ openHistory }>():聊天历史挪到 ChatPanel header 后已没有调用方
【ChatPanel.vue】
- header 右上角新增"聊天历史"图标(Tickets),点击直接 historyVisible = true 弹"历史消息"抽屉
(对齐微信 PC:右上角集中放会话级操作;并列在原"聊天信息 / 群聊信息"图标左侧)
- <MessageInput :key="…" @open-history="…"> 上的 listener 摘掉,emit 链路完整解耦
im
parent
fc82ed3d7e
commit
ccc9aca21c
|
|
@ -2,55 +2,8 @@
|
|||
<div
|
||||
class="relative flex flex-col bg-[var(--el-bg-color)] border-t border-[var(--el-border-color-lighter)]"
|
||||
>
|
||||
<!-- 顶部工具栏:表情 / 图片 / 文件 / 语音 / 历史 -->
|
||||
<div class="relative flex items-center gap-2 h-9 px-3">
|
||||
<el-tooltip content="表情" placement="top">
|
||||
<el-icon
|
||||
class="message-input__tool box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click.stop="toggleEmoji"
|
||||
>
|
||||
<Sunny />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="发送图片" placement="top">
|
||||
<el-icon
|
||||
class="message-input__tool box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="imageInputRef?.click()"
|
||||
>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="发送文件" placement="top">
|
||||
<el-icon
|
||||
class="message-input__tool box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="fileInputRef?.click()"
|
||||
>
|
||||
<Paperclip />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="语音消息" placement="top">
|
||||
<el-icon
|
||||
class="message-input__tool box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="voiceVisible = true"
|
||||
>
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="历史消息" placement="top">
|
||||
<el-icon
|
||||
class="message-input__tool box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="$emit('openHistory')"
|
||||
>
|
||||
<Tickets />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 浮层:表情面板,绝对定位到工具栏左上方 -->
|
||||
<EmojiPicker v-model:visible="emojiVisible" class="bottom-9 left-3" @select="insertText" />
|
||||
</div>
|
||||
|
||||
<!--
|
||||
输入区:contenteditable div(取代 textarea)
|
||||
输入区在上:contenteditable div(取代 textarea,对齐微信 PC:输入区在上,操作在下)
|
||||
- 让 @ 浮层能拿到真实光标 rect(textarea 拿不到)
|
||||
- 让 @ 成员以 <span data-id> token 节点存在,删 token 即删 id,避免 stale atUserIds
|
||||
- placeholder 通过 data-empty + ::before 模拟(contenteditable 没有原生 placeholder)
|
||||
|
|
@ -68,9 +21,61 @@
|
|||
@paste.prevent="onPaste"
|
||||
></div>
|
||||
|
||||
<!-- 发送按钮 -->
|
||||
<div class="flex justify-end px-3 pt-1.5 pb-2.5">
|
||||
<!--
|
||||
底部工具栏:左侧操作图标 + 右侧发送按钮(对齐微信 PC:操作图标统一放底部)
|
||||
- relative 给 EmojiPicker 提供 absolute 锚点,picker 用 bottom-full 向上弹出
|
||||
- 图标统一 30×30 点击区(18px icon + p-1.5),gap-1 让间距贴合微信观感
|
||||
-->
|
||||
<div class="relative flex items-center justify-between gap-2 px-3 pb-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<!--
|
||||
所有 icon 统一走 Iconify(ant-design outlined 系列):
|
||||
- 视觉风格更接近微信 PC(线性、圆角,比 Element Plus 内置的更轻量)
|
||||
- 笑脸 / 图片 / 文件夹 / 麦克风 同源,避免一个走 ep 一个走 antd 视觉割裂
|
||||
- 外层 span 复用 .message-input__tool 的 padding / hover 样式,scoped CSS 的 :deep(svg) 仍能命中
|
||||
-->
|
||||
<el-tooltip content="表情" placement="top">
|
||||
<span
|
||||
class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click.stop="toggleEmoji"
|
||||
>
|
||||
<Icon icon="ant-design:smile-outlined" :size="18" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="发送图片" placement="top">
|
||||
<span
|
||||
class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="imageInputRef?.click()"
|
||||
>
|
||||
<Icon icon="ant-design:picture-outlined" :size="18" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="发送文件" placement="top">
|
||||
<span
|
||||
class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="fileInputRef?.click()"
|
||||
>
|
||||
<Icon icon="ant-design:folder-outlined" :size="18" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="语音消息" placement="top">
|
||||
<span
|
||||
class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--el-fill-color)]"
|
||||
@click="voiceVisible = true"
|
||||
>
|
||||
<Icon icon="ant-design:audio-outlined" :size="18" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-button type="primary" :disabled="!canSend" @click="handleSend">发 送</el-button>
|
||||
|
||||
<!-- 表情面板:bottom-full 让 picker 下沿贴工具栏顶部,向上弹出(对齐工具栏左侧首图标) -->
|
||||
<EmojiPicker
|
||||
v-model:visible="emojiVisible"
|
||||
class="bottom-full left-3 mb-2"
|
||||
@select="insertText"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- @ 选择浮层:群聊才启用 -->
|
||||
|
|
@ -95,9 +100,9 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue'
|
||||
import { Sunny, Picture, Paperclip, Microphone, Tickets } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import Icon from '@/components/Icon/src/Icon.vue'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { updateFile } from '@/api/infra/file'
|
||||
import { useConversationStore } from '@/views/im/home/store/conversationStore'
|
||||
|
|
@ -118,10 +123,6 @@ import type { GroupMemberLite } from '../ChatGroupMember.vue'
|
|||
|
||||
defineOptions({ name: 'ImMessageInput' })
|
||||
|
||||
defineEmits<{
|
||||
openHistory: [] // 打开历史消息抽屉(由 ChatPanel / MessagePage 承接)
|
||||
}>()
|
||||
|
||||
const conversationStore = useConversationStore()
|
||||
const groupStore = useGroupStore()
|
||||
const { send, sendRaw } = useMessageSender()
|
||||
|
|
@ -682,12 +683,14 @@ async function onVoiceSend(payload: { blob: Blob; duration: number }) {
|
|||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
/* 输入区在上、工具栏在下时,编辑区视觉上承担"主体",min-height 撑大一些贴近微信观感;
|
||||
max-height 不再无限增长,超过则内部滚动,避免聊天列表被挤太短 */
|
||||
.message-input__editor {
|
||||
position: relative;
|
||||
min-height: 80px;
|
||||
min-height: 100px;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 12px;
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
|
|
|
|||
Loading…
Reference in New Issue