diff --git a/packages/pro-components/chat/chat-record/README.en-US.md b/packages/pro-components/chat/chat-record/README.en-US.md
new file mode 100644
index 000000000..eac998ec8
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/README.en-US.md
@@ -0,0 +1,110 @@
+---
+title: ChatRecord
+description: A component for displaying chat conversation history with features like time grouping, scroll loading, and message interactions.
+spline: base
+isComponent: true
+---
+
+## Import
+
+For global import, configure in `app.json` in the miniprogram root directory. For local import, configure in the `index.json` of the page or component where you need to import.
+
+```json
+"usingComponents": {
+ "t-chat-record": "tdesign-miniprogram/chat-record/chat-record"
+}
+```
+
+## Usage
+
+### 01 Component Types
+
+#### Basic Type
+
+Display a basic chat record list.
+
+```xml
+
+```
+
+#### With Time Grouping
+
+Support automatic time grouping display based on time intervals.
+
+```xml
+
+```
+
+#### Scroll Load More
+
+Support loading more history when scrolling to the top.
+
+```xml
+
+```
+
+#### Custom Message Rendering
+
+Support custom message rendering through slots.
+
+```xml
+
+
+
+
+
+```
+
+## API
+
+### ChatRecord Props
+
+Name | Type | Default | Description | Required
+-- | -- | -- | -- | --
+style | Object | - | Style | N
+custom-style | Object | - | Style, generally used for virtualized component node scenarios | N
+records | Array | [] | Chat record data list. TS Type: `ChatRecordItem[]` | N
+loading | Boolean | false | Whether to show loading state | N
+finished | Boolean | false | Whether all data has been loaded | N
+loading-text | String | Loading... | Loading prompt text | N
+finished-text | String | No more data | Finished loading prompt text | N
+empty-text | String | No chat records | Empty state prompt text | N
+show-time-group | Boolean | true | Whether to show time grouping | N
+time-group-interval | Number | 5 | Time grouping interval (minutes) | N
+virtual-scroll | Boolean | false | Whether to enable virtual scrolling | N
+scroll-view-height | String | 100vh | Scroll view height | N
+auto-scroll-to-bottom | Boolean | true | Whether to auto scroll to bottom | N
+
+### ChatRecord Events
+
+Name | Parameters | Description
+-- | -- | --
+loadmore | - | Triggered when scrolling to the top, used to load more history
+scroll | `(detail: ScrollDetail)` | Triggered when scrolling
+message-click | `(detail: { record: ChatRecordItem })` | Triggered when clicking a message
+message-long-press | `(detail: { record: ChatRecordItem })` | Triggered when long-pressing a message
+
+### ChatRecord Slots
+
+Name | Description
+-- | --
+empty | Custom empty state content
+message | Custom message content, receives record parameter
+
+### ChatRecord External Style Classes
+
+Class Name | Description
+-- | --
+t-class | Root node style class
+t-class-empty | Empty state style class
+t-class-time | Time grouping style class
+t-class-message | Message item style class
diff --git a/packages/pro-components/chat/chat-record/README.md b/packages/pro-components/chat/chat-record/README.md
new file mode 100644
index 000000000..bffa4bc88
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/README.md
@@ -0,0 +1,93 @@
+---
+title: ChatRecord 语音输入
+description: 用于聊天场景的语音输入组件,支持语音转文字、录音时长控制等功能。
+spline: base
+isComponent: true
+---
+
+## 引入
+
+全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。
+
+```json
+"usingComponents": {
+ "t-chat-record": "tdesign-miniprogram/chat-record/chat-record"
+}
+```
+
+## 前置配置
+
+### 1. 添加插件声明
+
+在 `app.json` 中声明微信同声传译插件:
+
+```json
+{
+ "plugins": {
+ "WechatSI": {
+ "version": "0.3.6",
+ "provider": "wx069ba97219f66d99"
+ }
+ }
+}
+```
+
+### 2. 麦克风权限
+
+使用语音输入需要用户授权麦克风权限。组件会自动处理授权流程,但开发者需要了解以下场景:
+
+#### 首次使用
+- 组件会自动调用 `wx.authorize` 申请麦克风权限
+- 用户同意后即可正常使用
+
+#### 用户拒绝授权
+- 如果用户点击拒绝,会显示"请授权麦克风权限"提示
+- 点击提示区域会引导用户前往设置页面开启权限
+
+#### 权限问题排查
+
+如果在小程序设置页面看不到麦克风权限选项:
+
+1. **检查微信 App 权限**
+ - 进入手机系统设置 > 应用管理 > 微信
+ - 确保微信有麦克风权限
+
+2. **检查小程序授权**
+ - 微信中下拉打开最近使用小程序列表
+ - 长按目标小程序 > 关于小程序 > 设置
+ - 查看是否有麦克风权限选项
+
+3. **重新授权**
+ - 删除小程序后重新搜索进入
+ - 首次点击语音输入时会重新触发授权弹窗
+
+4. **真机调试**
+ - 模拟器无法测试录音功能
+ - 必须使用真机预览或体验版测试
+
+## API
+
+### ChatRecord Props
+
+名称 | 类型 | 默认值 | 描述 | 必传
+-- | -- | -- | -- | --
+useSpeechNoAuthSlot | Boolean | false | 是否启用语音输入无权限时显示插槽 | N
+useSpeechInputSlot | Boolean | false | 是否启用语音输入时显示插槽 | N
+autoSendHeight| Number | 0 | 自动发送消息的高度 | N
+speechInput | slot | false | 语音输入按钮插槽 | N
+speechNoAuth | slot | false | 语音授权按钮插槽 | N
+
+
+### ChatRecord Events
+
+名称 | 参数 | 描述
+-- | -- | --
+recognize | msg: string | 识别到的文本内容
+
+### ChatRecord Slots
+
+名称 | 描述
+-- | --
+speechInput | 语音输入按钮插槽
+speechNoAuth | 语音授权按钮插槽
+
diff --git a/packages/pro-components/chat/chat-record/_example/base/index.json b/packages/pro-components/chat/chat-record/_example/base/index.json
new file mode 100644
index 000000000..5e834db8a
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/base/index.json
@@ -0,0 +1,9 @@
+{
+ "component": true,
+ "styleIsolation": "shared",
+ "usingComponents": {
+ "t-navbar": "tdesign-miniprogram/navbar/navbar",
+ "t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
+ "t-chat-record": "tdesign-miniprogram/chat-record/chat-record"
+ }
+}
diff --git a/packages/pro-components/chat/chat-record/_example/base/index.ts b/packages/pro-components/chat/chat-record/_example/base/index.ts
new file mode 100644
index 000000000..1a031ee2c
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/base/index.ts
@@ -0,0 +1,97 @@
+Page({
+ /**
+ * 页面的初始数据
+ */
+ data: {
+ query: '', // 输入框内容
+ placeholder: '请输入内容', // 输入框占位符
+ loading: false, // 发送按钮加载状态
+ showVoice: false, // 是否显示语音输入组件
+ },
+
+ /**
+ * 返回上一页
+ */
+ navigateBack() {
+ wx.navigateBack({
+ delta: 1,
+ });
+ },
+
+ /**
+ * 处理输入框内容变化
+ */
+ handleInput(e) {
+ this.setData({
+ query: e.detail.value,
+ });
+ },
+
+ /**
+ * 切换语音输入显示状态
+ */
+ handleVoice() {
+ this.setData({
+ showVoice: !this.data.showVoice,
+ });
+ },
+
+ /**
+ * 语音识别回调
+ * @param {Object} e - 事件对象
+ */
+ handleRecognize(e) {
+ const voiceMsg = e.detail.msg;
+ console.log('语音识别结果:', voiceMsg);
+
+ // 将语音识别结果设置到输入框中
+ this.setData({
+ query: voiceMsg,
+ showVoice: false, // 识别完成后隐藏语音输入组件
+ });
+
+ // 提示用户
+ wx.showToast({
+ title: '识别成功',
+ icon: 'success',
+ duration: 1500,
+ });
+ },
+
+ /**
+ * 发送消息
+ */
+ handleSend() {
+ const { query, loading } = this.data;
+
+ // 如果正在加载或内容为空,不处理
+ if (loading || !query.trim()) {
+ if (!query.trim()) {
+ wx.showToast({
+ title: '请输入内容',
+ icon: 'none',
+ duration: 1500,
+ });
+ }
+ return;
+ }
+ // 设置加载状态
+ this.setData({
+ loading: true,
+ });
+
+ // 模拟发送请求
+ setTimeout(() => {
+ this.setData({
+ loading: false,
+ query: '', // 清空输入框
+ });
+
+ wx.showToast({
+ title: '发送成功',
+ icon: 'success',
+ duration: 1500,
+ });
+ }, 1000);
+ },
+});
diff --git a/packages/pro-components/chat/chat-record/_example/base/index.wxml b/packages/pro-components/chat/chat-record/_example/base/index.wxml
new file mode 100644
index 000000000..9b907e6da
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/base/index.wxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 按住说话
+
+ 请授权麦克风权限
+
+
+
+
+
+
+
+
diff --git a/packages/pro-components/chat/chat-record/_example/base/index.wxss b/packages/pro-components/chat/chat-record/_example/base/index.wxss
new file mode 100644
index 000000000..51c640ae3
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/base/index.wxss
@@ -0,0 +1,84 @@
+.demo-base-container {
+ padding: 56rpx 0 0 0;
+ background-color: var(--td-bg-color-container);
+ height: 488rpx;
+ position: relative;
+}
+
+/* 聊天发送器包装器 */
+.chat-sender-demo-wrapper {
+ margin-bottom: 32rpx;
+ /* border: 2rpx solid #e5e5e5; */
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+
+.chat-sender-height-limit {
+ height: 72rpx;
+ padding: 0 24rpx;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.chat-sender-height-left-limit {
+ height: 70rpx;
+ width: 70rpx;
+ border-top: 1px var(--td-component-stroke) dashed;
+ border-left: 1px var(--td-component-stroke) dashed;
+ border-top-left-radius: 32rpx;
+}
+.chat-sender-height-right-limit {
+ height: 70rpx;
+ width: 70rpx;
+ border-top: 1px var(--td-component-stroke) dashed;
+ border-right: 1px var(--td-component-stroke) dashed;
+ border-top-right-radius: 32rpx;
+}
+.chat-sender-placeholder {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: var(--demo-chat-sender-placeholder);
+ text-align: center;
+ height: 48rpx;
+}
+
+.chat-sender-wrapper {
+ position: absolute;
+ width: 100%;
+ bottom: 0rpx;
+ background-color: var(--td-bg-color-container);
+}
+
+.demo-footer {
+ height: 32rpx;
+ width: 100%;
+ text-align: center;
+ font-size: 20rpx;
+ line-height: 32rpx;
+ color: var(--td-text-color-placeholder);
+ position: absolute;
+ bottom: 32rpx;
+}
+
+.demo-footer-prefix {
+ display: flex;
+ align-items: center;
+}
+
+/* 语音输入按钮样式 */
+.voice-input-button {
+ width: 100%;
+ height: 96rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--td-bg-color-container);
+ border-radius: 8rpx;
+ color: var(--td-text-color-primary);
+ font-size: 28rpx;
+}
+
+.voice-input-text {
+ margin-left: 8rpx;
+}
diff --git a/packages/pro-components/chat/chat-record/_example/chat-record.json b/packages/pro-components/chat/chat-record/_example/chat-record.json
new file mode 100644
index 000000000..36c6e4ce9
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/chat-record.json
@@ -0,0 +1,6 @@
+{
+ "navigationBarTitleText": "ChatRecord",
+ "usingComponents": {
+ "base": "./base"
+ }
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-record/_example/chat-record.less b/packages/pro-components/chat/chat-record/_example/chat-record.less
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/pro-components/chat/chat-record/_example/chat-record.ts b/packages/pro-components/chat/chat-record/_example/chat-record.ts
new file mode 100644
index 000000000..560d44d43
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/chat-record.ts
@@ -0,0 +1 @@
+Page({});
diff --git a/packages/pro-components/chat/chat-record/_example/chat-record.wxml b/packages/pro-components/chat/chat-record/_example/chat-record.wxml
new file mode 100644
index 000000000..364a909f7
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/_example/chat-record.wxml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/packages/pro-components/chat/chat-record/chat-record.json b/packages/pro-components/chat/chat-record/chat-record.json
new file mode 100644
index 000000000..66165257f
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/chat-record.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "styleIsolation": "apply-shared"
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-record/chat-record.less b/packages/pro-components/chat/chat-record/chat-record.less
new file mode 100644
index 000000000..dd10d18a3
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/chat-record.less
@@ -0,0 +1,637 @@
+@import '../../../components/common/style/base.less';
+
+@chat-record: ~'@{prefix}-chat-record';
+
+@chat-record-primary-color: #126dff;
+@chat-record-primary-color-light: rgba(18, 109, 255, 0.6);
+@chat-record-primary-gradient: linear-gradient(
+ 180deg,
+ @chat-record-primary-color 0%,
+ @chat-record-primary-color-light 100%
+);
+
+@chat-record-error-color: #ff5729;
+@chat-record-error-color-light: rgba(255, 87, 41, 0.6);
+@chat-record-error-gradient: linear-gradient(180deg, @chat-record-error-color 0%, @chat-record-error-color-light 100%);
+
+@chat-record-text-primary: rgba(0, 0, 0, 0.9);
+@chat-record-text-secondary: rgba(0, 0, 0, 0.4);
+@chat-record-text-tertiary: rgba(0, 0, 0, 0.26);
+@chat-record-text-white: #fff;
+
+@chat-record-bg-white: #fff;
+@chat-record-bg-mask: rgba(255, 255, 255, 0.54);
+@chat-record-bg-dark: rgba(0, 0, 0, 0.03);
+
+@chat-record-padding-vertical: 24rpx;
+@chat-record-padding-horizontal: 40rpx;
+@chat-record-margin-bottom: 60rpx;
+@chat-record-margin-bottom-small: 48rpx;
+
+@chat-record-border-radius-small: 16px;
+@chat-record-border-radius-medium: 48rpx;
+@chat-record-border-radius-large: 64rpx;
+@chat-record-border-radius-full: 50%;
+
+@chat-record-border-width: 2rpx;
+@chat-record-border-color: @chat-record-bg-white;
+
+@chat-record-font-size-large: 34rpx;
+@chat-record-font-size-medium: 28rpx;
+@chat-record-line-height: 42rpx;
+@chat-record-line-height-large: 58rpx;
+@chat-record-letter-spacing: 1.02rpx;
+
+@chat-record-btn-height: 96rpx;
+@chat-record-btn-width-small: 64rpx;
+@chat-record-btn-icon-size: 48rpx;
+
+@chat-record-ani-size: 256rpx;
+@chat-record-ani-inner-size: 160rpx;
+@chat-record-ani-main-size: 176rpx;
+
+@chat-record-footer-height: 348rpx;
+@chat-record-footer-bg-height: 300rpx;
+
+@chat-record-transition-fast: 0.25s linear;
+@chat-record-transition-normal: 0.6s;
+@chat-record-transition-slow: 1s;
+@chat-record-animation-duration: 1.5s;
+
+@chat-record-box-shadow: 0 8rpx 32rpx 0 rgba(18, 109, 255, 0.12);
+
+@chat-record-asset-base-url: 'https://static.wecity.qq.com/webrebuildmysscard-mini/250702-aigc-audio';
+
+.@{chat-record} {
+ padding: @chat-record-padding-vertical 0;
+ width: -webkit-fill-available;
+
+ .@{chat-record}-hook {
+ width: 100%;
+ color: @chat-record-text-primary;
+ text-align: center;
+ font-size: @chat-record-font-size-large;
+ font-style: normal;
+ font-weight: 500;
+ line-height: @chat-record-line-height;
+ letter-spacing: @chat-record-letter-spacing;
+ border-radius: @chat-record-border-radius-small;
+ border: @chat-record-border-width solid @chat-record-border-color;
+ }
+
+ .@{chat-record}-audio-input {
+ &.show {
+ .@{chat-record}-audio-input__mask {
+ opacity: 1;
+ pointer-events: auto;
+ }
+
+ .@{chat-record}-audio-input__main {
+ opacity: 1;
+ pointer-events: auto;
+ }
+ }
+
+ &.unknow {
+ .@{chat-record}-audio-input__ft__tips {
+ color: @chat-record-primary-color;
+ }
+
+ .@{chat-record}-audio-input__ft__tips__inner {
+ display: none;
+ }
+
+ .speak-btn {
+ display: flex;
+ }
+
+ .speak-close-btn {
+ display: flex;
+ }
+
+ .audio-loading-icon {
+ display: none;
+ }
+ }
+
+ &.cancel {
+ .@{chat-record}-audio-input__ft__tips__inner {
+ color: @chat-record-error-color;
+ }
+
+ .@{chat-record}-audio-input__ft__tips {
+ color: @chat-record-error-color;
+ }
+
+ .@{chat-record}-audio-input__ft__bg {
+ background: url('@{chat-record-asset-base-url}/img-ctrl-bg-red.png') no-repeat center;
+ background-size: cover;
+ }
+
+ .audio-loading-icon {
+ .audio-loading-dot {
+ background: @chat-record-error-gradient;
+ }
+ }
+ }
+
+ &.complete {
+ .@{chat-record}-audio-input__ft__tips__inner {
+ display: none;
+ }
+
+ .@{chat-record}-audio-input__ft__bg {
+ display: none;
+ }
+
+ .@{chat-record}-audio-input__ft__ct {
+ display: flex;
+ }
+
+ .audio-loading-icon {
+ display: none;
+ }
+
+ .@{chat-record}-audio-inputt__ft__tips {
+ display: none;
+ }
+ }
+
+ &.cover-ng-bar {
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 99999999;
+ }
+
+ &__mask {
+ width: 100vw;
+ height: 100vh;
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 98;
+ background: @chat-record-bg-mask;
+ backdrop-filter: blur(12px);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity @chat-record-transition-fast;
+ }
+
+ &__main {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 99;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity @chat-record-transition-fast;
+ }
+
+ &-ani {
+ display: flex;
+ align-items: center;
+ width: @chat-record-ani-size;
+ height: @chat-record-ani-size;
+ margin: 0 auto @chat-record-margin-bottom;
+ position: relative;
+ overflow: hidden;
+
+ .ani-cir {
+ width: @chat-record-ani-size;
+ height: @chat-record-ani-size;
+ position: relative;
+ z-index: 3;
+
+ &.diff-start {
+ .ani-cir-2 {
+ animation: diff @chat-record-transition-slow linear infinite;
+ }
+
+ .ani-cir-3 {
+ animation: diff @chat-record-transition-slow linear 0.5s infinite;
+ }
+
+ .ani-cir-4 {
+ animation: diff @chat-record-transition-slow linear 1s infinite;
+ }
+ }
+
+ .ani-cir-1,
+ .ani-cir-2,
+ .ani-cir-3,
+ .ani-cir-4 {
+ position: absolute;
+ left: 48rpx;
+ top: 48rpx;
+ width: @chat-record-ani-inner-size;
+ height: @chat-record-ani-inner-size;
+ background: url('@{chat-record-asset-base-url}/img-border.png') no-repeat center;
+ background-size: contain;
+ }
+
+ .ani-cir-1 {
+ animation: bg-turn 3s linear infinite;
+ }
+
+ @keyframes diff {
+ 0% {
+ transform: scale(1);
+ opacity: 0.33;
+ }
+
+ 100% {
+ transform: scale(1.61);
+ opacity: 0;
+ }
+ }
+ }
+
+ .ani-wrap {
+ width: @chat-record-ani-inner-size;
+ height: @chat-record-ani-inner-size;
+ z-index: 2;
+ position: absolute;
+ left: 48rpx;
+ top: 48rpx;
+ overflow: hidden;
+ border-radius: @chat-record-border-radius-full;
+
+ .ani-inner {
+ width: @chat-record-ani-inner-size;
+ height: @chat-record-ani-inner-size;
+ position: absolute;
+ left: 0;
+ top: 0;
+ border-radius: @chat-record-border-radius-full;
+ z-index: 2;
+ background: url('@{chat-record-asset-base-url}/img-clr-bg.png') no-repeat center;
+ background-size: contain;
+ animation: bg-turn 5s linear infinite;
+ }
+
+ @keyframes bg-turn {
+ 0% {
+ transform: rotateZ(0deg);
+ }
+
+ 100% {
+ transform: rotateZ(360deg);
+ }
+ }
+
+ .ani-mask {
+ width: @chat-record-ani-inner-size;
+ height: @chat-record-ani-inner-size;
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 3;
+ background: url('@{chat-record-asset-base-url}/img-ani-mask.png') no-repeat center;
+ background-size: contain;
+ }
+
+ .ani-main {
+ width: @chat-record-ani-main-size;
+ height: @chat-record-ani-main-size;
+ position: absolute;
+ left: -8rpx;
+ top: 11rpx;
+ z-index: 4;
+ background: url('@{chat-record-asset-base-url}/img-ani-spirit-new.png') no-repeat center 0;
+ background-size: 100% auto;
+
+ &.ani-start {
+ animation: spirit-start @chat-record-transition-normal steps(14) forwards;
+ }
+
+ &.ani-end {
+ background: url('@{chat-record-asset-base-url}/img-ani-spirit-new.png') no-repeat center 100%;
+ background-size: @chat-record-ani-main-size auto;
+ animation: spirit-end @chat-record-transition-normal steps(14) forwards;
+ }
+ }
+
+ @keyframes spirit-start {
+ 0% {
+ background-position: center 0;
+ }
+
+ 100% {
+ background-position: center -2464rpx;
+ }
+ }
+
+ @keyframes spirit-end {
+ 0% {
+ background-position: center -2464rpx;
+ }
+
+ 100% {
+ background-position: center 0;
+ }
+ }
+
+ @keyframes spirit-start-1 {
+ 0% {
+ background-position: center 0;
+ }
+
+ 100% {
+ background-position: center -2436rpx;
+ }
+ }
+
+ @keyframes spirit-end-1 {
+ 0% {
+ background-position: center -2436rpx;
+ }
+
+ 100% {
+ background-position: center 0;
+ }
+ }
+ }
+ }
+
+ &__con {
+ margin: 0 @chat-record-padding-horizontal 88rpx;
+ overflow-y: scroll;
+
+ &.disabled {
+ .@{chat-record}-audio-input__con__inner {
+ color: @chat-record-text-secondary;
+ }
+
+ .@{chat-record}-audio-input__con__ta {
+ color: @chat-record-text-secondary;
+ }
+ }
+
+ &__inner {
+ color: @chat-record-text-primary;
+ font-size: @chat-record-font-size-large;
+ font-weight: 500;
+ line-height: @chat-record-line-height-large;
+ min-height: 116rpx;
+ text-align: center;
+ }
+
+ &__ta {
+ color: @chat-record-text-primary;
+ font-size: @chat-record-font-size-large;
+ font-weight: 500;
+ line-height: @chat-record-line-height-large;
+ min-height: 116rpx;
+ text-align: left;
+ display: block;
+ width: 100%;
+ overflow: scroll;
+
+ // height: auto;
+ &.txt-limit-5 {
+ max-height: 120rpx;
+ }
+
+ &.txt-limit-9 {
+ max-height: 522rpx;
+ }
+ }
+ }
+
+ .audio-loading-icon {
+ display: flex;
+ align-items: center;
+ height: 24rpx;
+ position: absolute;
+ top: 218rpx;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+
+ .audio-loading-dot {
+ height: 10rpx;
+ width: 7rpx;
+ background: @chat-record-primary-gradient;
+ border-radius: 4rpx;
+
+ & + .audio-loading-dot {
+ margin-left: 7rpx;
+ }
+
+ &.dot-1 {
+ animation: dot-scale 1.5s linear infinite;
+ }
+
+ &.dot-2 {
+ transform: scaleY(1.6);
+ animation: dot-scale-2 1.5s linear infinite;
+ }
+
+ &.dot-3 {
+ transform: scaleY(2.4);
+ animation: dot-scale-3 1.5s linear infinite;
+ }
+ }
+
+ @keyframes dot-scale {
+ 0% {
+ transform: scaleY(1);
+ }
+
+ 50% {
+ transform: scaleY(2.4);
+ }
+
+ 100% {
+ transform: scaleY(1);
+ }
+ }
+
+ @keyframes dot-scale-2 {
+ 0% {
+ transform: scaleY(1.6);
+ }
+
+ 25% {
+ transform: scaleY(2.4);
+ }
+
+ 75% {
+ transform: scaleY(1);
+ }
+
+ 100% {
+ transform: scaleY(1.6);
+ }
+ }
+
+ @keyframes dot-scale-3 {
+ 0% {
+ transform: scaleY(2.4);
+ }
+
+ 50% {
+ transform: scaleY(1);
+ }
+
+ 100% {
+ transform: scaleY(2.4);
+ }
+ }
+
+ &__ft {
+ position: relative;
+
+ &__bg {
+ width: 100%;
+ height: @chat-record-footer-height;
+ display: block;
+ background: url('@{chat-record-asset-base-url}/img-ctrl-bg-new.png') no-repeat center;
+ background-size: cover;
+ }
+
+ &__tips {
+ color: @chat-record-text-tertiary;
+ text-align: center;
+ font-size: @chat-record-font-size-medium;
+ line-height: 36rpx;
+ }
+
+ &__tips__inner {
+ position: absolute;
+ left: 3rpx;
+ right: 3rpx;
+ top: 146rpx;
+ color: @chat-record-primary-color;
+ text-align: center;
+ font-size: @chat-record-font-size-medium;
+ line-height: 36rpx;
+ }
+
+ &__ct {
+ display: flex;
+ align-items: center;
+ height: 300rpx;
+ margin-top: 48rpx;
+ padding: 0 40rpx;
+ display: none;
+
+ &.keyboard-cover {
+ height: fit-content;
+ padding: 0 32rpx 32rpx;
+ }
+ }
+
+ &__btn {
+ border-radius: @chat-record-border-radius-medium;
+ height: @chat-record-btn-height;
+ background: @chat-record-bg-white;
+ box-shadow: @chat-record-box-shadow;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ & + .@{chat-record}-audio-input__ft__btn {
+ margin-left: 24rpx;
+ }
+
+ &.btn-send {
+ flex: 1;
+ font-size: 40rpx;
+ font-weight: 500;
+ background: @chat-record-primary-gradient;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ &.btn-close {
+ width: @chat-record-btn-height;
+ flex-shrink: 0;
+
+ .close-icon {
+ width: @chat-record-btn-icon-size;
+ height: @chat-record-btn-icon-size;
+ mask: url('@{chat-record-asset-base-url}/icon-close.svg') no-repeat center;
+ mask-size: contain;
+ background: @chat-record-text-tertiary;
+ }
+ }
+ }
+
+ .speak-close-btn {
+ position: absolute;
+ left: @chat-record-padding-horizontal;
+ top: 202rpx;
+ width: @chat-record-btn-width-small;
+ height: @chat-record-btn-width-small;
+ border-radius: @chat-record-border-radius-medium;
+ border: @chat-record-border-width solid @chat-record-border-color;
+ background: @chat-record-bg-dark;
+ box-sizing: border-box;
+ display: none;
+ align-items: center;
+ justify-content: center;
+
+ .close-icon {
+ width: @chat-record-btn-icon-size;
+ height: @chat-record-btn-icon-size;
+ border-radius: @chat-record-border-radius-full;
+ mask: url('@{chat-record-asset-base-url}/icon-close.svg') no-repeat center;
+ mask-size: contain;
+ background: @chat-record-text-tertiary;
+ }
+ }
+
+ .speak-btn {
+ position: absolute;
+ left: 50%;
+ top: 210rpx;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ display: none;
+ // pointer-events: none;
+
+ .speak-icon {
+ width: @chat-record-btn-icon-size;
+ height: @chat-record-btn-icon-size;
+ background: url('@{chat-record-asset-base-url}/icon-vol.svg') no-repeat center;
+ background-size: contain;
+ margin-right: 4rpx;
+ }
+
+ .tips-txt {
+ font-size: @chat-record-font-size-large;
+ font-weight: 500;
+ background: @chat-record-primary-gradient;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+ }
+
+ .audio-volume-icon {
+ width: @chat-record-btn-icon-size;
+ height: @chat-record-btn-icon-size;
+ position: absolute;
+ top: 60rpx;
+ left: 50%;
+ z-index: 2;
+ transform: translateX(-50%);
+ mask: url('@{chat-record-asset-base-url}/icon-play.svg') no-repeat center;
+ mask-size: contain;
+ background: @chat-record-text-secondary;
+
+ &.volume-w {
+ mask: url('@{chat-record-asset-base-url}/icon-play.svg') no-repeat center;
+ mask-size: contain;
+ background: @chat-record-text-white;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/pro-components/chat/chat-record/chat-record.ts b/packages/pro-components/chat/chat-record/chat-record.ts
new file mode 100644
index 000000000..618e7e721
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/chat-record.ts
@@ -0,0 +1,622 @@
+import { SuperComponent, wxComponent } from '../../../components/common/src/index';
+import config from '../../../components/common/config';
+import props from './props';
+import { TouchStatus, RecordStatus, ComponentStatus, CustomFocusEvent, CustomTouchEvent } from './type';
+
+declare const wx: any;
+declare function requirePlugin(name: string): any;
+
+const { prefix } = config;
+const name = `${prefix}-chat-record`;
+
+// 插件管理器(延迟加载,错误处理)
+let manager: any = null;
+const getManager = () => {
+ if (!manager) {
+ try {
+ const plugin = requirePlugin('WechatSI');
+ manager = plugin?.getRecordRecognitionManager?.() || null;
+ } catch (e) {
+ console.error('WechatSI 插件加载失败:', e);
+ }
+ }
+ return manager;
+};
+
+@wxComponent()
+export default class ChatRecord extends SuperComponent {
+ options = {
+ multipleSlots: true,
+ };
+
+ properties = props;
+
+ // 实例变量,避免多组件间共享
+ private startRecordTimer: ReturnType | null = null;
+
+ private recordTimer: ReturnType | null = null;
+
+ data = {
+ classPrefix: name,
+ showMask: false,
+ touchStatus: 'bottom',
+ startTime: 0,
+ recordCountDown: -1,
+ translateResult: '',
+ voiceInfo: {
+ voicePath: '',
+ duration: 0,
+ },
+ recordStatus: '',
+ recordAuthSetting: false,
+ recordAuthStatus: true,
+ isStarted: false,
+ bottomHeight: 0,
+ autoSendHeight: true,
+ windowHeight: 0,
+ translateSuccess: false,
+ showRecordCountDown: false,
+ status: 'normal' as ComponentStatus,
+ hasSpeechInputSlot: true,
+ hasSpeechNoAuthSlot: true,
+ };
+
+ observers = {
+ translateResult() {
+ this.setData({
+ translateSuccess: this.data.translateResult !== '-1' && !!this.data.translateResult,
+ });
+ },
+ recordCountDown() {
+ this.setData({
+ showRecordCountDown: this.data.recordCountDown >= 0,
+ });
+ },
+ 'touchStatus, recordStatus, translateResult'() {
+ const translateSuccess = this.data.translateResult !== '-1' && !!this.data.translateResult;
+ let status: ComponentStatus = 'normal';
+
+ if (this.data.touchStatus === 'top') {
+ status = 'cancel';
+ } else if (this.data.recordStatus === 'recording') {
+ status = 'recording';
+ } else if (this.data.recordStatus === 'thinking') {
+ status = 'thinking';
+ } else if (this.data.recordStatus === 'stop' && !translateSuccess) {
+ status = 'unknow';
+ } else if (this.data.recordStatus === 'stop' && translateSuccess) {
+ status = 'complete';
+ } else if (this.data.recordStatus === 'error') {
+ status = 'error';
+ }
+
+ this.setData({ status });
+ },
+ };
+
+ lifetimes = {
+ attached() {
+ this.initRecorderManager();
+ this.getWindowHeight();
+ },
+ detached() {
+ if (this.recordTimer) {
+ clearInterval(this.recordTimer);
+ this.recordTimer = null;
+ }
+ if (this.startRecordTimer) {
+ clearTimeout(this.startRecordTimer);
+ this.startRecordTimer = null;
+ }
+ const mgr = getManager();
+ if (mgr) {
+ mgr.stop();
+ }
+ },
+ };
+
+ /**
+ * 初始化同声传译插件
+ */
+ initRecorderManager(): void {
+ const mgr = getManager();
+ if (!mgr) {
+ console.warn('语音插件未加载,语音功能不可用');
+ return;
+ }
+
+ mgr.onStop = (res): void => {
+ const { tempFilePath, duration } = res;
+
+ if (this.data.touchStatus === 'top') {
+ this.resetRecordState();
+ return;
+ }
+
+ this.setData({
+ 'voiceInfo.voicePath': tempFilePath,
+ 'voiceInfo.duration': Math.floor(duration / 1000) || 1,
+ recordStatus: 'stop' as RecordStatus,
+ touchStatus: '' as TouchStatus,
+ });
+ };
+
+ mgr.onStart = (): void => {
+ this.setData({
+ recordStatus: 'thinking' as RecordStatus,
+ });
+ };
+
+ mgr.onRecognize = (res): void => {
+ if (res.result && !res.end) {
+ this.setData({
+ recordStatus: 'recording' as RecordStatus,
+ translateResult: res.result,
+ });
+ }
+ };
+
+ mgr.onError = (res): void => {
+ this.setData({
+ recordStatus: 'error' as RecordStatus,
+ touchStatus: '' as TouchStatus,
+ translateResult: '-1',
+ });
+
+ wx.showToast({
+ icon: 'none',
+ title: res.msg || '录音识别失败,请重试',
+ duration: 2000,
+ });
+ };
+ }
+
+ /**
+ * 重置录音状态
+ */
+ resetRecordState(): void {
+ this.setData({
+ showMask: false,
+ translateResult: '',
+ recordStatus: '' as RecordStatus,
+ touchStatus: '' as TouchStatus,
+ recordCountDown: -1,
+ startTime: 0,
+ });
+ }
+
+ /**
+ * 获取窗口高度
+ */
+ getWindowHeight(): void {
+ wx.getSystemInfo({
+ success: (res) => {
+ this.setData({
+ windowHeight: res.windowHeight,
+ });
+ },
+ });
+ }
+
+ /**
+ * 打开设置页授权
+ */
+ openVoiceSetting(): void {
+ wx.getSetting({
+ success: (res) => {
+ const authSetting = res.authSetting['scope.record'];
+ if (authSetting === true) {
+ // 已授权
+ this.setData({
+ recordAuthSetting: true,
+ recordAuthStatus: true,
+ });
+ wx.showToast({
+ title: '已授权麦克风',
+ icon: 'success',
+ });
+ return;
+ }
+
+ if (authSetting === false) {
+ // 用户曾经拒绝过,需要引导去设置页面
+ wx.showModal({
+ title: '需要麦克风权限',
+ content: '麦克风权限被拒绝,请前往微信设置开启',
+ confirmText: '去设置',
+ cancelText: '取消',
+ success: (modalRes) => {
+ if (modalRes.confirm) {
+ wx.openSetting({
+ success: (settingRes) => {
+ const newAuthStatus = !!settingRes.authSetting['scope.record'];
+ this.setData({
+ recordAuthSetting: newAuthStatus,
+ recordAuthStatus: newAuthStatus,
+ });
+ if (newAuthStatus) {
+ wx.showToast({
+ title: '授权成功',
+ icon: 'success',
+ });
+ }
+ },
+ fail: (err) => {
+ console.error('打开设置失败:', err);
+ wx.showToast({
+ title: '打开设置失败',
+ icon: 'none',
+ });
+ },
+ });
+ }
+ },
+ });
+ } else {
+ // 未申请过,尝试直接申请
+ this.applyAuthWithFallback();
+ }
+ },
+ fail: (err) => {
+ console.error('获取设置失败:', err);
+ // 降级处理:直接尝试申请
+ this.applyAuthWithFallback();
+ },
+ });
+ }
+
+ /**
+ * 带降级的授权申请
+ */
+ applyAuthWithFallback(): void {
+ wx.authorize({
+ scope: 'scope.record',
+ success: () => {
+ this.setData({
+ recordAuthSetting: true,
+ recordAuthStatus: true,
+ });
+ wx.showToast({
+ title: '授权成功',
+ icon: 'success',
+ });
+ },
+ fail: (err) => {
+ console.error('授权申请失败:', err);
+ this.handleAuthFail(err);
+ },
+ });
+ }
+
+ /**
+ * 处理授权失败
+ */
+ handleAuthFail(err: any): void {
+ // 判断是否是系统权限问题
+ const errMsg = err.errMsg || '';
+
+ if (errMsg.includes('system') || errMsg.includes('denied')) {
+ // 系统级权限被拒绝
+ wx.showModal({
+ title: '需要系统麦克风权限',
+ content:
+ '请检查手机系统设置:\n\n1. 进入手机「设置」>「应用管理」>「微信」\n2. 开启「麦克风」权限\n3. 返回小程序重试',
+ showCancel: false,
+ confirmText: '我知道了',
+ });
+ } else {
+ // 普通拒绝
+ wx.showModal({
+ title: '授权失败',
+ content: '无法使用语音输入功能。请在「设置」中允许小程序使用麦克风。',
+ confirmText: '去设置',
+ cancelText: '取消',
+ success: (res) => {
+ if (res.confirm) {
+ wx.openSetting();
+ }
+ },
+ });
+ }
+
+ this.setData({
+ recordAuthSetting: false,
+ recordAuthStatus: false,
+ });
+ }
+
+ /**
+ * 获取语音授权状态
+ */
+ getVoiceAuthSetting(): Promise {
+ return new Promise((resolve, reject) => {
+ wx.getSetting({
+ success: (res) => {
+ const authSettings = Object.keys(res.authSetting);
+ const recordAuthSetting = authSettings.includes('scope.record');
+ const recordAuthStatus = !!res.authSetting['scope.record'];
+ this.setData({
+ recordAuthSetting,
+ recordAuthStatus,
+ });
+ resolve(recordAuthSetting);
+ },
+ fail: () => {
+ reject(new Error('获取语音授权设置失败'));
+ },
+ });
+ });
+ }
+
+ /**
+ * 申请授权
+ */
+ applyAuth(): Promise {
+ return new Promise((resolve, reject) => {
+ wx.authorize({
+ scope: 'scope.record',
+ success: () => {
+ this.setData({
+ recordAuthSetting: true,
+ recordAuthStatus: true,
+ });
+ resolve(true);
+ },
+ fail: () => {
+ this.setData({
+ recordAuthSetting: false,
+ recordAuthStatus: false,
+ });
+ reject(new Error('语音授权申请失败'));
+ },
+ });
+ });
+ }
+
+ /**
+ * 修改语音转文字结果
+ */
+ onTranslateResultChange(value: string): void {
+ this.setData({
+ translateResult: value,
+ });
+ }
+
+ /**
+ * 直接发送语音消息
+ */
+ handleSendVoiceMsg(): void {
+ if (this.data.translateResult && this.data.translateResult !== '-1') {
+ this.sendVoiceMsg(this.data.translateResult);
+ }
+ this.resetRecordState();
+ }
+
+ /**
+ * 取消发送语音
+ */
+ handleCancelSend(): void {
+ this.resetRecordState();
+ }
+
+ /**
+ * 开始录音
+ */
+ async startRecord(e?: CustomTouchEvent): Promise {
+ if (this.data.isStarted) {
+ return;
+ }
+
+ // 阻止默认行为,防止冒泡
+ if (e?.preventDefault) {
+ e.preventDefault();
+ }
+
+ const mgr = getManager();
+ if (!mgr) {
+ wx.showToast({
+ title: '语音功能暂不可用',
+ icon: 'none',
+ });
+ return;
+ }
+
+ this.setData({ isStarted: true });
+
+ try {
+ await this.getVoiceAuthSetting();
+ if (!this.data.recordAuthSetting) {
+ await this.applyAuth();
+ this.setData({ isStarted: false });
+ return;
+ }
+ } catch (error) {
+ console.error('授权检查失败', error);
+ this.setData({ isStarted: false });
+ return;
+ }
+
+ this.setData({
+ touchStatus: 'bottom' as TouchStatus,
+ startTime: new Date().getTime(),
+ });
+
+ this.startRecordTimer = setTimeout(() => {
+ if (!this.data.isStarted) {
+ return;
+ }
+
+ this.setData({ showMask: true });
+
+ const currentMgr = getManager();
+ if (!currentMgr) {
+ this.setData({ isStarted: false });
+ return;
+ }
+
+ currentMgr.start({ duration: 30000, lang: 'zh_CN' });
+
+ this.recordTimer = setInterval(() => {
+ const recordTime = new Date().getTime() - this.data.startTime;
+ if (recordTime > 50000) {
+ if (this.data.recordCountDown === -1) {
+ this.setData({ recordCountDown: 10 });
+ } else {
+ this.setData({ recordCountDown: this.data.recordCountDown - 1 });
+ }
+ }
+ if (recordTime > 60000) {
+ this.stopRecord();
+ }
+ }, 1000);
+ }, 500);
+ }
+
+ /**
+ * 结束录音
+ */
+ stopRecord(): void {
+ this.setData({ isStarted: false });
+
+ if (this.recordTimer) {
+ clearInterval(this.recordTimer);
+ this.recordTimer = null;
+ }
+ if (this.startRecordTimer) {
+ clearTimeout(this.startRecordTimer);
+ this.startRecordTimer = null;
+ }
+
+ this.setData({ recordCountDown: -1 });
+
+ if (!this.data.recordAuthStatus) {
+ this.setData({
+ showMask: false,
+ startTime: 0,
+ });
+ return;
+ }
+
+ if (this.data.startTime === 0) {
+ return;
+ }
+
+ const recordTime = new Date().getTime() - this.data.startTime;
+ const mgr = getManager();
+
+ if (this.data.touchStatus === 'top') {
+ if (mgr) {
+ mgr.stop();
+ }
+ this.resetRecordState();
+ this.setData({ startTime: 0 });
+ wx.showToast({
+ icon: 'none',
+ title: '已取消发送',
+ duration: 1500,
+ });
+ return;
+ }
+
+ if (recordTime > 500) {
+ this.setData({
+ 'voiceInfo.duration': Math.floor(recordTime / 1000) || 1,
+ startTime: 0,
+ });
+
+ if (mgr) {
+ mgr.stop();
+ }
+ } else {
+ this.setData({
+ showMask: false,
+ startTime: 0,
+ });
+ if (mgr) {
+ mgr.stop();
+ }
+ wx.showToast({
+ icon: 'none',
+ title: '说话时间太短',
+ });
+ }
+ }
+
+ /**
+ * 录音过程中手指移动事件
+ */
+ touchmove(e: CustomTouchEvent): void {
+ if (!this.data.isStarted || !this.data.showMask) {
+ return;
+ }
+
+ const bottomHeight = 150;
+ const { changedTouches } = e;
+ if (!changedTouches || !changedTouches[0]) {
+ return;
+ }
+
+ const { clientY } = changedTouches[0];
+ const oldStatus = this.data.touchStatus;
+
+ let newTouchStatus: TouchStatus;
+ if (clientY > this.data.windowHeight - bottomHeight) {
+ newTouchStatus = 'bottom';
+ } else {
+ newTouchStatus = 'top';
+ }
+
+ if (oldStatus !== newTouchStatus) {
+ this.setData({ touchStatus: newTouchStatus });
+ }
+ }
+
+ /**
+ * 录音过程中被系统事件打断,结束录音
+ */
+ touchcancel(): void {
+ const mgr = getManager();
+ if (mgr) {
+ mgr.stop();
+ }
+
+ if (this.recordTimer) {
+ clearInterval(this.recordTimer);
+ this.recordTimer = null;
+ }
+ if (this.startRecordTimer) {
+ clearTimeout(this.startRecordTimer);
+ this.startRecordTimer = null;
+ }
+
+ this.resetRecordState();
+ this.setData({ isStarted: false });
+ }
+
+ /**
+ * 发送语音消息,触发recognize事件
+ */
+ sendVoiceMsg(voiceMsg: string): void {
+ this.triggerEvent('recognize', voiceMsg);
+ }
+
+ /**
+ * textarea获得焦点时
+ */
+ focusTextarea(e: CustomFocusEvent): void {
+ if (this.data.autoSendHeight) {
+ this.setData({
+ bottomHeight: e.detail?.height || 0,
+ });
+ }
+ }
+
+ /**
+ * textarea失去焦点时
+ */
+ blurTextarea(): void {
+ this.setData({
+ bottomHeight: 0,
+ });
+ }
+}
diff --git a/packages/pro-components/chat/chat-record/chat-record.wxml b/packages/pro-components/chat/chat-record/chat-record.wxml
new file mode 100644
index 000000000..08a2d4135
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/chat-record.wxml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ 按住 说话
+
+
+
+ 请授权麦克风权限
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{status === 'unknow' ? '不好意思,未能识别您的语音' : '我在听,请说话'}}
+
+
+
+
+
+
+ 松开手指 即可取消
+
+ 点击下方重新开始说话
+
+ 松手完成 上滑取消
+
+
+ {{recordCountDown}}秒后停止语音输入
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 按住说话
+
+
+
+
+
+
+
+ 发送
+
+
+
+
+
+
diff --git a/packages/pro-components/chat/chat-record/chat-record.wxs b/packages/pro-components/chat/chat-record/chat-record.wxs
new file mode 100644
index 000000000..2cc862d61
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/chat-record.wxs
@@ -0,0 +1,14 @@
+var utils = require('../../../components/common/utils.wxs');
+
+function getRecordClass(classPrefix, record) {
+ var classes = [classPrefix + '__record'];
+ if (record.placement) {
+ classes.push(classPrefix + '__record--' + record.placement);
+ }
+ return classes.join(' ');
+}
+
+module.exports = {
+ getRecordClass: getRecordClass,
+ _style: utils._style,
+};
diff --git a/packages/pro-components/chat/chat-record/index.ts b/packages/pro-components/chat/chat-record/index.ts
new file mode 100644
index 000000000..c1a91eadf
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/index.ts
@@ -0,0 +1,2 @@
+export * from './type';
+export { default as ChatRecord } from './chat-record';
diff --git a/packages/pro-components/chat/chat-record/props.ts b/packages/pro-components/chat/chat-record/props.ts
new file mode 100644
index 000000000..b6278d18c
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/props.ts
@@ -0,0 +1,24 @@
+/* eslint-disable */
+
+/**
+ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
+ */
+
+import { TdChatRecordProps } from './type';
+
+const props: TdChatRecordProps = {
+ useSpeechInputSlot: {
+ type: Boolean,
+ value: false,
+ },
+ useSpeechNoAuthSlot: {
+ type: Boolean,
+ value: false,
+ },
+ autoSendHeight: {
+ type: Boolean,
+ value: true,
+ },
+};
+
+export default props;
diff --git a/packages/pro-components/chat/chat-record/type.ts b/packages/pro-components/chat/chat-record/type.ts
new file mode 100644
index 000000000..96b0f148f
--- /dev/null
+++ b/packages/pro-components/chat/chat-record/type.ts
@@ -0,0 +1,55 @@
+/* eslint-disable */
+
+/**
+ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
+ */
+export interface TdChatRecordProps {
+ /**
+ * 是否使用自定义语音输入插槽
+ * @default false
+ */
+ useSpeechInputSlot?: {
+ type: BooleanConstructor;
+ value: boolean;
+ };
+ /**
+ * 是否使用自定义无权限插槽
+ * @default false
+ */
+ useSpeechNoAuthSlot?: {
+ type: BooleanConstructor;
+ value: boolean;
+ };
+ /**
+ * 是否自动抬升发送按钮高度
+ * @default true
+ */
+ autoSendHeight?: {
+ type: BooleanConstructor;
+ value: boolean;
+ };
+}
+
+/** 触摸状态类型 */
+export type TouchStatus = 'top' | 'bottom' | '';
+
+/** 录音状态类型 */
+export type RecordStatus = 'recording' | 'thinking' | 'stop' | 'error' | '';
+
+/** 组件状态类型 */
+export type ComponentStatus = 'normal' | 'cancel' | 'recording' | 'thinking' | 'unknow' | 'complete' | 'error';
+
+/** 触摸事件 */
+export interface CustomTouchEvent {
+ changedTouches?: Array<{
+ clientY: number;
+ }>;
+ preventDefault?: () => void;
+}
+
+/** 焦点事件 */
+export interface CustomFocusEvent {
+ detail?: {
+ height?: number;
+ };
+}
diff --git a/packages/pro-components/chat/chat-sender/README.md b/packages/pro-components/chat/chat-sender/README.md
index 46893c51e..5511fa887 100644
--- a/packages/pro-components/chat/chat-sender/README.md
+++ b/packages/pro-components/chat/chat-sender/README.md
@@ -59,6 +59,7 @@ adjust-position | Boolean | false | 默认键盘弹起不会把页面顶起来 |
attachments-props | Object | - | 附件列表属性。TS 类型:`AttachmentsProps`,[Attachments API Documents](./attachments?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/pro-components/chat/chat-sender/type.ts) | N
auto-rise-with-keyboard | Boolean | false | 键盘弹起时自动顶起来输入框 | N
disabled | Boolean | false | 是否禁用输入框 | N
+input-mode | String | text | 输入模式:text-文本输入模式(显示textarea),voice-语音输入模式(显示语音按钮)。可选项:text/voice | N
file-list | Array | [] | 附件文件列表。TS 类型:`FileItem[]` | N
loading | Boolean | false | 发送按钮是否处于加载状态 | N
placeholder | String | 请输入消息... | 输入框默认文案 | N
diff --git a/packages/pro-components/chat/chat-sender/chat-sender.less b/packages/pro-components/chat/chat-sender/chat-sender.less
index 445272129..c1d996160 100644
--- a/packages/pro-components/chat/chat-sender/chat-sender.less
+++ b/packages/pro-components/chat/chat-sender/chat-sender.less
@@ -19,6 +19,7 @@
@chat-sender-textarea-gap: 10rpx;
@chat-sender-textarea-padding: 0 24rpx;
@chat-sender-textarea-hook-padding-bottom: 16rpx;
+@chat-sender-textarea-hook-min-height: 134rpx;
@chat-sender-textarea-hook-max-height: 280rpx;
@chat-sender-input-max-height: 264rpx;
@@ -98,6 +99,7 @@
position: relative;
flex: 1;
padding-bottom: @chat-sender-textarea-hook-padding-bottom;
+ min-height: @chat-sender-textarea-hook-min-height;
max-height: @chat-sender-textarea-hook-max-height;
// 输入框
diff --git a/packages/pro-components/chat/chat-sender/chat-sender.wxml b/packages/pro-components/chat/chat-sender/chat-sender.wxml
index 02c2bf628..034a3cb0f 100644
--- a/packages/pro-components/chat/chat-sender/chat-sender.wxml
+++ b/packages/pro-components/chat/chat-sender/chat-sender.wxml
@@ -30,27 +30,34 @@
-
- {{placeholder}}
+
+
+
+ {{placeholder}}
+
+
+
+
+