You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

426 lines
9.1 KiB

<template>
<view class="upload-file">
<view class="upload-area" @click="chooseFile">
<view v-if="!uploading" class="upload-placeholder">
<text class="upload-icon">{{ getUploadIcon() }}</text>
<text class="upload-text">{{ getUploadText() }}</text>
</view>
<view v-if="uploading" class="uploading">
<text class="upload-icon">⏳</text>
<text class="upload-text">上传中...</text>
<progress :percent="uploadProgress" show-info />
</view>
<!-- <view v-if="fileInfo && !uploading" class="file-info">
<text class="file-name">{{ fileInfo.name }}</text>
<text class="file-size">{{ formatFileSize(fileInfo.size) }}</text>
</view> -->
</view>
<!-- <view v-if="error" class="error-message">
<text>{{ error }}</text>
</view> -->
</view>
</template>
<script setup>
import { ref } from "vue";
import {
useAuthStore
} from "@/store/modules/auth";
// 定义 props
const props = defineProps({
// 允许的文件类型
accept: {
type: String,
default: "*",
},
// 最大文件大小 (MB)
maxSize: {
type: Number,
default: 10,
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
});
// 定义 emits
const emit = defineEmits(["onSuccess", "onError", "onProgress"]);
// 响应式数据
const uploading = ref(false);
const uploadProgress = ref(0);
const fileInfo = ref(null);
const error = ref(null);
// 选择文件
const chooseFile = () => {
if (props.disabled || uploading.value) return;
error.value = null;
// 根据 accept 属性判断文件类型并选择对应的方法
const fileType = getFileTypeFromAccept();
if (fileType === "image") {
chooseImageFile();
} else if (fileType === "video") {
chooseVideoFile();
} else {
chooseMessageFile();
}
};
// 根据 accept 属性判断文件类型
const getFileTypeFromAccept = () => {
if (props.accept === "*") return "file";
const accept = props.accept.toLowerCase();
// 检查是否为图片类型
const imageTypes = [
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".webp",
"image/",
];
if (imageTypes.some((type) => accept.includes(type))) {
return "image";
}
// 检查是否为视频类型
const videoTypes = [
".mp4",
".avi",
".mov",
".wmv",
".flv",
".webm",
"video/",
];
if (videoTypes.some((type) => accept.includes(type))) {
return "video";
}
// 其他文件类型
return "file";
};
// 选择图片
const chooseImageFile = () => {
uni.chooseImage({
count: 1,
sourceType: ["album", "camera"],
success: (res) => {
console.log("选择图片成功:", res);
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
// 获取文件信息
uni.getFileInfo({
filePath: res.tempFilePaths[0],
success: (fileInfo) => {
const file = {
name: "image_" + Date.now() + ".jpg",
size: fileInfo.size,
path: res.tempFilePaths[0],
};
validateAndUpload(file);
},
fail: () => {
// 如果获取文件信息失败,使用默认值
const file = {
name: "image_" + Date.now() + ".jpg",
size: 0,
path: res.tempFilePaths[0],
};
validateAndUpload(file);
},
});
} else {
error.value = "未选择到图片";
}
},
fail: (err) => {
console.error("选择图片失败:", err);
error.value = "选择图片失败: " + (err.errMsg || "请重试");
emit("onError", err);
},
});
};
// 选择视频
const chooseVideoFile = () => {
uni.chooseVideo({
sourceType: ["album", "camera"],
maxDuration: 60,
camera: "back",
success: (res) => {
console.log("选择视频成功:", res);
const file = {
name: "video_" + Date.now() + ".mp4",
size: res.size || 0,
path: res.tempFilePath,
};
validateAndUpload(file);
},
fail: (err) => {
console.error("选择视频失败:", err);
error.value = "选择视频失败: " + (err.errMsg || "请重试");
emit("onError", err);
},
});
};
// 选择文件
const chooseMessageFile = () => {
uni.chooseMessageFile({
count: 1,
type: "all",
success: (res) => {
console.log("选择文件成功:", res);
if (res.tempFiles && res.tempFiles.length > 0) {
const file = res.tempFiles[0];
validateAndUpload(file);
} else {
error.value = "未选择到文件";
}
},
fail: (err) => {
console.error("选择文件失败:", err);
error.value = "选择文件失败: " + (err.errMsg || "请重试");
emit("onError", err);
},
});
};
// 验证文件类型
const validateFileType = (fileName) => {
if (props.accept === "*") return true;
const acceptTypes = props.accept.split(",").map((type) => type.trim());
const fileExt = "." + fileName.split(".").pop().toLowerCase();
return acceptTypes.some((type) => {
if (type.startsWith(".")) {
return type.toLowerCase() === fileExt;
}
return fileName.toLowerCase().includes(type.toLowerCase());
});
};
// 验证并上传文件
const validateAndUpload = (file) => {
error.value = null;
// 验证文件大小
if (file.size > props.maxSize * 1024 * 1024) {
error.value = `文件大小不能超过 ${props.maxSize}MB`;
return;
}
// 验证文件类型
if (props.accept !== "*" && !validateFileType(file.name)) {
error.value = "文件类型不支持";
return;
}
fileInfo.value = {
name: file.name,
size: file.size,
path: file.path,
};
uploadFile(file);
};
// 处理上传错误
const handleUploadError = (message) => {
uploading.value = false;
uploadProgress.value = 0;
error.value = message;
emit("onError", message);
};
// 上传文件
const uploadFile = (file) => {
uploading.value = true;
uploadProgress.value = 0;
console.log("开始上传文件:", file);
const uploadTask = uni.uploadFile({
url: "https://lift-dev.ruixininfo.com/filestransfer/upload",
filePath: file.path,
name: "file",
header:{
session:useAuthStore().accessToken
},
// formData: {
// fileName: file.name,
// },
success: (res) => {
console.log("上传成功:", res);
try {
const data = JSON.parse(res.data);
uploading.value = false;
uploadProgress.value = 100;
// 将接口返回信息传递给父组件的 onSuccess 方法
emit("onSuccess", {
response: data,
fileInfo: fileInfo.value,
});
} catch (err) {
console.error("解析响应失败:", err);
handleUploadError("响应数据解析失败");
}
},
fail: (err) => {
console.error("上传失败:", err);
handleUploadError(err.errMsg || "上传失败");
},
});
// 监听上传进度
uploadTask.onProgressUpdate((res) => {
console.log("上传进度:", res.progress);
uploadProgress.value = res.progress;
emit("onProgress", res.progress);
});
};
// 格式化文件大小
const formatFileSize = (bytes) => {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
// 获取上传图标
const getUploadIcon = () => {
const fileType = getFileTypeFromAccept();
switch (fileType) {
case "image":
return "🖼️";
case "video":
return "🎬";
default:
return "📁";
}
};
// 获取上传文字
const getUploadText = () => {
const fileType = getFileTypeFromAccept();
switch (fileType) {
case "image":
return "点击选择图片";
case "video":
return "点击选择视频";
default:
return "点击选择文件";
}
};
// 重置组件状态
const reset = () => {
uploading.value = false;
uploadProgress.value = 0;
fileInfo.value = null;
error.value = null;
};
// 暴露方法给父组件使用
defineExpose({
reset,
});
</script>
<style scoped>
.upload-file {
width: 100%;
}
.upload-area {
border: 2px dashed #dcdfe6;
border-radius: 8px;
padding: 0 20px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s;
background-color: #fafafa;
}
.upload-area:hover {
border-color: #409eff;
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.upload-icon {
font-size: 24px;
}
.upload-text {
color: #606266;
font-size: 14px;
}
.uploading {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.file-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.file-name {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.file-size {
font-size: 12px;
color: #909399;
}
.error-message {
margin-top: 10px;
padding: 8px 12px;
background-color: #fef0f0;
border: 1px solid #fbc4c4;
border-radius: 4px;
color: #f56c6c;
font-size: 12px;
text-align: center;
}
progress {
width: 100%;
height: 100%;
margin-top: 10px;
}
</style>