html
<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字符串同步到父组件中。
分析:
html
<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"
: 删除文件时触发的事件,弹出确认对话框确认删除。
html
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,主要用于身份验证。
html
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
)。
html
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
解析为文件列表。
javascript
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。如果上传失败,显示错误消息。
javascript
function onUploadChange(data) {
fileList.value = data.fileList;
}
onUploadChange
: 每次上传文件列表变化时触发,更新fileList
。
javascript
function onFileListChange() {
emit('update:modelValue', listUrlStr);
}
onFileListChange
: 当文件列表变化时,通过emit
触发update:modelValue
事件,将文件 URL 列表作为逗号分隔的字符串传递给父组件。
javascript
function onRemove() {
return new Promise((resolve, reject) => {
window.$dialog.create({
title: "提示",
content: "是否删除选中项?",
positiveText: "确定",
negativeText: "取消",
loading: false,
onPositiveClick: () => {
resolve();
},
onNegativeClick: () => {
reject();
}
});
});
}
onRemove
: 当用户点击删除按钮时,弹出确认对话框,确认是否删除文件。
4.处理上传文件的 URL 字符串
javascript
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
的值。