vue封装一个可处理表单的组件

一、需求。

最近在开发一个新系统,有很多功能以及表单需要处理,为了偷懒,想着封装一个组件统一处理下相关逻辑,这样下来,每个功能只需写一个列表查询的组件和一个表单组件,省时省心。
最终效果:


最终效果图

二、需要实现的点:

1、 增加、修改、查看三个功能都在这里进行处理
2、每个功能只需要写好表单以及对应事件,用该组件统一处理外部样式以及触发子组件中的表单事件(这里的表单作为子组件)
3、因为有左侧边栏的缘故,还需要动态调整宽度

三、具体实现

<!--基于element封装的抽屉组件,具体api见:https://element.eleme.io/#/zh-CN/component/drawer-->
<template>
  <div>
    <el-drawer ref="drawer"
               append-to-body
               :size="drawer.width"
               :before-close="beforeClose"
               :visible="drawer.show"
               :direction="direction"
               :destroy-on-close="destroy"
               :wrapperClosable="wrapperClosable"
               @open="getNewWidth"
               @close="closeHander">
      <!-- 标题部分 -->
      <template slot="title">
        <p>{{ titleText }}</p>
      </template>
      <!-- 主要内容 -->
      <slot name="main"></slot>
      <div class="drawer__footer">
        <Button size="large"
                icon="md-close"
                @click="CloseDrawer">{{
          showSubmitBtn ? "取消" : "关闭"
        }}</Button>
        <Button v-if="showSubmitBtn"
                size="large"
                type="primary"
                @click="submit"
                icon="md-checkbox-outline"
                :loading="loading">{{ loading ? "提交中 ..." : "确 定" }}</Button>
      </div>
    </el-drawer>
  </div>
</template>
<script>
export default {
  name: 'JcDrawer',
  props: {
    //模式:1新增  2修改  3查看
    mode: {
      default: 1,
      type: Number,
    },
    //是否显示
    show: {
      default: false,
    },
    //标题
    title: {
      type: String,
    },
    //打开方向
    direction: {
      default: 'rtl',
      type: String,
    },
    //控制是否在关闭 Drawer 之后将子元素全部销毁
    destroy: {
      default: true,
      type: Boolean,
    },
    //点击遮罩层是否可以关闭 Drawer
    wrapperClosable: {
      default: false,
      type: Boolean,
    },
    //是否显示提交按钮(有的界面无需提交)
    showSubmitBtn: {
      default: true,
      type: Boolean,
    },
  },
  data() {
    return {
      isAuto: false, // 是否自动关闭
      Bus: this.$BusFactory(this),
      loading: false,
      drawer: {
        width: 0,
        show: this.show,
        mode: this.mode,
      },
    }
  },
  mounted() {
    //初始化完成时添加一个事件用于监听屏幕大小变化,自适应调整宽度
    window.onresize = () => {
      return (() => {
        this.getNewWidth()
      })()
    }
  },

  watch: {
    //监听父组件的值控制是否显示
    show: {
      immediate: true,
      deep: true,
      handler(newvalue, oldvalue) {
        this.drawer.show = newvalue
      },
    },
    mode: {
      immediate: true,
      handler(newvalue, oldvalue) {
        this.drawer.mode = newvalue
      },
    },
  },
  computed: {
    /**
     * @description 标题
     */
    titleText() {
      if (this.drawer.mode === 1) return `添加${this.title}信息`
      if (this.drawer.mode === 2) return `修改${this.title}信息`
      if (this.drawer.mode === 3) return `查看${this.title}信息`
    },
  },
  methods: {
    /**
     * @description 关闭之后的事件
     */
    closeHander() {
      //取消自动关闭
      this.isAuto = false
    },
    /**
     * @description 关闭窗口
     */
    CloseDrawer() {
      if (this.showSubmitBtn) {
        this.isAuto = false
      } else {
        this.isAuto = true
      }
      this.$refs.drawer.closeDrawer()
    },

    /**
     * @description 关闭抽屉的事件
     * @param {function} done 关闭的回调
     */
    beforeClose(done) {
      if (!this.isAuto && this.showSubmitBtn) {
        this.$confirm('确定要关闭吗?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        }).then((_) => {
          this.$emit('close')
          done()
        })
      } else {
        this.$emit('close')
        done()
      }
    },
    //提交事件
    submit(callback) {
      var that = this
      that.loading = true
      if (this.drawer.mode == 1) {
        //使用事件总线的方式触发表单组件中的事件
        this.Bus.$emit('Add', function (res) {
          if (res.success) {
            that
              .$confirm('添加成功!', '提示', {
                confirmButtonText: '确定',
                showCancelButton: false,
                type: 'success',
                closeOnClickModal: false,
                closeOnPressEscape: false,
                showClose: false,
              })
              .then(() => {
                //点击确认之后直接退出并销毁表单
                that.isAuto = true
                that.$refs.drawer.closeDrawer()
              })
          }
        })
      }
      if (this.drawer.mode == 2) {
        this.Bus.$emit('Edit', function (res) {
          if (res.success) {
            that
              .$confirm('修改成功!', '提示', {
                confirmButtonText: '确定',
                showCancelButton: false,
                type: 'success',
                closeOnClickModal: false,
                closeOnPressEscape: false,
                showClose: false,
              })
              .then(() => {
                that.isAuto = true
                that.$refs.drawer.closeDrawer()
              })
          }
        })
      }
      if (this.drawer.mode == 3) {
        this.Bus.$emit('Detail', function () {
          that.isAuto = true
          that.$refs.drawer.closeDrawer()
        })
        that.loading = false
      }
    },
    /**
     * @description 设置主界面的宽度
     */
    getNewWidth() {
      //组件宽度=当前屏幕宽度-侧边栏宽度
      this.drawer.width =
        document.body.clientWidth -
        (parseInt(document.getElementById('left_nav')?.style?.width) || 0)
    },
  },
}
</script>
<style scoped>
.drawer__footer {
  width: 100%;
  border-top: 1px solid #e8eaec;
  position: absolute;
  bottom: 0;
  padding: 10px;
  display: flex;
  justify-content: center;
  background-color: white;
  z-index: 99;
}
.drawer__footer button {
  margin: 0 10px;
}
</style>

使用(列表查询组件):

<!--列表组件-->
<template>
  <!--...列表展示内容-->
  <jc-drawer :show="formShow"
             @close="formShow = false"
             :title="title"
             :mode="mode">
    <!-- 使用插槽插入表单 -->
    <template slot="main">
      <role-form :roleId="roleId"
                 @getList="getList"
                 :mode="mode"></role-form>
    </template>
  </jc-drawer>
</template>
<script>
import jcDrawer from '@/components/jc-cpn/jc-drawer.vue'
import RoleForm from './form'
export default {
  components: { jcDrawer, RoleForm },
  name: 'role',
  data() {
    return {
      roleId: null,
      mode: 1,
      title: '角色',
      formShow: false,
    }
  },

  methods: {
    // 添加角色
    roleAdd() {
      this.mode = 1
      this.formShow = true
    },
    //编辑角色
    roleEdit() {
      this.mode = 2
      this.roleId = 点击编辑按钮时行的id
      this.formShow = true
    },
    //查看角色
    roleDetail() {
      this.mode = 3
      this.roleId = 点击查看按钮时行的id
      this.formShow = true
    },
  },
}
</script>

表单组件:

<!-- 角色的form表单 -->
<template>
  <div>
    <!-- 表单元素 -->
  </div>
</template>
<script>
export default {
  name: 'RoleForm',
  props: {
    roleId: {
      type: Number,
      default: null,
    },
    mode: {
      type: Number,
      default: 1,
    },
  },
  data() {
    return {
      // 事件总线实例。详情见:https://zhuanlan.zhihu.com/p/32029461
      Bus: this.$BusFactory(this),
      loading: false,
    }
  },
  watch: {
    //监听父组件的值控制是否显示
    roleId: {
      immediate: true,
      deep: true,
      handler(newvalue, oldvalue) {
        if (this.mode == 2) {
          //回填数据
          this.setCurrentData()
        }
      },
    },
  },
  mounted() {
    //采用事件总线的方式绑定drawer组件中定义的方法,采用事件回调的方式通知drawer此次事件的完成情况,在drawer中统一处理
    //新增方法
    this.Bus.$on('Add', (callback) => {
      this.RoleAdd(callback)
    })
    //新增方法
    this.Bus.$on('Edit', (callback) => {
      this.RoleEdit(callback)
    })
  },
  methods: {
    /**
     * @description 角色添加
     * @param {function} callback 回调函数,请求完成后用于通知抽屉组件完成状态
     */
    RoleAdd(callback) {
      callback(...res)
    },
    /**
     * @description 角色修改
     * @param {function} callback 回调函数,请求完成后用于通知抽屉组件完成状态
     */
    RoleEdit(name, callback) {
      callback(...res)
    },
  },
}
</script>

最终目录结构
总结:完成这样一套逻辑之后,对应功能节点只需要完成如图表单组件和列表查询组件,而且表单中只需处理数据,提示或者报错都放到drawer组件中进行处理,极大程度上保护了自己的头发。

另:有关事件总线不明白的地方可参考:Vue自动销毁的vue event Bus

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容