封装组件库-表单(3)

2023, Mar 07    

实现表单的封装

基于Element-plus框架的Form表单组件进行封装。

实现的功能

  • 实现配置型表单,通过json对象的方式自动生成表单
  • 具备更加完备的功能,表单验证,动态删减表单,集成第三方插件(富文本编辑器)
  • 用法更简单,扩展性强(配置项形式),可维护
  • 能够进行弹框嵌套表单等多场景。

初始配置

  • 注册路由组件form,配置为Container组件的子路由组件
  • 采用路由懒加载,箭头函数异步引入组件。
  • 注册通用组件from,为路由组件的子组件,以便组件间通信。
  • 将通用组件form注册为全局组件

实现upload上传组件

  • 接口类型定义:在type里添加upload,定义uploadAttrs将upload组件的原生基本类型API添加进来,实现原生的所有功能
  export interface FormOptions {
    type: 'upload',
    //处理上传组件的属性和方法
    uploadAttrs?: {
        //上传地址
        action: string,
        //设置上传的请求头部
        header?: object,
        //设置上传的方法
        methods?: 'post' | 'put' | 'patch',
        //是否支持多选文件
        multiple?: boolean,
        //上传的额外参数
        data?: any,
        //上传的字段
        name?: string,
        //是否允许携带token
        withCreentials?: boolean,
        //是否显示上传文件列表
        showFileList?: boolean,
        //是否拖拽
        drag?: boolean,
        //接受上传的文件类型
        accept?: string,
        //缩略图
        thumbailMode?: boolean,
        //上传的文件列表
        fileList?: any[],
        //文件列表的类型
        listType?: 'text' | 'picture' | 'picture-card',
        //是否自动上传
        autoUpload?: boolean,
        //是否禁用
        disable?: boolean,
        //允许上传的最大数量
        limit?: number
    }
  } 
  • 父组件配置表单数据:这里基于官网进行定义url。
  let options: FormOptions[] =[
    {
      type: 'upload', 
      prop: 'pic',
      label: '上传',
      uploadAttrs:{
        action: 'https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15',
        multiple: true,
        limit: 3
      },
      rule: [
        {
          required: true,
          message: '图片不能为空',
          trigger: 'blur'
        }
      ],
    }
  ]
  • 子组件:由于上传组件的API等逻辑较多,单独处理该组件。
  <el-form-item :prop="item.prop" :label="item.label" 
        v-if="!item.children || !item.children.length">
          <component 
          <!-- 渲染不为上传组件的组件 -->
          v-if="item.type !== 'upload'"
          v-bind="item.attrs" 
          :is="`el-${item.type}`"
          v-model="model[item.prop]"
          :placeholder="item.placeholder">
          </component>
          <el-upload 
          <!-- 渲染上传组件 -->
          v-if="item.type === 'upload'"
          v-bind="item.uploadAttrs"
          <!-- 这里处理原生api,定义事件钩子 -->
          :on-preview="onPreview"
          :on-remove="onRemove"
          :on-success="onSuccess"
          :on-error="onError"
          :on-progress="onProgress"
          :on-change="onChange"
          :on-exceed="onExceed"
          :before-upload="beforeUpload"
          :before-remove="beforeRemove"
          :http-request	='httpRequest'>
            <!-- 子组件设置插槽,用于放置按钮和提示模块 -->
            <slot name="uploadArea"></slot>
            <slot name="uploadTip"></slot> 
          </el-upload>
  </el-form-item>
  • 子组件处理原生钩子:这里原来定义了http-request,这个api是用来覆盖默认xhr行为,应该是父组件传递过来的,所以用props接收。
  let emits = defineEmits(['on-preview','on-remove','on-success','on-error','on-progress',
  'on-change','on-exceed','before-upload','before-remove'])
  //上传组件的所有方法
  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 onExceed = (files: File,fileList: FileList)=>{
    emits('on-exceed',{files,fileList})
  }
  let beforeUpload = (file: File)=>{
    emits('before-upload',file)
  }
  let beforeRemove = (file: File,fileList: FileList)=>{
    emits('before-remove',{file,fileList})
  }
  • 父组件接收,完成原生api的功能
  <my-form 
  ref="form" 
  label-width="100px" 
  :options="options"
  @on-change="handleChange"
  @before-upload="beforeUpload"
  @on-preview="handlePreview"
  @on-remove="handleRemove"
  @before-remove="beforeRemove"
  @on-exceed="handleExceed"
  @on-success="handleSuccess">
  </my-form>
  
  const handleChange = (val: any) => {
    console.log(val)
  }

  const beforeUpload = (val: any) => {
    console.log(val);

  }

  const handleRemove = (val: any) => {
    console.log(val.file, val.fileList)
  }

  const handlePreview = (uploadFile: any) => {
    console.log(uploadFile)
  }

  const handleExceed = (val: any) => {
    ElMessage.warning(
      `The limit is 3, you selected ${val.files.length} files this time, add up to ${
        val.files.length + val.fileList.length
      } totally`
    )
  }

  const handleSuccess = (val: any) => {
      console.log(val);

  }

  const beforeRemove = (val: any) => {
    return ElMessageBox.confirm(
      `Cancel the transfert of ${val.file.name} ?`
    ).then(
      () => true,
      () => false
    )
  }
  • 父组件插槽
  <template #uploadArea>
    <el-button type="primary" size="small">Click to upload</el-button>
  </template>
  <template #uploadTip>
    <div style="color: #ccc;font-size:12px; margin-left:10px">
      jpg/png files with a size less than 500KB.
    </div>
  </template>

radio单选框组件

  {
  type: 'radio-group',
  value: '',
  prop: 'gender',
  label: '性别',
  rule: [
    {
      required: true,
      message: '性别不能为空',
      trigger: 'blur'
    }
  ],
  children:[
    {
      type: 'radio',
      value: 'male',
      prop: 'male',
      label: '男'
    },
    {
      type: 'radio',
      value: 'female',
      prop: 'female',
      label: '女'
    },
    {
      type: 'radio',
      value: 'not',
      prop: 'baomi',
      label: '保密'
    }
  ]
}