后台管理系统表单封装

背景:后台管理系统有很多布局类似的页面,查询展示的,表单提交的等。

系统基于Vue + ElementUI开发的单页应用。

每次写一个新增/编辑页面都会写一堆类似如下的表单代码:

<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
  <el-form-item label="活动名称" prop="name">
    <el-input v-model="ruleForm.name"></el-input>
  </el-form-item>
  <el-form-item label="活动区域" prop="region">
    <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
      <el-option label="区域一" value="shanghai"></el-option>
      <el-option label="区域二" value="beijing"></el-option>
    </el-select>
  </el-form-item>
  <el-form-item label="活动时间" required>
    <el-col :span="11">
      <el-form-item prop="date1">
        <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm.date1" style="width: 100%;"></el-date-picker>
      </el-form-item>
    </el-col>
    <el-col class="line" :span="2">-</el-col>
    <el-col :span="11">
      <el-form-item prop="date2">
        <el-time-picker placeholder="选择时间" v-model="ruleForm.date2" style="width: 100%;"></el-time-picker>
      </el-form-item>
    </el-col>
  </el-form-item>
  <el-form-item label="即时配送" prop="delivery">
    <el-switch v-model="ruleForm.delivery"></el-switch>
  </el-form-item>
  <el-form-item label="活动性质" prop="type">
    <el-checkbox-group v-model="ruleForm.type">
      <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
      <el-checkbox label="地推活动" name="type"></el-checkbox>
      <el-checkbox label="线下主题活动" name="type"></el-checkbox>
      <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
    </el-checkbox-group>
  </el-form-item>
  <el-form-item label="特殊资源" prop="resource">
    <el-radio-group v-model="ruleForm.resource">
      <el-radio label="线上品牌商赞助"></el-radio>
      <el-radio label="线下场地免费"></el-radio>
    </el-radio-group>
  </el-form-item>
  <el-form-item label="活动形式" prop="desc">
    <el-input type="textarea" v-model="ruleForm.desc"></el-input>
  </el-form-item>
  <el-form-item>
    <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
    <el-button @click="resetForm('ruleForm')">重置</el-button>
  </el-form-item>
</el-form>
<script>
  export default {
    data() {
      return {
        ruleForm: {
          name: '',
          region: '',
          date1: '',
          date2: '',
          delivery: false,
          type: [],
          resource: '',
          desc: ''
        },
        rules: {
          name: [
            { required: true, message: '请输入活动名称', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
          ],
          region: [
            { required: true, message: '请选择活动区域', trigger: 'change' }
          ],
          date1: [
            { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
          ],
          date2: [
            { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
          ],
          type: [
            { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
          ],
          resource: [
            { required: true, message: '请选择活动资源', trigger: 'change' }
          ],
          desc: [
            { required: true, message: '请填写活动形式', trigger: 'blur' }
          ]
        }
      };
    },
    methods: {
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    }
  }
</script>

想想要是能不用关注html代码结构,只需要通过js配置就能帮我实现试图层渲染多好?

这样肯定需要把表单封装起来!

如果想通过配置实现的话,常见表单项有这么多类型:输入框、下拉选、单选框、复选框...还有自定义组件等,肯定得有个字段需要告诉组件 我想要渲染的表单项类型。

我一开始想到的是类似这种(参考:vue后台管理系统常用的页面组件封装 - 掘金):

<!-- 搜索组件 -->
<template>
  <div class="content">
    <template v-for="(item, index) in fieldList">
      <div
        v-show="icon === 'close' ? index < count : true"
        :key="index"
      >
        <div class="item-style">
          <span class="label-style" :style="{width: width.labelWidth + 'px'}">{{ item.label }}</span>
          <!-- 普通输入框 -->
          <el-input
            v-if="item.type === 'input'"
            v-model.trim="defaultData[item.value]"
            :size="size"
            :style="{width: width.itemWidth + 'px'}"
            :type="item.type"
            :disabled="item.disabled"
            :placeholder="getPlaceholder(item)"
            @keyup.enter.native="handleFilter"
            @blur="handleEvent($event, item.value)"
          />
          <!-- 日期/时间 -->
          <el-date-picker
            v-if="item.type === 'date'"
            v-model="defaultData[item.value]"
            :size="size"
            :style="{width: width.itemWidth + 'px'}"
            :type="item.dateType"
            :picker-options="item.TimePickerOptions"
            :clearable="item.clearable"
            :disabled="item.disabled"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            value-format="yyyy-MM-dd HH:MM:SS"
            :placeholder="getPlaceholder(item)"
            @change="handleEvent($event, item.value, 'change')"
          />
          <!-- 选择框 -->
          <el-select
            v-if="item.type === 'select'"
            v-model="defaultData[item.value]"
            :size="size"
            :style="{width: width.itemWidth + 'px'}"
            :disabled="item.disabled"
            :clearable="item.clearable"
            :filterable="item.filterable"
            :multiple="item.multiple"
            :placeholder="getPlaceholder(item)"
            @change="handleEvent($event, item.value, 'change')"
          >
            <el-option
              v-for="childItem in listTypeInfo[item.list]"
              :key="childItem.id"
              :label="childItem.name"
              :value="childItem.id"
              :disabled="childItem.disabled"
            />
          </el-select>
          <!-- 计数器 -->
          <el-input-number
            v-if="item.type === 'inputNumber'"
            v-model="defaultData[item.value]"
            :size="size"
            :style="{width: width.itemWidth + 'px'}"
            :min="item.min"
            :max="item.max"
            @change="handleEvent($event, item.value, 'change')"
          />
        </div>
      </div>
    </template>
      <div
        v-if="!btnHidden && fieldList.length !== 0"
        class="btn-style"
      >
        <el-button
          :type="btnStyle[0].type"
          :size="size"
          :plain="btnStyle[0].plain"
          :round="btnStyle[0].round"
          :icon="btnStyle[0].icon"
          :disabled="btnStyle[0].disabled"
          @click="handleFilter"
        >
          {{ btnStyle[0].text }}
        </el-button>
        <el-button
          :type="btnStyle[1].type"
          :size="size"
          :plain="btnStyle[1].plain"
          :round="btnStyle[1].round"
          :disabled="btnStyle[1].disabled"
          :icon="btnStyle[1].icon"
          @click="handleReset"
        >
          {{ btnStyle[1].text }}
        </el-button>
      </div>
      <el-button
        v-if="!btnHidden && fieldList.length > count"
        type="text"
        @click="icon === 'open' ? icon = 'close' : icon = 'open'"
        style="margin-left: 6px;"
      >{{ icon === 'open' ? '收起' : '展开' }}
        <i :class="icon === 'open' ? 'el-icon-caret-top' : 'el-icon-caret-bottom'" />
      </el-button>
  </div>
</template>
<script>

export default {
    name: 'ESearch',
    props: {
      /**字段默认数据 */
      data: {
        type: Object,
        default: () => {},
        required: true
      },
      /**字段配置项 */
      fieldList: {
        type: Array,
        default: () => [],
        required: true
      },
      /**相关的列表 */
      listTypeInfo: {
        type: Object,
        default: () => {}
      },
      /**按钮区域是否隐藏 */
      btnHidden: {
        type: Boolean,
        default: false
      },
      /**组件尺寸 */
      size: {
        type: String,
        default: 'mini'
      },
      /**默认搜索数 */
      count: {
        type: Number,
        default: 4
      },
      /**组件及label宽度 */
      width: {
        type: Object,
        default: () => ({
          labelWidth: 110,
          itemWidth: 220
        })
      },
      /**按钮配置 */
      btnStyle: {
        type: Array,
        default: () => [
          { icon: null, text: '搜索', disabled: false, type: 'primary', plain: false, round: false },
          { icon: null, text: '重置', disabled: false, type: null, plain: false, round: false }
        ]
      }
    },
    data: () => ({
      defaultData: {},
      icon: 'close'
    }),
    mounted () {
      /**
       * 子组件无法直接修改父组件传递过来的值
       * 于是将父组件传递的值首先赋值给 defaultData
       * 在搜索条件中同样使用 defaultData
       * 永远保持 props 传递的 data 纯洁度
       */
      this.defaultData = {...this.data}
    },
    methods: {
        /**
         * @func 占位符显示
         * @param {Object} row
         * @desc 📝
         */
        getPlaceholder (row) {
          let placeholder
          if (row.type === 'input') {
            placeholder = '请输入' + row.label
          } else if (row.type === 'select' || row.type === 'time' || row.type === 'date') {
            placeholder = '请选择' + row.label
          } else {
            placeholder = row.label
          }
          return placeholder
        },
        /**
         * @func 事件处理
         * @desc 📝
         */
        handleEvent (event, val, change) {
          let obj = {
            value: change === 'change' ? event : event.target.value,
            label: val
          }
          this.$emit('handleEvent', obj)
        },
        /**
         * @func 搜索
         * @desc 📝
         */
        handleFilter () {
          this.$emit('handleFilter', this.defaultData)
        },
        /**
         * @func 重置
         * @desc 📝
         */
        handleReset () {
          this.defaultData = {...this.data}
          this.$emit('handleReset', this.defaultData)
        }
    }
}
</script>
<style lang="scss" scoped>
.content {
  display: flex;
  flex-wrap: wrap;
  position: relative;
}
.content .item-style {
  margin: 6px auto;
  line-height: 1;
}
.content .item-style .label-style {
  display: inline-block;
  justify-self: end;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  -o-text-overflow: ellipsis;
  text-overflow: ellipsis;
  text-align: right;
  margin-right: 12px;
  color: #222222;
}
.btn-style {
  margin: 6px 0 6px auto;
}
</style>

后来看到一篇文章感觉封装的更高级:我是如何让公司后台管理系统焕然一新的(下)-封装组件 - 知乎

所以我先尝试采用后面那种方式在项目中试试水,后续会记录遇到的各种问题。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值