Vue项目中FullCalendar资源列标题点击触发下拉菜单的实现问询
为FullCalendar资源列标题添加交互功能的解决方案
先说核心结论:FullCalendar 6.x 没有内置资源列标题的交互配置(比如点击弹出下拉菜单),你用 resourceLabelDidMount 回调的路子完全对,根本不需要jQuery,用原生JS或者Vue的方式就能搞定,下面给你优化现有代码的建议,还有更贴合Vue开发习惯的替代方案:
现有代码的优化方向
你现在的代码已经能实现点击弹出菜单,但有几个细节可以完善:
避免全局DOM查询冲突
你用document.querySelector('.dropdown-menu')容易碰到页面上其他同名的下拉菜单,给当前菜单加个和资源绑定的标识就好了:dropdownMenu.dataset.resourceId = arg.resource.id; // 关闭时精准查找当前资源的下拉菜单 function closeDropdownMenu(event) { const dropdownMenu = document.querySelector(`.dropdown-menu[data-resource-id="${arg.resource.id}"]`); if (dropdownMenu && !dropdownMenu.contains(event.target)) { dropdownMenu.remove(); document.removeEventListener('click', closeDropdownMenu); } }防止菜单点击时意外关闭
点击下拉选项的时候,会触发document的click事件把菜单关掉,得阻止事件冒泡:dropdownMenu.addEventListener('click', (e) => { e.stopPropagation(); if (e.target.tagName === 'A') { e.preventDefault(); // 这里写你的操作逻辑,比如打印动作或者调用Vue方法 console.log(`资源${arg.resource.name}触发操作:${e.target.textContent}`); } });绑定Vue实例上下文
如果要在回调里调用Vue的方法(比如操作store、触发组件事件),得把Vue的this存好,不然普通函数里的this会变:// 在calendarOptions外面先把Vue实例的this存起来 const vm = this; // 然后在resourceLabelDidMount里用vm代替this resourceLabelDidMount: function(arg) { // ...其他代码 dropdownMenu.addEventListener('click', (e) => { // 比如调用store的action vm.$store.dispatch('calendar/handleResourceAction', { resourceId: arg.resource.id, action: e.target.textContent }); }); }优化菜单定位,避免出界
用event.clientY/clientX定位有时候会让菜单跑到视口外面,改成根据资源标题的位置计算更稳妥:const rect = resourceTitle.getBoundingClientRect(); dropdownMenu.style.top = `${rect.bottom + window.scrollY}px`; dropdownMenu.style.left = `${rect.left + window.scrollX}px`;
更贴合Vue的替代方案:用Vue组件渲染下拉菜单
如果你的项目里已经有Vue的下拉组件(比如Element UI的Dropdown),可以直接动态挂载组件,比原生DOM创建更符合Vue的开发习惯:
import Vue from 'vue'; import ResourceDropdown from './ResourceDropdown.vue'; // 你的自定义下拉组件 resourceLabelDidMount: function(arg) { const resourceTitle = arg.el.querySelector('.fc-scrollgrid-sync-inner'); resourceTitle.addEventListener('click', (e) => { e.stopPropagation(); // 创建下拉组件实例 const dropdownInstance = new Vue({ render: h => h(ResourceDropdown, { props: { resource: arg.resource }, on: { // 组件内部触发关闭时销毁实例 close: () => { dropdownInstance.$destroy(); dropdownInstance.$el.remove(); }, // 接收组件传递的动作事件 action: (actionType) => { console.log(`资源${arg.resource.name}执行动作:${actionType}`); } } }) }); // 挂载组件到body const dropdownEl = dropdownInstance.$mount().$el; // 定位到资源标题下方 const rect = resourceTitle.getBoundingClientRect(); dropdownEl.style.position = 'absolute'; dropdownEl.style.top = `${rect.bottom + window.scrollY}px`; dropdownEl.style.left = `${rect.left + window.scrollX}px`; document.body.appendChild(dropdownEl); // 点击外部关闭菜单 const closeHandler = (event) => { if (!dropdownEl.contains(event.target)) { dropdownInstance.$destroy(); dropdownEl.remove(); document.removeEventListener('click', closeHandler); } }; document.addEventListener('click', closeHandler); }); }
额外注意点
- 全局的document点击事件一定要在菜单关闭时移除,不然会有内存泄漏;
- 如果资源列是动态更新的(比如新增/删除资源),
resourceLabelDidMount会自动重新触发,不用额外处理; - 不用纠结官方文档有没有配置,FullCalendar的回调就是用来做这种自定义交互的,你的思路完全没问题。
内容的提问来源于stack exchange,提问作者LuFerSYSArch
相关产品推荐
相关产品推荐

