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,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>
|