【Vue】动态方法调用

 

JS的动态方法调用是通过eval函数实现

但是在Vue中是通过组件的$options.methods实现,

写这篇的随笔的原因是因为今天为了封装面包屑组件,花了一下午折腾这个动态方法调用

 

调用DEMO,通过字符串调用对应方法

1、组件标签:

<template>
    <div>
        <h3>调用Demo</h3>
        <button @click="execDynamicMethod">动态方法调用</button>
    </div>
</template>

2、方法声明:

methods: {
  execDynamicMethod() {
    /* 1、获取当前实例的方法集合 */
    const methodList = this.$options.methods

    /* 2、类似Java的反射机制,动态调用需要指定调用的对象是谁 */
    const that = this

    /* 3、根据方法名进行调用,入参调用的对象,和方法参数 */
    const methodName = 'targetMethod'
    methodList[methodName](that, 100)
  },
  /**
   * 目标方法,
   *  注意,使用动态方法调用,首个参数为调用的组件实例,
   *  此组件实例可能和this实例不再一致,注意引用的变化
   * @param val
   * @param val2
   */
  targetMethod(val, val2) {
    console.log(`val === this ? ${val === this}`, val2)
  }
}

3、打印结果:

  

 

 

 

组件封装案例

封装成组件使用,需要配合EventBus的方式实现组件通讯,先定义一个EventBus.js

import Vue from 'vue'

/* 向外部共享一个独立的Vue实例 */
export default new Vue()

  

然后是面包屑组件 Breadcrumb.vue

<template>
  <div class="location">
    <span class="loc-text">
      <el-breadcrumb separator-class="el-icon-arrow-right">
        <el-breadcrumb-item v-if="itemList[0]">{{ itemList[0] }}</el-breadcrumb-item>
        <el-breadcrumb-item v-if="itemList[1]">{{ itemList[1] }}</el-breadcrumb-item>
        <el-breadcrumb-item v-if="itemList[2]">{{ itemList[2] }}</el-breadcrumb-item>
      </el-breadcrumb>
    </span>
    <span style="margin: 0 20px 0 0"><el-button v-show="hasAddAction" v-permission="btnPermit" type="primary" size="mini" :icon="iconClass" @click="addActionEvent()">{{ btnTxt }}</el-button></span>
  </div>
</template>

<script>
import EventBus from './EventBus'
export default {
  name: 'Breadcrumb',
  props: {
    itemList: {
      type: Array,
      required: true,
      default() {
        return []
      }
    }
  },
  data() {
    return {
      btnPermit: '',
      btnTxt: '新增',
      methodName: 'openEditDialog',
      iconClass: 'el-icon-plus',
      hasAddAction: false
    }
  },
  created() {
    this.receiveBrotherCompData()
  },
  methods: {
    /**
     * 接收兄弟组件发送的数据
     */
    receiveBrotherCompData() {
      EventBus.$on('listParam', val => {
        this.btnPermit = val.btnPermit
        this.hasAddAction = val.hasAddAction
        if (val.methodName) this.methodName = val.methodName
        if (val.iconClass) this.iconClass = val.iconClass
        if (val.btnTxt) this.btnTxt = val.btnTxt
      })
    },
    /**
     * 调用父组件事件,再通过父组件的$refs调用兄弟组件的方法
     */
    addActionEvent() {
      this.$emit('openAddDrawer', this.methodName)
    }
  }
}
</script>

 

界面效果是这样:

 

 面包屑的文本条通过父组件穿进来即可,重点是按钮通讯,每个按钮的图标class,文本,调用的功能都不一样

除了方法之外都可以通过对象设置属性来传递控制,但是方法调用比较麻烦

 

方法调用的实现思路:

$refs引用调用 + 自定义$emit

面包屑组件和父组件通过$emit通许,然后父组件和其他子组件通讯用$refs这样,

这样串起来,就相当于面包屑控制兄弟组件的方法了

 

父组件Left.vue $emit绑定的事件名是 openAddDrawer,通过itemList传面包屑的文本内容

<template>
  <div style="height: 100%">
    <el-container style="height: 100%">
      <el-aside class="aside" :style="{ width: isCollapse ? '63px' : '220px' }">
        <div class="navbox">
          <el-col class="sideleft-nav" :style="{ width: isCollapse ? '63px' : '220px' }">
            <el-menu index="0" class="el-menu-vertical-demo" :style="{ width: isCollapse ? '63px' : '220px' }" :collapse="isCollapse" :collapse-transition="isTransition" :default-active="funcNav[0].id + ' '">
              <template v-for="firstItem in funcNav">
                <el-menu-item v-if="firstItem.fuHref !== null && firstItem.fuHref !== undefined" :key="firstItem.id" :index="firstItem.id + ' '" @click="clickNav(firstItem.fuHref)">
                  <svg-icon :icon-class="firstItem.fuIcon" class="nav-icon" />
                  <span v-show="!isCollapse">{{ firstItem.fuName }}</span>
                </el-menu-item>
                <el-submenu v-else :key="firstItem.id" :index="firstItem.id + ' '">
                  <template slot="title">
                    <svg-icon :icon-class="firstItem.fuIcon" class="nav-icon" />
                    <span slot="title">{{ firstItem.fuName }}</span>
                  </template>
                  <template v-for="secondItem in firstItem.children">
                    <el-menu-item v-if="secondItem.fuHref !== null && secondItem.fuHref !== undefined" :key="secondItem.id" :index="secondItem.id + ' '" @click="clickNav(secondItem.fuHref, firstItem.fuName, secondItem.fuName)">
                      <template slot="title">
                        <svg-icon :icon-class="secondItem.fuIcon" class="nav-icon" />
                        <span slot="title">{{ secondItem.fuName }}</span>
                      </template>
                    </el-menu-item>
                    <el-submenu v-else :key="secondItem.id" :index="secondItem.id + ' '">
                      <template slot="title">
                        <svg-icon :icon-class="secondItem.fuIcon" class="nav-icon" />
                        <span slot="title">{{ secondItem.fuName }}</span>
                      </template>
                      <el-menu-item v-for="thirdItem in secondItem.children" :key="thirdItem.id" :style="[isCollapse ? stylePadding20 : stylePadding40]" :index="thirdItem.id + ' '" @click="clickNav(thirdItem.fuHref, firstItem.fuName, secondItem.fuName, thirdItem.fuName)">{{ thirdItem.fuName }}</el-menu-item>
                    </el-submenu>
                  </template>
                </el-submenu>
              </template>
            </el-menu>
          </el-col>
        </div>
        <div class="spread" :style="{ width: isCollapse ? '63px' : '220px' }">
          <svg-icon v-if="!isCollapse" icon-class="shrink-icon" class="shrink-icon" @click="shrinkNav" />
          <svg-icon v-if="isCollapse" icon-class="unfold-icon" class="shrink-icon" @click="shrinkNav" />
        </div>

      </el-aside>
      <el-main class="box-main">
        <breadcrumb :item-list="itemList" @openAddDrawer="openAddDrawer" />
        <component :is="currentComponent" ref="dynamicRefKey" />
      </el-main>
    </el-container>
  </div>
</template>

<script>
import { getFuncTree } from '@/api/system/privileges/func'
import { uuid } from '@/utils/UUID'

import Home from './home'

import SystemParam from '@/views/amerp/system/common/param/param-list'
import SystemLog from '@/views/amerp/system/common/log/log-list'
import AreaList from '@/views/amerp/system/common/area/area-list'
import Dict from '@/views/amerp/system/common/dict/dict-list'

import User from '@/views/amerp/system/privileges/user/user-list'
import Role from '@/views/amerp/system/privileges/role/role-list'

import ProcessModel from '@/views/amerp/system/process/model/model-list'
import ProcessInstance from '@/views/amerp/system/process/instance/instance-list'
import ProcessDesign from '@/views/amerp/system/process/design/design-panel'
import ProcessDefine from '@/views/amerp/system/process/define/define-list'

import PushTask from '@/views/amerp/system/push/task/task-list'

import Company from '@/views/amerp/system/archives/company/company-list'
import BankAccount from '@/views/amerp/system/archives/bankacco/bankacco-list'
import Department from '@/views/amerp/system/archives/department/department-list'
import Employee from '@/views/amerp/system/archives/employee/employee-list'
import Ware from '@/views/amerp/system/archives/ware/ware-list'
import Wacost from '@/views/amerp/system/archives/ware/wacost-list'
import Waprice from '@/views/amerp/system/archives/ware/waprice-list'
import Subject from '@/views/amerp/system/archives/subject/subject-list'
import Expeitem from '@/views/amerp/system/archives/expeitem/expeitem-list'
import ExSuList from '@/views/amerp/system/archives/expeitem/exsu-list'
import Customer from '@/views/amerp/system/archives/customer/customer-list'
import Cubank from '@/views/amerp/system/archives/customer/cubank-list'

import DiApp from '@/views/amerp/system/dingtalk/app/app-list'
import DiDept from '@/views/amerp/system/dingtalk/department/dept-list'
import DiUser from '@/views/amerp/system/dingtalk/user/user-list'

import PrInfo from '@/views/amerp/sales/project/info/info-list'
import PrIncontent from '@/views/amerp/sales/project/incontent/incontent-list'
import PrInrival from '@/views/amerp/sales/project/inrival/inrival-list'
import PrIntender from '@/views/amerp/sales/project/intender/intender-list'
import PrIndealfactor from '@/views/amerp/sales/project/indealfactor/indealfactor-list'
import PrInsolution from '@/views/amerp/sales/project/insolution/insolution-list'
import PrInvisit from '@/views/amerp/sales/project/invisit/invisit-list'
import PrInContact from '@/views/amerp/sales/project/incontact/incontact-list'
import PrInCoFamily from '@/views/amerp/sales/project/incofamily/incofamily-list'

import FinExApply from '@/views/amerp/financial/expense/exapply/exapply-list'
import FinExApDetail from '@/views/amerp/financial/expense/exapdetail/exapdetail-list'
import FinExApTravel from '@/views/amerp/financial/expense/exaptravel/exaptravel-list'
import FinExApAllot from '@/views/amerp/financial/expense/exapallot/exapallot-list'

import FinSpApply from '@/views/amerp/financial/spend/spapply/spapply-list'
import FinSpApDetail from '@/views/amerp/financial/spend/spapdetail/spapdetail-list'

import OpeSeApply from '@/views/amerp/operating/seal/seapply/seapply-list'
import Breadcrumb from '@/components/Breadcrumb/Index'

export default {
  name: 'Left',
  components: { Breadcrumb, Home, SystemParam, User, Role, Company, Dict, SystemLog, AreaList, BankAccount, Department, Ware, Wacost, Waprice, Subject, Expeitem, ExSuList, Employee, Customer, Cubank, PrInfo, PrIncontent, PrInrival, PrIntender, PrIndealfactor, PrInsolution, PrInvisit, FinExApply, FinExApDetail, FinExApTravel, FinExApAllot, FinSpApply, FinSpApDetail, PrInContact, PrInCoFamily, OpeSeApply, DiUser, DiDept, DiApp, ProcessModel, ProcessInstance, ProcessDesign, PushTask, ProcessDefine },
  data() {
    return {
      currentComponent: '',
      flag: true,
      funcNav: [],
      uuid: uuid,
      isCollapse: false,
      isTransition: false,
      defaultActive: '',
      stylePadding20: {
        'margin-left': '0px!important',
        'padding-left': '20px!important'
      },
      stylePadding40: {
        'margin-left': '14px!important',
        'padding-left': '28px!important'
      },
      itemList: null
    }
  },
  created() {
    this.initFuncNav('0')
  },
  methods: {
    openAddDrawer(val) {
      const methodList = this.$refs['dynamicRefKey'].$options.methods
      const that = this.$refs['dynamicRefKey']
      methodList[val](that, '')
    },
    initFuncNav(parentId) {
      getFuncTree({
        module: escape('系统管理-菜单管理-查询菜单'),
        uuid: this.uuid,
        sys_code: 'amerp',
        parentIds: parentId
      }).then(res => {
        if (res.code === 200) {
          if (res.data) {
            this.funcNav = res.data
            // 选中第一个
            this.defaultActive = res.data[0].id + ' '
            this.currentComponent = res.data[0].fuHref
          }
        }
      })
    },
    clickNav(href, nav1, nav2, nav3) {
      this.currentComponent = href
      this.itemList = [nav1, nav2, nav3]
    },
    shrinkNav() {
      this.isCollapse = !this.isCollapse
    }
  }
}
</script>

 

子组件代码:

声明了 initialBreadcrumb 方法,初始化的时候赋值面包屑组件需要的内容

<template>
  <div>
    <div class="row-search">
      <div class="row-1">
        <el-form ref="form" :model="form" label-width="82px" :inline="true">
          <el-form-item label="代码编号" size="small">
            <el-input v-model.trim="form.diCode" style="width: 170px" clearable placeholder="代码编号 \ 代码名称" />
          </el-form-item>

          <el-form-item label="代码类别" size="small">
            <el-input v-model.trim="form.diCateIdent" style="width: 190px" clearable placeholder="代码类别 \ 代码类别名称" />
          </el-form-item>

          <el-form-item label="封存状态" size="small">
            <el-select v-model="form.sealupState" clearable style="width: 100px" placeholder="请选择">
              <el-option v-for="item in sealupStateList" :key="item.value" :label="item.label" :value="item.value" />
            </el-select>
          </el-form-item>

          <el-form-item size="small">
            <el-button icon="el-icon-search" @click="searchPage">查询</el-button>
            <el-button icon="el-icon-delete" @click="resetInput">重置</el-button>
          </el-form-item>
        </el-form>
      </div>

    </div>
    <div class="list-table">
      <el-table v-loading="loading" size="small" stripe :data="tableData">
        <el-table-column align="center" type="index" width="50px" label="序号" />
        <el-table-column prop="diCode" min-width="120px" align="center" label="代码编号" />
        <el-table-column prop="diName" min-width="120px" align="left" label="代码名称" />
        <el-table-column prop="diCateIdent" min-width="120px" align="center" label="代码类别" />
        <el-table-column prop="diCateName" min-width="120px" align="center" label="代码类别名称" />
        <el-table-column prop="diParentCode" min-width="120px" align="center" label="上级代码编号" />
        <el-table-column prop="sort" min-width="80px" align="center" label="顺序" />
        <el-table-column prop="sealupState" fixed="right" min-width="80px" align="center" label="封存状态">
          <template slot-scope="scope">
            <el-switch v-model="scope.row.sealupState" :disabled="!permissions.indexOf('amerp:dict:seal') > 0" active-value="1" inactive-value="0" @change="sealup(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" width="120px" align="center">
          <template slot-scope="scope">
            <span v-permission="'amerp:dict:update'" class="link-color" @click="openEditDialog('', scope.row)">
              <i class="el-icon-edit-outline" /> 修改
            </span>  
            <!--<span v-permission="'amerp:dict:delete'" class="link-color" @click="deleteThisRow(scope.row)">-->
            <!--<i class="el-icon-circle-close" /> 删除-->
            <!--</span>-->
          </template>
        </el-table-column>
      </el-table>
      <el-pagination style="float: none; text-align: right; margin-top: 10px;" :current-page="page.current" :page-size="page.size" layout="total, prev, pager, next, jumper" :total="page.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
    </div>

    <!-- 编辑弹窗 -->
    <!--<el-dialog v-if="editDialogVisible" :title="editDialogTitle" :append-to-body="true" :close-on-click-modal="false" :visible.sync="editDialogVisible" width="760px" @close="closeEditDialog">-->
    <!--<dict-edit-dialog :ref="editDialogRef" :form="currentRow" />-->
    <!--</el-dialog>-->

    <el-drawer :title="editDialogTitle" :append-to-body="true" size="35%" :modal="false" :visible.sync="editDialogVisible" :destroy-on-close="true" @close="closeEditDialog">
      <dict-edit-dialog :ref="editDialogRef" :form="currentRow" />
    </el-drawer>
  </div>
</template>

<script>
import '@/assets/css/style.css'
import DictEditDialog from './dict-add'
import { getDictPage, deleteDict, sealupDict, unSealupDict } from '@/api/system/common/dict'
import { mapGetters } from 'vuex'
export default {
  name: 'DictList',
  components: {
    DictEditDialog
  },
  data() {
    return {
      /* 编辑弹窗 */
      editDialogVisible: false,
      editDialogTitle: '',
      editDialogRef: 'editDialogRefKey',

      /* 详情弹窗 */
      detailDialogVisible: false,
      detailDialogRef: 'detailDialogRefKey',

      currentRow: undefined,

      /* 列表变量 */
      loading: false,
      tableData: [],
      form: {
        diCode: '',
        diCateIdent: '',
        sealupState: ''
      },
      sealupStateList: [
        { label: '全部', value: '' },
        { label: '封存', value: '1' },
        { label: '启用', value: '0' }
      ],
      createTimeArr: [],
      page: {
        current: 0,
        size: 10,
        total: 0
      }
    }
  },
  computed: {
    ...mapGetters([
      'permissions'
    ])
  },
  created() {
    this.initialBreadcrumb()
    this.searchPage()
  },
  methods: {
    initialBreadcrumb() {
      const param = {
        btnPermit: 'amerp:dict:add',
        btnTxt: '新增',
        iconClass: 'el-icon-plus',
        methodName: 'openEditDialog',
        hasAddAction: true
      }
      this.$eventBus.$emit('listParam', param)
    },
    handleSizeChange(pageSize) {
      this.page.size = pageSize
      this.query()
    },
    handleCurrentChange(pageIndex) {
      this.page.current = pageIndex
      this.query()
    },
    searchPage() {
      this.page.current = 0
      this.page.total = 0
      this.query()
    },
    async query() {
      this.loading = true
      this.form.page = this.page
      const { data: res, total } = await getDictPage(this.form)
      this.tableData = res
      this.page.total = total
      this.loading = false
    },
    resetInput() {
      this.form = {
        diCode: '',
        diCateIdent: '',
        sealupState: ''
      }
    },
    openEditDialog(curr, row) {
      const isUpdate = !!row
      if (isUpdate) curr = this
      curr.editDialogTitle = isUpdate ? '更新公共字典' : '新增公共字典'
      curr.currentRow = isUpdate ? JSON.parse(JSON.stringify(row)) : undefined
      curr.editDialogVisible = true
    },
    closeEditDialog() {
      this.editDialogVisible = false
    },
    deleteThisRow(row) {
      this.$confirm(`确认要删除公共字典[${row.diCode}], 是否继续?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
        await deleteDict(row)
        this.$message.success('删除成功!')
        this.searchPage()
      }).catch(() => {
        this.query()
      })
    },
    createTimeChange(val) {
      const hasSelect = !!val
      this.form.startCreateTime = hasSelect ? val[0] : ''
      this.form.endCreateTime = hasSelect ? val[1] : ''
    },
    openDetailDialog(row) {
      this.currentRow = JSON.parse(JSON.stringify(row))
      this.detailDialogVisible = true
    },
    closeDetailDialog() {
      this.detailDialogVisible = false
    },
    sealup(row) {
      const isSealupState = row.sealupState === '1'
      this.$confirm(`确认要${isSealupState ? '封存' : '启用'}公共字典[${row.diCode}], 是否继续?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
        isSealupState ? await sealupDict(row) : await unSealupDict(row)
        this.$message.success(isSealupState ? '封存成功!' : '启用成功!')
        this.searchPage()
      }).catch(() => {
        this.query()
      })
    }
  }
}
</script>

  

每个功能页都需要用这个面包屑,所以EventBus注册成了全局变量,方便调用

/* main.js 全局注册 */
import EventBus from '@/components/Breadcrumb/EventBus'
Vue.prototype.$eventBus = EventBus

  

父组件动态调用时,通过$refs引用过去的:

    openAddDrawer(val) {
      const methodList = this.$refs['dynamicRefKey'].$options.methods
      const that = this.$refs['dynamicRefKey']
      methodList[val](that, '')
    },

  

被调用的方法需要多写this参数位:

这里要区分是从本this对象调用的还是父组件$refs调用

    openEditDialog(curr, row) {
      const isUpdate = !!row
      if (isUpdate) curr = this
      curr.editDialogTitle = isUpdate ? '更新公共字典' : '新增公共字典'
      curr.currentRow = isUpdate ? JSON.parse(JSON.stringify(row)) : undefined
      curr.editDialogVisible = true
    },

 

注意本实例调用和动态调用的参数位一致:

<span v-permission="'amerp:dict:update'" class="link-color" @click="openEditDialog('', scope.row)">

  

 

posted @ 2022-10-26 19:38  emdzz  阅读(2524)  评论(0编辑  收藏  举报