279 lines
6.8 KiB
Vue
279 lines
6.8 KiB
Vue
<template>
|
||
<view style="width: 100%;">
|
||
<button @click="choseFiles" size="mini" type="primary" :disabled="disabled">选择</button>
|
||
<view v-for="(item, index) in fileList" :key="index" style="height: 16px; margin-bottom: 12px;">
|
||
<view style="display:flex; justify-content: space-between;">
|
||
<view style="color:blue" @click="preview(item)">{{ item.name }}</view>
|
||
<view style="color:red;font-weight: bold;" @click="handleRemove(index)" v-if="disabled == false">×</view>
|
||
</view>
|
||
<view style="height:2px"></view>
|
||
<progress :percent="item.percent" activeColor="#4caf50" backgroundColor="#e0e0e0" stroke-width="2" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
// FILE_TYPE_DOC = 10
|
||
// FILE_TYPE_VIDEO = 20
|
||
// FILE_TYPE_AUDIO = 30
|
||
// FILE_TYPE_PIC = 40
|
||
// FILE_TYPE_OTHER = 50
|
||
import config from '@/utils/config.js'
|
||
import { ref, onMounted, watch, defineEmits } from 'vue';
|
||
const emit = defineEmits(["update:modelValue", "update:obj"]);
|
||
|
||
const props = defineProps({
|
||
xtype: { type: String, default: "path" }, // path 或 id
|
||
modelValue: { type: [Array, String], default: undefined },
|
||
obj: { type: Object, default: () => ({}) },
|
||
accept: { type: String, default: '' },
|
||
countMax: { type: Number, default: 1 },
|
||
sizeMax: { type: Number, default: 1024 * 1024 * 5 },
|
||
disabled: { type: Boolean, default: false },
|
||
});
|
||
|
||
// ⚠ reactive([]) 不能整体赋值,用 ref 更合理
|
||
const fileList = ref([])
|
||
const disabled = ref(props.disabled)
|
||
|
||
// header / action
|
||
const header = {
|
||
Authorization: `Bearer ${uni.getStorageSync('access')}`
|
||
}
|
||
const action = `${config.baseUrl}/file/`
|
||
|
||
const inited = ref(false)
|
||
onMounted(() => {
|
||
disabled.value = true
|
||
checkPropsUntilReady()
|
||
})
|
||
|
||
const checkPropsUntilReady = () => {
|
||
if (inited.value) return
|
||
|
||
if (props.modelValue !== undefined) {
|
||
initFileList()
|
||
if (props.disabled == false) {
|
||
disabled.value = false
|
||
}
|
||
inited.value = true
|
||
return
|
||
}
|
||
// ⭐ 每 200ms 查一次(非常轻量)
|
||
setTimeout(checkPropsUntilReady, 200)
|
||
}
|
||
|
||
const changeVal = () => {
|
||
let val = null;
|
||
if (props.xtype == "id") {
|
||
if (props.countMax > 1){
|
||
val= []
|
||
fileList.value.forEach(item => {
|
||
if(item.id){
|
||
val.push(item.id)
|
||
}
|
||
})
|
||
}else if (props.countMax == 1){
|
||
val = null;
|
||
if (fileList.value && fileList.value.length > 0 && fileList.value[0].id) {
|
||
val = fileList.value[0].id
|
||
}
|
||
}
|
||
}else if (props.xtype == "path"){
|
||
if (props.countMax > 1){
|
||
val = []
|
||
fileList.value.forEach(item => {
|
||
if(item.path && item.percent == 100){
|
||
val.push(item.path)
|
||
}
|
||
})
|
||
}else if (props.countMax == 1){
|
||
val = null;
|
||
if (fileList.value && fileList.value.length > 0 && fileList.value[0].path && fileList.value[0].percent == 100) {
|
||
val = fileList.value[0].path
|
||
}
|
||
}
|
||
}
|
||
|
||
let obj = null;
|
||
emit("update:modelValue", val)
|
||
if (props.countMax > 1) {
|
||
obj = fileList.value
|
||
emit("update:obj", obj)
|
||
}else{
|
||
if (fileList.value && fileList.value.length > 0) {
|
||
obj = fileList.value[0]
|
||
emit("update:obj", obj)
|
||
} else {
|
||
emit("update:obj", obj)
|
||
}
|
||
}
|
||
console.log(val, obj)
|
||
}
|
||
|
||
const getFileType = (path) =>{
|
||
let type = path.substring(path.lastIndexOf('.') + 1, path.length).toLowerCase()
|
||
if (type === 'png' || type === 'jpg' || type === 'jpeg' || type === 'gif' || type === 'bmp') {
|
||
return 40
|
||
}
|
||
else if (type === 'mp4' || type === 'avi' || type === 'rmvb') {
|
||
return 20
|
||
}
|
||
else if (type === 'mp3' || type === 'wav' || type === 'wma') {
|
||
return 30
|
||
}
|
||
return 10
|
||
}
|
||
/**
|
||
* 初始化 fileList
|
||
*/
|
||
const initFileList = () => {
|
||
if (props.countMax > 1) {
|
||
let clist = []
|
||
|
||
// xtype = id 情况
|
||
if (props.xtype === "id" && Array.isArray(props.obj) && props.obj.length > 0) {
|
||
|
||
props.obj.forEach(item => {
|
||
clist.push({
|
||
...item,
|
||
url: `${config.hostUrl}${item.path}`,
|
||
percent: 100
|
||
})
|
||
})
|
||
fileList.value = clist
|
||
}
|
||
else if (props.xtype === "path" && props.modelValue) {
|
||
props.modelValue.forEach(item => {
|
||
clist.push({
|
||
path: item,
|
||
url: `${config.hostUrl}${item}`,
|
||
percent: 100,
|
||
type: getFileType(item)
|
||
})
|
||
})
|
||
fileList.value = clist
|
||
}
|
||
} else if (props.countMax == 1) {
|
||
let clist = []
|
||
if (props.xtype === "id" && props.modelValue) {
|
||
clist.push({
|
||
...props.obj,
|
||
url: `${config.hostUrl}${props.obj.path}`,
|
||
percent: 100
|
||
})
|
||
fileList.value = clist
|
||
} else if (props.xtype === "path" && props.modelValue) {
|
||
clist.push({
|
||
path: props.modelValue,
|
||
url: `${config.hostUrl}${props.modelValue}`,
|
||
percent: 100,
|
||
type: getFileType(props.obj.path)
|
||
})
|
||
fileList.value = clist
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleRemove = (index) => {
|
||
let upTask = fileList.value[index].upTask
|
||
if (upTask) {
|
||
upTask.abort()
|
||
}
|
||
fileList.value.splice(index, 1)
|
||
changeVal()
|
||
}
|
||
|
||
const preview = (item) => {
|
||
if (item.type == 40) {
|
||
// 获取所有类型为 40 的图片 URL
|
||
const urls = fileList.value
|
||
.filter(file => file.type === 40)
|
||
.map(file => file.url)
|
||
|
||
// 找到当前图片的索引
|
||
const currentIndex = urls.indexOf(item.url)
|
||
|
||
uni.previewImage({
|
||
current: currentIndex >= 0 ? currentIndex : 0,
|
||
urls: urls,
|
||
})
|
||
}
|
||
}
|
||
|
||
const choseFiles = () => {
|
||
if (fileList.value.length >= props.countMax) {
|
||
uni.showToast({
|
||
title: `最多只能上传${props.countMax}个文件`,
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
uni.chooseFile({
|
||
count: props.countMax - fileList.value.length,
|
||
success(res) {
|
||
const tempFiles = res.tempFiles;
|
||
|
||
tempFiles.forEach(item => {
|
||
// 文件大小校验(单位:字节)
|
||
if (item.size > props.sizeMax * 1024 * 1024) {
|
||
uni.showToast({
|
||
title: `文件大小不能超过${props.sizeMax}M`,
|
||
icon: 'none'
|
||
})
|
||
return;
|
||
}
|
||
|
||
// 创建上传任务
|
||
const upTask = uni.uploadFile({
|
||
url: action,
|
||
filePath: item.path,
|
||
name: 'file',
|
||
formData: {},
|
||
header: header,
|
||
success: (uploadRes) => {
|
||
const data = JSON.parse(uploadRes.data || "{}")
|
||
if (data) {
|
||
fileList.value.forEach((file, index) => {
|
||
if (file.path === item.path) {
|
||
fileList.value[index].url = `${config.hostUrl}${data.path}`
|
||
fileList.value[index].path = data.path
|
||
fileList.value[index].percent = 100
|
||
fileList.value[index].id = data.id
|
||
fileList.value[index].upTask = null
|
||
}
|
||
})
|
||
// console.log("fv", fileList.value)
|
||
changeVal()
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.log("上传失败:", err)
|
||
}
|
||
})
|
||
|
||
// 添加到列表
|
||
fileList.value.push({
|
||
name: item.name,
|
||
url: null,
|
||
path: item.path,
|
||
percent: 0,
|
||
type: getFileType(item.name),
|
||
upTask: upTask
|
||
})
|
||
|
||
// 🔥 关键修复:关联上传进度
|
||
upTask.onProgressUpdate((res) => {
|
||
fileList.value.forEach((file, index) => {
|
||
if (file.path === item.path) {
|
||
fileList.value[index].percent = res.progress
|
||
}
|
||
})
|
||
});
|
||
})
|
||
}
|
||
})
|
||
}
|
||
</script>
|