xydata-tools-uniapp 1.0.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/README.md +60 -0
- package/dist/assets/defaultAddPicBg.png +0 -0
- package/dist/assets/file-icon/document.png +0 -0
- package/dist/assets/file-icon/image.png +0 -0
- package/dist/assets/file-icon/ms-excel.png +0 -0
- package/dist/assets/file-icon/ms-powerpoint.png +0 -0
- package/dist/assets/file-icon/ms-word.png +0 -0
- package/dist/assets/file-icon/pdf.png +0 -0
- package/dist/assets/file-icon/video.png +0 -0
- package/dist/assets/file-icon/zip.png +0 -0
- package/dist/assets/loading.png +0 -0
- package/dist/assets/uploadVideoBg.png +0 -0
- package/dist/assets/videoPlay.png +0 -0
- package/dist/components/uni-app/file-picker/file-picker.vue +633 -0
- package/dist/components/uni-app/file-picker/styles.scss +196 -0
- package/dist/components/uni-app/media-picker/media-picker.vue +1645 -0
- package/dist/components/uni-app/media-picker/shmily-drag-media.vue +924 -0
- package/dist/components/uni-app/media-picker/styles.scss +531 -0
- package/dist/components/uni-app/watermark/index.js +633 -0
- package/dist/index.d.ts +452 -0
- package/dist/index.js +9 -0
- package/dist/shims-vue.d.ts +8 -0
- package/dist/types/components/uni-app/watermark/index.d.ts +2 -0
- package/dist/types/utils/watermark-shared.d.ts +42 -0
- package/dist/utils/watermark-shared.js +346 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# xydata-tools-uniapp
|
|
2
|
+
|
|
3
|
+
鑫义科技 uni-app 组件库,从 `xydata-tools` 独立拆分而来,专注于 uni-app 端(微信小程序 / H5 / App)的组件能力。
|
|
4
|
+
|
|
5
|
+
## 组件清单
|
|
6
|
+
|
|
7
|
+
- **MediaPicker** - 媒体(图片/视频)选择上传组件,支持图片水印、拖拽排序、只读预览等
|
|
8
|
+
- **FilePicker** - 文件选择上传组件
|
|
9
|
+
|
|
10
|
+
## 本地开发
|
|
11
|
+
|
|
12
|
+
1. 在项目根目录执行:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
yarn dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. 在 example-uniapp 下运行:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
yarn link xydata-tools-uniapp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. 将 example-uniapp 添加到 H-Builder 中运行
|
|
25
|
+
|
|
26
|
+
> example-uniapp 的 `pages.json` 中 `easycom` 已配置好组件路径,会自动扫描引入。
|
|
27
|
+
|
|
28
|
+
## 构建发布
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run prepublishOnly
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
发布 npm 包:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm config set registry https://registry.npmjs.org
|
|
38
|
+
npm publish -d
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
发布完成后,还原 npm 源(淘宝源):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm config set registry https://registry.npmmirror.com
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 目录结构
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/
|
|
51
|
+
index.js # 组件库入口(导出 MediaPicker / FilePicker)
|
|
52
|
+
index.d.ts # 类型声明
|
|
53
|
+
components/uni-app/ # uni-app 组件源码
|
|
54
|
+
utils/ # 共享工具(watermark-shared 等)
|
|
55
|
+
assets/ # 组件图片资源
|
|
56
|
+
example-uniapp/ # 示例工程(HBuilder 直接运行)
|
|
57
|
+
docs/ # 组件文档
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="file-picker-wrapper">
|
|
3
|
+
<view class="file-picker-box" v-if="!readonly">
|
|
4
|
+
<view class="upload-view">
|
|
5
|
+
<!-- 上传中状态 -->
|
|
6
|
+
<view class="file-item uploading-item" v-if="uploading">
|
|
7
|
+
<view class="file-info">
|
|
8
|
+
<view class="file-icon">
|
|
9
|
+
<image class="loading-icon" src="../../../assets/loading.png" />
|
|
10
|
+
</view>
|
|
11
|
+
<view class="file-content">
|
|
12
|
+
<text class="file-name">上传中 {{ uploadProgress }}%</text>
|
|
13
|
+
</view>
|
|
14
|
+
</view>
|
|
15
|
+
<view class="progress-bar">
|
|
16
|
+
<view class="progress-bar-inner" :style="{ width: uploadProgress + '%' }"></view>
|
|
17
|
+
</view>
|
|
18
|
+
</view>
|
|
19
|
+
|
|
20
|
+
<!-- 自定义上传按钮插槽 -->
|
|
21
|
+
<slot name="upload-button" :select="selectAndUploadFile" :count="fileList.length">
|
|
22
|
+
<!-- 默认上传按钮 -->
|
|
23
|
+
<button class="upload-btn" v-if="fileList.length < limit && !uploading" @click="selectAndUploadFile">
|
|
24
|
+
{{ buttonTitle }}
|
|
25
|
+
</button>
|
|
26
|
+
</slot>
|
|
27
|
+
|
|
28
|
+
<!-- 文件列表 -->
|
|
29
|
+
<view class="file-item" v-for="(file, index) in fileList" :key="index">
|
|
30
|
+
<view class="file-info" @click="handleFileClick(file)">
|
|
31
|
+
<view class="file-icon">
|
|
32
|
+
<image :src="getFileIcon(file.name)" mode="aspectFit" class="icon-image"></image>
|
|
33
|
+
</view>
|
|
34
|
+
<view class="file-content">
|
|
35
|
+
<text class="file-name">{{ file.name }}</text>
|
|
36
|
+
</view>
|
|
37
|
+
</view>
|
|
38
|
+
<view class="file-actions">
|
|
39
|
+
<view class="action-btn delete-btn" @click="removeFile(index)"> × </view>
|
|
40
|
+
</view>
|
|
41
|
+
</view>
|
|
42
|
+
</view>
|
|
43
|
+
</view>
|
|
44
|
+
|
|
45
|
+
<!-- 只读模式 -->
|
|
46
|
+
<view class="file-readonly" v-else-if="fileList.length">
|
|
47
|
+
<view class="file-item" v-for="(file, index) in fileList" :key="index">
|
|
48
|
+
<view class="file-info" @click="handleFileClick(file)">
|
|
49
|
+
<view class="file-icon">
|
|
50
|
+
<image :src="getFileIcon(file.name)" mode="aspectFit" class="icon-image"></image>
|
|
51
|
+
</view>
|
|
52
|
+
<view class="file-content">
|
|
53
|
+
<text class="file-name">{{ file.name }}</text>
|
|
54
|
+
</view>
|
|
55
|
+
</view>
|
|
56
|
+
</view>
|
|
57
|
+
</view>
|
|
58
|
+
</view>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script>
|
|
62
|
+
import msWord from '../../../assets/file-icon/ms-word.png';
|
|
63
|
+
import msExcel from '../../../assets/file-icon/ms-excel.png';
|
|
64
|
+
import msPowerpoint from '../../../assets/file-icon/ms-powerpoint.png';
|
|
65
|
+
import pdfIcon from '../../../assets/file-icon/pdf.png';
|
|
66
|
+
import imageIcon from '../../../assets/file-icon/image.png';
|
|
67
|
+
import videoIcon from '../../../assets/file-icon/video.png';
|
|
68
|
+
import documentIcon from '../../../assets/file-icon/document.png';
|
|
69
|
+
import zipIcon from '../../../assets/file-icon/zip.png';
|
|
70
|
+
|
|
71
|
+
export default {
|
|
72
|
+
name: 'FilePicker',
|
|
73
|
+
// #ifdef VUE3
|
|
74
|
+
emits: ['change', 'update:modelValue', 'fileLimitReached', 'fileTypeInvalid', 'fileSizeExceed', 'uploadSuccess', 'uploadFail', 'progress', 'click'],
|
|
75
|
+
// #endif
|
|
76
|
+
props: {
|
|
77
|
+
// #ifdef VUE3
|
|
78
|
+
modelValue: {
|
|
79
|
+
type: Array,
|
|
80
|
+
default: () => []
|
|
81
|
+
},
|
|
82
|
+
// #endif
|
|
83
|
+
// #ifndef VUE3
|
|
84
|
+
value: {
|
|
85
|
+
type: Array,
|
|
86
|
+
default: () => []
|
|
87
|
+
},
|
|
88
|
+
// #endif
|
|
89
|
+
/** 上传地址 */
|
|
90
|
+
uploadUrl: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: ''
|
|
93
|
+
},
|
|
94
|
+
/** 只读模式 */
|
|
95
|
+
readonly: {
|
|
96
|
+
type: Boolean,
|
|
97
|
+
default: false
|
|
98
|
+
},
|
|
99
|
+
/** 文件列表(兼容非v-model用法) */
|
|
100
|
+
files: {
|
|
101
|
+
type: Array,
|
|
102
|
+
default: () => []
|
|
103
|
+
},
|
|
104
|
+
/** 最大上传数量 */
|
|
105
|
+
limit: {
|
|
106
|
+
type: Number,
|
|
107
|
+
default: 10
|
|
108
|
+
},
|
|
109
|
+
/** 允许的文件扩展名 */
|
|
110
|
+
accept: {
|
|
111
|
+
type: Array,
|
|
112
|
+
default: () => ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx', 'zip', 'rar', 'png', 'jpg']
|
|
113
|
+
},
|
|
114
|
+
/** 文件大小限制(MB) */
|
|
115
|
+
maxSize: {
|
|
116
|
+
type: Number,
|
|
117
|
+
default: 20
|
|
118
|
+
},
|
|
119
|
+
/** 上传按钮文字 */
|
|
120
|
+
buttonTitle: {
|
|
121
|
+
type: String,
|
|
122
|
+
default: '上传附件'
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
data() {
|
|
126
|
+
return {
|
|
127
|
+
fileList: [],
|
|
128
|
+
uploading: false,
|
|
129
|
+
uploadProgress: 0,
|
|
130
|
+
actualUploadUrl: '' // 实际使用的上传地址
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
computed: {
|
|
134
|
+
// 判断是否为H5环境
|
|
135
|
+
isH5() {
|
|
136
|
+
// #ifdef H5
|
|
137
|
+
return true;
|
|
138
|
+
// #endif
|
|
139
|
+
// #ifndef H5
|
|
140
|
+
return false;
|
|
141
|
+
// #endif
|
|
142
|
+
},
|
|
143
|
+
/** 实际使用的上传地址 */
|
|
144
|
+
finalUploadUrl() {
|
|
145
|
+
return this.actualUploadUrl || this.uploadUrl || (getApp().globalData && getApp().globalData.uploadUrl) || '';
|
|
146
|
+
},
|
|
147
|
+
/** 统一读取外部值 */
|
|
148
|
+
internalValue() {
|
|
149
|
+
// #ifdef VUE3
|
|
150
|
+
return this.modelValue && this.modelValue.length ? this.modelValue : this.files;
|
|
151
|
+
// #endif
|
|
152
|
+
// #ifndef VUE3
|
|
153
|
+
return this.value && this.value.length ? this.value : this.files;
|
|
154
|
+
// #endif
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
watch: {
|
|
158
|
+
internalValue: {
|
|
159
|
+
handler(val) {
|
|
160
|
+
this.fileList = val || [];
|
|
161
|
+
},
|
|
162
|
+
deep: true,
|
|
163
|
+
immediate: true
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
mounted() {
|
|
167
|
+
this.fileList = this.internalValue || [];
|
|
168
|
+
// 初始化上传地址
|
|
169
|
+
if (!this.uploadUrl) {
|
|
170
|
+
this.actualUploadUrl = (getApp().globalData && getApp().globalData.uploadUrl) || '';
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
methods: {
|
|
174
|
+
// ==================== 事件监听辅助方法 ====================
|
|
175
|
+
/**
|
|
176
|
+
* 检查事件是否被监听
|
|
177
|
+
* @param {String} eventName - 事件名称
|
|
178
|
+
* @returns {Boolean} - 是否被监听
|
|
179
|
+
*/
|
|
180
|
+
hasEventListener(eventName) {
|
|
181
|
+
// #ifdef VUE3
|
|
182
|
+
const onEventName = 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
|
|
183
|
+
return !!(this.$attrs[eventName] || this.$attrs[onEventName]);
|
|
184
|
+
// #endif
|
|
185
|
+
// #ifndef VUE3
|
|
186
|
+
return !!(this.$listeners && this.$listeners[eventName]);
|
|
187
|
+
// #endif
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
// ==================== 文件处理方法 ====================
|
|
191
|
+
/**
|
|
192
|
+
* 获取文件扩展名
|
|
193
|
+
*/
|
|
194
|
+
getFileExtension(fileName) {
|
|
195
|
+
if (!fileName) return '';
|
|
196
|
+
const parts = fileName.split('.');
|
|
197
|
+
return parts.length > 1 ? parts.pop().toLowerCase() : '';
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 获取文件图标
|
|
202
|
+
*/
|
|
203
|
+
getFileIcon(fileName) {
|
|
204
|
+
const ext = this.getFileExtension(fileName);
|
|
205
|
+
switch (ext) {
|
|
206
|
+
case 'doc':
|
|
207
|
+
case 'docx':
|
|
208
|
+
return msWord;
|
|
209
|
+
case 'xls':
|
|
210
|
+
case 'xlsx':
|
|
211
|
+
return msExcel;
|
|
212
|
+
case 'ppt':
|
|
213
|
+
case 'pptx':
|
|
214
|
+
return msPowerpoint;
|
|
215
|
+
case 'pdf':
|
|
216
|
+
return pdfIcon;
|
|
217
|
+
case 'png':
|
|
218
|
+
case 'jpeg':
|
|
219
|
+
case 'jpg':
|
|
220
|
+
case 'gif':
|
|
221
|
+
return imageIcon;
|
|
222
|
+
case 'mp4':
|
|
223
|
+
case 'mov':
|
|
224
|
+
case 'avi':
|
|
225
|
+
case 'wmv':
|
|
226
|
+
case 'flv':
|
|
227
|
+
case 'mkv':
|
|
228
|
+
case 'webm':
|
|
229
|
+
case 'mpg':
|
|
230
|
+
case 'mpeg':
|
|
231
|
+
case 'm4v':
|
|
232
|
+
return videoIcon;
|
|
233
|
+
case 'rar':
|
|
234
|
+
case 'zip':
|
|
235
|
+
case '7z':
|
|
236
|
+
return zipIcon;
|
|
237
|
+
default:
|
|
238
|
+
return documentIcon;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 格式化文件大小
|
|
244
|
+
*/
|
|
245
|
+
formatFileSize(bytes) {
|
|
246
|
+
if (!bytes) return '';
|
|
247
|
+
if (bytes < 1024) return bytes + 'B';
|
|
248
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + 'KB';
|
|
249
|
+
return (bytes / (1024 * 1024)).toFixed(2) + 'MB';
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 选择并上传文件
|
|
254
|
+
*/
|
|
255
|
+
selectAndUploadFile() {
|
|
256
|
+
if (!this.finalUploadUrl) {
|
|
257
|
+
uni.showToast({
|
|
258
|
+
title: '请先配置上传地址',
|
|
259
|
+
icon: 'none'
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (this.fileList.length >= this.limit || this.uploading) {
|
|
265
|
+
if (this.fileList.length >= this.limit) {
|
|
266
|
+
this.$emit('fileLimitReached', {
|
|
267
|
+
current: this.fileList.length,
|
|
268
|
+
limit: this.limit
|
|
269
|
+
});
|
|
270
|
+
if (!this.hasEventListener('fileLimitReached')) {
|
|
271
|
+
uni.showToast({
|
|
272
|
+
title: `最多上传${this.limit}个文件`,
|
|
273
|
+
icon: 'none'
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// #ifdef H5
|
|
281
|
+
uni.chooseFile({
|
|
282
|
+
count: 1,
|
|
283
|
+
extension: this.accept,
|
|
284
|
+
success: (res) => {
|
|
285
|
+
this.validateAndUpload(res.tempFiles[0]);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
// #endif
|
|
289
|
+
|
|
290
|
+
// #ifndef H5
|
|
291
|
+
uni.chooseMessageFile({
|
|
292
|
+
count: 1,
|
|
293
|
+
extension: this.accept,
|
|
294
|
+
success: (res) => {
|
|
295
|
+
this.validateAndUpload(res.tempFiles[0]);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
// #endif
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 验证并上传文件
|
|
303
|
+
*/
|
|
304
|
+
validateAndUpload(file) {
|
|
305
|
+
const ext = this.getFileExtension(file.name);
|
|
306
|
+
|
|
307
|
+
if (this.accept && this.accept.length && !this.accept.includes(ext)) {
|
|
308
|
+
this.$emit('fileTypeInvalid', {
|
|
309
|
+
fileName: file.name,
|
|
310
|
+
fileType: ext,
|
|
311
|
+
acceptTypes: this.accept,
|
|
312
|
+
file: file
|
|
313
|
+
});
|
|
314
|
+
if (!this.hasEventListener('fileTypeInvalid')) {
|
|
315
|
+
uni.showToast({
|
|
316
|
+
title: `不支持${ext}格式`,
|
|
317
|
+
icon: 'none'
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const maxBytes = this.maxSize * 1024 * 1024;
|
|
324
|
+
if (file.size > maxBytes) {
|
|
325
|
+
this.$emit('fileSizeExceed', {
|
|
326
|
+
size: file.size,
|
|
327
|
+
maxSize: maxBytes,
|
|
328
|
+
fileName: file.name
|
|
329
|
+
});
|
|
330
|
+
if (!this.hasEventListener('fileSizeExceed')) {
|
|
331
|
+
uni.showToast({
|
|
332
|
+
title: `不能超过${this.maxSize}MB`,
|
|
333
|
+
icon: 'none'
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this.uploadFile(file);
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 上传文件核心
|
|
344
|
+
*/
|
|
345
|
+
uploadFile(file) {
|
|
346
|
+
this.uploading = true;
|
|
347
|
+
this.uploadProgress = 0;
|
|
348
|
+
|
|
349
|
+
const uploadTask = uni.uploadFile({
|
|
350
|
+
url: this.finalUploadUrl,
|
|
351
|
+
filePath: file.path,
|
|
352
|
+
name: 'file',
|
|
353
|
+
success: (uploadFileRes) => {
|
|
354
|
+
this.handleUploadSuccess(uploadFileRes, file);
|
|
355
|
+
},
|
|
356
|
+
fail: (err) => {
|
|
357
|
+
this.handleUploadError(err);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// 监听上传进度
|
|
362
|
+
uploadTask.onProgressUpdate((res) => {
|
|
363
|
+
this.uploadProgress = res.progress;
|
|
364
|
+
this.$emit('progress', res);
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 处理上传成功
|
|
370
|
+
*/
|
|
371
|
+
handleUploadSuccess(uploadFileRes, originalFile) {
|
|
372
|
+
try {
|
|
373
|
+
const result = JSON.parse(uploadFileRes.data);
|
|
374
|
+
console.log('文件上传成功:', result);
|
|
375
|
+
|
|
376
|
+
const fileInfo = {
|
|
377
|
+
fileName: result.data.fileName,
|
|
378
|
+
name: result.data.originFileName || originalFile.name,
|
|
379
|
+
url: result.data.pathUrl
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
this.fileList.push(fileInfo);
|
|
383
|
+
this.uploadProgress = 100;
|
|
384
|
+
this.uploading = false;
|
|
385
|
+
this.emitChange();
|
|
386
|
+
|
|
387
|
+
// 触发上传成功事件
|
|
388
|
+
this.$emit('uploadSuccess', {
|
|
389
|
+
file: fileInfo,
|
|
390
|
+
response: result
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// 如果用户未监听事件,显示默认提示
|
|
394
|
+
if (!this.hasEventListener('uploadSuccess')) {
|
|
395
|
+
uni.showToast({
|
|
396
|
+
title: '上传成功',
|
|
397
|
+
icon: 'success'
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
} catch (e) {
|
|
401
|
+
this.handleUploadError(e);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 处理上传失败
|
|
407
|
+
*/
|
|
408
|
+
handleUploadError(err) {
|
|
409
|
+
console.error('文件上传失败:', err);
|
|
410
|
+
|
|
411
|
+
// 触发上传失败事件
|
|
412
|
+
this.$emit('uploadFail', {
|
|
413
|
+
error: err,
|
|
414
|
+
message: '文件上传失败'
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// 如果用户未监听事件,显示默认提示
|
|
418
|
+
if (!this.hasEventListener('uploadFail')) {
|
|
419
|
+
uni.showToast({
|
|
420
|
+
icon: 'none',
|
|
421
|
+
title: '文件上传失败'
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.uploading = false;
|
|
426
|
+
this.uploadProgress = 0;
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 删除文件
|
|
431
|
+
*/
|
|
432
|
+
removeFile(index) {
|
|
433
|
+
uni.showModal({
|
|
434
|
+
title: '提示',
|
|
435
|
+
content: '确定要删除该文件吗?',
|
|
436
|
+
success: (res) => {
|
|
437
|
+
if (res.confirm) {
|
|
438
|
+
this.fileList.splice(index, 1);
|
|
439
|
+
this.emitChange();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 处理文件点击
|
|
447
|
+
*/
|
|
448
|
+
handleFileClick(file) {
|
|
449
|
+
if (this.hasEventListener('click')) {
|
|
450
|
+
this.$emit('click', file);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (!file.url) {
|
|
455
|
+
uni.showToast({
|
|
456
|
+
title: '文件地址无效',
|
|
457
|
+
icon: 'none'
|
|
458
|
+
});
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// #ifdef H5
|
|
463
|
+
// H5环境直接下载
|
|
464
|
+
this.downloadFileH5(file);
|
|
465
|
+
// #endif
|
|
466
|
+
|
|
467
|
+
// #ifndef H5
|
|
468
|
+
// 小程序等环境使用 uni.openDocument
|
|
469
|
+
this.openDocument(file);
|
|
470
|
+
// #endif
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* H5环境下载文件
|
|
475
|
+
*/
|
|
476
|
+
downloadFileH5(file) {
|
|
477
|
+
uni.showLoading({
|
|
478
|
+
title: '下载中...'
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// 使用 fetch 下载文件
|
|
482
|
+
fetch(file.url)
|
|
483
|
+
.then((res) => res.blob())
|
|
484
|
+
.then((blob) => {
|
|
485
|
+
// 创建 blob URL
|
|
486
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
487
|
+
const a = document.createElement('a');
|
|
488
|
+
a.href = blobUrl;
|
|
489
|
+
a.download = file.name;
|
|
490
|
+
document.body.appendChild(a);
|
|
491
|
+
a.click();
|
|
492
|
+
document.body.removeChild(a);
|
|
493
|
+
|
|
494
|
+
// 释放 blob URL
|
|
495
|
+
setTimeout(() => {
|
|
496
|
+
URL.revokeObjectURL(blobUrl);
|
|
497
|
+
}, 100);
|
|
498
|
+
|
|
499
|
+
uni.hideLoading();
|
|
500
|
+
})
|
|
501
|
+
.catch((err) => {
|
|
502
|
+
console.error('下载文件失败:', err);
|
|
503
|
+
uni.hideLoading();
|
|
504
|
+
uni.showToast({
|
|
505
|
+
title: '下载失败',
|
|
506
|
+
icon: 'none'
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 打开文档(小程序等)
|
|
513
|
+
*/
|
|
514
|
+
openDocument(file) {
|
|
515
|
+
// 判断文件类型是否为可打开的文档类型
|
|
516
|
+
const ext = this.getFileExtension(file.name);
|
|
517
|
+
const supportedTypes = ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx'];
|
|
518
|
+
const isDocument = supportedTypes.includes(ext);
|
|
519
|
+
|
|
520
|
+
const imageTypes = ['png', 'jpeg', 'jpg', 'gif'];
|
|
521
|
+
console.log(ext);
|
|
522
|
+
if (imageTypes.includes(ext)) {
|
|
523
|
+
uni.previewImage({
|
|
524
|
+
urls: [file.url]
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// 显示加载提示
|
|
530
|
+
uni.showLoading({
|
|
531
|
+
title: isDocument ? '正在打开...' : '正在下载...'
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// 下载文件
|
|
535
|
+
uni.downloadFile({
|
|
536
|
+
url: file.url,
|
|
537
|
+
success: (res) => {
|
|
538
|
+
if (res.statusCode === 200) {
|
|
539
|
+
if (isDocument) {
|
|
540
|
+
// 打开文档
|
|
541
|
+
uni.openDocument({
|
|
542
|
+
filePath: res.tempFilePath,
|
|
543
|
+
fileType: ext,
|
|
544
|
+
success: () => {
|
|
545
|
+
uni.hideLoading();
|
|
546
|
+
},
|
|
547
|
+
fail: (err) => {
|
|
548
|
+
console.error('打开文档失败:', err);
|
|
549
|
+
uni.hideLoading();
|
|
550
|
+
uni.showToast({
|
|
551
|
+
title: '打开文档失败',
|
|
552
|
+
icon: 'none'
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
} else {
|
|
557
|
+
// 保存文件到本地
|
|
558
|
+
uni.saveFile({
|
|
559
|
+
tempFilePath: res.tempFilePath,
|
|
560
|
+
success: (saveRes) => {
|
|
561
|
+
uni.hideLoading();
|
|
562
|
+
uni.showToast({
|
|
563
|
+
title: '已保存到本地',
|
|
564
|
+
icon: 'success'
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
fail: () => {
|
|
568
|
+
uni.hideLoading();
|
|
569
|
+
uni.showToast({
|
|
570
|
+
title: '保存失败',
|
|
571
|
+
icon: 'none'
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
uni.hideLoading();
|
|
578
|
+
uni.showToast({
|
|
579
|
+
title: '下载失败',
|
|
580
|
+
icon: 'none'
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
fail: (err) => {
|
|
585
|
+
console.error('下载失败:', err);
|
|
586
|
+
uni.hideLoading();
|
|
587
|
+
uni.showToast({
|
|
588
|
+
title: '下载失败',
|
|
589
|
+
icon: 'none'
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
},
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* 触发变更事件
|
|
597
|
+
*/
|
|
598
|
+
emitChange() {
|
|
599
|
+
this.$emit('change', this.fileList);
|
|
600
|
+
// #ifdef VUE3
|
|
601
|
+
this.$emit('update:modelValue', this.fileList);
|
|
602
|
+
// #endif
|
|
603
|
+
// #ifndef VUE3
|
|
604
|
+
this.$emit('input', this.fileList);
|
|
605
|
+
// #endif
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* 获取文件列表
|
|
610
|
+
* @returns {Array} 文件列表
|
|
611
|
+
*/
|
|
612
|
+
getFileList() {
|
|
613
|
+
return this.fileList;
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 获取参数(不包含 url 字段)
|
|
618
|
+
* @returns {String} JSON 字符串
|
|
619
|
+
*/
|
|
620
|
+
getParams() {
|
|
621
|
+
const params = this.fileList.map((file) => {
|
|
622
|
+
const { url, ...rest } = file;
|
|
623
|
+
return rest;
|
|
624
|
+
});
|
|
625
|
+
return JSON.stringify(params);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
</script>
|
|
630
|
+
|
|
631
|
+
<style lang="scss">
|
|
632
|
+
@import './styles.scss';
|
|
633
|
+
</style>
|