123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- <template>
- <view class="upload-container">
- <u-upload
- :fileList="fileList"
- @afterRead="handleAfterRead"
- @delete="handleDelete"
- :name="name"
- :multiple="multiple"
- :maxCount="maxCount"
- :auto-upload="false"
- :show-progress="true"
- :sizeType="sizeType"
- :sourceType="sourceType"
- :accept="accept"
- previewFullImage
- >
- <view v-if="accept !== 'image'">
- <text class="file-icon">📎</text>
- <text>点击上传文件</text>
- </view>
- </u-upload>
-
- <!-- 文件列表区域,针对文件类型优化显示 -->
- <view class="file-list" v-if="fileList && fileList.length > 0 && accept !== 'image'">
- <view class="file-item" v-for="(file, index) in fileList" :key="index">
- <view class="file-info">
- <view class="file-icon">
- <text>{{ getFileIcon(file.fileName) }}</text>
- </view>
- <view class="file-details">
- <text class="file-name">{{ file.fileName || '未命名文件' }}</text>
- </view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script>
- import config from '@/config.js'; // 根据实际路径调整
- export default {
- name: 'UploadComponent',
- data() {
- return {
- // 上传URL
- uploadUrl: `${config.baseUrl}/common/upload`,
- fileList: []
- }
- },
- props: {
- // 组件标识名称
- name: {
- type: String,
- default: 'upload'
- },
- // 是否允许多文件
- multiple: {
- type: Boolean,
- default: false
- },
- // 最大文件数量
- maxCount: {
- type: Number,
- default: 10
- },
- // 文件类型限制
- accept: {
- type: String,
- default: 'image'
- },
- // 图片尺寸类型
- sizeType: {
- type: Array,
- default: () => ['original', 'compressed']
- },
- // 图片来源
- sourceType: {
- type: Array,
- default: () => ['album', 'camera']
- },
- // 额外的表单数据
- formData: {
- type: Object,
- default: () => ({})
- }
- },
- methods: {
- // 删除图片/文件
- handleDelete(event) {
- // 通知父组件删除文件
- this.$emit('delete', {
- name: this.name,
- index: event.index,
- file: this.fileList[event.index]
- });
-
- // 从本地列表中移除
- this.fileList.splice(event.index, 1);
- },
-
- // 新增图片/文件
- async handleAfterRead(event) {
- // 触发开始上传事件
- this.$emit('upload-start', {
- name: this.name,
- files: event.file
- });
-
- // 处理文件列表
- let lists = [].concat(event.file);
-
- // 更新UI显示上传中状态
- lists.forEach((item) => {
- // 添加上传中的文件到列表
- this.fileList.push({
- ...item,
- status: "uploading",
- message: "上传中"
- });
-
- this.$emit('file-added', {
- name: this.name,
- file: this.fileList[this.fileList.length - 1]
- });
- });
-
- try {
- // 逐个上传文件
- for (let i = 0; i < lists.length; i++) {
- const fileIndex = this.fileList.length - lists.length + i;
- const result = await this.uploadFilePromise(lists[i].url);
- this.fileList= [result]
- // 更新文件状态为成功
- this.fileList[fileIndex] = {
- ...this.fileList[fileIndex],
- ...result,
- status: "success",
- message: "上传成功"
- };
-
- this.$emit('file-updated', {
- name: this.name,
- index: fileIndex,
- file: this.fileList[fileIndex]
- });
- }
-
- // 触发上传完成事件
- this.$emit('upload-success', {
- name: this.name,
- files: lists
- });
- } catch (error) {
- // 标记上传失败
- lists.forEach((_, i) => {
- const fileIndex = this.fileList.length - lists.length + i;
- this.fileList[fileIndex].status = "error";
- this.fileList[fileIndex].message = "上传失败";
- });
-
- // 触发上传失败事件
- this.$emit('upload-error', {
- name: this.name,
- error: error
- });
- }
- },
-
- // 上传文件的Promise封装
- uploadFilePromise(url) {
- return new Promise((resolve, reject) => {
- uni.uploadFile({
- url: this.uploadUrl,
- filePath: url,
- name: "file",
- formData: {
- ...this.formData,
- },
- success: (res) => {
- try {
- // 尝试解析JSON响应
- const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
- resolve(data.data || data);
- } catch (e) {
- // 如果解析失败,直接返回原始数据
- resolve(res.data);
- }
- },
- fail: (err) => {
- reject(err);
- }
- });
- });
- },
-
- // 根据文件扩展名获取对应的图标
- getFileIcon(filename) {
- if (!filename) return '📄';
-
- const ext = filename.split('.').pop().toLowerCase();
-
- // 根据文件类型返回不同的图标emoji
- if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext)) return '🖼️';
- if (['pdf'].includes(ext)) return '📑';
- if (['doc', 'docx'].includes(ext)) return '📝';
- if (['xls', 'xlsx'].includes(ext)) return '📊';
- if (['ppt', 'pptx'].includes(ext)) return '📈';
- if (['mp3', 'wav', 'flac'].includes(ext)) return '🎵';
- if (['mp4', 'mov', 'avi'].includes(ext)) return '🎬';
- if (['zip', 'rar', '7z'].includes(ext)) return '🗜️';
-
- return '📄';
- },
-
- // 格式化文件大小
- formatFileSize(bytes) {
- if (bytes === 0) return '0 B';
-
- const k = 1024;
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .upload-container {
- .upload-hint {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- width: 80px;
- height: 80px;
- border: 1px dashed #d9d9d9;
- border-radius: 4px;
- background-color: #fafafa;
- color: #999;
- font-size: 12px;
-
- .file-icon {
- font-size: 24px;
- margin-bottom: 4px;
- }
- }
-
- .file-list {
- margin-top: 10px;
-
- .file-item {
- display: flex;
- align-items: center;
- padding: 8px 0;
- border-bottom: 1px solid #eee;
-
- .file-info {
- display: flex;
- align-items: center;
- flex: 1;
-
- .file-thumb {
- width: 32px;
- height: 32px;
- margin-right: 10px;
- border-radius: 4px;
- background-color: #f5f5f5;
- display: flex;
- align-items: center;
- justify-content: center;
-
- image {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 4px;
- }
-
- .file-icon {
- font-size: 20px;
- }
- }
-
- .file-details {
- .file-name {
- font-size: 14px;
- color: #333;
- max-width: 200px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .file-size {
- font-size: 12px;
- color: #999;
- }
- }
- }
- }
- }
- }
- </style>
|