5-1表单组件-一个强大的表单组件应该具备哪些功能
<template>
<el-form
ref="form"
v-if="model"
:validate-on-rule-change="false"
:model="model"
:rules="rules"
v-bind="$attrs"
>
<template v-for="(item, index) in options" :key="index">
<el-form-item
v-if="!item.children || !item.children!.length"
:prop="item.prop"
:label="item.label"
>
<component
v-if="item.type !== 'upload' && item.type !== 'editor'"
:placeholder="item.placeholder"
v-bind="item.attrs"
:is="`el-${item.type}`"
v-model="model[item.prop!]"
></component>
<el-upload
v-if="item.type === 'upload'"
v-bind="item.uploadAttrs"
:on-preview="onPreview"
:on-remove="onRemove"
:on-success="onSuccess"
:on-error="onError"
:on-progress="onProgress"
:on-change="onChange"
:before-upload="beforeUpload"
:before-remove="beforeRemove"
:http-request="httpRequest"
:on-exceed="onExceed"
>
<slot name="uploadArea"></slot>
<slot name="uploadTip"></slot>
</el-upload>
<div id="editor" v-if="item.type === 'editor'"></div>
</el-form-item>
<el-form-item
v-if="item.children && item.children.length"
:prop="item.prop"
:label="item.label"
>
<component
:placeholder="item.placeholder"
v-bind="item.attrs"
:is="`el-${item.type}`"
v-model="model[item.prop!]"
>
<component
v-for="(child, i) in item.children"
:key="i"
:is="`el-${child.type}`"
:label="child.label"
:value="child.value"
></component>
</component>
</el-form-item>
</template>
<el-form-item>
<slot name="action" :form="form" :model="model"></slot>
</el-form-item>
</el-form>
</template>
<script lang='ts' setup>
import { PropType, ref, onMounted, watch, nextTick } from 'vue'
import { FormInstance, FormOptions } from './types/types'
import cloneDeep from 'lodash/cloneDeep'
import E from "wangeditor"
let emits = defineEmits(['on-preview', 'on-remove', 'on-success', 'on-error', 'on-progress', 'on-change', 'before-upload', 'before-remove', 'on-exceed'])
let props = defineProps({
// 表单的配置项
options: {
type: Array as PropType<FormOptions[]>,
required: true
},
// 用户自定义上传方法
httpRequest: {
type: Function
}
})
let model = ref<any>(null)
let rules = ref<any>(null)
let form = ref<FormInstance | null>()
let edit = ref()
// 初始化表单
let initForm = () => {
if (props.options && props.options.length) {
let m: any = {}
let r: any = {}
props.options.map((item: FormOptions) => {
m[item.prop!] = item.value
r[item.prop!] = item.rules
if (item.type === 'editor') {
// 初始化富文本
nextTick(() => {
if (document.getElementById('editor')) {
const editor = new E('#editor')
editor.config.placeholder = item.placeholder!
editor.create()
// 初始化富文本的内容
editor.txt.html(item.value)
editor.config.onchange = (newHtml: string) => {
model.value[item.prop!] = newHtml
}
edit.value = editor
}
})
}
})
model.value = cloneDeep(m)
rules.value = cloneDeep(r)
}
}
// 重置表单
let resetFields = () => {
// 重置element-plus的表单
form.value!.resetFields()
// 重置富文本编辑器的内容
// 获取到富文本的配置项
if (props.options && props.options.length) {
let editorItem = props.options.find(item => item.type === 'editor')!
edit.value.txt.html(editorItem.value)
}
}
// 表单验证方法
let validate = () => {
return form.value!.validate
}
// 获取表单数据
let getFormData = () => {
return model.value
}
// 分发方法
defineExpose({
resetFields,
validate,
getFormData
})
onMounted(() => {
initForm()
})
// 监听父组件传递进来的options
watch(() => props.options, () => {
initForm()
}, { deep: true })
// 上传组件的所有方法
let onPreview = (file: File) => {
emits('on-preview', file)
}
let onRemove = (file: File, fileList: FileList) => {
emits('on-remove', { file, fileList })
}
let onSuccess = (response: any, file: File, fileList: FileList) => {
// 上传图片成功 给表单上传项赋值
let uploadItem = props.options.find(item => item.type === 'upload')!
model.value[uploadItem.prop!] = { response, file, fileList }
emits('on-success', { response, file, fileList })
}
let onError = (err: any, file: File, fileList: FileList) => {
emits('on-error', { err, file, fileList, })
}
let onProgress = (event: any, file: File, fileList: FileList) => {
emits('on-progress', { event, file, fileList })
}
let onChange = (file: File, fileList: FileList) => {
emits('on-change', { file, fileList })
}
let beforeUpload = (file: File) => {
emits('before-upload', file)
}
let beforeRemove = (file: File, fileList: FileList) => {
emits('before-remove', { file, fileList })
}
let onExceed = (files: File, fileList: FileList) => {
emits('on-exceed', { files, fileList })
}
</script>
<style lang='scss' scoped>
</style>
5-2表单组件-使用ts定义表单配置项的数据类型
// 可配置的表单
import { CSSProperties } from 'vue'
import { RuleItem } from "./rule"
import { ValidateFieldsError } from 'async-validator'
interface Callback {
(isValid?: boolean, invalidFields?: ValidateFieldsError): void,
}
// 表单每一项的配置选项
export interface FormOptions {
// 表单项显示的元素
type: 'cascader' | 'checkbox' | 'checkbox-group' | 'checkbox-button' | 'color-picker' |
'date-picker' | 'input' | 'input-number' | 'radio' | 'radio-group' | 'radio-button' | 'rate' |
'select' | 'option' | 'slider' | 'switch' | 'time-picker' | 'time-select' |
'transfer' | 'upload' | 'editor',
// 表单项的值
value?: any,
// 表单项label
label?: string,
// 表单项的标识
prop?: string,
// 表单项的验证规则
rules?: RuleItem[],
// 表单项的占位符
placeholder?: string,
// 表单元素特有的属性
attrs?: {
// css样式
style?: CSSProperties,
clearable?: boolean,
showPassword?: boolean,
disabled?: boolean,
},
// 表单项的子元素
children?: FormOptions[],
// 处理上传组件的属性和方法
uploadAttrs?: {
action: string,
headers?: object,
method?: 'post' | 'put' | 'patch',
multiple?: boolean,
data?: any,
name?: string,
withCredentials?: boolean,
showFileList?: boolean,
drag?: boolean,
accept?: string,
thumbnailMode?: boolean,
fileList?: any[],
listType?: 'text' | 'picture' | 'picture-card',
autoUpload?: boolean,
disabled?: boolean,
limit?: number,
}
}
export interface ValidateFieldCallback {
(message?: string, invalidFields?: ValidateFieldsError): void,
}
export interface FormInstance {
registerLabelWidth(width: number, oldWidth: number): void,
deregisterLabelWidth(width: number): void,
autoLabelWidth: string | undefined,
emit: (evt: string, ...args: any[]) => void,
labelSuffix: string,
inline?: boolean,
model?: Record<string, unknown>,
size?: string,
showMessage?: boolean,
labelPosition?: string,
labelWidth?: string,
rules?: Record<string, unknown>,
statusIcon?: boolean,
hideRequiredAsterisk?: boolean,
disabled?: boolean,
validate: (callback?: Callback) => Promise<boolean>,
resetFields: () => void,
clearValidate: (props?: string | string[]) => void,
validateField: (props: string | string[], cb: ValidateFieldCallback) => void,
}
export type RuleType =
| 'string'
| 'number'
| 'boolean'
| 'method'
| 'regexp'
| 'integer'
| 'float'
| 'array'
| 'object'
| 'enum'
| 'date'
| 'url'
| 'hex'
| 'email'
| 'pattern'
| 'any';
export interface ValidateOption {
// whether to suppress internal warning
suppressWarning?: boolean;
// when the first validation rule generates an error stop processed
first?: boolean;
// when the first validation rule of the specified field generates an error stop the field processed, 'true' means all fields.
firstFields?: boolean | string[];
messages?: Partial<ValidateMessages>;
/** The name of rules need to be trigger. Will validate all rules if leave empty */
keys?: string[];
error?: (rule: InternalRuleItem, message: string) => ValidateError;
}
export type SyncErrorType = Error | string;
export type SyncValidateResult = boolean | SyncErrorType | SyncErrorType[];
export type ValidateResult = void | Promise<void> | SyncValidateResult;
export interface RuleItem {
type?: RuleType; // default type is 'string'
required?: boolean;
pattern?: RegExp | string;
min?: number; // Range of type 'string' and 'array'
max?: number; // Range of type 'string' and 'array'
len?: number; // Length of type 'string' and 'array'
enum?: Array<string | number | boolean | null | undefined>; // possible values of type 'enum'
whitespace?: boolean;
trigger?: string | string[];
fields?: Record<string, Rule>; // ignore when without required
options?: ValidateOption;
defaultField?: Rule; // 'object' or 'array' containing validation rules
transform?: (value: Value) => Value;
message?: string | ((a?: string) => string);
asyncValidator?: (
rule: InternalRuleItem,
value: Value,
callback: (error?: string | Error) => void,
source: Values,
options: ValidateOption,
) => void | Promise<void>;
validator?: (
rule: InternalRuleItem,
value: Value,
callback: (error?: string | Error) => void,
source: Values,
options: ValidateOption,
) => SyncValidateResult | void;
}
export type Rule = RuleItem | RuleItem[];
export type Rules = Record<string, Rule>;
/**
* Rule for validating a value exists in an enumerable list.
*
* @param rule The validation rule.
* @param value The value of the field on the source object.
* @param source The source object being validated.
* @param errors An array of errors that this rule may add
* validation errors to.
* @param options The validation options.
* @param options.messages The validation messages.
* @param type Rule type
*/
export type ExecuteRule = (
rule: InternalRuleItem,
value: Value,
source: Values,
errors: string[],
options: ValidateOption,
type?: string,
) => void;
/**
* Performs validation for any type.
*
* @param rule The validation rule.
* @param value The value of the field on the source object.
* @param callback The callback function.
* @param source The source object being validated.
* @param options The validation options.
* @param options.messages The validation messages.
*/
export type ExecuteValidator = (
rule: InternalRuleItem,
value: Value,
callback: (error?: string[]) => void,
source: Values,
options: ValidateOption,
) => void;
// >>>>> Message
type ValidateMessage<T extends any[] = unknown[]> =
| string
| ((...args: T) => string);
type FullField = string | undefined;
type EnumString = string | undefined;
type Pattern = string | RegExp | undefined;
type Range = number | undefined;
type Type = string | undefined;
export interface ValidateMessages {
default?: ValidateMessage;
required?: ValidateMessage<[FullField]>;
enum?: ValidateMessage<[FullField, EnumString]>;
whitespace?: ValidateMessage<[FullField]>;
date?: {
format?: ValidateMessage;
parse?: ValidateMessage;
invalid?: ValidateMessage;
};
types?: {
string?: ValidateMessage<[FullField, Type]>;
method?: ValidateMessage<[FullField, Type]>;
array?: ValidateMessage<[FullField, Type]>;
object?: ValidateMessage<[FullField, Type]>;
number?: ValidateMessage<[FullField, Type]>;
date?: ValidateMessage<[FullField, Type]>;
boolean?: ValidateMessage<[FullField, Type]>;
integer?: ValidateMessage<[FullField, Type]>;
float?: ValidateMessage<[FullField, Type]>;
regexp?: ValidateMessage<[FullField, Type]>;
email?: ValidateMessage<[FullField, Type]>;
url?: ValidateMessage<[FullField, Type]>;
hex?: ValidateMessage<[FullField, Type]>;
};
string?: {
len?: ValidateMessage<[FullField, Range]>;
min?: ValidateMessage<[FullField, Range]>;
max?: ValidateMessage<[FullField, Range]>;
range?: ValidateMessage<[FullField, Range, Range]>;
};
number?: {
len?: ValidateMessage<[FullField, Range]>;
min?: ValidateMessage<[FullField, Range]>;
max?: ValidateMessage<[FullField, Range]>;
range?: ValidateMessage<[FullField, Range, Range]>;
};
array?: {
len?: ValidateMessage<[FullField, Range]>;
min?: ValidateMessage<[FullField, Range]>;
max?: ValidateMessage<[FullField, Range]>;
range?: ValidateMessage<[FullField, Range, Range]>;
};
pattern?: {
mismatch?: ValidateMessage<[FullField, Value, Pattern]>;
};
}
export interface InternalValidateMessages extends ValidateMessages {
clone: () => InternalValidateMessages;
}
// >>>>> Values
export type Value = any;
export type Values = Record<string, Value>;
// >>>>> Validate
export interface ValidateError {
message?: string;
fieldValue?: Value;
field?: string;
}
export type ValidateFieldsError = Record<string, ValidateError[]>;
export type ValidateCallback = (
errors: ValidateError[] | null,
fields: ValidateFieldsError | Values,
) => void;
export interface RuleValuePackage {
rule: InternalRuleItem;
value: Value;
source: Values;
field: string;
}
export interface InternalRuleItem extends Omit<RuleItem, 'validator'> {
field?: string;
fullField?: string;
fullFields?: string[];
validator?: RuleItem['validator'] | ExecuteValidator;
}
5-3表单组件-使用配置的数据完成一个基本版表单
- 使用import cloneDeep from 'lodash/cloneDeep' 尝试拷贝
- 表单实例类型
export interface FormInstance {
registerLabelWidth(width: number, oldWidth: number): void,
deregisterLabelWidth(width: number): void,
autoLabelWidth: string | undefined,
emit: (evt: string, ...args: any[]) => void,
labelSuffix: string,
inline?: boolean,
model?: Record<string, unknown>,
size?: string,
showMessage?: boolean,
labelPosition?: string,
labelWidth?: string,
rules?: Record<string, unknown>,
statusIcon?: boolean,
hideRequiredAsterisk?: boolean,
disabled?: boolean,
validate: (callback?: Callback) => Promise<boolean>,
resetFields: () => void,
clearValidate: (props?: string | string[]) => void,
validateField: (props: string | string[], cb: ValidateFieldCallback) => void,
}
- TS 定义函数
export interface ValidateFieldCallback {
(message?: string, invalidFields?: ValidateFieldsError): void,
}
- TS 定义 emit
emit: (evt: string, ...args: any[]) => void,
- TS Record
type petsGroup = 'dog' | 'cat' | 'fish';
interface IPetInfo {
name:string,
age:number,
}
type IPets = Record<petsGroup, IPetInfo>;
const animalsInfo:IPets = {
dog:{
name:'dogName',
age:2
},
cat:{
name:'catName',
age:3
},
fish:{
name:'fishName',
age:5
}
}
可以看到 IPets 类型是由 Record<petsGroup, IPetInfo>返回的。将petsGroup中的每个值(‘dog’ | ‘cat’ | ‘fish’)都转为 IPetInfo 类型。
当然也可以自己在第一个参数后追加额外的值,看下面demo2
type petsGroup = 'dog' | 'cat' | 'fish';
interface IPetInfo {
name:string,
age:number,
}
type IPets = Record<petsGroup | 'otherAnamial', IPetInfo>;
const animalsInfo:IPets = {
dog:{
name:'dogName',
age:2
},
cat:{
name:'catName',
age:3
},
fish:{
name:'fishName',
age:5
},
otherAnamial:{
name:'otherAnamialName',
age:10
}
}
可以看到在demo1的基础上,demo2在
type IPets = Record<petsGroup | ‘otherAnamial’, IPetInfo>; 中除了petsGroup的值之外,还追加了 'otherAnamial’这个值。
下面看一个略复杂的例子,用axios将http的几个请求封装一下,使用Record定义每个请求方法的形状。
enum IHttpMethods {
GET = 'get',
POST = 'post',
DELETE = 'delete',
PUT = 'put',
}
const methods = ["get", "post", "delete", "put"];
interface IHttpFn {
<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
}
type IHttp = Record<IHttpMethods, IHttpFn>;
const httpMethods: IHttp = methods.reduce((map: any, method: string) => {
map[method] = (url: string, options: AxiosRequestConfig = {}) => {
const { data, ...config } = options;
return (axios as any)[method](url, data, config)
.then((res: AxiosResponse) => {
if (res.data.errCode) {
//todo somethins
} else {
//todo somethins
}
});
}
return map
}, {})
export default httpMethods;
上面这个demo就先枚举除了几个常见的http请求的方法名,而每个方法都接受请求的url以及可选参数config,然后每个方法返回的都是一个Promise。这种业务常见使用Record再合适不过了。使用下面的方式定义了每个方法的形状。
type IHttp = Record<IHttpMethods, IHttpFn>;
最后只需要遍历一下几个方法,对每个方法有各自的具体实现即可。这里是用了reduce的特性,遍历了一下数据,然后将所有的方法体放在一个对象中,最终结果用 httpMethods接受,再将httpMethods对外暴露出去,那么外面就可直接调用了。这里把一些业务的部分抽离出去了(比如设置请求头、设置token之类的),只是为了简单说明一个比较合适使用Record的业务场景。
5-4表单组件-巧用component动态组件配置添加子元素组件
- CSS 对应的 TS类型
let c: CSSStyleDeclaration = { width: "300px", transform: "translateX(10px)", backgroundColor: "darkgray" };
5-5表单组件-单独处理上传组件-1
<el-upload
v-if="item.type === 'upload'"
v-bind="item.uploadAttrs"
:on-preview="onPreview"
:on-remove="onRemove"
:on-success="onSuccess"
:on-error="onError"
:on-progress="onProgress"
:on-change="onChange"
:before-upload="beforeUpload"
:before-remove="beforeRemove"
:http-request="httpRequest"
:on-exceed="onExceed"
>
<slot name="uploadArea"></slot>
<slot name="uploadTip"></slot>
</el-upload>
单独处理上传组件
5-6表单组件-单独处理上传组件-2
5-7表单组件-巧用插槽给表单加上操作项
<el-form-item>
<slot name="action" :form="form" :model="model"></slot>
</el-form-item>
通过作用域slot 传递form 实例和 model值
5-8表单组件-完善表单上传逻辑
5-9表单组件-集成富文本编辑器wangeditor
if (item.type === 'editor') {
// 初始化富文本
nextTick(() => {
if (document.getElementById('editor')) {
const editor = new E('#editor')
editor.config.placeholder = item.placeholder!
editor.create()
// 初始化富文本的内容
editor.txt.html(item.value)
editor.config.onchange = (newHtml: string) => {
model.value[item.prop!] = newHtml
}
edit.value = editor
}
})
}
5-10表单组件-完善表单重置逻辑
// 重置表单
let resetFields = () => {
// 重置element-plus的表单
form.value!.resetFields()
// 重置富文本编辑器的内容
// 获取到富文本的配置项
if (props.options && props.options.length) {
let editorItem = props.options.find(item => item.type === 'editor')!
edit.value.txt.html(editorItem.value)
}
}
分发方法
// 分发方法
defineExpose({
resetFields,
validate,
getFormData
})
父组件使用
form.resetFields()
5-11表单组件-弹出框表单的基本结构
进一步封装编辑窗口
<template>
<div :class="{ 'm-choose-icon-dialog-body-height': isScroll }">
<el-dialog v-model="dialogVisible" v-bind="$attrs">
<template #default>
<m-form
ref="form"
:options="options"
label-width="100px"
@on-change="onChange"
@before-upload="beforeUpload"
@on-preview="onPreview"
@on-remove="onRemove"
@before-remove="beforeRemove"
@on-success="onSuccess"
@on-exceed="onExceed"
>
<template #uploadArea>
<slot name="uploadArea"></slot>
</template>
<template #uploadTip>
<slot name="uploadTip"></slot>
</template>
</m-form>
</template>
<template #footer>
<slot name="footer" :form="form"></slot>
</template>
</el-dialog>
</div>
</template>
<script lang='ts' setup>
import { PropType, ref, watch } from 'vue'
import { FormOptions } from '../../form/src/types/types'
let props = defineProps({
// 是否只在可视区域内滚动
isScroll: {
type: Boolean,
default: false
},
visible: {
type: Boolean,
default: false
},
options: {
type: Array as PropType<FormOptions[]>,
required: true
},
onChange: {
type: Function
},
beforeUpload: {
type: Function
},
onPreview: {
type: Function
},
onRemove: {
type: Function
},
beforeRemove: {
type: Function
},
onSuccess: {
type: Function
},
onExceed: {
type: Function
},
})
let emits = defineEmits(['update:visible'])
// 表单实例
let form = ref()
// 弹出框的显示与隐藏
let dialogVisible = ref<boolean>(props.visible)
watch(() => props.visible, val => {
dialogVisible.value = val
})
watch(() => dialogVisible.value, val => {
emits('update:visible', val)
})
</script>
<style lang='scss' scoped>
</style>
5-12表单组件-使用defineExpose获取表单实例方法
-
Vue3 通过 defineExpose 来获取到子组件暴露的方法
-
在dialog的action slot中传递form实例
<slot name="footer" :form="form"></slot>
5-13表单组件-完善表单逻辑
slot的嵌套使用
<template #uploadArea>
<slot name="uploadArea"></slot>
</template>
<template #uploadTip>
<slot name="uploadTip"></slot>
</template>
5-14表单组件-表单组件总结
通过定义结构动态生成表单
<template>
<div>
<m-form
ref="form"
label-width="100px"
:options="options"
@on-change="handleChange"
@before-upload="handleBeforeUpload"
@on-preview="handlePreview"
@on-remove="handleRemove"
@before-remove="beforeRemove"
@on-success="handleSuccess"
@on-exceed="handleExceed"
>
<template #uploadArea>
<el-button size="small" type="primary">Click to upload</el-button>
</template>
<template #uploadTip>
<div style="color: #ccc;font-size: 12px;">jpg/png files with a size less than 500kb</div>
</template>
<template #action="scope">
<el-button type="primary" @click="submitForm(scope)">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</template>
</m-form>
</div>
</template>
<script lang='ts' setup>
import { FormInstance, FormOptions } from '../../components/form/src/types/types';
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue'
interface Scope {
form: FormInstance,
model: any
}
let options: FormOptions[] = [
{
type: 'input',
value: '',
label: '用户名',
prop: 'username',
placeholder: '请输入用户名',
rules: [
{
required: true,
message: '用户名不能为空',
trigger: 'blur'
},
{
min: 2,
max: 6,
message: '用户名在2-6位之间',
trigger: 'blur'
}
],
attrs: {
clearable: true
}
},
{
type: 'input',
value: '',
label: '密码',
prop: 'password',
placeholder: '请输入密码',
rules: [
{
required: true,
message: '密码不能为空',
trigger: 'blur'
},
{
min: 6,
max: 15,
message: '密码在6-15位之间',
trigger: 'blur'
}
],
attrs: {
showPassword: true,
clearable: true
}
},
{
type: 'select',
value: '',
placeholder: '请选择职位',
prop: 'role',
label: '职位',
attrs: {
style: {
width: '100%'
},
},
rules: [
{
required: true,
message: '职位不能为空',
trigger: 'change'
}
],
children: [
{
type: 'option',
label: '经理',
value: '1'
},
{
type: 'option',
label: '主管',
value: '2'
},
{
type: 'option',
label: '员工',
value: '3'
}
]
},
{
type: 'checkbox-group',
value: [],
prop: 'like',
label: '爱好',
rules: [
{
required: true,
message: '爱好不能为空',
trigger: 'change'
}
],
children: [
{
type: 'checkbox',
label: '足球',
value: '1'
},
{
type: 'checkbox',
label: '篮球',
value: '2'
},
{
type: 'checkbox',
label: '排球',
value: '3'
}
]
},
{
type: 'radio-group',
value: '',
prop: 'gender',
label: '性别',
rules: [
{
required: true,
message: '性别不能为空',
trigger: 'change'
}
],
children: [
{
type: 'radio',
label: '男',
value: 'male'
},
{
type: 'radio',
label: '女',
value: 'female'
},
{
type: 'radio',
label: '保密',
value: 'not'
}
]
},
{
type: 'upload',
label: '上传',
prop: 'pic',
uploadAttrs: {
action: 'https://jsonplaceholder.typicode.com/posts/',
multiple: true,
limit: 3
},
rules: [
{
required: true,
message: '图片不能为空',
trigger: 'blur'
}
],
},
{
type: 'editor',
value: '123',
prop: 'desc',
label: '描述',
placeholder: '请输入描述',
rules: [
{
required: true,
message: '描述不能为空',
trigger: 'blur'
}
]
}
]
let form = ref()
let submitForm = (scope: Scope) => {
scope.form.validate((valid) => {
if (valid) {
console.log(scope.model)
ElMessage.success('提交成功')
} else {
ElMessage.error('表单填写有误,请检查')
}
})
}
// 重置表单
let resetForm = () => {
form.value.resetFields()
}
let handleRemove = (file: any, fileList: any) => {
console.log('handleRemove')
console.log(file, fileList)
}
let handlePreview = (file: any) => {
console.log('handlePreview')
console.log(file)
}
let beforeRemove = (val: any) => {
console.log('beforeRemove')
return ElMessageBox.confirm(`Cancel the transfert of ${val.file.name} ?`)
}
let handleExceed = (val: any) => {
console.log('handleExceed', val)
ElMessage.warning(
`The limit is 3, you selected ${val.files.length
} files this time, add up to ${val.files.length + val.fileList.length} totally`
)
}
let handleSuccess = (val: any) => {
console.log('success')
console.log(val)
}
let handleChange = (val: any) => {
console.log('change')
console.log(val)
}
let handleBeforeUpload = (val: any) => {
console.log('handleBeforeUpload')
console.log(val)
}
</script>
<style lang='scss' scoped>
</style>