el-dropdown下的自定义内容必须在一个根元素中才能都打开下拉列表

文章讲述了在使用el-dropdown组件时遇到的问题,即TypeError提示不能设置svg元素的className属性,因为该属性只有getter。解决方案是更改自定义内容的根元素,避免使用svg,确保只有一个div作为根元素,以保证点击事件正常工作。

报错:TypeError: Cannot set property className of # which has only a getter,(svg不能作为el-dropdowm下自定义内容的根元素)

<el-dropdown class="right-button-setall" placement="bottom" @command="addFollowRecord">
    <div> // 只能有一个根元素,否则只有点击第一个div生效
      <div class="add-btn">
        <svg-icon icon-class="search-header-xinzeng" class="svg-icon" />
      </div>
      <span>新增跟进</span>
    </div>
    <el-dropdown-menu slot="dropdown" class="right-button-dropdown">
      <el-dropdown-item command="线上跟进">线上跟进</el-dropdown-item>
      <el-dropdown-item command="拜访">拜访</el-dropdown-item>
    </el-dropdown-menu>
</el-dropdown>

<think>我们之前讨论过,`el-dropdown-item` 使用自定义指令 `v-auth` 失效的原因,是因为 `el-dropdown-item` 是动态渲染的,而自定义指令在初始绑定阶段无法作用到还未渲染的元素上。同时,我们给出了三种解决方案。现在,用户进一步询问为什么在 `el-button` 上使用正常,而在 `el-dropdown-item` 上失效,且无权限时按钮可以隐藏。 ### 原因分析 1. **渲染时机不同**: - `el-button` 是静态渲染的,在父组件挂载时就会渲染,因此自定义指令 `v-auth` 可以在其生命周期钩子中正确执行,判断权限并移除无权限的按钮。 - `el-dropdown-item` 是动态渲染的,它位于 `el-dropdown-menu` 中,而 `el-dropdown-menu` 通常是在点击下拉按钮后才渲染的。因此,在初始阶段,自定义指令绑定到 `el-dropdown-item` 时,该元素尚未渲染,导致指令逻辑无法执行。 2. **指令的生命周期钩子**: - 自定义指令(如 `v-auth`)通常会在元素挂载时(`mounted` 或 `beforeMount` 钩子)执行权限判断并决定是否显示。但对于动态渲染的元素,如果指令在元素创建之前就已经执行完毕,那么指令逻辑就不会再次触发。 3. **Element Plus 组件的封装**: - `el-dropdown-item` 组件内部可能使用了 `v-if` 或 `v-show` 来控制显示,其元素可能被包裹在多层标签内,导致自定义指令无法正确获取到需要控制的元素。 ### 解决方案回顾 我们之前提出的三种解决方案中,最推荐的是**方法一:使用 `v-if` 配合权限检查函数**。因为这种方式可以确保在 `el-dropdown-item` 渲染之前就进行权限判断,从而避免指令失效的问题。 ### 示例代码 #### 方法一:使用 `v-if` 和权限检查函数 ```vue <el-dropdown> <span class="el-dropdown-link"> 下拉菜单 </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item v-if="checkAuth(&#39;system:role:edit&#39;)" @click="handleEdit" >编辑</el-dropdown-item> <el-dropdown-item v-if="checkAuth(&#39;system:role:delete&#39;)" @click="handleDelete" >删除</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> <script setup> import { useStore } from &#39;vuex&#39;; const store = useStore(); const checkAuth = (permission) => { // 假设权限列表存储在 Vuex 的 state.buttons 中 return store.state.buttons.includes(permission); }; </script> ``` ### 为什么按钮可以正常隐藏? 因为按钮(`el-button`)在初始渲染时就已经存在于 DOM 中,自定义指令 `v-auth` 的钩子函数能够在元素挂载时执行,检查权限并移除无权限的按钮。而对于 `el-dropdown-item`,它在初始渲染时并不在 DOM 中,所以指令钩子无法在正确的时机执行。 ### 相关技术点 1. **自定义指令的生命周期**: -Vue 3 中,自定义指令有以下几个生命周期钩子(与 Vue 2 有所不同): - `created`:在绑定元素的 attribute 或事件监听器被应用之前调用。 - `beforeMount`:在元素被插入到 DOM 前调用。 - `mounted`:在绑定元素的父组件及他自己的所有子节点都挂载完成后调用。 - `beforeUpdate`:在包含组件的 VNode 更新前调用。 - `updated`:在包含组件的 VNode 及其子组件的 VNode 更新后调用。 - `beforeUnmount`:在绑定元素的父组件卸载前调用。 - `unmounted`:在绑定元素的父组件卸载后调用。 对于动态组件,我们可能需要使用 `updated` 钩子来重新检查权限,但这种方法较为复杂,且需要确保在正确时机触发。 2. **Element Plus 下拉菜单的渲染机制**: - `el-dropdown` 使用 Vue 的 `Teleport` 或 `Popper` 来动态生成下拉菜单,这使得下拉菜单的内容在初始渲染时并不存在于父组件的 DOM 树中,而是按需生成。 ### 结论 `el-dropdown-item` 使用自定义指令 `v-auth` 失效的原因是**动态渲染导致的指令执行时机问题**。而 `el-button` 是静态渲染,因此指令可以正常生效。推荐使用 `v-if` 配合权限检查函数来替代自定义指令,以确保在动态渲染的组件中正确控制显示。 §§相关问题§§ 1. 如何正确注册和使用 Vue 3自定义指令? 2. Element Plus 下拉菜单的动态渲染机制是怎样的? 3.Vue 3 中,如何优化动态生成组件的性能? </think>### Element Plus 中 `el-dropdown-item` 使用 `v-auth` 失效的原因分析 据引用[1]提到的 Element Plus 渲染机制,结合自定义指令特性,`el-dropdown-item` 使用 `v-auth` 失效的本原因在于 **动态渲染时机冲突**: 1. **异步渲染机制** `el-dropdown-item` 属于动态渲染组件(点击下拉按钮时才生成 DOM 结构)。而自定义指令 `v-auth` 的钩子函数(如 `mounted`)**在父组件挂载阶段同步执行**,此时下拉项尚未渲染,导致权限判断逻辑无法执行[^1]。 2. **指令作用域差异** `el-button` 是静态组件(初始渲染即存在 DOM),指令可在 `mounted` 阶段正确操作 DOM。但 `el-dropdown-item` 被包裹在 `el-dropdown-menu` 内部,其真实 DOM 通过 `Popper.js` 动态生成,指令无法捕获其生命周期[^1]。 3. **组件封装层级** `el-dropdown-item` 实际渲染为多层嵌套结构: ```html <!-- 指令绑定在此处 --> <li class="el-dropdown-menu__item" v-auth="&#39;perm&#39;"> <!-- 但实际内容在内部 span --> <span>操作项</span> </li> ``` 权限验证后移除 `<li>` 标签会导致整个下拉项结构异常,而按钮是单一元素可直接移除。 --- ### 解决方案(推荐) #### 方法一:数据驱动权限过滤 ✅ ```vue <el-dropdown> <el-dropdown-menu> <!-- 提前过滤无权限项 --> <el-dropdown-item v-for="item in filteredActions" :key="item.id" @click="item.handler" > {{ item.label }} </el-dropdown-item> </el-dropdown-menu> </el-dropdown> <script setup> import { computed } from &#39;vue&#39;; import { useAuthStore } from &#39;@/stores/auth&#39;; const authStore = useAuthStore(); const menuActions = [ { id: 1, label: &#39;编辑&#39;, perm: &#39;edit&#39;, handler: handleEdit }, { id: 2, label: &#39;删除&#39;, perm: &#39;delete&#39;, handler: handleDelete } ]; const filteredActions = computed(() => { return menuActions.filter(item => authStore.hasPermission(item.perm)); }); </script> ``` #### 方法二:指令改造(监听下拉事件) ```typescript // auth.directive.ts export const vAuth = { updated(el, binding) { const { value } = binding; if (!checkPermission(value)) { el.parentNode?.removeChild(el); // 动态移除元素 } } } // 组件使用 <el-dropdown @visible-change="forceUpdate"> <el-dropdown-item v-auth="&#39;delete&#39;">删除</el-dropdown-item> </el-dropdown> ``` --- ### 为什么按钮(Button)可以正常隐藏? 1. **同步渲染优势** `el-button` 在组件挂载时即存在于 DOM 树中,指令的 `mounted` 钩子可立即操作 DOM 2. **独立元素结构** 按钮是单一元素组件,移除后不会破坏布局: ```html <!-- 移除整个button不影响其他元素 --> <button v-auth="&#39;create&#39;" class="el-button">创建</button> ``` --- ### 最佳实践建议 1. **避免在动态组件使用指令** 优先通过数据层(Vuex/Pinia)预过滤菜单项,而非操作 DOM 2. **统一权限校验入口** 封装 `useAuth()` 组合式函数,复用权限逻辑: ```ts // utils/auth.ts export function useAuth() { const check = (perm: string) => { return authStore.permissions.includes(perm); } return { check }; } ``` 3. **谨慎操作动态 DOM** 如需强制使用指令,配合 `el-dropdown` 的 `visible-change` 事件触发更新: ```vue <el-dropdown @visible-change="v => v && nextTick(() => {/* 触发指令更新 */})"> ``` > 数据驱动方案(方法一)可 100% 解决该问题,且符合 Vue 的响应式设计理念[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值