【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)">