Skip to content

表单组件单独处理上传

(最重要) 表单的数据结构

interfaces/chooseForm/rules

  • 请参照基础版本的代码

interfaces/chooseForm/type

ts
// 引入验证规则
import { type RuleItem } from './rules'
// 引入css style类型
import { type CSSProperties } from 'vue'
// 表单每一项的配置选项
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
    // 表单的表示
    prop: string
    // 表单项的label
    label?: string

    // 表单项的验证规则
    rules?: RuleItem[]
    // 表单项的占位符
    placeholder?: string
    // 表单元素特有的属性
    attrs?: {
        // css样式
        style?: CSSProperties
        clearable?: boolean
        showPassword?: boolean
        disabled?: boolean
    }
    // 表单项的子元素类似于select的option,checkbox等等
    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
    }
}

hooks

hooks/chooseForm/useForm

ts
import { watch, onMounted } from 'vue'
import { type FormOptions } from '@/interfaces/chooseForm/type'
export const useForm = (props: any, form: any, model: any, rules: any) => {
    // 赋值数据属性和验证规则
    let setRulesAndValue = () => {
        let m: any = {}
        let r: any = {}
        props.options.map((item: FormOptions) => {
            m[item.prop!] = item.value
            r[item.prop!] = item.rules
        })
        model.value = JSON.parse(JSON.stringify(m))
        rules.value = JSON.parse(JSON.stringify(r))
    }
    onMounted(() => {
        setRulesAndValue()
    })
    // 监听父组件传递进来的options
    watch(
        () => {
            return props.options
        },
        () => {
            setRulesAndValue()
        },
        {
            immediate: true
        }
    )
}

hooks/chooseForm/useUpload

ts
export const useUpload = (props: any, emits: any, form: any, model: any) => {
    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: any) => {
            return 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 })
    }

    return {
        onPreview,
        onRemove,
        onSuccess,
        onError,
        onProgress,
        onChange,
        beforeUpload,
        beforeRemove,
        onExceed
    }
}

组件

components/chooseForm/index.vue

vue
<template>
    <!--改变后不需要立即验证-->
    <el-form ref="form" v-if="model" v-bind="$attrs" :model="model" :rules="rules" :validate-on-rule-change="false">
        <template v-for="(item, index) in options" :key="index">
            <!--非checkbox.radio 之类的开始-->
            <el-form-item :prop="item.prop" :label="item.label" v-if="!item.children || !item.children!.length">
                <!--处理非上传组件开始-->
                <component
                    v-if="item.type !== 'upload'"
                    :placeholder="item.placeholder"
                    :is="`el-${item.type}`"
                    v-bind="item.attrs"
                    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>
                <!--处理上传组件-->
            </el-form-item>
            <!--非checkbox,radio之类的结束-->
            <!--有子元素类似checkbox,radio 之类的开始-->
            <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>
            <!--有子元素类似 checkbox,radio之类的结束-->
        </template>
        <el-form-item>
            <!--传递走两个值 一个是form 一个是model-->
            <slot name="action" :form="form" :model="model"></slot>
        </el-form-item>
    </el-form>
</template>

<script setup lang="ts">
    import { type PropType, ref } from 'vue'
    import { type FormOptions } from '@/interfaces/chooseForm/type'
    import { useForm } from '@/hooks/chooseForm/useForm'
    import { useUpload } from '@/hooks/chooseForm/useUpload'
    let props = defineProps({
        options: {
            type: Array as PropType<FormOptions[]>,
            required: true
        },
        // 用户自定义上传方法
        httpRequest: {
            type: Function
        }
    })
    // 最后的结果应该就是 {username: '', password: ''}
    // rules: { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }] }
    let model = ref<any>({})
     // 验证规则
    let rules = ref<any>({})
    // form 组件实例
    let form = ref<any>(null)

    /* 下面开始是处理 upload 上传的函数 */
    // 上传组件的所有方法
    let emits = defineEmits([
        'on-preview',
        'on-remove',
        'on-success',
        'on-error',
        'on-progress',
        'on-change',
        'before-upload',
        'before-remove',
        'on-exceed'
    ])
    // 普通
    useForm(props, form, model, rules)
    // 上传
    const { onPreview, onRemove, onSuccess, onError, onProgress, onChange, beforeUpload, beforeRemove, onExceed } =
        useUpload(props, emits, form, model)
</script>

<style scoped></style>

父元素调用

vue
<template>
    <div>
        <YJ-choose-form
            label-width="120px"
            :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>
                <div>
                    <el-button type="primary">点击上传</el-button>
                </div>
            </template>
            <template #uploadTip>
                <div style="color: red; font-size: 14px; text-indent: 20px">
                    图片只能是jpg/png格式,并且每个大小不能超过50KB
                </div>
            </template>
            <template #action="{ form, model }">
                <el-button type="primary" @click="submitForm(form, model)">提交</el-button>
                <el-button @click="resetForm(form)">重置</el-button>
            </template>
        </YJ-choose-form>
    </div>
</template>

<script setup lang="ts">
    import { type FormOptions } from '@/interfaces/chooseForm/type'
    import { ElMessage, ElMessageBox } from 'element-plus'

    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            prop: 'username',
            placeholder: '请输入用户名',
            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: '',
            label: '职位',
            prop: 'zhiwei',
            placeholder: '请选择职位',
            rules: [{ required: true, message: '职位不能为空', trigger: 'blur' }],
            children: [
                {
                    type: 'option',
                    value: '1',
                    label: '经理',
                    prop: 'options_id_1'
                },
                {
                    type: 'option',
                    value: '2',
                    label: '主管',
                    prop: 'options_id_2'
                },
                {
                    type: 'option',
                    value: '3',
                    label: '员工',
                    prop: 'options_id_3'
                }
            ],
            attrs: {
                style: {
                    width: '150px'
                }
            }
        },
        {
            type: 'checkbox-group',
            value: [],
            label: '爱好',
            prop: 'hobby',
            rules: [{ required: true, message: '爱好不能为空', trigger: 'blur' }],
            children: [
                {
                    type: 'checkbox',
                    value: '1',
                    label: '篮球',
                    prop: 'options_id_1'
                },
                {
                    type: 'checkbox',
                    value: '2',
                    label: '足球',
                    prop: 'options_id_2'
                },
                {
                    type: 'checkbox',
                    value: '3',
                    label: '排球',
                    prop: 'options_id_3'
                }
            ]
        },
        {
            type: 'radio-group',
            value: '',
            label: '作业',
            prop: 'zuoye',
            rules: [{ required: true, message: '作业不能为空', trigger: 'blur' }],
            children: [
                {
                    type: 'radio',
                    value: '1',
                    label: '语文',
                    prop: 'options_id_1'
                },
                {
                    type: 'radio',
                    value: '2',
                    label: '数学',
                    prop: 'options_id_2'
                },
                {
                    type: 'radio',
                    value: '3',
                    label: '外语',
                    prop: 'options_id_3'
                }
            ]
        },
        // 处理上传组件
        {
            type: 'upload',
            label: '上传',
            value: '',
            prop: 'upload',
            uploadAttrs: {
                action: 'https://jsonplaceholder.typicode.com/posts/',
                multiple: true,
                limit: 3,
                withCredentials: true
            },
            rules: [
                {
                    required: true,
                    message: '图片不能为空',
                    trigger: 'blur'
                }
            ]
        }
    ]
    // 表单元素

    // 表单方法
    let submitForm = (form: any, model: any) => {
        form.validate((valid: any) => {
            if (valid) {
                console.log(model)
                ElMessage.success('提交成功')
            } else {
                ElMessage.error('表单填写有误,请检查')
            }
        })
    }
    // 重置表单
    let resetForm = (form: any) => {
        form.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 scoped></style>