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.
Files changed (40) hide show
  1. package/components/im-avatar/im-avatar.vue +121 -121
  2. package/components/im-button/im-button.vue +630 -626
  3. package/components/im-cell/im-cell.vue +10 -15
  4. package/components/im-cell-group/im-cell-group.vue +16 -16
  5. package/components/im-chat-item/im-chat-item.vue +213 -213
  6. package/components/im-context-menu/im-context-menu.vue +138 -138
  7. package/components/im-file-upload/im-file-upload.vue +309 -309
  8. package/components/im-friend-item/im-friend-item.vue +82 -75
  9. package/components/im-group-item/im-group-item.vue +62 -62
  10. package/components/im-group-member-selector/im-group-member-selector.vue +202 -202
  11. package/components/im-group-rtc-join/im-group-rtc-join.vue +112 -112
  12. package/components/im-image-upload/im-image-upload.vue +94 -94
  13. package/components/im-loading/im-loading.vue +64 -64
  14. package/components/im-mention-picker/im-mention-picker.vue +191 -191
  15. package/components/im-message-item/im-message-item.vue +555 -555
  16. package/components/im-nav-bar/im-nav-bar.vue +98 -98
  17. package/components/im-read-receipt/im-read-receipt.vue +174 -174
  18. package/components/im-virtual-list/im-virtual-list.vue +52 -51
  19. package/components/im-voice-input/im-voice-input.vue +305 -305
  20. package/libs/index.ts +2 -3
  21. package/package.json +58 -58
  22. package/styles/button.scss +1 -1
  23. package/theme.scss +61 -62
  24. package/types/components.d.ts +0 -1
  25. package/types/index.d.ts +94 -94
  26. package/types/libs/index.d.ts +206 -204
  27. package/types/utils/datetime.d.ts +9 -9
  28. package/types/utils/dom.d.ts +11 -11
  29. package/types/utils/emoji.d.ts +8 -8
  30. package/types/utils/enums.d.ts +73 -73
  31. package/types/utils/messageType.d.ts +35 -35
  32. package/types/utils/recorderApp.d.ts +9 -9
  33. package/types/utils/recorderH5.d.ts +9 -9
  34. package/types/utils/requester.d.ts +15 -15
  35. package/types/utils/url.d.ts +5 -5
  36. package/types/utils/useDynamicRefs.d.ts +9 -9
  37. package/types/utils/websocket.d.ts +34 -34
  38. package/utils/enums.js +1 -1
  39. package/components/im-virtual-scroller/im-virtual-scroller.vue +0 -54
  40. 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
@@ -1,7 +1,5 @@
1
1
  import type { RTC_STATE, MESSAGE_TYPE } from '../types/utils/enums.d.ts'
2
2
 
3
-
4
-
5
3
  /**
6
4
  * 通话模式类型
7
5
  */
@@ -227,4 +225,5 @@ export interface Response<T> {
227
225
  message: string
228
226
  result: T
229
227
  success: boolean
230
- }
228
+ }
229
+