Sender 输入框
用于聊天的输入框组件。
何时使用
需要构建一个对话场景下的输入框
代码演示
基本用法
基础用法,受控进行状态管理。自定义触发器。
ts
<script setup lang="tsx">
import { App, Flex } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { onWatcherCleanup, ref, watch } from 'vue';
defineOptions({ name: 'AXSenderBasic' });
const value = ref('Hello? this is X!');
const loading = ref<boolean>(false);
const Demo = () => {
const { message } = App.useApp();
// Mock send message
watch(loading, () => {
if (loading.value) {
const timer = setTimeout(() => {
loading.value = false;
message.success('Send message successfully!');
}, 3000);
onWatcherCleanup(() => {
clearTimeout(timer);
})
}
});
return (
<Flex vertical gap="middle">
<Sender
loading={loading.value}
value={value.value}
onChange={(v) => {
value.value = v
}}
onSubmit={() => {
value.value = '';
loading.value = true
message.info('Send message!');
}}
onCancel={() => {
loading.value = false
message.error('Cancel sending!');
}}
autoSize={{ minRows: 2, maxRows: 6 }}
/>
<Sender value="Force as loading" loading readOnly />
<Sender disabled value="Set to disabled" />
</Flex>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
提交用法
通过 submitType
控制换行与提交模式。
ts
<script setup lang="tsx">
import { App } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
defineOptions({ name: 'AXSenderSubmitType' });
const Demo = () => {
const { message } = App.useApp();
return (
<Sender
submitType="shiftEnter"
placeholder="Press Shift + Enter to send message"
onSubmit={() => {
message.success('Send message successfully!');
}}
/>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
语音输入
语音输入,需要用户同意麦克风权限。
ts
<script setup lang="tsx">
import { App } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
defineOptions({ name: 'AXSenderSpeech' });
const Demo = () => {
const { message } = App.useApp();
return (
<Sender
allowSpeech
onSubmit={() => {
message.success('Send message successfully!');
}}
/>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
自定义语音输入
自定义语音逻辑,从而实现调用三方库的语音识别功能。
ts
<script setup lang="tsx">
import { App } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'AXSenderSpeechCustom' });
const recording = ref(false);
const Demo = () => {
const { message } = App.useApp();
return (
<Sender
onSubmit={() => {
message.success('Send message successfully!');
}}
allowSpeech={{
// When setting `recording`, the built-in speech recognition feature will be disabled
recording: recording.value,
onRecordingChange: (nextRecording) => {
message.info(`Mock Customize Recording: ${nextRecording}`);
recording.value = nextRecording;
},
}}
/>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
自定义按钮
通过 actions
属性,可以自定义发送按钮的行为。
`Shift + Enter` to submit
ts
<script setup lang="tsx">
import { App, Space, Spin, Typography } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { onWatcherCleanup, ref, watch } from 'vue';
defineOptions({ name: 'AXSenderActions' });
const value = ref('');
const loading = ref<boolean>(false);
const Demo = () => {
const { message } = App.useApp();
// Mock send message
watch(loading, () => {
if (loading.value) {
const timer = setTimeout(() => {
loading.value = false;
value.value = '';
message.success('Send message successfully!');
}, 2000);
onWatcherCleanup(() => {
clearTimeout(timer);
});
}
});
return (
<Sender
submitType="shiftEnter"
loading={loading.value}
value={value.value}
onChange={(v) => {
console.log('Sender onChange', v)
value.value = v
}}
onSubmit={() => {
loading.value = true;
}}
onCancel={() => {
loading.value = false;
}}
actions={(_, info) => {
const { SendButton, LoadingButton, ClearButton, SpeechButton } = info.components;
return (
<Space size="small">
<Typography.Text type="secondary">
<small>`Shift + Enter` to submit</small>
</Typography.Text>
<ClearButton />
<SpeechButton />
{loading.value ? (
<LoadingButton type="default" icon={<Spin size="small" />} disabled style={{ display: 'block' }} />
) : (
<SendButton type="primary" disabled={false} />
)}
</Space>
);
}}
/>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
展开面板
使用 header
自定义文件上传示例。
ts
<script setup lang="tsx">
import { CloudUploadOutlined, LinkOutlined } from '@ant-design/icons-vue';
import { App, Button, Flex, theme, Typography } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { computed, ref } from 'vue';
defineOptions({ name: 'AXSenderHeader' });
const { token } = theme.useToken();
const open = ref(false);
const Demo = () => {
const { message } = App.useApp();
const headerNode = computed(() => (
<Sender.Header title="Upload Sample" open={open.value} onOpenChange={v => open.value = v}>
<Flex vertical align="center" gap="small" style={{ marginBlock: token.value.paddingLG }}>
<CloudUploadOutlined style={{ fontSize: '4em' }} />
<Typography.Title
level={5}
// @ts-expect-error
style={{ margin: 0 }}
>
Drag file here(just demo)
</Typography.Title>
<Typography.Text type="secondary" >
Support pdf, doc, xlsx, ppt, txt, image file types
</Typography.Text >
< Button
onClick={() => {
message.info('Mock select file');
}}
>
Select File
</Button>
</Flex>
</Sender.Header>
));
return (
<Flex style={{ height: '350px' }} align="end" >
<Sender
header={headerNode.value}
prefix={
<Button
type="text"
icon={< LinkOutlined />}
onClick={() => {
open.value = !open.value
}}
/>
}
placeholder="← Click to open"
onSubmit={() => {
message.success('Send message successfully!');
}}
/>
</Flex>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
引用
使用 header
做引用效果。
"Tell more about Ant Design X"
ts
<script setup lang="tsx">
import { EnterOutlined } from '@ant-design/icons-vue';
import { App, Flex, Space, Switch, Typography } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { computed, ref } from 'vue';
defineOptions({ name: 'AXSenderHeaderFixed' });
const hasRef = ref(true);
const Demo = () => {
const { message } = App.useApp();
const headerNode = computed(() => (
<Sender.Header
open={hasRef.value}
title={
<Space>
<EnterOutlined />
<Typography.Text type="secondary">"Tell more about Ant Design X"</Typography.Text>
</Space>
}
onOpenChange={v => hasRef.value = v}
/>
));
return (
<Flex vertical gap="middle" align="flex-start">
<Switch
checked={hasRef.value}
onChange={() => hasRef.value = !hasRef.value}
checkedChildren="With Reference"
unCheckedChildren="With Reference"
/>
<Sender
header={headerNode.value}
onSubmit={() => {
message.success('Send message successfully!');
}}
/>
</Flex>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
自定义底部内容
使用 footer
自定义底部内容。
Deep Thinking
ts
<script setup lang="tsx">
import { ApiOutlined, LinkOutlined, SearchOutlined } from '@ant-design/icons-vue';
import { Button, Divider, Flex, Switch, theme } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { ref, watch } from 'vue';
defineOptions({ name: 'AXSenderFooter' });
const { token } = theme.useToken();
const loading = ref<boolean>(false);
const value = ref<string>('');
const iconStyle = {
fontSize: 18,
color: token.value.colorText,
}
watch(loading, () => {
if (loading.value) {
const timer = setTimeout(() => {
loading.value = false;
value.value = '';
console.log('Send message successfully!');
}, 2000);
return () => {
clearTimeout(timer);
};
}
});
defineRender(() => {
return (
<Sender
value={value.value}
onChange={(v) => {
value.value = v;
}}
// autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="Press Enter to send message"
footer={({ components }) => {
const { SendButton, LoadingButton, SpeechButton } = components;
return (
<Flex justify="space-between" align="center">
<Flex gap="small" align="center">
<Button style={iconStyle} type="text" icon={<LinkOutlined />} />
<Divider type="vertical" />
Deep Thinking
<Switch size="small" />
<Divider type="vertical" />
<Button icon={<SearchOutlined />}>Global Search</Button>
</Flex>
<Flex align="center">
<Button type="text" style={iconStyle} icon={<ApiOutlined />} />
<Divider type="vertical" />
<SpeechButton style={iconStyle} />
<Divider type="vertical" />
{loading.value ? (
<LoadingButton type="default" />
) : (
<SendButton type="primary" disabled={false} />
)}
</Flex>
</Flex>
);
}}
onSubmit={() => {
loading.value = true;
}}
onCancel={() => {
loading.value = false;
}}
actions={false}
/>
);
})
</script>
隐藏源代码
调整样式
通过 actions
属性,调整默认样式。
ts
<script setup lang="tsx">
import { SendOutlined } from '@ant-design/icons-vue';
import { type ButtonProps, App, Flex, Tooltip } from 'ant-design-vue';
import { Sender, theme } from 'ant-design-x-vue';
import { type CSSProperties, onWatcherCleanup, ref, watch } from 'vue';
defineOptions({ name: 'AXSenderSendStyle' });
const { token } = theme.useToken();
const value = ref('Ask something?');
const loading = ref(false);
const Demo = () => {
const { message } = App.useApp();
watch(loading, () => {
if (loading.value) {
const timer = setTimeout(() => {
loading.value = false;
}, 3000);
onWatcherCleanup(() => {
clearTimeout(timer);
})
}
});
const renderSend = (
props: ButtonProps & { ignoreLoading?: boolean; placeholder?: string; style?: CSSProperties } = {},
) => {
const { ignoreLoading, placeholder, ...btnProps } = props;
return (
<Sender
value={value.value}
onChange={v => value.value = v}
loading={loading.value}
onSubmit={(msg) => {
message.success(`Send: ${msg}`);
value.value = ''
loading.value = true
}}
placeholder={placeholder}
onCancel={() => {
loading.value = false
}}
actions={(_, info) => {
const { SendButton, LoadingButton } = info.components;
if (!ignoreLoading && loading.value) {
return (
<Tooltip title="Click to cancel">
<LoadingButton />
</Tooltip>
);
}
let node = <SendButton {...btnProps} />;
if (!ignoreLoading) {
node = (
<Tooltip title={value.value ? 'Send \u21B5' : 'Please type something'}>{node}</Tooltip>
);
}
return node;
}}
/>
);
};
return (
<Flex vertical gap="middle">
{renderSend({
shape: 'default',
placeholder: 'Change button border radius',
style: { borderRadius: '12px' },
})}
{renderSend({
type: 'text',
// variant: 'text',
placeholder: 'Change button icon',
// color: 'primary',
style: { color: token.value.colorPrimary },
icon: <SendOutlined />,
shape: 'default',
})}
{renderSend({ ignoreLoading: true, placeholder: 'Loading not change button' })}
</Flex>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
黏贴文件
使用 onPasteFile
获取黏贴的文件,配合 Attachments 进行文件上传。
Attachments
Upload files
Click or drag files to this area to uploadts
<script setup lang="tsx">
import { CloudUploadOutlined, LinkOutlined } from '@ant-design/icons-vue';
import { App, Button, Flex } from 'ant-design-vue';
import { Attachments, Sender } from 'ant-design-x-vue';
import { computed, ref } from 'vue';
defineOptions({ name: 'AXSenderPasteImage' });
const Demo = () => {
const open = ref(false);
const items = ref([]);
const text = ref('');
const attachmentsRef = ref(null);
const senderRef = ref<InstanceType<typeof Sender>>(null);
const senderHeader = computed(() => (
<Sender.Header
title="Attachments"
styles={{
content: {
padding: 0,
},
}}
open={open.value}
onOpenChange={v => open.value = v}
forceRender
>
<Attachments
ref={attachmentsRef}
// Mock not real upload file
beforeUpload={() => false}
items={items.value}
onChange={({ fileList }) => items.value = fileList}
placeholder={(type) =>
type === 'drop'
? {
title: 'Drop file here',
}
: {
icon: <CloudUploadOutlined />,
title: 'Upload files',
description: 'Click or drag files to this area to upload',
}
}
getDropContainer={() => senderRef.value?.nativeElement}
/>
</Sender.Header>
));
return (
<Flex style={{ height: '220px' }} align="end">
<Sender
ref={senderRef}
header={senderHeader.value}
prefix={
<Button
type="text"
icon={<LinkOutlined />}
onClick={() => {
open.value = !open.value;
}}
/>
}
value={text.value}
onChange={v => text.value = v}
onPasteFile={(_, files) => {
for (const file of files) {
attachmentsRef.value.current?.upload(file);
}
open.value = true;
}}
onSubmit={() => {
items.value = []
text.value = ''
}}
/>
</Flex>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
聚焦
使用 ref
选项控制聚焦。
ts
<script setup lang="tsx">
import { App, Button, Flex } from 'ant-design-vue';
import { Sender } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'AXSenderFocus' });
const senderRef = ref<InstanceType<typeof Sender> | null>(null);
const Demo = () => {
const senderProps = {
defaultValue: 'Hello, welcome to use Ant Design X!',
ref: senderRef,
};
return (
<Flex wrap={'wrap'} gap={12} >
<Button
onClick={
() => {
senderRef.value?.focus({
cursor: 'start',
});
}
}
>
Focus at first
</Button>
<Button
onClick={() => {
senderRef.value?.focus({
cursor: 'end',
});
}}
>
Focus at last
</Button>
<Button
onClick={() => {
senderRef.value?.focus({
cursor: 'all',
});
}}
>
Focus to select all
</Button>
<Button
onClick={() => {
senderRef.value?.focus({
preventScroll: true,
});
}}
>
Focus prevent scroll
</Button>
<Button
onClick={() => {
senderRef.value?.blur();
}}
>
Blur
</Button>
<Sender {...senderProps} />
</Flex>
);
};
defineRender(() => {
return (
<App>
<Demo />
</App>
)
});
</script>
隐藏源代码
API
SenderProps
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
actions | 自定义按钮,当不需要默认操作按钮时,可以设为 actions={false} | VNode | (oriNode, info: { components: ActionsComponents }) => VNode | - | - |
allowSpeech | 是否允许语音输入 | boolean | SpeechConfig | false | - |
classNames | 样式类名 | 见下 | - | - |
components | 自定义组件 | Record<'input', ComponentType> | - | - |
defaultValue | 输入框默认值 | string | - | - |
disabled | 是否禁用 | boolean | false | - |
loading | 是否加载中 | boolean | false | - |
header | 头部面板 | VNode | - | - |
prefix | 前缀内容 | VNode | - | - |
footer | 底部内容 | ReactNode | (info: { components: ActionsComponents }) => ReactNode | - | - |
readOnly | 是否让输入框只读 | boolean | false | - |
rootClassName | 根元素样式类 | string | - | - |
styles | 语义化定义样式 | 见下 | - | - |
submitType | 提交模式 | SubmitType | enter | shiftEnter | - |
value(v-model) | 输入框值 | string | - | - |
onSubmit | 点击发送按钮的回调 | (message: string) => void | - | - |
onChange | 输入框值改变的回调 | (value: string, event?: FormEvent | ChangeEvent ) => void | - | - |
onCancel | 点击取消按钮的回调 | () => void | - | - |
onPasteFile | 黏贴文件的回调 | (firstFile: File, files: FileList) => void | - | - |
autoSize | 自适应内容高度,可设置为 true | false 或对象:{ minRows: 2, maxRows: 6 } | boolean | { minRows?: number; maxRows?: number } | { maxRows: 8 } | - |
typescript
type SpeechConfig = {
// 当设置 `recording` 时,内置的语音输入功能将会被禁用。
// 交由开发者实现三方语音输入的功能。
recording?: boolean;
onRecordingChange?: (recording: boolean) => void;
};
typescript
type ActionsComponents = {
SendButton: InstanceType<ButtonProps>;
ClearButton: InstanceType<ButtonProps>;
LoadingButton: InstanceType<ButtonProps>;
SpeechButton: InstanceType<ButtonProps>;
};
Sender Slots
插槽名 | 说明 | 类型 |
---|---|---|
header | 头部面板 | - |
prefix | 前缀内容 | _ |
actions | 操作按钮 | { ori?: VNode; info?: { components: { SendButton: InstanceType<Button>; ClearButton: InstanceType<Button>; LoadingButton: InstanceType<Button>; SpeechButton: InstanceType<Button>; } } } |
footer | 底部内容 | { info?: { components: { SendButton: InstanceType<Button>; ClearButton: InstanceType<Button>; LoadingButton: InstanceType<Button>; SpeechButton: InstanceType<Button>; } } } |
Sender Ref
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
nativeElement | 外层容器 | HTMLDivElement | - | - |
focus | 获取焦点 | (option?: { preventScroll?: boolean, cursor?: 'start' | 'end' | 'all' }) | - | - |
blur | 取消焦点 | () => void | - | - |
Sender.Header
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
children | 面板内容 | VNode | - | - |
closable | 是否可关闭 | boolean | true | - |
forceRender | 强制渲染,在初始化便需要 ref 内部元素时使用 | boolean | false | - |
open | 是否展开 | boolean | - | - |
title | 标题 | VNode | - | - |
onOpenChange | 展开状态改变的回调 | (open: boolean) => void | - | - |
Semantic DOM
prefix
前缀input
输入框actions
操作列表
Header
Content
Footer
header
头部header-content
头部内容footer
尾部