应用场景
添加用户时支持上传图片作为头像显示,是前端获取到添加用户的所有参数后进行与后端交互,所以,我选择了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值来适用不同场景
本篇文章到这里就结束了,希望对小伙们有帮助