im-ui-mobile 0.1.14 → 0.1.16

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.
@@ -153,6 +153,9 @@
153
153
  import { ref, computed, watch } from 'vue'
154
154
  import ImButton from '../im-button/im-button.vue'
155
155
  import { chooseFile } from './utils/file-chooser'
156
+ import { isFileTypeAccepted } from './utils/file-validator'
157
+ import { createFileFromUniFile } from './utils/file-adapter'
158
+ import { universalUploadFile } from './utils/upload'
156
159
 
157
160
  // 定义文件类型
158
161
  interface UploadFile {
@@ -167,7 +170,7 @@ interface UploadFile {
167
170
  response?: any
168
171
  error?: Error
169
172
  rawFile?: any
170
- file?: File
173
+ file?: Object
171
174
  }
172
175
 
173
176
  // 定义 Props
@@ -207,7 +210,7 @@ interface Props {
207
210
  multiple?: boolean
208
211
  maxCount?: number
209
212
  maxSize?: number // 单位:字节
210
- beforeUpload?: (file: File) => boolean | Promise<boolean> // 上传前钩子
213
+ beforeUpload?: (file: Object) => boolean | Promise<boolean> // 上传前钩子
211
214
 
212
215
  // 上传配置
213
216
  action?: string
@@ -233,21 +236,21 @@ interface Props {
233
236
  readonly?: boolean
234
237
 
235
238
  // 自定义上传
236
- customRequest?: (file: File, onProgress: (percent: number) => void) => Promise<any>
239
+ customRequest?: (file: Object, onProgress: (percent: number) => void) => Promise<any>
237
240
  }
238
241
 
239
242
  // 定义 Emits
240
243
  interface Emits {
241
244
  (e: 'update:modelValue', files: UploadFile[]): void
242
245
  (e: 'change', files: UploadFile[]): void
243
- (e: 'select', file: File): void
246
+ (e: 'select', file: Object): void
244
247
  (e: 'upload', file: UploadFile): void
245
248
  (e: 'success', response: any, file: UploadFile): void
246
249
  (e: 'error', error: Error, file: UploadFile): void
247
250
  (e: 'progress', percent: number, file: UploadFile): void
248
251
  (e: 'remove', file: UploadFile, index: number): void
249
252
  (e: 'preview', file: UploadFile): void
250
- (e: 'exceed', files: File[]): void
253
+ (e: 'exceed', files: Object[]): void
251
254
  (e: 'before-upload', file: UploadFile): void
252
255
  (e: 'after-upload', file: UploadFile): void
253
256
  }
@@ -449,7 +452,7 @@ const processFile = async (uniFile: any) => {
449
452
  }
450
453
 
451
454
  // 检查文件类型
452
- if (!isFileTypeAccepted(uniFile)) {
455
+ if (!isFileTypeAccepted(uniFile, props.accept)) {
453
456
  uni.showToast({
454
457
  title: '不支持的文件类型',
455
458
  icon: 'none'
@@ -503,61 +506,61 @@ const processFile = async (uniFile: any) => {
503
506
  }
504
507
  }
505
508
 
506
- // 检查文件类型是否被接受
507
- const isFileTypeAccepted = (uniFile: any): boolean => {
508
- if (props.accept === '*') return true
509
+ // // 检查文件类型是否被接受
510
+ // const isFileTypeAccepted = (uniFile: any): boolean => {
511
+ // if (props.accept === '*') return true
509
512
 
510
- const acceptTypes = props.accept.split(',').map(type => type.trim())
513
+ // const acceptTypes = props.accept.split(',').map(type => type.trim())
511
514
 
512
- for (const acceptType of acceptTypes) {
513
- if (acceptType === '*') return true
515
+ // for (const acceptType of acceptTypes) {
516
+ // if (acceptType === '*') return true
514
517
 
515
- // 检查 MIME 类型
516
- if (acceptType.endsWith('/*')) {
517
- const category = acceptType.split('/')[0]
518
- if (uniFile.type?.startsWith(category + '/')) {
519
- return true
520
- }
521
- }
518
+ // // 检查 MIME 类型
519
+ // if (acceptType.endsWith('/*')) {
520
+ // const category = acceptType.split('/')[0]
521
+ // if (uniFile.type?.startsWith(category + '/')) {
522
+ // return true
523
+ // }
524
+ // }
522
525
 
523
- // 检查扩展名
524
- if (acceptType.startsWith('.')) {
525
- const ext = acceptType.substring(1).toLowerCase()
526
- const fileName = uniFile.name.toLowerCase()
527
- if (fileName.endsWith('.' + ext)) {
528
- return true
529
- }
530
- }
526
+ // // 检查扩展名
527
+ // if (acceptType.startsWith('.')) {
528
+ // const ext = acceptType.substring(1).toLowerCase()
529
+ // const fileName = uniFile.name.toLowerCase()
530
+ // if (fileName.endsWith('.' + ext)) {
531
+ // return true
532
+ // }
533
+ // }
531
534
 
532
- // 检查完整 MIME 类型
533
- if (uniFile.type === acceptType) {
534
- return true
535
- }
536
- }
535
+ // // 检查完整 MIME 类型
536
+ // if (uniFile.type === acceptType) {
537
+ // return true
538
+ // }
539
+ // }
537
540
 
538
- return false
539
- }
541
+ // return false
542
+ // }
540
543
 
541
544
  // 从 uniapp 文件创建 File 对象
542
- const createFileFromUniFile = (uniFile: any): File => {
543
- const file = new File([], uniFile.name, {
544
- type: uniFile.type || 'application/octet-stream',
545
- lastModified: uniFile.lastModified || Date.now()
546
- })
547
-
548
- Object.defineProperties(file, {
549
- size: {
550
- value: uniFile.size,
551
- writable: false
552
- },
553
- path: {
554
- value: uniFile.path,
555
- writable: false
556
- }
557
- })
558
-
559
- return file
560
- }
545
+ // const createFileFromUniFile = (uniFile: any): File => {
546
+ // const file = new File([], uniFile.name, {
547
+ // type: uniFile.type || 'application/octet-stream',
548
+ // lastModified: uniFile.lastModified || Date.now()
549
+ // })
550
+
551
+ // Object.defineProperties(file, {
552
+ // size: {
553
+ // value: uniFile.size,
554
+ // writable: false
555
+ // },
556
+ // path: {
557
+ // value: uniFile.path,
558
+ // writable: false
559
+ // }
560
+ // })
561
+
562
+ // return file
563
+ // }
561
564
 
562
565
  // 开始上传
563
566
  const startUpload = async (uploadFile: UploadFile) => {
@@ -619,59 +622,51 @@ const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
619
622
  'AccessToken': props.accessToken,
620
623
  }
621
624
 
622
- return new Promise((resolve, reject) => {
623
- uni.uploadFile({
624
- url: props.action!,
625
- filePath: uploadFile.rawFile.path,
626
- name: props.name,
627
- formData: {
628
- ...props.data,
629
- filename: uploadFile.name,
630
- size: uploadFile.size,
631
- type: uploadFile.type
632
- },
633
- header: headers,
634
- timeout: props.timeout,
635
- withCredentials: props.withCredentials,
636
- // onProgressUpdate: (res: any) => {
637
- // console.log('res.progress',res.progress)
638
- // uploadFile.progress = res.progress
639
- // emit('progress', res.progress, uploadFile)
640
- // updateFileList()
641
- // },
642
- success: (res: any) => {
643
- try {
644
- const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
645
- resolve(data)
646
- } catch (e: any) {
647
- reject(e)
648
- }
649
- },
650
- complete: () => {
651
- // 上传完成
652
- },
653
- fail: (e: any) => {
654
- reject(e)
655
- }
656
- })
657
-
658
- // // 监听上传进度
659
- // task.onProgressUpdate = (res:any) => {
660
- // uploadFile.progress = res.progress
661
- // emit('progress', res.progress, uploadFile)
662
- // updateFileList()
663
- // }
664
-
665
- // 保存任务用于取消
666
- // uploadTasks.set(uploadFile.uid, task)
667
-
668
- // // 上传完成后清理
669
- // task.then(() => {
670
- // uploadTasks.delete(uploadFile.uid)
671
- // }).catch(() => {
672
- // uploadTasks.delete(uploadFile.uid)
673
- // })
625
+ return universalUploadFile({
626
+ url: props.action!,
627
+ filePath: uploadFile.rawFile.path,
628
+ name: props.name,
629
+ formData: {
630
+ ...props.data,
631
+ filename: uploadFile.name,
632
+ size: uploadFile.size,
633
+ type: uploadFile.type
634
+ },
635
+ header: headers,
636
+ timeout: props.timeout,
637
+ withCredentials: props.withCredentials
674
638
  })
639
+
640
+ // return new Promise((resolve, reject) => {
641
+ // uni.uploadFile({
642
+ // url: props.action!,
643
+ // filePath: uploadFile.rawFile.path,
644
+ // name: props.name,
645
+ // formData: {
646
+ // ...props.data,
647
+ // filename: uploadFile.name,
648
+ // size: uploadFile.size,
649
+ // type: uploadFile.type
650
+ // },
651
+ // header: headers,
652
+ // timeout: props.timeout,
653
+ // withCredentials: props.withCredentials,
654
+ // success: (res: any) => {
655
+ // try {
656
+ // const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
657
+ // resolve(data)
658
+ // } catch (e: any) {
659
+ // reject(e)
660
+ // }
661
+ // },
662
+ // complete: () => {
663
+ // // 上传完成
664
+ // },
665
+ // fail: (e: any) => {
666
+ // reject(e)
667
+ // }
668
+ // })
669
+ // })
675
670
  }
676
671
 
677
672
  // 更新文件列表
@@ -0,0 +1,214 @@
1
+ // utils/file-adapter.js
2
+
3
+ /**
4
+ * 创建统一的文件对象(多平台兼容)
5
+ * @param {Object} rawFile - 原始文件数据
6
+ * @returns {Object} 统一格式的文件对象
7
+ */
8
+ export const createFileFromUniFile = (rawFile) => {
9
+ // 检查环境
10
+ // #ifdef H5
11
+ if (rawFile instanceof File) {
12
+ return rawFile
13
+ }
14
+ // #endif
15
+
16
+ const path = rawFile.path || rawFile.tempFilePath || rawFile.apFilePaths?.[0]
17
+ const name = rawFile.name || getFileNameFromPath(path)
18
+ const size = rawFile.size || 0
19
+ const type = rawFile.type || getFileTypeFromPath(name)
20
+
21
+ // 创建统一的文件对象
22
+ const unifiedFile = {
23
+ // 基础属性
24
+ path,
25
+ name,
26
+ size,
27
+ type,
28
+ lastModified: rawFile.lastModified || Date.now(),
29
+
30
+ // 原始数据
31
+ raw: rawFile,
32
+
33
+ // 方法(模拟 File 对象的部分行为)
34
+ slice(start, end) {
35
+ // 小程序环境不支持实际切片,返回一个标记对象
36
+ return {
37
+ path: this.path,
38
+ name: this.name,
39
+ size: end - start,
40
+ type: this.type,
41
+ slice: () => { }
42
+ }
43
+ },
44
+
45
+ // 转换为 base64(异步)
46
+ toBase64() {
47
+ return new Promise((resolve, reject) => {
48
+ // #ifdef MP-WEIXIN
49
+ wx.getFileSystemManager().readFile({
50
+ filePath: this.path,
51
+ encoding: 'base64',
52
+ success: (res) => resolve(res.data),
53
+ fail: reject
54
+ })
55
+ // #endif
56
+
57
+ // #ifdef H5
58
+ const reader = new FileReader()
59
+ reader.onload = () => resolve(reader.result.split(',')[1])
60
+ reader.onerror = reject
61
+ reader.readAsDataURL(this)
62
+ // #endif
63
+
64
+ // #ifdef APP-PLUS
65
+ plus.io.resolveLocalFileSystemURL(this.path, (entry) => {
66
+ entry.file((file) => {
67
+ const reader = new plus.io.FileReader()
68
+ reader.onload = (e) => resolve(e.target.result.split(',')[1])
69
+ reader.onerror = reject
70
+ reader.readAsDataURL(file)
71
+ })
72
+ })
73
+ // #endif
74
+ })
75
+ },
76
+
77
+ // 转换为 ArrayBuffer(异步)
78
+ toArrayBuffer() {
79
+ return new Promise((resolve, reject) => {
80
+ // #ifdef MP-WEIXIN
81
+ wx.getFileSystemManager().readFile({
82
+ filePath: this.path,
83
+ success: (res) => resolve(res.data),
84
+ fail: reject
85
+ })
86
+ // #endif
87
+
88
+ // #ifdef H5
89
+ const reader = new FileReader()
90
+ reader.onload = () => resolve(reader.result)
91
+ reader.onerror = reject
92
+ reader.readAsArrayBuffer(this)
93
+ // #endif
94
+ })
95
+ },
96
+
97
+ // 获取文件 URL(用于预览等)
98
+ getURL() {
99
+ // #ifdef H5
100
+ if (this.raw instanceof File) {
101
+ return URL.createObjectURL(this.raw)
102
+ }
103
+ // #endif
104
+ return this.path
105
+ },
106
+
107
+ // 释放资源
108
+ revoke() {
109
+ // #ifdef H5
110
+ if (this.raw instanceof File && this._objectURL) {
111
+ URL.revokeObjectURL(this._objectURL)
112
+ }
113
+ // #endif
114
+ }
115
+ }
116
+
117
+ // 如果是 H5 环境,尝试包装成 File 对象
118
+ // #ifdef H5
119
+ if (typeof File !== 'undefined' && !(rawFile instanceof File)) {
120
+ try {
121
+ // 尝试创建 File 对象(需要 fetch 支持)
122
+ return Object.assign(new Blob(), unifiedFile)
123
+ } catch (e) {
124
+ console.warn('无法创建 File 对象,使用统一格式:', e)
125
+ }
126
+ }
127
+ // #endif
128
+
129
+ return unifiedFile
130
+ }
131
+
132
+ /**
133
+ * 从文件路径提取文件名
134
+ */
135
+ const getFileNameFromPath = (path) => {
136
+ if (!path) return 'unnamed'
137
+ return path.split('/').pop().split('?')[0]
138
+ }
139
+
140
+ /**
141
+ * 从文件名获取文件类型
142
+ */
143
+ const getFileTypeFromPath = (filename) => {
144
+ if (!filename) return 'application/octet-stream'
145
+
146
+ const extension = filename.split('.').pop().toLowerCase()
147
+ const typeMap = {
148
+ // 图片
149
+ 'jpg': 'image/jpeg',
150
+ 'jpeg': 'image/jpeg',
151
+ 'png': 'image/png',
152
+ 'gif': 'image/gif',
153
+ 'bmp': 'image/bmp',
154
+ 'webp': 'image/webp',
155
+ 'svg': 'image/svg+xml',
156
+
157
+ // 视频
158
+ 'mp4': 'video/mp4',
159
+ 'avi': 'video/x-msvideo',
160
+ 'mov': 'video/quicktime',
161
+ 'wmv': 'video/x-ms-wmv',
162
+ 'flv': 'video/x-flv',
163
+ 'mkv': 'video/x-matroska',
164
+
165
+ // 音频
166
+ 'mp3': 'audio/mpeg',
167
+ 'wav': 'audio/wav',
168
+ 'aac': 'audio/aac',
169
+ 'ogg': 'audio/ogg',
170
+ 'flac': 'audio/flac',
171
+
172
+ // 文档
173
+ 'pdf': 'application/pdf',
174
+ 'doc': 'application/msword',
175
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
176
+ 'xls': 'application/vnd.ms-excel',
177
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
178
+ 'ppt': 'application/vnd.ms-powerpoint',
179
+ 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
180
+ 'txt': 'text/plain'
181
+ }
182
+
183
+ return typeMap[extension] || 'application/octet-stream'
184
+ }
185
+
186
+ /**
187
+ * 批量转换文件对象
188
+ */
189
+ export const createFilesFromUniFiles = (rawFiles) => {
190
+ if (!Array.isArray(rawFiles)) {
191
+ rawFiles = [rawFiles]
192
+ }
193
+
194
+ return rawFiles.map(file => createFileFromUniFile(file))
195
+ }
196
+
197
+ /**
198
+ * 环境检测
199
+ */
200
+ export const isBrowserEnv = () => {
201
+ // #ifdef H5
202
+ return typeof File !== 'undefined'
203
+ // #endif
204
+
205
+ return false
206
+ }
207
+
208
+ export const isWeChatEnv = () => {
209
+ // #ifdef MP-WEIXIN
210
+ return true
211
+ // #endif
212
+
213
+ return false
214
+ }
@@ -0,0 +1,306 @@
1
+ // utils/file-validator.js
2
+
3
+ /**
4
+ * 获取文件类型(多平台兼容)
5
+ */
6
+ const getFileType = (file) => {
7
+ // 优先使用 MIME 类型判断
8
+ if (file.type) {
9
+ return file.type.split('/')[0] // image, video, audio, application
10
+ }
11
+
12
+ // 通过文件名后缀判断
13
+ const path = file.path || file.name || ''
14
+ const extension = path.split('.').pop().toLowerCase()
15
+
16
+ // 图片类型
17
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
18
+ if (imageExtensions.includes(extension)) return 'image'
19
+
20
+ // 视频类型
21
+ const videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm']
22
+ if (videoExtensions.includes(extension)) return 'video'
23
+
24
+ // 音频类型
25
+ const audioExtensions = ['mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a']
26
+ if (audioExtensions.includes(extension)) return 'audio'
27
+
28
+ // 文档类型
29
+ const documentExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt']
30
+ if (documentExtensions.includes(extension)) return 'document'
31
+
32
+ return 'other'
33
+ }
34
+
35
+ /**
36
+ * 统一文件类型映射(将自定义类型映射到平台支持的类型)
37
+ */
38
+ const mapAcceptTypeToPlatform = (accept, platform) => {
39
+ const typeMap = {
40
+ image: {
41
+ wx: ['image'],
42
+ uni: ['image'],
43
+ alipay: ['image']
44
+ },
45
+ video: {
46
+ wx: ['video'],
47
+ uni: ['video'],
48
+ alipay: ['video']
49
+ },
50
+ audio: {
51
+ wx: ['file'],
52
+ uni: [],
53
+ alipay: []
54
+ },
55
+ all: {
56
+ wx: ['all'],
57
+ uni: ['all'],
58
+ alipay: ['all']
59
+ },
60
+ file: {
61
+ wx: ['file'],
62
+ uni: ['file'],
63
+ alipay: ['file']
64
+ }
65
+ }
66
+
67
+ return typeMap[accept]?.[platform] || [accept]
68
+ }
69
+
70
+ /**
71
+ * 检查文件类型是否被接受(多平台兼容)
72
+ * @param {File|Object} file - 文件对象
73
+ * @param {string|Array} accept - 接受的文件类型
74
+ * @returns {boolean}
75
+ */
76
+ export const isFileTypeAccepted = (file, accept = 'image') => {
77
+ if (!file) return false
78
+
79
+ // 如果 accept 是数组,转换为字符串处理
80
+ const acceptType = Array.isArray(accept) ? accept.join(',') : accept
81
+
82
+ // 获取文件类型
83
+ const fileType = getFileType(file)
84
+
85
+ // 处理 accept 字符串
86
+ const acceptTypes = acceptType.split(',').map(type => type.trim())
87
+
88
+ // 特殊类型处理
89
+ if (acceptTypes.includes('all')) return true
90
+ if (acceptTypes.includes('*')) return true
91
+
92
+ // 检查文件类型
93
+ for (const accepted of acceptTypes) {
94
+ // 精确匹配
95
+ if (accepted === fileType) return true
96
+
97
+ // MIME 类型匹配
98
+ if (file.type && accepted.includes('/')) {
99
+ if (file.type.includes(accepted)) return true
100
+ }
101
+
102
+ // 扩展名匹配
103
+ const extension = (file.path || file.name || '').split('.').pop().toLowerCase()
104
+ if (accepted.startsWith('.') && `.${extension}` === accepted) {
105
+ return true
106
+ }
107
+
108
+ // 通用类型匹配
109
+ const genericTypes = {
110
+ 'image': ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
111
+ 'video': ['mp4', 'avi', 'mov', 'wmv'],
112
+ 'audio': ['mp3', 'wav', 'ogg', 'm4a'],
113
+ 'document': ['pdf', 'doc', 'docx', 'xlsx']
114
+ }
115
+
116
+ if (genericTypes[accepted] && genericTypes[accepted].includes(extension)) {
117
+ return true
118
+ }
119
+ }
120
+
121
+ return false
122
+ }
123
+
124
+ /**
125
+ * 获取平台特定的文件选择配置
126
+ */
127
+ export const getPlatformFileConfig = (options = {}) => {
128
+ const { accept = 'image', count = 1 } = options
129
+
130
+ // #ifdef MP-WEIXIN
131
+ if (accept === 'image' || accept.includes('image')) {
132
+ return {
133
+ api: 'chooseImage',
134
+ config: {
135
+ count,
136
+ sizeType: ['original', 'compressed'],
137
+ sourceType: ['album', 'camera']
138
+ }
139
+ }
140
+ } else if (accept === 'video' || accept.includes('video')) {
141
+ return {
142
+ api: 'chooseVideo',
143
+ config: {
144
+ sourceType: ['album', 'camera'],
145
+ compressed: true,
146
+ maxDuration: 60
147
+ }
148
+ }
149
+ } else {
150
+ return {
151
+ api: 'chooseMessageFile',
152
+ config: {
153
+ count,
154
+ type: 'file',
155
+ extension: getWeChatExtensions(accept)
156
+ }
157
+ }
158
+ }
159
+ // #endif
160
+
161
+ // #ifdef H5 || APP-PLUS
162
+ return {
163
+ api: 'chooseFile',
164
+ config: {
165
+ count,
166
+ type: mapAcceptTypeToPlatform(accept, 'uni')[0]
167
+ }
168
+ }
169
+ // #endif
170
+
171
+ // #ifdef MP-ALIPAY
172
+ if (accept === 'image' || accept.includes('image')) {
173
+ return {
174
+ api: 'chooseImage',
175
+ config: {
176
+ count,
177
+ sizeType: ['original', 'compressed'],
178
+ sourceType: ['album', 'camera']
179
+ }
180
+ }
181
+ } else {
182
+ return {
183
+ api: 'chooseFile',
184
+ config: {
185
+ count
186
+ }
187
+ }
188
+ }
189
+ // #endif
190
+
191
+ return null
192
+ }
193
+
194
+ /**
195
+ * 获取微信小程序支持的扩展名
196
+ */
197
+ const getWeChatExtensions = (accept) => {
198
+ const extensions = []
199
+
200
+ if (accept.includes('image')) {
201
+ extensions.push('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp')
202
+ }
203
+ if (accept.includes('video')) {
204
+ extensions.push('.mp4', '.avi', '.mov', '.wmv')
205
+ }
206
+ if (accept.includes('.pdf')) {
207
+ extensions.push('.pdf')
208
+ }
209
+ if (accept.includes('.doc') || accept.includes('.docx')) {
210
+ extensions.push('.doc', '.docx')
211
+ }
212
+ if (accept.includes('.xls') || accept.includes('.xlsx')) {
213
+ extensions.push('.xls', '.xlsx')
214
+ }
215
+ if (accept.includes('.ppt') || accept.includes('.pptx')) {
216
+ extensions.push('.ppt', '.pptx')
217
+ }
218
+
219
+ return extensions
220
+ }
221
+
222
+ // /**
223
+ // * 统一文件选择方法(整合类型检查)
224
+ // */
225
+ // export const chooseFile = async (options = {}) => {
226
+ // const {
227
+ // accept = 'image',
228
+ // count = 1,
229
+ // sizeLimit, // 文件大小限制(字节)
230
+ // onTypeError, // 类型错误回调
231
+ // onSizeError // 大小错误回调
232
+ // } = options
233
+
234
+ // const platformConfig = getPlatformFileConfig({ accept, count })
235
+
236
+ // return new Promise((resolve, reject) => {
237
+ // // #ifdef MP-WEIXIN
238
+ // const wxApi = wx[platformConfig.api]
239
+ // wxApi({
240
+ // ...platformConfig.config,
241
+ // success: (res) => {
242
+ // // 统一处理返回结果
243
+ // let files = []
244
+ // if (platformConfig.api === 'chooseImage') {
245
+ // files = res.tempFiles.map(file => ({
246
+ // path: file.path,
247
+ // size: file.size,
248
+ // type: 'image'
249
+ // }))
250
+ // } else if (platformConfig.api === 'chooseMessageFile') {
251
+ // files = res.tempFiles
252
+ // }
253
+
254
+ // // 类型检查
255
+ // const filteredFiles = files.filter(file =>
256
+ // isFileTypeAccepted(file, accept)
257
+ // )
258
+
259
+ // // 大小检查
260
+ // if (sizeLimit) {
261
+ // const oversizedFiles = filteredFiles.filter(file => file.size > sizeLimit)
262
+ // if (oversizedFiles.length > 0 && onSizeError) {
263
+ // onSizeError(oversizedFiles)
264
+ // }
265
+ // }
266
+
267
+ // resolve({
268
+ // tempFilePaths: filteredFiles.map(f => f.path),
269
+ // tempFiles: filteredFiles
270
+ // })
271
+ // },
272
+ // fail: reject
273
+ // })
274
+ // // #endif
275
+
276
+ // // #ifdef H5 || APP-PLUS
277
+ // uni.chooseFile({
278
+ // ...platformConfig.config,
279
+ // success: (res) => {
280
+ // // 类型检查
281
+ // const filteredFiles = res.tempFiles.filter(file =>
282
+ // isFileTypeAccepted(file, accept)
283
+ // )
284
+
285
+ // if (filteredFiles.length === 0 && onTypeError) {
286
+ // onTypeError('没有符合类型的文件')
287
+ // }
288
+
289
+ // // 大小检查
290
+ // if (sizeLimit) {
291
+ // const oversizedFiles = filteredFiles.filter(file => file.size > sizeLimit)
292
+ // if (oversizedFiles.length > 0 && onSizeError) {
293
+ // onSizeError(oversizedFiles)
294
+ // }
295
+ // }
296
+
297
+ // resolve({
298
+ // tempFilePaths: filteredFiles.map(f => f.path),
299
+ // tempFiles: filteredFiles
300
+ // })
301
+ // },
302
+ // fail: reject
303
+ // })
304
+ // // #endif
305
+ // })
306
+ // }
@@ -0,0 +1,227 @@
1
+ // utils/upload.js
2
+ /**
3
+ * 获取平台特定的上传方法
4
+ */
5
+ const getPlatformUploadAPI = () => {
6
+ // #ifdef H5 || APP-PLUS
7
+ return uni.uploadFile
8
+ // #endif
9
+
10
+ // #ifdef MP-WEIXIN
11
+ return wx.uploadFile
12
+ // #endif
13
+
14
+ // #ifdef MP-ALIPAY
15
+ return my.uploadFile || my.httpRequest
16
+ // #endif
17
+
18
+ // #ifdef MP-BAIDU
19
+ return swan.uploadFile
20
+ // #endif
21
+
22
+ // #ifdef MP-TOUTIAO
23
+ return tt.uploadFile
24
+ // #endif
25
+
26
+ // #ifdef MP-KUAISHOU
27
+ return ks.uploadFile
28
+ // #endif
29
+
30
+ // 默认返回 uni.uploadFile
31
+ return uni.uploadFile
32
+ }
33
+
34
+ /**
35
+ * 获取平台特定的请求头处理
36
+ */
37
+ const getPlatformHeaders = (headers, platform) => {
38
+ // 微信小程序需要特殊处理
39
+ if (platform === 'wx') {
40
+ // 微信小程序中,header 字段名是小写的
41
+ const wxHeaders = {}
42
+ Object.keys(headers).forEach(key => {
43
+ wxHeaders[key.toLowerCase()] = headers[key]
44
+ })
45
+ return wxHeaders
46
+ }
47
+
48
+ return headers
49
+ }
50
+
51
+ /**
52
+ * 获取平台特定的请求配置
53
+ */
54
+ const getPlatformConfig = (config, platform) => {
55
+ const baseConfig = {
56
+ url: config.url,
57
+ filePath: config.filePath,
58
+ name: config.name || 'file',
59
+ formData: config.formData || {},
60
+ timeout: config.timeout || 60000,
61
+ complete: config.complete,
62
+ success: config.success,
63
+ fail: config.fail
64
+ }
65
+
66
+ // 平台特定的配置调整
67
+ switch (platform) {
68
+ case 'wx':
69
+ // 微信小程序
70
+ return {
71
+ ...baseConfig,
72
+ header: getPlatformHeaders(config.header || {}, 'wx')
73
+ }
74
+
75
+ case 'alipay':
76
+ // 支付宝小程序
77
+ return {
78
+ ...baseConfig,
79
+ headers: config.header || {}, // 支付宝使用 headers
80
+ fileType: 'image' // 支付宝可能需要指定文件类型
81
+ }
82
+
83
+ case 'baidu':
84
+ // 百度小程序
85
+ return {
86
+ ...baseConfig,
87
+ header: config.header || {}
88
+ }
89
+
90
+ case 'tt':
91
+ // 字节跳动小程序
92
+ return {
93
+ ...baseConfig,
94
+ header: config.header || {}
95
+ }
96
+
97
+ default:
98
+ // uni-app 和其他平台
99
+ return {
100
+ ...baseConfig,
101
+ header: config.header || {},
102
+ withCredentials: config.withCredentials || false
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * 获取当前平台标识
109
+ */
110
+ const getCurrentPlatform = () => {
111
+ // #ifdef MP-WEIXIN
112
+ return 'wx'
113
+ // #endif
114
+
115
+ // #ifdef MP-ALIPAY
116
+ return 'alipay'
117
+ // #endif
118
+
119
+ // #ifdef MP-BAIDU
120
+ return 'baidu'
121
+ // #endif
122
+
123
+ // #ifdef MP-TOUTIAO
124
+ return 'tt'
125
+ // #endif
126
+
127
+ // #ifdef MP-KUAISHOU
128
+ return 'ks'
129
+ // #endif
130
+
131
+ // #ifdef H5
132
+ return 'h5'
133
+ // #endif
134
+
135
+ // #ifdef APP-PLUS
136
+ return 'app'
137
+ // #endif
138
+
139
+ return 'uni'
140
+ }
141
+
142
+ /**
143
+ * 多平台兼容的文件上传方法
144
+ */
145
+ export const universalUploadFile = (config) => {
146
+ const platform = getCurrentPlatform()
147
+ const platformConfig = getPlatformConfig(config, platform)
148
+ const uploadAPI = getPlatformUploadAPI()
149
+
150
+ return new Promise((resolve, reject) => {
151
+ // 平台特定的成功回调处理
152
+ const successCallback = (res) => {
153
+ try {
154
+ // 统一处理返回数据格式
155
+ let data = res.data || res
156
+
157
+ if (typeof data === 'string') {
158
+ try {
159
+ data = JSON.parse(data)
160
+ } catch (e) {
161
+ // 如果不是 JSON,保持原样
162
+ }
163
+ }
164
+
165
+ // 构建统一的响应格式
166
+ const unifiedResponse = {
167
+ statusCode: res.statusCode || 200,
168
+ data,
169
+ errMsg: res.errMsg || 'uploadFile:ok'
170
+ }
171
+
172
+ // 调用原始 success 回调
173
+ if (config.success) {
174
+ config.success(unifiedResponse)
175
+ }
176
+
177
+ resolve(unifiedResponse)
178
+ } catch (error) {
179
+ reject(error)
180
+ }
181
+ }
182
+
183
+ // 平台特定的失败回调处理
184
+ const failCallback = (err) => {
185
+ const error = {
186
+ errMsg: err.errMsg || 'uploadFile:fail',
187
+ detail: err
188
+ }
189
+
190
+ if (config.fail) {
191
+ config.fail(error)
192
+ }
193
+
194
+ reject(error)
195
+ }
196
+
197
+ // 执行上传
198
+ try {
199
+ if (platform === 'alipay' && my.uploadFile) {
200
+ // 支付宝小程序特殊处理
201
+ my.uploadFile({
202
+ url: platformConfig.url,
203
+ fileType: 'image',
204
+ fileName: 'file',
205
+ filePath: platformConfig.filePath,
206
+ formData: platformConfig.formData,
207
+ headers: platformConfig.headers,
208
+ success: successCallback,
209
+ fail: failCallback,
210
+ complete: platformConfig.complete
211
+ })
212
+ } else {
213
+ // 其他平台
214
+ uploadAPI({
215
+ ...platformConfig,
216
+ success: successCallback,
217
+ fail: failCallback
218
+ })
219
+ }
220
+ } catch (error) {
221
+ reject({
222
+ errMsg: 'uploadFile:fail',
223
+ detail: error
224
+ })
225
+ }
226
+ })
227
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "im-ui-mobile",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "A Vue3.0 + Typescript instant messaging component library for Uniapp",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -16,7 +16,7 @@ declare interface UploadFile {
16
16
  response?: any
17
17
  error?: Error
18
18
  rawFile?: any
19
- file?: File
19
+ file?: Object
20
20
  }
21
21
 
22
22
  // 上传组件 Props
@@ -65,7 +65,7 @@ declare interface UploadProps {
65
65
  withCredentials?: boolean
66
66
  timeout?: number
67
67
  autoUpload?: boolean
68
- beforeUpload?: (file: File) => boolean | Promise<boolean> // // 上传前钩子
68
+ beforeUpload?: (file: Object) => boolean | Promise<boolean> // // 上传前钩子
69
69
 
70
70
  // 响应格式化
71
71
  responseFormatter?: (response: any) => { url: string;[key: string]: any }
@@ -81,13 +81,13 @@ declare interface UploadProps {
81
81
  readonly?: boolean
82
82
 
83
83
  // 自定义上传
84
- customRequest?: (file: File, onProgress: (percent: number) => void) => Promise<any>
84
+ customRequest?: (file: Object, onProgress: (percent: number) => void) => Promise<any>
85
85
  }
86
86
 
87
87
  // 上传组件方法
88
88
  declare interface UploadMethods {
89
89
  // 手动上传文件
90
- upload: (file: File) => Promise<void>
90
+ upload: (file: Object) => Promise<void>
91
91
 
92
92
  // 清空文件列表
93
93
  clearFiles: () => void
@@ -109,14 +109,14 @@ declare interface _Upload {
109
109
  $emit: {
110
110
  'update:modelValue': (files: UploadFile[]) => void
111
111
  'change': (files: UploadFile[]) => void
112
- 'select': (file: File) => void
112
+ 'select': (file: Object) => void
113
113
  'upload': (file: UploadFile) => void
114
114
  'success': (response: any, file: UploadFile) => void
115
115
  'error': (error: Error, file: UploadFile) => void
116
116
  'progress': (percent: number, file: UploadFile) => void
117
117
  'remove': (file: UploadFile, index: number) => void
118
118
  'preview': (file: UploadFile) => void
119
- 'exceed': (files: File[]) => void
119
+ 'exceed': (files: Object[]) => void
120
120
  'before-upload': (file: UploadFile) => void
121
121
  'after-upload': (file: UploadFile) => void
122
122
  }