前语

直至今天,看了一下他人写的代码,才发现以前自己写的代码太废物,所以在这次做的一个后台项目中,采用他的代码风格,怎样说呢,复用性特别好,封装的很好,学到很多,所以记载一下思路,我以为这个封装思路是真的很棒,写第一个页面的时候可能会费事一些,可是后边只要是类似的页面,事半功倍,直接CV改装备项就好了,是真的顶,记载一下,学习一下,我这儿用的是vue3+ts

Form表单的封装

简述

这儿是Form表单部分,下面是完好的思路,最后有附上完好的代码,大佬能够直接看完好的代码就能看懂了,小白们跟着我的思路估计能看懂….

正常的运用

假如咱们正常运用组件库里边的组件会是这样的

Element Plus组件Form表单-Table表格二次封装
代码如下

role.vue页面组件

<template>
 <div class="role">
  <el-form>
   <el-form-item label="用户id">
    <el-input placeholder="请输入用户id"></el-input>
   </el-form-item>
   <el-form-item label="用户名">
    <el-input placeholder="请输入用户名"></el-input>
   </el-form-item>
   <el-form-item label="实在名字">
    <el-input placeholder="请输入实在名字"></el-input>
   </el-form-item>
   <el-form-item label="用户名">
    <el-input placeholder="请输入用户名"></el-input>
   </el-form-item>
   <el-form-item label="电话号码">
    <el-input placeholder="请输入电话号码"></el-input>
   </el-form-item>
   <el-form-item label="用户状况">
    <el-select placeholder="请挑选用户状况">
     <el-option label="禁用" value="0"></el-option>
     <el-option label="启用" value="1"></el-option>
    </el-select>
   </el-form-item>
   <el-form-item label="创立时刻">
    <el-date-picker
     startPlaceholder="开端时刻"
     endPlaceholder="结束时刻"
     type="daterange"
    ></el-date-picker>
   </el-form-item>
  </el-form>
 </div>
</template><script setup lang="ts"></script><style scoped lang="less"></style>

这时咱们能够加点款式让他变得美观,并且布局也变一变就能够变成这样,当然款式布局能够自界说

Element Plus组件Form表单-Table表格二次封装
代码如下

role.vue页面组件

<template>
 <div class="role">
  <el-form labelWidth="120px">
   <el-row>
    <el-col :span="8">
     <el-form-item
      label="用户id"
      :style="{
       padding: '10px 20px'
      }"
     >
      <el-input placeholder="请输入用户id"></el-input>
     </el-form-item>
    </el-col>
    <el-col :span="8">
     <el-form-item
      label="用户名"
      :style="{
       padding: '10px 20px'
      }"
     >
      <el-input placeholder="请输入用户名"></el-input>
     </el-form-item>
    </el-col>
    <el-col :span="8">
     <el-form-item
      label="实在名字"
      :style="{
       padding: '10px 20px'
      }"
     >
      <el-input placeholder="请输入实在名字"></el-input>
     </el-form-item>
    </el-col>
    <el-col :span="8">
     <el-form-item
      label="电话号码"
      :style="{
       padding: '10px 20px'
      }"
     >
      <el-input placeholder="请输入电话号码"></el-input>
     </el-form-item>
    </el-col>
    <el-col :span="8">
     <el-form-item
      label="用户状况"
      :style="{
       padding: '10px 20px'
      }"
     >
      <el-select placeholder="请挑选用户状况">
       <el-option label="禁用" value="0"></el-option>
       <el-option label="启用" value="1"></el-option>
      </el-select>
     </el-form-item>
    </el-col>
    <el-col :span="8">
     <el-form-item
      label="创立时刻"
      :style="{
       padding: '10px 20px'
      }"
     >
      <el-date-picker
       startPlaceholder="开端时刻"
       endPlaceholder="结束时刻"
       type="daterange"
      ></el-date-picker>
     </el-form-item>
    </el-col>
   </el-row>
  </el-form>
 </div>
</template>
​
<script setup lang="ts"></script>
​
<style scoped lang="less">
.el-form-item {
 margin-top: 18px;
}
.el-select {
 width: 100%;
}
</style>
​

开端封装①

这时咱们就能够开端封装了,假如咱们能够经过传装备项的办法来控制款式和form表单项的类型和个数的话,是不是变得很便利,下次直接传装备项用就好了?话不多说直接上图上代码

能够看到作用相同,代码却简洁了,模板里边不会呈现大量重复的代码了

Element Plus组件Form表单-Table表格二次封装

role.vue页面组件

<template>
 <div class="role">
  <el-form :labelWidth="searchFormConfig.labelWidth">
   <el-row>
    <template v-for="item in searchFormConfig.formItems" :key="item.label">
     <el-col :span="8">
      <el-form-item
       :label="item.label"
       :style="searchFormConfig.itemStyle"
      >
       <template
        v-if="item.type === 'input' || item.type === 'password'"
       >
        <el-input 
          :placeholder="item.placeholder" 
          :show-password="item.type === 'password'"
        ></el-input>
       </template>
       <template v-else-if="item.type === 'select'">
        <el-select :placeholder="item.placeholder">
         <el-option
          v-for="option in item.options"
          :key="option.value"
          :label="option.label"
          :value="option.value"
         ></el-option>
        </el-select>
       </template>
       <template v-else>
        <el-date-picker v-bind="item.otherOptions"></el-date-picker>
       </template>
      </el-form-item>
     </el-col>
    </template>
   </el-row>
  </el-form>
 </div>
</template><script setup lang="ts">
// 界说表单装备项
const searchFormConfig = {
 formItems: [
   {
   type: 'input',
   label: '用户id',
   placeholder: '请输入用户id'
   },
   {
   type: 'input',
   label: '用户名',
   placeholder: '请输入用户名'
   },
   {
   type: 'input',
   label: '实在名字',
   placeholder: '请输入实在名字'
   },
   {
   type: 'input',
   label: '电话号码',
   placeholder: '请输入电话号码'
   },
   {
   type: 'select',
   label: '用户状况',
   placeholder: '请挑选用户状况',
   options: [
     {
     label: '禁用',
     value: 0
     },
     {
     label: '启用',
     value: 1
     }
    ]
   },
   {
   type: 'datepicker',
   label: '创立时刻',
   otherOptions: {
    startPlaceholder: '开端时刻',
    endPlaceholder: '结束时刻',
    type: 'daterange',
    'unlink-panels': true
    }
   }
  ],
 labelWidth: '120px',
 itemStyle: {
  padding: '10px 20px'
  },
 itemColLayout: {
  span: 8
  }
}
</script><style scoped lang="less">
.el-form-item {
 margin-top: 18px;
}
.el-select {
 width: 100%;
}
</style>

开端封装②

这时它复用的锥形现已有了,咱们能够将装备项抽出去,并给它一些类型约束,把这部分运用表单的代码抽出去,封装成form组件,这样之后咱们在用的时候,直接用这个组件然后给它传装备项就能够了

1.装备项类型约束文件(不用ts的话就没有,不想约束悉数给any类型随意,我这儿是为了让代码严谨一丢丢哈哈)

type IFormType = 'input' | 'password' | 'select' | 'datepicker'interface IFormOption {
 label: string
 value: string | number
}
​
export interface IFormItem {
 type: IFormType //输入框类型
 label: string //输入框标题
 placeholder?: any //输入框默许显现内容
 // 针对select
 options?: IFormOption[] //挑选器的可选子选项
 // 针对特殊特点
 otherOptions?: any
​
}
​
export interface IForm {
 formItems: IFormItem[]
 labelWidth?: string
 itemStyle?: any
 itemColLayout?: any
}
​

2.装备项文件

import { IForm } from '@/base-ui/form/type'
export const searchFormConfig: IForm = {
 formItems: [
   {
   type: 'input',
   label: '用户id',
   placeholder: '请输入用户id'
   },
   {
   type: 'input',
   label: '用户名',
   placeholder: '请输入用户名'
   },
   {
   type: 'input',
   label: '实在名字',
   placeholder: '请输入实在名字'
   },
   {
   type: 'input',
   label: '电话号码',
   placeholder: '请输入电话号码'
   },
   {
   type: 'select',
   label: '用户状况',
   placeholder: '请挑选用户状况',
   options: [
     { label: '启用', value: 1 },
     { label: '禁用', value: 0 }
    ]
   },
   {
   type: 'datepicker',
   label: '创立时刻',
   otherOptions: {
    startPlaceholder: '开端时刻',
    endPlaceholder: '结束时刻',
    type: 'daterange'
    }
   }
  ],
 labelWidth: '120px',
 itemColLayout: {
  span: 8
  },
 itemStyle: {
  padding: '10px 20px'
  }
}
​

3.form表单文件

留意:在这儿,我将labelWidth,itemColLayout,itemStyle设置了默许值,所以我上面的那些款式装备项能够不传,默许便是我设置的那些值,假如需求其他款式能够传入修正,不要款式能够传个空进去,这儿我还加了两个插槽,增加可扩展性

<template>
 <div class="header">
  <slot name="header"> </slot>
 </div>
 <el-form ref="ruleFormRef" :labelWidth="labelWidth">
  <el-row>
   <template v-for="item in formItems" :key="item.label">
    <el-col v-bind="itemColLayout">
     <el-form-item
      v-if="!item.isHidden"
      :label="item.label"
      :style="itemStyle"
      :prop="item.field"
     >
      <template v-if="item.type === 'input' || item.type === 'password'">
       <el-input
        :placeholder="item.placeholder"
        :show-password="item.type === 'password'"
       ></el-input>
      </template>
      <template v-else-if="item.type === 'select'">
       <el-select :placeholder="item.placeholder">
        <el-option
         v-for="option in item.options"
         :key="option.label"
         :label="option.label"
         :value="option.value"
        ></el-option>
       </el-select>
      </template>
      <template v-if="item.type === 'datepicker'">
       <el-date-picker v-bind="item.otherOptions"></el-date-picker>
      </template>
     </el-form-item>
    </el-col>
   </template>
  </el-row>
 </el-form>
 <div class="footer">
  <slot name="footer"></slot>
 </div>
</template><script setup lang="ts">
import { defineProps, withDefaults } from 'vue'
import { IFormItem } from './type'
interface Prop {
 formItems: IFormItem[] // 表单装备项
 labelWidth?: string // 每个表单标题宽度
 itemStyle?: object // 每个表单款式
 itemColLayout?: object // 表单布局
 isHidden?: boolean // 该输入框是否躲藏
}
const props = withDefaults(defineProps<Prop>(), {
 labelWidth: '120px',
 itemColLayout: () => ({
  xl: 6, // >=1920px
  lg: 8, // >=1200px
  md: 12, // >=992px
  sm: 24, // >=768px
  xs: 24 // <768px
  }),
 itemStyle: () => ({
  padding: '10px 20px'
  })
})
</script><style scoped>
.el-form-item {
 margin-top: 18px;
}
.el-select {
 width: 100%;
}
</style>

4.role.vue页面组件

<template>
 <div class="role">
  <form-test v-bind="searchFormConfig"></form-test>
 </div>
</template><script setup lang="ts">
import formTest from '@/base-ui/form/form-test.vue'
import { searchFormConfig } from './config/search-config-test'
</script><style scoped lang="less"></style>

这时现已开端封装好了,咱们能够运用一下看作用,咱们能够看到款式跟之前完全相同,可是页面的代码量就那么点,要用的话直接用咱们封装好的form组件然后传入装备项就出来了

Element Plus组件Form表单-Table表格二次封装

它的可扩展性也是很强的,比方:

这儿咱们把款式装备项悉数传空值,然后装备项也传一个,它又变成原来最丑的姿态了,证明咱们是能够随意更改它的款式和布局,只需求经过传入装备项更改就能够了,便利

装备项文件

import { IForm } from '@/base-ui/form/type'
export const searchFormConfig: IForm = {
  formItems: [
   {
    field: 'id',
    type: 'input',
    label: '用户id',
    placeholder: '请输入用户id'
   }
  ],
  labelWidth: '',
  itemColLayout: {},
  itemStyle: {}
}
​

Element Plus组件Form表单-Table表格二次封装

其实到这儿还没结束,由于这时的表单还输入不了东西,由于咱们根本就没给它的输入框绑定值,所以咱们要在装备项传入多一个field字段,它能够作为输入框绑定的值

开端封装③

这儿仅仅是给装备项中增加field字段(留意假如用了ts的还要去type文件里边给咱们界说的IFormItem接口增加一个field字段)

装备项文件

import { IForm } from '@/base-ui/form/type'
export const searchFormConfig: IForm = {
 formItems: [
   {
   field: 'id',
   type: 'input',
   label: '用户id',
   placeholder: '请输入用户id'
   },
   {
   field: 'name',
   type: 'input',
   label: '用户名',
   placeholder: '请输入用户名'
   },
   {
   field: 'realname',
   type: 'input',
   label: '实在名字',
   placeholder: '请输入实在名字'
   },
   {
   field: 'cellphone',
   type: 'input',
   label: '电话号码',
   placeholder: '请输入电话号码'
   },
   {
   field: 'enable',
   type: 'select',
   label: '用户状况',
   placeholder: '请挑选用户状况',
   options: [
     { label: '启用', value: 1 },
     { label: '禁用', value: 0 }
    ]
   },
   {
   field: 'createAt',
   type: 'datepicker',
   label: '创立时刻',
   otherOptions: {
    startPlaceholder: '开端时刻',
    endPlaceholder: '结束时刻',
    type: 'daterange'
    }
   }
  ],
 labelWidth: '120px',
 itemColLayout: {
  span: 8
  },
 itemStyle: {
  padding: '10px 20px'
  }
}

由于传入了fied字段,所以咱们要搜集一切的field字段,组成formData数据,传入表单组件,formData里边的每个子项分别作为每个输入框绑定的值

留意:这儿有两个难点

难点一:

咱们传进去的数据在里边是要做修正传出来的,而vue的准则是单项数据流传输,咱们不能直接将数据传进去(其实事实能够这样做,可是违背了单向数据流传输准则,咱们尽量不违背哈),所以咱们采用v-model的办法将formData传入form组件,这样做的话便是双向断定了,不算违背嘿嘿

难点二:由于咱们传进去的formData的数据,并不是在form组件里边用的,而是要绑定到form组件里边的element puls的输入框里边的,所以咱们在form组件里边接纳到formData数据,然后在把formData它的各个子项v-model绑定到输入框里边,可是这样会报错,不能直接用v-model,这儿就需求知道v-model是怎样实现的了,咱们在这儿是直接把接纳到的formData数据绑定到输入框里边的,在form组件并没有界说formData这个变量,所以不能直接用v-model的办法,这了可能有点懵,举个例子

(比方你将一个值test用v-model传入一个input的框,你输入框输入数据,你的test是会同步改动,也便是说,v-model会把你修正后的值传出来赋值给你的test,而在这儿,咱们将formData用v-model绑定到输入框,输入框值改动,正常来说它会将修正后的值赋值给咱们传进去的formData,可是咱们不能让它直接赋值给咱们的formData,由于咱们的formData也是从其他组件传进来的,所以咱们要把修正后的值再次传出去到传进来formData数据的那个组件中,而不是直接就赋值,这时咱们就要用到v-model的原始写法了,其实v-model是个语法糖来的)

form.vue组件

<template>
  <div class="header">
   <slot name="header"> </slot>
  </div>
  <el-form ref="ruleFormRef" :labelWidth="labelWidth">
   <el-row>
    <template v-for="item in formItems" :key="item.label">
     <el-col v-bind="itemColLayout">
      <el-form-item
       v-if="!item.isHidden"
       :label="item.label"
       :style="itemStyle"
      >
       <template v-if="item.type === 'input' || item.type === 'password'">
        <el-input
         :placeholder="item.placeholder"
         :show-password="item.type === 'password'"
         :model-value="modelValue[`${item.field}`]"
         @update:modelValue="valueChange($event, item.field)"
        ></el-input>
       </template>
       <template v-else-if="item.type === 'select'">
        <el-select
         :placeholder="item.placeholder"
         :model-value="modelValue[`${item.field}`]"
         @update:modelValue="valueChange($event, item.field)"
        >
         <el-option
          v-for="option in item.options"
          :key="option.label"
          :label="option.label"
          :value="option.value"
         ></el-option>
        </el-select>
       </template>
       <template v-if="item.type === 'datepicker'">
        <el-date-picker
         v-bind="item.otherOptions"
         :model-value="modelValue[`${item.field}`]"
         @update:modelValue="valueChange($event, item.field)"
        ></el-date-picker>
       </template>
      </el-form-item>
     </el-col>
    </template>
   </el-row>
  </el-form>
  <div class="footer">
   <slot name="footer"></slot>
  </div>
</template><script setup lang="ts">
import { defineProps, withDefaults, defineEmits } from 'vue'
import { IFormItem } from './type'
interface Prop {
  formItems: IFormItem[] // 表单装备项
  labelWidth?: string // 每个表单标题宽度
  itemStyle?: object // 每个表单款式
  itemColLayout?: object // 表单布局
  isHidden?: boolean // 该输入框是否躲藏
  modelValue: object //绑定表单的每个数据
}
const props = withDefaults(defineProps<Prop>(), {
  labelWidth: '120px',
  itemColLayout: () => ({
   xl: 6, // >=1920px
   lg: 8, // >=1200px
   md: 12, // >=992px
   sm: 24, // >=768px
   xs: 24 // <768px
  }),
  itemStyle: () => ({
   padding: '10px 20px'
  })
})
const emit = defineEmits<{
  (e: 'update:modelValue', value: any): void
}>()
​
// 输入框值改动该函数都会触发,将改动后的值传出去
const valueChange = (value: any, field: string) => {
  emit('update:modelValue', { ...props.modelValue, [field]: value })
}
</script><style scoped>
.el-form-item {
  margin-top: 18px;
}
.el-select {
  width: 100%;
}
</style>

role.vue页面组件

<template>
 <div class="role">
  <form-test v-bind="searchFormConfig" v-model="formData"></form-test>
 </div>
</template><script setup lang="ts">
import formTest from '@/base-ui/form/form-test.vue'
import { searchFormConfig } from './config/search-config-test'
import { ref } from 'vue'
// 在这儿取出一切的field字段组成formData数据
const formItems = searchFormConfig.formItems ?? []
​
let formDataInit = {}
formItems.map((item) => {
 formDataInit[item.field] = ''
})
let formData = ref(formDataInit)
</script><style scoped lang="less"></style>

Element Plus组件Form表单-Table表格二次封装

这时咱们发现它能够拿到数据了,很nice,其实这差不多现已算封装好了,能够经过装备项修正里边的东西了,一起也能够拿到数据,可是我这个项目不止于此,我这其实要做表单的查询的,所以我要改装一下变成这样

Element Plus组件Form表单-Table表格二次封装

其实便是加了两个插槽和两个办法,我这儿要实现功用便是点击重置按钮,它会重置表单数据,点击查找按钮就能够拿到表单数据,这样咱们就能够用咱们拿到的表单数据去进行咱们的操作拉,所以上代码

role.vue组件

该部分咱们传入了两个template,一个是标题:高档检索,一个是两个按钮

这儿要重置按钮重置表单数据,取到表单的ref调用resetFields办法就好了,然后点击查找按钮能够打印出formData数据,然后咱们就能够使用该数据去做咱们想要做的操作了,例如查询列表数据等

<template>
 <div class="role">
  <form-test v-bind="searchFormConfig" v-model="formData" ref="formTestRef">
   <template #header>
    <div class="header">
     <h1>高档检索</h1>
    </div>
   </template>
   <template #footer>
    <div class="footer">
     <el-button type="primary" :icon="Refresh" @click="resetBtnClick"
      >重置</el-button
     >
     <el-button type="primary" :icon="Search" @click="searchBtnClick"
      >查找</el-button
     >
    </div>
   </template>
  </form-test>
 </div>
</template><script setup lang="ts">
import formTest from '@/base-ui/form/form-test.vue'
import { searchFormConfig } from './config/search-config-test'
import { ref } from 'vue'
import { Search, Refresh } from '@element-plus/icons-vue'
// 在这儿取出一切的field字段组成formData数据
const formItems = searchFormConfig.formItems ?? []
​
let formDataInit = {}
formItems.map((item) => {
 formDataInit[item.field] = ''
})
let formData = ref(formDataInit)
​
const formTestRef = ref<InstanceType<typeof formTest>>()
​
// 重置点击
const resetBtnClick = () => {
 formTestRef.value?.resetForm()
}
// 查找点击
const searchBtnClick = () => {
 // 这儿需求遍历查找装备项,装备项里能够传dataType,要求数据回来什么类型的数据
 let queryInfo = { ...formData.value }
 searchFormConfig.formItems.map((item: any) => {
  if (item.dataType === 'number' && queryInfo[item.field] !== '') {
   queryInfo[item.field] = Number(queryInfo[item.field])
   }
  })
 // 清空queryInfo中没有值的特点
 for (const i in queryInfo) {
  if (queryInfo[i] === '') {
   delete queryInfo[i]
   }
  }
 console.log(queryInfo)
}
</script><style scoped lang="less">
.header {
 padding-top: 20px;
}
.footer {
 text-align: right;
 padding: 0 50px 20px 0;
}
</style>

form.vue

<template>
 <div class="header">
  <slot name="header"> </slot>
 </div>
 <el-form ref="ruleFormRef" :labelWidth="labelWidth" :model="modelValue">
  <el-row>
   <template v-for="item in formItems" :key="item.label">
    <el-col v-bind="itemColLayout">
     <el-form-item
      v-if="!item.isHidden"
      :label="item.label"
      :style="itemStyle"
      :prop="item.field"
     >
      <template v-if="item.type === 'input' || item.type === 'password'">
       <el-input
        :placeholder="item.placeholder"
        :show-password="item.type === 'password'"
        :model-value="modelValue[`${item.field}`]"
        @update:modelValue="valueChange($event, item.field)"
       ></el-input>
      </template>
      <template v-else-if="item.type === 'select'">
       <el-select
        :placeholder="item.placeholder"
        :model-value="modelValue[`${item.field}`]"
        @update:modelValue="valueChange($event, item.field)"
       >
        <el-option
         v-for="option in item.options"
         :key="option.label"
         :label="option.label"
         :value="option.value"
        ></el-option>
       </el-select>
      </template>
      <template v-if="item.type === 'datepicker'">
       <el-date-picker
        v-bind="item.otherOptions"
        :model-value="modelValue[`${item.field}`]"
        @update:modelValue="valueChange($event, item.field)"
       ></el-date-picker>
      </template>
     </el-form-item>
    </el-col>
   </template>
  </el-row>
 </el-form>
 <div class="footer">
  <slot name="footer"></slot>
 </div>
</template><script setup lang="ts">
import { defineProps, withDefaults, defineEmits, ref, defineExpose } from 'vue'
import { IFormItem } from './type'
import type { FormInstance } from 'element-plus'
​
interface Prop {
 formItems: IFormItem[] // 表单装备项
 labelWidth?: string // 每个表单标题宽度
 itemStyle?: object // 每个表单款式
 itemColLayout?: object // 表单布局
 isHidden?: boolean // 该输入框是否躲藏
 modelValue: object //绑定表单的每个数据
}
const props = withDefaults(defineProps<Prop>(), {
 labelWidth: '120px',
 itemColLayout: () => ({
  xl: 6, // >=1920px
  lg: 8, // >=1200px
  md: 12, // >=992px
  sm: 24, // >=768px
  xs: 24 // <768px
  }),
 itemStyle: () => ({
  padding: '10px 20px'
  })
})
const emit = defineEmits<{
  (e: 'update:modelValue', value: any): void
}>()
const ruleFormRef = ref<FormInstance>()
​
// 输入框值改动该函数都会触发,将改动后的值传出去
const valueChange = (value: any, field: string) => {
 emit('update:modelValue', { ...props.modelValue, [field]: value })
}
​
// 表单重置办法
const resetForm = () => {
 ruleFormRef.value?.resetFields()
}
defineExpose({
 resetForm
})
</script><style scoped>
.el-form-item {
 margin-top: 18px;
}
.el-select {
 width: 100%;
}
</style>

1.该组件要增加表单重置的办法

2.把formData的值绑定到form表单上:model=‘formData’

3.给el-form-item加上prop特点

2,3假如不加上的话,表单内置的重置表单办法会失效

Element Plus组件Form表单-Table表格二次封装

Element Plus组件Form表单-Table表格二次封装

这时咱们现已封装完成了,可是咱们能够发现,咱们的role组件东西有点多了,假如咱们其他组件比方,user组件等,都要用这样类似的布局,咱们这时就又要把这一堆代码给cv一遍,所以咱们有能够把role里边这堆东西再封装一次

开端封装④

page-search.vue组件

<template>
 <Bu-form v-bind="searchFormConfig" v-model="formData" ref="BuFormRef">
  <template #header>
   <div class="header">
    <h1>高档检索</h1>
   </div>
  </template>
  <template #footer>
   <div class="footer">
    <el-button type="primary" :icon="Refresh" @click="resetBtnClick"
     >重置</el-button
    >
    <el-button type="primary" :icon="Search" @click="searchBtnClick"
     >查找</el-button
    >
   </div>
  </template>
 </Bu-form>
</template><script setup lang="ts">
import { Search, Refresh } from '@element-plus/icons-vue'
import BuForm from '@/base-ui/form/form-test.vue'
import { defineProps, ref, defineEmits } from 'vue'
import { useStore } from '@/store'
interface Prop {
 searchFormConfig: any
}
const props = defineProps<Prop>()
const emit = defineEmits<{
  (e: 'resetBtnClick'): void
  (e: 'searchBtnClick', formData: object): void
}>()
const store = useStore()
const BuFormRef = ref<InstanceType<typeof BuForm>>()
​
const formItems = props.searchFormConfig?.formItems ?? []
​
let formDataInit = {}
formItems.map((item: any) => {
 formDataInit[item.field] = ''
})
let formData = ref(formDataInit)
​
// 重置点击
const resetBtnClick = () => {
 BuFormRef.value?.resetForm()
 emit('resetBtnClick')
}
// 查找点击
const searchBtnClick = () => {
 // 这儿需求遍历查找装备项,装备项里能够传dataType,要求数据回来什么类型的数据
 let queryInfo = { ...formData.value }
 props.searchFormConfig.formItems.map((item: any) => {
  if (item.dataType === 'number' && queryInfo[item.field] !== '') {
   queryInfo[item.field] = Number(queryInfo[item.field])
   }
  })
 // 清空queryInfo中没有值的特点
 for (const i in queryInfo) {
  if (queryInfo[i] === '') {
   delete queryInfo[i]
   }
  }
 emit('searchBtnClick', queryInfo)
}
</script><style scoped>
.header {
 padding-top: 20px;
}
.footer {
 text-align: right;
 padding: 0 50px 20px 0;
}
</style>

role.vue组件

<template>
 <div class="role">
  <page-search-test
   :searchFormConfig="searchFormConfig"
   @resetBtnClick="handlerReset"
   @searchBtnClick="handlerSearch"
  ></page-search-test>
 </div>
</template><script setup lang="ts">
import { searchFormConfig } from './config/search-config-test'
import pageSearchTest from '@/components/page-search/page-search-test.vue'
const handlerReset = () => {
 console.log('点击了重置按钮')
}
const handlerSearch = (queryInfo: any) => {
 console.log('点击了查找按钮,值为:', queryInfo)
}
</script><style scoped lang="less"></style>

作用图

Element Plus组件Form表单-Table表格二次封装

这儿咱们就能够体会到了,相同的作用,role里边的代码是这么的少,只需求传入装备项就能够搞出这个表单,并且还能拿到表单数据,并且重点是,下个页面再用这个布局,直接用page-search组件就能够了,只需求传入不同的装备项,假如不同布局,再封装另一个page-search便是了,可是这是后台耶?搞那么富丽呼哨?不便是查找表单,展现表单么

下面附上完好悉数封装代码

完好封装代码⑤

装备项类型文件

// type.tstype IFormType = 'input' | 'password' | 'select' | 'datepicker'interface IFormOption {
 label: string
 value: string | number
}
​
export interface IFormItem {
 field: string //字段名
 type: IFormType //输入框类型
 dataType?: string //输入框回来数据类型
 label: string //输入框标题
 rules?: any[] //输入框验证规矩
 placeholder?: any //输入框默许显现内容
 // 针对select
 options?: IFormOption[] //挑选器的可选子选项
 // 针对特殊特点
 otherOptions?: any
 // 该行是否躲藏
 isHidden?: boolean
}
​
export interface IForm {
 formItems: IFormItem[]
 labelWidth?: string
 itemStyle?: any
 itemColLayout?: any
}
​

装备项文件

import { IForm } from '@/base-ui/form/type'
export const searchFormConfig: IForm = {
  formItems: [
   {
    field: 'id',
    type: 'input',
    label: '用户id',
    placeholder: '请输入用户id'
   },
   {
    field: 'name',
    type: 'input',
    label: '用户名',
    placeholder: '请输入用户名'
   },
   {
    field: 'realname',
    type: 'input',
    label: '实在名字',
    placeholder: '请输入实在名字'
   },
   {
    field: 'cellphone',
    type: 'input',
    label: '电话号码',
    placeholder: '请输入电话号码'
   },
   {
    field: 'enable',
    type: 'select',
    label: '用户状况',
    placeholder: '请挑选用户状况',
    options: [
     { label: '启用', value: 1 },
     { label: '禁用', value: 0 }
    ]
   },
   {
    field: 'createAt',
    type: 'datepicker',
    label: '创立时刻',
    otherOptions: {
     startPlaceholder: '开端时刻',
     endPlaceholder: '结束时刻',
     type: 'daterange'
    }
   }
  ]
}
​

form表单组件文件

<template>
 <div class="header">
  <slot name="header"> </slot>
 </div>
 <el-form
  :label-width="labelWidth"
  ref="ruleFormRef"
  status-icon
  :model="modelValue"
 >
  <el-row>
   <template v-for="item in formItems" :key="item.label">
    <el-col v-bind="itemColLayout">
     <el-form-item
      v-if="!item.isHidden"
      :label="item.label"
      :rules="item.rules"
      :style="itemStyle"
      :prop="item.field"
     >
      <template v-if="item.type === 'input' || item.type === 'password'">
       <el-input
        :placeholder="item.placeholder"
        :show-password="item.type === 'password'"
        :model-value="modelValue[`${item.field}`]"
        @update:modelValue="valueChange($event, item.field)"
        clearable
       />
      </template>
      <template v-else-if="item.type === 'select'">
       <el-select
        :placeholder="item.placeholder"
        :model-value="modelValue[`${item.field}`]"
        @update:modelValue="valueChange($event, item.field)"
        style="width: 100%"
        clearable
       >
        <el-option
         v-for="option in item.options"
         :key="option.value"
         :value="option.value"
         :label="option.label"
        >
        </el-option>
       </el-select>
      </template>
      <template v-else-if="item.type === 'datepicker'">
       <el-date-picker
        unlink-panels
        v-bind="item.otherOptions"
        :model-value="modelValue[`${item.field}`]"
        @update:modelValue="valueChange($event, item.field)"
       ></el-date-picker>
      </template>
     </el-form-item>
    </el-col>
   </template>
  </el-row>
 </el-form>
 <div class="footer">
  <slot name="footer"></slot>
 </div>
</template>
​
<script setup lang="ts">
import { IFormItem } from './type'
import { defineProps, withDefaults, ref, defineEmits, defineExpose } from 'vue'
import type { FormInstance } from 'element-plus'
​
// 界说特点
interface Props {
 formItems: IFormItem[] // 表单装备项
 labelWidth?: string // 每个表单标题宽度
 itemStyle?: object // 每个表单款式
 itemColLayout?: object // 表单布局
 modelValue: object //绑定表单的每个数据
 isHidden?: boolean
}
const props = withDefaults(defineProps<Props>(), {
 formItems: () => [],
 labelWidth: '120px',
 itemStyle: () => ({ padding: '10px 20px' }),
 itemColLayout: () => ({
  xl: 6, // >=1920px
  lg: 8, // >=1200px
  md: 12, // >=992px
  sm: 24, // >=768px
  xs: 24 // <768px
  })
})
const emit = defineEmits<{
  (e: 'update:modelValue', value: any): void
}>()
​
const ruleFormRef = ref<FormInstance>()
​
// 界说办法
const valueChange = (value: any, field: string) => {
 emit('update:modelValue', { ...props.modelValue, [field]: value })
}
​
// 表单重置办法
const resetForm = () => {
 ruleFormRef.value?.resetFields()
}
defineExpose({
 resetForm
})
</script>
​
<style scoped lang="less">
.el-form-item {
 margin-top: 18px;
}
</style>
​

page-search组件文件

<template>
  <div class="page-search">
   <Bu-form v-bind="searchFormConfig" v-model="formData" ref="BuFormRef">
    <template #header>
     <div class="header">
      <h1>高档检索</h1>
     </div>
    </template>
    <template #footer>
     <div class="footer">
      <el-button type="primary" :icon="Refresh" @click="resetClick"
       >重置</el-button
      >
      <el-button type="primary" :icon="Search" @click="searchClick"
       >查找</el-button
      >
     </div>
    </template>
   </Bu-form>
  </div>
</template><script setup lang="ts">
import { useStore } from '@/store'
import BuForm from '@/base-ui/form/form.vue'
import { Search, Refresh } from '@element-plus/icons-vue'
import { ref, defineProps, defineEmits } from 'vue'
import { IForm } from '@/base-ui/form/type'// 界说特点
interface Props {
  searchFormConfig: IForm
}
const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'resetBtnClick'): void
  (e: 'searchBtnClick', formData: object): void
}>()
const store = useStore()
const BuFormRef = ref<InstanceType<typeof BuForm>>()
​
// 1.从接纳到的查找装备中取出各个field,组成表单的数据formData
const formItems = props.searchFormConfig?.formItems ?? []
let formDataInit = {}
formItems.map((item) => {
  formDataInit[item.field] = ''
})
let formData = ref(formDataInit)
​
// 2.重置与查找功用
// 重置按钮触发
const resetClick = () => {
  BuFormRef.value?.resetForm()
  emit('resetBtnClick')
}
// 查找按钮触发
const searchClick = () => {
  // 这儿需求遍历查找装备项,装备项里能够传dataType,要求数据回来什么类型的数据
  let queryInfo = { ...formData.value }
  props.searchFormConfig.formItems.map((item) => {
   if (item.dataType === 'number' && queryInfo[item.field] !== '') {
    queryInfo[item.field] = Number(queryInfo[item.field])
   }
  })
  // 清空queryInfo中没有值的特点
  for (const i in queryInfo) {
   if (queryInfo[i] === '') {
    delete queryInfo[i]
   }
  }
  emit('searchBtnClick', queryInfo)
}
</script><style scoped>
.header {
  padding-top: 20px;
}
.footer {
  text-align: right;
  padding: 0 50px 20px 0;
}
</style>

role页面组件文件

<template>
 <div class="role">
  <page-search
   :searchFormConfig="searchFormConfig"
   @resetBtnClick="handlerReset"
   @searchBtnClick="handlerSearch"
  ></page-search>
 </div>
</template><script setup lang="ts">
import { searchFormConfig } from './config/search-config-test'
import pageSearch from '@/components/page-search/page-search.vue'
const handlerReset = () => {
 console.log('点击了重置按钮')
}
const handlerSearch = (queryInfo: any) => {
 console.log('点击了查找按钮,值为:', queryInfo)
}
</script><style scoped lang="less"></style>

结语

写了这么多,终于写完了,这儿是归于Form表单部分的封装悉数进程,能写到这其实我挺有成就感的哈哈哈哈,由于我刚学会的时候思路有了,可是敲出来有点困难,不过这个进程是我又捋了一遍,然后自己敲出来的,感觉其实也不难,现已把握了这种封装思路与办法了哈哈,其他组件其实也能够使用这种思路,封装出来,在页面上的运用直接传装备项调用就完事,开发每个类似的页面功率简直是牛逼

Table表格的封装

简述

再来折磨一遍,这儿是table表单的封装,其实跟上面的差不多,有点小差异,会用到增加动态插槽,这儿就不墨迹了,直接上用装备项封装前的正常运用

正常运用

user.vue

<template>
 <div class="user">
  <el-table style="width: 100%" :data="dataList" border>
   <!-- 1.传入showSelectColumn时展现的全选列 -->
   <template v-if="contentTableConfig.showSelectColumn">
    <el-table-column type="selection" />
   </template>
   <!-- 2.传入showIndexColumn时展现的序号列 -->
   <template v-if="contentTableConfig.showIndexColumn">
    <el-table-column type="index" label="序号" />
   </template>
   <!-- 3.propList里边的一切列 -->
   <template v-for="item in contentTableConfig.propList" :key="item.prop">
    <el-table-column v-bind="item" show-overflow-tooltip>
     <!-- 传有slotName时展现的插槽列 -->
     <template #default="scope" v-if="item.slotName">
      <template v-if="item.slotName === 'handler'">
       <el-button size="small" :icon="Edit" type="text">修改</el-button>
       <el-button size="small" :icon="Delete" type="text"
        >删去</el-button
       >
      </template>
      <template v-if="item.slotName === 'status'">
       <el-button>{{
         scope.row.status === 0 ? '禁用' : '启用'
        }}</el-button>
      </template>
     </template>
    </el-table-column>
   </template>
  </el-table>
 </div>
</template><script setup lang="ts">
import { Delete, Edit } from '@element-plus/icons-vue'
import { useStore } from '@/store'
import { computed } from 'vue'
const store = useStore()
// 这儿是网络恳求数据
const getPageData = () => {
 store.dispatch(`main/getPageListAction`, {
  queryInfo: {
   offset: 0,
   size: 10
   },
  pageName: 'users'
  })
}
// 页面加载时第一次调用获取表单数据
getPageData()
const dataList = computed(() => store.getters[`main/pageListData`]('users'))
​
// 表格装备项
const contentTableConfig = {
 // 表格装备
 propList: [
   { prop: 'id', label: '用户id', minWidth: '100', align: 'center' },
   { prop: 'name', label: '用户名', minWidth: '100', align: 'center' },
   { prop: 'realname', label: '实在名字', minWidth: '100', align: 'center' },
   { prop: 'cellphone', label: '手机号码', minWidth: '100', align: 'center' },
   {
   prop: 'enable',
   label: '状况',
   minWidth: '100',
   slotName: 'status',
   align: 'center'
   },
   {
   label: '操作',
   minWidth: '120',
   slotName: 'handler',
   align: 'center'
   }
  ],
 // 表格具有序号列
 showIndexColumn: true,
 // 表格具有可选列
 showSelectColumn: true
}
</script><style scoped lang="less"></style>

Element Plus组件Form表单-Table表格二次封装

从上面能够看出,主页面的代码有多冗余,看到就头疼+裂开,所以开端一层封装

开端封装①

装备项类型文件

export interface ITbaleOption {
 prop?: string
 label: string
 minWidth?: string
 slotName?: string
}
export interface ITable {
 propList: ITbaleOption[]
 showIndexColumn?: boolean
 showSelectColumn?: boolean
 childrenProps?: object
}

装备项文件

import { ITable } from '@/base-ui/table/type'
export const contentTableConfig: ITable = {
 // 表格装备
 propList: [
   { prop: 'id', label: '用户id', minWidth: '100' },
   { prop: 'name', label: '用户名', minWidth: '100' },
   { prop: 'realname', label: '实在名字', minWidth: '100' },
   { prop: 'cellphone', label: '手机号码', minWidth: '100' },
   { prop: 'enable', label: '状况', minWidth: '100', slotName: 'status' },
   {
   label: '操作',
   minWidth: '120',
   slotName: 'handler'
   }
  ],
 // 表格具有序号列
 showIndexColumn: false,
 // 表格具有可选列
 showSelectColumn: true
}

table.vue文件

<template>
 <el-table style="width: 100%" :data="listData" border>
  <!-- 1.传入showSelectColumn时展现的全选列 -->
  <template v-if="showSelectColumn">
   <el-table-column type="selection" />
  </template>
  <!-- 2.传入showIndexColumn时展现的序号列 -->
  <template v-if="showIndexColumn">
   <el-table-column type="index" label="序号" />
  </template>
  <!-- 3.propList里边的一切列 -->
  <template v-for="item in propList" :key="item.prop">
   <el-table-column v-bind="item" show-overflow-tooltip>
    <!-- 传有slotName时展现的插槽列 -->
    <template #default="scope" v-if="item.slotName">
     <template v-if="item.slotName === 'handler'">
      <el-button size="small" :icon="Edit" type="text">修改</el-button>
      <el-button size="small" :icon="Delete" type="text">删去</el-button>
     </template>
     <template v-if="item.slotName === 'status'">
      <el-button>{{
        scope.row.status === 0 ? '禁用' : '启用'
       }}</el-button>
     </template>
    </template>
   </el-table-column>
  </template>
 </el-table>
</template><script setup lang="ts">
import { Delete, Edit } from '@element-plus/icons-vue'
import { defineProps, withDefaults, defineEmits } from 'vue'
import { ITbaleOption } from './type'
interface Props {
 listData: any[] //表单数据
 propList: ITbaleOption[] //表单装备项
 showIndexColumn?: boolean //是否显现索引
 showSelectColumn?: boolean //是否显现全选列
 childrenProps?: object // 是否有子数据,树形数据才用得到
}
const props = withDefaults(defineProps<Props>(), {
 showIndexColumn: false,
 showSelectColumn: false,
 childrenProps: () => ({})
})
</script><style scoped></style>

user.vue

<template>
 <div class="user">
  <table-test v-bind="contentTableConfig" :listData="dataList"></table-test>
 </div>
</template><script setup lang="ts">
// 导入表单装备项
import { contentTableConfig } from './config/table-config'
import tableTest from '@/base-ui/table/table-test.vue'
import { useStore } from '@/store'
import { computed } from 'vue'
const store = useStore()
​
// 这儿是网络恳求数据
const getPageData = () => {
 store.dispatch(`main/getPageListAction`, {
  queryInfo: {
   offset: 0,
   size: 10
   },
  pageName: 'users'
  })
}
// 页面加载时第一次调用获取表单数据
getPageData()
const dataList = computed(() => store.getters[`main/pageListData`]('users'))
</script><style scoped lang="less"></style>

Element Plus组件Form表单-Table表格二次封装

能够看到,代码抽出去了,可是咱们能够发现,里边的插槽其实不能给它写死,应该是动态决议的,所以咱们能够这样做

在拥有slotName部分的插槽列部分放入一个签字插槽,再将默许插槽列中的scope.row发回给签字插槽

table.vue(为了可扩展性,我仍旧在里边加了两个插槽,一个顶部一个底部)

<template>
 <div class="header">
  <slot name="header"> </slot>
 </div>
 <el-table style="width: 100%" :data="listData" border>
  <!-- 1.传入showSelectColumn时展现的全选列 -->
  <template v-if="showSelectColumn">
   <el-table-column type="selection" />
  </template>
  <!-- 2.传入showIndexColumn时展现的序号列 -->
  <template v-if="showIndexColumn">
   <el-table-column type="index" label="序号" />
  </template>
  <!-- 3.propList里边的一切列 -->
  <template v-for="item in propList" :key="item.prop">
   <el-table-column v-bind="item" show-overflow-tooltip>
    <!-- 传有slotName时展现的插槽列 -->
    <template #default="scope" v-if="item.slotName">
     <slot :name="item.slotName" :row="scope.row"></slot>
    </template>
   </el-table-column>
  </template>
 </el-table>
 <div class="footer">
  <slot name="footer"> </slot>
 </div>
</template><script setup lang="ts">
import { defineProps, withDefaults, defineEmits } from 'vue'
import { ITbaleOption } from './type'
interface Props {
 listData: any[] //表单数据
 propList: ITbaleOption[] //表单装备项
 showIndexColumn?: boolean //是否显现索引列
 showSelectColumn?: boolean //是否显现全选列
 childrenProps?: object // 是否有子数据,树形数据才用得到
}
const props = withDefaults(defineProps<Props>(), {
 showIndexColumn: false,
 showSelectColumn: false,
 childrenProps: () => ({})
})
</script><style scoped></style>

然后在user.vue中放入签字插槽,传进去table组件里,一起传入一些内容到可扩展插槽里边

user.vue

<template>
 <div class="user">
  <div class="content">
   <table-test v-bind="contentTableConfig" :listData="dataList">
    <!-- 1.header中的插槽 -->
    <template #header>
     <div class="header-default">
      <!-- 传入标题(坐落左边) -->
      <div class="title">用户列表</div>
      <!-- 传入处理内容(坐落右侧) -->
      <div class="handler">
       <el-button type="primary" @click="addUserBtnClick"
        >新建用户</el-button
       >
      </div>
     </div>
    </template>
    <!-- 2.该user页面独有部分 -->
    <template #status="scope">
     <el-button>{{ scope.row.status === 0 ? '禁用' : '启用' }}</el-button>
    </template>
    <!-- 3.每个页面都会有的部分 -->
    <template #handler="scope">
     <el-button
      size="small"
      :icon="Edit"
      type="text"
      @click="handleEditClick(scope.row)"
      >修改</el-button
     >
     <el-button
      size="small"
      :icon="Delete"
      type="text"
      @click="deleteBtnClick(scope.row)"
      >删去</el-button
     >
    </template>
    <!-- 4.footer插槽 -->
    <template #footer>
     <!-- 只有总条数>0才会有分页器 -->
     <div class="footer-default">
      <el-pagination
       :page-sizes="[100, 200, 300, 400]"
       layout="total, sizes, prev, pager, next, jumper"
       :total="400"
      />
     </div>
    </template>
   </table-test>
  </div>
 </div>
</template><script setup lang="ts">
// 导入表单装备项
import { contentTableConfig } from './config/table-config'
import { Delete, Edit } from '@element-plus/icons-vue'
import tableTest from '@/base-ui/table/table-test.vue'
import { useStore } from '@/store'
import { computed } from 'vue'
const store = useStore()
​
// 这儿是网络恳求数据
const getPageData = () => {
 store.dispatch(`main/getPageListAction`, {
  queryInfo: {
   offset: 0,
   size: 10
   },
  pageName: 'users'
  })
}
// 页面加载时第一次调用获取表单数据
getPageData()
const dataList = computed(() => store.getters[`main/pageListData`]('users'))
​
// 点击修改按钮触发事情
const handleEditClick = (row: any) => {
 console.log('点击了修改按钮,数据为:', row)
}
// 点击删去按钮触发事情
const deleteBtnClick = (row: any) => {
 console.log('点击了删去按钮,数据为:', row)
}
// 点击新建用户触发事情
const addUserBtnClick = () => {
 console.log('点击了新建用户')
}
</script><style scoped lang="less">
.content {
 border-top: 20px solid #dedee1;
 padding: 40px;
}
.header-default {
 display: flex;
 justify-content: space-between;
 align-items: center;
 margin-bottom: 20px;
 .title {
  font-size: 22px;
  font-weight: 700;
  }
}
.footer-default {
 display: flex;
 justify-content: flex-end;
 margin-top: 20px;
}
</style>

Element Plus组件Form表单-Table表格二次封装
显然此刻封装现已满意需求了,可是咱们发现主页面的代码页还是比较冗余,假如不用到插槽的话还好,要用到插槽的话,就要在主页面写入很多插槽,多个页面都这样的话页裂开,所以咱们要像之前相同把这坨代码再封一层

开端封装②

page-table.vue

<template>
 <table-test v-bind="contentTableConfig" :listData="dataList">
  <!-- 1.header中的插槽 -->
  <template #header>
   <div class="header-default">
    <!-- 传入标题(坐落左边) -->
    <div class="title">{{ pageNameInChinese }}列表</div>
    <!-- 传入处理内容(坐落右侧) -->
    <div class="handler">
     <el-button type="primary" @click="addUserBtnClick"
      >新建{{ pageNameInChinese }}</el-button
     >
    </div>
   </div>
  </template>
  <!-- 2.该user页面独有部分 -->
  <template #status="scope">
   <el-button>{{ scope.row.status === 0 ? '禁用' : '启用' }}</el-button>
  </template>
  <!-- 3.每个页面都会有的部分 -->
  <template #handler="scope">
   <el-button
    size="small"
    :icon="Edit"
    type="text"
    @click="handleEditClick(scope.row)"
    >修改</el-button
   >
   <el-button
    size="small"
    :icon="Delete"
    type="text"
    @click="deleteBtnClick(scope.row)"
    >删去</el-button
   >
  </template>
  <!-- 4.footer插槽 -->
  <template #footer>
   <!-- 只有总条数>0才会有分页器 -->
   <div class="footer-default">
    <el-pagination
     :page-sizes="[100, 200, 300, 400]"
     layout="total, sizes, prev, pager, next, jumper"
     :total="400"
    />
   </div>
  </template>
 </table-test>
</template><script setup lang="ts">
import { Delete, Edit } from '@element-plus/icons-vue'
import tableTest from '@/base-ui/table/table-test.vue'
import type { ITable } from '@/base-ui/table/type'import { useStore } from '@/store'
import { defineProps, computed } from 'vue'// 界说特点
interface Props {
 contentTableConfig: ITable //表单装备接纳
 pageName: string //表单名字接纳
}
const props = defineProps<Props>()
​
const store = useStore()
​
// 这儿是网络恳求数据
const getPageData = () => {
 store.dispatch(`main/getPageListAction`, {
  queryInfo: {
   offset: 0,
   size: 10
   },
  pageName: props.pageName
  })
}
// 页面加载时第一次调用获取表单数据
getPageData()
const dataList = computed(() =>
 store.getters[`main/pageListData`](props.pageName)
)
​
// 1.获取页面中文名称
const pageNameTransform = (pageName: string): string => {
 switch (pageName) {
  case 'users':
   return '用户'
  default:
   return ''
  }
}
const pageNameInChinese = pageNameTransform(props.pageName)
​
// 点击修改按钮触发事情
const handleEditClick = (row: any) => {
 console.log('点击了修改按钮,数据为:', row)
}
// 点击删去按钮触发事情
const deleteBtnClick = (row: any) => {
 console.log('点击了删去按钮,数据为:', row)
}
// 点击新建用户触发事情
const addUserBtnClick = () => {
 console.log('点击了新建用户')
}
</script><style scoped lang="less">
.header-default {
 display: flex;
 justify-content: space-between;
 align-items: center;
 margin-bottom: 20px;
 .title {
  font-size: 22px;
  font-weight: 700;
  }
}
.footer-default {
 display: flex;
 justify-content: flex-end;
 margin-top: 20px;
}
</style>

user.vue

<template>
 <div class="user">
  <div class="content">
   <page-table
    :contentTableConfig="contentTableConfig"
    pageName="users"
   ></page-table>
  </div>
 </div>
</template><script setup lang="ts">
// 导入表单装备项
import { contentTableConfig } from './config/table-config'
import pageTable from '@/components/page-table/page-table-test.vue'
</script><style scoped lang="less">
.content {
 border-top: 20px solid #dedee1;
 padding: 40px;
}
</style>

Element Plus组件Form表单-Table表格二次封装

图中能够看出作用其实是相同的,主页面的代码少了,只需求传入装备项和pageName(控制网络恳求数据)就能够生成一个表格,可是咱们能够发现,假如多个页面复用的话,其实操作列的插槽是公有的,可是状况列却是私有的,其他页面不一定有状况页,所以状况列内容插入的位置应该在主页面user里边而不该在封装的组件里边,并且修改新建逻辑也是页面独有,应该在主页面履行

开端封装③

user.vue

<template>
 <div class="user">
  <div class="content">
   <page-table
    :contentTableConfig="contentTableConfig"
    pageName="users"
    @editBtnClick="handleEditClick"
    @createBtnClick="handleCreateClick"
   >
    <template #status="scope">
     <el-button>{{ scope.row.status === 0 ? '禁用' : '启用' }}</el-button>
    </template>
   </page-table>
  </div>
 </div>
</template><script setup lang="ts">
// 导入表单装备项
import { contentTableConfig } from './config/table-config'
import pageTable from '@/components/page-table/page-table-test.vue'const handleEditClick = (row: any, pageNameInChinese: any) => {
 console.log('点击了修改按钮,数据为:', row, pageNameInChinese)
}
​
const handleCreateClick = (pageNameInChinese: any) => {
 console.log('点击了新建用户,数据为:', pageNameInChinese)
}
</script><style scoped lang="less">
.content {
 border-top: 20px solid #dedee1;
 padding: 40px;
}
</style>

page-table.vue

<template>
 <table-test v-bind="contentTableConfig" :listData="dataList">
  <!-- 1.header中的插槽 -->
  <template #header>
   <div class="header-default">
    <!-- 传入标题(坐落左边) -->
    <div class="title">{{ pageNameInChinese }}列表</div>
    <!-- 传入处理内容(坐落右侧) -->
    <div class="handler">
     <el-button type="primary" @click="addUserBtnClick"
      >新建{{ pageNameInChinese }}</el-button
     >
    </div>
   </div>
  </template>
  <!-- 2.该user页面独有部分 -->
  <template
   v-for="item in otherPropSlots"
   :key="item.prop"
   #[item.slotName]="scope"
  >
   <template v-if="item.slotName">
    <slot :name="item.slotName" :row="scope.row"></slot>
   </template>
  </template>
  <!-- 3.每个页面都会有的部分 -->
  <template #handler="scope">
   <el-button
    size="small"
    :icon="Edit"
    type="text"
    @click="handleEditClick(scope.row)"
    >修改</el-button
   >
   <el-button
    size="small"
    :icon="Delete"
    type="text"
    @click="deleteBtnClick(scope.row)"
    >删去</el-button
   >
  </template>
  <!-- 4.footer插槽 -->
  <template #footer>
   <!-- 只有总条数>0才会有分页器 -->
   <div class="footer-default">
    <el-pagination
     :page-sizes="[100, 200, 300, 400]"
     layout="total, sizes, prev, pager, next, jumper"
     :total="400"
    />
   </div>
  </template>
 </table-test>
</template><script setup lang="ts">
import { Delete, Edit } from '@element-plus/icons-vue'
import tableTest from '@/base-ui/table/table-test.vue'
import type { ITable } from '@/base-ui/table/type'import { useStore } from '@/store'
import { defineProps, computed, defineEmits } from 'vue'// 界说特点
interface Props {
 contentTableConfig: ITable //表单装备接纳
 pageName: string //表单名字接纳
}
const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'createBtnClick', pageNameInChinese: string): void
  (e: 'editBtnClick', rowData: any, pageNameInChinese: string): void
}>()
​
const store = useStore()
​
// 这儿是网络恳求数据
const getPageData = () => {
 store.dispatch(`main/getPageListAction`, {
  queryInfo: {
   offset: 0,
   size: 10
   },
  pageName: props.pageName
  })
}
// 页面加载时第一次调用获取表单数据
getPageData()
const dataList = computed(() =>
 store.getters[`main/pageListData`](props.pageName)
)
​
// 1.获取页面中文名称
const pageNameTransform = (pageName: string): string => {
 switch (pageName) {
  case 'users':
   return '用户'
  default:
   return ''
  }
}
const pageNameInChinese = pageNameTransform(props.pageName)
​
// 2.归于动态插槽的装备项筛选
const otherPropSlots: any = props.contentTableConfig?.propList.filter(
  (item: any) => {
  if (item.slotName === 'handler') return false
  return item.slotName
  }
)
​
// 点击修改按钮触发事情
const handleEditClick = (row: any) => {
 emit('editBtnClick', row, pageNameInChinese)
}
// 点击删去按钮触发事情
const deleteBtnClick = (row: any) => {
 console.log('点击了删去按钮,数据为:', row)
}
// 点击新建用户触发事情
const addUserBtnClick = () => {
 emit('createBtnClick', pageNameInChinese)
}
</script><style scoped lang="less">
.header-default {
 display: flex;
 justify-content: space-between;
 align-items: center;
 margin-bottom: 20px;
 .title {
  font-size: 22px;
  font-weight: 700;
  }
}
.footer-default {
 display: flex;
 justify-content: flex-end;
 margin-top: 20px;
}
</style>

Element Plus组件Form表单-Table表格二次封装

能够看到,这时咱们现已把独有私有的插槽区别开了,并且修改和新建的逻辑也在主页面中履行,封装结束,下面附上完好代码

完好封装代码④

装备项类型文件

export interface ITbaleOption {
 prop?: string
 label: string
 minWidth?: string
 slotName?: string
 align?: string
}
export interface ITable {
 propList: ITbaleOption[]
 showIndexColumn?: boolean
 showSelectColumn?: boolean
 childrenProps?: object
}
​

装备项文件

import { ITable } from '@/base-ui/table/type'
export const contentTableConfig: ITable = {
 // 表格装备
 propList: [
   { prop: 'id', label: '用户id', minWidth: '100', align: 'center' },
   { prop: 'name', label: '用户名', minWidth: '100', align: 'center' },
   { prop: 'realname', label: '实在名字', minWidth: '100', align: 'center' },
   { prop: 'cellphone', label: '手机号码', minWidth: '100', align: 'center' },
   {
   prop: 'enable',
   label: '状况',
   minWidth: '100',
   slotName: 'status',
   align: 'center'
   },
   {
   label: '操作',
   minWidth: '120',
   slotName: 'handler',
   align: 'center'
   }
  ],
 // 表格具有序号列
 showIndexColumn: false,
 // 表格具有可选列
 showSelectColumn: true
}
​

table表单组件文件

<template>
 <div class="header">
  <slot name="header"> </slot>
 </div>
 <el-table style="width: 100%" :data="listData" border>
  <!-- 1.传入showSelectColumn时展现的全选列 -->
  <template v-if="showSelectColumn">
   <el-table-column type="selection" />
  </template>
  <!-- 2.传入showIndexColumn时展现的序号列 -->
  <template v-if="showIndexColumn">
   <el-table-column type="index" label="序号" />
  </template>
  <!-- 3.propList里边的一切列 -->
  <template v-for="item in propList" :key="item.prop">
   <el-table-column v-bind="item" show-overflow-tooltip>
    <!-- 传有slotName时展现的插槽列 -->
    <template #default="scope" v-if="item.slotName">
     <slot :name="item.slotName" :row="scope.row"></slot>
    </template>
   </el-table-column>
  </template>
 </el-table>
 <div class="footer">
  <slot name="footer"> </slot>
 </div>
</template><script setup lang="ts">
import { defineProps, withDefaults, defineEmits } from 'vue'
import { ITbaleOption } from './type'
interface Props {
 listData: any[] //表单数据
 propList: ITbaleOption[] //表单装备项
 showIndexColumn?: boolean //是否显现索引列
 showSelectColumn?: boolean //是否显现全选列
 childrenProps?: object // 是否有子数据,树形数据才用得到
}
const props = withDefaults(defineProps<Props>(), {
 showIndexColumn: false,
 showSelectColumn: false,
 childrenProps: () => ({})
})
</script><style scoped></style>

page-table组件文件

user页面组件文件

<template>
 <div class="user">
  <div class="content">
   <page-table
    :contentTableConfig="contentTableConfig"
    pageName="users"
    @editBtnClick="handleEditClick"
    @createBtnClick="handleCreateClick"
   >
    <template #status="scope">
     <el-button>{{ scope.row.status === 0 ? '禁用' : '启用' }}</el-button>
    </template>
   </page-table>
  </div>
 </div>
</template><script setup lang="ts">
// 导入表单装备项
import { contentTableConfig } from './config/table-config'
import pageTable from '@/components/page-table/page-table-test.vue'const handleEditClick = (row: any, pageNameInChinese: any) => {
 console.log('点击了修改按钮,数据为:', row, pageNameInChinese)
}
​
const handleCreateClick = (pageNameInChinese: any) => {
 console.log('点击了新建用户,数据为:', pageNameInChinese)
}
</script><style scoped lang="less">
.content {
 border-top: 20px solid #dedee1;
 padding: 40px;
}
</style>

结语

刚学会的代码思路与写法,花了一天收拾,主要还是自己记载一下,颇有收获,感觉大佬共享使我前进~虽然还是很菜….