right-click-menu.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. // 右键菜单模块(复制自 client/js/disk/right-click-menu.js)
  2. (() => {
  3. class ResourceManagerRightClickMenu {
  4. constructor(options = {}) {
  5. this.target = options.target || document.body;
  6. this.menu = options.menu || null;
  7. this.onAction = options.onAction || (() => {});
  8. this.onBeforeShow = options.onBeforeShow || null;
  9. this.lastRightClickTarget = null;
  10. this.handleContextMenu = this.handleContextMenu.bind(this);
  11. this.handleDocumentClick = this.handleDocumentClick.bind(this);
  12. this.handleKeydown = this.handleKeydown.bind(this);
  13. this.bindEvents();
  14. }
  15. bindEvents() {
  16. if (!this.target || !this.menu) {
  17. console.error('[RightClickMenu] 缺少 target 或 menu 元素', { target: this.target, menu: this.menu });
  18. return;
  19. }
  20. this.target.addEventListener('contextmenu', this.handleContextMenu);
  21. document.addEventListener('click', this.handleDocumentClick);
  22. document.addEventListener('keydown', this.handleKeydown);
  23. this.menu.addEventListener('click', (e) => {
  24. const btn = e.target.closest('[data-action]');
  25. if (!btn) return;
  26. const action = btn.dataset.action;
  27. this.hide();
  28. this.onAction(action, e);
  29. });
  30. }
  31. handleContextMenu(e) {
  32. const editable = e.target.closest('input, textarea');
  33. if (editable) {
  34. return;
  35. }
  36. e.preventDefault();
  37. // 保存右键点击的目标元素
  38. this.lastRightClickTarget = e.target;
  39. // 如果有回调,在显示菜单前调用
  40. // 如果回调返回 false,不显示菜单
  41. if (this.onBeforeShow) {
  42. const shouldShow = this.onBeforeShow(e);
  43. if (shouldShow === false) {
  44. return;
  45. }
  46. }
  47. this.show(e.clientX, e.clientY);
  48. }
  49. handleDocumentClick(e) {
  50. // 点击重命名输入框时不隐藏菜单
  51. if (e.target.classList.contains('rename-input')) {
  52. return;
  53. }
  54. if (this.menu && !this.menu.contains(e.target)) {
  55. this.hide();
  56. }
  57. }
  58. handleKeydown(e) {
  59. if (e.key === 'Escape') {
  60. this.hide();
  61. }
  62. }
  63. show(x, y) {
  64. if (!this.menu) return;
  65. this.menu.classList.add('show');
  66. const { innerWidth, innerHeight } = window;
  67. const rect = this.menu.getBoundingClientRect();
  68. let left = x;
  69. let top = y;
  70. if (left + rect.width > innerWidth) {
  71. left = innerWidth - rect.width - 8;
  72. }
  73. if (top + rect.height > innerHeight) {
  74. top = innerHeight - rect.height - 8;
  75. }
  76. this.menu.style.left = `${left}px`;
  77. this.menu.style.top = `${top}px`;
  78. }
  79. hide() {
  80. if (this.menu) {
  81. this.menu.classList.remove('show');
  82. }
  83. }
  84. }
  85. window.ResourceManagerRightClickMenu = ResourceManagerRightClickMenu;
  86. })();