吃透 Sender 交互逻辑:提交快捷键事件与方法实战运用

吃透 Sender 交互逻辑:提交、快捷键、事件与方法实战运用

消息输入组件的交互逻辑决定了用户的使用体验。TinyRobot Sender 在提交方式、快捷键、事件体系和方法调用方面做了精心设计,本文带你深入理解每一项交互细节。

一、提交方式深度解析

Sender 提供三种提交方式,通过 submitType 属性灵活切换:

typescript 复制代码
type SubmitTrigger = 'enter' | 'ctrlEnter' | 'shiftEnter'

三种提交方式对比

配置值 提交快捷键 换行快捷键 适用场景
enter Enter Ctrl+Enter 或 Shift+Enter 即时通讯、快速交互
ctrlEnter Ctrl+Enter Enter 长文本编辑、邮件场景
shiftEnter Shift+Enter Enter 与 ctrlEnter 互为替代

实战代码:动态切换提交方式

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { TrSender, type SubmitTrigger } from '@opentiny/tiny-robot'

const content = ref('')
const submittedContent = ref('')
const submitType = ref<SubmitTrigger>('enter')

const handleSubmit = (value: string) => {
  submittedContent.value = value
  console.log('提交内容:', value)
}
</script>

<template>
  <div class="options-panel">
    <label>提交方式:</label>
    <label><input type="radio" value="enter" v-model="submitType" /> Enter</label>
    <label><input type="radio" value="ctrlEnter" v-model="submitType" /> Ctrl + Enter</label>
    <label><input type="radio" value="shiftEnter" v-model="submitType" /> Shift + Enter</label>
  </div>

  <tr-sender v-model="content" :submitType="submitType" placeholder="请输入内容..." @submit="handleSubmit" />

  <div v-if="submittedContent" class="result">
    <strong>已提交:</strong> {{ submittedContent }}
  </div>
</template>

单行模式下的特殊行为

在单行模式(mode="single")下使用换行快捷键时,会自动切换为多行模式

ini 复制代码
submitType="enter" + mode="single"
→ 按 Enter:提交
→ 按 Ctrl+Enter 或 Shift+Enter:自动切换到多行模式并换行

二、快捷键完整参考

快捷键 功能 适用条件
Enter 提交内容 / 换行 submitType="enter"
Ctrl+Enter 提交内容 / 换行 submitType="ctrlEnter" / submitType="enter"
Shift+Enter 提交内容 / 换行 submitType="shiftEnter" / submitType="enter"
Tab 选中联想项 Suggestion 扩展开启时
Esc 关闭联想 Suggestion 扩展开启时
↑ / ↓ 导航联想项 Suggestion 扩展开启时
Backspace 删除提及项 Mention 扩展开启时

自定义联想选中按键

通过 Suggestion 扩展的 activeSuggestionKeys 配置自定义选中按键:

typescript 复制代码
TrSender.Suggestion.configure({
  items: suggestions,
  activeSuggestionKeys: ['Enter', 'Tab'], // 默认支持 Enter 和 Tab
})

移动端键盘回车提示

通过 enterkeyhint 属性控制移动端虚拟键盘回车键的显示文本:

typescript 复制代码
type EnterKeyHint = 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
vue 复制代码
<!-- 移动端场景:显示"发送"按钮 -->
<tr-sender enterkeyhint="send" mode="single" />

<!-- 搜索场景:显示"搜索"按钮 -->
<tr-sender enterkeyhint="search" mode="single" />

三、事件体系详解

核心事件

事件名 说明 回调参数
update:modelValue 内容更新 (value: string)
submit 提交内容 (text: string, data?: StructuredData)
clear 清空内容 ()
focus 获得焦点 (event: FocusEvent)
blur 失去焦点 (event: FocusEvent)
input 输入变化 (value: string)
cancel 取消操作(loading 状态下) ()

submit 事件:纯文本 vs 结构化数据

submit 事件是 Sender 最核心的事件,它同时返回两个参数:

typescript 复制代码
function handleSubmit(text: string, data?: StructuredData) {
  // text: 纯文本内容,适用于简单场景
  // data: 结构化数据数组,仅在使用 Template 或 Mention 扩展时返回
}

简单场景 --- 只使用 text:

vue 复制代码
<script setup>
const handleSubmit = (text: string) => {
  // 直接将纯文本发送给 AI
  aiClient.send(text)
}
</script>

<template>
  <tr-sender @submit="handleSubmit" />
</template>

复杂场景 --- 使用 data 提取特殊节点:

typescript 复制代码
// Mention 扩展的结构化数据
function handleSubmit(text: string, data?: StructuredData) {
  // text: "帮我分析 @张三 的周报"
  // data: [
  //   { type: 'text', content: '帮我分析 ' },
  //   { type: 'mention', content: '张三', value: '用户ID' },
  //   { type: 'text', content: ' 的周报' }
  // ]

  // 提取所有提及项
  const mentions = data?.filter(item => item.type === 'mention') || []

  // 自定义 Slack 风格格式
  const customText = data?.map(item =>
    item.type === 'mention' ? `<@${item.value}>` : item.content
  ).join('')
}
typescript 复制代码
// Template 扩展的结构化数据
function handleSubmit(text: string, data?: StructuredData) {
  // data: [
  //   { type: 'text', content: '帮我分析 ' },
  //   { type: 'block', content: '张三' },
  //   { type: 'text', content: ' 的周报' }
  // ]

  // 提取所有模板块
  const blocks = data?.filter(item => item.type === 'block') || []

  // 自定义 Mustache 风格格式
  const customText = data?.map(item =>
    item.type === 'block' ? `{{${item.content}}}` : item.content
  ).join('')
}

cancel 事件实战

在 AI 响应场景中,cancel 事件用于取消正在进行的操作:

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { TrSender } from '@opentiny/tiny-robot'

const content = ref('')
const loading = ref(false)
const message = ref('')

const handleSubmit = (text: string) => {
  loading.value = true
  message.value = '正在处理...'

  // 模拟 AI 响应
  setTimeout(() => {
    loading.value = false
    message.value = `AI 回复: 收到您的消息 "${text}"`
    content.value = ''
  }, 3000)
}

const handleCancel = () => {
  loading.value = false
  message.value = '❌ 已取消响应'
}
</script>

<template>
  <tr-sender
    v-model="content"
    :loading="loading"
    stop-text="停止响应"
    @submit="handleSubmit"
    @cancel="handleCancel"
  />
</template>

UploadButton 事件

事件 说明 回调参数
select 文件选择成功 (files: File[])
error 文件验证失败 (error: Error, file?: File)
vue 复制代码
<template>
  <tr-sender>
    <template #footer-right>
      <UploadButton
        accept="image/*"
        :multiple="true"
        @select="handleFiles"
        @error="handleFileError"
      />
    </template>
  </tr-sender>
</template>

<script setup>
const handleFiles = (files: File[]) => {
  console.log('选择了文件:', files)
}

const handleFileError = (error: Error, file?: File) => {
  console.error('文件验证失败:', error.message)
}
</script>

VoiceButton 事件

事件 说明 回调参数
speech-start 开始录音 ()
speech-interim 中间结果 (transcript: string)
speech-final 最终结果 (transcript: string)
speech-end 结束录音 (transcript?: string)
speech-error 识别错误 (error: Error)
vue 复制代码
<template>
  <tr-sender>
    <template #footer-right>
      <VoiceButton
        @speech-start="onStart"
        @speech-interim="onInterim"
        @speech-final="onFinal"
        @speech-end="onEnd"
        @speech-error="onError"
      />
    </template>
  </tr-sender>
</template>

四、方法调用实战

方法一览

方法 说明 参数 返回值
focus() 使输入框获取焦点 - void
blur() 使输入框失去焦点 - void
clear() 清空输入内容 - void
submit() 手动触发提交 - void
setContent(content) 设置编辑器内容 content: string void
getContent() 获取编辑器内容 - string
cancel() 手动触发取消 - void

实战案例:通过按钮控制输入框

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { TrSender } from '@opentiny/tiny-robot'

const chatInputRef = ref()
const content = ref('')
const result = ref('')

const handleFocus = () => {
  chatInputRef.value?.focus()
  result.value = '已聚焦'
}

const handleBlur = () => {
  chatInputRef.value?.blur()
  result.value = '已失焦'
}

const handleSetContent = () => {
  chatInputRef.value?.setContent('这是通过方法设置的内容')
  result.value = '已设置内容'
}

const handleGetContent = () => {
  const c = chatInputRef.value?.getContent()
  result.value = `当前内容: ${c}`
}

const handleClear = () => {
  chatInputRef.value?.clear()
  result.value = '已清空'
}

const handleSubmitMethod = () => {
  chatInputRef.value?.submit()
}

const onSubmit = (value: string) => {
  result.value = `已提交: ${value}`
}
</script>

<template>
  <div>
    <div class="controls">
      <button @click="handleFocus">聚焦</button>
      <button @click="handleBlur">失焦</button>
      <button @click="handleSetContent">设置内容</button>
      <button @click="handleGetContent">获取内容</button>
      <button @click="handleClear">清空</button>
      <button @click="handleSubmitMethod">提交</button>
    </div>

    <tr-sender
      ref="chatInputRef"
      v-model="content"
      placeholder="通过上方按钮控制输入框..."
      mode="multiple"
      @submit="onSubmit"
    />
  </div>
</template>

UploadButton 方法

方法 说明 参数 返回值
open() 打开文件选择器 - void
vue 复制代码
<script setup>
const uploadRef = ref()
const triggerUpload = () => uploadRef.value?.open()
</script>

<template>
  <UploadButton ref="uploadRef" />
  <button @click="triggerUpload">触发上传</button>
</template>

VoiceButton 方法

方法 说明 参数 返回值
start() 开始录音 - void
stop() 停止录音 - void
vue 复制代码
<script setup>
const voiceRef = ref()
const triggerVoice = () => voiceRef.value?.start()
</script>

<template>
  <VoiceButton ref="voiceRef" />
  <button @click="triggerVoice">触发录音</button>
</template>

五、常见交互场景

场景1:表单验证 + 动态禁用

vue 复制代码
<script setup>
import { ref, computed } from 'vue'
import { TrSender } from '@opentiny/tiny-robot'

const content = ref('')
const isValid = computed(() => content.value.length >= 5)

const defaultActions = computed(() => ({
  submit: {
    disabled: !isValid.value,
    tooltip: isValid.value ? '发送消息' : '请输入至少 5 个字符',
  },
  clear: { tooltip: '清空内容' },
}))
</script>

<template>
  <tr-sender v-model="content" :default-actions="defaultActions" clearable />
</template>

场景2:AI 流式响应 + 取消操作

vue 复制代码
<script setup>
import { ref } from 'vue'
import { TrSender } from '@opentiny/tiny-robot'

const content = ref('')
const loading = ref(false)

const handleSubmit = async (text: string) => {
  loading.value = true
  try {
    await sendToAI(text)
  } finally {
    loading.value = false
  }
}

const handleCancel = () => {
  abortAIRequest()
  loading.value = false
}
</script>

<template>
  <tr-sender
    v-model="content"
    :loading="loading"
    @submit="handleSubmit"
    @cancel="handleCancel"
  />
</template>

场景3:外部按钮触发提交

vue 复制代码
<script setup>
const senderRef = ref()

const handleExternalSubmit = () => {
  senderRef.value?.submit()
}
</script>

<template>
  <button @click="handleExternalSubmit">外部提交</button>
  <tr-sender ref="senderRef" v-model="content" @submit="onSubmit" />
</template>

六、总结

Sender 的交互体系设计遵循"灵活但可控"的原则:

  1. 提交方式:三种快捷键配置覆盖主流输入场景
  2. 事件体系:submit 双参数设计兼顾简单和复杂场景
  3. 方法调用:focus/blur/setContent/getContent 提供完整的程序化控制
  4. 取消机制:loading + cancel 组合完美适配 AI 流式响应场景
  5. 结构化数据:Template 和 Mention 扩展自动产出结构化数据

掌握这些交互细节,你就能在实际项目中精确控制输入行为,打造流畅的用户体验。


🔗 TinyRobot 官网tiny-robot.opentiny.design

🔗 GitHub 仓库github.com/opentiny/ti...

相关推荐
Agatha方艺璇2 小时前
VUE复习笔记
前端·vue.js
chushiyunen4 小时前
vue el-pagination实现分页
javascript·vue.js·elementui
wanger614 小时前
Vue学习笔记
前端·javascript·vue.js
阿猫的故乡5 小时前
Vue动态组件+异步组件实战:Tab切换、按需加载、KeepAlive缓存,一次搞定
前端·vue.js·缓存
阿猫的故乡5 小时前
Vue3自定义插件:封装一个全局消息提示插件,所有组件都能直接用
前端·javascript·vue.js
用户83134859306985 小时前
Cesium实现实时联动鹰眼缩略图
vue.js·cesium
qq_422152576 小时前
视频转 GIF 工具怎么选?2026 年动图制作方案与画质参数对比
javascript·vue.js·音视频
2501_912784086 小时前
跨境电商独立站技术选型:为什么React+Vue+Laravel成为主流?
vue.js·react.js·laravel·taocarts