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

  

 

posted @   emdzz  阅读(2623)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 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 理论概念和入门上手
点击右上角即可分享
微信分享提示