<template><n-upload class="customUpload" :show-remove-button="remove" v-model:file-list="fileList" :action="uploadUrl" :headers="headers" :max="max" multiple list-type="image-card" @change="onUploadChange" @finish="onFinish" @update:file-list="onFileListChange" @remove="onRemove">点击上传</n-upload>
</template><script setup>
import { useGlobalStore } from '@/store';
const GlobalStore = useGlobalStore();
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + GlobalStore.token });const emit = defineEmits(['update:modelValue']);
// 回显 传数字字符串回来 逗号分隔
const props = defineProps({modelValue: {type: String,default: ''},// 图片数量限制max: {type: Number,default: 1,},// 大小限制(MB)fileSize: {type: Number,default: 1,},remove:{type:Boolean,default:true},// 文件类型, 例如['png', 'jpg', 'jpeg']fileType: {type: Array,default: () => ["png", "jpg", "jpeg"],},
});
const fileList = ref([]);// 回显图片 只第一次fileList是空的时候 回显
watch(() => props.modelValue, val => { if (val && !fileList.value.length) { fileList.value = props.modelValue.split(',').map(url=>{return {id: url,name: url,url: url,status: 'finished'}})}
}, { immediate: true });// 每次上传完成
function onFinish({file,event}) {const ret = JSON.parse(event.target.response);if(ret && ret.code == 200 && ret.data&&ret.data.url){file.url = ret.data.url;}else{file.status = 'error';window.$message.error(ret.msg || '上传失败')}return file;
}
// 上传结束赋值
function onUploadChange(data){fileList.value = data.fileList;
}
// 监听fileList变化,返回修改上层值
function onFileListChange(){emit('update:modelValue',listUrlStr);
}
function onRemove(){return new Promise((resolve,reject)=>{window.$dialog.create({title: "提示",content: "是否删除选中项?",positiveText: "确定",negativeText: "取消",loading: false,onPositiveClick: () => {resolve();},onNegativeClick: ()=>{reject();}});})
}
// 处理数组字符串
const listUrlStr = computed(()=>{ let str = '';if(fileList.value.length){let arr = fileList.value.filter(it=>it.status=='finished').map(it=>it.url);str = arr.join(',')}return str;
})
</script><style lang='scss' scoped>
.customUpload {// 上传图片 按钮之间的间距 增加点:deep(.n-upload-file-list ){.n-upload-file .n-upload-file-info .n-upload-file-info__action .n-button:not(:last-child) {margin-right: 10px !important;}}
}
.bigPhotoUpload {:deep(.n-upload-file-list ){display: block !important;.n-upload-trigger,.n-upload-file {width: 100%;height: auto;aspect-ratio: 16/9;}}
}
</style>
思想逻辑:
当用户点击上传按钮,选择文件,触发@change事件,执行onUploadChange函数,将上传文件添加到fileList
然后开始上传文件,通过n-upload
使用 action
指定的接口地址发送 HTTP 请求上传文件。上传请求需要包含: 上传的文件内容、请求头、其他参数
上传完成之后,n-upload
自动触发 @finish
事件,执行onFinish函数,解析服务器返回的JSON数据,如果上传成功,提取返回的文件URL并更新到file.url,如果上传失败,更新文件状态为 error
,并显示提示信息。
文件上传成功之后,由于上传的文件地址修改为服务器中文件的地址,由于我们的fileList是响应式的,所以对于其中的属性发生变化的话fileList的值会更新,因此会触发onUploadChange函数,更新
fileList的值
当fileList的值发生变化之后,会导致listUrlStr一起发生变化
所以当每次fileList被更新时,会触发@update:file-list
事件调用 onFileListChange,将文件列表的最终URL字符串同步到父组件中。
分析:
1.
<n-upload class="customUpload" :show-remove-button="remove" v-model:file-list="fileList" :action="uploadUrl" :headers="headers" :max="max" multiple list-type="image-card" @change="onUploadChange" @finish="onFinish" @update:file-list="onFileListChange" @remove="onRemove">点击上传
</n-upload>
vue3项目中使用n-upload组件来处理文件上传:
-
class="customUpload"
: 用来为上传组件添加样式类。 :show-remove-button="remove"
: 控制是否显示“删除”按钮v-model:file-list="fileList"
: 双向绑定上传文件列表,fileList
是一个响应式数据,用来存储上传的文件信息。:action="uploadUrl"
: 指定上传的 URL 地址(即图片上传的接口):headers="headers"
: 设置上传请求的 HTTP headers,通常用于身份验证等信息。:max="max"
: 上传文件的最大数量。multiple
: 允许多文件上传。list-type="image-card"
: 设置文件列表展示类型,这里使用的是图片卡片形式。@change="onUploadChange"
: 上传文件列表发生变化时触发的事件处理函数。@finish="onFinish"
: 每个文件上传结束后触发的事件,处理服务器响应。@update:file-list="onFileListChange"
: 每次文件列表变化时触发,更新外部组件的fileList
。@remove="onRemove"
: 删除文件时触发的事件,弹出确认对话框确认删除。
2.
import { useGlobalStore } from '@/store';
const GlobalStore = useGlobalStore();
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload");
const headers = ref({ Authorization: "Bearer " + GlobalStore.token });
useGlobalStore
: 从 Vuex store 中获取全局状态(如token
)。uploadUrl
: 存储上传接口的 URL 地址,默认从环境变量获取。headers
: 设置上传请求的 headers,主要用于身份验证。
3.
const emit = defineEmits(['update:modelValue']);
const props = defineProps({modelValue: {type: String,default: ''},max: {type: Number,default: 1,},fileSize: {type: Number,default: 1,},remove: {type: Boolean,default: true},fileType: {type: Array,default: () => ["png", "jpg", "jpeg"],},
});
modelValue
: 外部组件传递的值,通常用于回显上传的文件 URLs,类型为字符串,值为逗号分隔的 URL。max
: 限制上传的文件数量,默认 1。fileSize
: 限制文件大小(单位为 MB),默认 1MB。remove
: 是否显示文件删除按钮,默认为true
。fileType
: 支持的文件类型,默认为图片类型(png
,jpg
,jpeg
)。
const fileList = ref([]);watch(() => props.modelValue, val => { if (val && !fileList.value.length) { fileList.value = props.modelValue.split(',').map(url => {return {id: url,name: url,url: url,status: 'finished'}});}
}, { immediate: true });
fileList
: 通过ref
创建响应式数据,存储上传的文件列表。watch
: 监听modelValue
的变化(通常用于回显已上传的文件)。如果modelValue
有值且fileList
为空,则将字符串modelValue
解析为文件列表。
function onFinish({file, event}) {const ret = JSON.parse(event.target.response);if(ret && ret.code == 200 && ret.data && ret.data.url) {file.url = ret.data.url;} else {file.status = 'error';window.$message.error(ret.msg || '上传失败');}return file;
}
onFinish
: 文件上传完成后触发的回调函数。解析上传服务器的响应并处理返回的文件 URL。如果上传失败,显示错误消息。
function onUploadChange(data) {fileList.value = data.fileList;
}
onUploadChange
: 每次上传文件列表变化时触发,更新fileList
。
function onFileListChange() {emit('update:modelValue', listUrlStr);
}
onFileListChange
: 当文件列表变化时,通过emit
触发update:modelValue
事件,将文件 URL 列表作为逗号分隔的字符串传递给父组件。
function onRemove() {return new Promise((resolve, reject) => {window.$dialog.create({title: "提示",content: "是否删除选中项?",positiveText: "确定",negativeText: "取消",loading: false,onPositiveClick: () => {resolve();},onNegativeClick: () => {reject();}});});
}
onRemove
: 当用户点击删除按钮时,弹出确认对话框,确认是否删除文件。
4.处理上传文件的 URL 字符串
const listUrlStr = computed(() => {let str = '';if (fileList.value.length) {let arr = fileList.value.filter(it => it.status == 'finished').map(it => it.url);str = arr.join(',');}return str;
});
listUrlStr
: 计算属性,用于处理上传完成的文件 URL,将 fileList
中状态为 finished
的文件 URL 提取出来,拼接成逗号分隔的字符串,便于传递给父组件。
上传一张图片的步骤:
1.先触发@change
事件,执行onUploadChange,将当前文件的值赋值给fileList值
2.此时 fileList 值已经发生改变,listUrlStr
会被自动重新计算。
3.开始上传文件,然后将给定的接口地址发送http请求上传到服务器上面
4.上传完成之后,会触发@finish
事件,执行onFinish函数,更新 file.url
为服务器返回的 URL
5.由于文件地址发生变化,会触发@update:file-list事件,执行onFileListChange函数,这个函数负责将计算属性listUrlStr
的值传递给父组件,从而更新父组件中的 modelValue
。
6.所以在父组件中,使用v-model绑定一个地址,子组件中modelValue
(通过 update:modelValue
)。每次 fileList
更新,listUrlStr
会计算出最新的 URL 字符串,父组件会接收到这个更新,从而同步 form.logoUrl
的值。