TinyRobot Sender打造强大的AI聊天输入体验

TinyRobot Sender:打造强大的 AI 聊天输入体验

js 复制代码

为什么输入组件如此重要

在 AI 聊天应用中,输入框是用户与 AI 之间最重要的交互界面。一个优秀的输入组件,不仅是文本的"收容所",更是连接用户意图与 AI 能力的桥梁。无论模型能力多么强大,如果用户无法精准、高效地表达自己的想法,整个 AI 体验都会大打折扣。

传统的 <input><textarea> 已经无法满足现代 AI 应用的复杂需求。用户需要:

  • 结构化输入:通过模板快速搭建复杂 Prompt
  • 智能联想:输入时自动提示相关话题或产品
  • @提及:快速引用预设角色或上下文
  • 语音输入:解放双手,自然表达
  • 文件上传:将图片、文档等作为对话上下文

TinyRobot Sender 正是为此而生 ------ 它是一个高度可组合、可扩展的 AI 聊天输入组件,基于 Tiptap 富文本编辑器构建,提供企业级的输入体验。


快速上手

1. 基础用法:模式切换

Sender 支持单行 (single) 和多行 (multiple) 两种输入模式。单行模式下内容超出宽度时会自动切换为多行,无需用户手动调整。

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

const content = ref('')
const mode = ref<'single' | 'multiple'>('single')

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

<template>
  <div>
    <div style="display: flex; gap: 12px; margin-bottom: 16px;">
      <button
        :style="{ background: mode === 'single' ? '#1476ff' : '#f0f0f0', color: mode === 'single' ? '#fff' : '#333' }"
        @click="mode = 'single'"
      >
        单行模式
      </button>
      <button
        :style="{ background: mode === 'multiple' ? '#1476ff' : '#f0f0f0', color: mode === 'multiple' ? '#fff' : '#333' }"
        @click="mode = 'multiple'"
      >
        多行模式
      </button>
    </div>

    <TrSender
      v-model="content"
      :mode="mode"
      placeholder="请输入消息..."
      :max-length="200"
      show-word-limit
      clearable
      @submit="handleSubmit"
    />
  </div>
</template>

核心 Props 说明

属性 说明 类型 默认值
modelValue / v-model 输入框内容绑定 string ''
mode 输入模式 `'single' 'multiple'`
disabled 是否禁用 boolean false
loading 加载状态(显示停止按钮) boolean false
maxLength 最大输入长度 number Infinity
showWordLimit 显示字数统计 boolean false
size 组件尺寸 `'normal' 'small'`

扩展架构:可插拔的功能增强

Sender 的核心设计理念是可插拔的扩展架构 。通过 extensions prop,你可以按需组合 Template、Mention、Suggestion 等功能扩展,每个扩展互相独立、自由组合。

typescript 复制代码
// 扩展就是普通的数组元素
const extensions = [
  TrSender.template(templateData),
  TrSender.mention(mentionItems),
  TrSender.suggestion(suggestionItems, { filterFn: customFilter })
]

// 通过 props 传入
<TrSender :extensions="extensions" />

提供两种集成方式:

  • 便捷函数(推荐)TrSender.template()TrSender.mention()TrSender.suggestion(),用于简单场景
  • 标准配置TrSender.Mention.configure({...}),用于复杂配置(如自定义 onSelect 回调、filterFn 等)

2. Template 扩展:结构化 Prompt 模板

Template 扩展让用户通过预设模板快速填写结构化 Prompt。支持 text(固定文本)、block(可编辑字段)、select(下拉选择器)三种节点类型。

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

const content = ref('')
const templateData = ref<TemplateItem[]>([])

// 便捷函数:传入响应式 ref,数据变化时自动同步编辑器
const extensions = [TrSender.template(templateData)]

const setTemplate = () => {
  templateData.value = [
    { type: 'text', content: '请帮我写一份关于' },
    { type: 'block', content: '人工智能' },
    { type: 'text', content: '的' },
    { type: 'block', content: '技术报告' },
    { type: 'text', content: ',字数要求' },
    { type: 'block', content: '3000字' },
    { type: 'text', content: ',风格要求' },
    {
      type: 'select',
      placeholder: '选择风格',
      content: '',
      options: [
        { label: '正式学术', value: 'formal_academic' },
        { label: '通俗易懂', value: 'easy_reading' },
        { label: '技术深度', value: 'technical_deep' },
      ]
    },
    { type: 'text', content: '。' },
  ]
}

const handleSubmit = (text: string, data?: StructuredData) => {
  console.log('纯文本:', text)
  // data 结构:
  // [
  //   { type: 'text', content: '请帮我写一份关于' },
  //   { type: 'block', content: '人工智能' },
  //   { type: 'text', content: '的技术报告...' }
  // ]
}
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <Button size="small" @click="setTemplate">插入 Prompt 模板</Button>
    <TrSender
      v-model="content"
      :extensions="extensions"
      mode="multiple"
      placeholder="点击按钮插入模板..."
      :max-length="500"
      show-word-limit
      clearable
      @submit="handleSubmit"
    />
  </div>
</template>

Template 通过响应式 ref 机制,当 templateData.value 发生变化时,编辑器内容会自动更新,光标也会自动聚焦到第一个可编辑字段,无需手动调用任何方法。

3. Mention 扩展:@提及功能

Mention 扩展实现了类似社交平台的 @提及功能。输入触发字符(默认 @)会弹出选择列表,支持键盘导航(↑↓)和搜索过滤。

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

const content = ref('')

const assistants: MentionItem[] = [
  { label: '代码助手', value: 'you_are_a_coding_expert' },
  { label: '文案大师', value: 'you_are_a_copywriter' },
  { label: '数据分析师', value: 'you_are_a_data_analyst' },
  { label: '翻译专家', value: 'you_are_a_translator' },
]

// 便捷函数:默认触发字符为 @
const extensions = [TrSender.mention(assistants, '@')]

// 也可以自定义触发字符,如用 # 替代 @
// const extensions = [TrSender.mention(hashtags, '#')]

const handleSubmit = (text: string, data?: StructuredData) => {
  // text: "帮我分析 @代码助手 返回的代码"
  // data: [
  //   { type: 'text', content: '帮我分析 ' },
  //   { type: 'mention', content: '代码助手', value: 'you_are_a_coding_expert' },
  //   { type: 'text', content: ' 返回的代码' }
  // ]

  // 提取所有提及的助手
  const mentions = data?.filter(item => item.type === 'mention') || []
  const assistantIds = mentions.map(m => m.value)
  console.log('提及的助手:', assistantIds)
}
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <p style="color: #666; font-size: 14px; margin: 0;">
      💡 输入 <code>@</code> 触发提及选择,支持键盘导航和搜索过滤
    </p>
    <TrSender
      v-model="content"
      :extensions="extensions"
      placeholder="输入 @ 选择助手..."
      mode="multiple"
      :max-length="500"
      show-word-limit
      clearable
      @submit="handleSubmit"
    />
  </div>
</template>

Mention 扩展支持三个配置项:

配置项 类型 默认值 说明
items `MentionItem[] Ref<MentionItem[]>` []
char string '@' 触发字符
allowSpaces boolean false 是否允许触发字符后输入空格

4. Suggestion 扩展:智能联想与自定义过滤

Suggestion 扩展提供智能输入联想功能。用户输入时自动弹出建议列表,支持键盘导航和 Tab 自动补全。

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

const input = ref('')

const suggestions: SenderSuggestionItem[] = [
  { content: 'ECS-云服务器卡顿排查' },
  { content: 'ECS-备份弹性云服务器' },
  { content: 'ECS-实例无法启动处理' },
  { content: 'CDN-权限管理配置指南' },
  { content: 'CDN-缓存刷新问题排查' },
  { content: 'OSS-存储桶访问控制' },
]

// 标准配置:使用自定义 filterFn 实现按分类前缀匹配
const extensions = computed(() => [
  TrSender.Suggestion.configure({
    items: suggestions,
    // 自定义过滤逻辑
    filterFn: (items: SenderSuggestionItem[], query: string) => {
      if (!query) return []
      const lowerQuery = query.toLowerCase()
      // 按 - 前面的分类标签匹配
      return items.filter((item) => {
        const category = item.content.split('-')[0].toLowerCase()
        return category.startsWith(lowerQuery)
      })
    },
    showAutoComplete: true,
    popupWidth: 500,
    onSelect: (item) => {
      console.log('选中建议:', item.content)
    },
  }),
])
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <p style="color: #666; font-size: 14px; margin: 0;">
      💡 输入 ECS、CDN 或 OSS 查看分类联想,Tab 键快速补全
    </p>
    <TrSender
      v-model="input"
      :extensions="extensions"
      placeholder="输入 ECS 或 CDN 查看建议..."
      @submit="(text) => console.log('提交:', text)"
    />
  </div>
</template>

Suggestion 配置项详解:

配置项 说明 类型 默认值
items 建议项列表 SenderSuggestionItem[] []
filterFn 自定义过滤函数,不传则不过滤 (items, query) => items undefined
showAutoComplete 显示灰色补全提示 boolean true
activeSuggestionKeys 激活建议的按键 string[] ['Enter']
popupWidth 弹窗宽度(支持数字/百分比) `number string`
onSelect 选中回调,返回 false 阻止默认回填 `(item) => void false`

5. VoiceButton:语音输入

VoiceButton 是一个独立的语音输入按钮组件,通过 footerfooter-right 插槽集成到 Sender 中。它同时支持浏览器内置的 Web Speech API 和自定义第三方语音识别服务。

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

const content = ref('')
const voiceMode = ref<'mixed' | 'continuous'>('mixed')

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

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <div style="display: flex; gap: 12px; align-items: center;">
      <span>语音模式:</span>
      <label><input type="radio" v-model="voiceMode" value="mixed" /> 混合输入</label>
      <label><input type="radio" v-model="voiceMode" value="continuous" /> 连续识别</label>
    </div>
    <p style="color: #666; font-size: 13px; margin: 0;">
      {{ voiceMode === 'mixed'
        ? '识别结果追加到输入框,可继续编辑修改'
        : '持续识别并自动替换内容,适合长段语音输入' }}
    </p>

    <!-- VoiceButton 通过 key 在模式切换时重新实例化 -->
    <TrSender
      :key="voiceMode"
      v-model="content"
      mode="multiple"
      @submit="handleSubmit"
    >
      <template #footer-right>
        <VoiceButton
          :speech-config="voiceMode === 'mixed'
            ? { autoReplace: false, interimResults: true }
            : { autoReplace: true, continuous: true }"
          tooltip="语音输入"
          @speech-final="(transcript) => { content += transcript }"
        />
      </template>
    </TrSender>
  </div>
</template>

VoiceButton 核心配置:

属性 说明 类型 默认值
speechConfig.autoReplace 是否自动替换内容(false 则追加) boolean true
speechConfig.continuous 是否持续识别 boolean false
speechConfig.interimResults 是否返回中间结果 boolean false
speechConfig.lang 识别语言 string 浏览器默认语言
autoInsert 是否自动插入识别结果到编辑器 boolean true

如需集成阿里云、百度、Azure 等第三方语音服务,通过 speechConfig.customHandler 传入自定义处理器即可。

6. UploadButton:文件上传

UploadButton 允许用户上传文件(图片、文档等)作为对话的上下文输入,支持文件类型过滤、大小限制和数量限制。

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

const content = ref('')
const uploadedFiles = ref<File[]>([])

const handleSubmit = (text: string) => {
  console.log('提交文本:', text)
  console.log('附带文件:', uploadedFiles.value)
  content.value = ''
  uploadedFiles.value = []
}

const handleFilesSelected = (files: File[]) => {
  uploadedFiles.value = files
  // 可以在这里做上传逻辑或直接作为附件信息
}
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <TrSender
      v-model="content"
      mode="multiple"
      placeholder="输入问题,或上传相关文件..."
      clearable
      @submit="handleSubmit"
    >
      <template #footer-right>
        <UploadButton
          accept="image/*,.pdf,.doc,.docx"
          :multiple="true"
          :max-size="10"
          :max-count="5"
          tooltip="上传文件(最多5个,每个≤10MB)"
          tooltip-placement="top"
          @select="handleFilesSelected"
        />
      </template>
    </TrSender>

    <div v-if="uploadedFiles.length" style="font-size: 13px; color: #666;">
      已选择:{{ uploadedFiles.map(f => f.name).join(', ') }}
    </div>
  </div>
</template>

UploadButton 核心配置:

属性 说明 类型 默认值
accept 接受的 MIME 类型 string '*'
multiple 是否支持多选 boolean false
maxSize 文件大小限制(MB) number -
maxCount 最大文件数量 number -
tooltip 悬停提示文本 string -

7. 自定义插槽:灵活布局

Sender 提供多个插槽位置以支持灵活的布局扩展:

插槽 位置 作用域 典型用途
header 输入框上方 - 标题、提示信息
prefix 输入框内部左侧 - AI 图标、模型选择
content 编辑器内容区域 { editor } 完全自定义编辑器内容
actions-inline 单行模式操作区 - 单行模式下的操作按钮
footer 底部左侧 { editor, hasContent, disabled, loading } 增强功能按钮
footer-right 底部右侧 - 上传、语音按钮
vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { TrSender, UploadButton, VoiceButton } from '@opentiny/tiny-robot'

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

const handleSubmit = (text: string) => {
  console.log('提交:', text, deepThinking.value ? '(深度思考模式)' : '')
  content.value = ''
}
</script>

<template>
  <TrSender
    v-model="content"
    placeholder="输入你的问题..."
    mode="multiple"
    clearable
    @submit="handleSubmit"
  >
    <!-- 顶部标题 -->
    <template #header>
      <div style="text-align: center; font-weight: 700; padding: 8px 0;">
        🤖 AI 智能助手
      </div>
    </template>

    <!-- 输入框前缀图标 -->
    <template #prefix>
      <span style="font-size: 22px; margin-right: 8px;">🧠</span>
    </template>

    <!-- 底部左侧功能按钮 -->
    <template #footer="{ editor, hasContent }">
      <button
        :style="{
          padding: '4px 12px', borderRadius: '16px', border: '1px solid #e0e0e0',
          background: deepThinking ? '#1476ff' : 'transparent',
          color: deepThinking ? '#fff' : '#666', cursor: 'pointer', fontSize: '13px'
        }"
        @click="deepThinking = !deepThinking"
      >
        {{ deepThinking ? '✓ 深度思考' : '🌐 深度思考' }}
      </button>
      <span v-if="hasContent" style="font-size: 12px; color: #999; margin-left: 8px;">
        已输入 {{ content.length }} 字
      </span>
    </template>

    <!-- 底部右侧功能按钮 -->
    <template #footer-right>
      <UploadButton
        accept="image/*"
        tooltip="上传图片"
        tooltip-placement="top"
        @select="(files) => console.log('选择文件:', files)"
      />
      <VoiceButton
        :speech-config="{ autoReplace: false, interimResults: true }"
        tooltip="语音输入"
        tooltip-placement="top"
      />
    </template>
  </TrSender>
</template>

8. submitType:灵活控制提交流程

不同的应用场景需要不同的提交方式。Sender 通过 submitType 属性提供三种方式:

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

const content = ref('')
const submitType = ref<'enter' | 'ctrlEnter' | 'shiftEnter'>('ctrlEnter')

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

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <div style="display: flex; gap: 16px; align-items: center;">
      <span>提交方式:</span>
      <label><input type="radio" v-model="submitType" value="enter" /> Enter 提交</label>
      <label><input type="radio" v-model="submitType" value="ctrlEnter" /> Ctrl+Enter 提交</label>
      <label><input type="radio" v-model="submitType" value="shiftEnter" /> Shift+Enter 提交</label>
    </div>

    <TrSender
      v-model="content"
      :submit-type="submitType"
      placeholder="输入内容后使用对应快捷键提交..."
      @submit="handleSubmit"
    />
  </div>
</template>

快捷键行为对照表

submitType 提交快捷键 换行快捷键
enter Enter Ctrl+Enter / Shift+Enter
ctrlEnter Ctrl+Enter Enter
shiftEnter Shift+Enter Enter

提示:单行模式下使用换行快捷键时,会自动切换为多行模式。


结构化数据:读懂用户的意图

Sender 的 submit 事件不仅返回纯文本,当使用了 Template 或 Mention 扩展时,还会返回结构化的 data 参数,让你能够精确提取用户的意图。

typescript 复制代码
// submit 事件签名
@submit="(text: string, data?: StructuredData) => { ... }"

Mention 结构化数据

typescript 复制代码
const handleSubmit = (text: string, data?: StructuredData) => {
  // 用户输入: "帮我分析 @代码助手 的代码质量"
  // text: "帮我分析 @代码助手 的代码质量"
  // data: [
  //   { type: 'text', content: '帮我分析 ' },
  //   { type: 'mention', content: '代码助手', value: 'you_are_a_coding_expert' },
  //   { type: 'text', content: ' 的代码质量' }
  // ]

  // 提取提及的助手 ID 列表
  const mentions = data?.filter(item => item.type === 'mention') || []
  const assistantIds = mentions.map(item => item.value)

  // 自定义 Slack 风格格式
  const customText = data?.map(item =>
    item.type === 'mention' ? `<@${item.value}>` : item.content
  ).join('')
}

Template 结构化数据

typescript 复制代码
const handleSubmit = (text: string, data?: StructuredData) => {
  // 用户通过模板输入后提交
  // text: "请帮我写一份关于人工智能的技术报告,字数要求3000字"
  // data: [
  //   { type: 'text', content: '请帮我写一份关于' },
  //   { type: 'block', content: '人工智能' },
  //   { type: 'text', content: '的技术报告...' }
  // ]

  // 提取所有可编辑块的值
  const blocks = data?.filter(item => item.type === 'block') || []
  const blockValues = blocks.map(b => b.content)
}

这种结构化设计让你可以轻松实现:

  • 将 @提及转换为自定义协议格式
  • 提取模板中的关键填充字段
  • 构建更丰富的 AI 请求上下文

v0.4 升级与 SenderCompat 迁移

Sender 在 v0.4 版本进行了重大重构,底层从传统 textarea 升级为基于 Tiptap 的富文本编辑器,带来了更强大的扩展能力和更灵活的插槽系统。

如果你的项目正在使用 v0.3.x

TinyRobot 提供了 SenderCompat 兼容组件作为过渡方案,只需修改一行导入:

typescript 复制代码
// 旧代码 (v0.3.x)
import { TrSender } from '@opentiny/tiny-robot'

// 使用 SenderCompat 快速迁移
import { TrSenderCompat as TrSender } from '@opentiny/tiny-robot'

推荐的迁移路径

复制代码
v0.3.x Sender → SenderCompat(快速迁移,改导入即可)→ v0.4 Sender(按需完全升级)

v0.4 主要变更一览

变更类型 v0.3.x v0.4 说明
语音输入 allow-speech prop VoiceButton 独立组件 组件化设计,通过插槽集成
文件上传 allow-files + button-group UploadButton 独立组件 扁平化配置,更灵活
智能联想 suggestions prop Suggestion 扩展 通过 extensions 接入
@提及 - Mention 扩展 v0.4 新增功能
模板填充 v-model:templateData Template 扩展 响应式 ref 驱动
主题 theme prop ThemeProvider 包裹 全局继承,所有子组件自动生效
模板类型名 type: 'template' type: 'block' 类型名称变更,需手动适配

完全升级到 v0.4

如果你准备完全升级到 v0.4,下面是一个典型的迁移对照:

vue 复制代码
<!-- v0.3.x 写法 -->
<template>
  <TrSender
    v-model="content"
    :allow-speech="true"
    :speech="{ lang: 'zh-CN' }"
    :allow-files="true"
    :button-group="{ file: { accept: 'image/*' } }"
    :suggestions="filteredSuggestions"
    @speech-end="onSpeechEnd"
    @files-selected="onFilesSelected"
  />
</template>

<!-- v0.4 写法 -->
<template>
  <TrSender
    v-model="content"
    :extensions="extensions"
  >
    <template #footer-right>
      <UploadButton accept="image/*" @select="onFilesSelected" />
      <VoiceButton :speech-config="{ lang: 'zh-CN' }" @speech-final="onSpeechEnd" />
    </template>
  </TrSender>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { TrSender, UploadButton, VoiceButton } from '@opentiny/tiny-robot'

const content = ref('')

const extensions = [
  TrSender.Suggestion.configure({ items: [], filterFn: myFilter }),
]

const onFilesSelected = (files: File[]) => { /* ... */ }
const onSpeechEnd = (transcript: string) => { /* ... */ }
</script>

事件与方法速查

事件

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

方法(通过 ref 调用)

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

总结

TinyRobot Sender 通过以下设计原则,为 AI 聊天应用提供了企业级的输入体验:

  1. 可组合:各功能通过独立的扩展或组件实现,按需引入
  2. 响应式:扩展配置支持 Vue ref,数据变化自动同步 UI
  3. 结构化:submit 事件返回文本 + 结构化数据,精准传达用户意图
  4. 可插拔:Template、Mention、Suggestion 三大扩展独立运作,互不干扰
  5. 渐进迁移:通过 SenderCompat 兼容组件,平滑从 v0.3.x 升级

无论是构建一个简单的 AI 问答输入框,还是打造复杂的企业级 Copilot 交互界面,Sender 都能胜任。


OpenTiny NEXT 是一套企业智能前端开发解决方案,以生成式 UI 和 WebMCP 两大核心技术为基础,对现有传统的 TinyVue 组件库、TinyEngine 低代码引擎等产品进行智能化升级,构建出面向 Agent 应用的前端 NEXT-SDKs、AI Extension、TinyRobot智能助手、GenUI等新产品,加速企业应用的智能化改造,实现AI理解用户意图自主完成任务。

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~

OpenTiny 官网:opentiny.design/ TinyRobot 代码仓库:github.com/opentiny/ti... (欢迎star ⭐) TinyRobot skill源码:github.com/opentiny/ag... (欢迎 Star ⭐)

相关推荐
爱吃生蚝的于勒1 小时前
QT开发第三章——常用控件
linux·服务器·开发语言·前端·javascript·c++·qt
xuankuxiaoyao1 小时前
Axios-图书列表案例
开发语言·前端·javascript
影寂ldy1 小时前
C# 多播委托
前端·javascript·c#
dy17171 小时前
Vue3 多文件上传
前端·javascript·vue.js
文阿花2 小时前
Echarts实现3D饼状图
前端·javascript·echarts·饼状图
智码看视界2 小时前
老梁聊全栈系列:Vue2与Vue3核心区别及学习路线指南
前端·vue.js·学习
qq_363066932 小时前
react 使用web component导出静态html报告
前端·react.js·html·页面导出
weixin_457763082 小时前
展示youtube的视频
前端·javascript·html
雨翼轻尘2 小时前
03_HTML进阶标签与CSS入门
前端·css·html·入门·进阶标签