Vue中相似组件复用的几种种方法

问题

项目中有七八个页面是非常相似的,都是从左边选择人员,通过按钮移动到右边。下面截取了部分页面,这几个页面只有画红圈的地方可能有区别:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

几种复用页面的方法

  1. 最简单的方式是把这七八个页面的内容封装到一个组件中去,通过组件的属性控制不同部分的显示,但是这样会导致页面内容太多,不同页面的逻辑混到一起,不好维护。
  2. 另一种方式是把公共部分封装到一个组件中去,最下方使用<slot name="footer">开放一个接口,使用的时候通过footer定义页面最下方圆圈中的部分,类似这样:
<template>
  <chooseuser-template ref="chooseRef">
    <template #footer>
      <div id="#noticelay" style="width:100%;padding-left:50px">
        <el-form>
          <el-form-item style="margin-bottom: 2px;" label="加签办理方式">
            <el-radio-group ref="dealwayTypeRef" v-model="dealAddType">
              <el-radio label="backward">加签人办理完成后,我再办理</el-radio>
              <el-radio label="forward">加签人办理完成后,直接通过</el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item style="margin-bottom: 2px;" label="通知方式">
            <el-checkbox-group ref="noticetypeRef" v-model="noticeType">
              <el-checkbox v-for="(notice,index) in notices" :key="index" :label="notice.ccode">{{ notice.cname }}</el-checkbox>
            </el-checkbox-group>
          </el-form-item>

        </el-form>
      </div>
    </template>

  </chooseuser-template>
</template>
<script>
//省略...
export default {
  components: { chooseuserTemplate },
  data() {
    return {
      // 隐藏上移下移按钮
      showUpArrow: false,
      showDownArrow: false,
      // 通知类型
      noticeType: ['pm'],
      // 处理方式
      dealAddType: 'backward',
      notices: []
    }
  },
  mounted() {
    ChooseUserRequest.getNoticeType().then(res => {
      this.notices = res.data
    })
  },

  methods: {
    init(params) {
      this.$refs['chooseRef'].selectedItemsData = params.addPersonList
      this.noticeType = params.noticetype.split(',')
      this.dealAddType = params.dealway
    }
  }
}
</script>

<style lang="scss" scoped>
.formStyle {height:100%;overflow:auto}
</style>

<chooseuser-template>为公共组件,通过名为footer的slot可以自定义页面底部的部分

  1. 还有一种方式是使用extends关键字,vue开发中相似的页面可以通过extends关键字复用公共组件,但是如果在子页面中包含<template></template>标签,则会使用子页面的<template>覆盖公共组件的<template>,如果仅想改变一部分html该怎么办呢?vue本身并没有提供这种能力,不过就这几个页面来说可以结合vue的$mount()函数来实现,对于不同的footer部分,可以通过new Vue()结合jsx动态创建footer部分,然后mount到模板底部,具体是这样的,首先定义公共组件<chooseuser-template>作为模板:
<template>

  <el-form ref="form" :model="form" label-width="120px" class="formStyle">
    <el-row style="margin-top:10px">
      <el-col :span="12">
        <el-form-item label="所属组织" prop="cOrgnName">
          <el-input v-model="form.cOrgnName" suffix-icon="el-icon-more" readonly style="width:200px" @click.native="openOrgnChooser" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="搜索职员" prop="condtion">
          <el-input v-model="form.condtion" style="width:200px">
            <span slot="suffix" @click="search">
              <i class="el-input__icon el-icon-search"></i>
            </span>
          </el-input>
        </el-form-item>
      </el-col>
    </el-row>
    <el-row>
      <!-- 左侧 -->
      <el-col :span="11">
        <el-form-item prop="queryType">
          <el-radio-group v-model="form.queryType" style="margin-left:-70px" @change="handleQueryTypeChanged">
            <el-radio v-for="item in queryTypeData" :key="item.id" :label="item.id">{{ item.text }}</el-radio>
            <!-- <el-radio label="dept">部门</el-radio>
            <el-radio label="group">分组</el-radio>
            <el-radio label="relation">关系</el-radio> -->
          </el-radio-group>
        </el-form-item>
        <el-form-item v-show="showLeftTree1" style="margin-left:-70px">
          <el-scrollbar ref="leftTree1ScrollbarRef" style="height:130px" class="scroll-bar-custom" wrap-style="overflow-x:auto;">
            <el-tree
              ref="leftTree1Ref"
              v-loading="leftTree1Loading"
              :data="leftTree1Data"
              node-key="cguid"
              :default-expand-all="true"
              :props="leftTree1Props"
              :expand-on-click-node="false"
              @node-click="handleLeftTree1Click"
            >
              <span slot-scope="{node,data}" class="span-ellipsis">
                <span :title="node.label" :style="{color:data.enabled === false?'rgb(220 222 226)':'black'}">{{ node.label }}</span>
              </span>
            </el-tree>
          </el-scrollbar>
        </el-form-item>
        <el-form-item style="margin-left:-70px">
          <el-scrollbar ref="leftTree2ScrollbarRef" style="height:130px;" class="scroll-bar-custom" wrap-style="overflow-x:auto;">
            <el-tree
              ref="leftTree2Ref"
              v-loading="leftTree2Loading"
              :data="leftTree2Data"
              node-key="cguid"
              :default-expand-all="true"
              :props="leftTree2Props"
              :expand-on-click-node="false"
            >
              <span slot-scope="{node}" class="span-ellipsis">
                <span :title="node.label">{{ node.label }}</span>
              </span>
            </el-tree>
          </el-scrollbar>
        </el-form-item>
        <!-- 组织参照 -->
        <orgn-chooser
          :visible.sync="showOrgnDialog"
          :orgn-options="{root:'1', orgntype:'ADMIN'}"
          @close="handleOrgnChooserClose"
        />
      </el-col>

      <!-- 中间按钮 -->
      <el-col :span="2" class="center-col">
        <el-button v-if="showRightArrow" icon="el-icon-arrow-right" type="primary" circle @click="addUser"></el-button>
        <el-button v-if="showLeftArrow" icon="el-icon-arrow-left" type="primary" circle @click="delUser"></el-button>
        <el-button v-if="showUpArrow" icon="el-icon-arrow-up" type="primary" circle @click="moveUpDown('up')"></el-button>
        <el-button v-if="showDownArrow" icon="el-icon-arrow-down" type="primary" circle @click="moveUpDown('down')"></el-button>
      </el-col>

      <!-- 右侧 -->
      <el-col :span="11">
        <el-form-item id="selectedItems" label="已选">
          <el-scrollbar style="height:275px;" class="scroll-bar-custom" wrap-style="overflow-x:auto;">
            <el-tree
              ref="selectedItemsRef"
              v-loading="selectedItemsLoading"
              :data="selectedItemsData"
              node-key="code"
              :default-expand-all="true"
              :props="selectedItemsProps"
              draggable
              :allow-drop="allowDrop"
              :expand-on-click-node="false"
              :highlight-current="true"
            >
              <span slot-scope="{node}" class="span-ellipsis">
                <span :title="node.label">{{ node.label }}</span>
              </span>
            </el-tree>
          </el-scrollbar>
        </el-form-item>
      </el-col>
    </el-row>

    <!-- 通知方式 ,子类实现, 用于第三种实现方式-->
    <el-row>
      <div id="footer" />
    </el-row>
    <!-- 通知方式 ,子类实现, 用于第二种实现方式-->
    <slot name="footer"></slot>
  </el-form>

</template>

<script>
//省略...
</script>

代码最下方定义了footer,用作接口,当实现具体的页面时,可以这样:

<script>
//省略...

export default {
  name: 'FreeChooseCheckUser',
  extends: chooseuserTemplate,
  data() {
    return {
      dealType: 'serial',
      noticeType: ['pm']
    }
  },

  async mounted() {
    const instance = this
    const response = await ChooseUserRequest.getNoticeType()
    const footer = new Vue({
      parent: instance, // 必须手动指定父组件,否则watch监听器中获取不到$parent

      computed: {
        computedDealType: {
          get() {
            return this.$parent.dealType
          },
          set(v) {
            this.$parent.dealType = v
          }
        },
        computedNoticeType: {
          get() {
            return this.$parent.noticeType
          },
          set(v) {
            this.$parent.noticeType = v
          }
        }
      },
      render() {
        return (
          <div id='#noticelay' style='width:100%;padding-left:50px'>
            <el-form>
              <el-form-item label='执行方式' style='margin-bottom:5px'>
                <el-radio-group ref='dealtypeRef' v-model={this.computedDealType}>
                  <el-radio label='serial'>串行</el-radio>
                  <el-radio label='parallel'>并行</el-radio>
                </el-radio-group>
              </el-form-item>
              <el-form-item label='通知方式' style='margin-bottom:5px'>
                <el-checkbox-group ref='noticetypeRef' v-model={this.computedNoticeType}>
                  {
                    response.data.map((value) => {
                      return <el-checkbox label={value.ccode}>{value.cname}</el-checkbox>
                    })
                  }
                </el-checkbox-group>
              </el-form-item>
            </el-form>
          </div>
        )
      }
    }).$mount('#footer')

    this.$refs['footer'] = footer
  }
}
</script>

<style lang="scss" scoped src="@/views/aos/workflow/styles/chooseuser.scss"></style>

<style lang="scss" scoped>
.formStyle {height:100%;overflow:auto}
</style>

这里定义了一个FreeChooseCheckUser组件,它通过extends继承<chooseuser-template>后不需要定义<template>标签,<template>标签会完全继承<chooseuser-template>,而footer部分是通过new Vue()结合jsx动态生成的,生成后通过$mount方法挂载到公共组件上(其实也可以把footer写成vue文件,然后import进来,再mount到模板底部,不过footer部分都很简短,没必要单独写成一个vue文件)。这种方式看起来比第二种方式麻烦一点,但是逻辑上讲似乎更自然一些,extends继承公共组件后,缺少什么就补充什么,而第二种方式需要在<template>中写<chooseuser-template>,尽管整个页面只有<chooseuser-template>一个组件,凭空多了一层控件引用关系。
上面只说了footer部分的处理,至于上面的筛选条件(部门、分组、关系)和中间的按钮(上移下移左移右移)处理很简单,他们都是在<chooseuser-template>中通过data里的变量控制的,当使用第三种extends方式定义页面时,只需要覆盖相应的变量即可,类似这样:

<script>
export default {
name:'myComponent'data() {
    return {
      //....
      // 查询条件,部门,分组,关系
      queryTypeData: [{
        id: 'dept', text: '部门'
      }, {
        id: 'group', text: '分组'
      }
      //隐藏关系
      //, {
      //  id: 'relation', text: '关系'
      //}
      ],
      // 控制中间的四个按钮显示
      showUpArrow: true,
      showRightArrow: true,
      showLeftArrow: true,
      showDownArrow: true,
    }
}
}
      //....
</script>
Logo

前往低代码交流专区

更多推荐