im-ui-mobile 0.0.98 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/im-avatar/im-avatar.vue +121 -121
- package/components/im-button/im-button.vue +630 -626
- package/components/im-cell/im-cell.vue +10 -15
- package/components/im-cell-group/im-cell-group.vue +16 -16
- package/components/im-chat-item/im-chat-item.vue +213 -213
- package/components/im-context-menu/im-context-menu.vue +138 -138
- package/components/im-file-upload/im-file-upload.vue +309 -309
- package/components/im-friend-item/im-friend-item.vue +82 -75
- package/components/im-group-item/im-group-item.vue +62 -62
- package/components/im-group-member-selector/im-group-member-selector.vue +202 -202
- package/components/im-group-rtc-join/im-group-rtc-join.vue +112 -112
- package/components/im-image-upload/im-image-upload.vue +94 -94
- package/components/im-loading/im-loading.vue +64 -64
- package/components/im-mention-picker/im-mention-picker.vue +191 -191
- package/components/im-message-item/im-message-item.vue +555 -555
- package/components/im-nav-bar/im-nav-bar.vue +98 -98
- package/components/im-read-receipt/im-read-receipt.vue +174 -174
- package/components/im-virtual-list/im-virtual-list.vue +52 -51
- package/components/im-voice-input/im-voice-input.vue +305 -305
- package/libs/index.ts +2 -3
- package/package.json +58 -58
- package/styles/button.scss +1 -1
- package/theme.scss +61 -62
- package/types/components.d.ts +0 -1
- package/types/index.d.ts +94 -94
- package/types/libs/index.d.ts +206 -204
- package/types/utils/datetime.d.ts +9 -9
- package/types/utils/dom.d.ts +11 -11
- package/types/utils/emoji.d.ts +8 -8
- package/types/utils/enums.d.ts +73 -73
- package/types/utils/messageType.d.ts +35 -35
- package/types/utils/recorderApp.d.ts +9 -9
- package/types/utils/recorderH5.d.ts +9 -9
- package/types/utils/requester.d.ts +15 -15
- package/types/utils/url.d.ts +5 -5
- package/types/utils/useDynamicRefs.d.ts +9 -9
- package/types/utils/websocket.d.ts +34 -34
- package/utils/enums.js +1 -1
- package/components/im-virtual-scroller/im-virtual-scroller.vue +0 -54
- package/types/components/virtual-scroller.d.ts +0 -20
|
@@ -1,306 +1,306 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<view class="voice-input">
|
|
3
|
-
<view class="voice-input-bar" :class="{ recording: recording }" id="voice-input-bar" @click.stop=""
|
|
4
|
-
@touchstart.prevent="onStartRecord" @touchmove.prevent="onTouchMove" @touchend.prevent="onEndRecord">
|
|
5
|
-
{{ recording ? '正在录音' : '长按 说话' }}
|
|
6
|
-
</view>
|
|
7
|
-
<view v-if="recording" class="voice-input-window" :style="recordWindowStyle">
|
|
8
|
-
<view class="rc-wave">
|
|
9
|
-
<text class="note" style="--d: 0"></text>
|
|
10
|
-
<text class="note" style="--d: 1"></text>
|
|
11
|
-
<text class="note" style="--d: 2"></text>
|
|
12
|
-
<text class="note" style="--d: 3"></text>
|
|
13
|
-
<text class="note" style="--d: 4"></text>
|
|
14
|
-
<text class="note" style="--d: 5"></text>
|
|
15
|
-
<text class="note" style="--d: 6"></text>
|
|
16
|
-
</view>
|
|
17
|
-
<view class="rc-tip">{{ recordTip }}</view>
|
|
18
|
-
<view class="cancel-btn" @click="onCancel">
|
|
19
|
-
<u-icon name="close" :color="moveToCancel ? 'red' : 'black'" :size="moveToCancel ? 45 : 40"></u-icon>
|
|
20
|
-
</view>
|
|
21
|
-
<view class="opt-tip" :class="moveToCancel ? 'red' : 'black'">{{ moveToCancel ? '松手取消' : '松手发送,上划取消' }}
|
|
22
|
-
</view>
|
|
23
|
-
</view>
|
|
24
|
-
|
|
25
|
-
</view>
|
|
26
|
-
</template>
|
|
27
|
-
|
|
28
|
-
<script setup lang="ts">
|
|
29
|
-
import { ref, computed, onUnmounted } from 'vue'
|
|
30
|
-
import { RecorderApp, RecorderH5 } from '../../index'
|
|
31
|
-
|
|
32
|
-
const recording = ref(false);
|
|
33
|
-
const moveToCancel = ref(false);
|
|
34
|
-
const recordBarTop = ref(0);
|
|
35
|
-
const druation = ref(0);
|
|
36
|
-
const rcTimer = ref<number | null>(null);
|
|
37
|
-
|
|
38
|
-
const getRecorder = () => {
|
|
39
|
-
// #ifdef H5
|
|
40
|
-
return new RecorderH5()
|
|
41
|
-
// #endif
|
|
42
|
-
|
|
43
|
-
// #ifndef H5
|
|
44
|
-
return new RecorderApp()
|
|
45
|
-
// #endif
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let rc = getRecorder()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
interface Emits {
|
|
52
|
-
(e: 'send', data: any): void;
|
|
53
|
-
}
|
|
54
|
-
const emit = defineEmits<Emits>();
|
|
55
|
-
|
|
56
|
-
onUnmounted(() => {
|
|
57
|
-
stopTimer();
|
|
58
|
-
recording.value = false;
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const onTouchMove = (e: any) => {
|
|
62
|
-
const moveY = e.touches[0].clientY;
|
|
63
|
-
moveToCancel.value = moveY < recordBarTop.value - 40;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const onCancel = () => {
|
|
67
|
-
if (recording.value) {
|
|
68
|
-
moveToCancel.value = true;
|
|
69
|
-
onEndRecord();
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const onStartRecord = async () => {
|
|
74
|
-
/* 用户第一次使用语音会唤醒录音权限请求,此时会导致@touchend失效,
|
|
75
|
-
一直处于录音状态,这里允许用户再次点击发送语音并结束录音 */
|
|
76
|
-
if (recording.value) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
console.log("开始录音");
|
|
80
|
-
moveToCancel.value = false;
|
|
81
|
-
// await initRecordBar();
|
|
82
|
-
|
|
83
|
-
// if (!rc.checkIsEnable()) {
|
|
84
|
-
// return;
|
|
85
|
-
// }
|
|
86
|
-
|
|
87
|
-
rc.start().then(() => {
|
|
88
|
-
recording.value = true;
|
|
89
|
-
console.log("开始录音成功");
|
|
90
|
-
// 开始计时
|
|
91
|
-
startTimer();
|
|
92
|
-
}).catch((e: any) => {
|
|
93
|
-
console.log("录音失败" + JSON.stringify(e));
|
|
94
|
-
uni.showToast({
|
|
95
|
-
title: "录音失败",
|
|
96
|
-
icon: "none"
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const onEndRecord = () => {
|
|
102
|
-
if (!recording.value) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
recording.value = false;
|
|
106
|
-
// 停止计时
|
|
107
|
-
stopTimer();
|
|
108
|
-
// 停止录音
|
|
109
|
-
rc.close();
|
|
110
|
-
// 触屏位置是否移动到了取消区域
|
|
111
|
-
if (moveToCancel.value) {
|
|
112
|
-
console.log("录音取消");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
// 大于1秒才发送
|
|
116
|
-
if (druation.value <= 1) {
|
|
117
|
-
uni.showToast({
|
|
118
|
-
title: "说话时间太短",
|
|
119
|
-
icon: 'none'
|
|
120
|
-
});
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
rc.upload().then((data: any) => {
|
|
125
|
-
emit("send", data);
|
|
126
|
-
}).catch((e: any) => {
|
|
127
|
-
console.error(e)
|
|
128
|
-
uni.showToast({
|
|
129
|
-
title: '录音上传失败',
|
|
130
|
-
icon: 'none'
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const startTimer = () => {
|
|
136
|
-
druation.value = 0;
|
|
137
|
-
stopTimer();
|
|
138
|
-
rcTimer.value = Number(setInterval(() => {
|
|
139
|
-
druation.value++;
|
|
140
|
-
// 大于60s,直接结束
|
|
141
|
-
if (druation.value >= 60) {
|
|
142
|
-
onEndRecord();
|
|
143
|
-
}
|
|
144
|
-
}, 1000));
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const stopTimer = () => {
|
|
148
|
-
if (rcTimer.value) {
|
|
149
|
-
clearInterval(rcTimer.value);
|
|
150
|
-
rcTimer.value = null;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// const initRecordBar = () => {
|
|
155
|
-
// const query = uni.createSelectorQuery().in(getCurrentInstance());
|
|
156
|
-
// query.select('#voice-input-bar').boundingClientRect((rect: any) => {
|
|
157
|
-
// // 顶部高度位置
|
|
158
|
-
// recordBarTop.value = rect.top;
|
|
159
|
-
// }).exec();
|
|
160
|
-
// };
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 初始化录音条位置信息
|
|
164
|
-
* 兼容微信小程序和H5环境
|
|
165
|
-
*/
|
|
166
|
-
const initRecordBar = (): Promise<{ top: number; height: number; width: number }> => {
|
|
167
|
-
return new Promise((resolve, reject) => {
|
|
168
|
-
// 获取组件实例
|
|
169
|
-
const instance = getCurrentInstance()
|
|
170
|
-
if (!instance) {
|
|
171
|
-
reject(new Error('无法获取组件实例'))
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// 创建选择器查询
|
|
176
|
-
const query = uni.createSelectorQuery().in(instance)
|
|
177
|
-
|
|
178
|
-
query.select('#voice-input-bar').boundingClientRect((rect: any) => {
|
|
179
|
-
if (rect) {
|
|
180
|
-
const positionInfo = {
|
|
181
|
-
top: rect.top || 0,
|
|
182
|
-
height: rect.height || 0,
|
|
183
|
-
width: rect.width || 0,
|
|
184
|
-
left: rect.left || 0,
|
|
185
|
-
right: rect.right || 0,
|
|
186
|
-
bottom: rect.bottom || 0
|
|
187
|
-
}
|
|
188
|
-
resolve(positionInfo)
|
|
189
|
-
} else {
|
|
190
|
-
reject(new Error('无法获取录音条位置信息'))
|
|
191
|
-
}
|
|
192
|
-
}).exec()
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const recordWindowStyle = computed(() => {
|
|
197
|
-
const windowHeight = uni.getWindowInfo().windowHeight;
|
|
198
|
-
const bottom = windowHeight - recordBarTop.value + 12;
|
|
199
|
-
return `bottom:${bottom}px;`;
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const recordTip = computed(() => {
|
|
203
|
-
if (druation.value > 50) {
|
|
204
|
-
return `${60 - druation.value}s后将停止录音`;
|
|
205
|
-
}
|
|
206
|
-
return `录音时长:${druation.value}s`;
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
</script>
|
|
211
|
-
|
|
212
|
-
<style lang="scss" scoped>
|
|
213
|
-
.voice-input {
|
|
214
|
-
.rc-wave {
|
|
215
|
-
display: flex;
|
|
216
|
-
align-items: flex-end;
|
|
217
|
-
justify-content: center;
|
|
218
|
-
position: relative;
|
|
219
|
-
height: 80rpx;
|
|
220
|
-
|
|
221
|
-
.note {
|
|
222
|
-
background: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
223
|
-
width: 4px;
|
|
224
|
-
height: 50%;
|
|
225
|
-
border-radius: 5rpx;
|
|
226
|
-
margin-right: 4px;
|
|
227
|
-
animation: loading 0.5s infinite linear;
|
|
228
|
-
animation-delay: calc(0.1s * var(--d));
|
|
229
|
-
|
|
230
|
-
@keyframes loading {
|
|
231
|
-
0% {
|
|
232
|
-
background-image: linear-gradient(to right, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
233
|
-
height: 20%;
|
|
234
|
-
border-radius: 5rpx;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
50% {
|
|
238
|
-
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
239
|
-
height: 80%;
|
|
240
|
-
border-radius: 5rpx;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
100% {
|
|
244
|
-
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
245
|
-
height: 20%;
|
|
246
|
-
border-radius: 5rpx;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.voice-input-bar {
|
|
253
|
-
padding: 10rpx;
|
|
254
|
-
margin: 10rpx;
|
|
255
|
-
border-radius: 10rpx;
|
|
256
|
-
text-align: center;
|
|
257
|
-
box-shadow: $im-box-shadow;
|
|
258
|
-
|
|
259
|
-
&.recording {
|
|
260
|
-
background-color: $im-color-primary;
|
|
261
|
-
color: #fff;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.voice-input-window {
|
|
266
|
-
position: fixed;
|
|
267
|
-
left: 0;
|
|
268
|
-
right: 0;
|
|
269
|
-
height: 360rpx;
|
|
270
|
-
background-color: rgba(255, 255, 255, 0.95);
|
|
271
|
-
padding: 30rpx;
|
|
272
|
-
|
|
273
|
-
.icon-microphone {
|
|
274
|
-
text-align: center;
|
|
275
|
-
font-size: 80rpx;
|
|
276
|
-
padding: 10rpx;
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.rc-tip {
|
|
281
|
-
text-align: center;
|
|
282
|
-
font-size: $im-font-size-small;
|
|
283
|
-
color: $im-text-color-light;
|
|
284
|
-
margin-top: 20rpx;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.cancel-btn {
|
|
288
|
-
text-align: center;
|
|
289
|
-
margin-top: 40rpx;
|
|
290
|
-
height: 80rpx;
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.opt-tip {
|
|
295
|
-
text-align: center;
|
|
296
|
-
font-size: 30rpx;
|
|
297
|
-
padding: 20rpx;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.red {
|
|
301
|
-
color: $im-color-danger !important;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
}
|
|
1
|
+
<template>
|
|
2
|
+
<view class="voice-input">
|
|
3
|
+
<view class="voice-input-bar" :class="{ recording: recording }" id="voice-input-bar" @click.stop=""
|
|
4
|
+
@touchstart.prevent="onStartRecord" @touchmove.prevent="onTouchMove" @touchend.prevent="onEndRecord">
|
|
5
|
+
{{ recording ? '正在录音' : '长按 说话' }}
|
|
6
|
+
</view>
|
|
7
|
+
<view v-if="recording" class="voice-input-window" :style="recordWindowStyle">
|
|
8
|
+
<view class="rc-wave">
|
|
9
|
+
<text class="note" style="--d: 0"></text>
|
|
10
|
+
<text class="note" style="--d: 1"></text>
|
|
11
|
+
<text class="note" style="--d: 2"></text>
|
|
12
|
+
<text class="note" style="--d: 3"></text>
|
|
13
|
+
<text class="note" style="--d: 4"></text>
|
|
14
|
+
<text class="note" style="--d: 5"></text>
|
|
15
|
+
<text class="note" style="--d: 6"></text>
|
|
16
|
+
</view>
|
|
17
|
+
<view class="rc-tip">{{ recordTip }}</view>
|
|
18
|
+
<view class="cancel-btn" @click="onCancel">
|
|
19
|
+
<u-icon name="close" :color="moveToCancel ? 'red' : 'black'" :size="moveToCancel ? 45 : 40"></u-icon>
|
|
20
|
+
</view>
|
|
21
|
+
<view class="opt-tip" :class="moveToCancel ? 'red' : 'black'">{{ moveToCancel ? '松手取消' : '松手发送,上划取消' }}
|
|
22
|
+
</view>
|
|
23
|
+
</view>
|
|
24
|
+
|
|
25
|
+
</view>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { ref, computed, onUnmounted } from 'vue'
|
|
30
|
+
import { RecorderApp, RecorderH5 } from '../../index'
|
|
31
|
+
|
|
32
|
+
const recording = ref(false);
|
|
33
|
+
const moveToCancel = ref(false);
|
|
34
|
+
const recordBarTop = ref(0);
|
|
35
|
+
const druation = ref(0);
|
|
36
|
+
const rcTimer = ref<number | null>(null);
|
|
37
|
+
|
|
38
|
+
const getRecorder = () => {
|
|
39
|
+
// #ifdef H5
|
|
40
|
+
return new RecorderH5()
|
|
41
|
+
// #endif
|
|
42
|
+
|
|
43
|
+
// #ifndef H5
|
|
44
|
+
return new RecorderApp()
|
|
45
|
+
// #endif
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let rc = getRecorder()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
interface Emits {
|
|
52
|
+
(e: 'send', data: any): void;
|
|
53
|
+
}
|
|
54
|
+
const emit = defineEmits<Emits>();
|
|
55
|
+
|
|
56
|
+
onUnmounted(() => {
|
|
57
|
+
stopTimer();
|
|
58
|
+
recording.value = false;
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const onTouchMove = (e: any) => {
|
|
62
|
+
const moveY = e.touches[0].clientY;
|
|
63
|
+
moveToCancel.value = moveY < recordBarTop.value - 40;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const onCancel = () => {
|
|
67
|
+
if (recording.value) {
|
|
68
|
+
moveToCancel.value = true;
|
|
69
|
+
onEndRecord();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const onStartRecord = async () => {
|
|
74
|
+
/* 用户第一次使用语音会唤醒录音权限请求,此时会导致@touchend失效,
|
|
75
|
+
一直处于录音状态,这里允许用户再次点击发送语音并结束录音 */
|
|
76
|
+
if (recording.value) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log("开始录音");
|
|
80
|
+
moveToCancel.value = false;
|
|
81
|
+
// await initRecordBar();
|
|
82
|
+
|
|
83
|
+
// if (!rc.checkIsEnable()) {
|
|
84
|
+
// return;
|
|
85
|
+
// }
|
|
86
|
+
|
|
87
|
+
rc.start().then(() => {
|
|
88
|
+
recording.value = true;
|
|
89
|
+
console.log("开始录音成功");
|
|
90
|
+
// 开始计时
|
|
91
|
+
startTimer();
|
|
92
|
+
}).catch((e: any) => {
|
|
93
|
+
console.log("录音失败" + JSON.stringify(e));
|
|
94
|
+
uni.showToast({
|
|
95
|
+
title: "录音失败",
|
|
96
|
+
icon: "none"
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onEndRecord = () => {
|
|
102
|
+
if (!recording.value) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
recording.value = false;
|
|
106
|
+
// 停止计时
|
|
107
|
+
stopTimer();
|
|
108
|
+
// 停止录音
|
|
109
|
+
rc.close();
|
|
110
|
+
// 触屏位置是否移动到了取消区域
|
|
111
|
+
if (moveToCancel.value) {
|
|
112
|
+
console.log("录音取消");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// 大于1秒才发送
|
|
116
|
+
if (druation.value <= 1) {
|
|
117
|
+
uni.showToast({
|
|
118
|
+
title: "说话时间太短",
|
|
119
|
+
icon: 'none'
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
rc.upload().then((data: any) => {
|
|
125
|
+
emit("send", data);
|
|
126
|
+
}).catch((e: any) => {
|
|
127
|
+
console.error(e)
|
|
128
|
+
uni.showToast({
|
|
129
|
+
title: '录音上传失败',
|
|
130
|
+
icon: 'none'
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const startTimer = () => {
|
|
136
|
+
druation.value = 0;
|
|
137
|
+
stopTimer();
|
|
138
|
+
rcTimer.value = Number(setInterval(() => {
|
|
139
|
+
druation.value++;
|
|
140
|
+
// 大于60s,直接结束
|
|
141
|
+
if (druation.value >= 60) {
|
|
142
|
+
onEndRecord();
|
|
143
|
+
}
|
|
144
|
+
}, 1000));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const stopTimer = () => {
|
|
148
|
+
if (rcTimer.value) {
|
|
149
|
+
clearInterval(rcTimer.value);
|
|
150
|
+
rcTimer.value = null;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// const initRecordBar = () => {
|
|
155
|
+
// const query = uni.createSelectorQuery().in(getCurrentInstance());
|
|
156
|
+
// query.select('#voice-input-bar').boundingClientRect((rect: any) => {
|
|
157
|
+
// // 顶部高度位置
|
|
158
|
+
// recordBarTop.value = rect.top;
|
|
159
|
+
// }).exec();
|
|
160
|
+
// };
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 初始化录音条位置信息
|
|
164
|
+
* 兼容微信小程序和H5环境
|
|
165
|
+
*/
|
|
166
|
+
const initRecordBar = (): Promise<{ top: number; height: number; width: number }> => {
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
// 获取组件实例
|
|
169
|
+
const instance = getCurrentInstance()
|
|
170
|
+
if (!instance) {
|
|
171
|
+
reject(new Error('无法获取组件实例'))
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 创建选择器查询
|
|
176
|
+
const query = uni.createSelectorQuery().in(instance)
|
|
177
|
+
|
|
178
|
+
query.select('#voice-input-bar').boundingClientRect((rect: any) => {
|
|
179
|
+
if (rect) {
|
|
180
|
+
const positionInfo = {
|
|
181
|
+
top: rect.top || 0,
|
|
182
|
+
height: rect.height || 0,
|
|
183
|
+
width: rect.width || 0,
|
|
184
|
+
left: rect.left || 0,
|
|
185
|
+
right: rect.right || 0,
|
|
186
|
+
bottom: rect.bottom || 0
|
|
187
|
+
}
|
|
188
|
+
resolve(positionInfo)
|
|
189
|
+
} else {
|
|
190
|
+
reject(new Error('无法获取录音条位置信息'))
|
|
191
|
+
}
|
|
192
|
+
}).exec()
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const recordWindowStyle = computed(() => {
|
|
197
|
+
const windowHeight = uni.getWindowInfo().windowHeight;
|
|
198
|
+
const bottom = windowHeight - recordBarTop.value + 12;
|
|
199
|
+
return `bottom:${bottom}px;`;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const recordTip = computed(() => {
|
|
203
|
+
if (druation.value > 50) {
|
|
204
|
+
return `${60 - druation.value}s后将停止录音`;
|
|
205
|
+
}
|
|
206
|
+
return `录音时长:${druation.value}s`;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<style lang="scss" scoped>
|
|
213
|
+
.voice-input {
|
|
214
|
+
.rc-wave {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: flex-end;
|
|
217
|
+
justify-content: center;
|
|
218
|
+
position: relative;
|
|
219
|
+
height: 80rpx;
|
|
220
|
+
|
|
221
|
+
.note {
|
|
222
|
+
background: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
223
|
+
width: 4px;
|
|
224
|
+
height: 50%;
|
|
225
|
+
border-radius: 5rpx;
|
|
226
|
+
margin-right: 4px;
|
|
227
|
+
animation: loading 0.5s infinite linear;
|
|
228
|
+
animation-delay: calc(0.1s * var(--d));
|
|
229
|
+
|
|
230
|
+
@keyframes loading {
|
|
231
|
+
0% {
|
|
232
|
+
background-image: linear-gradient(to right, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
233
|
+
height: 20%;
|
|
234
|
+
border-radius: 5rpx;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
50% {
|
|
238
|
+
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
239
|
+
height: 80%;
|
|
240
|
+
border-radius: 5rpx;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
100% {
|
|
244
|
+
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
|
|
245
|
+
height: 20%;
|
|
246
|
+
border-radius: 5rpx;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.voice-input-bar {
|
|
253
|
+
padding: 10rpx;
|
|
254
|
+
margin: 10rpx;
|
|
255
|
+
border-radius: 10rpx;
|
|
256
|
+
text-align: center;
|
|
257
|
+
box-shadow: $im-box-shadow;
|
|
258
|
+
|
|
259
|
+
&.recording {
|
|
260
|
+
background-color: $im-color-primary;
|
|
261
|
+
color: #fff;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.voice-input-window {
|
|
266
|
+
position: fixed;
|
|
267
|
+
left: 0;
|
|
268
|
+
right: 0;
|
|
269
|
+
height: 360rpx;
|
|
270
|
+
background-color: rgba(255, 255, 255, 0.95);
|
|
271
|
+
padding: 30rpx;
|
|
272
|
+
|
|
273
|
+
.icon-microphone {
|
|
274
|
+
text-align: center;
|
|
275
|
+
font-size: 80rpx;
|
|
276
|
+
padding: 10rpx;
|
|
277
|
+
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.rc-tip {
|
|
281
|
+
text-align: center;
|
|
282
|
+
font-size: $im-font-size-small;
|
|
283
|
+
color: $im-text-color-light;
|
|
284
|
+
margin-top: 20rpx;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.cancel-btn {
|
|
288
|
+
text-align: center;
|
|
289
|
+
margin-top: 40rpx;
|
|
290
|
+
height: 80rpx;
|
|
291
|
+
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.opt-tip {
|
|
295
|
+
text-align: center;
|
|
296
|
+
font-size: 30rpx;
|
|
297
|
+
padding: 20rpx;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.red {
|
|
301
|
+
color: $im-color-danger !important;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
306
|
</style>
|
package/libs/index.ts
CHANGED