封装上传组件的详细步骤

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 的值。

相关推荐
PP东9 分钟前
ES6学习Symbol(五)
javascript·学习·es6
SomeB1oody11 分钟前
【Rust自学】3.5. 控制流:if else
开发语言·后端·rust
阿髙1 小时前
nginx 代理文件并下载,同时设置文件名,axios取不到Content-Disposition解决办法
前端·javascript·nginx
夕阳_醉了2 小时前
JS里面Map的使用以及与Object的对比
前端·javascript·vue.js
Q之路2 小时前
C++之多态
开发语言·c++
ling081408143 小时前
Vue3全局挂载Dialog组件
前端·javascript·vue
Catherinemin3 小时前
CSS|12 display属性
前端·css
Amo 67293 小时前
css filter: drop-shadow() 高级阴影效果
前端·css
天天进步20153 小时前
CSS中的深度选择器 deep 详解
前端·css