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,309 +1,309 @@
1
- <template>
2
- <view v-if="visible" class="file-upload">
3
- <u-upload ref="uploadRef" :max-count="props.maxCount" :max-size="props.maxSize" :size-type="props.sizeType"
4
- :source-type="props.sourceType" :deletable="props.deletable" :preview-full-image="props.previewFullImage"
5
- :multiple="props.multiple" :disabled="props.disabled" :custom-btn="props.customBtn"
6
- :show-progress="props.showProgress" :upload-text="props.uploadText" :width="props.width"
7
- :height="props.height" @after-read="onAfterRead" @delete="onDelete" @oversize="onOversize">
8
- <!-- 自定义上传按钮 -->
9
- <slot></slot>
10
- </u-upload>
11
- </view>
12
- </template>
13
-
14
- <script setup lang="ts">
15
- import { ref, reactive, watch } from 'vue'
16
-
17
- // 定义类型
18
- interface UploadFile {
19
- url: string
20
- name?: string
21
- size?: number
22
- type?: string
23
- progress?: number
24
- status?: 'uploading' | 'success' | 'error'
25
- response?: any
26
- }
27
-
28
- interface AfterReadFile {
29
- name: string
30
- size: number
31
- thumb: string
32
- type: string
33
- url: string
34
- }
35
-
36
- interface UploadProps {
37
- // 文件列表
38
- modelValue?: UploadFile[]
39
- // 最大上传数量
40
- maxCount?: number
41
- // 单个文件大小限制(字节)
42
- maxSize?: number
43
- // 文件大小选择 ['original', 'compressed']
44
- sizeType?: string[]
45
- // 文件来源 ['album', 'camera']
46
- sourceType?: string[]
47
- // 是否显示删除按钮
48
- deletable?: boolean
49
- // 是否预览完整图片
50
- previewFullImage?: boolean
51
- // 是否多选
52
- multiple?: boolean
53
- // 是否禁用
54
- disabled?: boolean
55
- // 是否使用自定义按钮
56
- customBtn?: boolean
57
- // 是否显示进度条
58
- showProgress?: boolean
59
- // 上传文字提示
60
- uploadText?: string
61
- // 图片宽度
62
- width?: number | string
63
- // 图片高度
64
- height?: number | string
65
- // 上传地址
66
- action?: string
67
- // 上传字段名
68
- name?: string
69
- // 请求头
70
- headers?: Record<string, string>
71
- // 附加数据
72
- data?: Record<string, any>
73
- // 是否自动上传
74
- autoUpload?: boolean,
75
- // 令牌
76
- token?: string
77
- }
78
-
79
- interface UploadEmits {
80
- (e: 'update:modelValue', files: UploadFile[]): void
81
- (e: 'change', files: UploadFile[]): void
82
- (e: 'before', file: UploadFile): void
83
- (e: 'success', file: UploadFile, response: any): void
84
- (e: 'fail', file: UploadFile, error: any): void
85
- (e: 'progress', file: UploadFile, progress: number): void
86
- (e: 'delete', file: UploadFile, index: number): void
87
- (e: 'oversize', file: UploadFile): void
88
- }
89
-
90
- // Props 和 Emits
91
- const props = withDefaults(defineProps<UploadProps>(), {
92
- modelValue: () => [],
93
- maxCount: 9,
94
- maxSize: 10 * 1024 * 1024, // 10MB
95
- sizeType: () => ['original', 'compressed'],
96
- sourceType: () => ['album', 'camera'],
97
- deletable: true,
98
- previewFullImage: true,
99
- multiple: true,
100
- disabled: false,
101
- customBtn: false,
102
- showProgress: true,
103
- uploadText: '',
104
- width: 50,
105
- height: 50,
106
- action: '', // https://example.com/file/upload
107
- name: 'file',
108
- headers: () => ({
109
- 'Authorization': `Bearer xxx`,
110
- 'AccessToken': 'xxx',
111
- 'Content-Type': 'multipart/form-data'
112
- }),
113
- data: () => ({}),
114
- autoUpload: true,
115
- token: ''
116
- })
117
-
118
- const emit = defineEmits<UploadEmits>()
119
-
120
- // 响应式数据
121
- const uploadRef = ref()
122
- const fileList = ref<UploadFile[]>([])
123
- const uploadingFiles = reactive(new Map<string, UploadFile>())
124
- const fileMap = ref(new Map<string, any>());
125
- const visible = ref(true)
126
-
127
- // 监听 modelValue 变化
128
- watch(() => props.modelValue, (newVal) => {
129
- fileList.value = [...newVal]
130
- }, { immediate: true, deep: true })
131
-
132
- // 文件选择后读取
133
- const onAfterRead = async ({ file }: any) => {
134
- const files: AfterReadFile[] = file
135
-
136
- for (const item of files) {
137
- // before
138
- if (!fileMap.value.has(item.name)) {
139
- emit('before', item)
140
- fileMap.value.set(item.name, item);
141
- }
142
-
143
- const uploadFile: UploadFile = {
144
- url: item.url,
145
- name: item.name,
146
- size: item.size,
147
- type: item.type,
148
- progress: 0,
149
- status: 'uploading'
150
- }
151
-
152
- const fileId = generateFileId()
153
- uploadingFiles.set(fileId, uploadFile)
154
-
155
- // 添加到文件列表
156
- fileList.value.push(uploadFile)
157
- emitFileChange()
158
-
159
- if (props.autoUpload) {
160
- await uploadFileToServer(item, uploadFile, fileId)
161
- } else {
162
- uploadFile.status = 'success'
163
- uploadingFiles.delete(fileId)
164
- }
165
- }
166
- }
167
-
168
- // 上传文件到服务器
169
- const uploadFileToServer = (file: AfterReadFile, uploadFile: UploadFile, fileId: string): Promise<void> => {
170
- return new Promise((resolve) => {
171
- if (!props.action) {
172
- // 如果没有上传地址,直接标记为成功
173
- uploadFile.status = 'success'
174
- uploadFile.response = { message: 'No upload action provided' }
175
- uploadingFiles.delete(fileId)
176
- emit('success', file, uploadFile)
177
- resolve()
178
- return
179
- }
180
-
181
- const headers = {
182
- 'Authorization': `Bearer ${props.token}`,
183
- 'AccessToken': props.token,
184
- // 'Content-Type': 'multipart/form-data' // Commented by Joncky becauce of error "Failed to read the request form. Missing content-type boundary."
185
- }
186
-
187
- uni.uploadFile({
188
- url: props.action,
189
- filePath: file.url,
190
- name: props.name,
191
- header: headers,
192
- // formData: props.data,
193
- success: (res) => {
194
- uploadFile.response = res
195
-
196
- try {
197
- uploadFile.status = 'success'
198
- uploadFile.progress = 100
199
-
200
-
201
- fileList.value.splice(fileList.value.length - 1, 1, {
202
- ...file,
203
- ...res,
204
- status: 'success'
205
- })
206
-
207
- const data = JSON.parse(res.data)
208
- let url = data.data
209
- if (!url && data.result) {
210
- url = data.result?.url
211
- }
212
-
213
- emit('success', file, url)
214
- } catch (error) {
215
- uploadFile.status = 'error'
216
- console.error(error)
217
- emit('fail', file, error)
218
- }
219
-
220
- uploadingFiles.delete(fileId)
221
- resolve()
222
- },
223
- fail: (error) => {
224
- uploadFile.status = 'error'
225
- console.error(error)
226
- uploadingFiles.delete(fileId)
227
- emit('fail', file, error)
228
- resolve()
229
- }
230
- })
231
- })
232
- }
233
-
234
- // 删除文件
235
- const onDelete = (event: any) => {
236
- const { index } = event
237
- const deletedFile = fileList.value[index]
238
-
239
- fileList.value.splice(index, 1)
240
- emitFileChange()
241
- emit('delete', deletedFile, index)
242
- }
243
-
244
- // 文件大小超出限制
245
- const onOversize = (event: any) => {
246
- const file = event
247
- emit('oversize', file)
248
- uni.showToast({
249
- title: `文件大小不能超过 ${props.maxSize / 1024 / 1024}MB`,
250
- icon: 'none'
251
- })
252
- }
253
-
254
- // 生成文件ID
255
- const generateFileId = (): string => {
256
- return Date.now() + '-' + Math.random().toString(36).substr(2, 9)
257
- }
258
-
259
- // 触发文件变化事件
260
- const emitFileChange = () => {
261
- emit('update:modelValue', fileList.value)
262
- emit('change', fileList.value)
263
- }
264
-
265
- // 手动上传
266
- const submit = (): Promise<UploadFile[]> => {
267
- return new Promise((resolve) => {
268
- const pendingFiles = fileList.value.filter(file => file.status !== 'success')
269
-
270
- if (pendingFiles.length === 0) {
271
- resolve(fileList.value)
272
- return
273
- }
274
-
275
- // 这里可以实现批量上传逻辑
276
- Promise.all(pendingFiles.map(file => {
277
- // 重新上传逻辑
278
- return Promise.resolve(file)
279
- })).then(() => {
280
- resolve(fileList.value)
281
- })
282
- })
283
- }
284
-
285
- // 清空文件列表
286
- const clearFiles = () => {
287
- fileList.value = []
288
- uploadingFiles.clear()
289
- emitFileChange()
290
- }
291
-
292
- // 获取文件列表
293
- const getFiles = (): UploadFile[] => {
294
- return fileList.value
295
- }
296
-
297
- // 隐藏
298
- const hide = () => {
299
- visible.value = false
300
- }
301
-
302
- // 暴露方法给父组件
303
- defineExpose({
304
- submit,
305
- clearFiles,
306
- getFiles,
307
- hide
308
- })
309
- </script>
1
+ <template>
2
+ <view v-if="visible" class="file-upload">
3
+ <u-upload ref="uploadRef" :max-count="props.maxCount" :max-size="props.maxSize" :size-type="props.sizeType"
4
+ :source-type="props.sourceType" :deletable="props.deletable" :preview-full-image="props.previewFullImage"
5
+ :multiple="props.multiple" :disabled="props.disabled" :custom-btn="props.customBtn"
6
+ :show-progress="props.showProgress" :upload-text="props.uploadText" :width="props.width"
7
+ :height="props.height" @after-read="onAfterRead" @delete="onDelete" @oversize="onOversize">
8
+ <!-- 自定义上传按钮 -->
9
+ <slot></slot>
10
+ </u-upload>
11
+ </view>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { ref, reactive, watch } from 'vue'
16
+
17
+ // 定义类型
18
+ interface UploadFile {
19
+ url: string
20
+ name?: string
21
+ size?: number
22
+ type?: string
23
+ progress?: number
24
+ status?: 'uploading' | 'success' | 'error'
25
+ response?: any
26
+ }
27
+
28
+ interface AfterReadFile {
29
+ name: string
30
+ size: number
31
+ thumb: string
32
+ type: string
33
+ url: string
34
+ }
35
+
36
+ interface UploadProps {
37
+ // 文件列表
38
+ modelValue?: UploadFile[]
39
+ // 最大上传数量
40
+ maxCount?: number
41
+ // 单个文件大小限制(字节)
42
+ maxSize?: number
43
+ // 文件大小选择 ['original', 'compressed']
44
+ sizeType?: string[]
45
+ // 文件来源 ['album', 'camera']
46
+ sourceType?: string[]
47
+ // 是否显示删除按钮
48
+ deletable?: boolean
49
+ // 是否预览完整图片
50
+ previewFullImage?: boolean
51
+ // 是否多选
52
+ multiple?: boolean
53
+ // 是否禁用
54
+ disabled?: boolean
55
+ // 是否使用自定义按钮
56
+ customBtn?: boolean
57
+ // 是否显示进度条
58
+ showProgress?: boolean
59
+ // 上传文字提示
60
+ uploadText?: string
61
+ // 图片宽度
62
+ width?: number | string
63
+ // 图片高度
64
+ height?: number | string
65
+ // 上传地址
66
+ action?: string
67
+ // 上传字段名
68
+ name?: string
69
+ // 请求头
70
+ headers?: Record<string, string>
71
+ // 附加数据
72
+ data?: Record<string, any>
73
+ // 是否自动上传
74
+ autoUpload?: boolean,
75
+ // 令牌
76
+ token?: string
77
+ }
78
+
79
+ interface UploadEmits {
80
+ (e: 'update:modelValue', files: UploadFile[]): void
81
+ (e: 'change', files: UploadFile[]): void
82
+ (e: 'before', file: UploadFile): void
83
+ (e: 'success', file: UploadFile, response: any): void
84
+ (e: 'fail', file: UploadFile, error: any): void
85
+ (e: 'progress', file: UploadFile, progress: number): void
86
+ (e: 'delete', file: UploadFile, index: number): void
87
+ (e: 'oversize', file: UploadFile): void
88
+ }
89
+
90
+ // Props 和 Emits
91
+ const props = withDefaults(defineProps<UploadProps>(), {
92
+ modelValue: () => [],
93
+ maxCount: 9,
94
+ maxSize: 10 * 1024 * 1024, // 10MB
95
+ sizeType: () => ['original', 'compressed'],
96
+ sourceType: () => ['album', 'camera'],
97
+ deletable: true,
98
+ previewFullImage: true,
99
+ multiple: true,
100
+ disabled: false,
101
+ customBtn: false,
102
+ showProgress: true,
103
+ uploadText: '',
104
+ width: 50,
105
+ height: 50,
106
+ action: '', // https://example.com/file/upload
107
+ name: 'file',
108
+ headers: () => ({
109
+ 'Authorization': `Bearer xxx`,
110
+ 'AccessToken': 'xxx',
111
+ 'Content-Type': 'multipart/form-data'
112
+ }),
113
+ data: () => ({}),
114
+ autoUpload: true,
115
+ token: ''
116
+ })
117
+
118
+ const emit = defineEmits<UploadEmits>()
119
+
120
+ // 响应式数据
121
+ const uploadRef = ref()
122
+ const fileList = ref<UploadFile[]>([])
123
+ const uploadingFiles = reactive(new Map<string, UploadFile>())
124
+ const fileMap = ref(new Map<string, any>());
125
+ const visible = ref(true)
126
+
127
+ // 监听 modelValue 变化
128
+ watch(() => props.modelValue, (newVal) => {
129
+ fileList.value = [...newVal]
130
+ }, { immediate: true, deep: true })
131
+
132
+ // 文件选择后读取
133
+ const onAfterRead = async ({ file }: any) => {
134
+ const files: AfterReadFile[] = file
135
+
136
+ for (const item of files) {
137
+ // before
138
+ if (!fileMap.value.has(item.name)) {
139
+ emit('before', item)
140
+ fileMap.value.set(item.name, item);
141
+ }
142
+
143
+ const uploadFile: UploadFile = {
144
+ url: item.url,
145
+ name: item.name,
146
+ size: item.size,
147
+ type: item.type,
148
+ progress: 0,
149
+ status: 'uploading'
150
+ }
151
+
152
+ const fileId = generateFileId()
153
+ uploadingFiles.set(fileId, uploadFile)
154
+
155
+ // 添加到文件列表
156
+ fileList.value.push(uploadFile)
157
+ emitFileChange()
158
+
159
+ if (props.autoUpload) {
160
+ await uploadFileToServer(item, uploadFile, fileId)
161
+ } else {
162
+ uploadFile.status = 'success'
163
+ uploadingFiles.delete(fileId)
164
+ }
165
+ }
166
+ }
167
+
168
+ // 上传文件到服务器
169
+ const uploadFileToServer = (file: AfterReadFile, uploadFile: UploadFile, fileId: string): Promise<void> => {
170
+ return new Promise((resolve) => {
171
+ if (!props.action) {
172
+ // 如果没有上传地址,直接标记为成功
173
+ uploadFile.status = 'success'
174
+ uploadFile.response = { message: 'No upload action provided' }
175
+ uploadingFiles.delete(fileId)
176
+ emit('success', file, uploadFile)
177
+ resolve()
178
+ return
179
+ }
180
+
181
+ const headers = {
182
+ 'Authorization': `Bearer ${props.token}`,
183
+ 'AccessToken': props.token,
184
+ // 'Content-Type': 'multipart/form-data' // Commented by Joncky becauce of error "Failed to read the request form. Missing content-type boundary."
185
+ }
186
+
187
+ uni.uploadFile({
188
+ url: props.action,
189
+ filePath: file.url,
190
+ name: props.name,
191
+ header: headers,
192
+ // formData: props.data,
193
+ success: (res) => {
194
+ uploadFile.response = res
195
+
196
+ try {
197
+ uploadFile.status = 'success'
198
+ uploadFile.progress = 100
199
+
200
+
201
+ fileList.value.splice(fileList.value.length - 1, 1, {
202
+ ...file,
203
+ ...res,
204
+ status: 'success'
205
+ })
206
+
207
+ const data = JSON.parse(res.data)
208
+ let url = data.data
209
+ if (!url && data.result) {
210
+ url = data.result?.url
211
+ }
212
+
213
+ emit('success', file, url)
214
+ } catch (error) {
215
+ uploadFile.status = 'error'
216
+ console.error(error)
217
+ emit('fail', file, error)
218
+ }
219
+
220
+ uploadingFiles.delete(fileId)
221
+ resolve()
222
+ },
223
+ fail: (error) => {
224
+ uploadFile.status = 'error'
225
+ console.error(error)
226
+ uploadingFiles.delete(fileId)
227
+ emit('fail', file, error)
228
+ resolve()
229
+ }
230
+ })
231
+ })
232
+ }
233
+
234
+ // 删除文件
235
+ const onDelete = (event: any) => {
236
+ const { index } = event
237
+ const deletedFile = fileList.value[index]
238
+
239
+ fileList.value.splice(index, 1)
240
+ emitFileChange()
241
+ emit('delete', deletedFile, index)
242
+ }
243
+
244
+ // 文件大小超出限制
245
+ const onOversize = (event: any) => {
246
+ const file = event
247
+ emit('oversize', file)
248
+ uni.showToast({
249
+ title: `文件大小不能超过 ${props.maxSize / 1024 / 1024}MB`,
250
+ icon: 'none'
251
+ })
252
+ }
253
+
254
+ // 生成文件ID
255
+ const generateFileId = (): string => {
256
+ return Date.now() + '-' + Math.random().toString(36).substr(2, 9)
257
+ }
258
+
259
+ // 触发文件变化事件
260
+ const emitFileChange = () => {
261
+ emit('update:modelValue', fileList.value)
262
+ emit('change', fileList.value)
263
+ }
264
+
265
+ // 手动上传
266
+ const submit = (): Promise<UploadFile[]> => {
267
+ return new Promise((resolve) => {
268
+ const pendingFiles = fileList.value.filter(file => file.status !== 'success')
269
+
270
+ if (pendingFiles.length === 0) {
271
+ resolve(fileList.value)
272
+ return
273
+ }
274
+
275
+ // 这里可以实现批量上传逻辑
276
+ Promise.all(pendingFiles.map(file => {
277
+ // 重新上传逻辑
278
+ return Promise.resolve(file)
279
+ })).then(() => {
280
+ resolve(fileList.value)
281
+ })
282
+ })
283
+ }
284
+
285
+ // 清空文件列表
286
+ const clearFiles = () => {
287
+ fileList.value = []
288
+ uploadingFiles.clear()
289
+ emitFileChange()
290
+ }
291
+
292
+ // 获取文件列表
293
+ const getFiles = (): UploadFile[] => {
294
+ return fileList.value
295
+ }
296
+
297
+ // 隐藏
298
+ const hide = () => {
299
+ visible.value = false
300
+ }
301
+
302
+ // 暴露方法给父组件
303
+ defineExpose({
304
+ submit,
305
+ clearFiles,
306
+ getFiles,
307
+ hide
308
+ })
309
+ </script>