表单组件单独处理上传
(最重要) 表单的数据结构
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>