155 lines
3.2 KiB
Vue
155 lines
3.2 KiB
Vue
<template>
|
||
<div class="chat-input-wrap">
|
||
<div class="input-area">
|
||
<!-- 多行输入框 -->
|
||
<el-input
|
||
ref="inputRef"
|
||
v-model="inputText"
|
||
:autosize="{ minRows:5, maxRows:8 }"
|
||
placeholder="请输入回复内容"
|
||
:disabled="disable"
|
||
type="textarea"
|
||
@keydown="handleKeyDown"
|
||
/>
|
||
<!-- 发送按钮 -->
|
||
<el-button
|
||
:disabled="!canSend || sending"
|
||
class="btn-send"
|
||
size="large"
|
||
type="primary"
|
||
@click="handleSend"
|
||
>
|
||
{{ sending ? '发送中' : '发送' }}
|
||
</el-button>
|
||
</div>
|
||
<div>
|
||
<!-- 图片上传按钮 -->
|
||
<el-button
|
||
class="btn-img"
|
||
plain
|
||
type="info"
|
||
:disabled="disable"
|
||
@click="handleSelectImage"
|
||
>
|
||
上传图片
|
||
</el-button>
|
||
</div>
|
||
<!-- 隐藏input用于选图 -->
|
||
<input
|
||
ref="fileRef"
|
||
accept="image/*"
|
||
style="display:none"
|
||
type="file"
|
||
@change="onFileChange"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import {ref, computed} from 'vue'
|
||
import type {PropType} from 'vue'
|
||
|
||
const props = defineProps({
|
||
/** 是否正在发送中(外部控制,防止重复点击) */
|
||
sending: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
|
||
/** 是否禁用输入框 */
|
||
disable: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits<{
|
||
/** 发送文本消息 */
|
||
(e: 'send', text: string): void
|
||
/** 选中图片文件,交给父组件上传oss */
|
||
(e: 'uploadImage', file: File): void
|
||
}>()
|
||
|
||
const inputRef = ref<InstanceType<typeof elInput>>()
|
||
const fileRef = ref<HTMLInputElement | null>(null)
|
||
const inputText = ref('')
|
||
|
||
// 是否可以发送
|
||
const canSend = computed(() => {
|
||
return !!inputText.value.trim() && !props.disable
|
||
})
|
||
|
||
// 点击图片图标,唤起文件选择
|
||
const handleSelectImage = () => {
|
||
fileRef.value?.click()
|
||
}
|
||
|
||
// 文件选择回调
|
||
const onFileChange = (e: Event) => {
|
||
const target = e.target as HTMLInputElement
|
||
const file = target.files?.[0]
|
||
if (!file) return
|
||
emit('uploadImage', file)
|
||
// 清空file,选同一个图片也能触发change
|
||
target.value = ''
|
||
}
|
||
|
||
// 键盘事件:回车发送,Shift+Enter换行
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === 'Enter') {
|
||
if (e.shiftKey) {
|
||
// shift+enter 原生换行,不拦截
|
||
return
|
||
}
|
||
// 直接回车发送
|
||
e.preventDefault()
|
||
handleSend()
|
||
}
|
||
}
|
||
|
||
// 发送消息
|
||
const handleSend = () => {
|
||
const rawText = inputText.value.trim()
|
||
if (!rawText) return
|
||
// 把换行 \n 替换成 <br/>
|
||
const htmlText = rawText.replaceAll('\n', '<br/>')
|
||
emit('send', htmlText)
|
||
// 发送成功后清空输入框
|
||
inputText.value = ''
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.chat-input-wrap {
|
||
flex-shrink: 0;
|
||
padding: 12px;
|
||
background: #fff;
|
||
}
|
||
|
||
.input-area {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: flex-end;
|
||
|
||
:deep(.el-textarea__inner) {
|
||
background-color: #f7f9fc;
|
||
}
|
||
}
|
||
|
||
.btn-img {
|
||
flex-shrink: 0;
|
||
margin-top: 12px;
|
||
padding: 9px 13px;
|
||
color: rgba(75, 85, 99, 1);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.btn-send {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
:deep(.el-textarea__inner) {
|
||
padding: 8px 12px;
|
||
}
|
||
</style>
|