【Vue】动态方法调用
JS的动态方法调用是通过eval函数实现
但是在Vue中是通过组件的$options.methods实现,
写这篇的随笔的原因是因为今天为了封装面包屑组件,花了一下午折腾这个动态方法调用
调用DEMO,通过字符串调用对应方法
1、组件标签:
1 2 3 4 5 6 | < template > < div > < h3 >调用Demo</ h3 > < button @click="execDynamicMethod">动态方法调用</ button > </ div > </ template > |
2、方法声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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
1 2 3 4 | import Vue from 'vue' /* 向外部共享一个独立的Vue实例 */ export default new Vue() |
然后是面包屑组件 Breadcrumb.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <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传面包屑的文本内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | <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 方法,初始化的时候赋值面包屑组件需要的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | <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注册成了全局变量,方便调用
1 2 3 | /* main.js 全局注册 */ import EventBus from '@/components/Breadcrumb/EventBus' Vue.prototype.$eventBus = EventBus |
父组件动态调用时,通过$refs引用过去的:
1 2 3 4 5 | openAddDrawer(val) { const methodList = this .$refs[ 'dynamicRefKey' ].$options.methods const that = this .$refs[ 'dynamicRefKey' ] methodList[val](that, '' ) }, |
被调用的方法需要多写this参数位:
这里要区分是从本this对象调用的还是父组件$refs调用
1 2 3 4 5 6 7 | 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 }, |
注意本实例调用和动态调用的参数位一致:
1 | < span v-permission="'amerp:dict:update'" class="link-color" @click="openEditDialog('', scope.row)"> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2021-10-26 【Java】关于获取注解的问题发现
2021-10-26 【Zookeeper】Re02 CuratorAPI
2020-10-26 【Vue】Re05 操作数组的API
2020-10-26 【Vue】Re04 指令:第二部分
2020-10-26 【Vue】Re03 computed属性计算和ES6的一些补充
2020-10-26 【Vue】Re02 指令:第一部分
2020-10-26 【Vue】Re01 理论概念和入门上手