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