当前位置: 首页 > 知识库问答 >
问题:

javascript - Vue 组件开发中 $createElement API 与右键菜单的动态渲染优化?

淳于嘉树
2024-07-03

在开发的时候发现公司封装的树组件的右键菜单是预先在页面中放置一个DOM,通过修改绝对定位top,left的方式来将菜单定位到鼠标点击的位置,但是又引发了一些样式和布局的问题。所以想用Vue的$createElementAPI去优化一下。同时也希望能更深入了解Vue的VNode实现原理。目前的设计思路是声明一个局部组件,在点击菜单项时通过$emit传递状态, 最后统一在父组件中处理点击事件。
代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
  <title>Title</title>
  <script src="./dependency/vue.js"></script>
</head>
<body>
<div id="app">
  <context-menu :test-render="testRender" @click-li="collectClick"></context-menu>
</div>
<script>
  let contextMenu = Vue.component('contextMenu', {
    props: ['testRender'],
    render: function (h) {
      return h('div', {
          class: 'contextMenuBox'
        },
        [h('ul', this.testRender.map(item => h('li', {
          on: {
            click: (...args) => {
              this.$emit('click-li', args)
            }
          }
        }, item)))]);
    }
  });
  const vm = new Vue({
    data: {
      testRender: ['Item 1', 'Item 2', 'Item 3'],
    },
    components: {
      'context-menu': contextMenu
    },
    methods: {
      collectClick(e) {
        console.log(e)
      }
    }
  }).$mount('#app');

</script>
</body>
</html>

目前遇到的问题是:这种实现方式与在页面中预先放置一个DOM的实现方式是一致的,只是代码的复用性会稍强一些(也可能更优雅)包括在createElementAPI中传递的参数,只能是固定的某些内容,那能否在一个场景下,譬如问题中的右键菜单,也许会加入权限来控制可选项的数量。这些更抽象的设计思路,有没有更优雅的设计思路呢?希望路过的大佬不吝赐教。谢谢各位大佬。

共有2个答案

姚煜
2024-07-03

了解一下vue2动态创建组件的方法吧,给个以前写的例子,也可以去github上逛逛

组件:

<template>
  <ul
    v-show="value"
    class="context-menu"
    :style="style"
    @contextmenu.prevent
    @click="onClick"
  >
    <li
      v-for="(i, index) in menuItems"
      :key="i.content"
      :data-index="index"
      class="context-menu-item"
    >
      {{ i.content }}
    </li>
  </ul>
</template>

<script>
/**
 * 右键菜单,用于页签栏,右键点击页签时弹出
 */

export default {
  name: 'ContextMenu',

  props: {
    // 是否显示,支持v-modal
    value: Boolean,
    // 菜单定义数组,{content: string 菜单文字, click: function 点击菜单时触发的函数}
    items: Array,
    // 菜单距离屏幕左侧的距离,单位px
    left: Number,
    // 菜单距离屏幕顶部的距离,单位px
    top: Number,
    // 菜单距离屏幕边缘的最小距离,单位px
    minDistance: { type: Number, default: 10 }
  },

  data() {
    this.willAutoAdaptLeft = false
    this.willAutoAdaptTop = false
    return {
      realLeft: '0px',
      realTop: '0px'
    }
  },

  computed: {
    style() {
      return { left: this.realLeft, top: this.realTop }
    },

    menuItems() {
      return this.items.filter(Boolean)
    }
  },

  watch: {
    value: {
      immediate: true,
      handler(v) {
        document.body[v ? 'addEventListener' : 'removeEventListener']('click', this.close)
        if (v) {
          this.willAutoAdaptLeft = true
          this.willAutoAdaptTop = true
          this.$nextTick(this.autoAdapt)
        }
      }
    },
    left: {
      immediate: true,
      handler: 'autoAdaptLeft'
    },
    top: {
      immediate: true,
      handler: 'autoAdaptTop'
    }
  },

  methods: {
    close() {
      this.$emit('input', false)
    },
    autoAdapt() {
      this.autoAdaptTop(this.top)
      this.autoAdaptLeft(this.left)
    },
    autoAdaptTop(v) {
      if (this.willAutoAdaptTop) {
        this.willAutoAdaptTop = false
        return
      }
      if (!this.value || v == null) return

      const elHeight = this.$el.offsetHeight
      const remainHeight = document.body.clientHeight - v - this.minDistance
      const over = elHeight - remainHeight
      const finalTop = over > 0 ? v - over : v

      this.realTop = `${finalTop}px`
    },
    autoAdaptLeft(v) {
      if (this.willAutoAdaptLeft) {
        this.willAutoAdaptLeft = false
        return
      }
      if (!this.value || v == null) return

      const elWidth = this.$el.offsetWidth
      const remainWidth = document.body.clientWidth - v - this.minDistance
      const over = elWidth - remainWidth
      const finalLeft = over > 0 ? v - over : v

      this.realLeft = `${finalLeft}px`
    },

    /**
     * 事件代理
     *
     * @param event {Event}
     */
    onClick(event) {
      if (!event.target.classList.contains('context-menu-item')) {
        return
      }

      const index = Number(event.target.dataset.index)
      const menuItem = this.menuItems[index]

      menuItem && menuItem.click()
    }
  },

  beforeDestroy() {
    document.body.removeEventListener('click', this.close)
  }
}
</script>

函数式调用:

import Vue from 'vue'
import ContextMenu from './index'

/**
 * 函数形式创建右键菜单
 * 注意!每次调用时都会创建一个新的实例,需要自行销毁上一次产生的实例
 *
 * @param items {{content:string,click:function}[]} 菜单数组
 * @param options {{left:number,top:number,minDistance?:number}} 配置项
 * @return {function} 关闭右键菜单的方法
 */
export default function(items, options) {
  const ctor = Vue.extend(ContextMenu)
  const instance = new ctor({ propsData: { value: true, items, ...options } })

  instance.$mount()
  instance.$once('input', value => {
    if (!value) {
      instance.$destroy()
      document.body.removeChild(instance.$el)
    }
  })
  document.body.appendChild(instance.$el)

  return () => instance.close()
}
韩自怡
2024-07-03

Teleport 和 https://floating-ui.com/docs/vue

teleport 可以将 dom 挂载在 body 的其他位置,floating-ui/vue 用来计算位置参数,位置这一块自己写定位的话 有太多边界情况要处理。

 类似资料:
  • 1。生产环境不渲染,本地环境正常 2。生产环境静态路径,资源加载,Vue 初始化,APP.vue 初始化都没有问题 3。只有一个 vue-router 不渲染,history 模式不行,hash 能正常渲染 !!注意,是疑难杂症,不是命名,静态路径那种小白问题

  • 问题内容: 在React JSX中,似乎无法执行以下操作: 我收到一个解析错误:意外令牌{。这不是React可以处理的吗? 我正在设计此组件,以便在内部隐藏的值将包含有效的HTML元素(h1,p等)。有什么办法可以使这项工作吗? 问题答案: 您不应该将组件子弹放在花括号中: 这是一个有效的小提琴:http : //jsfiddle.net/kb3gN/6668/ 此外,您还可以找到JSX编译器,有

  • 本文向大家介绍vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单,包括了vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单的使用技巧和注意事项,需要的朋友参考一下 今天分享的不是技术,今天给大家分享个插件,针对现有的vue右键菜单插件,大多数都是需要使用插件本身自定义的标签,很多地方不方便,可扩展性也很低,所以我决定写了一款自定义指令调用右键菜单(vuerightmenu)   安装

  • 如何在表格组件的表头部分定制右键,支持不同单元格显示不同菜单项目。

  • Vue中是否有一个事件在元素重新渲染后被触发?我有一个更新的对象,这导致我的模板更新。在它更新之后,我想触发一个运行jQuery函数的事件。代码如下所示: 如您所见,我尝试使用监视事件,但是当监视事件触发时,我的页面尚未更新。然而,blogPost已更改,但vue仍在呈现页面。我知道理论上我可以通过添加setTimeout来解决这个问题,但我更喜欢更干净的东西。

  • 当用户在网页中点击鼠标右键后,会唤出一个菜单,在上面有复制、粘贴和翻译等选项,为用户提供快捷便利的功能。Chrome也将这里开放给了开发者,也就是说我们可以把自己所编写的扩展功能放到右键菜单中。 要将扩展加入到右键菜单中,首先要在Manifest的permissions域中声明contextMenus权限。 "permissions": [ "contextMenus" ] 同时还要在i