VUE3 + TS + el-upload实现头像上传

应用场景

添加用户时支持上传图片作为头像显示,是前端获取到添加用户的所有参数后进行与后端交互,所以,我选择了el-upload :auto-upload="false" 手动上传的形式,还使用 list-type 属性来设定文件列表的样式,想要二次封装el-uplod,用来专门上传图片类型文件

接口定义

// 添加or编辑用户
export type UserForm = {
    username: string,
    password?: string,
    confirm?: string,
    /** 用户头像 */
    avatar?: File | string,
    remark: string,
    role: number,
}

el-upload组件封装与使用

手动上传,auto-upload=“false”,

不需要上传地址,设置action='#',

fileListc存放我们的文件

ps:  手动上传没有文件上传成功、失败的钩子

 <el-upload class="avatar-uploader" :class="fileList.length < limit ? '' : 'restrict-uploader'"
        v-model:file-list="fileList" action="#" list-type="picture-card" :auto-upload="false"
        :on-preview="handlePictureCardPreview">
        <el-icon>
            <Plus />
        </el-icon>
    </el-upload>

我们拿到文件数据,怎么交给父组件使用,我这里尝试过多种方法,使用父子组件传值,on-change文件上传状态改变的钩子,watch监听,最终敲定使用 defineExpose

// 文件列表暴露给父组件
defineExpose({
    fileList
})


// file-list本身是一个双向绑定的值,赋值defineProps里的数据会报错
// defineProps<{
//     fileList: UploadUserFile[]
// }>()

//  on-change 添加、删除、成功、失败都会调用
// const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
//    任务队列排在了双向绑定前面,使用定时器感觉不太优雅,触发频繁
//     setTimeout(() => {
//         console.log(fileList, 1); // fileList有值
//     }, 0)
//     console.log(fileList, 2); // fileList无值
// }

// watch(fileList, (n, o) => {
// 用户每一次操作都会触发,太蠢了
//     console.log(n, o); // fileList有值
// })

父组件如何使用子组件defineExpose暴露出的属性

1,父组件映入子组件,给子组件设置一个ref  

2,const 一个常量,名称和设置的ref保持一致,类型定义是固定写法InstanceType<typeof ‘引入的子组件名称’>

<template>
    <!-- .... -->
    <MyPicUpload ref="MyPicUploadRef" size="120px" :limit="3" />
    <!-- .... -->
</template>

<script setup lang='ts'>
import MyPicUpload from '@/components/MyPicUpload.vue';

const MyPicUploadRef = ref<InstanceType<typeof MyPicUpload>>()

console.log(MyPicUploadRef.value?.fileList)
</script>

图片回显

当我们编辑的用户时,想要显示用户原有头像,也可以借助这个组件显示,只需要fileList添加指定格式的数据 

 MyPicUploadRef.value?.fileList.push({
    name: 'food.jpeg',
    //  图片完整路径
    url: 'https://fuss10.elemecdn.com/3/63/....,
  },)

表单提交

const userSubmit = async () => {
    // 获取子组件文件数据
    if (MyPicUploadRef.value?.fileList.length) {
        // raw 新建时上传的图片文件
        // url 编辑时用户原本的图片路径 根据实际情况自行截取
        const { raw, url } = MyPicUploadRef.value.fileList[0]
        userForm.avatar = raw || url
    }
    let formData = new FormData()

    // 不能直接for in, 大概意思是key类型不缺定, 
    // js 自动将对象的key转成了字符串, ts没有

    for (const [key, val] of Object.entries(userForm)) {
        //  有一个重载的报错  使用了any
        // 个人理解 错误原因  val 的类型可能是接口定义中的 string, number, File
        //  key = username , name的类型是string  而不是 string | number | File
        formData.append(key, val as any)
    }

    // 先声明key的类型
    // let key: keyof UserForm
    // for (key in userForm) {
    //     formData.set(key, userForm[key] as any)
    // }

    await createUserApi(formData)
}

完整代码

父组件

<template>
    <div class="container">
        <div class="header">
            <el-input v-model="search" placeholder="请输入" style="width: 220px; margin-right:10px"></el-input>
            <el-button type="primary">检索</el-button>
            <el-button type="success" icon="Plus" @click="userVisible = true">新建</el-button>
        </div>
        <div class="main">
            <el-table :data="user_list" stripe height="calc(100% - 40px)">
                <el-table-column prop="name" label="用户">
                    <template #default="{ row }">
                        <el-avatar class="user-avatar" :size="50" :src="row.avatar || avatar" />
                        <span>{{ row.name }}</span>
                    </template>
                </el-table-column>
                <el-table-column prop="role" label="身份" />
                <el-table-column prop="remark" label="备注" />
            </el-table>
        </div>

        <el-dialog v-model="userVisible" title="新建用户" width="500">
            <el-form :model="userForm" label-width="80px">
                <el-form-item name="username" label="用户名">
                    <el-input v-model="userForm.username" placeholder="请输入用户名"></el-input>
                </el-form-item>
                <el-form-item name="username" label="密码">
                    <el-input v-model="userForm.password" placeholder="请输入密码"></el-input>
                </el-form-item>
                <el-form-item name="username" label="确认密码">
                    <el-input v-model="userForm.confirm" placeholder="请确认密码"></el-input>
                </el-form-item>
                <el-form-item name="avatar" label="头像">
                    <MyPicUpload ref="MyPicUploadRef" size="120px" :limit="1" />
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="userVisible = false">取消</el-button>
                    <el-button type="primary" @click="userSubmit">
                        确认
                    </el-button>
                </div>
            </template>
        </el-dialog>

    </div>
</template>

<script setup lang='ts'>
import { getUserListApi, createUserApi } from '@/api/user';
import { ref, reactive, onMounted } from 'vue';
import type { UserForm } from '@/types/user';
import MyPicUpload from '@/components/MyPicUpload.vue';
// require 不适用 
// require('@/assets/image/avatar.png') 

// 单文件引入
// import avatar from '@/assets/image/avatar.png'
// 多文件引入 介绍用法
import { getAssetsFile } from '@/utils/imageUrl';
const avatar = getAssetsFile('avatar.png')


const MyPicUploadRef = ref<InstanceType<typeof MyPicUpload>>()
const search = ref()  // 查询字段
const userVisible = ref(false) // 用户弹框
const user_list = ref() // 用户列表
//  表单数据
const userForm = reactive<UserForm>({
    username: '',
    password: '',
    confirm: '',
    avatar: '',
    remark: '',
    role: 2
})


onMounted(async () => {
    const res = await getUserListApi()
    user_list.value = res.result

})


const userSubmit = async () => {
    // 获取子组件文件数据
    if (MyPicUploadRef.value?.fileList.length) {
        userForm.avatar = MyPicUploadRef.value.fileList[0].raw
    }
    let formData = new FormData()

    for (const [key, val] of Object.entries(userForm)) {
        formData.append(key, val as any)
    }

    // 先声明key的类型
    // let key: keyof UserForm
    // for (key in userForm) {
    //     if (userForm.hasOwnProperty(key)) {
    //         formData.set(key, userForm[key] as any)
    //     }
    // }
    await createUserApi(formData)
}

</script>

<style lang="scss" scoped>
@import '@/assets/styles/frame.scss';

.user-avatar {
    vertical-align: middle;
    margin-right: 10px
}
</style>

子组件

<template>
    <el-upload class="avatar-uploader" :class="fileList.length < limit ? '' : 'restrict-uploader'"
        v-model:file-list="fileList" action="#" :limit="limit" list-type="picture-card" :auto-upload="false"
        :on-preview="handlePictureCardPreview">
        <el-icon>
            <Plus />
        </el-icon>
    </el-upload>

    <el-dialog v-model="avatarVisible">
        <img w-full :src="avatarImageUrl" alt="Preview Image" />
    </el-dialog>
</template>

<script setup lang='ts'>
import { ref } from 'vue'
import type { UploadProps, UploadUserFile } from 'element-plus'

const fileList = ref<UploadUserFile[]>([])

defineProps<{
    size: string, // 上传窗口大小
    limit: number // 物理层限制文件上传数量
}>()



const avatarVisible = ref(false) // 头像大图预览弹框
const avatarImageUrl = ref() // 头像大图预览地址

// 图片预览
const handlePictureCardPreview: UploadProps['onPreview'] = (file) => {
    avatarImageUrl.value = file.url
    avatarVisible.value = true
}

// 文件列表暴露给父组件
defineExpose({
    fileList
})



</script>

<style lang="scss" scoped>
.avatar-uploader {
    :deep(.el-upload-list--picture-card) {
        --el-upload-list-picture-card-size: v-bind(size)
    }

    :deep(.el-upload--picture-card) {
        --el-upload-picture-card-size: v-bind(size)
    }
}


.restrict-uploader {
    :deep(.el-upload) {
        display: none;
    }
}
</style>

代码补充,批量引入静态资源

export const getAssetsFile = (urlName: string) => {
    // `../assets/image/${urlName}`替换为自己项目路径
    return new URL(`../assets/image/${urlName}`, import.meta.url).href;
}

实现效果

可自行修改limit值来适用不同场景

本篇文章到这里就结束了,希望对小伙们有帮助

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对你的问题,我可以给你提供一些思路和代码示例。 首先,你可以使用 `el-upload` 组件的 `before-upload` 和 `action` 属性来实现自定义上传图片的功能。具体来说,`before-upload` 用于在上传图片之前进行一些处理,而 `action` 则指定了上传图片的地址。 其次,在使用 `typescript` 时,你需要定义上传图片的数据类型。在 `vue3` 中,你可以使用 `defineComponent` 函数来定义组件,并使用 `ref` 来引用组件中的数据。 下面是一个示例代码,供你参考: ``` <template> <el-upload class="upload-demo" :action="uploadUrl" :before-upload="beforeUpload" :show-file-list="false"> <el-button size="small" type="primary">点击上传</el-button> </el-upload> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; interface UploadResponse { code: number; data: string; } export default defineComponent({ setup() { const uploadUrl = ref('your_upload_url'); const beforeUpload = (file: File) => { // 在上传之前处理图片 return true; }; const handleUploadSuccess = (response: UploadResponse) => { // 处理上传成功后的响应 }; return { uploadUrl, beforeUpload, handleUploadSuccess, }; }, }); </script> ``` 在这个示例中,我定义了一个 `UploadResponse` 接口来表示上传图片后的响应数据类型。在 `setup` 函数中,我使用 `ref` 来定义了 `uploadUrl` 变量,并将其传递给 `el-upload` 组件的 `action` 属性。同时,我还定义了 `beforeUpload` 和 `handleUploadSuccess` 函数来处理上传前和上传后的数据。 当然,这只是一个简单的示例,你还需要根据具体的需求进行代码的修改和优化。希望这些信息能够对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值